5
\$\begingroup\$

PHP's eval() is general considered insecure with any user input.

However, with filtering of input (i.e. [-+/*0-9]) you can make eval() secure for a small subset of PHP; mathematical expressions.

I've tried to think of a way to extend that by allowing a limited set of math functions and I believe I've found a secure way of doing it.

function expression($expression) {
    static $function_map = array(
        'floor'     => 'floor',
        'ceil'      => 'ceil',
        'round'     => 'round',         
        'sin'       => 'sin',
        'cos'       => 'cos',
        'tan'       => 'tan',           
        'asin'      => 'asin',
        'acos'      => 'acos',
        'atan'      => 'atan',          
        'abs'       => 'abs',
        'log'       => 'log',           
        'pi'        => 'pi',
        'exp'       => 'exp',
        'min'       => 'min',
        'max'       => 'max',
        'rand'      => 'rand',
        'fmod'      => 'fmod',
        'sqrt'      => 'sqrt',
        'deg2rad'   => 'deg2rad',
        'rad2deg'   => 'rad2deg',
    );

    // Remove any whitespace
    $expression = strtolower(preg_replace('~\s+~', '', $expression));

    // Empty expression
    if ($expression === '') {
        trigger_error('Empty expression', E_USER_ERROR);
        return null;
    }

    // Illegal function
    $expression = preg_replace_callback('~\b[a-z]\w*\b~', function($match) use($function_map) {
        $function = $match[0];
        if (!isset($function_map[$function])) {
            trigger_error("Illegal function '{$match[0]}'", E_USER_ERROR);
            return '';
        }
        return $function_map[$function];
    }, $expression);

    // Invalid function calls
    if (preg_match('~[a-z]\w*(?![\(\w])~', $expression, $match) > 0) {
        trigger_error("Invalid function call '{$match[0]}'", E_USER_ERROR);
        return null;
    }

    // Legal characters
    if (preg_match('~[^-+/%*&|<>!=.()0-9a-z,]~', $expression, $match) > 0) {
        trigger_error("Illegal character '{$match[0]}'", E_USER_ERROR);
        return null;
    }       

    return eval("return({$expression});");
}

It's use would be simple: `$result = expression('floor(3 / 2)') and anything but math/boolean operators, numbers and a limited set of functions should be filtered out.

I can't find a way of hacking this expression() function, but I'd really like to have a second opinion on this.

Is the above expression function secure for user input?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

It looks pretty reasonable to me. A couple of points:

  1. You seem to be assuming that a function start with a letter. Functions CAN start with an underscore. Are you sure there is no such function that can be exploited?

  2. The regex in the section 'Invalid function calls' seems like it could be simpler. You are trying to check that any 'word' ends with a '(', is this right? If so, I don't think you need the second \w

Finally although this seems like a good job, the ideally ideal way to do something like this would be to make your own tokenizer. A tokenizer looks at a string char by char and decides at each point whether it's looking at part of an identifier, an operator, etc. You would simply need to call your checks at each point that it decides it has identified a complete token. The advantage of this is that it would be easy to extend the syntax you accept and if needed to make translations from the language you accept to valid PHP for eval().

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Re: 1. It shouldn't be a problem as the legal characters check does not recognize underscore as a legal character. \$\endgroup\$
    – Martijn
    Jan 17, 2014 at 11:59
  • \$\begingroup\$ Re: 2. The check is to find words that do not end with a (. The second \w is required as without it, abc( would match as ab. The \w ensures it matches at word boundary. Which is not to say there might be a more better regex that does the same job. \$\endgroup\$
    – Martijn
    Jan 17, 2014 at 12:00
  • \$\begingroup\$ Obviously you are right about 1. I may have tested 2 wrongly, will check \$\endgroup\$
    – jwg
    Jan 17, 2014 at 13:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.