Skip to content
Fast-turnaround security assessments available — 10+ years development & security experienceGet started
Back to Knowledge Base
vulnerabilityCWE-644Typical severity: High

Host Header Injection: How Trusted Headers Become Attack Vectors

·9 min read

Host Header Injection: How Trusted Headers Become Attack Vectors

The HTTP Host header tells a web server which website the client wants to reach. In the early days of the web, this was a simple routing mechanism for virtual hosting — one server, multiple websites. Today, applications rely on the Host header for far more than routing. They use it to generate absolute URLs, construct email links, determine redirect destinations, and create cache keys. The problem: the Host header comes from the client and is trivially manipulated.

Host header injection is not a single vulnerability but a class of attacks that exploit an application's trust in this user-controlled value. The consequences vary from account takeover through password reset poisoning to widespread content manipulation through cache poisoning.

How the Host Header Works

Every HTTP/1.1 request includes a Host header:

GET /reset-password?token=abc123 HTTP/1.1
Host: example.com

The server uses this header to determine which virtual host should handle the request. But many applications also use it programmatically:

python
# Common pattern — using the Host header to build URLs
reset_link = f"https://{request.headers['Host']}/reset?token={token}"
send_email(user.email, f"Reset your password: {reset_link}")

An attacker can set the Host header to any value:

GET /forgot-password HTTP/1.1
Host: evil.com

If the application uses the Host header to construct the reset link, the victim receives an email with a link pointing to https://evil.com/reset?token=abc123.

Attack Vectors

Password Reset Poisoning

This is the most impactful and most commonly exploited host header injection attack. The attack flow:

  1. The attacker identifies that the application uses the Host header when generating password reset links
  2. The attacker submits a password reset request for the victim's email address
  3. The attacker modifies the Host header to point to a server they control
  4. The application generates a reset link using the attacker's Host value and sends it to the victim's email
  5. The victim receives a legitimate email from the real application (the email itself is genuine — only the link is modified)
  6. When the victim clicks the link, they are directed to the attacker's server, which captures the reset token
  7. The attacker uses the captured token on the real application to reset the victim's password

The attack is particularly effective because the email is genuinely sent by the application. Email security checks (SPF, DKIM, DMARC) all pass. The email content looks legitimate. Only the URL domain is different, and many users do not inspect link destinations before clicking.

In a security assessment of a SaaS platform with over 50,000 users, testers found that the password reset feature used the Host header verbatim. The reset email template included:

Click here to reset your password:
https://{Host}/auth/reset?token={token}&email={email}

By submitting reset requests with a modified Host header, the testers demonstrated that any user account could be taken over. The platform had no rate limiting on reset requests, meaning an attacker could systematically target every user.

Web Cache Poisoning

When a caching layer (CDN, reverse proxy cache, or application cache) sits in front of an application, host header injection can poison the cache and serve malicious content to every user.

The attack relies on a mismatch between the cache key and the response content:

  1. Cache key: The cache typically keys responses on the URL path and query string
  2. Response content: The server generates the response using the Host header, which is not part of the cache key
  3. The mismatch: An attacker requests a page with a manipulated Host header. The server generates a response with malicious URLs. The cache stores this response keyed to the URL path. Every subsequent user requesting the same path receives the poisoned response.
GET / HTTP/1.1
Host: evil.com

Response:
<html>
<head>
  <link rel="stylesheet" href="https://evil.com/styles.css">
  <script src="https://evil.com/app.js"></script>
</head>
...

This response is cached. Every user who visits the homepage now loads CSS and JavaScript from the attacker's server. The attacker has achieved persistent cross-site scripting through the cache, without exploiting any XSS vulnerability in the application code.

During an assessment of a media company's website, testers discovered that the application generated absolute URLs for static assets using the Host header, and the CDN cached responses without including the Host header in the cache key. A single poisoned request caused the homepage to load an attacker-controlled JavaScript file for all visitors until the cache expired — a thirty-minute window affecting hundreds of thousands of users.

Server-Side Redirect Manipulation

Applications frequently use the Host header to construct redirect URLs:

python
# After login, redirect to the dashboard
return redirect(f"https://{request.headers['Host']}/dashboard")

An attacker who can manipulate the Host header can redirect users to a phishing site after authentication. The victim enters their credentials on the legitimate login page, and the POST request is processed correctly, but the subsequent redirect sends them to the attacker's site.

This is particularly dangerous in OAuth flows, where redirect URIs are critical security parameters. If the application derives the redirect URI from the Host header, the entire OAuth token exchange can be hijacked.

Internal Header Injection

Some applications and proxies use additional headers alongside Host:

  • X-Forwarded-Host — Set by reverse proxies to indicate the original Host header
  • X-Host — Non-standard header used by some frameworks
  • X-Forwarded-Server — Another proxy header

Applications may check the Host header but trust these alternative headers without validation:

GET /forgot-password HTTP/1.1
Host: legitimate.com
X-Forwarded-Host: evil.com

The application sees Host: legitimate.com and allows the request, but its URL generation function reads X-Forwarded-Host first (as it should behind a proxy), resulting in URLs pointing to the attacker's domain.

Virtual Host Brute-Forcing

In shared hosting environments, an attacker can enumerate virtual hosts by sending requests with different Host header values and observing the responses. This reveals internal applications, staging environments, and admin panels that are hosted on the same server but intended to be accessed only through specific domain names.

GET / HTTP/1.1
Host: admin.internal.example.com
→ 200 OK (admin panel exposed)

GET / HTTP/1.1
Host: staging.example.com
→ 200 OK (staging environment with debug mode enabled)

Prevention Strategies

Use a Hardcoded Canonical Domain

The most reliable fix is simple: never use the Host header for URL generation. Configure your application with a canonical domain name in its settings:

python
# In application configuration
CANONICAL_DOMAIN = "https://example.com"
 
# In code — use the configured domain, not the request header
reset_link = f"{settings.CANONICAL_DOMAIN}/reset?token={token}"
send_email(user.email, f"Reset your password: {reset_link}")

This eliminates the attack surface entirely. The Host header can contain anything — the application ignores it for URL generation.

Validate the Host Header

If your application must use the Host header (for example, to support multiple legitimate domains), validate it against an allowlist:

python
ALLOWED_HOSTS = {"example.com", "www.example.com", "app.example.com"}
 
def validate_host(request):
    host = request.headers.get("Host", "").split(":")[0]  # Strip port
    if host not in ALLOWED_HOSTS:
        return HttpResponse(status=400)

Most web frameworks provide this functionality:

  • Django: The ALLOWED_HOSTS setting rejects requests with unrecognized Host headers
  • Rails: The config.hosts setting performs the same validation
  • Express/Node: Requires manual implementation or middleware

Configure the Reverse Proxy

Your reverse proxy or load balancer should overwrite the Host header before forwarding requests to the application:

Nginx:

nginx
server {
    server_name example.com;
 
    location / {
        proxy_pass http://backend;
        proxy_set_header Host example.com;  # Override, don't pass through
        proxy_set_header X-Forwarded-Host $host;
    }
}

Apache:

apache
ProxyPreserveHost Off
ProxyPass / http://backend/
ProxyPassReverse / http://backend/
RequestHeader set Host "example.com"

This ensures the backend application always receives the correct Host value regardless of what the client sends.

Ignore or Validate Forwarded Headers

If your application reads X-Forwarded-Host, X-Forwarded-Proto, or similar headers, ensure these are set only by your trusted proxy infrastructure:

  1. Strip these headers at the edge — Your outermost proxy should remove any X-Forwarded-* headers sent by the client before adding its own
  2. Configure trusted proxy addresses — Only accept forwarded headers from known proxy IP addresses
  3. Use a shared secret — Some proxy configurations support a secret header to verify the request passed through the proxy

Cache Key Configuration

If you use a CDN or caching proxy, ensure the Host header is included in the cache key:

# Vary header tells caches to key on Host
Vary: Host

Alternatively, configure your CDN to include the Host header in its cache key derivation. Most CDN providers offer this configuration but do not enable it by default.

Additional Protections

Absolute URL validation in emails: Even with a canonical domain, verify that all URLs in outgoing emails match the expected domain pattern before sending.

Same-origin checks for redirects: After authentication, validate that redirect targets are same-origin or on an allowlist of permitted domains.

Monitor for anomalous Host values: Log and alert on requests with unexpected Host headers. These may indicate active exploitation attempts.

Testing for Host Header Injection

Testing is straightforward and should be performed on every application:

  1. Modify the Host header in password reset, email verification, and invitation flows. Check if the generated links use the modified value.
  2. Send duplicate Host headers — some servers use the first, others use the last:
    Host: evil.com
    Host: legitimate.com
    
  3. Test X-Forwarded-Host and similar headers while keeping the Host header legitimate
  4. Add a port to the Host headerHost: evil.com:legitimate.com sometimes bypasses basic validation
  5. Test with an absolute URL in the request line:
    GET https://evil.com/ HTTP/1.1
    Host: legitimate.com
    
    Some servers prioritize the URL in the request line over the Host header
  6. Check cache behavior — Send requests with modified Host headers and verify whether the response is cached and served to other users

Key Takeaways

Host header injection exploits a fundamental design assumption: that the Host header reflects the actual server. In practice, the Host header is an attacker-controlled input that should be treated with the same suspicion as any other user input.

The defenses are straightforward:

  1. Use a hardcoded canonical domain for all URL generation — never derive it from request headers
  2. Validate the Host header against an allowlist of permitted values
  3. Configure reverse proxies to overwrite the Host header before forwarding to backends
  4. Strip client-supplied X-Forwarded-* headers at the network edge
  5. Include the Host header in cache keys to prevent cache poisoning
  6. Test all email-generating features, redirect flows, and URL construction for host header trust

The vulnerability is simple, the fix is simple, but the consequences of leaving it unaddressed — account takeover, cache poisoning, and phishing — are severe.

Need your application tested for host header injection? Get in touch.

Need your application tested?

We find these vulnerabilities in real applications every day. Get a comprehensive security assessment with detailed remediation.

Request an Assessment
host-headerinjectioncache-poisoningpassword-resetweb-security

Summary

Host header injection exploits applications that trust the HTTP Host header for critical operations like password reset links, cache keys, and redirects — allowing attackers to poison caches, steal reset tokens, and redirect users to malicious sites.

Key Takeaways

  • 1The HTTP Host header is user-controlled and should never be trusted for security-sensitive operations
  • 2Password reset poisoning is the most common exploitation, allowing attackers to steal reset tokens by manipulating the link in reset emails
  • 3Cache poisoning via host header injection can serve malicious content to all users visiting a cached page
  • 4Applications must use a hardcoded canonical domain for generating URLs rather than relying on the Host header
  • 5Reverse proxies and load balancers should validate and overwrite the Host header before passing requests to backend applications

Frequently Asked Questions

Host header injection is a vulnerability that occurs when a web application uses the HTTP Host header value in its response without validation. Since the Host header is controlled by the client, an attacker can manipulate it to poison password reset links, pollute web caches, or trigger redirects to malicious domains.

When a user requests a password reset, many applications generate the reset link using the Host header value (e.g., https://{Host}/reset?token=abc123). An attacker initiates a reset for a victim's account while setting the Host header to their own domain. The victim receives a legitimate reset email but the link points to the attacker's server, which captures the reset token when clicked.

If a cache server uses the URL path (but not the Host header) as the cache key, an attacker can request a page with a manipulated Host header. The server generates a response containing malicious links based on the attacker's Host value, and the cache stores this response. All subsequent users requesting the same page receive the poisoned response from the cache.

Configure your application with a hardcoded canonical domain name and use it for all URL generation. Validate the Host header against a whitelist of expected values. Configure your reverse proxy to set the Host header to the correct value regardless of what the client sends. Never use the Host header to generate URLs in emails, redirects, or page content.