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);
}

エンジニアリング&システム堅牢化チェックリスト

参考資料