3

我需要使用 PHP 在服务器上处理一些用户提供的代码。该代码即将涵盖一些非常基本的编程功能,例如:变量、文字、(最好)函数和一些相关操作。

一种选择是使用 的危险功能eval()。对于我的具体情况,除了安全问题和性能瓶颈之外,它的功能非常全面且冗余。token_get_all()使用保护墨菲而不是马基雅维利对令牌进行消毒!不管它有什么缺点,它确实有能力实现我想要实现的目标。

我还检查了 Symphony 的ExpressionLanguage;它有一些缺点:

  • 它无法自行检测“变量”(应事先引入并了解它们)
  • 它缺乏基本的变量功能(仅初始化它们:没有赋值功能)
  • 它仅适用于“单线”表达式

唉! 一个更复杂的ExpressionLanguage就足够了。

我正在寻找能够为用户提供一些非常基本的“编程”功能的东西。有没有这样的东西,如果有,是什么?(即使它是用另一种语言编写的,但可以在服务器上以某种方式使用。)如果没有这样的事情,那么我应该如何对待eval()不画我?!或者,作为最后的手段,我该如何设计一个如此简单的编程能力?(请详细说明问题:)


根据下面的评论,这里是代码语法需要支持的“编程”功能列表。除了ExpressionLanguage 系统税提供的内容之外,如果还支持以下内容就足够了:

  • 顺序流:一个接一个地执行指令(与ExpressionLanguage的“单线”性质相反)
  • 局部变量声明(当然还有他们之后的检测)
  • 来自表达式的变量赋值(文字、函数调用、运算符的任何组合)
  • 将变量传递给函数
  • 流控制结构:至少一个条件结构(例如:)if和一个重复结构(例如:for循环)
4

3 回答 3

3

也许你可以看看Docker。例如,您可以将用户代码复制到服务器上的文件中(不执行它),然后容器中运行它。这将允许您:

  • 将代码运行到特定的专用容器中,该容器可以在脚本执行后销毁
  • 使用不同版本的 PHP 运行代码

一些例子:

docker run -v "$PWD":/usr/src -w /usr/src --rm php:7.4.5 php ./myScript.php

这会将文件 myScript.php 执行到基于 php 7.4.5 的新容器中,一旦完成,容器将被删除。

使用另一个 PHP 版本也是如此:

docker run -v "$PWD":/usr/src -w /usr/src --rm php:5.6 php ./myScript.php

有用的链接:

关于表演的编辑:

显然,使用容器运行代码比直接从 PHP 运行代码要长。

例如,我们可以运行以下代码对其进行测试:

<?php

function reverseArray(array $array): array {
    for ($i = 0; $i < count($array) / 2; $i++) {
        $tmp = $array[$i];
        $array[$i] = $array[count($array) -1 - $i]; 
        $array[count($array) - 1 - $i] = $tmp;
    }
    return $array;
}   


$tabToReverse = [5, 8, 95, 10, 6, 17, 42, 20];
echo 'Reversed array : '."\n";
echo  implode(' ', reverseArray($tabToReverse));

具有语法错误的相同代码:

<?php

// syntax error
function reverseArray($array: array): array {
    for ($i = 0; $i < count($array) / 2; $i++) {
        $tmp = $array[$i];
        $array[$i] = $array[count($array) -1 - $i]; 
        $array[count($array) - 1 - $i] = $tmp;
    }
    return $array;
}   


$tabToReverse = [5, 8, 95, 10, 6, 17, 42, 20];
echo 'Reversed array : '."\n";
echo  implode(' ', reverseArray($tabToReverse));

下面的 PHP 代码将比较两种执行(加上一些语法错误):

<?php

/**
 * Run code using eval
 */
$start = microtime(true);
$code = str_replace('<?php', '', file_get_contents('./reverseArray.php'));
echo eval($code)."\n";
$end = microtime(true);
$duration = $end - $start;
echo "Duration using eval $duration\n"; 


/**
 * Run code using container
 */
$start = microtime(true);
$cmd = 'docker run -v "$PWD":/usr/src -w /usr/src --rm php:7.4.5 php ./reverseArray.php';
exec($cmd, $result);
echo implode("\n", $result)."\n";
$end = microtime(true);
$duration = $end - $start;
echo "Duration using container $duration\n";

/**
 * Run code using container with an error
 */
$start = microtime(true);
$cmd = 'docker run -v "$PWD":/usr/src -w /usr/src --rm php:7.4.5 php ./reverseArrayWithError.php';
exec($cmd, $resultWithError);
echo implode("\n", $resultWithError)."\n";
$end = microtime(true);
$duration = $end - $start;
echo "Duration using container $duration\n";

我的笔记本电脑上的结果是:

php ./runCode.php 
Reversed array : 
20 42 17 6 10 95 8 5
Duration using eval 0.00031089782714844
Reversed array :
20 42 17 6 10 95 8 5
Duration using container 0.79519391059875

Parse error: syntax error, unexpected ':', expecting ')' in /usr/src/reverseArrayWithError.php on line 3
Duration using container 0.81346988677979

如您所见,启动容器需要时间。但代码已在特定区域执行。

在所有情况下,前端部分都是一样的,它将使用一些 Ajax 查询来在服务器上发布数据,等待结果并显示它。

注意 1:即使代码在特定容器中执行,也必须在之前对其进行清理,因为永远不应信任用户输入。

注意 2:使用此架构需要管理正在运行的容器以防止过载。如果 10 000 个用户同时提交代码会发生什么?但我认为这是另一个话题。

于 2020-06-02T11:44:14.733 回答
2

根据您对“代码”的有限和“基本”期望,除了表示的其他方法之外,您可以“创建”某种“程序集”以预定义的单指令(可能有参数)转换为可执行代码. “汇编”只是在指令和等效动作之间建立强对应关系的一种手段。例如,您可以定义一条dec指令来声明具有可选初始值的变量:

dec <variable> [initialValue]

dec myVar 4, 转换为以下可执行代码(以 PHP 表示法表示):

$myVar = 4;

或者,add带有两个操作数的指令将指定的数量添加到提供的变量中:

add <variable> <amount>

上述指令可以用作:add myVar 2,并且应该翻译成以下内容:

$myVar += 2;

您甚至可以关联高级控制结构(例如条件迭代

if <criteria>
   (consequent)
fi

这是导致各种编程语言的基本思想,它会遇到一些非常高级的主题!然而,您的特定需求使您能够施加一些暴露的限制以避免繁琐!为了简单起见,“参数”应该只支持单个实体;“文字”或“变量”。这确实会导致冗长和缺乏代数表达式,但它使“你”的一切变得非常简单,因为它让你远离表达式评估中涉及的高级主题!

dec needsMore false
gte needsMore myVar 5
if needsMore
   add myVar 3
fi

这应该转化为:

$needsMore = false;
$needsMore = $myVar >= 5;
if ($needsMore) {
   $myVar += 3;
}

作为另一个简化,禁止在重复结构中重新分配“保护变量”。此限制还使您可以事先禁止“无限循环”或昂贵的循环!您可以施加最适合您需求的任何规则,这些规则也将以您喜欢的方式以安全的方式清理用户代码!

为了帮助您的非编码用户,您还应该创建一个视觉辅助来封装组件以进行缓动,并帮助他们使用“指令”和相应的参数(插入、修改或删除它们);特别是关于“选择”和“重复”结构。

于 2020-06-06T04:52:22.847 回答
1

想到的一件事是Twig,它是 Symfony 使用的模板引擎。它具有您可以启用的沙盒模式,从而可以安全地评估不受信任的代码。我之前使用它来允许用户编写自己的模板。

看看这些资源:

一个可能的例子:

因此,尽管它是用于模板的,但我认为您可以对其进行调整以实现您想要的。

祝你好运!

于 2020-06-02T21:02:02.440 回答