Description
CRLF injection (CWE-93) exploits the HTTP protocol's use of carriage return (\r, %0d) and line feed (\n, %0a) characters as header delimiters. HTTP responses are structured as a sequence of headers separated by \r\n, with a blank line (\r\n\r\n) separating the header section from the body. When user-supplied data containing these characters is embedded in a response header without sanitization, an attacker can inject arbitrary headers or split the response entirely.
The vulnerability most commonly appears in redirect parameters, cookie-setting endpoints, and any server functionality that reflects user input into an HTTP header such as Location, Set-Cookie, X-Custom-Header, or Content-Disposition. It falls under OWASP A03:2021 (Injection) because the attacker is injecting structural characters that alter the intended meaning of the HTTP message.
CRLF injection is a prerequisite for HTTP response splitting — a more severe attack where the injected content creates a synthetic second HTTP response that can be served to other users in a shared proxy or CDN cache, leading to cross-site scripting, cache poisoning, or session fixation.
How It Works
Consider a redirect endpoint that reflects the url parameter into a Location header:
GET /redirect?url=https://example.com
Server response:
HTTP/1.1 302 Found
Location: https://example.com
An attacker injects CRLF characters:
GET /redirect?url=https://example.com%0d%0aSet-Cookie:%20sessionid=attacker_controlled_value
Server response becomes:
HTTP/1.1 302 Found
Location: https://example.com
Set-Cookie: sessionid=attacker_controlled_value
The attacker has injected an arbitrary Set-Cookie header, enabling session fixation.
XSS via response splitting — inject a full blank line to terminate the header section and begin an attacker-controlled body:
/redirect?url=x%0d%0a%0d%0a<script>alert(document.cookie)</script>
This produces a response where the attacker-injected HTML becomes the response body, bypassing CSP if the injected content is served under the application's origin.
Cache poisoning — in shared reverse proxy environments (Varnish, Squid, CDNs), a crafted response split can cause the proxy to cache the malicious second response and serve it to subsequent legitimate users requesting a common URL.
Impact
- Arbitrary Header Injection — Setting
Set-Cookie,X-Frame-Options,Content-Security-Policy, and other security headers to attacker-controlled values - Session Fixation — Planting a known session cookie in the victim's browser via an injected
Set-Cookieheader - Cross-Site Scripting — Injecting an HTML body through response splitting to execute JavaScript under the application origin
- Cache Poisoning — Causing shared proxies and CDNs to serve injected malicious content to all users of a cached URL
- Security Header Bypass — Overriding
Content-Security-PolicyorX-Content-Type-Optionsheaders set by the application
Detection
- Identify all endpoints that reflect user input into headers — focus on
Locationredirect parameters,Set-Cookievia URL parameters,Content-Dispositionin file downloads, and any custom header endpoint. - Inject basic CRLF sequences — test
%0d%0a,%0a,%0d, and%250d%250aappended to parameter values. Use Burp Suite Repeater to inspect raw response headers for injected content. - Test double-encoding —
%250d%250a(URL-encoded%0d%0a) bypasses single-decode filters. Also try Unicode variants like%u000d%u000a. - Fuzz
Locationredirect endpoints — append%0d%0aX-Injected: trueand verify the custom header appears in the response. - Use Burp Suite Scanner — the active scanner includes CRLF injection checks. Run it against all redirect and header-setting endpoints.
- Review server-side code — search for direct string concatenation into
response.setHeader(),res.redirect(),Response.addHeader(), andheader()PHP calls with user-controlled values.
Remediation
Strip or reject CRLF characters before setting headers. Apply a filter that removes \r and \n from any user input used in HTTP header values:
# Python / Flask
def safe_redirect(url):
# Strip CRLF characters
url = url.replace('\r', '').replace('\n', '')
return redirect(url)
Use framework-provided redirect functions. Modern frameworks like Django, Rails, and Spring sanitize Location header values automatically. Avoid building raw HTTP responses with string concatenation.
Validate redirect URLs against an allowlist. Only permit redirect targets to specific trusted domains or URL patterns. A redirect allowlist prevents CRLF injection from being useful even if the filtering is incomplete.
Apply URL validation with a URL parser. Parse the redirect target with a URL library (urllib.parse.urlparse, new URL()) and reject URLs with unexpected components (fragments containing \r\n, unexpected schemes, etc.).
Encode header values. When embedding user data in headers is unavoidable, percent-encode the value and ensure the receiving system decodes it safely.
