什么时候是PHP的eval邪恶?

时间:2009-06-04 15:43:27

标签: php eval

在我使用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).';');

19 个答案:

答案 0 :(得分:126)

我会谨慎地调用eval()纯粹的邪恶。动态评估是一个强大的工具,有时可以成为一个救生员。使用eval()可以解决PHP的缺点(见下文)。

eval()的主要问题是:

  • 潜在的不安全输入。传递不受信任的参数是一种失败的方法。确保参数(或其一部分)完全受信任通常不是一项微不足道的任务。
  • 棘手。使用eval()会使代码变得更聪明,因此更难以遵循。引用Brian Kernighan“调试是第一次编写代码的两倍。因此,如果您尽可能巧妙地编写代码,那么根据定义,您不够聪明,无法调试它

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

  • 没有经过充分考虑使用它的缺乏经验的开发人员。

根据经验,我倾向于遵循这个:

  1. 有时eval()是唯一/正确的解决方案。
  2. 对于大多数情况,人们应该尝试别的东西。
  3. 如果不确定,请转到2。
  4. 否则,要非常非常小心。

答案 1 :(得分:38)

当评估字符串中包含userinput的最轻微可能性时,eval是邪恶的。 当你没有来自用户的内容时,你应该是安全的。

然而,在使用eval之前你应该至少考虑两次,它看起来很简单,但是在处理错误时(参见VBAssassins注释),可调试性等等,它不再那么简单了。

所以作为经验法则: 忘掉它。当eval是答案时,你可以提出错误的问题! ; - )

答案 2 :(得分:17)

eval()在任何时候都同样邪恶。

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

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

此外,没有必要使用eval()的情况 - 没有它,PHP是一种功能完备的编程语言。

你是否真的认为这些是邪恶的,或者在某些情况下你可以亲自证明使用eval()取决于你。对某些人来说,邪恶太大了,无法证明这一点,而对于其他人来说,eval()是一个方便的捷径。

然而,如果你看到eval()是邪恶的,那么它在任何时候都是邪恶的。根据具体情况,它并没有神奇地失去它的邪恶。

答案 3 :(得分:14)

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

虽然它并不是那么优雅。这基本上是一个文本解析问题,滥用PHP的解析器处理似乎有点hacky。如果您想滥用语言功能,为什么不滥用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];

答案 4 :(得分:11)

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

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

答案 5 :(得分:10)

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

我们对它的不良用处可能导致代码注入并且是邪恶的。

一个简单的例子:

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

一个有害的例子:

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

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

答案 6 :(得分:7)

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

  
      
  1. Eval的性质始终是一个安全问题。

  2.   
  3. 除了安全问题,eval还存在速度极慢的问题。在我对PHP 4.3.10的测试中,它比普通代码慢10倍,在PHP 5.1 beta1上慢28倍。

  4.   

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

答案 7 :(得分:5)

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

我也觉得,因为使用eval的95%(或更多)是积极危险的,所以在其他情况下可能提供的小的潜在时间节省并不值得沉迷于使用它的不良做法。另外,你以后必须向你的小伙子解释为什么你使用eval是好的,而且他们的不好。

当然,你的PHP最终看起来像Perl;)

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

1)可能会造成伤害 2)它可能只是崩溃

和一个更具社交性而非技术性的人:

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

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

更有可能(在这种情况下)你只会崩溃,你的字符串将以一个无端的模糊错误消息终止。恕我直言,所有代码都应该尽可能整齐地失败,否则应该抛出异常(作为最可处理的错误形式)。

我建议,在这个例子中,你是巧合编码而不是编码行为。是的,SQL enum语句(并且你确定该字段的枚举吗? - 你是否调用了正确版本数据库的正确表的正确字段?它实际上是否回答?)恰好看起来像PHP中的数组声明语法,但我建议你真正想做的是找不到从输入到输出的最短路径,而是解决指定的任务:

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

这大致是您的选项所做的,但为了清晰和安全,我会在其周围包含一些if和注释(例如,如果第一个匹配不匹配,则抛出异常或设置null结果)。

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

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

答案 8 :(得分:4)

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

eval()不仅仅是看待并知道应该发生什么的容易,你的例子并不是那么糟糕,但在其他地方它可能是一个正确的噩梦。

答案 9 :(得分:4)

eval() 总是邪恶。

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

答案 10 :(得分:3)

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

答案 11 :(得分:3)

PHP建议您编写代码,使其可以通过call_user_func执行,而不是执行显式的evals。

答案 12 :(得分:2)

另一个原因eval是邪恶的,它无法通过PHP字节码缓存(如eAccelertor或ACP)进行缓存。

答案 13 :(得分:2)

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

答案 14 :(得分:2)

  

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

我预测你很快就会遇到严重问题......

说实话,在像PHP这样的解释性语言中,使用诸如eval之类的过高功能绝对没有用处。我从未见过eval执行程序功能,这些功能无法使用其他更安全的方式执行...

Eval是所有邪恶的根源,我全心全意地同意所有认为测试用户输入有帮助的人。三思而后行,用户输入可以有多种不同形式,正如我们所说,黑客正在利用你不太关心的功能。在我看来,完全避免eval。

我看过精心设计的例子,滥用超出我自己创造力的评估功能。从安全立场出发,不惜一切代价避免,我甚至会要求它至少是PHP配置中的一个选项,而不是“给定”。

答案 15 :(得分:1)

我曾经经常使用eval(),但我发现大多数情况下你不必使用eval来做技巧。好吧,你在PHP中有call_user_func()和call_user_func_array()。静态和动态调用任何方法都足够了。

执行静态调用将回调构造为数组('class_name','method_name'),或者甚至是像'class_name :: method_name'这样的简单字符串。要执行动态调用,请使用数组($ object,'method')样式回调。

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

答案 16 :(得分:1)

这是一个运行从数据库中提取的PHP代码而不使用eval的解决方案。允许所有范围内的功能和例外:

$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');

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

答案 17 :(得分:0)

除了安全问题,eval()无法编译,优化或操作码缓存,因此它总是比正常的PHP代码更慢 - 慢一点。因此,使用eval是不符合要求的,尽管它并不会使它变得邪恶。 (goto是邪恶的,eval只是不好的做法/臭臭的代码/难看)

答案 18 :(得分:0)

大多数人会指出一个事实,那就是当您处理用户输入(可以处理)时,这很危险。

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

  • 难以调试
  • 难以更新
  • 限制工具和助手(例如IDE)的使用