【问题标题】:How to evaluate the code provided by user?如何评估用户提供的代码?
【发布时间】:2020-09-06 01:23:02
【问题描述】:

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

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

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

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

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

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


根据下面的 cmets,这里列出了代码语法需要支持的“编程”功能。除了ExpressionLanguage systax 提供的内容之外,如果还支持以下内容就足够了:
  • 顺序流:一个接一个地执行指令(与 ExpressionLanguage 的“单线”性质相反)
  • 局部变量声明(当然还有之后的检测)
  • 来自表达式的变量赋值(文字、函数调用、运算符的任何组合)
  • 将变量传递给函数
  • 流控制构造:至少一个条件构造(例如:if)和一个重复构造(例如:for 循环)

【问题讨论】:

  • 在沙箱中运行程序,使其无法访问任何内容并造成损坏。
  • 虚拟机也是一种解决方案。
  • Docker 容器,类似的解决方案。
  • @Barmar 请详细说明一下。
  • @Progman 假设最终用户总是足够聪明地发现任何现有的漏洞。要么编写自己的解析器和解释器(不建议),要么走简单的路线,让用户编写在浏览器上执行的自己的 Javascript 脚本(无服务器安全问题)。

标签: php eval evaluation expression-evaluation


【解决方案1】:

也许你可以看看 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 查询来在服务器上 POST 数据,等待结果并显示它。

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

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

【讨论】:

  • 能否请您详细说明以这种方式考虑的性能:使用 docker 容器运行代码,然后将数据检索回原始脚本,与在流上执行代码相比(不考虑安全性)问题),甚至使用 JavaScript 代码而不是 PHP(或任何其他“轻量级”脚本语言)
【解决方案2】:

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

看看这些资源:

一个可能的例子:

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

祝你好运!

【讨论】:

    【解决方案3】:

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

    dec <variable> [initialValue]
    

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

    $myVar = 4;
    

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

    add <variable> <amount>
    

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

    $myVar += 2;
    

    您甚至可以关联高级控制结构(例如conditionalsiterations

    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;
    }
    

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

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

    【讨论】:

      猜你喜欢
      • 2011-01-25
      • 2015-03-11
      • 2015-02-18
      • 2011-10-06
      • 2018-11-05
      • 2010-09-05
      • 2023-04-02
      • 1970-01-01
      相关资源
      最近更新 更多