Host Header Injection
A password-reset link is built from the request's X-Forwarded-Host header. An attacker forges it to steer a real user's reset email to a lookalike domain, steal the clicked token, and take over the account — then you ship the configured-base-URL fix.
What Is Host Header Injection?
Host Header Injection is the special case where an application uses the HTTP host header, a value the requester controls (the raw Host header or a proxy's X-Forwarded-Host ), to build URLs it sends back to other parties. The most damaging variant is the password-reset email: a forged host header on a normal reset request makes the application email the legitimate user a reset link that begins with the attacker's domain and ends with a real token bound to the user's account. The victim clicks (the email is genuine, sent by the app from its real address; the token is real), their browser delivers the token to the attacker's server, and the attacker replays it against the real reset endpoint within its TTL to take over the account. The vulnerability lives anywhere the application treats the request host as if it identified the application, when the header actually identifies the requester's claim. This exercise puts you on both sides. As Bob, you use the forgot-password page to see the request the app makes, read the endpoint and body schema in the browser's Network panel, then rebuild the request in the API Tester with a registered lookalike ( account-verdanta.app , deliberately a different TLD from the real account.verdanta.com ) as the X-Forwarded-Host header, targeting Alice's account. As Alice, you receive the genuine-looking reset email and click the link, landing on the lookalike domain. The view then cuts back to Bob, who tails his relay server's access log to catch the reset token the click delivered and replays it against the real reset endpoint to seize the account. Back as Alice, you read the security alert that traces the takeover to the header-derived link, then, as the engineer who owns the service, you open the handler, see the host read from the request, apply the configured APP_BASE_URL fix, and prove it by re-running the exact forged request against the patched service and watching the emailed link come back clean. The exercise closes with quiz questions on what the bug class is, what the right replacement looks like, and where else the same pattern hides.
What You'll Learn in Host Header Injection
- Recognize <code>req.headers.host</code> as untrusted input, the header identifies the requester's claim about where they sent the request, not the application's identity
- Apply the configured-constant fix: build URLs from <code>process.env.APP_BASE_URL</code> (set per environment in deployment config), never from request-derived values
- Pair with a strict Host-header allow-list at the framework or proxy level so any other call site still reading the header is hardened
- Audit every URL-building site (email templates, signed download URLs, redirects, cache keys, email footers) for the same anti-pattern
- Internalize: an attacker controls request headers, an operator controls deployment configuration, security-sensitive URL bases belong on the operator side of that line
Host Header Injection — Training Steps
-
Use the reset feature
Bob is after Verdanta accounts. It is a personal-finance app, so every account he takes over links straight to someone's real bank balances. He does not know anyone's password, so he starts where he does not need one: the forgot-password page. First he submits it with a throwaway address of his own, so he can watch exactly what request the page makes.
-
Find the endpoint
The request went out and the browser recorded it. Bob opens the Network panel to read where the page actually sent it.
-
Read the body schema
One request, and it tells Bob everything he needs. He checks what the page put in the request body.
-
Forge the reset request
Bob rebuilds that same request in the API Tester, where he can add one thing a browser form never lets him set: a forged host header. He asks for a reset on Alice's real account but tells the app the request arrived at account-verdanta.app, a lookalike domain he registered and controls (note the .app, where Verdanta's real site is account.verdanta.com). If the app builds the reset link from the header, Alice's email will carry his domain and her real token.
-
The response gives nothing away
The API answers with a plain 200 and a generic message. Nothing in the response hints that anything went wrong, that is exactly why this bug is quiet. The damage is not in the response Bob can see. It is in the email now sitting in Alice's inbox, carrying a link that starts with his domain.
-
A password-reset email
Off the clock, Alice gets a password-reset email from Verdanta. She did not ask for one, but it looks exactly like the real thing: same sender, same wording, same layout.
-
Click the reset link
Alice clicks the link to reset her password, exactly the way anyone would.
-
Look at the address bar
The reset page opens. It looks like Verdanta, but the address bar tells a different story.
-
Catch the token
Alice's click never reached Verdanta. Her browser opened account-verdanta.app, the server Bob controls, and the reset token rode along in the URL. Back on his own machine, Bob tails the access log for that server to catch what just arrived.
-
Replay the token
The token is Alice's, but it is in Bob's hands now, and reset tokens do not care who presents them. Before it expires, he replays it against the real endpoint and sets a password of his own choosing.