An open redirect vulnerability happens when a web app takes a destination URL from user input and sends the browser there without checking where “there” is. The app means to bounce you back to a page on its own site. Instead an attacker hands it a link that quietly forwards you to a site they control. It looks small. It is the start of phishing, token theft, and server side attacks.
What an open redirect vulnerability actually is
Most apps redirect users all the time. You log in and the app sends you back to the page you were trying to reach. You log out and it returns you to the homepage. To remember where you were headed, the app stashes that destination in a URL parameter. The classic name is next, but url, return, redirect, dest, and continue show up just as often.
The bug is what the app does with that value. If it reads the parameter and redirects to it as is, anyone can set it to any address. The trust you place in the visible domain at the start of the link is the exact thing the attacker borrows.
A concrete example on Acme Notes
Say Acme Notes protects its app behind a login. When you hit a private page while logged out, it sends you to the login screen and remembers your target:
https://acme-notes.example/login?next=/dashboard
After you sign in, the server reads next and forwards you to /dashboard. Useful. Now an attacker crafts a different link:
https://acme-notes.example/login?next=https://acme-n0tes-login.example/steal
The link still begins with the real acme-notes.example domain, so it reads as safe. The victim logs in as normal. Then Acme Notes itself forwards the browser to the attacker page. The user never sees the swap because the trusted domain did the forwarding.
Why an open redirect vulnerability matters
On its own a redirect feels harmless. The damage comes from what it enables.
- Phishing that starts on a trusted domain. A link in an email begins with a name the victim knows. Their eye stops at the first domain. The forward lands them on a fake login page that copies the real one, and they type their password into it.
- OAuth and token theft. When the redirect is chained with a weak
redirect_uricheck in an OAuth flow, the authorization code or access token in the URL can be forwarded straight to an attacker host. The login provider sees a request that looks valid because it started on the real client. - A stepping stone to SSRF. If a server side component follows the redirect instead of a browser, an open redirect can push a backend fetch toward an internal address it should never reach. That turns a client side annoyance into server side request forgery against systems behind the firewall.
An open redirect is rarely the whole attack. It is the trusted first hop that makes the rest of the attack believable.
The vulnerable handler, and a fix
Here is the heart of the problem. A handler that trusts the parameter:
// Vulnerable: redirects to whatever the user supplies
app.get("/login", (req, res) => {
const next = req.query.next || "/dashboard";
// ... authenticate the user ...
return res.redirect(next); // next = "https://evil.example" works fine
});
The fix is to never redirect to raw user input. Treat the parameter as a hint, then map it to a destination you control. The reliable approach is an allowlist of relative paths or known hosts, with absolute and protocol relative URLs rejected outright:
// Fixed: only allow safe, internal, relative paths
const SAFE_PATHS = new Set(["/dashboard", "/settings", "/notes"]);
function safeNext(next) {
if (typeof next !== "string") return "/dashboard";
// Reject absolute URLs: http:, https:, javascript:, data:, mailto:
if (/^[a-z][a-z0-9+.-]*:/i.test(next)) return "/dashboard";
// Reject protocol relative URLs like //evil.example
if (next.startsWith("//")) return "/dashboard";
// Must be a path we recognise
return SAFE_PATHS.has(next) ? next : "/dashboard";
}
app.get("/login", (req, res) => {
// ... authenticate the user ...
return res.redirect(safeNext(req.query.next));
});
If you need to allow more than a fixed set of paths, parse the value and compare its host against an allowlist of hostnames you own. Reject anything that does not match, and always fall back to a safe default rather than to the input.
Why blocklists fail
A common first attempt is to block bad strings. Strip out http:// and https://, or refuse anything containing evil.example. This loses, every time, because the set of ways to write a hostile URL is open ended:
//evil.examplehas no scheme, so a filter looking forhttpmisses it. The browser still treats it as an absolute address.https:/\evil.exampleand backslash tricks get normalised by some browsers into a real redirect.https://acme-notes.example.evil.examplecontains your domain as a substring, so a naive contains check passes it.- URL encoding, double encoding, and whitespace such as
%2F%2Fevil.exampleslip past simple matching.
A blocklist tries to name every bad input. You cannot. An allowlist names the small set of good outputs, which you can. That is the whole reason allowlists win here: you are deciding what is allowed, not guessing at everything that is not. If you want to see how different parsers read the same value, our free URL parser confusion analyzer shows where a host or scheme can disagree and slip past an allowlist check.
How to detect and prevent open redirects
Detection starts with finding every place the app turns user input into a destination.
- Grep for redirect calls. Search the codebase for
redirect,Locationheaders,res.redirect,sendRedirect, and meta refresh tags. For each one, trace the destination back to its source. If the source is a query parameter, form field, or header, you have a candidate. - Watch the usual parameter names. Look at every
next,url,return,returnTo,redirect,continue, anddestin your routes. - Test the obvious payloads. Set the parameter to
https://example.organd to//example.organd see if the browser leaves your domain. If it does, you have an open redirect.
Prevention comes down to a few rules you apply everywhere:
- Never pass raw user input into a redirect.
- Prefer relative paths from a known allowlist. Map a short token or path to a destination instead of carrying a full URL.
- If you must accept hosts, compare against an allowlist of hostnames you own and reject everything else.
- Reject absolute URLs and protocol relative
//evil.examplevalues up front. - Always fall back to a safe default when validation fails, never to the input.
If you want the background on this and related logic bugs, the vulnerability basics category covers the patterns that show up again and again.
Why this bug hides from simple scanners
An open redirect is a logic bug, not a payload. A scanner that fires a list of known strings might catch the simplest case. It tends to miss the redirect that only triggers after login, or the one that needs a specific parameter order, or the chain where the redirect feeds an OAuth flow two steps later. Finding those means understanding what the app is trying to do and where its trust in user input quietly leaks out.
That is the kind of assumption testing an autonomous researcher is built for: tracing a destination from input to redirect, then checking whether the app’s belief about “safe” actually holds. You can read more about that approach on the about page.
Frequently asked questions
Is an open redirect actually a serious vulnerability on its own?
On its own a redirect feels minor, but its value is as the trusted first hop in a larger attack. It makes phishing believable because the link starts on a domain the victim knows, and it can be chained into OAuth token theft or server side request forgery. Treat it as the opening move, not the whole attack.
Why use an allowlist instead of blocking bad redirect URLs?
A blocklist tries to name every hostile input, which is impossible because of forms like //evil.example with no scheme, backslash tricks, and encoded values that slip past simple matching. An allowlist names the small set of good destinations you actually support, which you can define exactly. You are deciding what is allowed rather than guessing at everything that is not.
How can an open redirect lead to server side request forgery?
If a server side component follows the redirect instead of a browser, the open redirect can push a backend fetch toward an internal address it should never reach. That turns a client side annoyance into a request against systems behind the firewall. The PortSwigger Web Security Academy guide on SSRF covers how those internal requests get abused.
Which parameter names commonly hide open redirect bugs?
Watch for next, url, return, returnTo, redirect, continue, and dest. For each one, trace the destination back to its source, and if it comes from a query parameter, form field, or header that flows into a redirect without checks, you have a candidate to test.
