FlawOpen

flawopen.com/Teardowns/cve-2024-27980-nodejs-windows-batch-injection

● CVE-2024-27980 · CVSS 8.1 · 高
セキュリティ研究 · FlawOpen

CVE-2024-27980:Node.js Windowsの問題

Windowsではshellオプションなしでも、spawn()とspawnSync()でバッチファイルを起動するとコマンド注入が起きる可能性がありました。

💡 わかりやすい解説 (ELI5)

プログラムがバッチファイルへ引数を渡しても、Windowsではコマンドインタープリターを介して実行します。インタープリターは引数をコマンド構文として解釈することがあります。Node.jsは各引数の安全なエスケープを推測せず、バッチファイルの直接起動を拒否して修正しました。

主要な概念と専門用語

child_process.spawn()
実行ファイルと引数配列を指定してプロセスを起動するNode.js非同期APIです。
バッチファイル
Windowsシェルが解釈する.bat/.cmdファイルで、共通のargv解析を行う通常の実行ファイルとは異なります。
シェルメタ文字
コマンドインタープリターにより意味が変わり、コマンド構造を変更し得る文字です。
EINVAL
Node.jsがバッチファイルの危険な直接起動を拒否するときに返すエラーコードです。

フレームワーク対応

  • このガイドで説明する弱点の CWE 参照: CWE-77

根本原因の分析 (Root Cause)

Windowsでは.batと.cmdはシェルに解釈され、引数を常に曖昧さなくエスケープできるとは限りません。Node.jsの告知によると、shellを有効にしていなくてもchild_process.spawn()とspawnSync()への悪意ある引数でコマンドを注入できました。upstreamパッチは大文字小文字を区別せず.bat/.cmdの末尾を検出し、uv_spawn()の前にEINVALを返します。この修正はsanitizeBatchArg()を実装したものではなく、shell: trueが安全という意味でもありません。後続の告知では他のバッチ拡張子に対する修正が不完全とされました(CVE-2024-36138)。両方の修正を含むバージョンを使ってください。

ステップ・バイ・ステップの攻撃フロー

ステップ 1

アプリがプロセスAPIを呼ぶ

Windows上でアプリがchild_process.spawn()またはspawnSync()を使い、外部データの影響を受ける引数とともに.bat/.cmdを起動します。

ステップ 2

対象はバッチファイル

バッチファイルにはシェルの規則が適用されます。引数配列だけではメタ文字の解釈を防げません。

ステップ 3

Windowsが引数を解釈する

脆弱な動作ではshell: falseでも引数がコマンド構文として解釈される可能性がありました。

ステップ 4

Node.jsが直接起動を拒否

修正は.bat/.cmdの直接起動でEINVALを返します。CVE-2024-36138では他の拡張子に残る不備が判明したため、両方の修正を含む版へ更新します。

ソースコード比較:脆弱 vs 堅牢化

脆弱な実装
// セキュリティパッチ適用前の src/process_wrap.cc の抜粋
node::Utf8Value file(env->isolate(), file_v);
options.file = *file;

// バッチファイルを拒否せずにファイル名を uv_spawn() に渡していた。
堅牢化されたセキュアパッチ
// CVE-2024-27980 の上流パッチからの抜粋
node::Utf8Value file(env->isolate(), file_v);
options.file = *file;

// バッチファイルの引数は常に曖昧さなくエスケープできるとは限らない。
if (IsWindowsBatchFile(options.file))
  err = UV_EINVAL;

if (err == 0) {
  err = uv_spawn(env->event_loop(), &wrap->process_, &options);
  wrap->MarkAsInitialized();
}

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

参考資料