尽量不要在php中使用eval()来计算带有条件语句的字符串


Trying not to use eval() in php to evaluate a string with a condition statement

我正在创建一个验证器,通过允许开发人员在条件规则中使用条件语句,该验证器将能够处理复杂的条件。

以一组规则为例:

...
"element_name":{
     "required": "conditional",
     "conditions" : {
         "requirements" : "(4 < 5)",
         "requirements" : "('something' == 'something_else')" 
     }
}
...

然后PHP要做的是循环遍历这些requirements,并将它们作为代码进行评估,以返回一个布尔值,该布尔值将确定是否需要该元素。

使用eval()函数的问题非常明显。所以我想问,考虑到条件语句是只允许的,有没有比更安全的方法呢

$result = eval(element_name->requirements[0]);

谢谢大家。

----更新-----

谢谢迈克和每个人的想法,我希望我能把你们都标记为答案,因为说实话,我最终使用了每个人的一点想法。更多的迈克,所以他得到了。

因此,这可能是未来将要研究的问题,因为这是一种非常有吸引力的有条件验证字段的方法。我的目标是创造一种直观的方式来实现这一点。我喜欢在json配置文件中直接插入一个条件语句的想法。当然,这会涉及一些严重的安全风险或超级复杂的解析引擎,所以我最终要求开发人员学习我们的条件语言方法,但正如你所看到的,我保持了与原始语言非常相似的方法。我认为拥有简单的API很重要,否则会阻碍平台上的开发。看看:

"element_name":{
     "required": "conditional",
     "conditions" : {
         "<" : ['4', '5'],
         "==" : [':element_id', ':other_element'], // will grab the values of those elements and compare
         "exp" : [['something', '==', 'something_else'], 'OR', [':other_element', '!=', '0']] 
     }
}

为了扩展dethtron5000的答案,我想到的一种防止复杂得离谱的正则表达式的方法是让开发人员将他的条件分解成更多的多维条件,并使用递归函数对其进行循环。在每个级别上,您都会有一个"运算符",它可以是"AND"或"or"(至少我希望这被称为"运算符"(如果不能随意更改的话)。

在您的示例中,您有:(32 < 40 AND 10 > 5 OR 20 == 10)

(看起来你正在对条件进行json_encoding,所以我从下面的PHP数组开始,并从那里向后工作。我假设你只需要json_decode你的开发提供给你的内容,就可以获得一个有效的PHP数组)。上面的示例将表示为以下PHP数组:

$arr = array(
    'required' => 'conditional',
    'conditions' => array(
        'requirements' => array(
            'operator' => 'OR', // this makes it so that if any conditions at this level are true, it returns true
            0 => array(
                'operator' => 'AND', // this makes it so that all the conditions at this sub-level need to be satisfied to return true
                array(
                    'conditional1' => 32,
                    'conditional2' => 40,
                    'operation' => 'lessthan',
                ),
                array(
                    'conditional1' => 10,
                    'conditional2' => 5,
                    'operation' => 'greaterthan',
                ),
            ),
            1 => array(
                // Since there is only one condition here, it is not necessary to specify "AND" or "OR"
                array(
                    'conditional1' => 20,
                    'conditional2' => 10,
                    'operation' => 'equals',
                ),
            ),
        ),
    ),
);

然后,您可以使用如下递归函数循环条件:

function check_req(Array $reqs) {
    $operator = (isset($reqs['operator'])) ? $reqs['operator'] : 'AND';
    unset($reqs['operator']);
    foreach ($reqs as $req) {
        if (isset($req['operation'])) {
            switch ($req['operation']) {
                case 'lessthan':
                    $valids[] = $req['conditional1'] < $req['conditional2'];
                    break;
                case 'greaterthan':
                    $valids[] = $req['conditional1'] > $req['conditional2'];
                    break;
                case 'equals':
                    $valids[] = $req['conditional1'] == $req['conditional2'];
                    break;
            }
        }
        else {
            $valids[] = check_req($req);
        }
    }
    if ($operator == 'OR') {
        foreach ($valids as $valid) {
            if ($valid == true) {
                return true;
            }
        }
        return false;
    }
    else {
        foreach ($valids as $valid) {
            if ($valid == false) {
                return false;
            }
        }
        return true;
    }
}
var_dump(check_req($arr['conditions']['requirements'])); // true in this case

当我对其进行json_encode时,我得到:

{
    "required":"conditional",
    "conditions":{
        "requirements":{
            "operator":"OR",
            "0":{
                "operator":"AND",
                "0":{
                    "conditional1":32,
                    "conditional2":40,
                    "operation":"lessthan"
                },
                "1":{
                    "conditional1":10,
                    "conditional2":5,
                    "operation":"greaterthan"
                }
            },
            "1":[{
                "conditional1":20,
                "conditional2":10,
                "operation":"equals"
            }]
        }
    }
}

我假设这就是开发人员必须为您提供的。

一种解决方案是构建JSON,从而限制可以执行的操作总量。例如

"element_name":{
 "required": "conditional",
 "requirements" : [
        {
            "condition1": 4,
            "condition1": 5,
            "operation": "greaterthan"
        }
}

然后在你的PHP中(psedo codey,但你可以得到这个想法):

foreach($requirements as $key => $test){
    switch($test->operation) {
        case 'greaterthan':
            return ($test->condition1 > $test->condition2);
        /// put other comparison types here
    }
}

这意味着编码更多的业务逻辑,但最终会更安全,并防止像eval这样的注入。

要分解初始文本,可以使用json_decode()吗?这不会运行任何操作,但会将大字符串转换为数组结构。

对于实际的单个表达式,您是否有权访问parsekit_compile_string?

这将允许将原始文本转换为php字节码操作。通过switch语句来解释操作应该不难。由于您所说的在需求中所期望的内容是受约束的,所以这不会是太多的代码。

可以尝试不使用parsekit_compile_string的regex,但这将是一个更脆弱的解决方案。

是-您可以使用开关语句vis:

交换机($operator)

 case "==":
 return $a==$b;
 break;
 case "+":
 return $a+$b;
 break;
 etc...
 default:
 return false;
 break;

关键是如何将零件发送到交换机——毕竟有很多方法。

将eval()公开给不受信任的用户代码将是疯狂的。我可以看到它在Drupal这样的情况下的使用,在Drupal中,超级用户可以创建PHP页面,然后对其进行评估,但当然只有可信的用户才能做到这一点。限制可以用这种方式运行的代码要好得多。

编辑:

为了处理多个运算符,您仍然将其作为算术单元运行,但需要分别计算每个运算符。这可能意味着您必须一次计算一个指令字符串的一个字符,以获取例如括号。在客户端用javascript进行计算可能会更好。只要答案不发送到您的服务器,这是安全的(JS中的eval可能导致DOM注入攻击)

如果我可以提出一个无礼的建议,你真的需要这样做吗?有没有更好的方法,也许是以更小的块处理输入?如果我使用这个系统,我可能会拿起一个台式计算器来做这项工作,而不是使用网站!试着为用户做更多的工作。