eval()不是魔鬼,只是被误解了(翻译)

原文来自:https://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/ 做者:Nicholas C.Zakasgit

在JavaScript中,我不肯定是否有比eval()受到更多诽谤的。它就是个简单的函数被设计用来将字符串转换为可被执行的JavaScript代码。在个人早期的职业生涯里,它比任何其余的东西更受关注和误解。github

大多数人认为‘eval()是魔鬼’这句话是Douglas Crockford说的。他说:“eval函数(及其亲属,Function,setTimeout,和setInterval)提供对JavaScript编译器的访问。这有时是必要的,但大多数状况,它证实这个存在是极其糟糕的代码。由于eval()这个特性经常被误用”。浏览器

由于道格拉斯的大多数做品并无注明日期,因此,我不肯定他是不是在2002年创造了这个术语。无论怎么说吧,不论是否真正理解了eval()的使用,他都成为了一个热门的短语。安全

尽管这种理论流行开来了(道格拉斯的坚持),但这并不意味着eval()的存在就有问题。使用eval()不会自动触发XSS攻击,或者你没有意识到的但存在的安全漏洞。就像工具同样,你要知道如何使用它,但即便你使用不正确,但潜在风险依然很低,而且是可容忍的。服务器

误用(滥用)

eval()之因此成为了魔鬼,是由于那些对JavaScript语言理解不够深的人误用的缘由。你可能会奇怪,误用跟安全和性能好型没有关系吧。误用是不理解如何在JavaScript中构造和使用引用。假设你有几个表单input的名字包含数字,如:option1,option2,常见的代码实现以下:cookie

function isChecked(optionNumber) {
    return eval("forms[0].option" + optionNumber + ".checked");
}

var result = isChecked(1);

在这种状况下,开发人员在尽力尝试写forms[0].option1.checked,而没有想我不用eval()该如何作。这样的状况出如今不少10年左右工做经验的开发者,它们不明白如何更好地使用。这里也不是说eval()在这里不合适,而是由于没有必要,你彻底能够更简单的实现,用以下的代码:网络

function isChecked(optionNumber) {
    return forms[0]["option" + optionNumber].checked;
}

var result = isChecked(1);

在不少状况下,你可使用[]表示法来替换eval()的使用去构造属性名,这也是 [] 存在的一个缘由。包括道格拉斯在内的早期博主们都是在讨论这个问题。闭包

可调式性

不使用eval()的一个重要理由是为了达到调试的目的。之前,若是出现问题,不可能进入eval()代码。这就意味着你的代码运行在一个黑盒中,而后从中取出。如今Chrome开发工具能够调试eval()内的代码,可是有一个问题是,你必须等代码执行一次后才出如今源面板中。函数

不使用eval()能够令咱们的代码调试起来更容易,跟方便的查看源代码。但这并不能说明eval()是魔鬼,这只是开发工做流程中的一点问题。工具

性能

对eval()的另外一个重要影响是它的性能。在旧的浏览器中,你遇到了双重解释惩罚,也就是说,你的代码被解释,而eval()中的代码被解释。在没有编译JavaScript引擎的浏览器中,结果可能会慢十倍(甚至更糟)。

在现代编译JavaScript的引擎中,eval()仍然是一个问题。大多数引擎能够用两种方式运行代码:快速路径或慢路径。快速路径代码是一种稳定且可预测的代码,所以能够为更快的执行而编译。缓慢的路径代码是不可预测的,这使得编译很难,而且可能仍然使用一个解释程序运行。在你的代码中仅仅存在eval()意味着它是不可预测的,所以将在解释器中运行它以“旧浏览器”的速度运行,而不是“新浏览器”的速度(10倍的差别)。

一样的,eval()使YUI压缩器不可能在调用eval()的范围内munge变量名。因为eval()能够直接访问任何这些变量,重命名它们会引入错误(其余工具如闭包编译器和UglifyJS可能仍然会蒙混这些变量——最终致使错误)。

所以,在使用eval()时,性能仍然是一个大问题。但这很难让它成为邪恶,但这是一个须要注意的警告。

安全

在说到eval()的安全时,这是大部分人认为eval是魔鬼的有力佐证。大多数状况下,就是说XSS攻击,以及eval()如何向它们打开代码。在表面上,这种混淆是能够理解的,由于eval()在页面上下文中可执行任意代码。若是你接受用户输入并经过eval()运行它,这将是危险的。可是,若是你的输入不是来自用户,是否存在真正的危险?

我收到了不止一个的抱怨,在个人CSS解析器中使用eval()的一段代码中使用的代码使用eval()将字符串标记从CSS转换为JavaScript字符串值。除了建立本身的字符串解析器外,这是得到token的正确字符串值的最简单方法。到目前为止,尚未人可以或愿意提出一种攻击方案,在这种状况下,这段代码会引发麻烦,由于:

  • eval()的值来自于tokenizer
  • tokenizer已经验证了它是一个有效的字符串
  • 代码最常在命令行上运行。
  • 即便在浏览器中运行,该代码也被封闭在闭包中,不能直接调用。

固然,因为这段代码的主要目标是命令行,因此这个故事有点不一样。

设计用于浏览器的代码面临不一样的问题,可是eval()的安全性一般不是其中之一。一样,若是你以某种方式接收用户输入并将其传递给eval(),那么你就是在自找麻烦,不要这样作。可是,若是你使用eval()的输入,只有你控制而且不能被用户修改,那么就没有安全风险。

最多见的攻击是来自服务器的eval()代码。这一模式以引入JSON而著名,它之因此流行,是由于它能够经过eval()快速转换成JavaScript。实际上,Douglas Crockford本身在他的原始JSON实用程序中使用eval(),由于它能够转换速度。他确实添加了检查以确保没有真正的可执行代码,可是实现从根本上是eval()。

如今,大多数人都使用浏览器内置的JSON解析功能来实现这一目的,尽管有些人仍然经过eval()来获取任意的JavaScript,做为延迟加载策略的一部分。一些人认为,这才是真正的安全漏洞。若是正在进行中间人攻击,那么你将在页面上执行任意攻击代码。

中间人攻击被认为是eval()的永远存在的危险,会受到蠕虫的的攻击。可是,这是一个与我无关的场景,由于任什么时候候你不能相信你正在联系的服务器,就意味着有可能出现不少很差的事情。中间人攻击能够经过多种方式向页面注入代码:

  • 返回经过 <script></script> 加载的JavaScript代码。
  • 经过返回攻击者控制的JSON-P请求代码。
  • 经过从一个Ajax请求返回attacker控制的代码,而后eval()。

此外,这样的攻击能够很容易地窃取cookie和用户数据,而不会改变任何东西,更不用说经过返回攻击者控制的HTML和CSS来进行网络钓鱼的可能性了。

简单地说,eval()不会像加载外部JavaScript那样打开中间人攻击。若是你不能信任服务器上的代码,那么你将遇到比eval()调用更大的问题。

结论

我不是说你应该跑出去,开始使用eval()。实际上,不多有好的用例来运行eval()。对于代码清晰性、调试性,以及不该该忽略的性能,确实存在一些问题。可是在eval()有意义的状况下,你不该该惧怕使用它。不要第一次使用它,可是不要让任何人吓到你,认为你的代码在使用eval()时更加脆弱或不安全。

文章稍后可能还会继续修改,也欢迎各位批评指正。有问题或者有其余想法的能够在个人GitHub上pr。

相关文章
相关标签/搜索