The Node.js platform is used by millions of developers worldwide to build scalable and efficient server-side applications. One of its emerging security features is the --experimental-permission flag, designed to restrict what resources JavaScript code can access at runtime. Unfortunately, CVE-2023-30587 exposes a way for attackers to bypass these important restrictions under certain conditions.

This article breaks down CVE-2023-30587 in simple terms, explains how attackers can exploit it, and gives code snippets to illustrate the vulnerability. We also provide links to original references and further reading for those who want to dive deeper.

TL;DR

- Vulnerability: Node.js 20’s experimental permission system can be bypassed via the built-in inspector and Worker APIs.

Attack scope: Only affects environments using the experimental permission model.

- Technique: Alters the Worker’s isInternal status by exploiting the inspector’s ability to attach before permission enforcement.

What’s the Node.js Experimental Permission Model?

Introduced as an experimental feature in Node.js 20, the permission model aims to restrict access to features like file system access, networking, or child process spawning via the --experimental-permission CLI flag. Think of it as a sandbox: code should be unable to access things it’s not meant to.

For example, running

node --experimental-permission --allow-fs-read=/home/user/ safeScript.js

…should normally prevent safeScript.js from reading files outside /home/user/.

> Warning: This model is still in experimental stages and NOT yet considered stable for production.

Where Does node:inspector Fit In?

node:inspector lets you connect debugging tools (like Chrome DevTools) to your Node process. When the inspector is active, Node.js often grants certain privileges to internal "Worker" threads, like debugging hooks.

The Node.js Worker class can run scripts in separate threads.

- Internally, a symbol called kIsInternal marks whether a Worker is an internal system task or a user-created script.
- When an *inspector* is attached inside the Worker constructor (before the actual implementation is created), the isInternal status may be set to true even for user code.

If internal, the Worker bypasses several permission checks.

- Result: malicious code, once in a Worker with inspector attached, can access restricted resources even under the permission model.

To understand this issue, let’s look at a simplified example exploiting CVE-2023-30587

// demo-exploit.js
const { Worker } = require('worker_threads');
const inspector = require('inspector');

// Start the inspector programmatically
inspector.open(9229, '127...1', true);

// This Worker will inherit elevated permissions due to the bug
const worker = new Worker(`
    const fs = require('fs');
    // Try to read a restricted file
    const secret = fs.readFileSync('/etc/shadow', 'utf8');
    parentPort.postMessage(secret);
`, { eval: true });

worker.on('message', (msg) => {
    console.log('Leaked secret:', msg);
});

How this works

- Even with --experimental-permission and without file access permissions, the worker is still able to read protected files if the inspector is attached at the right moment.

This is not intended behavior.

Reference from the official Node.js security advisory:
> “If the built-in 'node:inspector' module is enabled, it is possible to provide the Worker class with the kIsInternal Symbol to create an internal Worker, which may then manipulate process permissions...”
Node.js Security Announcements

Real-Life Exploitation

An attacker would generally need code execution inside your Node.js process and/or the ability to activate the inspector and spawn Workers. In some hosting environments, merely enabling the inspector could expose this risk.

Spawns a Worker that tricks Node into marking it as “internal”.

4. Worker code circumvents the filesystem/network/etc restrictions.

- Update Node.js: The fix for CVE-2023-30587 is included in v20.4. and above (Release Notes).
- Don’t use the permission model for security isolation in production. (It is still experimental!)
- Audit your codebase for dependency or runtime code that could spawn Workers and activate the inspector.

References and Further Reading

- Node.js CVE-2023-30587 Advisory
- Node.js Permission Model Documentation (Experimental)
- Node.js Worker Threads Docs
- Original security patch (GitHub Commit)

Summary

CVE-2023-30587 exposes a design flaw in how Node.js’s experimental permissions model interacts with Workers and the built-in inspector. Attach the inspector at the right step, and suddenly your “restricted” Workers can do almost anything.

Until Node’s permission system matures, never trust it alone for sandboxing untrusted code. Combine defense-in-depth: keep Node.js patched, don’t expose the inspector, and restrict the use of dynamic code and Workers where possible.


*Stay safe, and always keep your runtime security up to date!*

Timeline

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