JavaScript的eval()函数在什么情况下不是邪恶的?

303
我正在编写一些JavaScript代码来解析用户输入的函数(用于类似电子表格的功能)。解析了公式后,我可以将其转换为JavaScript并对其运行eval()以得出结果。
然而,如果可以避免使用eval(),我总是回避它,因为它是有害的(而且,正确或错误地说,我一直认为在JavaScript中它甚至更加有害,因为要评估的代码可能会被用户更改)。
那么,在什么情况下可以使用它呢?

7
大多数 JSON 库实际上并没有在底层使用 eval,这样做正是为了防止安全风险。 - Sean McMillan
13
@Sean - JQuery 和 Prototype 都使用 eval(JQuery 使用 new Function) - plodder
8
@plodder - 你获取信息的来源是什么?自2010年1月以来,jQuery已经使用原生的JSON.parse()函数,可以自行查看:http://code.jquery.com/jquery-1.4.js。 - ken
4
“显然,必须使用eval()来解析JSON”——这并不正确,相反地-我们*不应该使用eval来解析JSON!*使用Douglas Crockford(JSON的创作者)在http://www.json.org/上提供的json2.js脚本! - Tomas
17
讽刺的是,json2.js使用eval来解析JSON。 - tobyodavies
显示剩余10条评论
27个回答

2

eval 很少是正确的选择。虽然有很多情况下您可以通过将脚本连接在一起并即时运行来完成所需的工作,但通常您拥有更强大和可维护的技术:关联数组表示法(obj["prop"] 相当于 obj.prop),闭包,面向对象技术,函数式技术 - 使用它们而不是 eval


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

缺点是:
  • 如果未经充分测试,代码可能会出现错误并破坏服务。

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

  • 验证脚本的一种方法是在某个安全的地方保留脚本的哈希值,以便在运行之前进行检查。


2
就客户端脚本而言,我认为安全问题是一个无意义的点。所有加载到浏览器中的内容都可能被操纵,因此应该将其视为这样处理。使用eval()语句没有任何风险,因为有更简单的方法来执行JavaScript代码和/或操作DOM中的对象,例如您浏览器中的URL栏。
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>

6
当有更简单的方法可以执行JavaScript和/或操作DOM对象时,使用eval()语句是零风险的。当一个用户可以输入代码,然后在另一个用户的浏览器中运行时,代码注入就成为了一个问题。浏览器控制台本身并不能让一个用户在另一个用户的浏览器中运行代码,因此它们在决定是否值得防止代码注入时是无关紧要的。 - Mike Samuel
即使是空的,<head></head>不是也是必需的吗? - Peter Mortensen
2
这个回答完全忽略了XSS的风险。 - Ruud Helderman

2

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

如果你创建的代码自己可信或者可以信任它,那么就没问题了。人们一直在谈论eval与用户输入无关,但有点类似~

如果有用户输入进入服务器,然后回到客户端,并且该代码在未经过消毒处理的情况下被用于eval中。恭喜你,你已经为用户数据发送给任何人打开了潘多拉魔盒。

根据eval所处的位置,许多网站使用SPAs,而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


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

在这种情况下,它也是安全的,因为您完全控制传入的内容。嗯,我应该说,在这种情况下,它并不比使用C#组合SQL语句更不安全,也就是说,每当使用用户提供的数据生成脚本时,都需要小心处理(正确转义字符串等)。但是,在互操作情况下,它有明确的位置,远非“邪恶”。


0

代码生成。我最近编写了一个名为Hyperbars的库,它在virtual-domhandlebars之间架起了一座桥梁。它通过解析handlebars模板并将其转换为hyperscript来实现这一点。首先将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() 的性能在这种情况下也不是问题,因为您只需要解释生成的字符串一次,然后多次重复使用可执行输出。

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


超文本脚本首先生成为字符串形式(...)。在构建阶段完成所有代码生成,将生成的超文本脚本代码写入单独的可执行文件(.js),然后部署该文件进行测试和生产,这样做更有意义。我喜欢您使用代码生成的方式。只是eval提示某些应该在编译时处理的责任已经转移到运行时。 - Ruud Helderman

0

我认为eval是客户端Web应用程序中非常强大的函数,也是安全的...与JavaScript一样安全,而JavaScript并不总是安全的。 :-) 安全问题本质上是服务器端的问题,因为现在有像Firebug这样的工具,可以攻击任何JavaScript应用程序。


1
使用 eval 需要防止 XSS 攻击,这并不总是容易做到的。 - Benjamin

0
如果真的需要,eval并不是邪恶的。但我碰到的99.9%的eval使用都不是必须的(不包括setTimeout相关的内容)。
对我而言,这种邪恶并非仅仅是性能或安全问题(好吧,间接地两者都是)。所有这些不必要的eval使用都会导致维护上的困难。重构工具会失效。查找代码很困难。这些eval的副作用不可预知。

5
setTimeout不需要使用eval,也可以使用函数引用。 - Matthew Crumley

0

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


3
如果您对传递给 eval 的内容有完全的控制权,那么重要的问题就是,在什么情况下将其作为字符串而不是真正的 JS 是有意义的? - cHao
例如,如果您有一个大型的游戏应用程序(5-10MB JavaScript),最好先构建一个简单的快速加载AJAX预加载器(1kb),它可以在加载大型主脚本时显示加载栏或类似的内容。下载后,您可以使用“eval(source)”或更好的“new Function(source)”来运行已加载的游戏应用程序脚本。这样用户就可以看到应用程序需要时间下载,直到游戏可以开始。如果没有这个,用户必须等待整个应用程序加载而没有任何视觉反馈。 - SammieFox
@SammieFox 还有其他更好的方法可以实现这个功能,最 notable 的就是使用<script async="true" src="...">。另请参阅:https://w3bits.com/async-javascript/ - Ruud Helderman
答案是危险的建议;太多开发人员有一种虚假的控制感。这个建议对于不再积极维护的软件确实有些道理。但是这样的软件应该被视为死亡。 - Ruud Helderman

0

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

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


关于“只要您可以确保代码来源于您或实际用户,就没有理由不使用eval()”的说法。这假设只有一个用户。但是,该前提在原始帖子中并未说明。当存在多个用户时,粗心地使用eval来组成自一个用户内容的字符串可能会允许该用户在其他用户的浏览器中执行代码。 - Mike Samuel
@MikeSamuel,eval可以在其他用户的浏览器中执行代码,我从未听说过这一点,你试过吗?这在浏览历史上从未发生过,你能给我们展示一个例子吗? - Akash Kava
@AkashKava,一个字符串可以由一个用户代理发起,存储在数据库中,然后提供给另一个浏览器进行“eval”操作。这种情况经常发生。 - Mike Samuel
@MikeSamuel 数据库?在哪里?是谁提供了错误的字符串?难道不应该归咎于服务器端的数据库吗?首先,EVAL不能为编写不良的服务器端代码负责。使用jsfiddle并展示一个真实世界的例子,证明它可能会造成伤害。 - Akash Kava
2
@AkashKava,我不明白你的问题。我们并没有谈论特定的应用程序,而是讨论不使用“eval”的原因。指责服务器有什么用处呢?如果要指责任何人,那就应该是攻击者。无论如何,一个客户端即使在服务器存在漏洞的情况下也不容易受到XSS攻击,这总比一个容易受到攻击的客户端更好,其他条件相等的情况下。 - Mike Samuel
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接