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

vulnerabilidade 소스 코드 심층 기술 분석 및 시스템 보안 강화 가이드: 취약점 근본 원인과 패치 메커니즘 분석.

💡 알기 쉬운 설명 (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.

소스 코드 비교: 취약한 구현 vs 보안 패치

취약한 구현
// 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);
}

엔지니어링 및 시스템 보안 강화 체크리스트

출처