Auth Context Confusion in Serendipity CMS
CVE-2026-67351 is a high-severity authentication context confusion flaw in Serendipity CMS allowing privilege escalation from Editor to Administrator.
Overview
CVE-2026-67351 is an authentication context confusion vulnerability in Serendipity, a PHP-based blogging and content management system, affecting all releases prior to version 2.6.1. The flaw arises from a fundamental architectural defect in the login pipeline: password validation and session hydration are performed as two independent operations that never verify they are operating on the same underlying user record. An attacker who holds a legitimate Editor-level account can exploit this by manufacturing a username that collides with — or is sufficiently close to — an Administrator account identifier, then authenticating with their own valid credentials. The session layer resolves to the Administrator’s record, granting full administrative control.
The vulnerability was identified by security researchers who reported it through Serendipity’s coordinated disclosure process. It is classified CVSS 8.8 (High) under the AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H vector, reflecting that exploitation requires only a valid low-privilege account and no user interaction. Any Serendipity deployment that permits user self-registration, or where an adversary has otherwise obtained Editor credentials, is directly exposed.
Serendipity is deployed across a non-trivial number of independent blogs, community sites, and small enterprise content portals, many of which run infrequently updated versions. The practical blast radius of this vulnerability is therefore broader than the project’s market share might suggest: a single compromised Editor account on a multi-author Serendipity instance is sufficient to achieve complete administrative takeover.
Technical Analysis
The root cause lives in how Serendipity’s authentication subsystem is structured. During login, the application executes two logically separate queries: one to validate the supplied password against the stored credential for a username, and a subsequent one to load the full user session object. In a correctly implemented system these operations would be gated on a single authoritative user identifier — typically a numeric primary key — returned from the credential check. In the vulnerable Serendipity code, the session load instead re-resolves the username through a lookup that is susceptible to normalization or collision, meaning the session can hydrate a different record than the one whose password was verified.
A simplified representation of the vulnerable pattern:
// VULNERABLE — password check and session load use independent lookups
function serendipity_login(string $username, string $password): bool
{
// Step 1: validate credentials against the supplied username
$row = serendipity_db_query(
"SELECT password, authorid FROM {$serendipity['dbPrefix']}authors
WHERE username = '" . serendipity_db_escape_string($username) . "'
LIMIT 1",
true
);
if (empty($row) || !password_verify($password, $row['password'])) {
return false;
}
// Password is valid — now load the session.
// BUG: session is resolved by username string again, not by authorid
// from the credential row. A colliding username resolves to a
// *different* record at this point.
$author = serendipity_fetchAuthorByUsername($username); // ← second lookup
$_SESSION['serendipityAuthedUser'] = $author['username'];
$_SESSION['serendipityAuthedUserType'] = $author['userlevel'];
// userlevel for Administrator is higher than Editor
return true;
}
The attacker registers (or renames) their Editor account to a value that, after any case-folding, whitespace trimming, or Unicode normalization applied inside serendipity_fetchAuthorByUsername, resolves to the Administrator’s username. The password check in step one succeeds against the Editor’s own credential row because that lookup uses the raw input. The session load in step two retrieves the Administrator’s row because the helper function applies different normalization rules, producing a string match against the admin record. The session is then populated with userlevel equal to USERLEVEL_ADMIN, and all subsequent privilege checks pass.
The two-query split is the canonical form of a TOCTOU (Time-of-Check / Time-of-Use) defect applied to identity resolution: the identity checked is not guaranteed to be the identity used.
Impact
A successful exploit grants an authenticated Editor complete administrative control over the Serendipity instance. Concretely, an attacker can:
- Create, modify, or delete any content and user accounts, including removing the legitimate Administrator account or changing its password to maintain persistence.
- Install or modify plugins and themes, which in Serendipity execute arbitrary PHP code — pivoting from content-management access to remote code execution on the host.
- Exfiltrate the full user database, including hashed passwords, email addresses, and any stored personal data, triggering GDPR/CCPA breach notification obligations.
- Leverage server-side access to move laterally within a hosting environment, particularly on shared hosting where filesystem boundaries are weak.
The CVSS High rating is well-justified. Confidentiality, Integrity, and Availability are all rated High because administrative access provides a path to full system compromise. The low attack complexity and absence of required user interaction make this straightforwardly exploitable by any Editor-level account holder.
How to Fix It
Upgrade immediately to Serendipity 2.6.1 or later. If you manage a Serendipity installation manually (the most common deployment model), replace the application files with the patched release from the official repository.
The correct architectural fix binds the session load to the authorid primary key returned during credential validation — never to a re-resolved username string:
// FIXED — session is loaded by primary key from the credential row
function serendipity_login(string $username, string $password): bool
{
$row = serendipity_db_query(
"SELECT authorid, password FROM {$serendipity['dbPrefix']}authors
WHERE username = '" . serendipity_db_escape_string($username) . "'
LIMIT 1",
true
);
if (empty($row) || !password_verify($password, $row['password'])) {
return false;
}
// FIXED: load session by the numeric PK from the *same* credential row.
// No second username resolution — no collision possible.
$author = serendipity_fetchAuthorByID((int) $row['authorid']);
$_SESSION['serendipityAuthedUser'] = $author['username'];
$_SESSION['serendipityAuthedUserType'] = $author['userlevel'];
return true;
}
Additional hardening measures to apply alongside the upgrade:
- Enforce unique, case-insensitive username constraints at the database layer with a normalized unique index, preventing collision registrations from being created in the first place.
- Audit existing usernames for near-collisions against Administrator accounts before upgrading, particularly on instances with open registration.
- Restrict self-registration unless operationally required; reduce the attacker’s ability to choose their own username.
- Rotate Administrator credentials on any instance where Editor-level accounts were held by untrusted parties.
Our Take
Authentication context confusion is an underappreciated vulnerability class. Most developers intuitively think of authentication as a single atomic event, but in practice it is almost always a pipeline with multiple steps — and each step that re-derives identity from a mutable input is a potential injection point for a different identity than the one originally validated. Serendipity’s defect is a textbook example: the code is not obviously broken at any single line, but the composition of two individually reasonable functions produces an insecure whole.
This pattern appears repeatedly in legacy PHP applications that grew organically, where utility functions were written without awareness of how they would be composed in security-critical paths. Enterprises running SAST programs should treat any authentication pipeline where identity resolution occurs more than once — without anchoring on an immutable primary key — as a finding worthy of manual review, regardless of whether a known CVE is attached.
From a defense-in-depth perspective, this case also illustrates why the principle of least privilege for CMS user roles matters operationally: if Editor accounts have no plausible need to exist for untrusted external parties, restricting registration eliminates the precondition for exploitation entirely.
Detection with SAST
This vulnerability class maps to CWE-287 (Improper Authentication) and more specifically to CWE-302 (Authentication Bypass by Assumed-Immutable Data). SAST detection focuses on dataflow analysis across authentication pipelines.
Offensive360’s analysis engine flags the following patterns:
- Split identity resolution: a taint source originating from user-supplied login input that flows into two or more independent database queries within the same authentication function, where the second query is used to populate a session or privilege structure without being gated on the result of the first.
- String-based session hydration post-authentication: session objects populated from a username string lookup rather than from a numeric or UUID primary key returned by the credential-validation query.
- Normalization divergence: helper functions used in session loading that apply case folding,
trim(), or Unicode normalization that are not applied identically in the credential check, creating potential for collision.
The rule category in our platform is AUTH-CONTEXT-SPLIT, and findings are surfaced with a dataflow graph showing the divergence point between the credential-check sink and the session-population sink.
References
Detect this vulnerability class in your codebase
Offensive360 SAST scans your source code for CVE-2026-67351-class vulnerabilities and thousands of other patterns — across 60+ languages.