什么时候在php中是邪恶的?


84

在我用php开发的所有年份中,我一直都听说使用eval()邪恶。

考虑以下代码,使用第二个(更优雅的)选项是否有意义?如果没有,为什么?

// $type is the result of an SQL statement
// e.g. SHOW COLUMNS FROM a_table LIKE 'a_column';
// hence you can be pretty sure about the consistency
// of your string
$type = "enum('a','b','c')";

// possibility one
$type_1 = preg_replace('#^enum\s*\(\s*\'|\'\s*\)\s*$#', '', $type);
$result = preg_split('#\'\s*,\s*\'#', $type_1);

// possibility two
eval('$result = '.preg_replace('#^enum#','array', $type).';');

2
eval总是很邪恶的,总有一种更好的代码编写方法,特别是因为PHP引入了匿名函数。在这种情况下,我将使用$result = array(); preg_replace_callback('#^enum\s*\(\s*\'|\'\s*\)\s*$#', function($m) use($result) { $result[] = $m[1]; }, $type);
Geoffrey

好吧,老实说,我认为php的主要问题不是语言本身,而是使用它的人。这个问题的三个正确答案(托马斯·特雷布尔,脑裂的和我的)都被否决了,没有人反对他们。另一方面,一个答案声称“有时eval()是唯一/正确的解决方案”,没有示例或解释,并且为此受到了最高投票……
Francois Bourgeois

Answers:


133

在将eval()称为纯邪恶时,我会保持谨慎。动态评估是一个强大的工具,有时可以节省生命。使用eval()可以解决PHP的缺点(请参见下文)。

eval()的主要问题是:

  • 潜在的不安全输入。传递不受信任的参数是一种失败的方法。确保参数(或其一部分)得到完全信任通常不是一件容易的事。
  • 整。使用eval()使代码更聪明,因此更难遵循。用布莱恩·科尼根(Brian Kernighan)的话说:“调试是一开始编写代码的两倍。因此,如果您尽可能聪明地编写代码,就定义而言,您不够聪明,无法对其进行调试

实际使用eval()的主要问题只有一个:

  • 没有经验的开发人员在没有充分考虑的情况下使用它。

根据经验,我倾向于遵循以下规则:

  1. 有时eval()是唯一/正确的解决方案。
  2. 在大多数情况下,应该尝试其他方法。
  3. 如果不确定,请转到2。
  4. 否则,请非常非常小心。

4
可以重构它以避免eval(),尤其是在$ className被列入白名单的情况下,为了娱乐eval()的使用,必须将其列入白名单。好消息是,从5.3开始,$ foo :: bar()有效。
rojoca 2009年

@rojoca:请给我们示例如何在PHP 5.2中没有eval()的情况下进行操作吗?
米哈尔鲁德尼茨基

30
我不确定是否算作eval,但是$ result = call_user_func(array('Foo','bar')); 奇迹般有效。
Ionuț G. Stan,2009年

3
关于棘手的好地方-在您的(简单)示例中,变量“从无处弹出”。如果代码变得稍微复杂一点,那么请下一个查看代码并尝试追逐该变量的人(在那儿,做了那件事,我所感到的只是头疼和一件糟糕的T恤),祝您好运。
Piskvor

不安全的输入是可以避免的
强大的功能

40

当用户输入包含在评估字符串中的可能性很小时,eval是邪恶的。当您进行评估而没有来自用户的内容时,您应该是安全的。

不过,在使用eval之前,您至少应该三思而后行,它看起来看似简单,但是考虑到错误处理(请参见VBAssassins注释),可调试性等方面,它不再那么简单了。

因此,根据经验:算了吧。当eval是答案时,您很可能会问错问题!;-)


6
有时评估是答案。我们致力于网络游戏的应用,由于实体之间的关系非常复杂,很难避免在那里发生评估……但是,恕我直言,在90%的情况下评估并不能解决问题。
喷气机

19
我真的很好奇看到只有评估才是答案的情况。
Ionuț G. Stan

2
@Ionut G. Stan数据库存储的对象/实体自定义触发器?
黑木风风

8
我真的不认为这些都是eval()的合理用法。在每种情况下,至少仍可以进行不使用eval()的相同操作。好像没有eval()会使PHP瘫痪。诚然,在这种情况下,eval()是一种快捷方式,但是它仍然使您的代码路径更难遵循,而问题也更难调试。那是我的意见。
thomasrutter

4
@Christian:您应该首先编写一个示例(使用pastebin.com,并在此处粘贴链接),在该示例中,避免使用eval()不可能,这是一种更好的方法。许多人说eval()在某些情况下是不可避免的,但是他们没有提到我们可以争论的任何具体例子,这样的辩论就没有意义了。因此,请说eval()不可避免的人首先证明这一点!
Sk8erPeter

18

eval()始终都是邪恶的。

“什么时候eval()不邪恶?” 我认为这是一个错误的问题,因为这似乎暗示在某些情况下使用eval()的弊端会神奇地消失。

使用eval()通常是个坏主意,因为它会降低代码的可读性,使您在运行时之前预测代码路径的能力(以及可能的安全隐患),从而降低调试代码的能力。使用eval()还可以防止操作码缓存(例如集成到PHP 5.5及更高版本中的Zend Opcache)或JIT编译器(例如HHVM中的编译器)对评估的代码及其周围的代码进行优化。

而且,没有绝对必要使用eval()的情况-PHP是没有它的全功能编程语言。

是否实际将其视为邪恶,还是在某些情况下可以使用eval()亲自证明情况由您决定。对某些人来说,弊端太大,无法证明其合理性;对另一些人来说,eval()是方便的快捷方式。

但是,如果您将eval()视为邪恶,那么它始终都是邪恶的。它不会根据上下文神奇地失去其邪恶性。


1
你让程序员很讨厌。
克里斯蒂安

1
“此外,没有绝对必要使用eval()的情况”-按照PHP文档中给出的示例,如果我要执行存储在数据库中的代码该怎么办?
Yarin

4
我要说的是,将PHP代码存储在数据库中同样是邪恶的,并且同样是不必要的。无论您要实现什么目标,除了将PHP存储在数据库中或使用eval()之外,还有其他方法可以实现。如果愿意,可以这样做,如果它对您有帮助,但是如果将eval()视为邪恶,那么您可能还必须将在数据库中存储PHP视为邪恶。
thomasrutter

12
它增加了另一个攻击媒介-SQL注入然后还可以在Web服务器上运行任意PHP代码。它需要使用eval()。无法通过字节码缓存对其进行优化。它违反了分离代码和数据的原则。它会使您的应用程序不那么容易移植-要更新/修复PHP,还必须更新数据库。
thomasrutter

3
我要说的是,如果只能通过使用eval才能实现某些目标,那么重新考虑这样做是一件好事。在运行时动态创建类似乎是个坏主意。为什么不使用一些代码生成并提前生成定义它们的代码?
thomasrutter

15

在这种情况下,eval可能足够安全,只要用户永远不可能在表中创建任意列。

不过,它实际上并没有更优雅。这基本上是一个文本解析问题,并且滥用PHP的解析器来处理似乎有点棘手。如果要滥用语言功能,为什么不滥用JSON解析器?至少对于JSON解析器,根本没有代码注入的可能性。

$json = str_replace(array(
    'enum', '(', ')', "'"), array)
    '',     '[', ']', "'"), $type);
$result = json_decode($json);

正则表达式可能是最明显的方法。您可以使用单个正则表达式从此字符串中提取所有值:

$extract_regex = '/
    (?<=,|enum\()   # Match strings that follow either a comma, or the string "enum("...
    \'      # ...then the opening quote mark...
    (.*?)       # ...and capture anything...
    \'      # ...up to the closing quote mark...
    /x';
preg_match_all($extract_regex, $type, $matches);
$result = $matches[1];

1
即使这本身并不能回答问题,但这还是一个很好的答案:解析enum()的最佳方法是什么?…谢谢;)
Pierre Spring

13

eval() 速度很慢,但我不会称之为邪恶。

我们对它的不好使用会导致代码注入并变得邪恶。

一个简单的例子:

$_GET = 'echo 5 + 5 * 2;';
eval($_GET); // 15

一个有害的例子:

$_GET = 'system("reboot");';
eval($_GET); // oops

我建议您不要使用,eval()但如果您使用,请确保将所有输入验证/列入白名单。


12

在eval中使用外部数据(例如用户输入)时。

在上面的示例中,这不是问题。


7

我会在这里公然窃取内容:

  1. 评估本质上永远都是安全问题。

  2. 除了安全方面的问题,eval还存在难以置信的缓慢问题。在我对PHP 4.3.10的测试中,它的速度比普通代码慢10倍,而对PHP 5.1 beta1则慢28倍。

blog.joshuaeichorn.com:using-eval-in-php


5

eval()永远是邪恶的。

  • 出于安全原因
  • 出于性能原因
  • 出于可读性/可重用性的原因
  • 由于IDE /工具的原因
  • 出于调试原因
  • 总有更好的方法

@bracketworks:但是您错了-所有这些固有的问题在某些情况下都不会神奇消失。为什么要这样?以性能为例:您是否真的认为解释器只会因为以某种神秘的“非邪恶”方式使用了极慢的函数eval()而提高了速度?还是在某个幸运的日子里,逐步调试将在您的评估条款中起作用?
弗朗索瓦·布尔乔亚

3
坦白说,我认为@MichałRudnicki的回答说得最好。每当我问自己一个问题时,请不要误解我的意思,答案是eval(),我就以为我问错了这个问题。很少有“正确”的答案,但总括来说,永远都是邪恶是根本不正确的。
Dan Lugg 2013年

如果我想将代码存储在数据库中?我如何执行它?
Konstantin XFlash Stratigenas

@KonstantinXFlashStratigenas您应该问自己为什么要在数据库中存储可执行代码。数据库用于存储代码(而非代码)产生的数据。至少,您正在增加攻击向量。
杰克B,

4

我还要考虑一些维护您代码的人。

eval()并非只是查看并知道应该发生什么的简单方法,您的示例并没有那么糟糕,但是在其他地方,这可能是一场噩梦。


4

就我个人而言,我认为代码仍然很邪恶,因为您没有评论它在做什么。它还没有测试其输入的有效性,因此非常脆弱。

我还感到,由于95%(或更多)的eval使用具有积极危险性,因此在其他情况下可能节省的少量潜在时间不值得沉迷于使用eval的不良做法。另外,您稍后将不得不向您的奴才解释为什么使用eval是好事,而使用eval是坏事。

而且,当然,您的PHP最终看起来像Perl;)

eval()有两个关键问题(作为“注入攻击”场景):

1)可能会造成伤害2)可能会导致崩溃

还有一个比技术更社会化的:

3)会诱使人们不当使用它作为其他地方的捷径

在第一种情况下,您冒着任意代码执行的风险(显然,不是在评估已知字符串时)。但是,您的输入可能不如您想像的那样知名或固定。

在这种情况下,您更有可能崩溃,而您的字符串将以无用的模糊错误消息终止。恕我直言,所有代码都应尽可能整齐地失败,否则将引发异常(这是最易于处理的错误形式)。

我建议在此示例中,您是通过巧合进行编码,而不是根据行为进行编码。是的,SQL枚举语句(您确定该字段的枚举?-您是否调用了正确版本的数据库的正确表的正确字段?实际回答了吗?)碰巧像PHP中的数组声明语法,但我建议您真正要做的不是找到输入到输出的最短路径,而是解决指定的任务:

  • 确定您有一个枚举
  • 提取内部列表
  • 解压列表值

这大致就是您的选择所要做的,但是为了清楚和安全起见,我会在其中包裹一些if和注释(例如,如果第一个匹配项不匹配,则抛出异常或将结果设置为null)。

转义的逗号或引号仍然可能存在一些问题,您可能应该解压缩数据然后再将其取消引用,但是它至少确实将数据视为数据而不是代码。

使用preg_version时,最糟糕的结果可能是$ result = null,而使用eval版本,最糟糕的结果是未知的,但至少会导致崩溃。


3

eval将字符串评估为代码,这样做的问题是,如果字符串以任何方式“受污染”,则可能暴露出巨大的安全威胁。通常问题是在字符串中评估用户输入的情况下,在许多情况下,用户可以输入代码(例如php或ssi),然后在eval中运行该代码,它将以与您的php脚本相同的权限运行,并且可以用于获取信息/访问您的服务器。在将用户输入传递给eval之前,确保正确清理了用户输入可能非常棘手。还有其他问题...其中一些值得商bat


3

PHP建议您以这样的方式编写代码,使其可以通过call_user_func执行,而不用执行显式评估。


2

另一个eval邪恶的原因是,它无法被eAccelertor或ACP之类的PHP字节码缓存所缓存。


2

糟糕的编程会使eval()变成邪恶,而不是函数。我有时会用它,因为我无法在多个站点上进行动态编程。我无法在一个站点上解析PHP,因为我不会收到想要的东西。我只会收到结果!我很高兴eval()函数存在,因为它使我的生活更加轻松。用户输入?只有糟糕的程序员才会被黑客吸引。我不用担心


2

糟糕的编程会使eval()变成邪恶,而不是函数。我有时会用它,因为我无法在多个站点上进行动态编程。我无法在一个站点上解析PHP,因为我不会收到想要的东西。我只会收到结果!我很高兴eval()函数存在,因为它使我的生活更加轻松。用户输入?只有糟糕的程序员才会被黑客吸引。我不用担心

我预计您很快就会遇到严重的问题...

老实说,在像PHP这样的解释语言中,过高的功能(例如eval)绝对没有什么用处。我从未见过eval执行无法使用其他更安全的方法执行的程序功能...

我衷心同意,对于所有认为测试用户输入会有所帮助的人们,评估是万恶之源。三思而后行,用户输入可能以多种不同形式出现,而正如我们所说,黑客正在利用您不太在意的功能。我认为,请完全避免评估。

我已经看到了精心制作的示例,这些示例滥用了超出我自己创造力的评估功能。从安全的角度出发,不惜一切代价避免,我甚至会要求它至少是PHP配置中的一个选项,而不是“给定的”。


“无论您多么仔细地尝试保护有关功能X的代码,聪明的黑客总是领先一步……”的推理可以应用于任何其他技术,而不仅仅是X == eval。因此,对于任何功能X:X是所有评估的根...呃...邪恶,所以最好完全放弃编程(这样就从那些愚蠢的黑客手中夺走了地毯,最终仍然使它们变得聪明)。
Sz。

“对于像eval这样的过高功能绝对没有任何用处”->任何可以使用“绝对”之类的词的人,无论是编程新手还是不好的程序员。
unity100 '02

2

这是在不使用eval的情况下运行从数据库提取的PHP代码的解决方案。允许所有范围内的函数和异常:

$rowId=1;  //database row id
$code="echo 'hello'; echo '\nThis is a test\n'; echo date(\"Y-m-d\");"; //php code pulled from database

$func="func{$rowId}";

file_put_contents('/tmp/tempFunction.php',"<?php\nfunction $func() {\n global \$rowId;\n$code\n}\n".chr(63).">");

include '/tmp/tempFunction.php';
call_user_func($func);
unlink ('/tmp/tempFunction.php');

基本上,它使用文本文件中包含的代码创建一个唯一的函数,包括该文件,调用该函数,然后在完成处理后将其删除。我正在使用它来执行每日数据库提取/同步,其中每个步骤都需要唯一的代码来处理。这解决了我所面临的所有问题。


这看起来像是对答案的回应;请尽可能张贴。
rfornal 2015年

1

我过去经常使用eval(),但是我发现大多数情况下您不必使用eval来完成技巧。好吧,您在PHP中拥有call_user_func()和call_user_func_array()。静态地和动态地调用任何方法就足够了。

要执行静态调用,请将回调构造为array('class_name','method_name'),甚至构造为简单的字符串,例如'class_name :: method_name'。要执行动态调用,请使用array($ object,'method')样式回调。

eval()的唯一明智用途是编写自定义编译器。我做了一个,但是eval仍然是邪恶的,因为它是如此难以调试。最糟糕的是,无效代码中的致命错误使调用它的代码崩溃。我至少使用Parsekit PECL扩展来检查语法,但仍然不满意-尝试引用未知类和整个应用程序崩溃。


0

除了安全方面的考虑,eval()不能被编译,优化或缓存操作码,因此,它总是比普通的php代码慢(得多)。因此,使用eval是无效的,尽管这不会使它变得邪恶。(goto是邪恶的,eval只是不好的作法/臭味的代码/丑陋的)


eval得到的邪恶等级低于goto?是对面的日子吗?
JLRishe 2014年

只是我对实际goto在5.3中实现的PHP开发人员怀恨在心。
Aron Cederholm 2014年

1
不,对goto-fobians:有(有或没有)使用工具的工匠。这是永远不会犯错误的时候,让一个专业的外观像一个白痴,工具,而是无法使用合适的工具(正常)。但这始终是指责的工具……
Sz。

0

大多数人会指出这样的事实,当您处理用户输入(可能会处理)时,这很危险。

对我来说,最糟糕的部分是它降低了代码的可维护性

  • 难以调试
  • 难以更新
  • 限制工具和助手(如IDE)的使用
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.