Medium Severity CWE-601 OWASP A01:2021

Open Redirect: How Attackers Hijack Your Login Flow

Attackers use your trusted domain to send users to phishing sites

Quick Answer

Open redirect happens when your app redirects users to any URL they specify in a parameter. Attackers craft links using your trusted domain that redirect to phishing sites. Always validate redirect URLs against an allowlist of trusted destinations.

What is open redirect?

Open redirect (CWE-601) happens when your application blindly redirects users to any URL specified in a request parameter. Users see your familiar domain and trust the link, but after clicking, they end up on an attacker's phishing site.

Think of it like a receptionist at a trusted company who gives visitors directions to wherever someone asks - including to a scam office down the street. The receptionist (your app) doesn't verify the destination, just redirects.

Why is this dangerous? Users trust your domain. They see the SSL lock, recognize the URL, and click. By the time they realize they're on a fake site that looks just like yours, they've already entered their credentials.

Why open redirects are dangerous

Phishing with Trusted Domains

A link like https://yourapp.com/login?returnUrl=https://evil-clone.com looks legitimate. Users trust it because it starts with your domain. After logging in on your real site, they're redirected to the attacker's identical-looking phishing page.

OAuth Token Theft

In OAuth flows, if attackers control the redirect destination, they can steal authorization codes or tokens. The victim authenticates legitimately, then the token lands in the attacker's hands.

Malware Distribution

Open redirects can send users to malware-hosting sites. Drive-by downloads become more effective when the initial link appears to come from a trusted source.

Vulnerability Chaining

Open redirects can enhance SSRF attacks by providing redirects to internal URLs, or enable XSS via javascript: protocol URLs.

Where AI tools create open redirects

When you ask AI tools to "redirect after login" or "add a return URL," they generate the simplest working code without validation. This is a classic vibe coding trap - the code works perfectly until an attacker finds it.

Common vibe coded patterns that create open redirects:

  • Login flows - "Return to original page after login"
  • OAuth callbacks - "Redirect after successful authentication"
  • Action completions - "Redirect after form submission"
  • Email links - "Track clicks and redirect to destination"
  • Short URLs - "Create redirect service"

Common open redirect patterns

Attackers have various techniques to exploit and bypass weak validation:

Query Parameter

/redirect?url=https://evil.com

The classic pattern. URL in query string is used directly.

Protocol-Relative

/redirect?url=//evil.com

Bypasses checks for "http" because it starts with //.

JavaScript Protocol

/redirect?url=javascript:alert(1)

Can execute code if the redirect becomes href.

URL Encoding

/redirect?url=%2f%2fevil.com

Encoded // bypasses simple string checks.

Subdomain Tricks

/redirect?url=https://trusted.com.evil.com

Looks like your domain but isn't.

Backslash Confusion

/redirect?url=/\evil.com

Some parsers treat backslash as path separator.

For detailed exploitation techniques, see the PortSwigger Open Redirect Guide.

Vulnerable code examples

Pattern 1: Vulnerable Return URL (AI Default)

VULNERABLE
// AI generates this for "return after login"
app.get('/login', (req, res) => {
  const returnUrl = req.query.returnUrl || '/'
  // ... authenticate user ...
  res.redirect(returnUrl) // DANGEROUS!
})

// Attacker: /login?returnUrl=https://evil-phishing-site.com
// User sees your trusted login, then lands on phishing page

Any URL accepted as redirect target. The attacker crafts links that look legitimate but redirect to phishing.

Pattern 2: Vulnerable OAuth Callback

VULNERABLE
// OAuth callback without validation
app.get('/auth/callback', async (req, res) => {
  const { code, state } = req.query
  // ... exchange code for token ...

  const redirectTo = req.query.redirect || '/dashboard'
  res.redirect(redirectTo) // DANGEROUS!
})

// Attacker: /auth/callback?code=xxx&redirect=https://evil.com
// Token ends up exposed in attacker-controlled destination

OAuth flows are high-value targets. Without validation, attackers can steal tokens by controlling the redirect.

How to fix open redirects

Secure Pattern: Path Allowlist (Recommended)

SECURE
// Validate against allowlist of paths
const ALLOWED_REDIRECTS = ['/', '/dashboard', '/profile', '/settings']

function isValidRedirect(url) {
  // Must be a string
  if (!url || typeof url !== 'string') return false

  // Must start with / (relative path)
  if (!url.startsWith('/')) return false

  // Must NOT be protocol-relative (//evil.com)
  if (url.startsWith('//')) return false

  // Remove query string for comparison
  const path = url.split('?')[0]

  // Check against allowlist
  return ALLOWED_REDIRECTS.some(allowed =>
    path === allowed || path.startsWith(allowed + '/')
  )
}

app.get('/login', (req, res) => {
  const returnUrl = req.query.returnUrl || '/'

  if (!isValidRedirect(returnUrl)) {
    return res.redirect('/') // Default to safe page
  }

  // ... authenticate ...
  res.redirect(returnUrl)
})

Only allow specific, known paths. Reject external URLs, protocol-relative URLs, and unexpected paths.

Secure Pattern: Domain Validation

SECURE
// Validate URL is on trusted domain
const TRUSTED_DOMAINS = ['myapp.com', 'app.myapp.com']

function isValidRedirectUrl(urlString) {
  try {
    // Parse with base URL to handle relative paths
    const url = new URL(urlString, 'https://myapp.com')

    // Must be trusted domain
    if (!TRUSTED_DOMAINS.includes(url.hostname)) {
      return false
    }

    // Block dangerous protocols
    if (!['http:', 'https:'].includes(url.protocol)) {
      return false
    }

    return true
  } catch {
    return false
  }
}

Parse URLs properly using the URL constructor. Validate hostname and protocol explicitly.

Most Secure: Mapping Approach

MOST SECURE
// Map IDs to URLs instead of accepting URLs directly
const REDIRECT_MAP = {
  'dashboard': '/dashboard',
  'profile': '/profile',
  'settings': '/settings',
  'home': '/',
}

app.get('/goto', (req, res) => {
  const target = req.query.target
  const redirectUrl = REDIRECT_MAP[target] || '/'
  res.redirect(redirectUrl)
})

// Usage: /goto?target=dashboard
// Attacker cannot inject arbitrary URLs

Never accept URLs directly. Map safe identifiers to hardcoded URLs. Impossible to exploit.

Key Security Points

  • Allowlist, don't blocklist - specify exactly what's allowed
  • Check for // - protocol-relative URLs bypass http checks
  • Block javascript: and data: - can execute code
  • Use URL parser - don't regex match URLs manually
  • Consider mapping - IDs are more secure than URLs

AI fix prompt: Open redirect audit

Copy this prompt to your AI tool to scan your codebase for open redirect vulnerabilities:

Copy-paste into your AI tool
You are a security auditor. Scan this codebase for Open Redirect vulnerabilities (CWE-601).

## What to Search For

### 1. Redirect Functions with User Input
Search for redirect operations that use request parameters:
- res.redirect(req.query.url)
- redirect(userInput)
- Response.redirect(urlFromRequest)
- window.location = userUrl
- router.push(userPath)

### 2. Common Parameter Names
Search for these parameter names in redirect context:
- returnUrl, returnTo, return_to, return
- redirectUrl, redirectTo, redirect_to, redirect
- next, nextUrl, next_url
- url, target, destination, dest
- callback, callbackUrl, callback_url
- goto, go, forward, continue

### 3. OAuth/Auth Callback Handlers
Look for OAuth callback endpoints:
- /auth/callback
- /api/auth/callback
- /oauth/callback
- /login/callback
- OAuth state parameter handling

## Validation Requirements

For any redirect, verify ALL of these checks exist:

```javascript
function isValidRedirect(url) {
  // 1. Must start with / (relative path)
  if (!url.startsWith('/')) return false

  // 2. Must NOT be protocol-relative (//evil.com)
  if (url.startsWith('//')) return false

  // 3. Block dangerous protocols if full URL
  try {
    const parsed = new URL(url, 'https://example.com')
    if (!['http:', 'https:'].includes(parsed.protocol)) return false
  } catch {}

  // 4. Allowlist check (preferred)
  const basePath = url.split('?')[0]
  const ALLOWED = ['/dashboard', '/profile', '/settings']
  if (!ALLOWED.some(p => basePath.startsWith(p))) return false

  return true
}
```

## Report Format

For each finding, report:
1. File and line number
2. The vulnerable code pattern
3. What parameter controls the redirect
4. What validation exists (if any)
5. Whether it's in auth/login flow (higher severity)
6. Specific fix with code

Frequently Asked Questions

What is an open redirect vulnerability?

Open redirect (CWE-601) happens when your application redirects users to any URL specified in a parameter without validation. Attackers craft malicious links using your trusted domain that redirect victims to phishing sites. Because the link starts with your legitimate domain, users trust it and click.

Why are open redirects dangerous?

Open redirects enable sophisticated phishing attacks. Users see your trusted domain and SSL certificate, so they click the link and enter credentials. They are then redirected to a fake site that looks like yours. Attackers can also use open redirects to deliver malware or chain with other vulnerabilities like SSRF.

How do I validate redirect URLs?

Use an allowlist of permitted redirect destinations. Only allow relative paths that start with "/" and match known routes in your app. Parse URLs properly using the URL constructor to prevent protocol tricks. Never accept arbitrary external URLs. For OAuth callbacks, validate the redirect_uri against registered values.

Can open redirects lead to account takeover?

Yes, especially in OAuth flows. If an attacker can control the redirect_uri or post-login redirect, they may be able to steal authorization codes or tokens. The victim authenticates on your real site, then gets redirected to the attacker who captures the token in the URL. Always validate OAuth callbacks.

How do I fix open redirect in Next.js?

In Next.js, validate the redirect path before using redirect() or res.redirect(). Check that the path starts with "/" but not "//". Validate against an allowlist of permitted paths. For API routes handling OAuth callbacks, strictly validate the redirect destination matches your configured callback URLs.

Related Security Topics

External Resources

Find Open Redirects in Your Code

VibeShip Scanner automatically detects unvalidated redirects in your codebase, including OAuth callback handlers and login return URLs.

Scan Your Code Free