"Open Redirects Explained: How a Trusted Domain Becomes a Phishing Weapon"

An open redirect is a bug that "just forwards users somewhere else" — which sounds harmless until you consider what makes phishing work: trust in the domain shown in the link. https://login.yourbank.com/redirect?url=https://evil.example doesn't just navigate somewhere else; it puts your domain in the address bar, in the email filter's reputation system, and in the user's muscle memory — and then hands them to the attacker. Browsers, mail providers and EDR tools all evaluate the first hop; the second hop is the user's problem.

This guide covers the mechanics (where redirect parameters come from), the two abuse classes that matter (credential phishing and OAuth token theft), the redirect-chain problems that show up on scans even without a deliberately open endpoint — downgrades, loops, excessive hops — and the fix patterns that don't break legitimate use. The free security scan checks both: it probes common redirect parameters the way an attacker would, and it walks your full redirect chain grading every hop.

Table of contents

  1. Where open redirects come from
  2. Abuse #1: phishing with your reputation
  3. Abuse #2: OAuth token theft
  4. Redirect chains: the quieter problems
  5. The fixes, in order of robustness
  6. How to test your own site
  7. Frequently asked questions

Where open redirects come from

The bug needs one ingredient: the application redirects to a URL it derives from user input without validating it. The patterns are endless because the use cases are legitimate — that's what makes the bug persistent:

// post-login bounce back to where the user came from
http.Redirect(w, r, r.URL.Query().Get("next"), 302)

Real-world variants of the same mistake:

  • ?next= / ?return_to= / ?redirect= / ?callback= on login flows — "return the user to the page they were trying to reach."
  • Logout redirects (?logout_url=...) — added for SSO convenience, forgotten forever.
  • Email click-tracking wrappers — /link?to=... endpoints that log the click then bounce. These are open redirects by design and are a favorite of attackers exactly because they're permanent, stable and on your main domain.
  • Localization/region pickers (?locale_url=...), download mirrors, interstitial "you are leaving our site" pages whose target is user-supplied.
  • Header-based routing — some frameworks honor X-Forwarded-* or Referer for the bounce target; those headers are attacker-controlled on direct requests (see the spoofing caveat in any proxy setup: only trust them from your own reverse proxy).

The validation, when it exists at all, is usually the wrong kind: strings.Contains(url, "example.com") is defeated by https://evil.example.com.attacker.io and https://example.com@attacker.io (userinfo trick), and url.StartsWith("/") is defeated by //attacker.io (protocol-relative) unless the check is StartsWith("/\") && !StartsWith("//"). Redirect validation is a parser problem, not a string problem.

Abuse #1: phishing with your reputation

The phishing chain is one click:

  1. Attacker mass-mails: "Unusual sign-in detected — verify your account: https://example.com/login?next=https://evil.example/verify"
  2. The victim's mail scanner evaluates example.com — legitimate, aged, maybe even DMARC-enforced (see the email security guide). It passes.
  3. The user clicks; your server 302s to https://evil.example/verify; the attacker serves a pixel-perfect copy of your login page.
  4. The user "logs in." The credentials — and any 2FA code they enter on the next screen — belong to the attacker.

Every step exploits a different trust layer: your domain's reputation gets the click, your TLS gets the padlock on the first hop, and user habit ("the link said my bank, so it's my bank") gets the credentials. Open redirects convert all three into attack infrastructure — which is why security scanners, browser Safe Browsing and bug-bounty programs treat them as reportable findings and not cosmetic quirks.

The variant that hurts regulated organizations most is chain laundering: attacker links to a compromised minor subdomain (see subdomain takeover) or an open redirect on a partner site, which redirects to yours, which redirects to the payload. Multi-hop chains defeat reputation systems that only resolve one level deep.

Abuse #2: OAuth token theft

The higher-severity variant targets OAuth/OIDC flows. The authorization-code flow ends with the identity provider redirecting the user's browser to a redirect_uri carrying the code (or, in implicit flows, the token itself, in the URL fragment). If an attacker can influence where that redirect lands, the code lands in their handler — and a stolen code is exchanged for tokens that impersonate the user.

Open redirect parameters interact with this in two ways:

  • Directly, when the app's own redirect allowlist logic is weak: redirect_uri=https://example.com/auth/callback?next=https://evil.example — if the app follows its next parameter after handling the callback, the fragment/code leaks onward. Attackers enumerate exactly these parameters on login and callback paths; our Auth check probes the common ones (/login?redirect=, /signin?return_to=, /oauth?redirect_uri=, /?next=) with a foreign URL and flags any 3xx that lands on it as a High finding.
  • Indirectly, when the identity provider's redirect_uri validation is prefix-matched: registering https://example.com as a client and abusing https://example.com/redirect?to=... to bounce the code. This is the documented mechanism behind real-world OAuth account-takeover chains; the defense lives in your app no matter whose client is abused: have no open redirect, and never follow user-supplied URLs after authentication handling.

The rule that closes the whole class: codes and tokens must only ever arrive at pre-registered, exact-match URIs, and nothing in the application may redirect based on request parameters during or after the exchange.

Redirect chains: the quieter problems

Even with no deliberately open endpoint, redirect chains accumulate problems as sites evolve — the scan walks every hop (following each 301/302/307/308 manually, resolving relative Location values) and grades four properties:

  1. HTTPS → HTTP downgrade (High). A hop that sends users from an https:// URL to an http:// one exposes everything after that point to interception — and since the scanner measures the chain, a single lazy legacy rule buried behind two clean hops is still caught. Fix by making every Location value https:// explicitly.
  2. Redirect to an unexpected external domain (Medium). Legitimate hops land on your own hosts or known partners (SSO providers, regional mirrors); anything else is either a hijack, a misconfigured CDN rule, or a plugin gone rogue. Intentional partner hops can be allowlisted per-module (trusted_redirect_domains) so the finding stays signal, not noise.
  3. Redirect loops (Medium). Two rules that each defer to the other (classic: HTTP→HTTPS redirect at the server plus the same redirect at the CDN). Users get a browser error page; crawlers abandon the URL.
  4. Excessive chain length (Low). Each hop is a full round-trip; chains of 5+ hops (domain migrations are the usual source) measurably slow every landing and dilute SEO signals. Consolidate to one hop: update the oldest rule to point directly at the final destination.

That last category is performance and SEO hygiene wearing a security hat — the same chain walk that catches downgrades catches them, which is why it belongs in a security scan and not just an SEO audit.

The fixes, in order of robustness

1. Relative-only redirects (best default). If the destination should always be inside your app, accept only paths and validate structurally:

func safeRedirect(next string) string {
    if next == "" || !strings.HasPrefix(next, "/") || strings.HasPrefix(next, "//") {
        return "/dashboard" // default landing
    }
    return next // "/invoice/42" — same-origin by construction
}

This single pattern eliminates the class: a value starting with / (and not //) cannot leave your origin, and no parser edge cases (userinfo, backslashes, Unicode homoglyphs, scheme smuggling) survive it. For internal tools and most apps, user-supplied full URLs should simply never reach a redirect.

2. Exact-match allowlist for genuine cross-domain hops. When "return to partner X" is a product requirement:

var allowed = map[string]bool{
    "https://partners.example.org":  true,
    "https://app.partner.io":        true,
}

func safeExternal(next string) (string, bool) {
    u, err := url.Parse(next)
    if err != nil || u.Scheme != "https" || !allowed[u.Scheme+"://"+u.Host] {
        return "", false
    }
    return u.String(), true
}

Parse, compare scheme + host (not substrings — url.Parse handles the userinfo and dot-segment tricks for you), and reconstruct from the parsed parts so path/query can't smuggle surprises.

3. Confirmation interstitial for anything external. If you must redirect to arbitrary destinations (click-tracking endpoints), insert your own "You are leaving example.com — continue?" page with the target shown in full text. This converts the trust weapon into an informed decision and gives your abuse team a log point.

4. Kill the endpoints you don't need. Every ?next=-style parameter is future attack surface. If nothing consumes it, remove it; if it's consumed, log destinations and alert on external ones.

5. Fix the chain, not the symptom. For downgrade/loop/length findings: point each hop directly at the final https:// destination, then re-scan. The chain walk verifies the fix end-to-end — including the hops you forgot existed (CDN rules, .htaccess relics, service-worker caches are the usual suspects).

How to test your own site

# 1. Probe common parameters (adjust paths to your app):
for p in "login?next=" "signin?return_to=" "auth?callback=" "oauth?redirect_uri=" "/?next="; do
  curl -s -o /dev/null -D - "https://example.com/${p}https://evil-attacker-example.com" \
    | grep -i "^location" && echo "  ^ open redirect via /$p"
done

# 2. Parser edge cases against any redirect endpoint:
#   //evil.attacker.io        (protocol-relative)
#   https://example.com@attacker.io   (userinfo)
#   https://attacker.io#example.com   (fragment)
#   /\attacker.io             (backslash normalization)

# 3. Walk your real chains the way a crawler does:
curl -sIL "http://example.com" | grep -iE "^(HTTP|location)"

Any Location echoing the attacker host = open redirect (fix immediately — it's the High-severity class). Clean responses are the norm, but run the check after every auth-flow refactor, SSO integration or CDN rule change: redirects are exactly the kind of config that regresses silently.

Frequently asked questions

Is an open redirect really a vulnerability if no data is exposed?

Yes — it's a reputation vulnerability. The exploited asset is the trust users, mail filters and browsers place in your domain. Most bug-bounty programs and the OWASP Top 10-adjacent guidance classify it as reportable for exactly that reason.

We use ?next= only with relative paths — are we safe?

If the handler enforces relative-only structurally (starts with /, not //) — yes, that's the recommended pattern. If it merely usually receives relative values from your own pages, it's still open: attackers type the parameter by hand.

Do 301 vs 302 vs 307 matter for security?

Not for the vulnerability itself, but they matter for correctness: 301 is cached aggressively (hard to undo), 307/308 preserve method and body (needed for POST flows), 302/303 for everything else. A mis-cached 301 on an auth path has caused more than one extended outage during fixes.

Can I just block redirects to known-bad domains?

No — a blocklist is unwinnable (attacker rotates domains hourly) and the failure mode is permissive. Use allowlists or relative-only; treat blocklists at best as extra logging signal.

How does this relate to CSP or CSRF defenses?

They're different layers: CSRF protects form-driven state changes, CSP constrains what your pages may load/connect to, redirects control where your server sends users. The scan checks all three because real incidents chain them — a takeover enables a redirect, a redirect enables phishing, phishing enables takeover of accounts.


Run the free security scan to probe your auth paths for open redirects and walk your redirect chains — downgrade hops, loops, external domains and all — alongside the rest of the checks.