Triaging Open Redirect Reports: When Low Severity Is the Right Call
How to triage open redirect reports: why most are low severity, when OAuth and SSO flows make them critical, parser confusion bypasses, and the phishing argument.
Open redirect sits near the top of every bug bounty queue by volume and near the bottom by average payout, and both of those facts are correct. It is trivially discoverable, every scanner tests for it, every "your first bug" tutorial covers it, and on its own it crosses no security boundary inside the application. A user who clicks a link and lands on evil.com sees evil.com in the address bar. Nothing was read, nothing was written, no session was touched.
The reason open redirect still deserves a careful look is that it is a component. In isolation it is a Low or an Informative on most programs. Wired into an OAuth authorization flow, an SSO relay, or a server-side fetcher, it is the piece that turns a validated allowlist into a full account takeover. Triage that closes every redirect report on sight will eventually close a critical.
Confirm the basic mechanics first
A surprising number of "open redirect" reports do not redirect anywhere. Check what actually happens:
GET /login?next=https://evil.com HTTP/1.1
Host: app.example.com
HTTP/1.1 302 Found
Location: https://evil.com
That is a server-side open redirect. Variants that are not:
- The parameter is rendered as a link in the page body. The user has to click a second time and the page shows the destination. This is content injection at best.
- The redirect only accepts same-origin paths.
?next=/dashboardworking while?next=https://evil.comreturns the login page is the control functioning. - The redirect target is a company-controlled subdomain. Not a finding unless that subdomain is takeover-able, which is a different report entirely and covered in our subdomain takeover guide.
- The "redirect" is a
meta refreshorwindow.locationassignment. Still a redirect, but note it, because client-side redirects have different chaining properties than 3xx responses.
That last distinction matters more than it looks. A client-side redirect through location = userInput or location.href = params.get('next') accepts javascript: and data: URIs in ways a Location header does not, because browsers refuse to navigate to javascript: from a 3xx response. If the sink is client-side and the scheme is unfiltered, you are triaging DOM XSS, not open redirect, and it should be rated as XSS.
Also record whether the endpoint is reachable before authentication. A redirect on /login is exploitable against anyone; a redirect on /settings/billing/return requires an authenticated victim and a delivery step.
Where open redirects actually matter
Before you assign a Low, spend a few minutes on these five checks. Each one converts the same bug into something worth several severity bands.
OAuth and OIDC redirect_uri handling. This is the big one. Authorization servers validate redirect_uri against a registered allowlist. If any URL on an allowlisted origin performs an open redirect, the attacker registers redirect_uri=https://app.example.com/r?next=https://evil.com, the authorization server accepts it because the origin matches, and the browser forwards the authorization code straight to the attacker.
GET /oauth/authorize?client_id=abc&response_type=code
&redirect_uri=https://app.example.com/r?next=https://evil.com
&scope=profile+email&state=xyz HTTP/1.1
Host: idp.example.com
With an authorization code and a public client (or a leaked or guessable client secret), that is account takeover. With the implicit flow it is worse: the token arrives in the URL fragment, and browsers preserve the fragment across a 302 when the redirect target has no fragment of its own, so the access token lands on the attacker's server without any code exchange step. PKCE limits the code variant substantially, so check whether the flow enforces it, but do not assume PKCE is present because the provider supports it.
SAML and SSO relay parameters. RelayState and equivalent post-login destination parameters are open redirects by design, and are supposed to be validated. When they are not, an attacker can complete a legitimate SSO handshake and land the victim on a controlled page in a trusted-looking flow, or leak parameters appended to the relay target.
Chaining into SSRF. Server-side fetchers routinely validate the submitted hostname and then follow redirects without revalidating. An open redirect on the target's own domain is often the exact primitive needed to defeat an allowlist that only permits the target's domains. If the product has any URL fetcher, test this combination before rating. The full picture is in our SSRF guide, and the short version is that a redirect chain converts a restricted fetcher into an unrestricted one.
Token leakage via Referer. If sensitive values live in query strings (password-reset tokens, magic-link tokens, invite tokens, session identifiers in legacy flows) and the page performs a redirect to an attacker origin, the Referer header can carry the full originating URL to the attacker. This depends entirely on the Referrer-Policy in force. no-referrer or strict-origin-when-cross-origin kills it; a missing policy on an older stack does not. Check the actual response headers rather than assuming the modern default applies.
CRLF injection in the redirect parameter. If the parameter lands in the Location header unencoded and accepts %0d%0a, you have header injection, which enables response splitting, cookie setting, and sometimes cached poisoning. That is a materially higher-severity finding than the redirect that led you to it.
Parser confusion bypasses
Most redirect validators are string checks, and string checks disagree with URL parsers. When a report claims a bypass of an existing allowlist, the bypass is usually one of these:
//evil.comand///evil.com: protocol-relative, passes a "must start with /" check.https:/\evil.comand/\/\evil.com: browsers normalize backslashes to forward slashes; many validators do not.https://[email protected]: everything before the@is userinfo, so the host isevil.com. Defeats "starts with the allowed origin" checks.https://evil.com#app.example.comandhttps://evil.com?x=app.example.com: defeats "contains the allowed domain" checks.https://app.example.com.evil.com: defeats prefix matching on the domain.https://evil.com/app.example.com: defeats naiveinorcontainschecks./%09/evil.com,/%2f%2fevil.com,/%5cevil.com: tab, encoded slash, and encoded backslash variants that survive one round of decoding.https://evil。comand other unicode dot lookalikes, which some parsers normalize to.during IDNA processing.
The reason these keep working is that the validator and the consumer are different pieces of software. Python's urllib.parse, Java's java.net.URI, Go's net/url, and the WHATWG parser in the browser all differ on userinfo handling, backslashes, and control characters. A report that demonstrates a live bypass of an existing allowlist is more valuable than a report on an endpoint with no validation at all, because it proves the control is broken rather than absent, and the correct fix (a server-side mapping table or a strict allowlist of relative paths, not a regex) is different.
The phishing enabler argument
Every open redirect report that gets pushback eventually arrives at some version of "an attacker can send https://trusted.example.com/?next=https://evil.com and users will trust the link." Most programs reject this, and the reasoning is consistent:
- After the redirect fires, the address bar shows the attacker's domain. The deception ends at the moment the user could actually be harmed.
- Phishing does not require a redirect. Lookalike domains, link shorteners, and compromised sites are all cheaper and more effective.
- No boundary inside the application was crossed, so there is nothing for the vendor to be responsible for beyond removing a minor convenience.
- Accepting the argument implies paying for a bug that exists on a large fraction of the internet, including inside major products that document it as out of scope.
There is a narrow version of the argument that carries more weight, and it is worth recognizing when a reporter makes it well: redirects from a domain that sits on an enterprise URL-filter allowlist, an email gateway allowlist, or an in-product link preview that renders the trusted domain's branding. Those bypass a control that someone deliberately configured. That is still usually a Low, and it is still a real observation rather than a rhetorical one.
What you should not do is close the report with "open redirect is out of scope" if you have not checked the OAuth flow. Scope decisions about the class are fine. Skipping the two-minute check for the chain is where programs lose money.
Common false positives
| Reported as open redirect | Usually is |
|---|---|
| Redirect to a path on the same origin | The control working |
Destination appears as a clickable link, not a Location header |
Content injection at most |
| Redirect target is a company-owned domain | Not a finding without a takeover |
| Requires the victim to already be authenticated and to submit a POST | Real but heavily preconditioned; note it |
Location value is URL-encoded so the browser treats it as a path |
No redirect occurs; test it in a browser, not just in a proxy |
| Redirect fires only in the reporter's proxy because they edited the response | Not a bug |
The encoded-Location case catches people out. Location: /https://evil.com and Location: https%3A%2F%2Fevil.com both look wrong in a raw response and both navigate to a path on the original origin. Confirm in an actual browser before accepting a bypass claim.
Severity reasoning
| Situation | Rough impact |
|---|---|
| Redirect enables OAuth code or token theft leading to account takeover | Critical |
| Redirect defeats a server-side fetcher allowlist, yielding internal reach | High |
| CRLF injection in the redirect parameter (header injection) | High to Medium |
Client-side sink accepting javascript: (DOM XSS) |
Rate as XSS |
Leaks a reset or invite token via Referer with a permissive policy |
Medium |
| Redirect on an SSO relay parameter, no token exposure demonstrated | Low to Medium |
| Plain redirect, no chain, no sensitive parameters in the URL | Low to Informative |
Two habits keep this consistent. First, rate the chain, not the component. If a reporter submits the redirect and the OAuth theft as one report, it is one Critical, not a Low plus a Critical. If they submit only the redirect and you find the OAuth chain during triage, upgrade it and say why, because the researcher found the primitive that made it possible. Second, write the verdict in terms of what is missing rather than the class name. "Low: redirect confirmed on /login?next=, no OAuth flow on this host, no tokens present in the query string" is a verdict a researcher can argue with productively. Blanket class-based scoring is what our severity guide exists to push back on, and open redirect is the clearest example of a class where the label carries almost no information about the number.
Open redirect is the class where fast triage is usually right and occasionally expensive, because the same three-line finding is either noise or the first link in an account takeover. TRIAGERS™ runs on-demand triage teams who check the OAuth flow before assigning the Low. If your queue is drowning in redirect reports, let's talk.