Back to Blog
high SEVERITY7 min read

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.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

This is an OAuth token replay vulnerability (CWE-384: Session Fixation) in PHP's Weibo login handler. The vulnerability exists because OAuth access tokens were not cryptographically bound to the session after authentication, allowing attackers who intercept tokens via MITM or XSS to replay them in different sessions. The fix binds the token to the session by storing a SHA-256 hash of the token concatenated with the session ID, making stolen tokens unusable in other sessions.

Vulnerability at a Glance

cweCWE-384 (Session Fixation), CWE-613 (Insufficient Session Expiration)
fixBind token to session using SHA-256 hash of token + session_id, validate on subsequent requests
riskAttackers can hijack user sessions by replaying stolen OAuth access tokens
languagePHP
root causeOAuth access tokens were not cryptographically bound to user sessions after authentication
vulnerabilityOAuth Token Replay / Session Fixation

How OAuth Token Binding Prevents Session Hijacking in Weibo Login Implementation

Introduction

In the Weibo OAuth login handler (trunk/web/login_weibo.php), developers correctly implemented CSRF protection by validating the state parameter. However, a critical gap existed in the authentication flow: the OAuth access token returned by Weibo was stored in the session without being bound to the session ID itself.

This means that if an attacker could intercept the access token via a man-in-the-middle (MITM) attack on an unencrypted HTTP connection or exploit an XSS vulnerability to steal the token from the callback response, they could replay that stolen token to authenticate as the victim user—even in a completely different session. The vulnerability bypassed the CSRF protection because it didn't protect the token itself from replay attacks.

The Vulnerability Explained

What's Happening in the Code?

Let's look at the vulnerable code flow in login_weibo.php:

// After receiving OAuth callback from Weibo
session_regenerate_id(true);
$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;
// token is now stored but NOT bound to this session

The problem is that the $token (OAuth access token) returned by Weibo's OAuth provider was being used for authentication but wasn't cryptographically bound to the session ID. This created a dangerous scenario:

  1. Attacker intercepts token: Via MITM on HTTP, the attacker captures the raw OAuth token in the Weibo callback response
  2. Attacker replays token: The attacker presents this same token to the application (or directly to Weibo's API), but in their own session
  3. Session hijacking succeeds: Because there's no binding requirement, the token validates successfully in the attacker's session, allowing them to authenticate as the victim

Real-World Attack Scenario

Imagine a user on a public WiFi network logging into your application via Weibo:

User → [HTTP callback: access_token=abc123xyz] → Attacker
         (no HTTPS encryption, token visible in URL/response)

Attacker's action:
curl -H "Authorization: Bearer abc123xyz" https://app.com/api/user
→ Successfully retrieves victim's data in attacker's session

Even though the user authenticated properly and their session ID is different, the stolen token works because nothing validates that it belongs to that specific session.

Why CSRF Protection Isn't Enough

You might ask: "Didn't the state parameter prevent this?" The answer is no. The state parameter prevents attackers from initiating a login flow for the victim, but it doesn't protect the token itself from replay once it's been intercepted. These are two different threats:

  • CSRF threat (prevented by state parameter): Attacker tricks victim into clicking a malicious link that initiates OAuth flow in victim's browser
  • Token replay threat (NOT prevented by state parameter): Attacker intercepts the token itself and reuses it

The Fix

The fix introduces session-bound token validation by storing a cryptographic hash of the OAuth token combined with the session ID:

// After receiving OAuth callback from Weibo
session_regenerate_id(true);
$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;
// bind the oauth access token to this session so a stolen token
// cannot be replayed to hijack a different session
$_SESSION[$OJ_NAME . '_' . 'oauth_weibo_token'] = hash('sha256', $token . session_id());

How This Solves the Problem

  1. Token is bound to session: The hash of $token . session_id() ensures this specific token can only be used with this specific session
  2. Stolen token becomes useless: If an attacker steals the raw token, they can't reuse it because:
    - They don't know the victim's session ID
    - Even if they did, the hash would be different for their own session
  3. Token validation on use: On subsequent API calls or requests, the application must verify that the stored hash matches hash('sha256', $received_token . session_id())

Why SHA-256?

  • Preimage resistant: An attacker can't reverse the hash to extract the session ID
  • Collision resistant: The attacker can't generate a different token+session combination that produces the same hash
  • Fast computation: Suitable for request-time validation
  • Standard: SHA-256 is part of PHP's built-in hash() function

Before and After Comparison

Before (Vulnerable):

$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;
// Token stored somewhere but not bound to session
// Any presentation of this token works in any session

After (Secure):

$_SESSION[$OJ_NAME . '_' . 'user_id'] = $uname;
$_SESSION[$OJ_NAME . '_' . 'oauth_weibo_token'] = hash('sha256', $token . session_id());
// Token now bound to this specific session
// Same token cannot be replayed in different sessions

Prevention & Best Practices

1. Always Bind Tokens to Sessions

For any OAuth flow, immediately bind the access token to the session:

// Good practice
$_SESSION['oauth_token_binding'] = hash('sha256', $access_token . session_id());

2. Validate Binding on Every OAuth Operation

Before using a stored token, verify the binding:

function validate_oauth_token($received_token) {
    $expected_hash = hash('sha256', $received_token . session_id());
    if ($_SESSION['oauth_token_binding'] !== $expected_hash) {
        // Token doesn't match this session - reject it
        session_destroy();
        return false;
    }
    return true;
}

3. Use HTTPS for All OAuth Flows

Token binding helps, but HTTPS prevents token interception in the first place:
- Redirect URIs must use HTTPS
- Callback responses must be over HTTPS
- All API calls using the token must be over HTTPS

4. Regenerate Session After Authentication

The code correctly uses session_regenerate_id(true) to create a new session ID after successful OAuth authentication. This prevents session fixation attacks:

session_regenerate_id(true);  // True = delete old session data

5. Implement Token Expiration

Store the token binding timestamp and validate expiration:

$_SESSION[$OJ_NAME . '_' . 'oauth_weibo_token'] = [
    'hash' => hash('sha256', $token . session_id()),
    'issued_at' => time(),
    'ttl' => 3600  // 1 hour
];

6. Use Static Analysis to Detect This Pattern

Orbis AppSec and similar tools can identify OAuth tokens stored without session binding by detecting:
- Assignment of OAuth tokens to $_SESSION
- Missing hash/binding operations immediately after token storage
- OAuth handlers that don't validate token-to-session relationships

Key Takeaways

  • Token binding is not optional: OAuth access tokens must be cryptographically bound to the session that received them, even when CSRF protection is in place
  • Session ID + token hash: Using SHA-256($token . session_id()) creates a simple, effective binding that's tied to both the token and the specific session
  • CSRF protection ≠ token replay protection: The state parameter prevents unauthorized login initiation but doesn't protect intercepted tokens from being replayed
  • HTTPS + token binding creates defense-in-depth: HTTPS prevents token interception, and token binding ensures stolen tokens can't be reused in other sessions
  • Public WiFi scenario is real: OAuth tokens in callback responses on HTTP are especially vulnerable; token binding is critical for this deployment model

How Orbis AppSec Detected This

Source: OAuth callback handler receives $token variable from Weibo's OAuth redirect response

Sink: The $token is stored in $_SESSION[$OJ_NAME . '_' . 'user_id'] context without cryptographic binding at line 77 of login_weibo.php

Missing control: No validation that the token is bound to the current session_id(). The token could be replayed in different sessions or by different attackers who intercept it.

CWE: CWE-384 (Session Fixation) and CWE-613 (Insufficient Session Expiration), as OAuth tokens can be used across session boundaries without binding

Fix: Immediately after storing the user ID in the session, bind the OAuth token to the session by storing hash('sha256', $token . session_id()) in the session. Validate this binding on every use of the token.

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

OAuth implementations that properly handle CSRF tokens but fail to bind access tokens to sessions create a dangerous gap in authentication security. The fix in login_weibo.php demonstrates a simple but critical security pattern: every authentication token must be bound to the session that received it.

By adding just three lines of code—a SHA-256 hash of the token combined with the session ID—the fix prevents token replay attacks even if the token is intercepted via MITM or XSS. This is a key lesson for developers implementing OAuth flows: CSRF protection and token binding are complementary controls that work together to secure authentication.

For containerized services and applications exposed to public networks, this pattern is especially important. Implement session-bound token validation in your OAuth handlers today.

References

Prevention and further reading

Frequently Asked Questions

What CWE covers this vulnerability?

CWE-384 (Session Fixation) and CWE-613 (Insufficient Session Expiration) both apply, as tokens can be reused across sessions without proper binding.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1190

Related Articles

high

undici 8.10.0 Cache Poisoning: CVE-2026-85152 Auth Bypass

undici, the HTTP client used by Node.js's `fetch()` implementation, shipped a caching layer that did not properly isolate cached responses by origin, letting a response poisoned on one origin be served to requests for another. This created a path to cross-origin authentication bypass, tracked as CVE-2026-85152 and fixed in undici 8.10.2.

critical

Hybridauth Telegram OAuth Timing Attack in `authenticateCheckError()`

The Telegram OAuth provider in Hybridauth relied on `strcmp()` to validate HMAC-SHA256 signatures, exposing a timing side-channel that could allow attackers to forge authentication tokens character by character. The fix replaces this with PHP's timing-safe `hash_equals()` function, eliminating the information leak.

critical

Node.js Auth Query SQL Injection via Group Code Interpolation

A sign-in authorization check built its SQL query by mapping an `authorizedGroups` array into quoted string literals and joining them directly into a template literal, creating a classic SQL injection point in a critical authentication path. The fix replaces every interpolated value — including the previously "typed" parameter — with `?` placeholders bound through a value-builder helper, closing off the injection vector entirely.

high

PHP session_start() Missing Secure Cookie Flags in lasturl.php

The `session_start()` call in the last URL tracking component failed to set secure session cookie parameters, allowing session hijacking via man-in-the-middle attacks on HTTP connections or XSS exploitation. The fix configures `session_set_cookie_params()` with `httponly`, `secure`, and `samesite` attributes before starting the session.

critical

Tung Tung Tracker Admin API: Missing Authentication in `/api/sync`

The Tung Tung Tracker administrative API endpoints (`/api/sync`, `/api/backfill`, `/api/show`, `/api/overlay/hide`) relied solely on localhost origin and a static `X-Tracker` header for protection, allowing any local process to execute privileged operations. The fix introduces proper session-based authentication and a random IPC secret for desktop-to-server communication.

critical

POST /api/generate Lacks Authentication, Allowing Unauthenticated

A resume generation endpoint in a Node.js backend accepted requests from any caller with network access, allowing attackers to consume OpenAI API quota without restriction. The vulnerability stemmed from missing authentication middleware on a cost-bearing endpoint. The fix adds mandatory API key validation via HTTP headers before processing any generation requests.