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:
-
Victim initiates login: User clicks "Login with Weibo" on your site. The browser redirects to Weibo with a
stateparameter but nocode_challenge. -
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. -
Attacker captures the code: Before the response reaches the victim's browser, Mallory extracts
AUTH_CODE. -
Attacker exchanges for token: Mallory immediately POSTs to
https://api.weibo.com/oauth2/access_tokenwith the stolencode,client_id, andclient_secret. Since there's nocode_verifiercheck, Weibo's token endpoint accepts the exchange. -
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
-
Always use PKCE for public clients — Mobile apps, SPAs, and any client where
client_secretcould be exposed must implement PKCE. -
Use PKCE even for confidential clients — Modern best practice (OAuth 2.1 draft) recommends PKCE for all clients as defense-in-depth.
-
Proper entropy for verifiers — Generate at least 32 bytes (43+ characters base64url) of cryptographically secure randomness.
random_bytes(32)meets this requirement. -
Single-use verifiers — Clear
code_verifierfrom session immediately after token exchange to prevent replay attacks. -
Validate
code_challenge_method— Ensure the authorization server supports and validates the S256 method.
Detection Tools
- Static analysis: Rules like
V-002in multi-agent AI scanners detect OAuth flows missing PKCE parameters - Manual code review: Search for OAuth authorization URLs without
code_challengeor token exchanges withoutcode_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
stateparameter does not prevent authorization code interception — it only blocks CSRF during the redirect phase. PKCE is required for cryptographic binding. -
login_weibo.phpnow generatescode_verifierwithrandom_bytes(32)and computes SHA256code_challengefor every authorization request, stored in$_SESSION['oauth_weibo_code_verifier']. -
Token exchange in
http_request()now includescode_verifier— Weibo's token endpoint validates this against the originalcode_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.