【テクニカル・上級編】JavaScriptのeval()やsetTimeout(string)の危険性と代替案 – アプリケーションセキュリティ & 安全な開発防御ガイド

コードの動的実行という「禁忌」:eval() が招く現代的インジェクションの深淵

我々が日々向き合っているコードベースにおいて、最も「安易で、最も破滅的」な実装の一つが、文字列を命令として解釈させる関数群だ。eval()、setTimeout(string)、setInterval(string)、そして new Function()。これらは単なるレガシーな記法ではない。攻撃者にとっては、あなたのアプリケーションの実行コンテキストを直接乗っ取るための「バックドア」そのものだ。

なぜ eval() は依然として脆弱性の温床なのか

セキュリティ界隈では「eval is evil」と教えられる。だが、なぜそこまで忌み嫌われるのか。単なるベストプラクティスの遵守といった甘い話ではない。

根本的な問題は、実行時のスコープ汚染と、入力値の検証不能性にある。

JSエンジンは、eval() に渡された文字列をパースする際、呼び出し元のスコープをそのまま引き継ぐ。もし、あなたがユーザー入力をバリデーションせずにこの関数へ流し込んだとしたら、それは「OSコマンドインジェクション」をアプリケーション層で再現しているに等しい。攻撃者は、単なる計算式ではなく、fetch() を使った認証トークンの外部送出、あるいは document.cookie の奪取を、あなたのコードの権限で実行できる。

特に、近年のフロントエンドフレームワークで多用される「非同期処理」や「データバインディング」の裏側で、開発者が利便性のみを優先して文字列評価を選択した瞬間、防御壁は霧散する。

安全な代替案:実行コンテキストの分離と静的解析

では、動的なデータ処理が必要な場合はどうすべきか。我々アーキテクトが推奨するのは、「実行」と「データ」の徹底的な分離だ。

1. JSON.parse() によるデータ駆動型の設計

動的な構成設定を文字列で受け取っているなら、それはコードではなくデータだ。必ず JSON.parse() を使用し、スキーマバリデーション(ZodやAJVなど)を通せ。

// 危険なパターン:外部入力をそのままeval
// const config = eval(‘(‘ + userInput + ‘)’);

// 推奨パターン:JSONとして安全にパースし、スキーマで検証
import { z } from ‘zod’;

const ConfigSchema = z.object({
theme: z.enum([‘light’, ‘dark’]),
maxItems: z.number().positive()
});

try {
const data = JSON.parse(userInput);
const validatedConfig = ConfigSchema.parse(data); // 型安全を保証
console.log(validatedConfig.theme);
} catch (e) {
// 不正なデータ構造はここで遮断
console.error(“悪意ある、あるいは無効なペイロードを検知”);
}

2. 関数参照による動的呼び出し

setTimeout に文字列を渡すのは、1990年代の遺物だ。関数参照(コールバック)を渡すのが現代のエンジニアリングにおける鉄則である。

// 危険:setTimeout(“doSomething()”, 1000);

// 安全:第一引数に関数参照を渡す
setTimeout(() => {
doSomething(); // スコープが汚染されず、文字列インジェクションも不可能
}, 1000);

AI時代の「プロンプトインジェクション」と動的コードの相関

今、我々が対峙している最大の脅威は、生成AIの台頭による「プロンプトインジェクション」だ。

もしあなたのシステムが、AIの出力結果を eval() に渡すようなアーキテクチャになっているなら、それは即座にシステム全体を明け渡すのと同義だ。LLMはプロンプト次第でコードを生成し、そのコードが eval() で実行される。これは、外部の未知なるAIモデルが、あなたのサーバー内部のメモリ空間に直接アクセス権を得ることを意味する。

これに対するガードレイルとして、以下の設計指針を徹底してほしい。

  • サンドボックスの分離: コードの実行が必要なら、V8の vm モジュール(Node.jsの場合)などでContextを完全に隔離せよ。
  • 静的解析の自動化: CI/CDパイプラインにおいて、ESLintの no-eval ルールを強制するだけでなく、AST(抽象構文木)解析を行い、潜在的な動的実行パスをビルド前にすべて摘み取る。
  • 通信のZero Trust化: もし万が一、実行権限が奪取された場合に備え、外部への通信はEgressフィルターで厳密に制御せよ。

結びに:エンジニアの美学

セキュリティとは、単にパッチを当てる作業ではない。我々が書くコードの一行一行が、攻撃者にとっての「攻略対象」であることを常に意識する、その「防衛の美学」こそが信頼を勝ち取る。

eval() を使うことは、ドアの鍵を自分で外して外へ放り投げる行為だ。今すぐあなたのリポジトリから文字列評価関数を検索し、静的な参照へと置換せよ。それが、テックリードとしての最低限の責任であり、真のエンジニアリングへの第一歩である。

—
追記:もし、どうしても動的なロジック切り替えが必要な場合は、文字列による評価ではなく、戦略パターン(Strategy Pattern)やファクトリ関数を用いた静的なマッピング構造へのリファクタリングを検討してほしい。それこそが、堅牢なアーキテクチャへの道だ。

コメント

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