Back to Blog
critical SEVERITY8 min read

How session fixation happens in PHP OAuth login handlers and how to fix it

A session fixation vulnerability in `trunk/web/login_weibo.php` allowed attackers to hijack authenticated user sessions by pre-setting a victim's PHP session ID before they logged in via Weibo OAuth. The fix was a single, critical call to `session_regenerate_id(true)` inserted immediately before assigning the authenticated user's ID to the session. Left unpatched, this vulnerability could have given an attacker full access to any account whose Weibo OAuth login they could observe or influence.

O
By Orbis AppSec
Published July 29, 2026Reviewed July 29, 2026

Answer Summary

This is a session fixation vulnerability (CWE-384) in PHP's Weibo OAuth login handler (`login_weibo.php`). The root cause is that `$_SESSION['user_id']` was set after successful authentication without first calling `session_regenerate_id()`, meaning the pre-authentication session ID persisted into the authenticated session. An attacker who could plant a known session cookie on the victim (via subdomain injection or MITM) could then reuse that same session ID to access the authenticated account. The fix is a single line: call `session_regenerate_id(true)` before assigning any session variables after login succeeds.

Vulnerability at a Glance

cweCWE-384
fixAdded `session_regenerate_id(true)` immediately before the session assignment on line 68
riskAttacker hijacks an authenticated user session after Weibo OAuth login completes
languagePHP
root cause`$_SESSION[user_id]` assigned post-login without regenerating the session ID
vulnerabilitySession Fixation

How Session Fixation Happens in PHP OAuth Login Handlers and How to Fix It

Summary

A session fixation vulnerability in trunk/web/login_weibo.php allowed attackers to hijack authenticated user sessions by pre-setting a victim's PHP session ID before they logged in via Weibo OAuth. The fix was a single, critical call to session_regenerate_id(true) inserted immediately before assigning the authenticated user's ID to the session. Left unpatched, this vulnerability could have given an attacker full access to any account whose Weibo OAuth login they could observe or influence.


Introduction

The login_weibo.php file handles the OAuth callback flow for Weibo-based authentication — one of the most sensitive code paths in any web application. After exchanging the OAuth authorization code for an access token and resolving the user's identity, the handler writes the authenticated username into the PHP session:

$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;
header("Location: ./");

This looks innocuous. But there is a critical missing step: the session ID is never rotated at the moment of privilege escalation. The same session token that existed before the user authenticated continues to exist after they authenticate — and that is the heart of a session fixation vulnerability.


The Vulnerability Explained

What is session fixation?

In a normal session hijacking attack, an adversary must steal a session token the server has already issued to a victim. Session fixation flips this: the attacker plants a known session token on the victim before they log in, then waits. Once the victim completes authentication, the server binds their authenticated identity to the attacker's pre-known token — and the attacker can immediately use that token to access the account.

The vulnerable code (before the fix)

Here is the exact code path in login_weibo.php around line 47–68:

// After OAuth token exchange and user lookup/creation:
pdo_query($sql, $uname, $email, $ip, $password, $nick, $school);

// login it
$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;  // ← VULNERABLE: session ID unchanged
// redirect it
header("Location: ./");

The session variable $OJ_NAME . '_' . 'user_id' is set to $uname (the resolved Weibo username) without any call to session_regenerate_id(). PHP happily reuses the session file and cookie that was created when the unauthenticated user first visited the site.

Step-by-step attack scenario

This is a 3-step attack chain specific to this OAuth handler:

Step 1 — Session planting. The attacker visits the application to obtain a valid (unauthenticated) session ID, for example PHPSESSID=attacker_known_value. They then deliver this session cookie to the victim. Two practical delivery methods apply here:

  • Subdomain cookie injection: If the application runs at oj.example.com and any subdomain (e.g., static.example.com) can be influenced by the attacker, a Set-Cookie response from that subdomain with Domain=.example.com will plant the cookie in the victim's browser.
  • Network-level MITM: On a shared network (coffee shop Wi-Fi, corporate proxy), the attacker intercepts an HTTP response and injects a Set-Cookie header.

Step 2 — Victim authenticates. The victim clicks "Login with Weibo," completes the OAuth flow, and login_weibo.php runs. Because the victim's browser already holds PHPSESSID=attacker_known_value, PHP loads that session. The handler then executes:

$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;

The victim's Weibo username is now stored in the session file identified by attacker_known_value.

Step 3 — Session reuse. The attacker, who has always known attacker_known_value, sends a request with Cookie: PHPSESSID=attacker_known_value. The server reads the session, finds user_id = victim_username, and treats the attacker as the fully authenticated victim.

Real-world impact

  • Full account takeover of any user who completes Weibo OAuth login while the attacker's planted cookie is active.
  • The attacker does not need to know the victim's Weibo credentials.
  • The attack is silent — no failed login attempts, no brute-force signals.
  • In a containerized deployment (as noted in the threat model), if the application is network-exposed, this attack surface is reachable from outside the container network.

The Fix

The repair is minimal but decisive. A single line was inserted at line 68 of login_weibo.php, immediately before the session write:

Before

// login it
$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;
// redirect it
header("Location: ./");

After

// login it
session_regenerate_id(true);  // ← NEW: invalidate pre-auth session, issue fresh ID
$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;
// redirect it
header("Location: ./");

Why this specific change works

session_regenerate_id(true) does two things atomically:

  1. Issues a new session ID and sends a fresh Set-Cookie: PHPSESSID=<new_random_value> header to the client.
  2. Deletes the old session file on the server (the true argument). This is important: without true, the old session file remains on disk, and PHP may still accept the old ID depending on server configuration.

After this call, the attacker's pre-planted PHPSESSID=attacker_known_value is worthless. The server has deleted the corresponding session file, and the victim's browser now holds a brand-new session ID that the attacker does not know. The authenticated user_id is written only into this new session.

The placement matters: session_regenerate_id(true) must be called after the authentication decision is made (so we know the login succeeded) but before any privileged data is written to $_SESSION. This fix gets that ordering exactly right.


Key Takeaways

  • session_regenerate_id(true) is mandatory in every PHP OAuth callback handler — not optional, not a "nice to have." login_weibo.php is a textbook example of what happens when it is omitted.
  • OAuth login handlers are high-value targets because they involve cross-domain redirects, making cookie planting via subdomain injection especially practical.
  • The true argument to session_regenerate_id() matters — without it, the old session file survives on disk and the protection is incomplete.
  • Placement is everything: regenerate the session ID after confirming authentication success but before writing $_SESSION[$OJ_NAME . '_' . 'user_id'].
  • Restricting the session cookie domain to the exact hostname (not a wildcard) removes the subdomain injection delivery vector that makes this attack practical in shared-domain deployments.

How Orbis AppSec Detected This

  • Source: The Weibo OAuth callback delivers an authenticated user identity ($uname) via the OAuth token exchange flow in login_weibo.php.
  • Sink: $_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname at line 68 of trunk/web/login_weibo.php — a privileged session write with no preceding session ID rotation.
  • Missing control: No call to session_regenerate_id() between the authentication decision and the session variable assignment.
  • CWE: CWE-384 — Session Fixation.
  • Fix: Inserted session_regenerate_id(true) on the line immediately preceding the $_SESSION assignment, ensuring the pre-authentication session ID is invalidated and deleted before any authenticated state is stored.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

Session fixation is one of those vulnerabilities that is trivially easy to introduce and trivially easy to fix — but only if you know to look for it. In login_weibo.php, the entire attack surface was a single missing function call. An attacker exploiting this would have needed no knowledge of the victim's credentials, would have left no failed-login traces in logs, and could have achieved full account takeover silently.

The lesson for any developer writing PHP authentication code — whether for OAuth, SAML, password login, or MFA — is to treat session_regenerate_id(true) as a non-negotiable step in the login sequence, as automatic as calling session_start(). Pair it with tight cookie domain scoping, Secure and HttpOnly flags, and automated static analysis, and this entire class of vulnerability becomes effectively unreachable.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1183

Related Articles

high

How OAuth Token Binding Prevents Session Hijacking in Weibo Login Implementation

A critical vulnerability in the Weibo OAuth login implementation allowed attackers to replay stolen access tokens across different user sessions. By binding the OAuth access token to the session ID using cryptographic hashing, the fix ensures that intercepted tokens cannot be reused to hijack other sessions, even if compromised via MITM or XSS attacks.

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

critical

eval() in Async Function Constructor Enables Runtime Escape

The eval.mjs command handler used raw `eval()` to execute JavaScript expressions, creating a critical code injection path if owner credentials are compromised. The fix replaces `eval()` with the `AsyncFunction` constructor and explicitly shadows `process`, `require`, and other runtime globals as parameters, preventing evaluated code from reaching the Node.js runtime even when authentication boundaries fail.

critical

How stored XSS happens in TinyMCE plugins and how to fix it

The snippets plugin's `Main.ts` inserted raw, unsanitized snippet content directly into the TinyMCE editor via `editor.insertContent(snippet.content)`, allowing stored JavaScript payloads saved by any snippet editor to execute in every user's browser. The fix routes snippet content through TinyMCE's own parser and serializer before insertion, stripping dangerous markup while preserving legitimate formatting.

critical

How credential leakage through console logging happens in JavaScript browser extensions and how to fix it

A browser extension's `src/background/credentials.js` printed the full Strava authentication cookie string — including a signed JWT and CloudFront-Signature values — straight into the extension console via `console.debug`. Anyone who could open DevTools on the background page (or any tooling that scraped the console) could copy a live session and impersonate the user. The fix replaces the credential payload in both log statements with `Boolean(credentials)` and strips a realistic-looking JWT out

critical

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.