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

● CVE-2023-32558 · CVSS 7.5 · Haute
Recherche · FlawOpen

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

Analyse technique détaillée du code source et mesures de durcissement pour vulnerabilidade : How accessing low-level C++ internal bindings via process.binding('fs') bypassed the experimental --permission --allow-fs-read flags.

💡 Explication en Langage Simple (ELI5)

Analogie concrète : 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.

Concepts Clés et Termes

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.

Analyse de Cause Racine

La cause fondamentale provient de paramètres de limites non validés dans les systèmes open source, permettant une désynchronisation d'état et le contournement des contrôles de sécurité.

Déroulement de l'Attaque Étape par Étape

Step 1

Étape d'attaque : Start Sandboxed Node.js Process

Mécanisme technique d'exploitation : A developer runs untrusted code with node --permission --allow-fs-read=/tmp app.js.

Step 2

Étape d'attaque : Access Internal Binding

Mécanisme technique d'exploitation : The script invokes const binding = process.binding('fs');.

Step 3

Étape d'attaque : Bypass High-Level JS Checks

Mécanisme technique d'exploitation : The script calls binding.open('/etc/passwd') directly, bypassing fs.readFile() wrappers.

Step 4

Étape d'attaque : Arbitrary System File Read

Mécanisme technique d'exploitation : The native C++ binding executes without permission checks, leaking confidential server files.

Code Source : Vulnérable vs Sécurisé

IMPLÉMENTATION VULNÉRABLE
// 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);
}
PATCH SÉCURISÉ ET ROBUSTE
// 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);
}

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources