コード実行の「パンドラの箱」:eval()とsetTimeout()が生む脆弱性の深層
セキュリティの最前線にいる諸君なら、「eval()は悪だ」という教訓は耳にタコができるほど聞かされてきたはずだ。だが、なぜそれが現代のアプリケーションアーキテクチャにおいて致命的なのか、単に「コードが実行されるから」という表面的な理解で止まってはいないか?
今日は、反射型・格納型・DOM型といったXSSの分類を飛び越え、JavaScriptエンジンが抱える「実行コンテキストの汚染」という低レイヤの脆弱性について、エンジニアの深淵に触れる話をしよう。
1. 実行コンテキストの汚染:なぜ eval() は死刑宣告なのか
eval() が危険なのは、単に文字列をJavaScriptとして評価するからではない。「現在の実行コンテキスト(Scope Chain)」を完全に掌握させるからだ。
攻撃者が eval() に渡される文字列を制御できる場合、それはローカル変数へのアクセス、プロトタイプ汚染、さらには Function コンストラクタを介したグローバルスコープの書き換えを意味する。
攻撃シナリオ:メモリの境界を越える
例えば、以下のような実装を見かけることがある。
// 悪夢の起点:APIからのレスポンスをそのままevalで処理する実装
const userData = JSON.parse(apiResponse);
eval(“updateUI(” + userData.callbackName + “)”);
もし userData.callbackName に alert(document.cookie)// が混入していたら? 攻撃者は即座にセッション情報を奪取し、さらには __proto__ を書き換えることで、アプリケーション全体の振る舞いを不可逆的に破壊する。これは、メモリ上のオブジェクト構造を動的に操作される「型混乱」の一種だ。
2. 見落とされる「setTimeout」の罠
多くの開発者が eval() は避けるが、setTimeout() や setInterval() の第一引数に文字列を渡すことには甘い。
// 非常に危険な実装
setTimeout(“console.log(‘” + userInput + “‘)”, 1000);
setTimeout() は内部的に eval() と同等の処理系を呼び出している。これはブラウザの仕様(古いLegacy APIの互換性)に依存しており、パケットレベルで見れば、正当なJavaScriptコードの中に悪意あるコードがシリアライズされている状態だ。WAF(Web Application Firewall)が高度なペイロードを検出したとしても、この「文字列として渡されるコード」は、解析エンジンのコンテキスト外で実行されるため、しばしば検知をすり抜ける。
3. 推奨されるアーキテクチャ:関数参照とセマンティックな設計
我々アーキテクトが目指すべきは、「文字列をコードに変換する」という設計思想そのものの排除だ。
代替手法:関数参照の活用
関数名が動的に決まる場合でも、文字列を評価してはならない。ホワイトリストを用いてマッピングを行うのが鉄則だ。
const UI_ACTIONS = {
‘updateProfile’: updateProfileFn,
‘updateSettings’: updateSettingsFn
};
// ユーザー入力から安全に関数を引き出す
const action = UI_ACTIONS[userInput.action] || defaultAction;
setTimeout(() => action(userInput.data), 1000); // 匿名関数によるカプセル化
このアプローチの利点は、「実行可能なコードの範囲が事前に定義されている」ことにある。入力値は単なる「キー」として扱われ、実行コンテキストの汚染は発生しない。
4. 防御の多層化(Defense in Depth)
個別の脆弱性をつぶすことは基本だが、究極的にはブラウザのネイティブ機能によるガードレイルを設計する必要がある。
Content Security Policy (CSP) による実行制限
強力な CSP を設定することで、攻撃者が注入に成功したとしても、eval() や setTimeout の文字列実行をブラウザ側で拒否できる。
HTTPヘッダー設定例
Content-Security-Policy: default-src ‘self’; script-src ‘self’; object-src ‘none’;
unsafe-eval を許可しないCSPを強制することで、たとえコードに脆弱性が残っていても、攻撃者はJavaScriptを動的に生成・実行できなくなる。
5. 生成AI時代の新たな脅威:プロンプトインジェクションとの類似性
最後に、諸君に警鐘を鳴らしておきたい。現在の生成AI(LLM)に対するプロンプトインジェクションは、かつての eval() インジェクションと構造的に酷似している。
LLMのコンテキスト(System Prompt)に外部入力が混入し、意図しない命令が実行されるのは、JavaScriptの実行コンテキストが汚染されるのと同じ論理だ。この「境界を越えてコードが実行される」という性質は、今後、耐量子暗号等の新しい暗号化技術を導入したとしても、アプリケーションレイヤーの最大の脆弱性として残り続けるだろう。
まとめ:監査の現場で問うべきこと
現場のコードレビューで eval() や文字列を渡す setTimeout() を見つけたら、単に「修正せよ」と告げるのではなく、「この処理が実行コンテキストをどう汚染し、攻撃者にメモリ上のどの範囲へのアクセス権を与える可能性があるか」をチームに問うてほしい。
技術の本質を理解したエンジニアだけが、この終わりのない「攻撃者とのチェス」に勝ち続けることができるのだ。セキュリティはパッチを当てる作業ではない。設計の思想そのものを強固にすることに他ならない。
コメント