Ce mail provient de l'extérieur, restons vigilants ===================================================================== CERT-Renater Note d'Information No. 2026/VULN815 _____________________________________________________________________ DATE : 04/08/2026 HARDWARE PLATFORM(S): / OPERATING SYSTEM(S): Systems running s9y/serendipity (Composer) versions prior to 2.6.1. ===================================================================== https://blog.s9y.org/archives/299-Serendipity-2.6.1-released!.html https://github.com/s9y/Serendipity/security/advisories/GHSA-v645-243f-jwgh https://github.com/s9y/Serendipity/security/advisories/GHSA-77rw-27c5-4hxm https://github.com/s9y/Serendipity/security/advisories/GHSA-2m48-gjj5-5x86 https://github.com/s9y/Serendipity/security/advisories/GHSA-6c2x-mjmq-vx4q _____________________________________________________________________ Serendipity 2.6.1 released! Serendipity 2.6.1 is a security and bugfix release. It addresses four security issues reported to us and fixes smaller issues that were missed in 2.6.0. Please update to 2.6.1 to get security fixes for a possible attack by duplicating existing usernames (and getting the original [admin] rights), to plug an XSS bug on the search page caused by incorrect escaping of the search term, to block access to some internal network resources when downloading files directly to the media library and to stop a possible open redirect when using the trackexit plugin and its included URL redirect via the blog installation. 2.6.1 also improves the integrated cache by deleting it more often and solving a 404 error in plugin pages caused by it, fixes a PHP 8.3 compatibility issue in the backend comment page and fixes the plugin backend administration on new installs, where a missing default value caused the Javascript to throw an error, blocking the plugin drag'n drop functionality from working. Please see the release page on GitHub for more (technical) details and downloads. Thanks to the reporters and contributors. _____________________________________________________________________ Authentication Context Confusion: Editor Escalates to Admin via Username Collision High onli published GHSA-v645-243f-jwgh Package s9y/serendipity (Composer) Affected versions <= 2.6.0 Patched versions 2.6.1 Description Summary The authentication subsystem separates password validation from session loading into two independent database queries without ensuring both operate on the same user record. Combined with the absence of a UNIQUE constraint on the username column, this allows an authenticated Editor to create a username collision and obtain an Administrator session without knowing the administrator's password. Details When multiple accounts share the same username, serendipity_authenticate_author() successfully validates the password against one matching record but subsequently calls serendipity_load_userdata() using only the username. serendipity_load_userdata() performs a second lookup and loads the first matching record instead of the authenticated one, resulting in authentication context confusion. Because authenticated users can rename their account to an existing administrator username, an Editor can authenticate using their own password while the session is initialized using the Administrator's account data, resulting in privilege escalation. Proof of Concept An authenticated Editor: Changes their username to match an existing Administrator account. Logs in using the duplicated username and their own password. Authentication succeeds against the Editor account. Session initialization loads the Administrator account, granting administrative privileges. Impact CWE-287 – Improper Authentication Privileges Required: Low (authenticated Editor) User Interaction: None An authenticated Editor can obtain full Administrator privileges without knowledge of the Administrator's password, resulting in complete compromise of the Serendipity instance. Severity Severity: High CVSS v3.1: 8.8 (High) Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H Metric Breakdown Metric Value Attack Vector Network Attack Complexity Low Privileges Required Low User Interaction None Scope Unchanged Confidentiality High Integrity High Availability High CVE Assignment CVE-2026-67351 was assigned by VulnCheck. This GitHub Security Advisory documents the same vulnerability and corresponds to the assigned CVE. Users and downstream consumers should reference CVE-2026-67351 when tracking this issue. Link to the CVE-2026-67351 Severity High 8.8/ 10 CVSS v3 base metrics Attack vector Network Attack complexity Low Privileges required Low User interaction None Scope Unchanged Confidentiality High Integrity High Availability High CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H CVE ID No known CVE Weaknesses Weakness CWE-287 Credits @DevVaibhav07 DevVaibhav07 Reporter _____________________________________________________________________ An open redirect vulnerability in `exit.php` allows any unauthenticated attacker to craft a URL that appears to originate from a trusted Serendipity blog domain Moderate onli published GHSA-77rw-27c5-4hxm Package s9y/serendipity (Composer) Affected versions <= 2.6.0 Patched versions 2.6.1 Description Summary An open redirect vulnerability in exit.php allows an unauthenticated attacker to craft a URL that appears to originate from a trusted Serendipity blog domain but redirects visitors to an arbitrary external site. This can be abused for phishing, credential theft, malware delivery, and URL reputation abuse. Affected Component File: exit.php Condition: serendipity_event_trackexits plugin installed with commentredirection = s9y Details When the url parameter is supplied, exit.php decodes a user-controlled Base64 value and redirects to it without validating the destination. If the Track Exits plugin is configured with commentredirection = s9y, any unauthenticated user can trigger a redirect to an arbitrary external URL. Proof of Concept Request: GET /exit.php?url=aHR0cHM6Ly9ldmlsLmNvbQ== HTTP/1.1 Host: TARGET Response: HTTP/1.1 301 Moved Permanently Location: https://evil.com image * image Impact CWE: CWE-601 – URL Redirection to Untrusted Site Authentication: None User Interaction: Victim visits the crafted URL An attacker can abuse the trusted blog domain to redirect victims to attacker-controlled websites, facilitating phishing campaigns, malware distribution, and reputation bypass attacks. CVE was granted by: VulnCheck CVE-2026-67350 Severity Moderate 6.1/ 10 CVSS v3 base metrics Attack vector Network Attack complexity Low Privileges required None User interaction Required Scope Changed Confidentiality Low Integrity Low Availability None CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N CVE ID No known CVE Weaknesses Weakness CWE-601 Credits @DevVaibhav07 DevVaibhav07 Reporter _____________________________________________________________________ SSRF in Serendipity media URL import: filter bypassed via hex IPv4, IPv6, and link-local addresses High onli published GHSA-2m48-gjj5-5x86 Package No package listed Affected versions < 2.6.0 Patched versions 2.6.1 Description Summary An authenticated user with the adminImagesAdd permission (granted to USERLEVEL_EDITOR and above) can make the Serendipity host issue HTTP GET requests to internal services, then retrieve the response body through the public uploads/ directory. The existing serendipity_url_allowed() filter, added in commit cfd75ec8 (Sep 2016) to address SSRF and updated in 26de428c (Nov 2016) to recheck redirects, only inspects dotted-decimal IPv4. Several address forms that PHP's HTTP client happily accepts are not in the deny logic at all. This is not a duplicate of CVE-2016-9752. The 2016 fixes still work against http://127.1/ and 30x redirects (verified). The bypass surface I'm reporting uses parser forms the 2016 author didn't have in mind, including AWS IMDS at 169.254.169.254 which became security-relevant after the original fix. The function has not been modified since November 2016. Details Root cause include/functions.inc.php, lines 1362-1395: function serendipity_url_allowed($url) { global $serendipity; if ($serendipity['allowLocalURL']) return true; $parts = @parse_url($url); if (!is_array($parts) || empty($parts['host'])) return false; $host = trim($parts['host'], '.'); if (preg_match('@^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}...@', $host)) { $ip = $host; } else { $ip = gethostbyname($host); if ($ip === $host) $ip = false; } if ($ip) { $ipparts = array_map('intval', explode('.', $ip)); if ( 127 === $ipparts[0] || 10 === $ipparts[0] || 0 === $ipparts[0] || ( 172 === $ipparts[0] && 16 <= $ipparts[1] && 31 >= $ipparts[1] ) || ( 192 === $ipparts[0] && 168 === $ipparts[1]) ) { return false; } } return true; } Two structural problems: The deny list is incomplete. 169.254.0.0/16 (link-local, AWS/Azure/GCP metadata) and 100.64.0.0/10 (RFC 6598 CGNAT) are not in the integer check. They pass. The deny list is IPv4-only. Any IPv6 literal, including [::1] and [::ffff:127.0.0.1], fails the regex, isn't resolved by gethostbyname to a dotted form, so $ip = false, the deny block is skipped, the function returns true. Hex IPv4 isn't normalized. gethostbyname("0x7f000001") returns the input unchanged on glibc (unlike octal 017700000001 and decimal 2130706433, which it does normalize to 127.0.0.1). So hex slips through while octal and decimal are correctly blocked. The sink at include/admin/images.inc.php:307-336 re-uses the same broken filter after redirects (line 322), so the redirect re-check from CVE-2016-9752 doesn't help here either. Confirmed bypasses Tested against a real HTTP_Request2 instance loaded from the bundled libs, with a local server serving SSRF_SECRET_INTERNAL_DATA on 127.0.0.1:8089. Five baseline cases (127.0.0.1, 10.0.0.1, 172.16.0.1, 192.168.0.1, 0.0.0.0) block correctly. The following bypass the filter: URL Filter Fetch Body saved http://0x7f000001:8089/secret.txt allow OK yes http://0x7f.0x0.0x0.0x1:8089/secret.txt allow OK yes http://[::ffff:127.0.0.1]:8089/secret.txt allow OK yes http://[::1]:8089/secret.txt allow env-dependent (v6 stack) n/a here http://169.254.169.254/latest/meta-data/ allow env-dependent (cloud) n/a here [::1] and 169.254.169.254 pass the filter; whether the fetch lands depends on the host (IPv6 enabled, cloud deployment). Most production Linux installs have ::1 bound, and Serendipity is frequently self-hosted on cloud VMs. PoC Authenticated as any user with adminImagesAdd (editor or higher). One HTTP request: POST /serendipity_admin.php HTTP/1.1 Host: Cookie: Content-Type: application/x-www-form-urlencoded serendipity[adminModule]=images &serendipity[adminAction]=addSelect &serendipity[imageurl]=http://0x7f000001:8089/secret.txt &serendipity[imageimporttype]=link &serendipity[target_filename][1]=loot.txt &serendipity[target_directory][1]= &serendipity[token]= After the request, the attacker fetches https:///uploads/loot.txt to read whatever http://127.0.0.1:8089/secret.txt returned to the server. For local code-only reproduction without standing up a full Serendipity instance, I have a PHP harness that loads the real bundled HTTP_Request2 and the real serendipity_url_allowed() from the audited commit. It demonstrates filter pass + actual fetch + body written to disk in three of the listed cases. Suggested fix Replace the integer deny list with a canonical address validator. PHP's FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE covers RFC1918, loopback, link-local, multicast, and reserved ranges for both IPv4 and IPv6. function serendipity_url_allowed($url) { global $serendipity; if (!empty($serendipity['allowLocalURL'])) return true; $parts = @parse_url($url); if (!is_array($parts) || empty($parts['host'])) return false; if (!in_array($parts['scheme'] ?? '', ['http', 'https'], true)) return false; // Strip brackets from IPv6 host $host = trim($parts['host'], '[].'); // Collect every IP this host resolves to (A and AAAA) $ips = []; if (filter_var($host, FILTER_VALIDATE_IP)) { $ips[] = $host; } else { foreach ((array) @dns_get_record($host, DNS_A | DNS_AAAA) as $r) { if (!empty($r['ip'])) $ips[] = $r['ip']; if (!empty($r['ipv6'])) $ips[] = $r['ipv6']; } } if (empty($ips)) return false; // unresolvable, treat as unsafe foreach ($ips as $ip) { // Rejects private (10/8, 172.16/12, 192.168/16, fc00::/7) and // reserved (loopback, link-local 169.254/16 and fe80::/10, // multicast, IPv4-mapped, etc.) if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { return false; } } return true; } Two additional hardening notes: The effective-URL recheck at images.inc.php:322 runs after the request has already completed. If a redirect chain reaches an internal target, the body has already been read. Either disable redirects (follow_redirects=false) and validate every Location header manually, or perform egress filtering at the network layer. To defeat DNS rebinding, pin the IP that passed validation to the actual socket connect. With pure PHP this is awkward; in practice the cleanest defense is iptables/firewall rules blocking the PHP-FPM user from reaching 169.254.0.0/16 and other sensitive ranges. Impact An editor account can: Read the full HTTP response body of any service reachable from the PHP process at loopback, IPv6 loopback, link-local (169.254.0.0/16), or carrier-grade NAT (100.64.0.0/10). Persist that body as a media file. Since the file lives under the public uploads/ directory and serendipity_checkMediaSize() does not delete non-image responses, the attacker downloads it via a normal URL. On AWS-hosted Serendipity installations, reach the EC2 instance metadata service at 169.254.169.254. If the instance is using IMDSv1, this yields temporary IAM credentials. In a typical multi-author blog setup, the editor role is given to guest contributors. The attacker does not need admin. Severity High 8.5/ 10 CVSS v3 base metrics Attack vector Network Attack complexity Low Privileges required Low User interaction None Scope Changed Confidentiality High Integrity Low Availability None CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N CVE ID No known CVE Weaknesses Weakness CWE-918 Credits @riodrwn riodrwn Reporter _____________________________________________________________________ Reflected XSS in search clean-URL route (/search/) Moderate onli published GHSA-6c2x-mjmq-vx4q Package s9y/serendipity (Composer) Affected versions >= 2.3.5, <= 2.6.0 Patched versions 2.6.1 Description Summary The Serendipity clean-URL search handler reflects the attacker-controlled search term into the page without HTML encoding, because the sanitisation pipeline runs urldecode() after the HTML-encoding step. A single-URL-encoded payload (e.g. %3Csvg…%3E) survives strip_tags() and htmlspecialchars() as inert text, then is decoded back into live HTML and printed into the page. A crafted link executes arbitrary JavaScript in a victim's browser. Details include/functions_routing.inc.php — serveSearch(): // line 198 $serendipity['GET']['searchTerm'] = urldecode( serendipity_specialchars( strip_tags( implode(' ', $search) ) ) ); PoC Executable POC (verified on an up to date instance) https://example.com/search/zz%3Csvg/onload%3Dalert%601%60%3Ezz image Impact A single crafted GET link can execute attacker JavaScript in the victim's authenticated session. Remediation In include/functions_routing.inc.php:198, do not urldecode() after encoding. Decode first, then sanitise: $term = urldecode( implode(' ', $search) ); $serendipity['GET']['searchTerm'] = serendipity_specialchars( strip_tags( $term ) ); Or drop the trailing urldecode() entirely, $search is already URL-decoded by the router. Severity Moderate 6.1/ 10 CVSS v3 base metrics Attack vector Network Attack complexity Low Privileges required None User interaction Required Scope Changed Confidentiality Low Integrity Low Availability None CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N CVE ID No known CVE Weaknesses Weakness CWE-79 Credits @hutsbotnet hutsbotnet Reporter ========================================================= + CERT-RENATER | tel : 01-53-94-20-44 + + 23/25 Rue Daviel | fax : 01-53-94-20-41 + + 75013 Paris | email:cert@support.renater.fr + =========================================================