与其他可能有害的特征相比,为什么将类似eval的特征视为邪恶?


51

大多数现代语言(以某种方式解释)都具有某种评估功能。这样的函数执行任意语言代码,大部分时间作为字符串作为主要参数传递(不同的语言可能会向eval函数添加更多功能)。

我知道不应允许用户执行此功能(编辑,即直接或间接地从任意用户获取任意输入以传递给eval),尤其是对于服务器端软件,因为它们可能会迫使进程执行恶意代码。这样,教程和社区会告诉我们不要使用评估。但是,在很多情况下eval都是有用的和被使用的:

  • 对软件元素的自定义访问规则(IIRC OpenERP有一个ir.rule可以使用动态python代码的对象)。
  • 自定义计算和/或条件(OpenERP具有允许自定义代码计算的字段)。
  • OpenERP报表解析器(是的,我知道我对OpenERP的东西很感兴趣……但这是我的主要示例)。
  • 在某些RPG游戏中编码法术效果。

因此,只要正确使用它们,它们就有很好的用途。主要优点是该功能允许管理员编写自定义代码,而无需创建更多文件并包含它们(尽管大多数使用eval功能的框架也可以指定要读取的文件,模块,程序包...)。

但是,在流行文化中,评估是邪恶的。就像闯入您的系统一样。

但是,如果用户以某种方式访问​​其他功能,这些功能可能会有害:取消链接,读取,写入(文件语义),内存分配和指针算术,数据库模型访问(即使不考虑SQL可注入的情况)。

因此,基本上,在大多数情况下,任何代码编写不正确或未正确监视(资源,用户,环境等)时,代码都是邪恶的,甚至可能导致经济影响。

但是eval函数有一些特殊之处(与语言无关)。

问题:这种恐惧成为流行文化的一部分,是否有任何历史事实,而不是同样关注其他可能危险的特征?


11
我不同意这个范围太广,并且作为证据,我提出了四个合理长度的答案。

12
他们投票决定关闭范围是否太广泛?(在该社区中,我看不到亲密的选票)亲密的原因正在成为通配符原因,最近毫无意义。特别是当我提出的要点很清楚,并带有示例和要问的具体要点时。我会在Meta ...中问这个话题
Luis Masuelli

13
为什么它成为流行文化的一部分,并且比其他危险功能更受关注?因为押韵EVAL - 容易记住
BERGI

5
许多认为内存分配和指针算术邪恶的。
user253751'3

5
应该注意的是,OpenERP(现在称为Odoo)不只是调用eval,它还具有一个内部函数,该函数safe_eval为环境做准备以防止代码做危险的事情。但是,由于Python是一种非常灵活的语言,因此已经发现了bug,因此很难控制。
安德烈·帕拉梅斯(AndréParamés)'16

Answers:


76

一个eval函数本身并不是邪恶的,有一点我不相信您在说:

允许程序执行任意用户输入是不好的

我编写的代码使用了某种eval功能,而且很安全:程序和参数是硬编码的。有时,没有语言或库功能可以执行程序所需的操作,而运行Shell命令是很短的路径。“我必须在几个小时内完成编码,但是编写Java / .NET / PHP /任何代码都需要两天。或者我可以eval在五分钟内完成。”

一旦允许用户执行他们想要的任何事情,即使被用户特权锁定或在“安全”屏幕后面,您都将创建攻击媒介。每个星期,一些随机的CMS,博客软件等都会修补一个安全漏洞,攻击者可以利用此漏洞利用此漏洞。您将依赖整个软件堆栈来保护对可以使用的功能的访问rm -rf /或其他灾难性的操作(请注意:该命令不太可能成功,但会造成一些损害失败)。

这种恐惧成为流行文化的一部分,而不是将同样的注意力放在其他可能存在的危险特征上,是否有任何历史事实?

是的,有一个历史先例。由于多年来已在允许远程攻击者执行任意代码的各种软件中修复了许多错误,因此,这种想法在eval大多数情况下不受欢迎。现代语言和库具有丰富的功能集,这些功能的eval重要性已降低,这绝非偶然。它既使功能更易于使用,又降低了被利用的风险。

流行语言中的许多潜在不安全功能已经引起了很多关注。是否受到更多关注主要是一个见解,但是这些eval功能肯定存在一个易于理解的可证明的安全问题。首先,它们允许执行操作系统命令,包括标准的外壳程序内置程序和外部程序(例如rmdel)。第二,结合其他漏洞利用,攻击者可能可以上载自己的可执行文件或Shell脚本,然后通过您的软件执行该脚本,从而为几乎所有事情发生打开了大门(没有一个是好的)。

这是一个难题。软件是复杂的,并且软件堆栈(例如LAMP)是以复杂方式彼此交互的多个软件。请注意如何使用诸如此类的语言功能,并且切勿允许用户执行任意命令。


2
必须是你 :)。尽管如果有一个很好的众所周知的例子促成这种文化,我想在答案中看到它。
Luis Masuelli '16

5
我还没有一个例子,我相信这是更多的“千刀斩ath”,其中有许多小的例子正在被使用和打补丁。当我说了一些远程利用在每周一次的基础上打补丁,我并没有夸张一些的软件。

1
我的IDE是一个程序,它允许我-它是用户-执行任意输入。我觉得它有用而不是坏。
书斋

3
@Den是个例外。作为一个IDE,我相信没有该功能,您可能会造成混乱。只需将其编写在您的源代码中即可。另外,您已经拥有运行它的计算机的完全访问权限。这里的含义是eval可以提升特权。如果它们已经升高,这不是问题。

3
@丹:这是雷蒙德·陈(Raymond Chen)概括为“气闸的另一面”。您已经可以运行了rm -rf /,不需要通过IDE进行复杂的操作。问题eval在于,它将这种能力向许多本不应该具有这种能力的演员开放。
MSalters '16

38

部分原因是细微差别很难。说起来很简单,例如永远不要使用goto,公共字段,用于SQL查询的字符串插值或eval。这些陈述不应真正理解为在任何情况下都没有理由使用它们。但是,避免将它们作为一般经验法则是一个好主意。

不建议使用Eval,因为它结合了几个常见问题。

首先,它容易受到注入攻击。这里就像SQL注入一样,当将用户控制的数据插入代码中时,很容易意外地允许插入任意代码。

其次,初学者倾向于使用eval来解决结构不良的代码。初学者可能会编写如下代码:

x0 = "hello"
x1 = "world"
x2 = "how"
x3 = "are"
x4 = "you?"
for index in range(5):
   print eval("x" + index)

这可行,但实际上是解决此问题的错误方法。显然,使用列表会更好。

第三,评估通常效率低下。花费了大量的精力来加快我们的编程语言的实现。但是评估很难加速,使用它通常会对您的表现产生不利影响。

因此,评估不是邪恶的。我们可以说eval是邪恶的,因为好吧,它是一种吸引人的表达方式。任何初学者都应严格远离评估,因为无论他们想做什么,评估几乎肯定是错误的解决方案。对于某些高级用例,eval是有意义的,您应该使用它,但显然要当心陷阱。


13
您的第二种情况非常重要。在具有eval但也经过编译的语言中,评估字符串以产生变量引用是很重要的。例如,如果var x = 3; print(x)编译, 则无需在运行时知道使用名称“ x”。为了var x = 3; print(eval("x"))工作,需要记录该映射。这是一个真实的问题:在Common Lisp中,(let ((x 3)) (print (eval 'x)))将引发未绑定变量异常,因为在编译代码后,词法变量x与名称没有任何关系。
约书亚·泰勒

@JohnuaTaylor您的意思是第三个原因,不是第二原因?
user253751'3

1
@immibis,我认为他是在第二个原因中引用代码。但是您是正确的,它确实与第三个原因有关:性能。
Winston Ewert'3

看到了这个问题,不是吗?;)
jpmc26 2016年

@ jpmc26,不。但是我已经看到了很多类似的东西。
Winston Ewert

20

归结为,“任意代码执行”是“可以做任何事情”的技术讨论。如果某人能够利用您代码中的任意代码执行,则这实际上是可能的最严重的安全漏洞,因为这意味着他们能够执行系统可能执行的所有操作。

“其他可能有害的错误”可能有一定的局限性,这意味着,从定义上讲,与被利用的任意代码执行相比,它们的危害较小。


2
出于争论的目的,滥用指针还会导致任意代码执行,但指针的皱眉要少得多eval
德米特里·格里戈里耶夫

12
@DmitryGrigoryev他们是真的吗?在C ++之外,通常认为指针是非常危险的,应该避免使用。例如,C#支持指针,但在耗尽所有其他选项(通常出于数据处理的性能原因)之后,它往往是您尝试的最后一件事。大多数现代语言不支持指针。甚至C ++本身也朝着“更安全”的指针发展,这些指针试图缓解任意指针的问题(例如,使用真实的列表/数组而不是仅使用指针,使用safe_ptr,引用传递...)。
a安

2
@DmitryGrigoryev:您能为有人说指针没有eval危险提供参考吗?
JacquesB '16


1
@JacquesB,是的;这本来是个玩笑(我希望笑容会足够清楚)。OP声明了该声明,而不是我,因此您应要求他提供证明。
德米特里·格里戈里耶夫

15

有实际和理论上的原因。

实际原因是我们观察到它经常会引起问题。很少有eval可以提供一个好的解决方案,并且常常会导致糟糕的解决方案,如果您假装不存在eval并以不同的方式解决问题,那么最终会得到更好的解决方案。因此,简化建议就是忽略它,如果您想忽略简化建议,那么,希望您已经充分考虑了一下,以了解简化建议为什么不适用且常见。陷阱不会影响您。

更理论上的原因是,如果这是很难写出好的代码,它写更难代码写入好的代码。无论您是使用eval,还是通过将字符串粘在一起来生成SQL语句,还是编写JIT编译器,您尝试执行的操作通常都比预期的难。恶意代码注入的可能性是问题的很大一部分,但除此之外,如果您的代码直到运行时才存在,通常很难知道您的代码是否正确。因此,简化的建议是让您自己更轻松:“使用参数化的SQL查询”,“不要使用eval”。

以您的咒语效果为例:在游戏中构建Lua(或任何其他)编译器或解释器是一回事,以使游戏设计师比C ++(或任何其他语言)更容易描述咒语效果。如果您所做的只是评估已编写和测试并包含在游戏或DLC或您拥有的代码中,则大多数“评估问题”均不适用。那只是混合语言。当您尝试动态生成Lua(或C ++,SQL,Shell命令或其他任何东西)并将其弄乱时,遇到的大问题会打击您。


1
当然,运行时代码生成可以被正确地完成,和eval 可以是有用的。例如,我经常使用eval进行元编程/类似宏的东西,尤其是当我希望获得比“更干净的” OOP或功能性解决方案所提供的性能更高的性能时。生成的代码通常更好,更简单,因为模板更少。但是,要意识到与沙箱,转义机制,代码注入,作用域规则,错误报告,优化等有关的各种问题,就需要精通该语言,并且如果没有该知识,评估是非常危险的。
阿蒙(Amon)

11

不,没有明显的历史事实。

从一开始就可以清楚地看到eval的弊端。其他功能有轻微危险。人们可以删除数据。人们可以看到他们不应该看到的数据。人们可以写他们不应该写的数据。而且,如果您以某种方式搞砸了并且不验证用户输入,那么他们只能做大多数事情。

使用eval,他们可以破解五边形,使其看起来像您所做的那样。他们可以检查您的击键来获取您的密码。假设使用图灵完整的语言,他们就可以执行您的计算机能够执行的任何操作。

而且您无法验证输入。这是一个任意的自由格式字符串。验证它的唯一方法是为所讨论的语言构建解析器和代码分析引擎。祝您好运。


1
...或使输入来自可信来源,例如游戏的咒语定义文件。
user253751'3

3
@immibis,但是如果输入已预定义并经过安全测试,为什么在可以将eval包含在系统源中时使用eval?
Jules

3
@immibis-因为人能破解游戏文件...
Telastyn

2
……这不是“转向完整”的意思(转向完整是与无关紧要的计算相距仅一步之遥)。
Leushenko '16

1
@Telastyn:这是Javascript程序无法做到的。甚至是C ++程序。Javascript在一个沙箱(浏览器)中运行,而沙箱本身又在另一个沙箱中(在x86中为3环)。在操作系统的控制下,中断向量在该沙箱外部。而外部沙盒(第3环)是由CPU强制执行的。
MSalters '16

6

我认为可以归结为以下几个方面:

  • 必要性
  • (受保护的)用法
  • 访问
  • 可验证性
  • 多阶段攻击

必要性

嗨,我已经写了这个非常酷的图像编辑工具(售价0.02美元)。打开图像后,可以在图像上传递大量过滤器。您甚至可以使用Python(我在其中编写应用程序的程序)编写脚本。我只用eval您的意见,相信您会成为一个受人尊敬的用户。

(后来)

感谢您购买。如您所见,它的功能完全符合我的承诺。哦,您要打开图像?不,你不能。我不会使用该read方法,因为它有些不安全。保存?不,我不会使用write

我想说的是:您几乎需要基本工具的读/写功能。同样用于存储您如此出色的游戏的高分。

如果没有读/写,则图像编辑器将无用。没有eval?为此,我将为您编写一个自定义插件!

(受保护的)用法

许多方法都可能有危险。例如readwrite。一个常见的示例是Web服务,允许您通过指定名称从特定目录读取图像。但是,“名称”实际上可以是系统上的任何有效(相对)路径,从而使您可以读取Web服务可以访问的所有文件,而不仅仅是图像。滥用此简单示例称为“路径遍历”。如果您的应用允许路径遍历,那就不好了。如果read没有为路径遍历辩护,则可以称为邪恶。

但是,在其他情况下,for字符串read完全在程序员的控制之下(也许是硬编码的)。在那种情况下,使用它几乎不是邪恶的read

访问

现在,使用另一个简单示例eval

在网络应用中的某个位置,您需要一些动态内容。您将允许管理员输入一些可执行的代码。鉴于管理员是受信任的用户,从理论上讲这是可以的。只要确保不执行非管理员提交的代码,就可以了。

(也就是说,直到您解雇了那个不错的管理员,但忘了撤消他的访问权限。现在您的Web应用程序已被删除)。

可验证性。

我认为,另一个重要方面是验证用户输入的容易程度。

在通话中使用用户输入吗?只需(非常)确保读取调用的输入中没有任何恶意内容。标准化路径,并确认打开的文件在您的媒体目录中。现在很安全。

用户在写呼叫中输入?相同!

SQL注入?只需对其进行转义,或使用参数化查询,就可以保证安全。

评估?您将如何验证用于eval呼叫的输入?您可以非常努力地工作,但是要使其安全地工作确实非常困难(如果不是不可能的话)。

多阶段攻击

现在,每次使用用户输入时,都需要权衡使用它的好处和危险。尽可能保护其使用。

再次考虑eval管理示例中的功能。我告诉过你还可以。

现在,考虑一下您的Web应用程序中确实有一个您忘记转义用户内容(HTML,XSS)的地方。这比用户可访问的评估要小。但是,使用未经转义的用户内容,用户可以接管管理员的网络浏览器,并eval通过管理员会话添加一个功能强大的Blob,从而再次允许完全系统访问。

(可以通过SQL注入而不是XSS来完成相同的多阶段攻击,或者通过写入任意文件来替换可执行代码来代替使用eval


2
“您可以非常努力地工作,但是要使其安全地工作确实非常困难(如果不是不可能的话)。” –这不可能的。简单的证明:不是试图找出是否将代码提供的用户是“安全”,只是揣摩的东西多了,简单多了,看看有多难已经是:不经用户停止提供的代码?
约尔格W¯¯米塔格

2
@JörgWMittag:给我一个用Coq编写的程序,我会证明这一点。
安德烈·帕拉梅斯(AndréParamés)'16

1
@JörgWMittag:同意。取决于语言和用户输入的范围。可能是这样eval("hard coded string" + user_input_which_should_be_alphanumeric + "remainder")。可以验证输入为字母数字。同样,“是否停止”与“是否修改/访问不应该触摸的状态”和“是否应该调用的方法”正交。
Sjoerd Job Postmus'3

3
@JörgWMittag不可能证明给定的程序不会做某事,但是要保守地定义一组可以证明它的受限程序并强制输入程序是该程序集的成员,这并非不可能。
user253751'3

3

为了使此功能完全起作用,这意味着我需要在其周围保留一个反射层,以便能够完全访问整个程序的内部状态。

对于解释语言,我可以简单地使用解释器状态,这很容易,但是与JIT编译器结合使用时,它仍然大大增加了复杂性。

没有eval,JIT编译器通常可以证明没有从任何其他代码访问线程的本地数据,因此完全可以接受对访问进行重新排序,省略锁并将常用数据缓存更长的时间。当另一个线程执行一条eval语句时,可能有必要对此进行同步,因此,JIT生成的代码突然需要一种回退机制,该机制可以在合理的时间内返回未优化的执行。

这种代码倾向于包含许多细微,难以重现的错误,同时,它也限制了JIT编译器的优化。

对于已编译的语言,折衷甚至更糟:大多数优化都是被禁止的,而且我需要保留大量的符号信息和解释器,因此通常不值得额外的灵活性-定义一些内部接口通常更容易结构,例如,通过提供脚本视图控制器并发访问程序模型


“要使此功能完全起作用,这意味着我需要在其周围保留一个反射层,以便可以完全访问整个程序的内部状态”-不,仅是要影响的部分。对于OpenERP / Odoo,被逃避的代码只能访问其可以调用的非常有限的变量和函数,而且它们都是线程局部的。
安德烈·帕拉梅斯(AndréParamés)'16

1

我拒绝这样一个前提:它eval被认为比指针算术更邪恶,或者比直接内存和文件系统访问更危险。我不知道有什么明智的开发商会相信这一点。此外,支持指针算术/直接内存访问的语言通常不支持eval,反之亦然,因此,我要确保这种比较甚至有多重要。

eval可能是一个更为知名的漏洞,原因很简单,JavaScript支持该漏洞。JavaScript是一种沙盒语言,没有直接的内存或文件系统访问权限,因此它仅具有这些漏洞,可消除语言实现本身的弱点。Eval因此,它是该语言最危险的功能之一,因为它打开了执行任意代码的可能性。我相信使用JavaScript开发的开发人员比使用C / C ++的开发人员还多,因此eval对于大多数开发人员而言,要意识到的要比缓冲区溢出更重要。


0

没有认真的程序员会认为Eval是“邪恶的”。与其他任何工具一样,它只是一个编程工具。对这种功能的恐惧(如果有恐惧)与流行文化无关。这只是一个危险的命令,经常被滥用,并且会引入严重的安全漏洞并降低性能。根据我自己的经验,我会说很少遇到无法通过其他方法更安全有效地解决的编程问题。最有可能使用eval的程序员是最没有资格安全使用eval的那些程序员。

话虽如此,有些语言适合使用eval。Perl浮现在脑海。但是,我个人发现在其他更现代的语言中很少需要它,它们本来支持结构化异常处理。


这甚至都没有试图解决所问的问题:“这种恐惧是否成为流行文化的一部分,是否存在任何历史事实”。请参阅如何回答
蚊蚋

我认为这个问题是基于错误的前提,所以我回答了这个问题。
user1751825 '16

0

我认为您在问题的以下部分(重点是我的)中很好地涵盖了这一点:

只要使用正确,它们就会很好地使用

对于95%可能正确使用它的人来说,这一切都很好。但是总会有人使用不当。其中一些将归因于缺乏经验和能力不足,其余的将是恶意的。

总会有人想要突破界限并发现安全漏洞-有些是好事,有些是坏事。

至于它的历史事实方面,eval类型函数本质上允许任意代码执行,而以前在流行的Web CMS Joomla!中已经利用了任意代码。和Joomla一起!为全球超过250万个站点提供了强大的动力,这不仅给那些站点的访问者带来了很大的潜在损害,而且还潜在地损害了其托管的基础架构以及所开发站点/公司的声誉。

Joomla!这个例子可能很简单,但是已经被记录下来了。


0

有一些充分的理由不鼓励使用eval(尽管有些是特定于某些语言的)。

  • 所使用的环境(在词法上和动态上)eval经常令人惊讶(也就是说,您认为eval(something here)应该做一件事,但是又要做另一件事,可能触发异常)
  • 通常有一种更好的方法来完成同一件事(构造的词法闭包的连接有时是更好的解决方案,但这很可能是Common Lisp特有的)
  • 最终要评估不安全的数据太容易了(尽管大多数情况下都可以避免)。

我个人不会说它是邪恶的,但是我将始终对eval代码审查中的用法提出质疑,其措辞类似于“您是否在这里考虑过一些代码?” (如果我有时间至少进行一次临时更换),或“您​​确定评估确实是这里的最佳解决方案?” (如果我不这样做)。


这甚至都没有试图解决所问的问题:“这种恐惧是否成为流行文化的一部分,是否存在任何历史事实”。请参阅如何回答
蚊蚋

0

他在《明斯基的心灵社会》第6.4章中说

头脑有一种观察自己并仍然跟踪正在发生的事情的方法。将大脑分为A和B两部分。将A大脑的输入和输出连接到现实世界-这样它就可以感知在那里发生的事情。但是,根本不要将B脑连接到外部世界。而是连接它,以便A大脑成为B大脑的世界!

当一个程序(B)编写另一个程序(A)时,它正在充当元程序。B的主题是程序A。

有一个C程序。那就是编写程序B的想法。

危险是可能有恶意的人(C')干预此过程,因此您会遇到SQL注入之类的问题。

面临的挑战是使软件更智能,同时又不使其变得更加危险。


两个赞成,两个反对。看起来这很让人不安。
Mike Dunlavey

0

您在这里有很多好的答案,主要的原因显然是任意代码执行都是不好的mmmkay,但是我将添加其他因素,其他因素仅涉及以下方面:

这是真的很难排除正被评估出来的文本字符串的代码。普通的调试工具几乎可以直接使用,而您只能使用老式的跟踪或回显工具。显然,如果您希望在一个代码行中包含一个片段,那么没关系,eval但是作为一个经验丰富的程序员,您可能会找到一个更好的解决方案,而较大规模的代码生成可能是评估函数所需要的地方。成为潜在有用的盟友。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.