15 KiB
name, description
| name | description |
|---|---|
| header-injection | HTTP header injection testing covering CRLF / response splitting, cache poisoning, Host-header confusion, cookie fixation, and proxy / forwarding header smuggling |
HTTP Header Injection
Header injection turns user input into protocol-level control: response splitting, cache poisoning, session fixation, authentication bypass, and request smuggling all trace back to a server-controlled header value that wasn't normalized. The bug usually lives in middle layers — frameworks that copy a request value into a response header, proxies that trust forwarded headers, caches keyed on something the attacker influences. Treat any user-controlled value that reaches a header as code-execution-equivalent until proven otherwise.
Attack Surface
Input shapes that reach headers
- Query/body/path values echoed into
Set-Cookie,Location,Content-Type,Content-Disposition,Link, customX-* - Request headers re-emitted into responses (Referer, User-Agent, X-Forwarded-*, custom correlation IDs)
- Webhook / callback flows where the server constructs outbound requests using user-supplied URLs (Host, Referer)
- Outbound email headers (To/From/Subject) populated from user input
Code patterns that enable injection
- Direct concatenation of user input into header values without CR/LF stripping
- Frameworks that accept header values as strings and serialize verbatim (no normalization)
- Proxy chains trusting
X-Forwarded-*/Forwarded/X-Real-IPset by an upstream that anyone can spoof X-HTTP-Method-Overrideand similar method-shaping headers respected past auth layers
Transports and parser layers
- HTTP/1.0, HTTP/1.1, HTTP/2, HTTP/3 each parse framing differently
- CDN / reverse proxy → application server (where each side may disagree on framing)
- Chunked transfer encoding boundaries and multipart/form-data delimiters
High-Value Targets
- Password-reset and account-recovery flows (Host header determines the link sent to the user)
- OAuth / SSO redirect endpoints (
Location,redirect_uriechoes) - Auth gateways that trust
X-Forwarded-For/X-Real-IPfor IP allowlists or rate limits - CDN / WAF caches (poisoning a public cache with a per-user response)
- Multi-tenant routing keyed on Host or
X-Tenant-Id - File-download endpoints (
Content-Dispositionfilename derived from user input) - Outbound notification / email systems where user input lands in the message header
Reconnaissance
Header Inventory
- Enumerate every response header that varies with input — flip query / body / cookie values and diff
Set-Cookie,Location,Content-Type,Content-Disposition,ETag,Vary, customX-* - For each varying header, identify the source field (user-controlled vs. server-derived)
- Look for request headers reflected into responses (Referer in error pages, User-Agent in correlation IDs, X-Forwarded-Host echoed back)
CR/LF and Whitespace Variants
- Bare LF (
%0a), bare CR (%0d), CRLF (%0d%0a) - Double encoding (
%250d%250a) for WAFs that decode once - Overlong UTF-8 of CR/LF (
%c0%8d,%c0%8a) — invalid per spec but accepted by some parsers - Unicode line/paragraph separators (
%e2%80%a8U+2028,%e2%80%a9U+2029) — sometimes folded to LF by intermediaries - Tab (
%09) — RFC 7230 allows tabs in field values, useful for sneaking past simple\s+filters - Null byte (
%00) — can truncate the header value in some parsers
Parser and Server Fingerprinting
Server,Via,X-Powered-By,X-AspNet-Version,X-Served-By,CF-Ray,X-Amzn-RequestIdreveal the stackVary,Age,X-Cache,CF-Cache-Statusreveal caching layer and key composition- Same payload over HTTP/1.1 vs HTTP/2 vs chunked — diff status, headers, body length to map parsing differences
- Compare
HostandX-Forwarded-Hostprecedence: send both with different values and observe which wins in redirects, links, log entries
Key Vulnerabilities
CRLF Response Splitting and Smuggling
Inject \r\n\r\n to terminate the current response and prepend a second attacker-controlled response. Cache or downstream proxy may key on the first response and serve the second to other users.
GET /redirect?to=foo%0d%0aSet-Cookie:%20admin=1%0d%0a%0d%0a<html>poisoned</html> HTTP/1.1
Request smuggling is the same primitive at the request layer: inject a header that causes the proxy and backend to disagree on message framing — most commonly conflicting Content-Length and Transfer-Encoding, or two Content-Length headers with different values. Backend reads one request, frontend reads a different one; the leftover bytes become a smuggled request prepended to the next victim's connection.
Cache Poisoning
- Unkeyed input → keyed response: input that influences the response body but not the cache key (an
X-Forwarded-Hostechoed in a link, an unkeyed query parameter reflected in HTML) Varymanipulation: inject aVaryheader to over-fragment the cache (DoS-flavored) or under-fragment it (cross-user serving)X-Forwarded-Proto/X-Forwarded-Hostpoisoning: backend uses these to build canonical URLs in the response; CDN caches the response with attacker-controlled linksCache-Controlinjection: flipprivatetopublic(or vice versa) to change cache eligibility; injectmax-age=999999for persistent poisoning, ormax-age=0/no-cacheto flush —Ageis generated by the cache itself and isn't a freshness control, don't bother with it- Web cache deception: trick the cache into storing an authenticated response at a public-looking URL (
/account/profile.css) by appending a cacheable extension
Host Header Confusion
Backends often trust Host (or X-Forwarded-Host) when constructing absolute URLs — password reset emails, OAuth redirect_uri, canonical link tags. Sending a forged Host produces a reset link pointing at attacker-controlled infrastructure that still carries the victim's reset token.
POST /password-reset HTTP/1.1
Host: attacker.tld
Also test: precedence between Host and X-Forwarded-Host, IPv6 bracketing (Host: [::1]:80), trailing dot (Host: example.com.), and port confusion (Host: example.com:@attacker.tld).
Cookie / Set-Cookie Manipulation
- Inject
Domain=.example.comorPath=/to widen scope of an attacker-set cookie - Inject
SameSite=None; Secureto allow cross-site inclusion - Inject
Max-Age=999999999for persistence, orMax-Age=-1to nuke the victim's session - Inject a cookie with the same name as a real session cookie — precedence rules let a same-domain attacker shadow it (cookie tossing)
- Reflected cookie XSS: if a cookie value is later rendered unescaped in HTML, the injection point is the header but the sink is the page
Proxy and Forwarding Header Spoofing
The X-Forwarded-* family is informational — there is no protocol guarantee about who set them. Any application that trusts them past the boundary it controls is exploitable.
X-Forwarded-For: 127.0.0.1to bypass IP allowlists or rate limits keyed on client IPX-Forwarded-Proto: httpsto satisfy "HTTPS-only" checks while still using HTTPX-Forwarded-Host: attacker.tldfor the Host-confusion variants aboveX-Real-IP,Client-IP,True-Client-IP,CF-Connecting-IP,Forwarded(RFC 7239) — same primitive, different header names; spray all of themX-Original-URL/X-Rewrite-URL(IIS, ASP.NET) — server-side URL rewriting after auth check, classic admin-panel auth bypass
Content-Type / Encoding Confusion
- Inject
Content-Type: text/htmlinto an endpoint that returned JSON; browsers may sniff and render → XSS - Inject
charset=utf-7inContent-Typefor legacy XSS via UTF-7-encoded payloads - Inject
Content-Disposition: inlineto switch a download into in-page rendering - Inject
Content-Encoding: gzipwithout actually compressing — clients decode-fail and may reveal raw response bytes in error paths - Absence of
X-Content-Type-Options: nosniffis what enables the sniffing attacks above; the header is a hardening control, not an attack surface — but if a server sets it inconsistently across endpoints, target the ones that don't
XSS via Response Headers
Location: javascript:alert(1)if redirect target is reflected unescaped (browsers usually block, but some legacy clients and Electron-style hosts don't)Location: data:text/html,<script>alert(1)</script>— same caveatRefresh: 0; url=javascript:alert(1)— the legacyRefreshheader is a JavaScript-free meta-refresh equivalent- Reflected request header XSS:
Refererechoed into a custom error page,User-Agentechoed into a debug header — combine CRLF injection with a body-injection sink
Open Redirect via Headers
Locationis the obvious oneRefresh: 0; url=https://attacker.tld— bypasses someLocation-only filtersLink: <https://attacker.tld>; rel="canonical"— usually informational but consumed by SEO tooling and some clientsX-Accel-Redirect: /internal/file(Nginx) — if user input reaches this, internal-only files become accessible
HTTP/2 Pseudo-Header and Frame Confusion
- HTTP/2 splits headers into pseudo-headers (
:method,:path,:authority,:scheme) and regular fields. Servers downgrading to HTTP/1.1 sometimes mishandle pseudo-header values, enabling smuggling across the H2 → H1 boundary. - HTTP/2 lowercases header names; an upstream H1 filter that's case-sensitive may miss a lowercase variant that the H2 backend then accepts.
- HEADERS / CONTINUATION frame splitting: payload spans frames, intermediaries differ on whether they reassemble before applying filters.
Bypass Techniques
Encoding
- URL-encode (
%0d%0a) and double-encode (%250d%250a) for WAFs that decode the wrong number of times - Mix encodings within one payload:
%0d%0A,%0D\n, alternating case - Newline-equivalent Unicode: U+2028 / U+2029 (sometimes folded to LF), overlong UTF-8 of CR/LF
Header normalization edges
- Leading / trailing whitespace and tabs in header names and values
- Header folding (obs-fold per RFC 7230 — formally obsolete, but some parsers still accept continuation lines starting with whitespace)
- Duplicate headers — RFC says join with
,; in practice servers pick first, last, or differ from the proxy in front of them
Method and method-override
X-HTTP-Method-Override: PUT(andX-Method-Override,X-HTTP-Method) to reach state-changing handlers when the framework consults the override before applying method-based authorization- Effective from server-side or non-browser clients (curl, internal tooling, server-to-server proxies); from a browser the header is non-safelisted and triggers a CORS preflight, so it isn't a CSRF primitive on its own
Header name games
- Case mangling for filters that key off exact casing
- Null byte truncation in header name (
X-Forwarded-For\x00Evil) on parsers that stop at NUL
Testing Methodology
- Inventory varying headers — enumerate every response header whose value moves with input
- Probe CR/LF normalization — inject
%0d%0a(and the encoding variants) into each varying header source; observe whether the second line lands as a real header - Test Host / X-Forwarded-Host — submit a password-reset or any link-generating flow with attacker-controlled Host; confirm the link in the response or follow-up email
- Probe forwarding headers — spoof
X-Forwarded-For,X-Real-IP,True-Client-IP,CF-Connecting-IPagainst IP-restricted endpoints (admin, rate-limited) - Test cache key / response content split — find inputs that change the body but not the cache key; confirm a second request from a different session sees the poisoned response
- Test method override —
X-HTTP-Method-Overridepaired with state-changing endpoints reachable via POST or GET - Test request smuggling pairs — conflicting
Content-LengthandTransfer-Encoding, twoContent-Lengthheaders, malformed chunked encoding, against any frontend → backend pair - Cross-protocol — replay payloads over HTTP/1.1 and HTTP/2; diff behavior
Validation
- Show two distinct users (or sessions) receiving content keyed on attacker-supplied header — proves cache poisoning
- Capture a password-reset / OAuth link pointing at attacker-controlled host — proves Host injection
- Demonstrate the same endpoint returning different auth decisions with and without a forged forwarding header
- For response splitting: show a downstream cache or proxy serving the injected second response to an unrelated request
- For request smuggling: show one victim request seeing data from a different request appended (not just timing or single-shot anomaly)
- All findings should produce a durable artifact (cached response, sent email, log entry, session change) — transient anomalies are not validation
False Positives
- Headers that vary by input but are correctly keyed in the cache (intentional personalization, Vary set correctly)
X-Forwarded-*reflected back but only used for logging — not a security boundary, may not be exploitable- Browsers blocking
Location: javascript:orLocation: data:— capability exists in the protocol but most modern browsers refuse to navigate - CRLF appearing in response headers but stripped by an outer proxy before reaching any client or cache
- Request smuggling indicators that turn out to be normal pipelining or keep-alive behavior
Impact
- Cross-user cache poisoning (defacement, XSS, account takeover via cached auth response)
- Account takeover via Host-confused password-reset / OAuth flows
- Auth bypass on endpoints trusting forwarding headers
- Session fixation and cookie tossing leading to account hijack
- Open redirect for phishing / OAuth
redirect_uriabuse - Request smuggling — one victim's request reads another victim's response, including auth headers and cookies
- WAF / detection bypass via header-name and encoding tricks
Pro Tips
- The fastest win is usually Host /
X-Forwarded-Hostin a password-reset or OAuth flow — try first, costs one request - For cache poisoning, find the unkeyed input first (header that influences body but not cache key); the rest follows
X-HTTP-Method-Overrideis high-yield against backends that route on it before checking method-based auth — most useful from server-side / non-browser callers (it triggers CORS preflight in a browser, so not a CSRF primitive)- Smuggling lives at the boundary — identify the proxy → backend pair (CDN → origin, ingress → service) and target the framing disagreement
X-Original-URL/X-Rewrite-URLagainst IIS / ASP.NET admin endpoints is still a high-yield bypass- Before claiming a CRLF win, verify the second line landed as a real header in the cache or downstream consumer — many servers strip CRLF silently
- Outbound email flows are a separate but related surface — user input flowing into SMTP headers (To, Cc, Subject, Reply-To) is its own injection class with the same root cause
Summary
Header injection is fundamentally a normalization failure: somewhere on the request → response path, user input reached a header value without CR/LF stripping or proper escaping. The impact tiers up from open redirect to cache poisoning to request smuggling depending on which downstream component trusts the resulting header. Audit every header whose value moves with input, and treat every X-Forwarded-* / Host trust as a security boundary that needs explicit justification.