DEV Community

Davi
Davi

Posted on Originally published at blog.mago.team

API Session Fixation Is Three Attacks, Not One — and Rotating Tokens Blocks None of Them

GoFiber CVE-2024-38513 scored CVSS 10.0. The vulnerability was not missing session rotation. The API accepted a session ID supplied by the caller before any authentication took place. Rotation happened after login; the token the attacker already knew was bound from the start.

Post-Login Rotation Is Necessary but Insufficient

Session fixation is a pre-authentication attack by definition (CWE-384: the application does not assign a new session ID after an established authentication event). The attacker plants the identifier before the victim logs in. Any rotation that occurs afterward does not undo what was established before, because the planted ID is what the authenticated session carries.

In classic web applications, the server generates the initial session ID, and the client stores the received value. The REST API variant exposes a different surface. The attacker sends POST /api/init with {"session_id": "known_value"}, the server creates a session with that exact ID, and the victim authenticates against it. The pre-planted session now carries privileges, and the attacker intercepted no traffic.

The distinction that matters: rotation changes what happens after login. It does not change whether the API accepted an external value before that point. These are two separate controls, and most checklists address only the second.

Vector 1 — APIs That Accept Caller-Supplied Session Identifiers

CVE-2024-38513 (GoFiber prior to v2.52.5, CVSS 10.0) is the definitive example of this vector. The session middleware created sessions from client-supplied values without any origin validation. The server stored whatever ID arrived in the cookie or request body.

The pattern repeats across frameworks. The genid option in express-session defines the ID generation function, but it is commonly misconfigured to return a client-supplied value instead of generating one via CSPRNG. Any custom session store calling store.set(req.body.sessionId, data) replicates the same structural flaw. The pattern GET /api/auth/init?token=X where X is reflected into Set-Cookie is detectable without credentials.

The detection signal is direct: send Cookie: session=known_value on an initialization request and check whether Set-Cookie in the response mirrors that exact value. The fix lives in middleware: the server generates the ID via CSPRNG, and any externally supplied value is rejected before reaching session logic.

Vector 2 — Subdomain Takeover Enables Parent-Domain Cookie Injection

An attacker who controls any subdomain of api.target.com can set a session cookie scoped to .target.com. No application-layer vulnerability needs to be exploited.

FlowFixation (Tenable, 2024) demonstrated this against AWS MWAA Apache Airflow. The *.amazonaws.com domain is shared across all AWS customers. An abandoned subdomain pointing to a deprovisioned AWS resource let the attacker host content on attacker.region.airflow.amazonaws.com and set Set-Cookie: session=known_value; Domain=.amazonaws.com. Any victim visiting any subdomain under .amazonaws.com would send that cookie to the MWAA endpoints.

CVE-2023-40273 (Apache Airflow, HackerOne #2121960) completes the chain: the session was not invalidated after a password change, extending the attack window after the cookie was planted. Apache patched CVE-2023-40273 in Airflow 2.7.0. AWS separately scoped the MWAA session cookie to the exact service hostname.

The browser sends the cookie to every subdomain within the Domain attribute's scope. Remediation requires Domain set to the exact hostname without a leading dot. Quarterly audits of dangling CNAME records pointing to deprovisioned assets eliminate the precondition. The OWASP Subdomain Takeover Prevention Cheat Sheet documents this cookie tossing vector explicitly.

Vector 3 — OAuth State Fixation: Session Donation

The OAuth state parameter is not just CSRF protection. It is the mechanism that binds the authorization code returned by the authorization server to the specific session that initiated the flow. When that binding fails, the attacker receives the authorization code intended for the victim.

HackerOne #233379 (Mixmax) documents the vector. The attacker completes their own Google OAuth flow and captures the final callback URL containing state=attacker_value and the attacker's own authorization code. The attacker sends that URL to the victim. The victim visits the Mixmax callback endpoint and is logged in as the attacker. Session donation: the attacker donates their identity to the victim.

HackerOne #2688 (Slack) reached the same outcome through the complete absence of the state parameter. CVE-2024-7341 (Keycloak SAML, CVSS 7.1) replicates the pattern in federated authentication. JSESSIONID was not rotated after SAML login, leaving the pre-authentication session reusable.

The state must be a single-use CSPRNG nonce stored server-side, bound to the pre-authorization session, validated on callback, and deleted after first use.

Detection — Fingerprinting APIs That Accept Client-Supplied Session Identifiers

3 probes without credentials cover all 3 vectors.

Session ID mirror test: send Cookie: session=mago-probe-known-value on an initialization request (routes like /api/auth/session, /api/v*/init, /api/token). If Set-Cookie in the response contains the same value, the endpoint accepts external IDs. No prior authentication is required.

Subdomain and dangling CNAME enumeration: DNS sweep for CNAME records pointing to deprovisioned cloud assets (S3, GitHub Pages, Heroku, Netlify). Any claimable subdomain within *.target.com is a concrete precondition for Vector 2. Burp Sequencer measures session token entropy for complementary detection.

OAuth state entropy probe: initiate the OAuth flow, extract the state parameter, and verify presence, minimum 128-bit entropy, and variation across distinct flows. Replay a known state on the callback to test whether the server validates the binding to the originating session. OWASP WSTG-SESS-03 covers this test case formally.

The MAGO Intel tool (intel.mago.team) surfaces APIs accepting client-supplied session identifiers via automated mirror testing at scale, with output structured by endpoint and identified vector.

Defense: Four Controls, One Per Vector

Vector 1: the server generates session IDs via CSPRNG and rejects any external value in middleware before any session logic executes. No value from request body, query string, or cookie not issued by this server is accepted. GoFiber 2.52.5 adds server-side ID validation; Spring Security auto-rotates on authentication; Django always rotates.

Vector 2: the Domain cookie attribute targets the exact hostname without a leading dot. Quarterly CNAME audits remove dangling records before third parties claim them. SameSite=Strict or SameSite=Lax on all session cookies reduces cross-origin attack surface.

Vector 3: the OAuth state is a CSPRNG nonce stored server-side, bound to the session that initiated the flow, validated on callback, and deleted after first use. The absence of state is not a simplification; it is a documented security failure.

General: pre-authentication sessions are invalidated on login, regardless of whether the existing ID is considered "valid." The correct gate is not whether this API rotates after login. It is whether this API ever accepts a session identifier it did not generate.

Top comments (0)