Kepp the following Quote in mind:
If eval() is the answer, you're almost certainly asking the
wrong question. -- Rasmus Lerdorf, BDFL of PHP
(PHP 4, PHP 5, PHP 7)
eval — Evaluate a string as PHP code
Evaluates the given code
as PHP.
The eval() language construct is very dangerous because it allows execution of arbitrary PHP code. Its use thus is discouraged. If you have carefully verified that there is no other option than to use this construct, pay special attention not to pass any user provided data into it without properly validating it beforehand.
code
Valid PHP code to be evaluated.
The code must not be wrapped in opening and closing PHP tags, i.e. 'echo "Hi!";' must be passed instead of '<?php echo "Hi!"; ?>'. It is still possible to leave and re-enter PHP mode though using the appropriate PHP tags, e.g. 'echo "In PHP mode!"; ?>In HTML mode!<?php echo "Back in PHP mode!";'.
Apart from that the passed code must be valid PHP. This includes that all statements must be properly terminated using a semicolon. 'echo "Hi!"' for example will cause a parse error, whereas 'echo "Hi!";' will work.
A return statement will immediately terminate the evaluation of the code.
The code will be executed in the scope of the code calling eval(). Thus any variables defined or changed in the eval() call will remain visible after it terminates.
eval() returns NULL
unless
return is called in the evaluated code, in which case
the value passed to return is returned. As of PHP 7, if there is a
parse error in the evaluated code, eval() throws a ParseError exception.
Before PHP 7, in this case eval() returned
FALSE
and execution of the following code continued normally. It is
not possible to catch a parse error in eval()
using set_error_handler().
Example #1 eval() example - simple text merge
<?php
$string = 'cup';
$name = 'coffee';
$str = 'This is a $string with my $name in it.';
echo $str. "\n";
eval("\$str = \"$str\";");
echo $str. "\n";
?>
The above example will output:
This is a $string with my $name in it. This is a cup with my coffee in it.
Note: Because this is a language construct and not a function, it cannot be called using variable functions.
As with anything that outputs its result directly to the browser, the output-control functions can be used to capture the output of this function, and save it in a string (for example).
Note:
In case of a fatal error in the evaluated code, the whole script exits.
Kepp the following Quote in mind:
If eval() is the answer, you're almost certainly asking the
wrong question. -- Rasmus Lerdorf, BDFL of PHP
Eval() is no more dangerous than allowing SQL injection or other security flaws within your scripts. I feel it is unfair to single out eval() as "dangerous" when it is as dangerous as many other features of the PHP language if misused.
I am writing a powerful PHP Framework and without eval() I could not implement some of its very best features.
With power comes responsibility and it is up to each of us to ensure that no unfiltered user input ever ends up in eval().
eval() can easily be used in if-statements (in contrast to jkuckartz1984 at hotmail dot com): simply put a "return()" around the statement: if(eval('return($total'.$i.');')) ...
And in answer to the top-comment: I use eval() for my personal bug-fixes in e.g. open-source-software, where a function gets old code and new code as strings, so it can produce a bugreport (with some more added information) and the new code is evaled by eval(). Just a little overhead, but a nice way to keep all my changes together. So I think, there are some "right" questions where eval() is the (proper) answer.
At least I think, if you say "The eval() language construct is very dangerous because it allows execution of arbitrary PHP code." you just as well can say "PHP is very dangerous because it allows execution of arbitrary PHP code." The point is not to use any user provided data - that's all.
Sharing a code which might be useful to someone. my_eval() is useful if
- You don't want to introduce new variables like what eval() would do.
- You just want to execute some calculations or some file/database retrieval and return the result.
- By returning the result in an array, you can differentiate a FALSE between a parse error or a result returned by the $code.
function my_eval($code) {
$my_code = '$my_function = function () {' . $code . '}; return array($my_function());';
return eval($my_code);
}
Inception with eval()
<pre>
Inception Start:
<?php
eval("echo 'Inception lvl 1...\n'; eval('echo \"Inception lvl 2...\n\"; eval(\"echo \'Inception lvl 3...\n\'; eval(\'echo \\\"Limbo!\\\";\');\");');");
?>
For them who are facing syntax error when try execute code in eval,
<?php
$str = '<?php echo "test"; ?>';
eval('?>'.$str.'<?php;'); // outputs test
eval('?>'.$str.'<?'); // outputs test
eval('?>'.$str.'<?php');// throws syntax error - unexpected $end
?>
I have coding experience since 1980 since I was 13 years old (have programmed ALL/Most languages from Z80/6502/8080 upwards). I have owned technology and IT businesses since 1989.
eval is NO MORE dangerous than a 'user' having access to root deleting the partition with fdisk (or any other command/statement/function).
It is all about proper design and engineering, and, control/policy. But we live in a 'freelance' world where anybody who can 'cut-and-paste' is a programmer working for the cheapest bidder (aka outsourcing).
If you want to allow math input and make sure that the input is proper mathematics and not some hacking code, you can try this:
<?php
$test = '2+3*pi';
// Remove whitespaces
$test = preg_replace('/\s+/', '', $test);
$number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number
$functions = '(?:sinh?|cosh?|tanh?|abs|acosh?|asinh?|atanh?|exp|log10|deg2rad|rad2deg|sqrt|ceil|floor|round)'; // Allowed PHP functions
$operators = '[+\/*\^%-]'; // Allowed math operators
$regexp = '/^(('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?2))?)+$/'; // Final regexp, heavily using recursive patterns
if (preg_match($regexp, $q))
{
$test = preg_replace('!pi|π!', 'pi()', $test); // Replace pi with pi function
eval('$result = '.$test.';');
}
else
{
$result = false;
}
?>
I can't guarantee you absolutely that this will block every possible malicious code nor that it will block malformed code, but that's better than the matheval function below which will allow malformed code like '2+2+' which will throw an error.
Sorry, some corrections to my note... I've:
* removed the minus sign -? at the beginning and readded the |pi|π at the end like in previous examples;
* added some math functions;
* added the , in the operators: now it is possible to calculate pow(1,3) or rand(1,10);
* changed the $number definition to accept scientific notation like in:
http://stackoverflow.com/questions/638565/parsing-scientific-notation-sensibly
<?php
function calc($equation)
{
// Remove whitespaces
$equation = preg_replace('/\s+/', '', $equation);
echo "$equation\n";
$number = '((?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?|pi|π)'; // What is a number
$functions = '(?:sinh?|cosh?|tanh?|acosh?|asinh?|atanh?|exp|log(10)?|deg2rad|rad2deg
|sqrt|pow|abs|intval|ceil|floor|round|(mt_)?rand|gmp_fact)'; // Allowed PHP functions
$operators = '[\/*\^\+-,]'; // Allowed math operators
$regexp = '/^([+-]?('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?1))?)+$/'; // Final regexp, heavily using recursive patterns
if (preg_match($regexp, $equation))
{
$equation = preg_replace('!pi|π!', 'pi()', $equation); // Replace pi with pi function
echo "$equation\n";
eval('$result = '.$equation.';');
}
else
{
$result = false;
}
return $result;
}
?>
Just a note when using eval and expecting return values - the eval()'ed string must do the returning. Take the following example script:
<?php
function ReturnArray()
{
return array("foo"=>1, "bar"=>2);
}
$test = eval("ReturnArray();");
print("Got back $test (".count($test).")\n");
$test = eval("return ReturnArray();");
print("Got back $test (".count($test).")\n");
?>
You will get back:
Got back (0)
Got back Array (2)
This ran me afoul for a little bit, but is the way eval() is supposed to work (eval is evaluating a new PHP script).
To catch a parse error in eval()'ed code with a custom error handler, use error_get_last() (PHP >= 5.2.0).
<?php
$return = eval( 'parse error' );
if ( $return === false && ( $error = error_get_last() ) ) {
myErrorHandler( $error['type'], $error['message'], $error['file'], $error['line'], null );
// Since the "execution of the following code continues normally", as stated in the manual,
// we still have to exit explicitly in case of an error
exit;
}
?>
Be careful when using eval() on heavy usage sites in PHP 4.0+ as it takes vastly longer to activate due to the limitations of the Zend engine.
The Zend engine changes the PHP to a binary structure at the START of the file, and then parses it. Every time an eval is called, however, it has to reactivate the parsing procedure and convert the eval()'d code into usable binary format again.
Basically, if you eval() code, it takes as long as calling a new php page with the same code inside.
Magic constants like __FILE__ may not return what you expect if used inside eval()'d code. Instead, it'll answer something like "c:\directory\filename.php(123) : eval()'d code" (under Windows, obviously, checked with PHP5.2.6) - which can still be processed with a function like preg_replace to receive the filename of the file containing the eval().
Example:
<?php
$filename = preg_replace('@\(.*\(.*$@', '', __FILE__);
echo $filename;
?>
The following code
<?php
eval( '?> foo <?php' );
?>
does not throw any error, but prints the opening tag.
Adding a space after the open tag fixes it:
<?php
eval( '?> foo <?php ' );
?>
When using Dynamic Proxy design pattern we must create a class automaticly. Here is a sample code.
<?php
$clazz = "class SomeClass { var \$value = 'somevalue'; function show() { echo get_class(\$this);}}";
eval($clazz);
$instance = new SomeClass;
// Here output 'somevalue';
echo $instance->value;
echo "<br>";
//Here output 'someclass'
$instance->show();
?>
<?php
function safe_eval($code,&$status) { //status 0=failed,1=all clear
//Signs
//Can't assign stuff
$bl_signs = array("=");
//Language constructs
$bl_constructs = array("print","echo","require","include","if","else",
"while","for","switch","exit","break");
//Functions
$funcs = get_defined_functions();
$funcs = array_merge($funcs['internal'],$funcs['user']);
//Functions allowed
//Math cant be evil, can it?
$whitelist = array("pow","exp","abs","sin","cos","tan");
//Remove whitelist elements
foreach($whitelist as $f) {
unset($funcs[array_search($f,$funcs)]);
}
//Append '(' to prevent confusion (e.g. array() and array_fill())
foreach($funcs as $key => $val) {
$funcs[$key] = $val."(";
}
$blacklist = array_merge($bl_signs,$bl_constructs,$funcs);
//Check
$status=1;
foreach($blacklist as $nono) {
if(strpos($code,$nono) !== false) {
$status = 0;
return 0;
}
}
//Eval
return @eval($code);
}
?>
Note: Try to include this after all of your other self-defined functions and consider whether the blacklist is appropriate for your purpose
I wouldn't recommend this function if you're going to use eval extensively in your script. However, it's worth a try if you are going to put user input into eval
Might you have to do eval in if statements, you will find it's quite some task to make it work.
The only way to make it work is to make a reference to the eval'd variable. This example will show the different usage of eval in if-statements. It simply becomes clear that an eval() in an if() is not working as you want to.
<?php
$total2=5;
$total3=0;
$i=2;
if (eval("\$total".$i.";")) {
echo "eval: total2 is full<br>";
} else {
echo "eval: total2 is empty<br>";
}
// returns "empty"
// eval without the ";" will generate a warning
$str="\$refer=&\$total".$i.";";
eval($str);
if ($refer) {
echo "eval: total2 is full<br>";
} else {
echo "eval: total2 is empty<br>";
}
// returns "full"
?>
eval'd code within namespaces which contain class and/or function definitions will be defined in the global namespace... not incredibly obvious :/
Runkit_Sandbox
Sandboxing class may be what you look for, but you need to compile your php with a special flag :
Sandboxing support requires that PHP (not just the extension mind you, but the main PHP bundle) be compiled with ZTS support enabled.
The configure switch would be --enable-maintainer-zts, not sure what gentoo calls this within it's USE flags.
Is there no native function like this one that can let programmer specify security context without having to compile back php ?
http://php.net/manual/en/runkit.sandbox.php
with reference to the problem of mathematic equations validation
, as an operator is not a good idea... i've removed the comma from operators and added the optional group (,(?1))? to validate functions like pow(2,3)
$operators = '[\/*\^\+-]'; // Allowed math operators
$regexp = '/^([+-]?('.$number.'|'.$functions.'\s*\((?1)+(,(?1))?\)|\((?1)+\))(?:'.$operators.'(?1))?)+$/'; // Final regexp, heavily using recursive patterns
If you would like to handle doing an eval on a string with mixed PHP and other stuff such as HTML, you can use the following:
Example:
<?php
$test_string = 'A friendly llama <?php 1 == 1 ? "spit on" : "hugged"; ?> me';
$p = new PhpStringParser();
echo $p->parse($mixed_string);
?>
Result:
A friendly llama spit on me
You can optionally make variables that are outside of the string you are parsing available to the eval level scope by passing them into the constructor.
Example:
<?php
$llama_action = 'hugged';
$test_string = 'A friendly llama <?=$llama_action?> me';
$p = new PhpStringParser(array('llama_action', $llama_action));
echo $p->parse($mixed_string);
?>
Result:
A friendly llama hugged me
<?php
class PhpStringParser
{
protected $variables;
public function __construct($variables = array())
{
$this->variables = $variables;
}
protected function eval_block($matches)
{
if( is_array($this->variables) && count($this->variables) )
{
foreach($this->variables as $var_name => $var_value)
{
$$var_name = $var_value;
}
}
$eval_end = '';
if( $matches[1] == '<?=' || $matches[1] == '<?php=' )
{
if( $matches[2][count($matches[2]-1)] !== ';' )
{
$eval_end = ';';
}
}
$return_block = '';
eval('$return_block = ' . $matches[2] . $eval_end);
return $return_block;
}
public function parse($string)
{
return preg_replace_callback('/(\<\?=|\<\?php=|\<\?php)(.*?)\?\>/', array(&$this, 'eval_block'), $string);
}
}
?>
It may be a bit obvious, but if you don't want eval'd code to interfere with main code variables, all you have to do is to call an eval from another function like this:
<?php
function localeval($code) {
return eval($code);
};
$a = "a";
echo '$a before eval code: ' . $a . "\n"; // prints "a"
localeval('$a = \'b\';');
echo '$a after localeval: ' . $a . "\n"; // still prints "a"
eval('$a = \'b\';');
echo '$a after eval: ' . $a . "\n"; // prints "b"
?>
I have looked for a simple way of forcing PHP to interpolate strings after the event (that is, after they have already been assigned, say from a text file).
The following seems to work:
<?php
$text='$a + $b = $c';
$a=2; $b=3; $c=$a+$b;
print eval("return<<<END\n$text\nEND;\n");
?>
I have even tried it with some potentially evil code, but the context seems to preclude interpreting it as anything other than a string.
Mark
Fixed matheval function when percentage is less than 10:
<?php
function matheval($equation)
{
$equation = preg_replace("/[^0-9+\-.*\/()%]/","",$equation);
// fix percentage calcul when percentage value < 10
$equation = preg_replace("/([+-])([0-9]{1})(%)/","*(1\$1.0\$2)",$equation);
// calc percentage
$equation = preg_replace("/([+-])([0-9]+)(%)/","*(1\$1.\$2)",$equation);
// you could use str_replace on this next line
// if you really, really want to fine-tune this equation
$equation = preg_replace("/([0-9]+)(%)/",".\$1",$equation);
if ( $equation == "" )
{
$return = 0;
}
else
{
eval("\$return=" . $equation . ";" );
}
return $return;
}
?>
eval does not work reliably in conjunction with global, at least not in the cygwin port version.
So:
<?PHP
class foo {
//my class...
}
function load_module($module) {
eval("global \$".$module."_var;");
eval("\$".$module."_var=&new foo();");
//various stuff ... ...
}
load_module("foo");
?>
becomes to working:
<?PHP
class foo {
//my class...
}
function load_module($module) {
eval('$GLOBALS["'.$module.'_var"]=&new foo();');
//various stuff ... ...
}
load_module("foo");
?>
Note in the 2nd example, you _always_ need to use $GLOBALS[$module] to access the variable!
If you attempt to call a user defined function in eval() and .php files are obfuscated by Zend encoder, it will result in a fatal error.
Use a call_user_func() inside eval() to call your personal hand made functions.
This is user function
<?php
function square_it($nmb)
{
return $nmb * $nmb;
}
?>
//Checking if eval sees it?
<?php
$code = var_export( function_exists('square_it') );
eval( $code ); //returns TRUE - so yes it does!
?>
This will result in a fatal error:
PHP Fatal error: Call to undefined function square_it()
<?php
$code = 'echo square_it(55);' ;
eval( $code );
?>
This will work
<?php
$code = 'echo call_user_func(\'square_it\', 55);' ;
eval( $code );
?>
Safer Eval
eval() is used way to often. It slows down code, makes it harder to maintain and it created security risks. However, sometimes, I found myself wishing I could allow some user-controlled scripting in my software, without giving access to dangerous functions.
That's what the following class does: it uses PHP's tokenizer to parse a script, compares every function call against a list of allowed functions. Only if the script is "clean", it gets eval'd.
<?php
class SaferScript {
var $source, $allowedCalls;
function SaferScript($scriptText) {
$this->source = $scriptText;
$this->allowedCalls = array();
}
function allowHarmlessCalls() {
$this->allowedCalls = explode(',',
'explode,implode,date,time,round,trunc,rand,ceil,floor,srand,'.
'strtolower,strtoupper,substr,stristr,strpos,print,print_r');
}
function parse() {
$this->parseErrors = array();
$tokens = token_get_all('<?'.'php '.$this->source.' ?'.'>');
$vcall = '';
foreach ($tokens as $token) {
if (is_array($token)) {
$id = $token[0];
switch ($id) {
case(T_VARIABLE): { $vcall .= 'v'; break; }
case(T_STRING): { $vcall .= 's'; }
case(T_REQUIRE_ONCE): case(T_REQUIRE): case(T_NEW): case(T_RETURN):
case(T_BREAK): case(T_CATCH): case(T_CLONE): case(T_EXIT):
case(T_PRINT): case(T_GLOBAL): case(T_ECHO): case(T_INCLUDE_ONCE):
case(T_INCLUDE): case(T_EVAL): case(T_FUNCTION): {
if (array_search($token[1], $this->allowedCalls) === false)
$this->parseErrors[] = 'illegal call: '.$token[1];
}
}
}
else
$vcall .= $token;
}
if (stristr($vcall, 'v(') != '')
$this->parseErrors[] = array('illegal dynamic function call');
return($this->parseErrors);
}
function execute($parameters = array()) {
foreach ($parameters as $k => $v)
$$k = $v;
if (sizeof($this->parseErrors) == 0)
eval($this->source);
else
print('cannot execute, script contains errors');
}
}
?>
Usage example:
<?php
$ls = new SaferScript('horribleCode();');
$ls->allowHarmlessCalls();
print_r($ls->parse());
$ls->execute();
?>
Of course it is not entirely safe, but it's a start ;-)
This is another function that return true if $formula is a syntactically valid Math function, false otherwise.
NDR.
1) It not allows %, pi|π.
2) It allows this Math functions:
abs, atan, ceil, cot, cos, exp, gmp_fact, intval, sin, sqrt, tan, log, log10, rand, round.
I write it on pastebin.com because php.net not allow me to post long code.
enjoy it! :-)
http://pastebin.com/EgwV7QdK
Modified version of sooncj answer: your regex not allow something like this: 1-(1). So I modified your code: I change the 'Allowed PHP functions':
<?php
function calc($equation)
{
// Remove whitespaces
$equation = preg_replace('/\s+/', '', $equation);
echo "$equation\n";
$number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number
$functions = '(?:abs|atan|ceil|cos|exp|gmp_fact|intval|log(10)?|rand|round|sin|sqrt|tan)'; // Allowed PHP functions
$operators = '[\/*\^\+-]'; // Allowed math operators
$regexp = '/^([+-]?('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?1))?)+$/'; // Final regexp, heavily using recursive patterns
if (preg_match($regexp, $equation))
{
$equation = preg_replace('!pi|π!', 'pi()', $equation); // Replace pi with pi function
echo "$equation\n";
eval('$result = '.$equation.';');
}
else
{
$result = false;
}
return $result;
}
?>
This function allow also this 'strange' math function:
1) a-+b
2) a+-b
3) a++b << PHP not allow this
4) a--b. << PHP not allow this
But not allow with 3 '+' or '-':
1) a---b, a+++b, a-++b, a+--b, a+-+b, a-+-b.
Allow other Math functions.
For a++b or a--b i write another function that block that!
Modified version of Daniele Mams's answer: I change the $number definition to accept scientific notation like in:
http://stackoverflow.com/questions/638565/parsing-scientific-notation-sensibly
<?php
function calc($equation)
{
// Remove whitespaces
$equation = preg_replace('/\s+/', '', $equation);
echo "$equation\n";
$number = '(-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)'; // What is a number
$functions = '(?:abs|atan|ceil|cos|exp|gmp_fact|intval|log(10)?|rand|round|sin|sqrt|tan)'; // Allowed PHP functions
$operators = '[\/*\^\+-]'; // Allowed math operators
$regexp = '/^([+-]?('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?1))?)+$/'; // Final regexp, heavily using recursive patterns
if (preg_match($regexp, $equation))
{
$equation = preg_replace('!pi|π!', 'pi()', $equation); // Replace pi with pi function
echo "$equation\n";
eval('$result = '.$equation.';');
}
else
{
$result = false;
}
return $result;
}
?>
An obvious security reminder, which I think wasn't yet mentioned here. Special care is required when variables entered by the user are passed to the eval() function. You should validate those user inputs, and really make sure they have the format you expect.
E.g., if you evaluate math expressions with something like
<?php
eval("\$result = $equation;");
?>
without any check on the $equation variable, a bad user could enter in the $equation field
""; echo file_get_contents('/etc/passwd')
- or whatever PHP code he wants! - which would evaluate to
<?php
$result = ""; echo file_get_contents('/etc/passwd');
?>
and seriously compromising your security!
The only way to retreive information on parse errors in eval'd code seems to be the output buffering.
<?PHP
// Append a return true to php-code to check on errors
$code.= "\nreturn true;";
// Send any output to buffer
ob_start();
// Do eval()
$check = eval($code);
$output = ob_get_contents();
ob_end_clean();
// Send output or report errors
if ($check === true) {
echo $output;
} else {
// Manually parse output for errors and
// generate usable information for the user
// especially content of error-lines.
$pattern = '/^\s*Parse error\s*:(.+) in (.+) on line (\d+)\s*$/m';
etc ...
}
Modified version of bohwaz's answer: with negative number support and removes the requirement of properly grouped (bracketed) expressions.
<?php
function calc($equation)
{
// Remove whitespaces
$equation = preg_replace('/\s+/', '', $equation);
echo "$equation\n";
$number = '(?:-?\d+(?:[,.]\d+)?|pi|π)'; // What is a number
$functions = '(?:sinh?|cosh?|tanh?|abs|acosh?|asinh?|atanh?|exp|log10|deg2rad|rad2deg|sqrt|ceil|floor|round)'; // Allowed PHP functions
$operators = '[+\/*\^%-]'; // Allowed math operators
$regexp = '/^(('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?1))?)+$/'; // Final regexp, heavily using recursive patterns
if (preg_match($regexp, $equation))
{
$equation = preg_replace('!pi|π!', 'pi()', $equation); // Replace pi with pi function
echo "$equation\n";
eval('$result = '.$equation.';');
}
else
{
$result = false;
}
return $result;
}
?>