288

我正在编写一些 JavaScript 代码来解析用户输入的函数(用于类似电子表格的功能)。解析公式后,我可以将其转换为 JavaScript 并在其上运行eval()以产生结果。

但是,如果我可以避免使用它,我总是回避使用eval()它,因为它是邪恶的(无论对错,我一直认为它在 JavaScript 中更加邪恶,因为要评估的代码可能会被用户更改)。

那么,什么时候可以使用呢?

4

26 回答 26

276

我想花点时间来解决您问题的前提- eval() 是“邪恶的”。编程语言人们使用的“邪恶”一词通常意味着“危险”,或者更准确地说是“能够用一个看起来很简单的命令造成很多伤害”。那么,什么时候可以使用危险的东西呢?当您知道危险是什么并采取适当的预防措施时。

言归正传,让我们看看使用 eval() 的危险。和其他所有事情一样,可能存在许多小的隐患,但两大风险 - eval() 被认为是邪恶的原因 - 是性能和代码注入。

  • 性能 - eval() 运行解释器/编译器。如果您的代码被编译,那么这是一个很大的打击,因为您需要在运行时调用一个可能很重的编译器。然而,JavaScript 仍然主要是一种解释性语言,这意味着调用 eval() 在一般情况下不会对性能造成很大影响(但请参阅下面的具体说明)。
  • 代码注入 - eval() 可能在提升的权限下运行一串代码。例如,以管理员/root 身份运行的程序永远不会想要 eval() 用户输入,因为该输入可能是“rm -rf /etc/important-file”或更糟。同样,浏览器中的 JavaScript 不存在这个问题,因为该程序无论如何都在用户自己的帐户中运行。服务器端 JavaScript 可能有这个问题。

关于你的具体情况。据我了解,您是自己生成字符串,所以假设您小心不允许生成像“rm -rf something-important”这样的字符串,没有代码注入风险(但请记住,它非常非常在一般情况下很难确保这一点)。另外,如果你在浏览器中运行,那么代码注入风险很小,我相信。

至于性能,您必须权衡它与编码的易用性。我认为,如果您正在解析公式,您不妨在解析期间计算结果,而不是运行另一个解析器(eval() 中的那个)。但是使用 eval() 编码可能更容易,并且性能损失可能不明显。在这种情况下,看起来 eval() 并不比任何其他可能为您节省时间的函数更邪恶。

于 2008-10-13T15:40:39.707 回答
77

eval()不是邪恶的。或者,如果是,它是邪恶的,就像反射、文件/网络 I/O、线程和 IPC 在其他语言中是“邪恶的”一样。

如果出于您的目的eval()它比手动解释更快,或者使您的代码更简单或更清晰......那么您应该使用它。如果两者都不是,那么你不应该。就那么简单。

于 2008-10-13T14:44:03.970 回答
61

当您信任来源时。

在 JSON 的情况下,篡改源或多或少是困难的,因为它来自您控制的 Web 服务器。只要 JSON 本身不包含用户上传的数据,使用 eval 就没有大的缺点。

在所有其他情况下,我会竭尽全力确保用户提供的数据符合我的规则,然后再将其提供给 eval()。

于 2008-10-13T14:34:37.687 回答
25

让我们得到真正的人:

  1. 现在每个主要浏览器都有一个内置控制台,你的潜在黑客可以大量使用它来调用具有任何值的任何函数——即使他们可以,他们为什么还要费心使用 eval 语句?

  2. 如果编译 2000 行 JavaScript 需要 0.2 秒,如果我 eval 4 行 JSON,我的性能下降是多少?

甚至 Crockford 对“eval is evil”的解释也很薄弱。

eval 是邪恶的,eval 函数是 JavaScript 中被滥用最多的特性。躲开它

正如克罗克福德自己可能会说的那样,“这种说法往往会产生非理性的神经症。不要买它。”

了解 eval 并知道它何时有用更为重要。例如,eval 是一个明智的工具,用于评估由您的软件生成的服务器响应。

顺便说一句:Prototype.js 直接调用 eval 五次(包括在 evalJSON() 和 evalResponse() 中)。jQuery 在 parseJSON 中使用它(通过 Function 构造函数)。

于 2010-04-13T20:49:38.817 回答
19

我倾向于遵循Crockford 的建议eval()并完全避免它。即使看起来需要它的方式也不需要。例如,setTimeout()允许您传递函数而不是 eval。

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

即使它是受信任的来源,我也不使用它,因为 JSON 返回的代码可能是乱码,这充其量可能会做一些不可靠的事情,最坏的情况是会暴露一些不好的东西。

于 2008-10-13T14:36:50.647 回答
6

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"

作为我的显示名称,它可以来自数据库并且不是硬编码的。

于 2013-04-19T10:18:08.900 回答
6

在 Chrome (v28.0.1500.72) 中调试时,我发现如果变量没有在生成闭包的嵌套函数中使用,则它们不会绑定到闭包。我猜,这是对 JavaScript 引擎的优化。

但是:当eval()在导致闭包的函数内部使用时,外部函数的所有变量都绑定到闭包,即使它们根本没有使用。如果有人有时间测试是否会产生内存泄漏,请在下面给我留言。

这是我的测试代码:

(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

于 2013-07-23T12:03:40.710 回答
6

底线

如果您创建或清理了您的代码eval,它永远不会是邪恶的。

稍微详细一点

eval如果使用不是由开发人员创建未经开发人员清理的客户端提交的输入在服务器上运行,这是邪恶的。

eval如果在客户端上运行,即使使用客户端制作的未经处理的输入也不是邪恶的。

显然,您应该始终清理输入,以便对您的代码消耗的内容进行一些控制。

推理

客户端可以运行任何他们想要的任意代码,即使开发人员没有编写代码;这不仅适用于被评估的内容,而且适用于对自身的调用eval

于 2019-08-13T19:59:40.407 回答
4

我看到人们提倡不要使用 eval,因为它是邪恶的,但我看到同样的人动态使用 Function 和 setTimeout,所以他们在幕后使用 eval :D

顺便说一句,如果您的沙箱不够确定(例如,如果您在允许代码注入的站点上工作),那么 eval 是您的最后一个问题。安全的基本规则是所有输入都是邪恶的,但在 JavaScript 的情况下,甚至JavaScript 本身也可能是邪恶的,因为在 JavaScript 中你可以覆盖任何函数,但你不能确定你使用的是真实的,所以,如果恶意代码在您之前启动,则您不能信任任何 JavaScript 内置函数:D

现在这篇文章的结尾是:

如果您真的需要它(80% 的时间不需要 eval 并且您确定自己在做什么,只需使用 eval(或更好的 Function ;)),闭包和 OOP 涵盖了 80/90%在 eval 可以使用另一种逻辑替换的情况下,其余的是动态生成的代码(例如,如果您正在编写解释器)并且正如您已经说过评估 JSON(在这里您可以使用 Crockford 安全评估;))

于 2008-10-13T17:24:09.700 回答
2

您应该使用 eval() 的唯一实例是当您需要动态运行动态 JS 时。我说的是你从服务器异步下载的 JS ......

...而 10 的 9 倍,您可以通过重构轻松避免这样做。

于 2008-10-13T14:35:22.100 回答
2

在服务器端 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 中验证任务或事件的规则

缺点:

  • 如果没有经过全面测试,可能是代码中的错误并破坏了服务中的内容。

  • 如果黑客可以在您的系统上编写脚本,那么您就完蛋了。

  • 验证脚本的一种方法是将脚本的哈希保存在安全的地方,以便在运行之前检查它们。

于 2018-02-13T07:49:55.563 回答
2

eval 不是邪恶的,只是被滥用了。

如果您创建了进入其中的代码或可以信任它,那没关系。人们一直在谈论用户输入如何与 eval 无关。好样的~

如果有用户输入到服务器,然后返回到客户端,并且该代码在 eval 中使用而没有被清理。恭喜,您打开了潘多拉魔盒,可以将用户数据发送给任何人。

根据 eval 所在的位置,许多网站都使用 SPA,而 eval 可以让用户更轻松地访问应用程序内部,否则这将是不容易的。现在他们可以制作一个伪造的浏览器扩展程序,可以将其录入该评估并再次窃取数据。

只是要弄清楚你使用 eval 有什么意义。当您可以简单地创建方法来执行此类操作、使用对象等时,生成代码并不是很理想。

现在是使用 eval 的一个很好的例子。您的服务器正在读取您创建的 swagger 文件。许多 URL 参数都是以{myParam}. 因此,您希望读取 URL,然后将它们转换为模板字符串,而无需进行复杂的替换,因为您有许多端点。所以你可以做这样的事情。请注意,这是一个非常简单的示例。

const params = { id: 5 };

const route = '/api/user/{id}';
route.replace(/{/g, '${params.');

// use eval(route); to do something

于 2020-01-17T18:02:01.680 回答
1

eval很少是正确的选择。虽然可能有许多情况下您可以通过将脚本连接在一起并即时运行它来完成您需要完成的事情,但您通常可以使用更强大且可维护的技术:关联数组表示法(obj["prop"]与 相同obj.prop) 、闭包、面向对象的技术、函数式技术——改用它们。

于 2009-02-11T13:06:49.723 回答
1

就客户端脚本而言,我认为安全问题是一个有争议的问题。加载到浏览器中的所有内容都受到操纵,应该被这样对待。当有更简单的方法来执行 JavaScript 代码和/或操作 DOM 中的对象(例如浏览器中的 URL 栏)时,使用 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>
于 2009-07-29T16:52:55.463 回答
0

如果您可以完全控制传递给eval函数的代码,则可以使用它。

于 2008-10-13T14:36:17.310 回答
0

如果可能,仅在测试期间。另请注意, eval() 比其他专门的 JSON 等评估器慢得多。

于 2008-10-13T14:40:27.427 回答
0

只要您可以确定代码的来源来自您或实际用户,就没有理由不使用 eval()。尽管他可以操纵发送到 eval() 函数的内容,但这不是安全问题,因为他能够操纵网站的源代码,因此可以更改 JavaScript 代码本身。

那么......什么时候不使用eval()?仅当第三方有可能更改它时,才不应使用 Eval()。就像拦截客户端和服务器之间的连接一样(但如果这是一个问题,请使用 HTTPS)。您不应该 eval() 来解析其他人(例如在论坛中)编写的代码。

于 2008-10-13T14:47:41.840 回答
0

如果真的需要 eval 不是邪恶的。但是我偶然发现的 eval 的 99.9% 的使用是不需要的(不包括 setTimeout 的东西)。

对我来说,邪恶不是性能,甚至不是安全问题(嗯,间接地两者兼而有之)。所有这些不必要的 eval 使用增加了维护地狱。重构工具被抛弃。搜索代码很困难。这些评估的意想不到的影响是军团。

于 2008-12-20T18:50:23.520 回答
0

我的使用示例evalimport

一般是怎么做的。

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;
}
于 2013-04-30T06:59:29.917 回答
0

我认为任何 eval 被证明是合理的情况都很少见。你更有可能使用它认为它是合理的,而不是你在它实际上是合理的时候使用它。

安全问题是最广为人知的。但也请注意,JavaScript 使用 JIT 编译,这与 eval 效果很差。Eval 有点像编译器的黑匣子,JavaScript 需要能够提前(在某种程度上)预测代码,以便安全、正确地应用性能优化和范围。在某些情况下,性能影响甚至会影响 eval 之外的其他代码。

如果您想了解更多信息: https ://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval

于 2019-03-19T05:22:06.353 回答
0

既然还没有人提到它,让我补充一下eval,它对 Webassembly-Javascript 互操作非常有用。虽然将预制脚本包含在您的页面中以供您的 WASM 代码直接调用当然是理想的,但有时这是不切实际的,您需要从 C# 之类的 Web 汇编语言传入动态 Javascript 才能真正完成您需要做的事情。

在这种情况下它也是安全的,因为您可以完全控制传入的内容。好吧,我应该说,它的安全性不亚于使用 C# 编写 SQL 语句,也就是说它需要小心地完成(正确转义字符串等) .) 每当用户提供的数据用于生成脚本时。但是有了这个警告,它在互操作情况下有着明确的位置,并且远非“邪恶”。

于 2022-01-21T21:04:40.210 回答
-1

我的信念是 eval 是客户端 Web 应用程序的一个非常强大的功能,并且安全......与 JavaScript 一样安全,但不是。:-) 安全问题本质上是服务器端问题,因为现在使用 Firebug 之类的工具,您可以攻击任何 JavaScript 应用程序。

于 2009-01-15T07:34:08.467 回答
-1

JavaScript 的 eval() 什么时候不是邪恶的?

我总是试图阻止使用 eval。几乎总是有一个更干净和可维护的解决方案可用。即使是 JSON 解析也不需要Eval 。eval增加了维护地狱。并非没有道理,像道格拉斯·克罗克福德这样的大师对此并不满意。

但我发现了一个应该使用它的例子:

当您需要传递表达式时。

例如,我有一个google.maps.ImageMapType为我构造一个通用对象的函数,但我需要告诉它配方,它应该如何从zoomandcoord参数构造磁贴 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);
        },
        ....
    });
}
于 2012-04-25T23:43:03.113 回答
-1

当您没有宏时,Eval 对代码生成很有用。

对于(一个愚蠢的)示例,如果您正在编写一个Brainfuck编译器,您可能想要构造一个函数,将指令序列作为字符串执行,并对其进行 eval 以返回一个函数。

于 2013-07-12T19:54:19.057 回答
-1

代码生成。我最近编写了一个名为Hyperbars的库,它弥补了virtual-domhandlebars之间的差距。它通过解析把手模板并将其转换为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()也不是问题,因为您只需要解释一次生成的字符串,然后多次重复使用可执行输出。

如果您对此感到好奇,可以查看代码生成是如何实现

于 2016-11-28T10:59:39.990 回答
-5

当您使用解析函数(例如,jQuery.parseJSON)解析 JSON 结构时,它需要一个完美的 JSON 文件结构(每个属性名称都用双引号括起来)。但是,JavaScript 更灵活。因此,您可以使用 eval() 来避免它。

于 2012-05-17T23:00:22.590 回答