Auditing open redirect and forced navigation: when the app sends the user wherever the URL says
An application constantly sends users onward: back to where they were after login, to a success page after
an action, to an identity provider and back during authorization. When the destination comes from an
untrusted parameter, a stored value, or a header, and the app redirects there without confirming it is one
of its own pages, the app becomes a trusted springboard to an attacker's site. The link starts on the real
domain, so it survives a glance and a mail filter, and lands on a lookalike login that harvests
credentials. Worse, when the redirect carries an authorization code or a token in the URL, forcing the
destination steals that secret outright. The bug is not the redirect feature; it is a destination taken
from the request and trusted without an allowlist. You find it by locating every redirect and navigation
and asking whether an attacker chooses where it goes.
When to use
- A return, next, redirect, callback, or continue parameter controls where a user is sent after an action.
- Client code assigns a location or writes a meta refresh from a value influenced by the request or storage.
- An authorization or single-sign-on flow returns to a URL supplied in the request and carries a code.
Scope check
Test redirect behavior only against applications you own or are authorized to assess, sending the redirect
to a benign destination you control rather than a live phishing page, and never capturing a real user's
token. A confirmed forced redirect that carries a token is credential theft, so keep every probe within the
authorized scope. If you can't name the authorization, stop.
The loop
Establish whether the target is checked against an allowlist first. For each redirect, determine
whether the destination is confirmed to be one of the app's own pages, by an allowlist of paths or hosts
matched exactly, or whether it is used as given. This is the false-positive killer: a redirect that only
ever sends the user to a fixed internal path, or validates the target against an exact allowlist, is not
open, while one that reflects the parameter is. Name the check before crafting a target.
Map every redirect and navigation fed untrusted input. Trace return and next parameters, stored
redirect values, and referer or other headers into server redirects, client-side location assignments,
and meta refreshes, including the return URL of any authorization flow. Each is a candidate sink.
Test the bypasses a naive check misses. A check that requires the target to start with a slash is
beaten by a protocol-relative slash-slash-host and by a backslash the browser treats as a slash; a host
allowlist is beaten by a userinfo at-sign, an embedded credential, a lookalike or encoded host, and
trailing whitespace or control characters. Decide which of these the check as written lets through.
Judge the destination scheme. A redirect that permits a script or data scheme in a client-side
navigation is not only off-site but executes in the current origin; confirm whether the sink restricts to
HTTP and HTTPS or passes any scheme, since the impact differs from a plain off-site send.
Follow what the redirect carries. A plain open redirect is a phishing primitive; a redirect in an
authorization flow carries a code or token in the URL or fragment, so forcing its destination steals that
secret. Determine whether the sink is a bare navigation or one that transports a credential, because that
sets the severity.
Confirm and record. Confirm by supplying a target that resolves to a benign host you control and
observing the app send the user there, and for authorization flows, the code or token arriving at your
host, on an isolated instance. Kill the lead if the target is matched against an exact allowlist of the
app's own pages, if only a fixed internal path is used, or if no untrusted value reaches the redirect.
Record the parameter, the sink, the bypass used, and whether a credential was carried, or set a
kill_reason.
Where forced navigation leaks
- The allowlist is the whole defense. A destination confirmed to be one of the app's own pages cannot be
redirected off-site; a reflected parameter can. The presence and exactness of the allowlist is the finding.
- Starts-with-slash is not enough. A protocol-relative slash-slash-host and a backslash-host both pass a
naive leading-slash check and send the user to another origin.
- Host checks fall to userinfo and lookalikes. An at-sign puts the real host in the userinfo and the
attacker host after it, and an encoded or homoglyph host defeats a substring match; only an exact host
comparison holds.
- Schemes matter on the client. A client-side navigation that accepts a script or data scheme executes in
the origin, turning an open redirect into script execution rather than a mere off-site send.
- Authorization redirects carry secrets. When the redirect transports a code or token, controlling the
destination hands that credential to the attacker, which is far worse than phishing.
Worked example (a confirm and a kill)
Confirm. A login flow reflects a return parameter into the post-login redirect after only checking that
it begins with a slash. A value of backslash-backslash-host slips past the check, and the app sends the
authenticated user, carrying the session-establishing fragment, to a host the tester controls on an
isolated instance. Confirmed open redirect carrying a credential to an attacker host, high,
remediation = validate the return target against an exact allowlist of the app's own paths, reject
protocol-relative, backslash, userinfo, and non-HTTP targets, and never place a code or token on a redirect
whose destination is client-influenced.
Kill. The same flow resolves the return target against an allowlist of the app's own relative paths,
rejects any absolute URL, protocol-relative or backslash form, userinfo, and non-HTTP scheme, and falls
back to a fixed internal path when the target is not on the list. A crafted off-site target is discarded and
the user lands on the default page. Killed, kill_reason = "redirect target is matched against an exact
allowlist of the app's own paths and every off-site or alternate-scheme form is rejected; the destination is
never attacker-chosen and carries no credential off-site."
Rationalizations to reject
- "It only redirects within our site." -> Confirm that with an exact allowlist; a leading-slash or substring
check is bypassed by protocol-relative, backslash, and userinfo forms that leave the site.
- "An open redirect is low severity." -> On an authorization flow it steals the code or token, and anywhere
it is a credible phishing springboard from your trusted domain; rate it by what it carries.
- "We block http and https other hosts." -> Also block protocol-relative and backslash hosts and non-HTTP
schemes; the bypasses live in the forms a naive host check does not parse.
- "The parameter is validated." -> Validated how; a check that the value is a URL or starts with a slash is
not a check that it is one of your pages. Only an exact allowlist answers that.
- "Users can see the address bar." -> The link begins on your domain and the redirect is instant; the user
and the mail filter both trust the starting host, which is the whole point of the abuse.
Executing this in practice
You need every redirect and client-side navigation fed an untrusted target, the check applied to each, and
whether the sink carries a code or token. For each, decide whether an allowlist confines the destination to
the app's own pages or a bypass reaches another origin, and whether the scheme is restricted. Reading the
validation settles most leads; supplying a target that resolves to a benign controlled host, and watching
for a carried credential, on an isolated instance settles the rest.
Related
hunting-crlf-and-response-splitting - the Location header is the shared sink; a redirect target can also
carry a CRLF, so the two analyses meet on the same header.
hunting-host-header-and-url-parsing-trust - the parser differentials and host-confusion tricks that beat a
redirect allowlist are the same ones that skill treats for URL parsing trust.
hunting-unicode-normalization-and-canonicalization-bypass - an encoded or homoglyph host that defeats the
redirect allowlist is a canonicalization failure that skill analyzes directly.
hunting-reflected-and-stored-xss - a client-side navigation that accepts a script or data scheme turns an
open redirect into the script execution that skill hunts.
- FINDING-SCHEMA.md - source = the untrusted target URL, sink = the redirect or
navigation, evidence = the user sent to a benign controlled host, or a carried code or token arriving there,
on an isolated instance.
1---2name: auditing-open-redirect-and-forced-navigation3description: Audit redirect and navigation flows where an untrusted return, next, or callback URL, a stored value, or a referer drives a server redirect, a client-side location assignment, or a meta refresh to an attacker-chosen destination, enabling phishing, credential capture on a lookalike page, or theft of an OAuth code or token carried on the redirect. Use when a parameter or stored value controls where a user is sent after login, logout, an action, or an authorization step. Covers protocol-relative and backslash hosts, userinfo and whitespace tricks, and dangerous schemes that a naive allowlist misses. The untrusted target URL is the source, the redirect or navigation is the sink, and sending the user to an attacker-controlled destination is the bug.4license: MIT5---67# Auditing open redirect and forced navigation: when the app sends the user wherever the URL says89An application constantly sends users onward: back to where they were after login, to a success page after10an action, to an identity provider and back during authorization. When the destination comes from an11untrusted parameter, a stored value, or a header, and the app redirects there without confirming it is one12of its own pages, the app becomes a trusted springboard to an attacker's site. The link starts on the real13domain, so it survives a glance and a mail filter, and lands on a lookalike login that harvests14credentials. Worse, when the redirect carries an authorization code or a token in the URL, forcing the15destination steals that secret outright. The bug is not the redirect feature; it is a destination taken16from the request and trusted without an allowlist. You find it by locating every redirect and navigation17and asking whether an attacker chooses where it goes.1819## When to use2021- A return, next, redirect, callback, or continue parameter controls where a user is sent after an action.22- Client code assigns a location or writes a meta refresh from a value influenced by the request or storage.23- An authorization or single-sign-on flow returns to a URL supplied in the request and carries a code.2425## Scope check2627Test redirect behavior only against applications you own or are authorized to assess, sending the redirect28to a benign destination you control rather than a live phishing page, and never capturing a real user's29token. A confirmed forced redirect that carries a token is credential theft, so keep every probe within the30authorized scope. If you can't name the authorization, stop.3132## The loop33341. **Establish whether the target is checked against an allowlist first.** For each redirect, determine35 whether the destination is confirmed to be one of the app's own pages, by an allowlist of paths or hosts36 matched exactly, or whether it is used as given. This is the false-positive killer: a redirect that only37 ever sends the user to a fixed internal path, or validates the target against an exact allowlist, is not38 open, while one that reflects the parameter is. Name the check before crafting a target.39402. **Map every redirect and navigation fed untrusted input.** Trace return and next parameters, stored41 redirect values, and referer or other headers into server redirects, client-side location assignments,42 and meta refreshes, including the return URL of any authorization flow. Each is a candidate sink.43443. **Test the bypasses a naive check misses.** A check that requires the target to start with a slash is45 beaten by a protocol-relative slash-slash-host and by a backslash the browser treats as a slash; a host46 allowlist is beaten by a userinfo at-sign, an embedded credential, a lookalike or encoded host, and47 trailing whitespace or control characters. Decide which of these the check as written lets through.48494. **Judge the destination scheme.** A redirect that permits a script or data scheme in a client-side50 navigation is not only off-site but executes in the current origin; confirm whether the sink restricts to51 HTTP and HTTPS or passes any scheme, since the impact differs from a plain off-site send.52535. **Follow what the redirect carries.** A plain open redirect is a phishing primitive; a redirect in an54 authorization flow carries a code or token in the URL or fragment, so forcing its destination steals that55 secret. Determine whether the sink is a bare navigation or one that transports a credential, because that56 sets the severity.57586. **Confirm and record.** Confirm by supplying a target that resolves to a benign host you control and59 observing the app send the user there, and for authorization flows, the code or token arriving at your60 host, on an isolated instance. Kill the lead if the target is matched against an exact allowlist of the61 app's own pages, if only a fixed internal path is used, or if no untrusted value reaches the redirect.62 Record the parameter, the sink, the bypass used, and whether a credential was carried, or set a63 `kill_reason`.6465## Where forced navigation leaks6667- **The allowlist is the whole defense.** A destination confirmed to be one of the app's own pages cannot be68 redirected off-site; a reflected parameter can. The presence and exactness of the allowlist is the finding.69- **Starts-with-slash is not enough.** A protocol-relative slash-slash-host and a backslash-host both pass a70 naive leading-slash check and send the user to another origin.71- **Host checks fall to userinfo and lookalikes.** An at-sign puts the real host in the userinfo and the72 attacker host after it, and an encoded or homoglyph host defeats a substring match; only an exact host73 comparison holds.74- **Schemes matter on the client.** A client-side navigation that accepts a script or data scheme executes in75 the origin, turning an open redirect into script execution rather than a mere off-site send.76- **Authorization redirects carry secrets.** When the redirect transports a code or token, controlling the77 destination hands that credential to the attacker, which is far worse than phishing.7879## Worked example (a confirm and a kill)8081> **Confirm.** A login flow reflects a return parameter into the post-login redirect after only checking that82> it begins with a slash. A value of backslash-backslash-host slips past the check, and the app sends the83> authenticated user, carrying the session-establishing fragment, to a host the tester controls on an84> isolated instance. **Confirmed** open redirect carrying a credential to an attacker host, `high`,85> remediation = validate the return target against an exact allowlist of the app's own paths, reject86> protocol-relative, backslash, userinfo, and non-HTTP targets, and never place a code or token on a redirect87> whose destination is client-influenced.88>89> **Kill.** The same flow resolves the return target against an allowlist of the app's own relative paths,90> rejects any absolute URL, protocol-relative or backslash form, userinfo, and non-HTTP scheme, and falls91> back to a fixed internal path when the target is not on the list. A crafted off-site target is discarded and92> the user lands on the default page. **Killed**, `kill_reason` = "redirect target is matched against an exact93> allowlist of the app's own paths and every off-site or alternate-scheme form is rejected; the destination is94> never attacker-chosen and carries no credential off-site."9596## Rationalizations to reject9798- *"It only redirects within our site."* -> Confirm that with an exact allowlist; a leading-slash or substring99 check is bypassed by protocol-relative, backslash, and userinfo forms that leave the site.100- *"An open redirect is low severity."* -> On an authorization flow it steals the code or token, and anywhere101 it is a credible phishing springboard from your trusted domain; rate it by what it carries.102- *"We block http and https other hosts."* -> Also block protocol-relative and backslash hosts and non-HTTP103 schemes; the bypasses live in the forms a naive host check does not parse.104- *"The parameter is validated."* -> Validated how; a check that the value is a URL or starts with a slash is105 not a check that it is one of your pages. Only an exact allowlist answers that.106- *"Users can see the address bar."* -> The link begins on your domain and the redirect is instant; the user107 and the mail filter both trust the starting host, which is the whole point of the abuse.108109## Executing this in practice110111You need every redirect and client-side navigation fed an untrusted target, the check applied to each, and112whether the sink carries a code or token. For each, decide whether an allowlist confines the destination to113the app's own pages or a bypass reaches another origin, and whether the scheme is restricted. Reading the114validation settles most leads; supplying a target that resolves to a benign controlled host, and watching115for a carried credential, on an isolated instance settles the rest.116117## Related118119- `hunting-crlf-and-response-splitting` - the Location header is the shared sink; a redirect target can also120 carry a CRLF, so the two analyses meet on the same header.121- `hunting-host-header-and-url-parsing-trust` - the parser differentials and host-confusion tricks that beat a122 redirect allowlist are the same ones that skill treats for URL parsing trust.123- `hunting-unicode-normalization-and-canonicalization-bypass` - an encoded or homoglyph host that defeats the124 redirect allowlist is a canonicalization failure that skill analyzes directly.125- `hunting-reflected-and-stored-xss` - a client-side navigation that accepts a script or data scheme turns an126 open redirect into the script execution that skill hunts.127- [FINDING-SCHEMA.md](../../FINDING-SCHEMA.md) - source = the untrusted target URL, sink = the redirect or128 navigation, evidence = the user sent to a benign controlled host, or a carried code or token arriving there,129 on an isolated instance.