AppSec · Jun 29, 2026 · 6 min read

Host-header injection: proving the reflected value reaches a dangerous sink

Reflecting an injected Host header is the first step - confirming that value reaches a password-reset link or a redirect target is the step that decides severity.

Applications lean on the Host header more than their authors remember. Password-reset links, email confirmation paths and self-referencing redirects are often built from the request host rather than a configured base URL. Reflect an injected host and you have reachability; show that the injected value reaches one of those sinks and you have decided the severity. The gap between the two is the entire finding.

01The Host header becomes an absolute URL

A framework that writes https://{request.host}/reset?token=... is trusting a client-supplied value to build a link it will email to a real user. Change the host and you change where that link points - and where the token travels when the user clicks.

Host: attacker.example reset email ──► https://attacker.example/reset?token=SECRET token exfiltrated on click ✗ high redirect ──► Location: https://attacker.example/... open redirect ~ medium body echo ──► "You are on attacker.example" reflection only note
One injected header, three sinks, three different severities. The sink reached is the whole story.

02Probe the sinks, not just the echo

The evidence bar is a benign, nonce-bearing hostname - never a real attack domain, never a payload that executes - checked against each sink in sequence. Seeing the nonce echoed in the body is step one. Seeing it become the origin of a reset link or a Location header is the step that raises the finding.

host-sink-probe.httphttp
# request a reset with an injected, benign nonce host
POST /account/reset HTTP/1.1
Host: ap-probe-62615533.example
X-Forwarded-Host: ap-probe-62615533.example

email body / captured link:
  https://ap-probe-62615533.example/reset?token=...
# the nonce host reached the link builder == confirmed, not just reflected
Proven - HighHost-header injection reaches the reset-link builder - /account/reset
X-Forwarded-Host: ap-probe-62615533.example ──► reset link built as https://ap-probe-62615533.example/reset?token=... nonce host controls the token's destination; no real inbox targeted
Benign nonce host, no live attack domain. High: a real host would send the reset token to an attacker on click. Redirect-only sinks rate lower.

03Frameworks disagree on what to trust

Whether X-Forwarded-Host is honoured by default varies by framework, and a CDN in front adds another rewrite. A probe validated against one stack quietly fails against another, so the engine tests the behaviour rather than assuming it.

FrameworkX-Forwarded-Host by defaultCommon pitfall
RailsHonoured unless host authorization is setMissing config.hosts allowlist
SpringRespected when behind a forwarded-headers filterForwardedHeaderFilter trusting unvalidated proxies
DjangoGated by ALLOWED_HOSTSWildcard ALLOWED_HOSTS in non-debug deploys
ExpressReads req.headers.host directlyTrusting the header with no proxy validation

04Severity is the sink, not the reflection

A controlled host in a password-reset link is high - the token leaves with the user. A host that only lands in a redirect target is a medium open redirect. A host that is merely echoed in a page body, reaching no dangerous sink, is a note. We rate the sink we proved, never the echo we saw.

3
sinks, three severities
nonce
benign host - no real domain
0
real inboxes or users touched

Build links from a configured base URL and validate the host against an allowlist. The header stops being an input the attacker can steer, and the reset link goes back to pointing where it should.

Evidence over heuristicsReachable beats foundMonitor → Block: rolling out a gate without slowing teamsIaC-grounded threat modelingAir-gapped AppSec with no phone-homeOne platform vs three tools
See it on your own app →