# Loose OAuth redirect_uri validation

Check redirect_uri is exact-parsed and bound to the code, not wildcard, prefix, or unanchored regex matched.

_Area: Web authentication and account takeover. Seen in 3 disclosed reports; one confirmed bounty: $4,000; latest 2026-10-30. Updated 2026-11-07._

## How it works
The OAuth client trusts a redirect_uri it should not. The authorization server must send the code or token only to a destination the client owner proved it controls, matched exactly. When the check is a wildcard, a prefix, a subdirectory rule, or an unanchored regex, an attacker crafts a URL that passes the check but points at their own host, and the victim's browser delivers the credential there. A second assumption often breaks too: that the token endpoint binds the code to the same redirect_uri used at authorize time. If it does not, a code obtained under one redirect can be redeemed under another [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md).

## Where it shows up
- redirect_uri allowlists using `*`, path/query wildcards, or `https://host/login?*` patterns [E2](https://bugbountylibrary.com/r/h1%3A202781.md)
- Subdirectory-style validation that can be escaped with `/../` path traversal [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md)
- Regex host checks run against `new URL(x).origin` without anchors, so a path segment is captured as the host group [E2](https://bugbountylibrary.com/r/h1%3A202781.md)
- Client registration (`POST /integrations`) that accepts a redirect_uri with no ownership proof, no scheme check, allowing loopback, custom schemes, wildcard host/path [E3](https://bugbountylibrary.com/r/h1%3A3955956.md)
- Token endpoint that does not re-check redirect_uri against the authorize request [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md)
- Pre-approved/first-party clients that skip consent and auto-approve any requested scope [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md)
- Internal redirect params (`next_url`, `return_to`) and Referer-based logout redirects that can bounce a token onward [E2](https://bugbountylibrary.com/r/h1%3A202781.md)

## How to review
State the invariant: the redirect_uri at authorize time must exactly equal a value the client owner has proven control of, and the token endpoint must require the same value. Check that matching is a parsed, normalized exact compare, not substring/regex/wildcard. Check registration separately from authorize: exact match at authorize is useless if the registered URI was never ownership-verified [E3](https://bugbountylibrary.com/r/h1%3A3955956.md). Confirm the code is bound to redirect_uri at redemption [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md). For pre-approved clients, confirm scopes are constrained, not auto-granted [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md).

## How finders spotted it
- Read the OAuth docs, saw redirect_uri must be a subdirectory of the callback, tried `/../` traversal; then tested whether get-token enforced redirect_uri binding and found it did not [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md)
- Enumerated pre-approved OAuth clients on the Applications page, picked Gist, hunted a Referer-leaking vector, bypassed Camo with `///host` after recalling Ruby-vs-browser URL parsing differences, and decoded the `_gist_session` cookie out of curiosity [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md)
- Noticed the Facebook OAuth app's permissive `https://auth.uber.com/login?*` pattern, then probed whether `next_url` could chain further, and found logout redirects on Referer [E2](https://bugbountylibrary.com/r/h1%3A202781.md)
- Reviewed the AS registration flow, noticed `POST /integrations` does no ownership proof, then tested authorize-time behavior across grant types and redirect shapes [E3](https://bugbountylibrary.com/r/h1%3A3955956.md)

## Variants seen
- Path traversal (`/../`) bypassing subdirectory validation [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md)
- Wildcard path/query in redirect_uri plus internal redirect params [E2](https://bugbountylibrary.com/r/h1%3A202781.md)
- Unverified registration plus implicit grant delivering token to attacker redirect [E3](https://bugbountylibrary.com/r/h1%3A3955956.md)

## Impact and severity
Leaked authorization codes and access tokens leading to account takeover and private data access: repo-scoped token exposing private repositories [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md); FB OAuth token enabling account takeover [E2](https://bugbountylibrary.com/r/h1%3A202781.md); victim-bound token reading, renaming, deleting Basecamp projects [E3](https://bugbountylibrary.com/r/h1%3A3955956.md). Impact rose through chaining several low-severity redirect weaknesses into one exfiltration URL [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md)[E2](https://bugbountylibrary.com/r/h1%3A202781.md), and through scope escalation on pre-approved clients [E1](https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md).

## Seen in real software
Open-source packages and projects, and products, where this pattern was found. If the target runs one, compare the version it runs with the fix.
- **GitHub** (OAuth authorize/get-token endpoints and Gist client) (version not stated): How I hacked Github again.. Report: https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Ahomakov-blogspot-com-2014-02-how-i-hacked-github-again-html.md
- E2: https://bugbountylibrary.com/r/h1%3A202781.md
- E3: https://bugbountylibrary.com/r/h1%3A3955956.md
