Open Redirect
Also known as: Unvalidated redirect, Unvalidated redirects and forwards, URL redirection to untrusted site
A web flaw in which an endpoint sends the browser to a destination taken from a user-supplied parameter without validating it, so a link on a trusted domain can lead users to an attacker-controlled site.
How it works
An open redirect exists when an application accepts a destination from the request, often a parameter named next, returnUrl or redirect, and sends the browser there without checking it. The application itself is not breached. Instead its good name is borrowed: the link begins with a domain the user trusts, so the user and many filters treat it as safe.
The usual setting is a login or logout flow that returns the user to the page they came from. The developer wants convenience and forgets that the value is attacker-influenced. Anything that can place a value in that parameter, such as a link in an email or a message, can choose where the browser lands next.
The harm is indirect but real. A convincing link on a legitimate domain makes phishing more believable and can slip past link reputation checks, and in some flows a redirect can also help leak tokens carried in a URL. Because the impact depends on what the destination page does, the flaw is rated lower than injection, yet it is common and cheap to fix.
The defence is to stop treating the parameter as a URL. Prefer destinations the server decides: a map from short tokens to known pages, an allowlist of permitted hosts, or a rule that only relative same-origin paths are accepted. When a link to an external site is genuinely needed, show an interstitial that names the destination and asks the user to confirm.
Walk through it
- 1Review the redirect handler
- 2Name the missing control
- 3Decide what to accept
- Write the validation
- Verify and monitor the fix
A pre-release review flags the sign-in flow. After login the portal sends the user back to the page they asked for. Read the handler as a reviewer and note where the destination comes from and what is checked.
1// GET /login/done?next=<where to go after sign-in>2router.get("/login/done", requireSession, (req, res) => {3 const next = req.query.next;4 res.redirect(next || "/home");5});Spot it
- Redirect-style parameters (next, returnUrl, redirect, url) whose values point to a host other than your own.
- Responses with a 3xx status whose Location header names an external domain right after a request to a login or logout path.
- Spikes of requests to a single redirect endpoint with many different off-domain destination values.
- Referrers on your redirect endpoint coming from email or messaging domains rather than your own pages.
- Code review findings where a request value flows into a redirect call with no validation step.
Application access log
ts=2026-10-11T08:02:11Z method=GET path=/login/done user=u_1093 status=302 next=https://unrelated-site.example/welcome location=https://unrelated-site.example/welcome
ts=2026-10-11T08:03:40Z method=GET path=/login/done user=u_1093 status=302 next=/courses/intro location=/courses/introWAF
ts=2026-10-11T08:02:11Z rule=external-redirect-param action=log host=portal.contoso-learning.example path=/login/done param=next dest_host=unrelated-site.examplesplSplunk: redirects to a host outside your domain
index=app sourcetype=access status IN (301,302,303,307,308)
| rex field=location "^https?://(?<dest>[^/]+)"
| where isnotnull(dest) AND NOT match(dest, "(^|\.)contoso-learning\.example$")
| stats count by path, destAllow-list legitimate partner hosts before alerting to cut noise.
grepgrep: redirect parameters carrying an absolute URL
grep -E 'path=/login/done .*next=https?://' access.logStop it
Never redirect to a raw user-supplied URL
Treat the destination as untrusted. Prefer values the server controls, such as a token that maps to a known page, so the request never carries a full URL to begin with.
Allow only permitted destinations
Accept only relative same-origin paths, or compare the parsed host against an explicit allowlist. Parse the URL with a real parser and compare the host exactly; do not use substring or prefix checks.
Warn before leaving your site
When an external destination is genuinely required, show an interstitial page that states the destination and asks the user to confirm, and keep the default fallback a safe internal page.
Map tokens to known pages and fall back safely
Vulnerable
router.get("/login/done", requireSession, (req, res) => {
res.redirect(req.query.next || "/home");
});Hardened
const DEST = { courses: "/courses", profile: "/profile", home: "/home" };
router.get("/login/done", requireSession, (req, res) => {
const key = String(req.query.next ?? "");
res.redirect(DEST[key] ?? "/home");
});
// Where a path must be accepted, allow only a single leading slash.
function isLocalPath(v: string): boolean {
return v.startsWith("/") && !v.startsWith("//") && !v.includes("\\");
}A token map is the strongest form. If paths are accepted, validate with a parser and keep the fallback internal.
- Replace free-form destination parameters with tokens that map to known pages wherever possible.
- Where a URL is accepted, parse it and compare the host against an explicit allowlist using exact matches.
- Reject values that start with two slashes or contain backslashes, which browsers can read as another host.
- Show an interstitial naming the destination before sending users to any external site.
- Add a code review checklist item for every redirect call that takes input from the request.
- Log rejected destinations so attempts show up in monitoring.
If it already happened
Ship the allowlist or token map, or temporarily disable the destination parameter, and ask your email security team to flag links to the affected endpoint.
Search every redirect call in the codebase for unvalidated request input, fix each one, and remove old links that relied on free-form destinations.
Notify users who received suspicious links, confirm the fix with tests that submit absolute, double-slash and backslash values, and watch for rejected destinations.
Provide a shared safe-redirect helper, add a lint or review rule for raw redirects, and add the log detection above.
Check yourself
1. Why is an open redirect on a trusted domain useful for phishing?
2. Which fix is the strongest for a post-login return flow?
3. What should happen when a feature genuinely must link to an external site?