PST-065: The OAuth token-exchange client follows redirects while carrying the client secret and the code #794

Closed
opened 2026-10-10 10:56:36 +00:00 by tiagoagueda · 1 comment
Owner

Found in the security audit of 2026-10-10 (SECURITY-AUDIT-2026-10-10.md), run against main at c30851b from 8 to 10 October 2026, as PST-065. Severity: Informational.

The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it.


  • Severity: Informational
  • Category: credential-handling / redirect
  • Location: src/postulo/plugins/consent.py (240-247)
  • Found by: SSRF and outbound traffic

Who can do it, and what they need. A provider (or a plugin-declared token endpoint) that answers the token request with a 307/308 redirect to another origin. Today the shipped token URLs are Google's and Microsoft's fixed endpoints (the user-typed Microsoft tenant only alters the path on login.microsoftonline.com), so no practical …

How it works.

  1. exchange() opens http.client() (follow_redirects=True, max 3) and POSTs client_id, client_secret and the grant (code or refresh_token) as form data.
  2. httpx strips only the Authorization header on a cross-origin redirect; the form body is re-sent as-is on 307/308, and on 301/302/303 the method becomes GET with no body.
  3. A redirecting token endpoint would therefore deliver the client secret and the refresh token to the redirect target.

Impact. Credential exposure only if a provider endpoint redirects cross-origin, which the two shipped providers do not. Recorded as a hardening note: an OAuth token endpoint should never be followed.

Verification (verdict: confirmed; severity after verification: Informational). Only 307/308 re-send the body; 301/302/303 become a bodiless GET. Precondition (a cross-origin redirect from the real provider, or a plugin-declared token_url) is not attacker-reachable by a user or a stranger; the user-typed Microsoft tenant only changes the path on login.microsoftonline.com. Remediation: call the token endpoint with follow_redirects=False (http.client(follow_redirects=False)) and treat any 3xx as ConsentFailed.

Suggested remediation. Call http.client(follow_redirects=False) in exchange and treat any 3xx from a token endpoint as a failure.

Found in the security audit of 2026-10-10 (`SECURITY-AUDIT-2026-10-10.md`), run against `main` at `c30851b` from 8 to 10 October 2026, as **PST-065**. Severity: **Informational**. The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it. --- - **Severity:** Informational - **Category:** credential-handling / redirect - **Location:** `src/postulo/plugins/consent.py` (240-247) - **Found by:** SSRF and outbound traffic **Who can do it, and what they need.** A provider (or a plugin-declared token endpoint) that answers the token request with a 307/308 redirect to another origin. Today the shipped token URLs are Google's and Microsoft's fixed endpoints (the user-typed Microsoft tenant only alters the path on login.microsoftonline.com), so no practical … **How it works.** 1. `exchange()` opens `http.client()` (follow_redirects=True, max 3) and POSTs `client_id`, `client_secret` and the grant (`code` or `refresh_token`) as form data. 2. httpx strips only the `Authorization` header on a cross-origin redirect; the form body is re-sent as-is on 307/308, and on 301/302/303 the method becomes GET with no body. 3. A redirecting token endpoint would therefore deliver the client secret and the refresh token to the redirect target. **Impact.** Credential exposure only if a provider endpoint redirects cross-origin, which the two shipped providers do not. Recorded as a hardening note: an OAuth token endpoint should never be followed. **Verification** (verdict: confirmed; severity after verification: Informational). Only 307/308 re-send the body; 301/302/303 become a bodiless GET. Precondition (a cross-origin redirect from the real provider, or a plugin-declared token_url) is not attacker-reachable by a user or a stranger; the user-typed Microsoft tenant only changes the path on login.microsoftonline.com. Remediation: call the token endpoint with follow_redirects=False (http.client(follow_redirects=False)) and treat any 3xx as ConsentFailed. **Suggested remediation.** Call `http.client(follow_redirects=False)` in `exchange` and treat any 3xx from a token endpoint as a failure.
tiagoagueda added this to the 1.0.0 milestone 2026-10-10 10:58:02 +00:00
Author
Owner

Fixed in 85629e928 on main.

Fixed in 85629e928 on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#794
No description provided.