flawopen.com/Teardowns/cve-2023-32558-nodejs-permission-model-fs-escape

● CVE-2023-32558 · CVSS 7.5 · Alta
Investigación · FlawOpen

CVE-2023-32558: Node.js Permission Model Filesystem Escape Teardown

Análisis técnico del código fuente y mitigaciones de ingeniería para vulnerabilidade: How accessing low-level C++ internal bindings via process.binding('fs') bypassed the experimental --permission --allow-fs-read flags.

💡 Explicación en Lenguaje Sencillo (ELI5)

Analogía práctica: Imagine a high-security office building with security guards at the front doors checking badges. A worker who doesn't have a badge walks around back, opens the maintenance door marked 'Internal Staff Only', and walks straight into the vault. In Node.js, the developer guarded the public JavaScript functions, but forgot to lock the underlying C++ internal door.

Conceptos Clave y Términos

Node.js Permission Model
The experimental --permission CLI flag restricting process capabilities like filesystem, child process, and worker access.
process.binding()
The legacy internal Node.js API that directly exposes native C++ bindings to JavaScript code.
Path Containment
Ensuring file operations remain strictly inside specified directory boundaries.
Defense-in-Depth
Enforcing security checks at the lowest possible layer (native C++ layer) rather than superficial JS wrappers.

Análisis de Causa Raíz

La causa raíz se debe a parámetros de límite no validados en sistemas de código abierto, lo que permite la desincronización de estado y la elusión de controles de seguridad.

Flujo de Ataque Paso a Paso

Step 1

Fase de ataque: Start Sandboxed Node.js Process

Mecanismo técnico de explotación: A developer runs untrusted code with node --permission --allow-fs-read=/tmp app.js.

Step 2

Fase de ataque: Access Internal Binding

Mecanismo técnico de explotación: The script invokes const binding = process.binding('fs');.

Step 3

Fase de ataque: Bypass High-Level JS Checks

Mecanismo técnico de explotación: The script calls binding.open('/etc/passwd') directly, bypassing fs.readFile() wrappers.

Step 4

Fase de ataque: Arbitrary System File Read

Mecanismo técnico de explotación: The native C++ binding executes without permission checks, leaking confidential server files.

Código Fuente: Vulnerable vs. Seguro

IMPLEMENTACIÓN VULNERABLE
// VULNERABLE: src/node_file.cc before patch
static void Open(const FunctionCallbackInfo<Value>& args) {
    Environment* env = Environment::GetCurrent(args);
    
    // ROOT CAUSE:
    // Only verified permissions if called through JS 'fs' module wrappers!
    // Direct callers of process.binding('fs').open() evaded the check!
    const char* path = *Utf8Value(env->isolate(), args[0]);
    int fd = uv_fs_open(..., path, ...);
    args.GetReturnValue().Set(fd);
}
PARCHE SEGURO Y ROBUSTO
// SECURE: src/node_file.cc patch
static void Open(const FunctionCallbackInfo<Value>& args) {
    Environment* env = Environment::GetCurrent(args);
    
    const char* path = *Utf8Value(env->isolate(), args[0]);
    
    // 1. Enforce permission verification directly inside native C++ binding layer
    if (env->permission()->is_enabled()) {
        if (!env->permission()->is_granted(PermissionScope::kFileSystemRead, path)) {
            THROW_ERR_ACCESS_DENIED(env, "Access to path %s denied by Permission Model", path);
            return;
        }
    }
    
    int fd = uv_fs_open(..., path, ...);
    args.GetReturnValue().Set(fd);
}

Lista de Verificación de Seguridad para Ingeniería

Fuentes