Back to Blog
high SEVERITY6 min read

How OAuth 2.0 Authorization Code Interception happens in PHP and how to fix it

The Weibo OAuth login implementation in `trunk/web/login_weibo.php` was missing PKCE (Proof Key for Code Exchange), allowing attackers with network access to exchange intercepted authorization codes for access tokens. The fix adds cryptographic binding between the authorization request and token exchange using SHA256 code challenges.

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

Answer Summary

This is an OAuth 2.0 Authorization Code Interception vulnerability (CWE-352) in PHP's `login_weibo.php`. The Weibo OAuth flow validated `state` for CSRF protection but lacked PKCE, leaving authorization codes vulnerable to MITM interception. The fix adds `code_verifier` generation in the authorization request (line 86-89) and `code_verifier` validation in the token exchange (line 27-28), cryptographically binding the two phases with SHA256 code challenges.

Vulnerability at a Glance

cweCWE-352 (Cross-Site Request Forgery)
fixAdded `code_verifier` generation with SHA256 `code_challenge` in authorization, and `code_verifier` validation in token exchange
riskAttackers with network access can exchange stolen authorization codes for access tokens
languagePHP
root causePKCE extension not implemented—authorization codes lacked cryptographic binding to the original request
vulnerabilityOAuth 2.0 Authorization Code Interception (Missing PKCE)

Introduction

In trunk/web/login_weibo.php, we discovered a high severity OAuth 2.0 implementation flaw that left Weibo authentication vulnerable to authorization code interception. The file handles third-party login via Weibo's OAuth 2.0 service, but the http_request() function and redirect generation logic were missing a critical security extension: PKCE (Proof Key for Code Exchange).

While the code correctly implemented state parameter validation for CSRF protection (lines 22-26), it failed to bind the authorization request cryptographically to the subsequent token exchange. This meant any attacker with network-level access—through compromised WiFi, malicious proxies, or ISP-level interception—could steal an authorization code from the callback and exchange it for a valid access token, completely bypassing the intended user's session.

The Vulnerability Explained

The Missing Cryptographic Binding

OAuth 2.0's authorization code flow is designed for confidential clients, but when used by public clients (or in scenarios where client secrets could be compromised), the authorization code itself becomes a high-value target. Without PKCE, the token endpoint has no way to verify that the entity exchanging the code is the same one who initiated the authorization.

Here's the vulnerable code before the fix:

// Lines 24-35: Token exchange with no code_verifier validation
unset($_SESSION['oauth_weibo_state']);
$code = $_GET['code'];
$GURL = "https://api.weibo.com/oauth2/access_token?";
$vars = array(
    'client_id' => $OJ_WEIBO_AKEY,
    'client_secret' => $OJ_WEIBO_ASEC,
    'grant_type' => 'authorization_code',
    'redirect_uri' => $OJ_WEIBO_CBURL,
    'code' => $code);  // ❌ No code_verifier!
$GURL = $GURL . http_build_query($vars);
$ret = http_request($GURL, True);

And the vulnerable authorization request generation (lines 80-83):

$state = bin2hex(random_bytes(16));
$_SESSION['oauth_weibo_state'] = $state;
$CBURL = "https://api.weibo.com/oauth2/authorize?client_id={$OJ_WEIBO_AKEY}&response_type=code&redirect_uri=$OJ_WEIBO_CBURL&state=" . urlencode($state);
header("Location: " . $CBURL);  // ❌ No code_challenge!

How the Attack Works

Consider this scenario:

  1. Victim initiates login: User clicks "Login with Weibo" on your site. The browser redirects to Weibo with a state parameter but no code_challenge.

  2. Attacker intercepts: Mallory operates a malicious proxy or compromised network node. She observes the callback to login_weibo.php?code=AUTH_CODE&state=VALID_STATE.

  3. Attacker captures the code: Before the response reaches the victim's browser, Mallory extracts AUTH_CODE.

  4. Attacker exchanges for token: Mallory immediately POSTs to https://api.weibo.com/oauth2/access_token with the stolen code, client_id, and client_secret. Since there's no code_verifier check, Weibo's token endpoint accepts the exchange.

  5. Account compromise: Mallory now has a valid access token for the victim's Weibo account, potentially accessing profile data, social graphs, or any scopes granted.

The state parameter doesn't prevent this—it's already validated and consumed. The attack happens after CSRF validation, during the code-to-token exchange where no additional proof exists.

The Fix

The fix implements PKCE with S256 method, adding cryptographic proof that the token exchange originates from the same client that initiated authorization.

Authorization Request Changes (Lines 86-89)

// NEW: Generate code_verifier and code_challenge
$code_verifier = bin2hex(random_bytes(32));
$_SESSION['oauth_weibo_code_verifier'] = $code_verifier;
$code_challenge = rtrim(strtr(base64_encode(hash('sha256', $code_verifier, true)), '+/', '-_'), '=');
$CBURL = "https://api.weibo.com/oauth2/authorize?client_id={$OJ_WEIBO_AKEY}&response_type=code&redirect_uri=$OJ_WEIBO_CBURL&state=" . urlencode($state) . "&code_challenge=$code_challenge&code_challenge_method=S256";

Key additions:
- $code_verifier: 64-character cryptographically random string (32 bytes hex-encoded)
- $code_challenge: Base64url-encoded SHA256 hash of the verifier
- Session storage: Verifier stored server-side, bound to user's session
- S256 method: Explicitly declares SHA256 challenge method per RFC 7636

Token Exchange Changes (Lines 27-28, 36)

// NEW: Retrieve and validate code_verifier
$code_verifier = isset($_SESSION['oauth_weibo_code_verifier']) ? $_SESSION['oauth_weibo_code_verifier'] : '';
unset($_SESSION['oauth_weibo_code_verifier']);  // Single-use, prevent replay

$vars = array(
    'client_id' => $OJ_WEIBO_AKEY,
    'client_secret' => $OJ_WEIBO_ASEC,
    'grant_type' => 'authorization_code',
    'redirect_uri' => $OJ_WEIBO_CBURL,
    'code' => $code,
    'code_verifier' => $code_verifier);  // NEW: Cryptographic proof

Before/After Comparison

Aspect Before (Vulnerable) After (Fixed)
Authorization URL state only state + code_challenge + code_challenge_method=S256
Token request code, client_secret code, client_secret, code_verifier
Session data oauth_weibo_state oauth_weibo_state + oauth_weibo_code_verifier
MITM protection None Cryptographic binding via SHA256

Key Takeaways

  • The state parameter does not prevent authorization code interception — it only blocks CSRF during the redirect phase. PKCE is required for cryptographic binding.

  • login_weibo.php now generates code_verifier with random_bytes(32) and computes SHA256 code_challenge for every authorization request, stored in $_SESSION['oauth_weibo_code_verifier'].

  • Token exchange in http_request() now includes code_verifier — Weibo's token endpoint validates this against the original code_challenge, blocking MITM replay attacks.

  • Always unset sensitive OAuth session data immediately after use — the fix adds unset($_SESSION['oauth_weibo_code_verifier']) to prevent replay attacks.

  • PKCE is now mandatory in OAuth 2.1 — implement it retroactively in existing OAuth 2.0 integrations for defense-in-depth.

How Orbis AppSec Detected This

Source: HTTP request parameters and session data in trunk/web/login_weibo.php

Sink: Token exchange http_request() call at line 31 constructing https://api.weibo.com/oauth2/access_token URL

Missing control: No code_verifier generation in authorization phase, no code_verifier parameter in token exchange request—breaking the cryptographic binding required by PKCE

CWE: CWE-352 (Cross-Site Request Forgery) — though the specific OAuth 2.0 flaw is authorization code interception due to missing PKCE extension

Fix: Added code_verifier generation with random_bytes(32), SHA256 code_challenge computation with base64url encoding, session storage, and code_verifier validation in token exchange

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

The login_weibo.php vulnerability demonstrates how OAuth 2.0 implementations can appear secure—CSRF protection via state—while missing critical protections against network-level attackers. PKCE isn't just a "nice-to-have" for mobile apps; it's essential security infrastructure for any authorization code flow.

By implementing PKCE with S256, the fixed code ensures that even if an attacker intercepts the authorization code, they cannot exchange it without the original code_verifier—a value that never traverses the network and remains bound to the user's session. This is the difference between "probably secure" and "provably secure" in OAuth implementations.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1189

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.