CRLF Injection occurs when an attacker can inject Carriage Return (CR, %0d, \r) and Line Feed (LF, %0a, \n) characters into an application’s output that is included in an HTTP response header. Since CR+LF sequences (\r\n) are used to separate headers and delimit the start of the response body in HTTP/1.1, injecting them allows an attacker to add fake headers or even inject content into the response body. This can lead to HTTP Response Splitting, Cross-Site Scripting (XSS), session fixation, or cache poisoning. 📄✂️
This vulnerability happens when user-supplied data is directly included in HTTP response headers without proper sanitization to remove or encode CR (\r) and LF (\n) characters. Common sinks include custom header values, redirect URLs (Location header), and cookie values (Set-Cookie header).Modern web frameworks and libraries often provide some level of automatic protection by validating or encoding header values, but vulnerabilities can still occur in custom code or older/misconfigured setups. The primary defense is output encoding/sanitization specifically targeting CR and LF characters in any data destined for headers.
Manually setting headers in Django/Flask using user input without sanitization. Django’s built-in HttpResponseRedirect generally handles the Location header safely, but custom headers are a risk.
Avoid reflecting user input directly into headers. If necessary, sanitize the input by removing or encoding \r and \n characters. Use Django’s built-in HttpResponseRedirect or redirect() shortcut which handles URL validation for the Location header.
Identify all instances where user input is incorporated into response headers (response['Header-Name'] = user_input). Submit URL-encoded CRLF sequences (%0d%0a) followed by test headers (e.g., Injected-Header: test) or HTML content (%0d%0a%0d%0a<script>...). Use curl -v or browser developer tools to inspect the raw response headers and body for unexpected content.
Manually setting headers using HttpServletResponse.setHeader() or addHeader() with unsanitized user input. Frameworks like Spring often sanitize common headers like Location in RedirectView, but custom headers remain a risk.
Vulnerable Scenario 2: Setting Cookie with Unsanitized Value
Setting a cookie where the value comes directly from user input.
// controller/PreferenceController.java@PostMapping("/set-preference")public String setPreference(HttpServletResponse response, @RequestParam String theme) { // DANGEROUS: Cookie value not sanitized for CR/LF. // Input: theme = "dark\r\nSet-Cookie: AttackerCookie=Hijacked" // This could inject a second Set-Cookie header. Cookie prefCookie = new Cookie("userTheme", theme); prefCookie.setPath("/"); // Note: Modern browsers might block CRLF in cookie values, but relying on this is risky. response.addCookie(prefCookie); return "redirect:/";}
Sanitize all input intended for response headers by removing or encoding CR (\r) and LF (\n) characters. Use libraries or utility functions designed for HTTP header value validation/encoding if available. Be cautious when setting cookies with user-controlled values.
Identify calls to response.setHeader(), addHeader(), addCookie(). Trace the origin of the values being set. If they include user input, test by submitting URL-encoded CRLF (%0d%0a) followed by test headers or HTML. Use curl -v or a proxy (like Burp Suite) to inspect the raw response headers.
Manually adding headers to HttpResponse.Headers using unsanitized user input. ASP.NET Core’s header handling is generally robust against basic splitting, but complex scenarios or direct manipulation of the response stream could be risky.
Vulnerable Scenario 1: Reflecting Custom Header Data
// Controllers/ApiController.cspublic class ApiController : ControllerBase{ [HttpGet("info")] public IActionResult GetInfo([FromHeader(Name = "X-User-Input")] string userInput) { if (!string.IsNullOrEmpty(userInput)) { // DANGEROUS: Reflecting header input back into another header. // Modern ASP.NET Core might sanitize this by default, but relying // on implicit behavior is risky. Explicit sanitization is better. // Input: X-User-Input = "value\r\nInjected: Bad" Response.Headers.Add("X-Reflected-Input", userInput); } return Ok("Info processed"); }}
Sanitize input by removing CR and LF characters before adding it to any header value, even if the framework might do it implicitly. Use built-in methods like Redirect() or LocalRedirect() which handle Location header validation.
// Controllers/ApiController.cs (Secure)using Microsoft.Extensions.Primitives; // For StringValuespublic class ApiController : ControllerBase{ [HttpGet("info")] public IActionResult GetInfo([FromHeader(Name = "X-User-Input")] string userInput) { if (!string.IsNullOrEmpty(userInput)) { // SECURE: Explicitly remove CR and LF. string sanitizedInput = userInput.Replace("\r", "").Replace("\n", ""); // Use TryAdd for headers that might exist. Use AddOrUpdate logic if needed. Response.Headers.TryAdd("X-Reflected-Input", new StringValues(sanitizedInput)); } return Ok("Info processed"); }}// Controllers/RedirectController.cs (Secure)public class RedirectController : Controller{ public IActionResult GoSecure(string targetUrl) { // SECURE: Use LocalRedirect for internal paths, which validates. // Or use Redirect() for external AFTER validating with Url.IsLocalUrl() or an allow-list. if (!string.IsNullOrEmpty(targetUrl) && Url.IsLocalUrl(targetUrl)) { return LocalRedirect(targetUrl); // Handles Location header safely } return RedirectToAction("Index", "Home"); // Safe default }}
Find where Response.Headers.Add, Response.Headers["Header"] =, or Response.Cookies.Append are used with user-controlled data. Send requests with URL-encoded CRLF sequences (%0d%0a) in the relevant input (query params, headers, body). Inspect the raw response using curl -v or a proxy to check for injected headers or body content.
Using the header() function directly with unsanitized user input. Frameworks like Laravel abstract header setting, often providing some protection, but direct header() calls bypass this.
<?php// tracking.php$referer = $_SERVER['HTTP_REFERER'] ?? 'unknown';// DANGEROUS: Referer comes from the client and can contain CRLF.// Input (Referer Header): [http://example.com](http://example.com)\r\nInjected: valueheader("X-Tracked-Referer: " . $referer);echo "Tracked.";?>
Sanitize all data placed into headers using header(). Remove CR (\r) and LF (\n) characters. Validate input against an allow-list where possible (e.g., for content types). Use framework helpers (like Laravel’s response()->header()) which may offer better protection than raw header().
Identify all uses of the header() function where the value includes variables derived from $_GET, $_POST, $_COOKIE, or $_SERVER. Submit URL-encoded CRLF sequences (%0d%0a) in the relevant input. Use curl -v or a proxy to inspect the raw response headers for injection.
Using res.setHeader(name, value) or res.writeHead(statusCode, headers) where header values contain unsanitized user input. Express’s built-in header functions generally try to prevent splitting, but edge cases or direct manipulation might exist.
While modern Node.js and Express versions have improved header validation, it’s safest to explicitly sanitize input by removing CR (\r) and LF (\n) characters before setting headers.
Identify uses of res.setHeader() or res.writeHead() where values incorporate req.query, req.params, req.headers, or req.body. Submit URL-encoded CRLF sequences (%0d%0a) in these inputs. Use curl -v or a proxy to inspect the raw response headers for injection. Test different Node.js/Express versions if compatibility is a concern.
Manually setting headers in Rails using response.headers['Header-Name'] = value where value contains unsanitized user input. Rails’ built-in header handling is generally safe against basic splitting, but explicit sanitization is best practice.
Identify where response.headers[...] = or cookies[...] = are used with params or other user-controlled data. Submit URL-encoded CRLF sequences (%0d%0a) in the input. Use curl -v or a proxy to inspect the raw HTTP response for injected headers or unexpected body content.