The "low severity" finding
The app had a classic open redirect: /go?next= would bounce
the browser to any URL with no allow-list. Reported alone, this gets
triaged as informational and closed. The interesting part is never the
primitive — it's what you can wire it to.
https://app.target.com/go?next=https://attacker.example/
The leaky OAuth flow
The login flow used OAuth with a redirect_uri that the
authorization server validated only by prefix. The access token
came back in the URL fragment. Two weak checks, individually defensible,
catastrophic together.
The chain
- Start an OAuth flow with
redirect_uripointing at the trusted/goendpoint — it passes the prefix check. - The provider redirects back to
/go?next=with the token in the fragment. - The open redirect forwards the browser — fragment and all — to the attacker's origin.
- Attacker's page reads
location.hash, lifts the token, and replays it for full session access.
Two "won't fix" findings can multiply into a critical. Bug bounty is about the graph, not the node.
The fix
Kill the open redirect with a strict allow-list of internal paths. Enforce
exact redirect_uri matching on the authorization server, and
deliver tokens via the authorization-code flow with PKCE instead of
returning them in a fragment. Defense in depth means any one of these
breaks the chain.
Takeaway
Don't dismiss low-severity primitives. Catalog them, then go looking for the second bug that turns the pair into something that pays.