BlogOpen Redirects: The Phishing Vector Hiding in Your Login FlowScan · Site Quality

Open Redirects: The Phishing Vector Hiding in Your Login Flow

OH
Omar Haddad · February 2026 · 9 min read

TL;DR

An open redirect is a humble-looking bug, your app takes a URL from a parameter and sends the user there without checking it, and it is rated low-severity right up until it becomes the centerpiece of a credential-theft campaign. The danger is that the phishing link starts on your trusted domain: https://yourbank.com/login?next=evil.com looks legitimate because the visible host is yours. Cataloged as CWE-601 and folded into Broken Access Control in the OWASP Top 10, open redirects are most dangerous in login and OAuth flows, where they bypass domain-based trust checks. The fix is to never redirect to user-supplied absolute URLs, and a scan finds every parameter that does.

The bug that weaponizes your good reputation

Most vulnerabilities exploit something broken in your code to attack your systems. An open redirect is different and more insidious: it uses your domain's good reputation to attack your users, and the payload runs on someone else's server. That is why it slips through the cracks, it does not crash anything, leak your database, or trip an intrusion detector. It quietly turns your trusted URL into a launchpad, and the only victim is a user who trusted you.

The mechanism is simple. Per the OWASP description of open redirect, the vulnerability exists whenever an application redirects a user to a destination specified in an unvalidated parameter. The attacker crafts a link to your legitimate domain with a redirect parameter pointing at their phishing site:

https://www.trusted-brand.com/login?returnUrl=https://trusted-brand.evil.com/login

# The victim sees "trusted-brand.com" and clicks.
# Your server happily redirects them to the attacker's pixel-perfect
# clone, which harvests the credentials they enter.

The victim does the security thing they were taught: they hover the link, check the domain, and see that it is yours before clicking. The domain is yours. They trust it. They land on a clone, type their password, and the attacker has their credentials, and your brand is what made it work. Email security gateways and link scanners also tend to trust your domain, so the malicious link sails through filters that would block a direct link to the attacker's host.

Why it is rated low, and why that is wrong: in isolation, an open redirect "only" sends a user elsewhere, so scanners and triage often mark it low-severity and it sits in the backlog. But it is rarely used in isolation. It is a force multiplier in phishing and a bypass for the domain-validation checks that protect OAuth and SSO flows. Severity in context is the only severity that matters, and in a login flow the context is severe.

Where open redirects actually live

The parameters that cause open redirects are everywhere, hiding behind innocuous names. The most dangerous live in exactly the flows attackers most want to abuse.

Login and "return to where you were" flows

The classic pattern: a user hits a protected page, gets bounced to /login?next=/account, and after authenticating is sent to next. If next accepts an absolute external URL, an attacker sends a victim to /login?next=https://evil.com. The phishing link is on your login page, the most trusted page you have, the one users are conditioned to enter passwords on. After a real login, the redirect to the attacker's site lands the user somewhere primed to ask for credentials "again."

OAuth and SSO redirect URIs

This is the high-stakes case. OAuth flows pass a redirect_uri, and the authorization server is supposed to validate it against an allowlist. A loose validation, matching a prefix, allowing subdomains, or tolerating a redirect parameter within an allowed URL, lets an attacker capture the authorization code or token and take over the account. The OWASP Unvalidated Redirects cheat sheet calls out exactly this: open redirects let attackers bypass domain-based validation and route security-sensitive flows through your legitimate domain.

Logout, tracking, and marketing parameters

Logout endpoints with a post-logout redirect, click-tracking wrappers (/r?url=...), and campaign parameters all redirect based on input. Each is a candidate for abuse, and the tracking-redirect pattern is especially common and especially overlooked because it is "just marketing plumbing", until it is the link in a phishing email that your own brand vouches for.

The fix: never trust a user-supplied destination

The root cause is treating a parameter as a destination. The fix is to never redirect to a raw user-supplied absolute URL. There are three solid strategies, in order of robustness.

1. Use an allowlist or indirection map (best)

Do not accept URLs at all. Accept a key, and map it server-side to a known-safe destination. The user can never name a destination you did not pre-approve, which makes the entire class of bug impossible rather than merely filtered.

// Indirection: the parameter is a KEY, not a URL
const SAFE_DESTINATIONS = {
  account:  '/account',
  orders:   '/orders',
  settings: '/settings',
};
function safeRedirect(req, res) {
  const dest = SAFE_DESTINATIONS[req.query.next] || '/';  // default home
  res.redirect(dest);   // attacker-supplied URLs are simply impossible
}

2. Allow only same-origin relative paths

If you must accept a path (for deep-linking back to where the user was), accept only relative paths and reject anything that could resolve to another origin. The validation must be airtight against the tricks attackers use to smuggle an absolute URL past a naive check.

function isSafePath(input) {
  // Reject protocol-relative (//evil.com), absolute URLs, and backslash tricks
  if (typeof input !== 'string') return false;
  if (input.startsWith('//') || input.startsWith('/\\')) return false;
  if (/^[a-z][a-z0-9+.-]*:/i.test(input)) return false;   // has a scheme
  if (!input.startsWith('/')) return false;                // must be relative
  // Resolve against our origin and confirm it stayed on our origin
  const url = new URL(input, 'https://example.com');
  return url.origin === 'https://example.com';
}

3. Validate the host against a strict allowlist

If a cross-origin redirect is genuinely required (paying out to a partner, say), parse the URL and confirm the host is an exact match against an allowlist, never a substring or suffix match. endsWith('trusted.com') is broken because evil-trusted.com passes it, and includes('trusted.com') is broken because evil.com?x=trusted.com passes it; require url.hostname === 'trusted.com' after parsing with a real URL parser.

Insight: the common patches are the broken ones. Blocklisting http:// misses //evil.com and https:evil.com and \/\/evil.com. Substring-matching your domain lets evil.com?x=trusted.com through. Decoding once misses double-encoded payloads like %252f%252fevil.com. The only reliable approaches are indirection (a key, not a URL) and strict origin/host equality after proper parsing. If your fix is a regex blocklist, it is probably bypassable.

Why scanners skip it, and why that is a problem

Open redirects fall into a coverage gap. Network and infrastructure scanners do not look at application parameters at all. Many web vulnerability scanners do detect the simplest reflected case, but they routinely miss the dangerous variants: the open redirect nested inside an OAuth redirect_uri, the one reachable only after authentication, the one hidden behind a click-tracking wrapper, or the one that requires a double-encoded payload to trigger. Because the bug is rated low-severity, even when it is found it often sits unremediated in the backlog, until it shows up in a phishing campaign and your brand is suddenly in the security news, fielding takedown requests and reassuring customers.

What you actually need is a scan that enumerates every redirect-capable parameter across your app, including post-auth and OAuth flows, and tests each with the full range of bypass payloads, then ranks findings by where they live. An open redirect on a marketing tracking link is one thing; an open redirect on your login or OAuth flow is a credential-theft kit waiting to be assembled, and the ranking is what tells you which one to fix before lunch versus next sprint.

Verify the fix and keep it closed

Once fixed, lock it down with a regression test that throws the known bypass payloads at every redirect endpoint and asserts they all stay on-origin. New redirect parameters get added constantly, a new login flow, a new partner integration, a new tracking wrapper, and each is a fresh chance to reintroduce the bug, often by a developer who has never heard of CWE-601.

# Regression test: every bypass payload must NOT escape our origin
const PAYLOADS = [
  'https://evil.com', '//evil.com', '/\\evil.com', 'https:evil.com',
  'https://example.com.evil.com', '/%2f%2fevil.com', '/%252f%252fevil.com',
  'javascript:alert(1)', 'http:/\\evil.com', 'https://evil.com',
];
for (const p of PAYLOADS) {
  const res = await fetch(`/login?next=${encodeURIComponent(p)}`, {redirect: 'manual'});
  const loc = res.headers.get('location') || '';
  const dest = new URL(loc, 'https://example.com');
  assert(dest.origin === 'https://example.com',
    `Open redirect via payload: ${p} -> ${loc}`);
}

Run that suite against every redirect endpoint in CI. The payload list is the institutional memory of every bypass trick, so a developer who reintroduces the bug with a naive fix gets a red build instead of a phishing campaign.

The bottom line

Open redirects are the vulnerability that punches above its severity rating because they weaponize your own trusted domain against your users. They are catalogued as CWE-601 and grouped under Broken Access Control in the OWASP Top 10, and they are most dangerous exactly where they are easiest to overlook: login flows and OAuth redirect URIs, where they bypass the domain-based checks that are supposed to keep authentication safe. The fix is to stop trusting user-supplied destinations, use indirection keys, allow only same-origin relative paths, or enforce strict host equality, and the common regex patches are mostly broken. Scan every redirect-capable parameter, including post-auth and OAuth flows, test the full bypass payload set, and gate a regression suite so the bug cannot creep back. Find it before a phishing crew does, because your domain is the part that makes their campaign work.

Find the Gaps Before They Cost You

Scan audits your site for the accessibility, performance, AEO, and security gaps that quietly drain revenue and invite lawsuits, in one pass.

Try Scan Free →
Omar Haddad Omar Haddad writes about AI quality engineering at alt.qa, built by TheWorkCompany.