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

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

然而,我总是避免使用eval(),如果我可以避免它,因为它是邪恶的(而且,正确或错误,我一直认为它在JavaScript中更邪恶,因为要计算的代码可能会被用户更改)。

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

107924 次浏览

当你相信消息来源时。

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

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

唯一需要使用eval()的情况是需要动态地运行JS。我说的是你从服务器异步下载的JS…

...10次中的9次你都可以通过重构来避免这种情况。

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

我倾向于用Crockford的建议代替eval(),并完全避免使用它。即使是看起来需要它的方法也不需要。例如,setTimeout()允许你传递一个函数而不是eval。

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

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

如果可能的话,只在测试期间使用。还要注意eval()比其他专门的JSON等求值器要慢得多。

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

如果,为了你的目的eval()比手动解释更快,或者使您的代码更简单,或更清楚……那么你应该使用它。如果两者都不是,那么你就不应该这么做。就这么简单。

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

所以…何时不使用eval()?Eval()只应该在第三方有可能更改它的情况下才使用。比如拦截客户端和服务器之间的连接(但如果这是一个问题,请使用HTTPS)。你不应该用eval()来解析别人写的代码,比如在论坛上。

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

首先,让我们看看使用eval()的危险。就像其他事情一样,可能有许多小的隐患,但是两个大的风险——eval()被认为是邪恶的原因——是性能和代码注入。

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

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

至于性能,您必须将其与编码的便捷性进行权衡。我的观点是,如果要解析公式,不妨在解析期间计算结果,而不是运行另一个解析器(eval()内的解析器)。但是使用eval()编码可能更容易,而且性能上的影响可能不太明显。在这种情况下,看起来eval()并不比任何其他可能为您节省时间的函数更邪恶。

我看到有人主张不使用eval,因为它是邪恶的,但我也看到同样的人动态地使用Function和setTimeout,所以他们使用eval 引擎盖下:D

顺便说一句,如果你的沙盒不够确定(例如,如果你在一个允许代码注入的网站上工作),eval是你的最后一个问题。安全的基本规则是所有输入是邪恶的,但对于JavaScript 甚至 JavaScript本身可能是邪恶的,因为在JavaScript中你可以覆盖任何函数,你只是不能确定你使用的是真正的函数,所以,如果恶意代码在你之前开始,你不能相信任何JavaScript内置函数:D

现在这篇文章的尾声是:

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

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

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

我相信eval对于客户端web应用程序来说是一个非常强大的功能,并且是安全的。和JavaScript一样安全,但JavaScript不安全。:-)安全问题本质上是服务器端的问题,因为现在,使用像Firebug这样的工具,你可以攻击任何JavaScript应用程序。

eval很少是正确的选择。虽然在很多情况下,你可以通过将脚本连接在一起并动态运行来完成你需要完成的任务,但你通常可以使用更强大和可维护的技术:关联数组符号(obj["prop"]obj.prop相同)、闭包、面向对象技术、函数技术——请使用它们。

至于客户端脚本,我认为安全性问题是一个有争议的问题。加载到浏览器中的所有内容都受到操作的影响,因此应该这样对待。当有更简单的方法来执行JavaScript代码和/或操作DOM中的对象(例如浏览器中的URL栏)时,使用eval()语句的风险为零。

javascript:alert("hello");

如果有人想操纵他们的DOM,我说swing away。防止任何类型的攻击的安全性应该始终是服务器应用程序的责任。

从实用主义的角度来看,在可以用其他方法完成任务的情况下,使用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>

让我们看看真正的人:

  1. 现在每个主流浏览器都有一个内置的控制台,你想成为黑客的人可以使用它来调用任何值的任何函数-为什么他们会麻烦使用eval语句-即使他们可以?

  2. 如果编译2000行JavaScript需要0.2秒,那么如果我计算4行JSON,性能会下降多少?

即使克罗克福德对“eval是邪恶的”的解释也很软弱。

eval is Evil, eval函数是最被滥用的特性 JavaScript。避免它< / p >

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

理解eval并知道它什么时候可能有用更重要。例如,eval是评估软件生成的服务器响应的明智工具。

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

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

我总是尝试不鼓励使用eval。几乎总是有更干净和可维护的解决方案可用。Eval 即使是JSON解析也不需要。Eval 增加维护难度。道格拉斯·克罗克福德(Douglas Crockford)这样的大师并不赞同这一点,这不无道理。

但是我找到了一个应该是使用的例子:

当你需要传递表达式时。

例如,我有一个函数为我构造了一个通用的google.maps.ImageMapType对象,但我需要告诉它配方,它应该如何从zoomcoord参数构造tile 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);
},
....
});
}

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

Eval是编译的补充,编译用于代码模板。我所说的模板是指编写一个简化的模板生成器,生成有用的模板代码,从而提高开发速度。

我写了一个框架,其中开发人员不使用EVAL,但他们使用我们的框架,反过来,该框架必须使用EVAL来生成模板。

通过以下方法可以提高EVAL的性能:您必须返回一个函数,而不是执行脚本。

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

它应该被组织成

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


var a = f(3,5);

缓存肯定会提高速度。

Chrome也允许调试这样的功能非常容易。

在安全性方面,是否使用eval几乎没有什么区别。

  1. 首先,浏览器在沙盒中调用整个脚本。
  2. 任何在EVAL中是邪恶的代码,在浏览器中也是邪恶的。攻击者或任何人都可以很容易地在DOM中注入脚本节点,如果他/她可以评估任何东西,就可以做任何事情。不使用EVAL不会有任何区别。
  3. 最有害的是糟糕的服务器端安全性。服务器上糟糕的cookie验证或糟糕的ACL实现导致了大多数攻击。
  4. 在Java的原生代码中存在一个最近的Java漏洞等。JavaScript过去和现在都是设计在沙盒中运行的,而applet则是设计在沙盒外运行的,带有证书等,这导致了漏洞和许多其他问题。
  5. 编写模拟浏览器的代码并不难。您所要做的就是使用您最喜欢的用户代理字符串向服务器发出HTTP请求。所有的测试工具都模仿浏览器;如果攻击者想要伤害你,EVAL是他们最后的手段。他们有许多其他方法来处理服务器端安全性。
  6. 浏览器DOM不能访问文件,也不能访问用户名。事实上,计算机上没有eval可以访问的东西。

如果您的服务器端安全性足够坚固,任何人都可以从任何地方进行攻击,那么您就不应该担心EVAL。正如我提到的,如果EVAL不存在,攻击者就有很多工具来入侵你的服务器,而不管你浏览器的EVAL能力如何。

Eval只适用于生成一些模板,根据事先没有使用的内容进行复杂的字符串处理。例如,我更喜欢

"FirstName + ' ' + LastName"

而不是

"LastName + ' ' + FirstName"

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

我的使用eval的例子:进口

通常的做法。

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

但是在eval和一个小的helper函数的帮助下,它得到了更好的外观:

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;
}

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

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

在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引用的变量应该是闭包的一部分,但实际上不是时,它的计算可能会失败。

代码生成。我最近写了一个名为高气压的库,它弥合了virtual-dom车把之间的差距。它通过解析句柄模板并将其转换为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()的性能也不是问题,因为你只需要解释一次生成的字符串,然后多次重用可执行输出。

如果你好奇在这里,你可以看到代码生成是如何实现的。

在服务器端,当处理sql、influxdb或mongo等外部脚本时,eval非常有用。可以在运行时进行自定义验证,而无需重新部署服务。

例如,具有以下元数据的成就服务

{
"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中的任务或事件

缺点:

  • 如果没有完全测试,代码中的错误可能会破坏服务中的内容。

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

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

我认为评价被证明是正确的情况将是罕见的。”你更可能认为它是合理的,而不是在实际上合理的情况下使用它。

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

如果你想知道更多 https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval < / p >

底线

如果你创建或净化了你eval的代码,它永远不会是邪恶的

略为详细

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

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

显然,你应该总是净化输入,以便对你的代码消耗的东西有一些控制。

推理

客户端可以运行任何他们想要的代码,即使开发人员没有编写这些代码;这不仅适用于什么,而且适用于调用eval本身

Eval并不邪恶,只是被滥用了。

如果你创建了代码,或者可以信任它,它是好的。 人们一直在说用户输入对eval不重要。有点像~

如果用户输入到服务器,然后返回到客户端,那么这些代码就被用于eval而没有被净化。恭喜你,你打开了潘多拉的盒子,用户数据可以发送给任何人。

根据eval的位置不同,许多网站都使用spa, 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


由于还没有人提到它,让我补充一下,eval对于Webassembly-Javascript互操作非常有用。虽然在您的页面中包含WASM代码可以直接调用的预先制作的脚本当然是理想的,但有时这是不可行的,您需要从Webassembly语言(如c#)传入动态Javascript来真正完成您需要做的事情。

在这种情况下,它也是安全的,因为您可以完全控制传入的内容。好吧,我应该说,这并不比使用c#编写SQL语句更安全,也就是说,无论何时使用用户提供的数据来生成脚本,都需要谨慎地执行(正确转义字符串等)。但有了这样的警告,它在互操作情况下有一个明确的位置,远非“邪恶”。

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