Header Injection
debt(d5/e1/b3/t7)
Closest to 'specialist tool catches it' (d5), because the detection_hints list semgrep and psalm — both specialist SAST/static-analysis tools — as the means to catch unsanitised values passed to header(). It won't be caught by a default linter and is silent at runtime until exploited, so d5 is the right anchor.
Closest to 'one-line patch or single-call swap' (e1), because the quick_fix states: strip or reject \r or \n from values passed to header(). In PHP 8.0+ the runtime throws automatically. This is a single sanitisation call at the point of use, well within e1 territory.
Closest to 'localised tax' (b3), because header() calls are confined to web-context PHP output logic (the applies_to context is 'web'). Once CRLF stripping is added at the relevant call sites, the rest of the codebase is unaffected. The fix doesn't spread across the architecture.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7), grounded in the misconception field: developers commonly believe header injection is merely a cosmetic formatting issue, when in fact injecting newlines enables full HTTP response splitting, XSS via reflected headers, cache poisoning, and open redirects. This dramatically underestimates severity, making it a serious cognitive trap just short of catastrophic.
Also Known As
TL;DR
Explanation
HTTP headers are separated by CRLF (\r\n). If an attacker injects a newline into a header value, they can inject additional headers or even a second HTTP response body — a technique called HTTP Response Splitting. This can be used to poison caches, hijack redirects, or inject HTML/JavaScript. Prevention: strip all \r and \n characters from any user-supplied value before including it in a header().
How It's Exploited
# Injects: Set-Cookie: admin=1 as a separate response header
Common Misconception
Why It Matters
Common Mistakes
- Passing unsanitised user input to PHP's header() function, especially in Location or Set-Cookie headers.
- Trusting X-Forwarded-For or Referer headers and reflecting them into responses.
- Not stripping \r and \n from any value that ends up in an HTTP header.
- URL-decoding values before passing to header() without re-stripping control characters.
Code Examples
// User input injected into header — attacker can inject newlines
$lang = $_GET['lang'];
header("Content-Language: $lang");
// ?lang=en%0d%0aSet-Cookie:admin=1 → injects extra header
// Allowlist approach
$allowed = ['en', 'fr', 'de', 'es', 'ja'];
$lang = in_array($_GET['lang'] ?? 'en', $allowed, true)
? $_GET['lang'] : 'en';
header("Content-Language: $lang");
// If dynamic values are unavoidable, strip CR/LF:
$clean = str_replace(["\r", "\n"], '', $value);
header("X-Custom: $clean");