It all started while I was listening to Episode 129 of the Critical Thinking podcast titled “Is this how Bug Bounty Ends?” During a segment on bug bounties, the host pulled up a Node.js security fix page. While going through the list, one particular CVE caught my eye.
It looked simple—something that could be exploited with a bit of code. That was enough to get me curious. I paused the episode, dug into the CVE details, and thought: Why not try writing a proof of concept (PoC) and see if it really works?
That small decision turned into a hands-on dive into Node.js internals, security behaviors, and exploit development. This post breaks down what I found, how I built the exploit, and what you can learn from it.
Chapter 1:
What’s the issue with CVE-2025-23167?
According to the NVD (National Vulnerability Database), CVE-2025-23167 is a flaw in the HTTP parser used in Node.js v20. The issue is that the server allows HTTP headers to be ended with \r\n\rX
instead of the correct \r\n\r\n
.
This difference creates a security gap. It can be used to perform HTTP request smuggling technique where attackers sneak in a second hidden request inside a normal one. If the app is behind a proxy, this hidden request might bypass security rules and reach the backend server directly.
If the app is using Node.js 20.x before version 20.19.2, app could be vulnerable. This was fixed in Node.js 20.19.2 and later, where the team upgraded the internal HTTP parser (llhttp) to version 9. This update ensures that headers must end correctly, blocking the smuggling technique.
Chapter 2:
What is HTTP Request Smuggling?
Let’s understand this with a simple example. Imagine you stack two pizza boxes and send them through a security check:
- The top box is opened and inspected.
- The second box is ignored—and that’s where the problem is.
That’s similar to how HTTP request smuggling works.
In this attack, an attacker sends one HTTP request that secretly includes a second one inside it. If the server and proxy don’t agree on where the first request ends, the second one might go unnoticed and get processed without proper checks. This can lead to:
- Information leaks
- Bypassed access control
- Other unintended behavior
It happens because of inconsistent parsing between systems. One system sees the request differently than the other—and that’s where the attacker sneaks in. Click here to learn more!
Chapter 3:
Setting Up the Lab Environment
To test this vulnerability, I used Node.js version 20.0.0, which is one of the versions affected by CVE-2025-23167. Created a basic Node.js application with a single route:
- GET / – responds with a simple message.
This minimal setup was enough for testing, since the vulnerability lies in how Node.js handles HTTP parsing—not in the complexity of the application itself.
The idea was to simulate a real Node.js server running a vulnerable version, so I could observe how it behaves when it receives malformed HTTP requests.
Chapter 4:
Building the Exploit
Wrote a basic Python script using raw sockets. The goal is to send a crafted HTTP request that takes advantage of the incorrect header termination (\r\n\rX) and smuggles in a second request behind it.
The first request ends with \rX instead of \r\n\r\n, which is invalid, but accepted by vulnerable versions of Node.js. This tricks the parser into intrepret the second request as well. If the server processes both requests as separate ones, it means the vulnerability exists.
The script uses a raw TCP connection (socket.create_connection) to send the request byte-for-byte—important because some high-level libraries clean up malformed data, which would defeat the test. We use .encode(‘latin1’) and .decode(‘latin1’) to preserve raw byte formatting when printing and sending data.
After sending the payload, the script collects all the response data from the server and If the server returns multiple HTTP responses, it suggests that both requests were processed separately. That confirms the presence of HTTP request smuggling.
Chapter 5:
How to use the Exploit?{#chapter5}
Using the exploit is simple. Just follow these steps:
- Navigate to the GitHub repository where the exploit is hosted. Click Here! (or) https://github.com/abhisek3122/CVE-2025-23167
- Clone the repository to your local machine
- Run the exploit using Python 3 (remove the quotes while running):
- python3 exploit.py “Domain or IP” “PORT”
That’s it. If the target is running a vulnerable version of Node.js (before 20.19.2), and multiple HTTP responses are returned, the script will detect and alert you about the potential vulnerability.
SAMPLE INPUT & OUTPUT
Chapter 6:
What did I learn?{#chapter6}
- Even though the vulnerability looked simple on the surface, it took time to fully understand it, set up different lab environments, and figure out reliable ways to test it.
- I ran into various dependency issues during setup and had to troubleshoot and fix them one by one. Working through all of this gave me a deeper understanding of how Node.js handles HTTP parsing and why small inconsistencies in protocol handling can lead to serious vulnerabilities.
Hope you had fun reading this and found it helpful! If you run into any issues or have questions, feel free to reach out—happy to help.