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

Posted

技术标签:

【中文标题】什么时候 eval 在 php 中是邪恶的?【英文标题】:When is eval evil in php? 【发布时间】:2010-10-31 09:13:30 【问题描述】:

在我从事 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')";

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

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

【问题讨论】:

eval 总是邪恶的,总有更好的方法来编写代码,尤其是自从 PHP 引入了匿名函数以来。在这种情况下,我会使用$result = array(); preg_replace_callback('#^enum\s*\(\s*\'|\'\s*\)\s*$#', function($m) use($result) $result[] = $m[1]; , $type); 好吧,老实说,我认为 php 的主要问题不是语言本身,而是使用它的人。这个问题的三个正确答案(thomasrutter 的、braincracking 的和我的)都被否决了,没有人反对他们。另一方面,一个答案声称“有时 eval() 是唯一/正确的解决方案”,没有示例或解释,并获得最高票数...... 【参考方案1】:

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

eval() 的主要问题是:

潜在的不安全输入。传递不受信任的参数是一种失败的方式。确保参数(或其中的一部分)完全受信任通常并非易事。 技巧。 使用 eval() 使代码更聪明,因此更难遵循。引用 Brian Kernighan 的话:“调试的难度是一开始编写代码的两倍。因此,如果你尽可能巧妙地编写代码,那么根据定义,你就不够聪明,无法调试它 "

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

没有经验的开发人员在使用它时没有给予足够的考虑。

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

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

【讨论】:

这可以被重构以避免 eval(),特别是如果 $className 被列入白名单,它必须是为了招待 eval() 的使用。不过好消息是,从 5.3 开始,$foo::bar() 是有效的。 @rojoca: 请给我们举例说明如何在 PHP 5.2 中不使用 eval() 来实现? 我不确定它是否算作 eval,但 $result = call_user_func(array('Foo', 'bar'));像魅力一样工作。 关于技巧的好点 - 在您的(简单)示例中,一个变量“从无处”弹出。如果代码变得稍微复杂一点,那么祝下一个查看代码并试图追查该变量的人好运(去过那里,做到了,我得到的只是头疼和这件糟糕的 T 恤)。 不安全的输入是可以避免的【参考方案2】:

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

尽管如此,在使用 eval 之前您应该至少三思,它看起来非常简单,但考虑到错误处理(参见 VBAssassins 评论)、可调试性等,它不再那么简单了。

所以根据经验: 忘掉它。当 eval 是答案时,您可能是在问错误的问题! ;-)

【讨论】:

有时 eval 是答案。我们致力于在线游戏应用程序,由于实体之间的关系非常复杂,因此很难避免 evals……但恕我直言,在 90% 的情况下,eval 不是答案。 我真的很想看到只有 eval 是答案的情况。 @Ionut G. Stan 数据库存储的对象/实体自定义触发器? 我并不认为这些都是 eval() 的正当用途。在每种情况下,至少可以在不使用 eval() 的情况下做同样的事情。这不像没有 eval() 的 PHP 就瘫痪了。诚然,eval() 在这种情况下是一种捷径,但它仍然使您的代码路径更难遵循,问题也更难调试。这是我的看法。 @Christian:你应该先写一个例子(使用pastebin.com,并在此处粘贴链接),你认为避免使用eval() 是不可能的,这是一个更好的方法。许多人说eval() 在某些情况下是不可避免的,但他们没有提到任何我们可以争论的具体例子——这样这场争论就没有意义了。 所以请说eval()的人是不可避免的,先证明一下!【参考方案3】:

eval 在任何时候都同样“邪恶”。

如果你认为它是邪恶的,那么它在任何时候都是同样邪恶的。许多人将其描述为邪恶的原因并没有随着上下文而消失。

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

此外,没有绝对必要使用 eval() 的情况——没有它,PHP 是一种功能齐全的编程语言。不管你想用 eval() 做什么,在 PHP 中都会有另一种方法。

您是否真的认为这些是邪恶的,或者您是否可以个人证明使用 eval() 是由您决定的。对某些人来说,陷阱太大而无法证明它的合理性,而对另一些人来说,eval() 是一个方便的捷径。

【讨论】:

你真是个程序员 “此外,没有绝对必要使用 eval() 的情况” - 如果我想执行我存储在数据库中的代码,根据给出的示例PHP 文档? 对此我想说,在数据库中存储 PHP 代码同样邪恶,同样没有必要。无论您想要实现什么,除了将 PHP 存储在数据库中或使用 eval() 之外,还有其他方法可以实现。如果你喜欢就去做,如果它对你有用,但如果你把 eval() 当作邪恶,那么你可能不得不把在数据库中存储 PHP 也当作邪恶。 它增加了另一个攻击向量 - SQL 注入也可以在 Web 服务器上运行任意 PHP 代码。它需要使用 eval()。它无法通过字节码缓存进行优化。它违反了代码和数据分离的原则。它会使您的应用程序的可移植性降低 - 要更新/修复 PHP,您还必须更新数据库。 我想说,如果某些事情只能通过使用 eval 来实现,那么重新考虑这样做可能会很好。在运行时动态创建类似乎是个坏主意。为什么不使用一些代码生成并提前生成定义它们的代码?【参考方案4】:

在这种情况下,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];

【讨论】:

尽管这本身并没有回答问题,但这是对这个问题的一个很好的回答:解析 enum() 的最佳方法是什么……谢谢 ;)【参考方案5】:

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

这是我们对它的不当使用,可能导致代码注入和邪恶。

一个简单的例子:

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

一个有害的例子:

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

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

【讨论】:

【参考方案6】:

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

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

【讨论】:

【参考方案7】:

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

    从本质上讲,评估始终是一个安全问题。

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

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

【讨论】:

【参考方案8】:

eval()总是邪恶。

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

【讨论】:

@bracketworks:但你错了——所有这些固有的问题在某些情况下不会神奇地消失。他们为什么要这样做?以性能为例:你真的认为解释器会因为你以某种神秘的“非邪恶”方式使用它而以更快的速度执行极其缓慢的函数 eval() 吗?或者在某个幸运的日子里,逐步调试将在您的 eval 子句中起作用? 坦率地说,我认为@MichałRudnicki 的回答说得最好。不要误会我的意思,每当我问(我自己)一个问题时,答案是eval(),我会马上假设我问错了问题;它很少是“正确”的答案,但笼统地说它总是邪恶是完全不正确的。 如果我想将代码存储在数据库中?我怎样才能执行它? @KonstantinXFlashStratigenas 您应该问问自己为什么要将可执行代码存储在数据库中。数据库用于存储由您的代码而不是您的代码产生的数据。至少,您正在增加攻击向量。【参考方案9】:

我也会考虑维护您的代码的人。

eval() 并不容易仅查看并知道应该发生什么,您的示例还不错,但在其他地方它可能是一场正确的噩梦。

【讨论】:

【参考方案10】:

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

我还认为,由于 eval 的 95%(或更多)使用非常危险,因此它在其他情况下可能提供的少量潜在时间节省不值得沉迷于使用它的不良做法。另外,您稍后必须向您的爪牙解释为什么您使用 eval 是好的,而他们的使用是坏的。

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

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

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

还有一个比技术更社交的:

3) 它会诱使人们在别处不恰当地使用它作为快捷方式

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

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

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

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

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

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

对于 preg_version,您最坏的结果可能是 $result=null,对于 eval 版本,最坏的结果是未知的,但至少是崩溃。

【讨论】:

【参考方案11】:

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

【讨论】:

【参考方案12】:

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

【讨论】:

【参考方案13】:

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

【讨论】:

【参考方案14】:

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

【讨论】:

【参考方案15】:

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

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

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

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

我见过一些精心设计的例子来滥用超出我自己创造力的 eval 函数。从安全的角度来看,不惜一切代价避免,我什至会要求它至少是 PHP 配置中的一个选项,而不是“给定的”。

【讨论】:

的推理“无论您如何仔细尝试保护有关功能 X 的代码,聪明的黑客总是领先一步......” 可以应用于任何其他技术,而不仅仅是 X == eval。因此,对于任何功能 X:X 是所有 eval 的根源......呃......邪恶,所以最好完全放弃编程(从而从那些愚蠢的黑客下面拉开地毯,最终仍然胜过他们)。 " 对于像 eval 这样的过高函数绝对没有用处" -> 任何能使用像 'absolutely' 这样的词的人,要么是编程新手,要么是一个糟糕的程序员。【参考方案16】:

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

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

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

【讨论】:

【参考方案17】:

以下是运行从数据库中拉出的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');

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

【讨论】:

这看起来像是对答案的回应;请尽可能张贴。【参考方案18】:

除了安全问题之外,eval() 不能被编译、优化或操作码缓存,因此它总是比普通的 php 代码慢 -- --。因此,使用 eval 是没有性能的,尽管这并不会使它变得邪恶。 (goto 是邪恶的,eval 只是不好的做法/臭代码/丑陋)

【讨论】:

eval 的邪恶等级低于goto?是相反的一天吗? 只是我对 php 开发者在 5.3 中实际实现 goto 怀恨在心。 不,致goto-fobians:有工具,也有使用(或不使用)工具的工匠。 从不使专业人士在犯错时看起来像白痴的工具,而是无法(正确地)使用正确的工具。但它总是应该归咎于工具......【参考方案19】:

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

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

难以调试 很难更新 限制工具和帮助程序(如 IDE)的使用

【讨论】:

【参考方案20】:

另一种选择

参考:ClosureStream

class ClosureStream

    const STREAM_PROTO = 'closure';

    protected static $isRegistered = false;

    protected $content;

    protected $length;

    protected $pointer = 0;

    function stream_open($path, $mode, $options, &$opened_path)
    
        $this->content = "<?php\nreturn " . substr($path, strlen(static::STREAM_PROTO . '://')) . ";";
        $this->length = strlen($this->content);
        return true;
    

    public function stream_read($count)
    
        $value = substr($this->content, $this->pointer, $count);
        $this->pointer += $count;
        return $value;
    

    public function stream_eof()
    
        return $this->pointer >= $this->length;
    

    public function stream_set_option($option, $arg1, $arg2)
    
        return false;
    

    public function stream_stat()
    
        $stat = stat(__FILE__);
        $stat[7] = $stat['size'] = $this->length;
        return $stat;
    

    public function url_stat($path, $flags)
    
        $stat = stat(__FILE__);
        $stat[7] = $stat['size'] = $this->length;
        return $stat;
    

    public function stream_seek($offset, $whence = SEEK_SET)
    
        $crt = $this->pointer;

        switch ($whence) 
            case SEEK_SET:
                $this->pointer = $offset;
                break;
            case SEEK_CUR:
                $this->pointer += $offset;
                break;
            case SEEK_END:
                $this->pointer = $this->length + $offset;
                break;
        

        if ($this->pointer < 0 || $this->pointer >= $this->length) 
            $this->pointer = $crt;
            return false;
        

        return true;
    

    public function stream_tell()
    
        return $this->pointer;
    

    public static function register()
    
        if (!static::$isRegistered) 
            static::$isRegistered = stream_wrapper_register(static::STREAM_PROTO, __CLASS__);
        
    


ClosureStream::register();
// Your code here!
$closure=include('closure://function()echo "hola mundo";');
$closure();

【讨论】:

以上是关于什么时候 eval 在 php 中是邪恶的?的主要内容,如果未能解决你的问题,请参考以下文章

eval 是邪恶的问题

eval???php??????????????????

为啥要在 Bash 中避免使用 eval,而应该使用啥?

eval是啥品牌

为啥全局变量是邪恶的? [关闭]

serializeArray在传递给eval之前是否会对输入进行消毒?