# Open redirect leaking auth tokens

Redirect and callback params checked by prefix, suffix, case, or path-only, leaking codes and tokens

_Area: Web authentication and account takeover. Seen in 12 disclosed reports; 7 confirmed bounties, median $2,450, range $200-$10,000; latest 2023-05-30. Updated 2026-10-23._

## How it works
A redirect parameter is trusted to name a destination, but the code checks only part of the URL (a prefix, host suffix, scheme, or just the protocol) or nothing. The broken assumption is that "validated redirect" means "destination is on our origin." Partial checks leave a way to reach an attacker-controlled destination while still passing. When that redirect carries an OAuth code, access token, or reset token, the token lands where the attacker can read it. Two adjacent shapes appear: tokens leaked through a redirect chain rather than a classic open redirect [E3](https://bugbountylibrary.com/r/blog%3Amishresec-wordpress-com-2017-10-12-yahoo-bug-bounty-exploiting-oauth-misconfiguration-to-takeover-flickr-accounts.md)[E5](https://bugbountylibrary.com/r/h1%3A1923672.md), and tokens appended to a steerable redirect [E6](https://bugbountylibrary.com/r/h1%3A1327742.md)[E7](https://bugbountylibrary.com/r/blog%3Ar29k-com-articles-bb-ato.md)[E8](https://bugbountylibrary.com/r/h1%3A905607.md).

## Where it shows up
- Redirect/returnURL/RelayState/.done/reply_to/next params validated by prefix, suffix, or substring rather than exact origin match.
- Rails `url_for(only_path: true)` where `:script_name` is attacker-influenced; it is prepended without a leading slash and can shift the host [E1](https://bugbountylibrary.com/r/blog%3Adevcraft-io-2020-10-19-github-gist-account-takeover-html.md).
- Callback-URL locking that accepts path-only or relative values, bypassed with `../` traversal to another path on the trusted host [E2](https://bugbountylibrary.com/r/h1%3A110293.md).
- Redirect handlers that append tokens to the Location header, or accept custom/arbitrary URL schemes.
- OAuth hybrid flows (`response_type=code id_token`) placing codes in the fragment, which survives redirects [E3](https://bugbountylibrary.com/r/blog%3Amishresec-wordpress-com-2017-10-12-yahoo-bug-bounty-exploiting-oauth-misconfiguration-to-takeover-flickr-accounts.md).
- SAML RelayState or OAuth state persisted in cookies/session and later used as a redirect target [E5](https://bugbountylibrary.com/r/h1%3A1923672.md).
- Scheme checks that are case-sensitive (`HTTPS://` vs `https://`) or bypassable by encoding or backslashes.
- Allowlists that include third-party domains the owner does not control or has let expire [E6](https://bugbountylibrary.com/r/h1%3A1327742.md).

## How to review
Find every place a user-supplied URL becomes a redirect target or is stored for later redirect. The invariant: the final destination must be an exact, allowlisted origin and scheme, resolved after all decoding and normalization. Parse the URL, compare scheme+host+port exactly, and reject userinfo, backslashes, case tricks, and path-only values. Confirm tokens/codes are never appended to a redirect the user can steer, and that fragment-carried values cannot survive into a cross-origin destination.

## Variants seen
- Rails `:script_name` host shift with `only_path: true`, leaking Gist OAuth code [E1](https://bugbountylibrary.com/r/blog%3Adevcraft-io-2020-10-19-github-gist-account-takeover-html.md)
- Path-only callback_url passing validation, traversal to a Twitter open redirect leaking the token [E2](https://bugbountylibrary.com/r/h1%3A110293.md)
- Hybrid `code id_token` flow putting the code in the fragment, leaked via a Flickr open redirect (provider-side) [E3](https://bugbountylibrary.com/r/blog%3Amishresec-wordpress-com-2017-10-12-yahoo-bug-bounty-exploiting-oauth-misconfiguration-to-takeover-flickr-accounts.md)
- Loose `reply_to` accepting any parent-domain host, chained with subdomain takeover [E4](https://bugbountylibrary.com/r/blog%3Ablog-assetnote-io-2020-06-29-subdomain-takeover-to-account-takeover.md)
- Unvalidated SAML RelayState persisted in cookies, chained with implicit grant [E5](https://bugbountylibrary.com/r/h1%3A1923672.md)
- Allowlisted but unowned/expired third-party domain receiving the token [E6](https://bugbountylibrary.com/r/h1%3A1327742.md)
- Reset token appended to a steerable `returnURL` when the victim is logged in [E7](https://bugbountylibrary.com/r/blog%3Ar29k-com-articles-bb-ato.md)
- Triple-slash (`host///target`) bypassing a host-prefix check, token appended [E8](https://bugbountylibrary.com/r/h1%3A905607.md)

## Why fixes fail
Prefix and suffix checks pass traversal and lookalike hosts [E2](https://bugbountylibrary.com/r/h1%3A110293.md)[E4](https://bugbountylibrary.com/r/blog%3Ablog-assetnote-io-2020-06-29-subdomain-takeover-to-account-takeover.md). Filters that block only `//` or `://` miss triple-slash and backslash normalization [E8](https://bugbountylibrary.com/r/h1%3A905607.md). Fixes that only block one sink leave persisted RelayState/state values exploitable later [E5](https://bugbountylibrary.com/r/h1%3A1923672.md). Allowlists that keep stale third-party domains stay exploitable even after new domains are blocked [E6](https://bugbountylibrary.com/r/h1%3A1327742.md).

## Impact and severity
Led to account takeover across OAuth, SAML, and SSO flows [E1](https://bugbountylibrary.com/r/blog%3Adevcraft-io-2020-10-19-github-gist-account-takeover-html.md)[E2](https://bugbountylibrary.com/r/h1%3A110293.md)[E3](https://bugbountylibrary.com/r/blog%3Amishresec-wordpress-com-2017-10-12-yahoo-bug-bounty-exploiting-oauth-misconfiguration-to-takeover-flickr-accounts.md)[E4](https://bugbountylibrary.com/r/blog%3Ablog-assetnote-io-2020-06-29-subdomain-takeover-to-account-takeover.md)[E5](https://bugbountylibrary.com/r/h1%3A1923672.md)[E6](https://bugbountylibrary.com/r/h1%3A1327742.md)[E7](https://bugbountylibrary.com/r/blog%3Ar29k-com-articles-bb-ato.md)[E8](https://bugbountylibrary.com/r/h1%3A905607.md). Severity rises when the redirect carries long-lived tokens, when chained with subdomain takeover or implicit grants, and when the token lands in a fragment that survives redirects [E4](https://bugbountylibrary.com/r/blog%3Ablog-assetnote-io-2020-06-29-subdomain-takeover-to-account-takeover.md)[E5](https://bugbountylibrary.com/r/h1%3A1923672.md).

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Adevcraft-io-2020-10-19-github-gist-account-takeover-html.md
- E2: https://bugbountylibrary.com/r/h1%3A110293.md
- E3: https://bugbountylibrary.com/r/blog%3Amishresec-wordpress-com-2017-10-12-yahoo-bug-bounty-exploiting-oauth-misconfiguration-to-takeover-flickr-accounts.md
- E4: https://bugbountylibrary.com/r/blog%3Ablog-assetnote-io-2020-06-29-subdomain-takeover-to-account-takeover.md
- E5: https://bugbountylibrary.com/r/h1%3A1923672.md
- E6: https://bugbountylibrary.com/r/h1%3A1327742.md
- E7: https://bugbountylibrary.com/r/blog%3Ar29k-com-articles-bb-ato.md
- E8: https://bugbountylibrary.com/r/h1%3A905607.md
