From 71a1c7310d5113d5814622d1900a98a0d4947a7a Mon Sep 17 00:00:00 2001 From: Rohan Isawe Date: Tue, 1 Sep 2026 14:08:46 -0700 Subject: [PATCH] docs(oauth): clarify code-injection ATO is code-grant-specific and how PKCE affects it Attack technique 4 (leaking codes/tokens through a lax AS) didn't state which grant type it applies to, or how PKCE changes the outcome once a code has leaked -- both real gaps an interviewer would probe. Clarifies: the attack is code-grant-specific (implicit leaks the token itself, no replay step needed, so it's not exempt, just a more direct version); and that the replay succeeds unconditionally against a confidential client (secret lives server-side) but is blocked against a correctly PKCE-bound public client, since the attacker's browser won't hold the matching code_verifier. Ties this back to why RFC 9700 recommends PKCE for confidential clients too, not just public ones -- it's the independent fix for the replay, where redirect_uri re-validation at /token is the fix for the leak. --- docs/14-oauth-oidc.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/14-oauth-oidc.md b/docs/14-oauth-oidc.md index e513907..7729615 100644 --- a/docs/14-oauth-oidc.md +++ b/docs/14-oauth-oidc.md @@ -154,7 +154,11 @@ If the client omits or fails to validate `state`, the OAuth flow has no CSRF tok ### 4. Leaking codes/tokens through a lax AS (the classic ATO) -If the AS mis-validates `redirect_uri`, the attacker CSRFs the victim into an OAuth flow whose code lands at an attacker page. For the code grant the attacker does not even need the secret or token: they replay the stolen code to the *real* `/callback`, and the client completes the exchange and logs the attacker into the victim's account. More secure servers require `redirect_uri` again at `/token` and reject a mismatch (back channel, attacker cannot control it). +Applies to the **code grant** specifically. Implicit has no redeemable artifact to intercept and replay; the access token itself is the thing that leaks, and leaking it grants access immediately with no further step, so implicit is strictly worse here, not exempt. + +If the AS mis-validates `redirect_uri`, the attacker CSRFs the victim into an OAuth flow whose code lands at an attacker page. The attacker then replays that stolen code to the client's *real* `/callback`; the client, not the attacker, performs the `/token` exchange, and the resulting session lands in the attacker's browser under the victim's identity. + +Whether that replay succeeds depends on what the client presents at `/token`. A **confidential client** already holds its `client_secret` server-side, so the exchange completes with nothing extra from the attacker, PKCE or not. A **PKCE-protected public client** only completes it if the attacker's browser also holds the matching `code_verifier`[[9]](#ref9) — normally it does not, since the verifier is generated and stored locally by whichever browser called `/authorize` for that code, not the one that later replays it. PKCE bound correctly (per-attempt, `S256`) closes this specific replay; an AS that doesn't require it, or accepts `code_challenge_method=plain`, or a client that stores the verifier somewhere replayable (a predictable value, shared across tabs) reopens it. That's why RFC 9700 recommends PKCE for confidential clients too[[1]](#ref1), not just public ones: it is the independent fix for the *replay*, where exact `redirect_uri` re-validation at `/token` is the fix for the *leak*. An AS needs both; either alone still leaves the other half of this attack open. ### 5. Flawed scope validation (scope upgrade)