web-dev-qa-db-ja.com

JavaScriptのeval()が悪ではないのはいつですか?

(スプレッドシートのような機能のために)ユーザーが入力した関数を解析するJavaScriptコードをいくつか書いています。数式を解析した後could JavaScriptに変換し、eval()を実行して結果を生成します。

ただし、それが悪であるため回避できる場合は、eval()を使用することを常に避けています(そして、正しくも誤って、評価されるコードがユーザーによって変更されます)。

それで、それを使用してもいいですか?

250
Richard Turner

私はあなたの質問の前提に対処するために時間を割いたいと思います-そのeval()は "evil"です。プログラミング言語の人々が使用する「evil」という言葉は、通常「危険」、より正確には「単純に見えるコマンドで多くの害を引き起こす可能性がある」という意味です。それで、いつ危険なものを使用しても大丈夫ですか?危険とは何か、そして適切な予防策を講じているとき。

ここまでで、eval()の使用における危険性を見てみましょう。おそらく他のすべてと同じように、多くの小さな隠れた危険がありますが、2つの大きなリスク-eval()が悪と見なされる理由-はパフォーマンスとコードインジェクションです。

  • パフォーマンス-eval()は、インタープリター/コンパイラーを実行します。コードがコンパイルされている場合、これは大ヒットです。実行中に重いコンパイラを呼び出す必要があるためです。ただし、JavaScriptはまだほとんどがインタープリター言語です。つまり、eval()を呼び出すことは、一般的なケースでは大きなパフォーマンスヒットにはなりません(ただし、以下の具体的な説明を参照)。
  • コードインジェクション-eval()は、潜在的に昇格した権限でコードの文字列を実行します。たとえば、管理者/ルートとして実行されているプログラムは、ユーザー入力をeval()したくないでしょう。なぜなら、その入力は潜在的に「rm -rf/etc/important-file」またはそれよりも悪いからです。繰り返しになりますが、プログラムはユーザー自身のアカウントで実行されているため、ブラウザーのJavaScriptにはこの問題はありません。サーバー側のJavaScriptにはその問題がある可能性があります。

特定のケースに移ります。私が理解していることから、あなたは自分で文字列を生成しているので、「rm -rf something-important」のような文字列を生成しないように注意すると、コードインジェクションのリスクはありません(覚えておいてください、それは- 非常に非常に難しいこれは一般的なケースでこれを保証するため)。また、ブラウザで実行している場合、コードインジェクションは非常に小さなリスクです。

パフォーマンスに関しては、コーディングのしやすさを重視する必要があります。式を解析している場合、別のパーサー(eval()内のパーサー)を実行するのではなく、解析中に結果を計算することもできます。しかし、eval()を使用してコーディングする方が簡単な場合があり、パフォーマンスの低下はおそらく目立たないでしょう。この場合、eval()は、時間を節約できる可能性のある他の関数と同じように悪意があるようです。

253
user27476

eval()は悪ではありません。または、そうである場合、リフレクション、ファイル/ネットワークI/O、スレッド、およびIPCが他の言語で「悪」であるのと同じように悪です。

目的のための場合、eval()が手動での解釈よりも高速であるか、コードがよりシンプルに、またはより明確になったら...それを使用する必要があります。どちらでもない場合、そうすべきではありません。そのような単純な。

72
Shog9

ソースを信頼するとき。

JSONの場合、ソースを制御するWebサーバーから取得されるため、ソースを改ざんするのは多少困難です。 JSON自体にユーザーがアップロードしたデータが含まれていない限り、evalを使用しても大きな欠点はありません。

他のすべての場合では、ユーザーが提供したデータがeval()に送信される前に、ルールに確実に準拠するように最大限努力します。

55
Tomalak

本物の人々を取得しましょう:

  1. 現在、すべての主要なブラウザには組み込みのコンソールがあり、ハッカーになる人はそれを豊富に使用して、任意の値を持つ関数を呼び出すことができます。

  2. 2000行のJavaScriptをコンパイルするのに0.2秒かかる場合、4行のJSONを評価するとパフォーマンスが低下しますか?

「評価は悪」というクロックフォードの説明でさえ弱い。

evalはEvilです。eval関数はJavaScriptの最も誤用された機能です。避けてください

クロックフォード自身が「この種の発言は不合理な神経症を引き起こす傾向がある。それを買わないで」と言うかもしれない。

Evalを理解し、それがいつ役に立つかを知ることは、はるかに重要です。たとえば、evalは、ソフトウェアによって生成されたサーバー応答を評価するための賢明なツールです。

ところで、Prototype.jsはevalを5回直接呼び出します(evalJSON()およびevalResponse()を含む)。 jQueryはparseJSONでそれを使用します(関数コンストラクター経由)。

23
plodder

私は Crockfordのアドバイス に従ってeval()に従い、それを完全に避ける傾向があります。それを必要とするように見える方法でさえもそうではありません。たとえば、setTimeout()を使用すると、evalではなく関数を渡すことができます。

setTimeout(function() {
  alert('hi');
}, 1000);

たとえそれがtrustedソースであっても、JSONによって返されるコードが文字化けする可能性があるため、それを使用しません。最悪の場合、悪いものを公開します。

18
swilliams

Chrome(v28.0.1500.72)でデバッグすると、クロージャーを生成するネストされた関数で使用されない変数はクロージャーにバインドされないことがわかりました。これはJavaScriptエンジンの最適化だと思います。

BUT:クロージャを引き起こす関数内でeval()が使用される場合、ALL外側の関数の変数はクロージャにバインドされます。それらはまったく使用されません。誰かがそれによってメモリリークが発生する可能性があるかどうかをテストする時間があれば、下にコメントを残してください。

これが私のテストコードです。

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is visible in debugger
            eval("1");
        })();
    }

    evalTest();
})();

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is NOT visible in debugger
            var noval = eval;
            noval("1");
        })();
    }

    evalTest();
})();

(function () {
    var noval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();    // Variable "unused" is NOT visible in debugger
            noval("1");
        })();
    }

    evalTest();
})();

ここで指摘したいのは、eval()は必ずしもネイティブeval()関数を参照する必要はないということです。 それはすべて関数の名前に依存します。したがって、ネイティブeval()をエイリアス名(たとえばvar noval = eval;で、次に内部関数noval(expression);)で呼び出すと、次の変数を参照するときにexpressionの評価が失敗することがあります。クロージャの一部であるべきですが、実際にはそうではありません。

4
Benjamin

evilであるため、evalを使用しないことを支持する人々を見ましたが、同じ人々がFunctionとsetTimeoutを動的に使用するので、evalフードの下:D

ところで、サンドボックスが十分に確信できない場合(たとえば、コードインジェクションを許可するサイトで作業している場合)、evalが問題の最後です。セキュリティの基本的なルールは、all入力は悪ですが、JavaScriptの場合evenJavaScript自体は悪である可能性があります。JavaScriptでは、任意の関数を上書きでき、実際の関数を使用していることを確認できないため、悪意のあるコードが開始されると、 JavaScript組み込み関数:D

この投稿のエピローグは次のとおりです。

本当に必要な場合(80%の時間でevalがNOT必要)、何をしているのか確信がある場合は、evalを使用してください(またはより良い機能;))、クロージャとOOPは、evalが別の種類のロジックを使用して置き換えられる場合の80/90%をカバーし、残りは動的に生成されたコードです(たとえば、インタプリタを作成する)、そしてあなたがすでにJSONを評価していると言ったように(ここではCrockfordの安全な評価を使用できます;))

4
kentaromiura

Evalは、コードのテンプレート化に使用されるコンパイルを補完します。テンプレート化とは、開発速度を向上させる便利なテンプレートコードを生成する単純化されたテンプレートジェネレーターを記述することを意味します。

開発者がEVALを使用しないフレームワークを作成しましたが、開発者はフレームワークを使用し、そのフレームワークはテンプレートを生成するためにEVALを使用する必要があります。

EVALのパフォーマンスは、次の方法を使用して向上させることができます。スクリプトを実行する代わりに、関数を返す必要があります。

var a = eval("3 + 5");

次のように編成する必要があります

var f = eval("(function(a,b) { return a + b; })");

var a = f(3,5);

Fをキャッシュすると、確かに速度が向上します。

また、Chromeを使用すると、このような関数のデバッグを非常に簡単に行うことができます。

セキュリティに関しては、evalを使用しても使用しなくてもほとんど違いはありませんが、

  1. まず、ブラウザはサンドボックス内のスクリプト全体を呼び出します。
  2. EVALで悪意のあるコード、ブラウザ自体で悪意のあるコード。攻撃者または誰でもDOMにスクリプトノードを簡単に挿入し、評価できる場合は何でもできます。EVALを使用しなくても違いはありません。 。
  3. ほとんどの場合、サーバー側のセキュリティが不十分であり、有害です。 Cookieの検証が不十分であるか、サーバーでのACLの実装が不十分であると、ほとんどの攻撃が発生します。
  4. 最近のJavaの脆弱性などがJavaのネイティブコードにありました。 JavaScriptは、サンドボックスで実行するように設計されていましたが、アプレットは、脆弱性などの原因となる証明書などを使用してサンドボックスの外部で実行するように設計されていました。
  5. ブラウザを模倣するコードを書くことは難しくありません。必要なことは、お気に入りのユーザーエージェント文字列を使用してサーバーにHTTPリクエストを行うことだけです。とにかく、すべてのテストツールはブラウザーをモックします。攻撃者があなたに危害を加えたい場合、EVALは最後の手段です。サーバー側のセキュリティに対処する他の多くの方法があります。
  6. ブラウザのDOMは、ユーザー名ではなくファイルにアクセスできません。実際、evalがアクセスできるマシン上のものは何もありません。

サーバー側のセキュリティが強固で、だれでもどこからでも攻撃できる場合は、EVALについて心配する必要はありません。EVALが存在しない場合、攻撃者はブラウザの種類に関係なく、 EVAL機能。

Evalは、事前に使用されていない何かに基づいて複雑な文字列処理を実行するテンプレートを生成する場合にのみ有効です。たとえば、私は好むだろう

"FirstName + ' ' + LastName"

とは対照的に

"LastName + ' ' + FirstName"

データベースから取得でき、ハードコードされていない表示名として。

4
Akash Kava

Microsoftは、IEブログのブラウザでeval()が遅い理由を説明しています。IE + JavaScriptパフォーマンスの推奨事項パート2:JavaScriptコードの非効率性

3

Eval()を使用する必要があるのは、動的JSをその場で実行する必要がある場合だけです。私はあなたがサーバーから非同期にダウンロードするJSについて話している...

...そして、10回のうち9回は、リファクタリングすることで簡単に回避できます。

2
Oli

サーバー側では、evalはsql、influxdb、mongoなどの外部スクリプトを処理するときに役立ちます。サービスを再デプロイせずに実行時にカスタム検証を行うことができる場所。

たとえば、次のメタデータを持つアチーブメントサービス

{
  "568ff113-abcd-f123-84c5-871fe2007cf0": {
    "msg_enum": "quest/registration",
    "timely": "all_times",
    "scope": [
      "quest/daily-active"
    ],
    "query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" LIMIT 1`",
    "validator": "valid > 0",
    "reward_external": "ewallet",
    "reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/registration:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/registration\"}`"
  },
  "efdfb506-1234-abcd-9d4a-7d624c564332": {
    "msg_enum": "quest/daily-active",
    "timely": "daily",
    "scope": [
      "quest/daily-active"
    ],
    "query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" WHERE time >= '${today}' ${ENV.DAILY_OFFSET} LIMIT 1`",
    "validator": "valid > 0",
    "reward_external": "ewallet",
    "reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/daily-active:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/daily-active\"}`"
  }
}

その後、許可します

  • Jsonのリテラル文字列を介したオブジェクト/値の直接注入。テキストのテンプレート化に役立ちます

  • コンパレーターとして使用できます。CMSでクエストやイベントを検証する方法をルールにするとします

これの短所:

  • 完全にテストされていない場合、コード内のエラーであり、サービス内のものを分割する可能性があります。

  • ハッカーがシステム上でスクリプトを作成できる場合、ほとんど混乱しています。

  • スクリプトを検証する1つの方法は、スクリプトのハッシュを安全な場所に保管することです。これにより、実行前にそれらをチェックできます。

1
MichaelC

ボトムライン

evalのコードを作成またはサニタイズした場合、evilにはなりません。

やや詳細

evalは、クライアントから送信された入力を使用してサーバーで実行されている場合開発者によって作成されていないまたはサニタイズされていない開発者

eval悪ではないクライアントで実行されている場合、クライアントによって作成された非サニタイズされた入力を使用している場合でもです。

明らかに、あなたはshould入力を常にサニタイズし、コードが消費するものを制御できるようにします。

推論

開発者がコーディングしなくても、クライアントは任意のコードを実行できます。これはwhatが評価されるだけでなく、eval自体への呼び出し

1
Steven Spungin

Evalが正当化されるケースはまれだと思います。 実際に正当化されたときに使用するよりも、正当化されていると考えて使用する可能性が高くなります。

セキュリティの問題は最もよく知られています。ただし、JavaScriptはJITコンパイルを使用し、これはevalでは非常に不十分に動作することにも注意してください。 Evalはコンパイラにとってブラックボックスのようなものであり、パフォーマンスの最適化とスコープを安全かつ正しく適用するために、JavaScriptは事前に(ある程度)コードを予測できる必要があります。場合によっては、パフォーマンスへの影響がeval以外の他のコードにも影響することがあります。

詳細を知りたい場合: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval

1
Andre O

evalはめったに正しい選択ではありません。スクリプトを連結し、その場で実行することで達成する必要があることを達成できる多くのインスタンスがあるかもしれませんが、通常は自由に使えるはるかに強力で保守可能なテクニックがあります:連想配列表記(obj["prop"]obj.prop)、クロージャ、オブジェクト指向技術、機能的技術と同じです-代わりに使用してください。

1
yfeldblum

eval関数に渡されるコードを完全に制御できる場合は、使用しても構いません。

1
John Topley

JavaScriptのeval()はいつ悪ではありませんか?

私は常に evalを使用することからの遠慮 を試みています。ほとんど常に、よりクリーンで保守可能なソリューションが利用可能です。評価 JSON解析でも不要 。評価 保守地獄に追加 。理由がないわけではないが、ダグラス・クロックフォードのよ​​うな巨匠に嫌われている。

しかし、を使用する必要がある例を1つ見つけました。

式を渡す必要がある場合

たとえば、一般的な google.maps.ImageMapType オブジェクトを作成する関数がありますが、zoomおよびcoordパラメーターからタイルURLを作成する方法をレシピに伝える必要があります。

my_func({
    name: "OSM",
    tileURLexpr: '"http://tile.openstreetmap.org/"+b+"/"+a.x+"/"+a.y+".png"',
    ...
});

function my_func(opts)
{
    return new google.maps.ImageMapType({
        getTileUrl: function (coord, zoom) {
            var b = zoom;
            var a = coord;
            return eval(opts.tileURLexpr);
        },
        ....
    });
}
0
TMS

本当に必要な場合、evalは悪ではありません。しかし、私が偶然遭遇したevalの使用の99.9%はnotが必要です(setTimeoutを含まない)。

私にとって、悪とはパフォーマンスやセキュリティの問題でさえありません(間接的には両方です)。 evalのこのような不必要な使用はすべて、メンテナンスヘルムに追加されます。リファクタリングツールは廃止されました。コードの検索は困難です。これらの評価の予期せぬ影響は大勢です。

0
PEZ

コード生成。私は最近 Hyperbars というライブラリを作成しました。これは virtual-domhandlebars の間のギャップを埋めます。これを行うには、handlebarsテンプレートを解析し、それを hyperscript に変換します。ハイパースクリプトは最初に文字列として生成され、それを返す前に、eval()それが実行可能コードに変換されます。この特定の状況では、eval()が悪の正反対であることがわかりました。

基本的に

<div>
    {{#each names}}
        <span>{{this}}</span>
    {{/each}}
</div>

これに

(function (state) {
    var Runtime = Hyperbars.Runtime;
    var context = state;
    return h('div', {}, [Runtime.each(context['names'], context, function (context, parent, options) {
        return [h('span', {}, [options['@index'], context])]
    })])
}.bind({}))

eval()のパフォーマンスも、このような状況では問題になりません。生成された文字列を一度だけ解釈し、実行可能出力を何度も再利用するだけで済むからです。

here に興味がある場合は、コード生成がどのように達成されたかを確認できます。

0
Wikened

evalの使用例:import

通常の方法。

var components = require('components');
var Button = components.Button;
var ComboBox = components.ComboBox;
var CheckBox = components.CheckBox;
...
// That quickly gets very boring

しかし、evalと小さなヘルパー関数の助けを借りて、見栄えが良くなります:

var components = require('components');
eval(importable('components', 'Button', 'ComboBox', 'CheckBox', ...));

importableは次のようになります(このバージョンは具象メンバーのインポートをサポートしていません)。

function importable(path) {
    var name;
    var pkg = eval(path);
    var result = '\n';

    for (name in pkg) {
        result += 'if (name !== undefined) throw "import error: name already exists";\n'.replace(/name/g, name);
    }

    for (name in pkg) {
        result += 'var name = path.name;\n'.replace(/name/g, name).replace('path', path);
    }
    return result;
}
0
Yaroslav

可能であれば、テスト中のみ。また、eval()は他の特殊なJSONなどのエバリュエーターよりもはるかに遅いことに注意してください。

0
Eric Wendelin

コードのソースがあなたまたは実際のユーザーからのものであることが確実である限り、eval()を使用しない理由はありません。 eval()関数に送信されるものを操作することはできますが、Webサイトのソースコードを操作できるため、JavaScriptコード自体を変更できるため、セキュリティ上の問題ではありません。

それで... eval()を使用しない場合は? Eval()は、第三者が変更する可能性がある場合にのみ使用してください。クライアントとサーバー間の接続を傍受するようなものです(ただし、問題が発生する場合はHTTPSを使用してください)。フォーラムのような他の人によって書かれたコードを解析するためにeval()をすべきではありません。

0
Georg Schölly

クライアントスクリプトに関する限り、セキュリティの問題は議論の余地があると思います。ブラウザーにロードされたものはすべて操作の対象であり、そのように扱う必要があります。ブラウザのURLバーなど、JavaScriptコードを実行したり、DOMのオブジェクトを操作したりするはるかに簡単な方法がある場合、eval()ステートメントを使用してもリスクはありません。

javascript:alert("hello");

誰かが自分のDOMを操作したい場合、私は振り回します。あらゆる種類の攻撃を防ぐためのセキュリティは、常にサーバーアプリケーションの責任です。

実用的な観点からは、他の方法で実行できる状況でeval()を使用してもメリットはありません。ただし、evalを使用する必要がある特定のケースがあります。その場合、ページを爆破するリスクなしに間違いなく実行できます。

<html>
    <body>
        <textarea id="output"></textarea><br/>
        <input type="text" id="input" />
        <button id="button" onclick="execute()">eval</button>

        <script type="text/javascript">
            var execute = function(){
                var inputEl = document.getElementById('input');
                var toEval = inputEl.value;
                var outputEl = document.getElementById('output');
                var output = "";

                try {
                    output = eval(toEval);
                }
                catch(err){
                    for(var key in err){
                        output += key + ": " + err[key] + "\r\n";
                    }
                }
                outputEl.value = output;
            }
        </script>
    <body>
</html>
0
SawedUp

Evalは、マクロがない場合のコード生成に役立ちます。

(馬鹿げた)例として、 Brainfuck コンパイラを書いている場合、おそらく一連の命令を文字列として実行する関数を構築し、それを評価して関数を返すことができます。

0
Erik Haliewicz

私の考えでは、evalはクライアント側のWebアプリケーションにとって非常に強力な関数であり、安全です... JavaScriptと同じくらい安全です。 :-)セキュリティの問題は本質的にサーバー側の問題です。Firebugのようなツールを使用すると、JavaScriptアプリケーションを攻撃できるためです。

0
maza