JavaScript eval()を代替できる方法

JavaScript eval()を代替できる方法

D
dongAuthor
2 min read

JavaScriptの eval() が危険な理由と、 new Function() によって安全に代替する方法を実用的な例とともにご紹介します。セキュリティとパフォーマンスの両方を重視するヒントです!

JavaScriptを使っていると、文字列として表現されたコードを実行しなければならない場面があります。その際、最初に思い浮かぶのが eval() 関数でしょう。しかし eval() は、セキュリティの脆弱性とパフォーマンスの問題で悪名高い存在です。

この記事では、 eval() の危険性を確認し、より安全な代替手段である new Function() をどのように活用できるか、実用的な例とともにご紹介します。

eval()とは何か?

MDN の文書によれば、 eval() は文字列で表された JavaScript コードを実行する関数です。簡単な例を使って動作を見てみましょう。

let a = 3;
let b = 5;
eval('a += ' + b + ' + 2');
console.log(a); // 10

上のコードでは、 eval() が文字列 'a += 5 + 2' を実際の JavaScript コードとして実行しています。一見便利に見えますが、この単純さの裏には重大な問題が潜んでいます。

eval()のセキュリティ上のリスク

eval() は呼び出し元の権限でコードを実行します。これはどういう意味でしょうか?もしユーザー入力を eval() で実行してしまったら、悪意のあるコードがそのまま実行される可能性があります。

var userContent = getUserInput(); // ユーザーからの入力
eval(userContent); // 危険!

ハッカーが "window.location = 'http://malicious-site.com'" のようなコードを入力したらどうなるでしょうか?利用者は悪意のあるサイトへリダイレクトされるか、もっと深刻な場合は機密データが盗まれる可能性があります。

また eval() は呼び出されたスコープ(scope)に直接アクセスできます。これはローカル変数を読み取ったり変更できるということです:

function MyFunc() {
  let secretToken = "abc123";
  eval('console.log(secretToken); secretToken = "hacked";');
  console.log(secretToken); // "hacked"
}

eval()のパフォーマンス問題

eval() は JavaScript インタプリタを直接呼び出すため、他の代替手段より遅くなります。現代の JavaScript エンジンはコードを最適化しますが、 eval() はどんなコードが実行されるか予め把握できないため、その最適化を妨げます。

new Function()でより安全に

new Function()eval() と似て、文字列から関数を生成しますが、ずっと安全です。どのように使うか見てみましょう:

const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3)); // 5

構文は次の通りです:

let func = new Function([arg1, arg2, ...argN], functionBody);

ここで重要なのは、 new Function() で生成された関数は 常にグローバルスコープ で実行されるという点です。ローカル変数にはアクセスできません:

function MyFunc() {
  let b = 123;
  new Function('console.log(b);')(); // ReferenceError: b is not defined
}

このような制約が、むしろセキュリティを強化します。悪意のあるコードが関数内部の機密変数にアクセスするのを根本的に防止するわけです。

eval() と new Function() を比較する

2つの方式の核心的な違いを整理しましょう。

実行コンテキストとスコープ

eval() は現在の実行コンテキストでコードを評価します:

function testEval() {
  let x = 10;
  eval('console.log(x); x = 20;');
  console.log(x); // 20 ― 変数が変更されている
}
testEval();

一方、 new Function() はグローバルコンテキストでのみ実行されます:

function testFunction() {
  let x = 10;
  const func = new Function('console.log(typeof x);'); // "undefined"
  func();
  console.log(x); // 10 ― 変数が安全に保護されている
}
testFunction();

セキュリティ性

new Function() の限定されたスコープアクセスは、セキュリティ上大きな利点です。ローカル変数にアクセスできないため、たとえ悪意のあるコードが実行されても被害の範囲が限定されます。

実践例で学ぶ

実際にどのように eval()new Function() に置き換えられるのでしょうか?

シンプルな数式計算

// eval() 使用(推奨されません)
const result1 = eval('2 + 3 * 4');

// new Function() 使用(推奨)
const calculate = new Function('return 2 + 3 * 4');
const result2 = calculate();

動的関数の生成

// ユーザーが入力した関数本体から関数を生成する
const functionBody = 'return x * 2';
const double = new Function('x', functionBody);
console.log(double(5)); // 10

外部変数を安全に渡す

グローバル変数は new Function() でもアクセス可能ですが、より安全な方法としてはパラメーターとして明示的に渡すことです:

const multiplier = 3;

// パラメーターとして明示的に渡す
const multiply = new Function('x', 'multiplier', 'return x * multiplier');
console.log(multiply(5, multiplier)); // 15

JSONパースの代替

厳格なJSONではなく、JavaScriptオブジェクトリテラルをパースしたいときにも活用できます:

function looseJsonParse(obj) {
  return Function('"use strict";return (' + obj + ")")();
}

const result = looseJsonParse("{a:(4-1), b:function(){}, c:new Date()}");
console.log(result.a); // 3

いつ new Function() を使うべきか?

new Function() が有用なシナリオを見ていきましょう:

動的コード生成時

設定ファイルやユーザー入力に基づいて関数を動的に生成する必要があるときに便利です。例えば、ユーザー定義のフィルターやソートロジックを実装するときなど。

サンドボックス環境

グローバルスコープでのみ実行されるため、プラグインシステムやユーザースクリプト実行環境を構築する際に適しています。

パフォーマンスが重要な場合

繰り返し実行されるコードであれば、一度 new Function() でコンパイルしてから再利用することで、 eval() を毎回呼び出すよりも効率的です。

注意事項と限界

new Function() も完璧な解決策ではありません。文字列からコードを生成するという点では依然としてリスクがあり、信頼できない入力を扱う場合は厳重な検証が必要です。

また、いくつかのエッジケースでは eval()new Function() のどちらも適さないことがあります。可能であれば、次のような代替手段をまず検討してください:

オブジェクトのプロパティアクセス時:

// eval() 使用(悪い例)
const propName = 'username';
const value = eval('user.' + propName);

// ブラケット表記使用(良い例)
const value = user[propName];

イベントハンドラー登録時:

// インライン文字列(悪い例)
element.setAttribute("onclick", "handleClick()");

// イベントリスナー使用(良い例)
element.addEventListener("click", handleClick);

タイマー関数使用時:

// 文字列コード(悪い例)
setTimeout("console.log('Hello')", 1000);

// 関数渡し(良い例)
setTimeout(() => console.log('Hello'), 1000);

より安全なコードへ向けて

eval() は便利そうに見えますが、セキュリティとパフォーマンスの観点から重大な問題を引き起こす可能性があります。new Function() はグローバルスコープの制約を通じてこれらのリスクを大きく軽減してくれる、より良い代替手段です。

もちろん、最善の方法は動的コード実行自体を避けることです。しかしどうしても避けられない状況であれば、new Function() を選び、入力値の検証を徹底することを忘れないでください。

セキュリティは妥協できない領域です。たった一行のコードがアプリケーション全体の安全性を左右するという点を、常に心に留めながら開発を進めてください :)