首页 > 解决方案 > 从字符串运行脚本的 eval 替代方法

问题描述

$variable = "echo 'example'";
eval ($variable);

我需要运行包含代码的 $variable,而不使用 eval 函数,我使用的是 php 7.4。我怎样才能做到这一点?

提示:我也在使用 Laravel 6.x,是否也有 Laravel 函数呢?

标签: php

解决方案


你确定你必须这样做吗?如果您只有有限数量的可能函数,最安全的做法是简单地映射它们,然后使用数组将所需函数和变量中的参数分开。稍后再谈。

如果您想为调用函数提供通用方法,例如,您可以:

$cmd = ['printf', 'foo'];
// interpreted as: printf('foo')
$cmd[0]($cmd[1]); 

我在printf这里使用,因为echo是语言语句而不是函数,因此不会像这样工作。如果您必须在此处回显,则必须创建自己的回声包装函数,例如function do_echo($str) { echo $str; },然后调用它。

或者,对于更具可读性的代码,您可以将数组列为:

list($func, $arg) = $cmd;
$func($arg);

如果你需要多个参数,你可以使用这个结构:

$cmd = ['str_replace', ['sub', 'ob', 'subject']];
list($func, $args) = $cmd;

// Interpreted as: str_replace('sub', 'ob', 'subject'):
$result = $func(...$args);

echo $result; // 'object'

参数数组中列出的参数(使用 splat 运算符解包)与目标函数的参数顺序相同。

当然,只有少数有用的函数可以直接输出任何东西。因此,在上面的示例中,我们从$func相反捕获结果,然后回显;这只是为了说明变量函数和参数的基本用法。

但是,这种方法将提供对系统中任何功能的无限制访问,因此不应将其用于除来自受信任来源(您)的数据/调用之外的任何东西。允许用户提供任何命令将使您的系统进入一个充满恶作剧和痛苦的世界。

因此,我最初建议提供功能图。你可以:

  1. 有一个包含允许的核心功能列表的数组,
  2. 有一个包含自定义包装函数列表的数组,或者
  3. 检查以前缀命名的有效包装函数

例如,您可以按如下方式实现包装器:

function do_echo($str) {
    echo $str;
}
function do_replace($se, $re, $str) {
    // return or echo, up to you:
    echo str_replace($se, $re, $str);
}

这将使您可以控制执行的内容,以及是否返回或输出值等前/后处理。然后迭代你的变量命令如下:

$cmd = ['echo', 'off'];
list($func, $args) = $cmd;

$func = 'do_' . $func; // adding your prefix

if(function_exists($func)) {
    // call with array, unpack arguments:
    if(is_array($args) {
        $func(...$args);
    } 
    // or call with scalar argument:
    else {
        $func($args); // interpreted as `do_echo('off')
    }
}

对于额外的绝缘层,使用您的自定义调用作为其方法创建一个类,而不是使用更多功能污染全局空间。Ciykd 使用公共路由器方法来处理不匹配的情况;调用 eg Func::code($cmd), wherecode()返回do_*方法。

当我需要将用户请求映射到返回输出的类公共方法时,我经常会这样做;命名为 egview_homeview_search,并view_default作为不匹配请求的后备。方便快速原型设计,其中index.php?view=*=> $this->view_*

如果您无法接收字符串命令(为什么?),您可以使用正则表达式将其解析为可以传递给函数的内容。或者,如果您绝对信任这些数据,那么eval它在本质上和绝对意义上都不是邪恶的,尤其是在解决方法会为您的系统带来更复杂但同样广泛的后门的情况下。它只是不是很优雅,而且有一种草率的设计。


推荐阅读