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
Published September 7, 2026Reviewed 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

Prevention & Best Practices

OAuth 2.0 Security Checklist

  1. Always use PKCE for public clients — Mobile apps, SPAs, and any client where client_secret could be exposed must implement PKCE.

  2. Use PKCE even for confidential clients — Modern best practice (OAuth 2.1 draft) recommends PKCE for all clients as defense-in-depth.

  3. Proper entropy for verifiers — Generate at least 32 bytes (43+ characters base64url) of cryptographically secure randomness. random_bytes(32) meets this requirement.

  4. Single-use verifiers — Clear code_verifier from session immediately after token exchange to prevent replay attacks.

  5. Validate code_challenge_method — Ensure the authorization server supports and validates the S256 method.

Detection Tools

  • Static analysis: Rules like V-002 in multi-agent AI scanners detect OAuth flows missing PKCE parameters
  • Manual code review: Search for OAuth authorization URLs without code_challenge or token exchanges without code_verifier
  • Dynamic testing: Intercept OAuth flows with Burp Suite or OWASP ZAP, verify PKCE parameters are present and validated

Standards & References

  • RFC 7636: Proof Key for Code Exchange by OAuth Public Clients
  • OAuth 2.0 Security Best Current Practice: IETF draft recommending PKCE for all clients
  • OWASP OAuth 2.0 Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/OAuth2_Security_CheatSheet.html

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.

References

Frequently Asked Questions

What is OAuth 2.0 Authorization Code Interception?

An attack where an adversary intercepts an OAuth authorization code and exchanges it for an access token, impersonating the legitimate user. Without PKCE, the token endpoint accepts any valid code without verifying it came from the original client.

How do you prevent Authorization Code Interception in PHP?

Implement PKCE by generating a cryptographically random `code_verifier` (32+ bytes), storing it in session, computing a SHA256 `code_challenge`, sending the challenge in the authorization URL, and validating the verifier during token exchange.

What CWE is OAuth Authorization Code Interception?

CWE-352 (Cross-Site Request Forgery), though it also relates to CWE-287 (Improper Authentication) when lacking cryptographic binding.

Is `state` parameter validation enough to prevent Authorization Code Interception?

No. The `state` parameter only prevents CSRF attacks during the redirect phase. It does not protect against authorization code interception and replay by MITM attackers who can observe the callback.

Can static analysis detect missing PKCE?

Yes. Security scanners can flag OAuth authorization URLs lacking `code_challenge` parameters, or token exchange requests missing `code_verifier` fields, matching patterns like `V-002`.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1189

Related Articles

critical

How Authentication Bypass Happens in Node.js WebSocket Services and How to Fix It

The HousePanel push notification service exposed GET and POST endpoints without any authentication checks, allowing unauthenticated attackers to send arbitrary push notifications to connected smart devices. This critical vulnerability was fixed by implementing mandatory token validation on all protected endpoints, ensuring only authenticated requests can trigger push operations.

critical

How Missing API Authentication Happens in Node.js and How to Fix It

The GitHub API integration in `src/github.mjs` was making unauthenticated requests, subjecting the application to GitHub's strict 60 requests/hour rate limit. This fix adds secure authentication token injection from environment variables using conditional header spreading, enabling authenticated requests with a much higher rate limit (5,000 requests/hour).

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.

high

How Unauthenticated Endpoint Exposure Happens in Node.js and How to Fix It

A high-severity unauthenticated endpoint exposure was discovered in `dep/src/server/index.js`, where the `/--ziko--` route served internal application state (`globalThis.Ziko`) to any network-connected client without any authentication or environment guard. The fix adds a single production environment check that returns a `404` before the sensitive data is ever sent. This kind of "debug route left in production" vulnerability is surprisingly common in Node.js applications and can silently leak c

high

How Missing Authentication on Sensitive Endpoints Happens in Node.js Express APIs and How to Fix It

Four critical endpoints in the Everclaw Key API — `/bootstrap/challenge`, `/bootstrap`, `/verify-xpost`, and `/forget` — lacked authentication checks, allowing any unauthenticated attacker to request bootstrap funds, claim codes, and even trigger GDPR data deletion. The fix adds `x-admin-secret` header validation to each endpoint, matching the pattern already used on the `/api/stats` route.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.