
CRLF injection is a web vulnerability that exploits how the HTTP protocol uses the carriage return and line feed characters, written as %0d and %0a in URL encoding, to separate one header from the next. When user-controlled input is written into a response header without filtering those bytes, you can inject your own headers or even an entire second response, an attack known as HTTP response splitting.
This post explains the mechanics, walks through the payloads and tooling to find CRLF injection, shows what it escalates into (cache poisoning, XSS, session fixation, log forging), and covers the one-line fix. It is tested under WSTG-INPV-15 and maps to A03:2021 Injection.
CRLF injection is an attack that inserts carriage-return (CR, %0d, \r) and line-feed (LF, %0a, \n) characters into application output that becomes part of an HTTP response, breaking the intended structure of the message. HTTP uses the sequence \r\n to end each header line and a doubled \r\n\r\n to mark the end of headers and the start of the body, so injecting those bytes lets you forge structure the server never intended.
The vulnerable pattern is any place user input lands in a response header: a redirect that reflects a url parameter into Location, a language preference written into Set-Cookie, or a custom header echoing a request value. CRLF injection is the gateway to header injection and response splitting, both tested in the input-validation portion of a web application pentesting checklist.
It works by smuggling encoded newline characters into a value that the server writes into a header, so your text after the newline becomes a new header line. Consider an endpoint that redirects using a parameter and reflects it into the Location header. Supplying encoded CRLF lets you append a header of your choosing.
# Original
/redirect?url=/home
# Response: Location: /home
# Inject a forged Set-Cookie via CRLF
/redirect?url=/home%0d%0aSet-Cookie:%20sessionid=attacker
# Response now contains:
# Location: /home
# Set-Cookie: sessionid=attackerDoubling the sequence (%0d%0a%0d%0a) closes the header block and lets you write a body, which is full HTTP response splitting. Inject HTML there and you get reflected XSS even if the normal page output is encoded.
Find every parameter that influences a response header, then inject encoded CRLF sequences and inspect the raw response for a new line you control. Burp Suite is the cleanest way to see exactly which bytes land where, because you can view the unrendered response and confirm the injected header appears.
# Payloads to try (and double-encoded variants)
%0d%0aSet-Cookie:%20test=crlf
%0d%0aX-Injected:%20yes
%0d%0a%0d%0a<script>alert(1)</script>
# Double-encoded, for filters that decode once
%250d%250aX-Injected:%20yes
# Fuzz header-reflecting params at scale
ffuf -w params.txt -u "https://target/redirect?FUZZ=%0d%0aX-Injected:yes"Watch redirect (Location), cookie, and any reflected custom headers. If your injected header or body shows up in the raw response, it is confirmed. This is WSTG-INPV-15 and belongs in your penetration testing test cases.
CRLF injection is rarely the end goal; it is a primitive that unlocks several higher-impact attacks depending on what you can inject. The severity ranges from log noise to full response control.
Prevent CRLF injection by removing or rejecting CR and LF characters from any value before it is written into an HTTP header, and by relying on framework APIs that do this for you. Most modern HTTP libraries (the Servlet API, Node's http module, ASP.NET) refuse header values containing raw newlines, so the bug usually appears where developers build headers from raw strings or use older platforms. Continuous coverage of injection flaws like this is exactly what agentic pentesting is built to automate.
Concretely: strip or URL-encode \r and \n in redirect targets, cookie values, and any reflected header; validate redirect destinations against an allowlist rather than reflecting user input; and never log raw, unsanitized request data on the same line as structured fields. As with all injection bugs, treat data and protocol structure as separate things.