Description
The HTTP Host header tells a web server which virtual host or application the request is intended for. Web applications frequently use the Host header value to construct absolute URLs in responses—password reset links, email confirmation links, redirect targets, and API base URLs. When an application trusts and uses the Host header without validating it against a whitelist of allowed hostnames, attackers can supply an arbitrary value and cause the application to generate URLs pointing to attacker-controlled infrastructure.
CWE-644 (Improper Neutralization of HTTP Headers for Scripting Syntax) captures this class of header injection. A03:2021 Injection applies because the attacker is injecting a malicious value into a header that is subsequently used in application logic without sanitization. Host header injection is particularly insidious because it exploits trusted application functionality—the password reset feature—rather than requiring a separate XSS or injection point.
Password reset poisoning is the most critical real-world exploitation scenario. The application sends a password reset email containing a link like https://[Host-header-value]/reset?token=<token>. If the victim clicks this link, they contact the attacker's server, which receives the token. The attacker can then use the token to reset the victim's password.
How It Works
A standard password reset flow with a vulnerable Host header trust:
POST /reset-password HTTP/1.1
Host: attacker.example.com
Content-Type: application/x-www-form-urlencoded
email=victim@example.com
The application generates a reset email with the attacker's domain in the link:
Subject: Reset your password
From: noreply@example.com
To: victim@example.com
Click here to reset your password:
https://attacker.example.com/reset?token=a1b2c3d4e5f6
When the victim clicks the link, their browser sends the request to attacker.example.com, delivering the token. The attacker captures it and resets the victim's password:
# Attacker's HTTP log shows:
GET /reset?token=a1b2c3d4e5f6 HTTP/1.1
Host: attacker.example.com
Additional attack variants use X-Forwarded-Host and X-Host as bypass headers when the application preferentially trusts override headers:
POST /reset-password HTTP/1.1
Host: example.com
X-Forwarded-Host: attacker.example.com
email=victim@example.com
Penetration testers test Host header injection using Burp Suite's Collaborator to receive out-of-band DNS and HTTP callbacks:
POST /reset-password HTTP/1.1
Host: <burp_collaborator_subdomain>.burpcollaborator.net
email=victim@example.com
# Check Collaborator for DNS/HTTP interaction — confirms server used the injected host
Web cache poisoning via Host header injection occurs when the cache key does not include the Host header but the response body reflects it as an absolute URL—poisoning the cache with the attacker's domain for all subsequent users.
Impact
- Account takeover via password reset poisoning — Victims' password reset tokens are delivered to attacker-controlled servers.
- Web cache poisoning — Host header reflection in cached responses poisons the cache with attacker-controlled URLs.
- Server-side request forgery — Internal routing systems that trust the
Hostheader to determine forwarding destinations can be redirected to internal services. - Password reset link spoofing — Even without token theft, poisoned links redirect users to phishing pages after authenticating with a valid token.
- CORS bypass — Applications that dynamically build
Access-Control-Allow-Originvalues from theHostheader can be manipulated to permit cross-origin access.
Detection
- Intercept a password reset request and modify the
Hostheader to a Burp Collaborator subdomain; trigger the reset and check whether the Collaborator receives a DNS or HTTP interaction. - Test
X-Forwarded-Host,X-Original-Host,X-Host, andForwarded: host=headers as alternate injection vectors for applications that preferentially trust proxy headers. - Test double
Hostheaders: some HTTP parsing libraries use the second value, while others use the first—Host: example.com\r\nHost: attacker.com. - Check whether password reset links in received emails contain the value of the injected
Hostheader rather than the application's actual domain. - Test with an absolute URL in the
Hostheader:Host: example.com@attacker.comorHost: attacker.com#example.com.
Remediation
Validate the Host header against a whitelist. Maintain a list of allowed hostnames and reject or sanitize requests where the Host value does not match:
ALLOWED_HOSTS = ['example.com', 'www.example.com', 'api.example.com']
def get_safe_host(request):
host = request.headers.get('Host', '').split(':')[0]
if host not in ALLOWED_HOSTS:
raise ValueError(f"Invalid host: {host}")
return host
Hardcode the application's base URL for security-sensitive operations. Password reset links, email confirmation URLs, and any security-critical URLs should be constructed from a hardcoded configuration value, never from the Host header:
BASE_URL = settings.APPLICATION_BASE_URL # "https://example.com"
reset_link = f"{BASE_URL}/reset?token={token}"
Strip and distrust forwarding headers. Unless operating behind a trusted reverse proxy, strip X-Forwarded-Host, X-Original-Host, and X-Host headers at the ingress point before requests reach the application.
