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

● CVE-2023-32558 · CVSS 7.5 · 高危
安全研究 · FlawOpen

深度技术拆解:CVE-2023-32558: Node.js Permission Model Filesystem Escape Teardown

CVE-2023-32558 源代码级技术深度解析与系统加固工程指南:深入剖析漏洞触发条件、攻击利用链条与加固补丁的具体实现。

💡 通俗易懂的原理解析 (ELI5)

通俗原理解析: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.

核心概念与专有名词

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.

根本原因剖析 (Root Cause)

根本原因在于开源系统中未经验证的边界参数,导致状态不同步并绕过安全控制。

攻击执行流程分解

Step 1

攻击阶段剖析:Start Sandboxed Node.js Process

技术利用机制与执行路径分析:A developer runs untrusted code with node --permission --allow-fs-read=/tmp app.js.

Step 2

攻击阶段剖析:Access Internal Binding

技术利用机制与执行路径分析:The script invokes const binding = process.binding('fs');.

Step 3

攻击阶段剖析:Bypass High-Level JS Checks

技术利用机制与执行路径分析:The script calls binding.open('/etc/passwd') directly, bypassing fs.readFile() wrappers.

Step 4

攻击阶段剖析:Arbitrary System File Read

技术利用机制与执行路径分析:The native C++ binding executes without permission checks, leaking confidential server files.

源代码对比:漏洞与安全实现

存在漏洞的实现
// 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);
}
加固后的安全修复
// 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);
}

工程与系统安全加固清单

参考来源