【テクニカル・上級編】JavaScriptのeval()およびsetTimeout()への文字列渡しによるコード実行リスク – アプリケーションセキュリティ & 安全な開発防御ガイド

eval() との決別:JavaScriptにおける動的実行が招く「境界なき悪夢」

現場でコードレビューをしていると、今でも時折、魔が差したかのような eval() や、文字列を引数に取る setTimeout() に遭遇する。これらは単なる「古い書き方」ではない。メモリ上の実行コンテキストを意図せず開放し、攻撃者に「神の権限」を明け渡すための招待状だ。

なぜこれらが危険なのか。表面的な「XSSの危険性」を超えて、アーキテクトの視点からその本質を解剖する。

1. メモリとコンテキスト:なぜ eval() は制御不能なのか

eval() が実行される際、JavaScriptエンジンは現在のスコープ内でその文字列をパースし、コンパイルして実行する。これは単なる関数の呼び出しではない。呼び出し元のレキシカル環境(環境レコード)に直接介入するという挙動だ。

攻撃者が eval("var x = " + userInput) のように入力値を注入できた場合、彼らは対象アプリケーションの変数を上書きし、プロトタイプ汚染を介してグローバルなプロトタイプチェーンを操作する。

これをパケット解析の観点で見れば、攻撃者は「データ」として送り込んだはずの文字列を、「命令」としてエンジンに再解釈させている。これはバッファオーバーフローの古典的な手法である「シェルコードの実行」と論理的に同義だ。メモリ上の実行ビットが立っていない領域を、言語レベルのインタープリタが強引に実行可能領域に変えてしまう。これが eval() を使うことの、設計上の原罪である。

2. 「見えないインジェクション」:setTimeoutの罠

多くの開発者が setTimeout(callback, 1000) と書くべき場所で、setTimeout("doSomething('" + userInput + "')", 1000) と書いてしまう。

この文字列渡しは、内部で暗黙的に eval() が呼ばれる仕様になっている。もし userInput に '); alert(document.cookie); // のようなペイロードが含まれていれば、コンテキストは完全に侵害される。

対策:安全な代替手法の実装

動的な処理が必要な場合、JSONの解析や関数の参照渡しを用いるのが鉄則だ。

// ❌ 絶対にやってはいけない実装
// ユーザー入力がそのままスクリプト実行コンテキストに注入される
setTimeout(“processData(‘” + input + “‘)”, 1000);

// ✅ 推奨される設計(クロージャの活用)
// 関数を「値」として渡すことで、実行コンテキストを分離する
setTimeout(() => {
processData(input);
}, 1000);

// ✅ JSONデータを受け取る場合
// eval() を使わず、標準のシリアライザを使う
const data = JSON.parse(userInput);

3. 生成AI時代のガードレイル:アーキテクチャ設計の新たな責務

現代のアーキテクチャにおいて、この問題は「LLMプロンプトインジェクション」と地続きだ。LLMが生成したコードやJSONを、そのまま eval() や new Function() に放り込むシステムが散見されるが、これは自ら「未知の攻撃コードを動的に生成・実行するゲート」を設置しているに等しい。

防御層(ガードレイル)を設計する際は、以下の層を重ねる必要がある。

1. 静的解析(SAST)の強制: CI/CDパイプライン上で eval などの危険な関数を禁止するESLintルール(no-eval, no-implied-eval)を強制適用する。これはもはや推奨ではなく、ビルドを通さないための「門番」だ。
2. CSP(Content Security Policy)による実行制御: script-src ディレクティブで 'unsafe-eval' を徹底的に排除する。ブラウザレベルで動的実行を封じ込めることで、万が一コードに脆弱性が残っていても、攻撃の連鎖を断ち切る。
3. サンドボックス化: LLMや外部入力の処理を行う部分は、Node.jsの vm モジュール(ただし適切に設定が必要)や、Web Workerを用いたメインスレッドからの分離を行う。

結論:技術的負債ではなく「脆弱性」として扱う

私が設計に関わる現場では、eval() を使うことは「技術的負債」ではなく、「セキュリティインシデントの予兆」として扱う。

耐量子暗号の実装や、高度な通信プロトコルのセキュア化を議論する前に、まず我々の足元の実行エンジンが、意図しない入力を「コード」として解釈していないか、今一度コードベースを精査してほしい。

優れたセキュリティアーキテクトとは、高尚な暗号理論を語る人物ではない。コードの一行一行がメモリ上でどう振る舞い、攻撃者がどのパスを辿って権限昇格を狙うかを、泥臭く追い続けられる人物のことだ。

コードは、常に性悪説で書く。それが、この過酷なサイバー空間を生き残るための唯一の流儀である。

コメント

タイトルとURLをコピーしました