悪魔の招待状:JavaScriptでeval()を使ってはいけない「本当の理由」と現場の防衛術
「とりあえず動けばいいや」で書いた数行のコードが、数年後に会社を傾ける脆弱性の温床になる。セキュリティの現場で何度も見てきた光景だ。
今日取り上げるのは、初心者が犯しがちで、かつ中級者が油断して踏み抜く地雷。eval() を筆頭とする「文字列をコードとして実行する関数群」だ。なぜこれらが現代のWeb開発において「死刑宣告」に等しいのか、そしてどう代替すべきか。プロの視点で徹底解説する。
—
1. なぜ eval() が「悪魔の関数」なのか
eval()、setTimeout(string)、setInterval(string)、new Function(string)。これらは全て、「外部からの入力(文字列)を、そのままプログラムの実行権限で動かす」という特性を持つ。
これがなぜ危険か? 答えはシンプルだ。「実行されるコードの制作者」が、あなたではなく「攻撃者」になる可能性があるからだ。
攻撃者の視点:PoC(概念実証)の恐怖
例えば、ユーザーがプロフィール画面で入力した名前を、動的に表示する処理を考えてみよう。
// 脆弱な実装例
const userInput = “alert(document.cookie)”; // 攻撃者が入力した値
eval(“console.log(‘Hello, ” + userInput + “‘)”);
これだけで、ユーザーのセッションCookieは即座に外部の攻撃サーバーへ送信される。eval() は現在のスコープで実行されるため、そのWebページが持つあらゆる権限を乗っ取ることができる。OSコマンドインジェクションのJavaScript版と言えば、その恐ろしさが伝わるだろうか。
—
2. 安全な代替案:なぜ「評価」ではなく「参照」を使うのか
現代のセキュアな開発において、文字列をコードとして実行する必要はほぼ存在しない。もしあるとすれば、それは設計が間違っている証拠だ。
代替パターン1:JSONのパース
外部からのデータをオブジェクトとして扱いたいなら、eval() ではなく必ず JSON.parse() を使う。これは文字列を「データ」としてしか扱わないため、悪意のあるコードが含まれていても、ただの文字列として処理が止まるだけだ。
// 安全なデータ処理
const jsonString = ‘{“name”: “Alice”, “role”: “admin”}’;
const data = JSON.parse(jsonString); // 確実にデータとして扱う
console.log(data.name);
代替パターン2:関数参照とルックアップテーブル
「文字列から関数を呼び出したい」というケースもよくある。この場合、eval() を使うのではなく、オブジェクトを辞書(ルックアップテーブル)として定義するのが鉄則だ。
// 安全な関数呼び出しのパターン
const actions = {
“save”: () => { console.log(“保存しました”); },
“delete”: () => { console.log(“削除しました”); }
};
const command = “save”; // 外部から来た入力値
if (actions[command]) {
actions[command](); // 安全に関数を実行
} else {
console.error(“無効なコマンドです”);
}
—
3. 実務で「封じ込める」ための防御レイヤー
コードレベルでの修正に加え、万が一の脆弱性混入を抑え込む「多層防御」の設定も紹介しておく。これらは今すぐ本番環境の設定ファイルに組み込むべきだ。
CSP (Content Security Policy) で実行を制限する
HTTPレスポンスヘッダーでCSPを設定すれば、仮に攻撃者がコードを埋め込んでも、ブラウザ側で実行を拒否させることができる。unsafe-eval を許可しない設定がデフォルトだ。
Nginx設定例:
CSP設定:evalやインラインスクリプトを禁止する
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’;”;
サーバーサイド(Node.js/Python/PHP)での教訓
JavaScriptだけでなく、バックエンドでも eval() や exec() の類は避けるべきだ。もしPHPで eval() を使っているコードを見つけたら、それはリファクタリングの優先度を「緊急」にすべきだ。
PHPでの悪い例:
// 絶対にやるな:外部入力をそのまま実行
eval($_GET[‘code’]);
PHPでの対策(設計の見直し):
PHPの場合は、call_user_func() を使う場合でも、必ずホワイトリストによる制限を設けること。
$allowed_methods = [‘get_user’, ‘get_post’];
$method = $_GET[‘action’];
if (in_array($method, $allowed_methods)) {
call_user_func($method);
}
—
最後に:プロフェッショナルとして
「便利だから」「昔からこうしていたから」という理由は、脆弱性に対する言い訳にはならない。
コードを記述する際、一瞬だけ立ち止まって自分に問いかけてほしい。
「この文字列は、誰が生成したのか?」
「この文字列をコードとして解釈させる必要は、本当にあるのか?」
あなたが書くその1行が、ユーザーの個人情報を守る壁になる。あるいは、崩壊させる亀裂になる。この違いを常に意識できるエンジニアこそが、信頼される技術者であると私は信じている。
もし、プロジェクト内にまだ eval() が残っているなら、今すぐ修正チケットを切るべきだ。それが、君のシステムを守るための第一歩だ。
コメント