Web Security Explained12 October 2026

Host Header Injection: How It Works and How UK Sites Can Prevent It

Host header injection exploits web applications that trust the HTTP Host header without validation. It enables password reset poisoning, cache poisoning, and SSRF attacks on UK websites.

By Yrzo AI — UK cybersecurity specialists

What Is the HTTP Host Header?

Every HTTP request sent by a browser includes a \`Host\` header that tells the server which website is being requested. When a single server hosts multiple websites — a configuration known as virtual hosting — the server uses the \`Host\` header to route the request to the correct web application.

A request to \`example.com\` looks like this:

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

The server reads the \`Host\` header, determines which application should handle this request, and processes it accordingly.

The problem arises when a web application uses the \`Host\` header value in its logic — to generate links, to look up configuration, to make backend requests — without verifying that it is the expected value. An attacker who can control the \`Host\` header can manipulate what the application does.

---

What Is Host Header Injection?

Host header injection is a class of vulnerability where an attacker supplies a manipulated \`Host\` header value, and the application uses that value in a way that causes unintended behaviour.

The most impactful attack enabled by host header injection is **password reset link poisoning** — a scenario where an attacker can cause a password reset email to contain a link pointing to a domain they control, delivering the victim's reset token to the attacker.

---

How Host Header Injection Works

Most web servers accept the \`Host\` header value from the incoming request and make it available to the application. The application then uses it — often to build absolute URLs — without further validation:

\`\`\`python # Vulnerable pseudo-code def send_password_reset(email): token = generate_token(email) host = request.headers.get("Host") # Taken directly from the request reset_url = f"https://\${host}/reset?token=\${token}" send_email(email, reset_url) \`\`\`

An attacker requests a password reset for the victim's email address but sets the \`Host\` header to their own domain:

\`\`\` POST /forgot-password HTTP/1.1 Host: attacker.com

email=victim@example.com \`\`\`

The application generates a reset link using the attacker-supplied host and emails it to the victim. If the victim clicks it, their reset token is sent to the attacker's server — and the attacker now controls the victim's account.

Header Variants

Modern infrastructure introduces additional headers that web applications may use as alternatives to the standard \`Host\` header:

- \`X-Forwarded-Host\` — used by proxies to pass the original host to the backend - \`X-Host\` — a non-standard alternative used by some frameworks - \`X-HTTP-Host-Override\`

A server may be configured to ignore a manipulated \`Host\` header but still accept and use the value from \`X-Forwarded-Host\`, making the application vulnerable via a different injection point.

---

Attack Scenarios

Password Reset Poisoning

The attacker submits a password reset request for the target's email, manipulates the \`Host\` header to point to their own domain, and waits for the victim to click the link in the email they receive. The email arrives from the legitimate source and the link uses valid HTTPS — it is difficult for the victim to identify as malicious.

Web Cache Poisoning

CDNs and caching layers may cache responses keyed on the URL but ignore the \`Host\` header for caching decisions. An attacker can inject a malicious \`Host\` value, trigger a response that gets cached, and have that poisoned response served to other users. If the application includes the host in the response body — for example, in an absolute URL within a JavaScript file — subsequent legitimate users receive the attacker's version.

SSRF via Host Header

Some applications use the \`Host\` header to determine which backend service to forward a request to. Supplying an internal IP address as the \`Host\` value can cause the server to make requests to internal network resources:

\`\`\` GET /api/status HTTP/1.1 Host: 169.254.169.254 \`\`\`

This is the AWS Instance Metadata Service endpoint. A vulnerable application that routes backend requests using the \`Host\` header might forward this to the metadata service, returning cloud credentials to the attacker.

Virtualhost Brute-Forcing

By iterating \`Host\` header values and observing differences in responses, an attacker can enumerate subdomains and internal hostnames, potentially revealing internal applications or development environments not meant to be publicly accessible.

---

Testing for Host Header Injection

\`\`\`bash # Basic test curl -H "Host: attacker.com" https://target.com/forgot-password -d "email=test@test.com"

# Test X-Forwarded-Host header curl -H "Host: target.com" -H "X-Forwarded-Host: attacker.com" https://target.com/forgot-password -d "email=test@test.com" \`\`\`

The tester observes whether the injected value appears in the application's response, in emails sent by the application, or causes the application to make requests to the injected host.

---

Prevention

Validate the Host Header Against an Allowlist

The most robust fix is to validate the incoming \`Host\` header against a known list of acceptable values:

\`\`\`python ALLOWED_HOSTS = ["example.com", "www.example.com"]

def get_host(): host = request.headers.get("Host", "").split(":")[0] if host not in ALLOWED_HOSTS: raise ValueError(f"Untrusted Host header: \${host}") return host \`\`\`

Most web frameworks provide configuration for this. Django has \`ALLOWED_HOSTS\`. Rails validates \`Host\` by default in production. Check your framework's documentation.

Configure the Server to Reject Unexpected Hosts

Configure your web server to explicitly define the expected \`Host\` values and reject requests with unrecognised values before they reach the application:

\`\`\`nginx server { listen 443 ssl; server_name example.com www.example.com;

if (\$host !~ ^(example\\.com|www\\.example\\.com)\$) { return 444; } } \`\`\`

Use Absolute URLs From Configuration, Not From the Request

Never derive the base URL for generated links from the \`Host\` header. Hardcode it in your application's configuration:

\`\`\`python # Vulnerable base_url = f"https://\${request.headers.get('Host')}"

# Safe base_url = settings.BASE_URL # e.g., "https://example.com" \`\`\`

Strip Ambiguous Headers at the Proxy

If your infrastructure does not require \`X-Forwarded-Host\` or similar headers, block them at the load balancer before they reach your application.

---

Summary

Host header injection exploits the gap between what web applications assume — that the \`Host\` header reliably identifies who they are — and reality, which is that the header is user-supplied and can be set to anything. Password reset poisoning is the most dangerous consequence, allowing account takeover without any interaction with the victim's inbox beyond getting them to click a plausible-looking link.

Prevention is straightforward: validate the \`Host\` header against an explicit allowlist at the server level, and derive base URLs from application configuration rather than request headers.

Related reading: [What Is SSRF Vulnerability?](/blog/what-is-ssrf-vulnerability) · [What Is HTTP Parameter Pollution?](/blog/what-is-http-parameter-pollution) · [What Is Open Redirect Vulnerability?](/blog/what-is-open-redirect-vulnerability)

Find out if your website has these vulnerabilities

Yrzo AI runs 44 automated security checks and delivers a full report in under 20 minutes. Starting from £399.

Scan your website →