2012-06-01 45 views
3

我们有一个系统必须执行用户输入提供的计算。PHP EVAL - 解决不安全问题

我发现做这些计算的一个最简单的方法是EVAL - 试图找出一个解析器:

(3 + 6)/2 + 27 * 5/2 

只是似乎很难。如果有人有解决方案 - 我会很高兴听到它。

假设你打算使用EVAL(我知道它是可怕的函数),那么允许他们在该盒子中输入任何他们想要的东西将是一个主要的不安全因素。

所以,我提出一个问题,如果我做了一个正则表达式去除除了数字的一切,标准的运营商(+ -/*)和括号,像

$equation = preg_replace('/[^0-9+-\/*()]/', '', $input_equation); 
$result = eval($equation); 

是否有可能可能发生在任何伤害一个系统?

+0

google for php数学表达式求值器。我见过很多,我写过一篇,至少有一篇关于梨。 – goat

+0

危害?可能不会。显然他们可以写一些无效的代码,除以零等。我会用@chris的建议去。 – Matthew

+0

@Dagon输入通过正则表达式进行验证。我认为这不好。 –

回答

4

最近,我编写了一个PEDMAS compliant interpreter使用BCMath功能:

function BC($string, $precision = 32) 
{ 
    if (extension_loaded('bcmath') === true) 
    { 
     if (is_array($string) === true) 
     { 
      if ((count($string = array_slice($string, 1)) == 3) && (bcscale($precision) === true)) 
      { 
       $callback = array('^' => 'pow', '*' => 'mul', '/' => 'div', '%' => 'mod', '+' => 'add', '-' => 'sub'); 

       if (array_key_exists($operator = current(array_splice($string, 1, 1)), $callback) === true) 
       { 
        $x = 1; 
        $result = @call_user_func_array('bc' . $callback[$operator], $string); 

        if ((strcmp('^', $operator) === 0) && (($i = fmod(array_pop($string), 1)) > 0)) 
        { 
         $y = BC(sprintf('((%1$s * %2$s^(1 - %3$s))/%3$s) - (%2$s/%3$s) + %2$s', $string = array_shift($string), $x, $i = pow($i, -1))); 

         do 
         { 
          $x = $y; 
          $y = BC(sprintf('((%1$s * %2$s^(1 - %3$s))/%3$s) - (%2$s/%3$s) + %2$s', $string, $x, $i)); 
         } 

         while (BC(sprintf('%s > %s', $x, $y))); 
        } 

        if (strpos($result = bcmul($x, $result), '.') !== false) 
        { 
         $result = rtrim(rtrim($result, '0'), '.'); 

         if (preg_match(sprintf('~[.][9]{%u}$~', $precision), $result) > 0) 
         { 
          $result = (strncmp('-', $result, 1) === 0) ? bcsub($result, 1, 0) : bcadd($result, 1, 0); 
         } 

         else if (preg_match(sprintf('~[.][0]{%u}[1]$~', $precision - 1), $result) > 0) 
         { 
          $result = bcmul($result, 1, 0); 
         } 
        } 

        return $result; 
       } 

       return intval(version_compare(call_user_func_array('bccomp', $string), 0, $operator)); 
      } 

      $string = array_shift($string); 
     } 

     $string = str_replace(' ', '', str_ireplace('e', ' * 10^', $string)); 

     while (preg_match('~[(]([^()]++)[)]~', $string) > 0) 
     { 
      $string = preg_replace_callback('~[(]([^()]++)[)]~', __METHOD__, $string); 
     } 

     foreach (array('\^', '[\*/%]', '[\+-]', '[<>]=?|={1,2}') as $operator) 
     { 
      while (preg_match(sprintf('~(?<![0-9])(%1$s)(%2$s)(%1$s)~', '[+-]?(?:[0-9]++(?:[.][0-9]*+)?|[.][0-9]++)', $operator), $string) > 0) 
      { 
       $string = preg_replace_callback(sprintf('~(?<![0-9])(%1$s)(%2$s)(%1$s)~', '[+-]?(?:[0-9]++(?:[.][0-9]*+)?|[.][0-9]++)', $operator), __METHOD__, $string, 1); 
      } 
     } 
    } 

    return (preg_match('~^[+-]?[0-9]++(?:[.][0-9]++)?$~', $string) > 0) ? $string : false; 
} 

它支持以下运算符:

  • ^(POW)
  • *
  • /
  • %(模数)
  • +
  • -
  • ===<<=>>=(比较)

你这样称呼它:

echo BC('(3 + 6)/2 + 27 * 5/2'); 

我这样做,所以我不得不执行任意长度的计算,但是,你的情况,你可能也只是去除所有空格和验证使用的字符的简单方法正则表达式如下:

if (preg_match('~^(?:[0-9()*/%+-<>=]+)$~', $expression) > 0) { 
    // safe to eval() 
} 
+0

谢谢!我会比较两者(速度明智),看看哪个效果最好 - 如果可以,我喜欢避免使用eval。 –

0

这对我来说似乎比较简单,但我还没有寻找原生的PHP解决方案。我会发布它只是为了踢。

而不是使用eval的,我想使用的第一件事就是exec调用bc,并有bc做一切对我的工作(假设你是一个Linux机器上)。

所以,你会做这样的事情(非常未经):

$user_input = '(3 + 6)/2 + 27 * 5/2'; 
$return = exec('echo "scale=1; ' . escapeshellarg($user_input) . '" | bc', $output, $retval); 

$calculation = $output[0]; 
if(!is_numeric($calculation)) { 
    echo "Invalid input!"; 
} 
echo $calculation; // Outputs 72