从字符串运行脚本的Eval备选方案


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

我需要运行包含代码的$variable,而不使用eval函数,我使用的是php7.4。我该怎么做?

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

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

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

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

我在这里使用printf,因为echo是一个语言语句而不是函数,因此不会像这样工作。如果必须在这里进行echo,则必须创建自己的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运算符解压缩的arguments数组中列出的自变量与目标函数的自变量的顺序相同。

当然,只有有限数量的有用函数可以直接输出任何内容。因此,在上面的例子中,我们从$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使用公共路由器方法来处理不匹配的情况;调用例如Func::code($cmd),其中code()返回do_*方法。

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

如果您一直在接收字符串命令(为什么?(,您可以使用regex将其解析为可以传递给函数的内容。或者,如果你绝对信任这些数据,eval并不是天生的、绝对的邪恶,尤其是在变通方案会给你的系统带来更复杂但同样广阔的后门的情况下。它只是不太优雅,而且散发着邋遢的设计气息。

最新更新