PHP Eval and Capturing Errors (As Much as Possible)

PHP eval and capturing errors (as much as possible)

I've found a good alternative/answer to my question.

First of, let me start by saying that nikic's suggestion works when I set error_reporting(E_ALL); notices are shown in PHP output, and thanks to OB, they can be captured.

Next, I've found this very useful code:

/**
* Check the syntax of some PHP code.
* @param string $code PHP code to check.
* @return boolean|array If false, then check was successful, otherwise an array(message,line) of errors is returned.
*/
function php_syntax_error($code){
if(!defined("CR"))
define("CR","\r");
if(!defined("LF"))
define("LF","\n") ;
if(!defined("CRLF"))
define("CRLF","\r\n") ;
$braces=0;
$inString=0;
foreach (token_get_all('<?php ' . $code) as $token) {
if (is_array($token)) {
switch ($token[0]) {
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case T_START_HEREDOC: ++$inString; break;
case T_END_HEREDOC: --$inString; break;
}
} else if ($inString & 1) {
switch ($token) {
case '`': case '\'':
case '"': --$inString; break;
}
} else {
switch ($token) {
case '`': case '\'':
case '"': ++$inString; break;
case '{': ++$braces; break;
case '}':
if ($inString) {
--$inString;
} else {
--$braces;
if ($braces < 0) break 2;
}
break;
}
}
}
$inString = @ini_set('log_errors', false);
$token = @ini_set('display_errors', true);
ob_start();
$code = substr($code, strlen('<?php '));
$braces || $code = "if(0){{$code}\n}";
if (eval($code) === false) {
if ($braces) {
$braces = PHP_INT_MAX;
} else {
false !== strpos($code,CR) && $code = strtr(str_replace(CRLF,LF,$code),CR,LF);
$braces = substr_count($code,LF);
}
$code = ob_get_clean();
$code = strip_tags($code);
if (preg_match("'syntax error, (.+) in .+ on line (\d+)$'s", $code, $code)) {
$code[2] = (int) $code[2];
$code = $code[2] <= $braces
? array($code[1], $code[2])
: array('unexpected $end' . substr($code[1], 14), $braces);
} else $code = array('syntax error', 0);
} else {
ob_end_clean();
$code = false;
}
@ini_set('display_errors', $token);
@ini_set('log_errors', $inString);
return $code;
}

Seems it easily does exactly what I need (yay)!

How can I catch a parse error in php eval()?

Your code doesn't work as expected because, in PHP, an undefined variable doesn't trigger a parse error but a notice instead. Thanks to set_error_handler native function, you can convert a notice to error then catch it with this PHP 7 code:

<?php

set_error_handler(function($_errno, $errstr) {
// Convert notice, warning, etc. to error.
throw new Error($errstr);
});

$one = "hello";
$two = " world";
$three = '';
$cmdstr = '$three = $one . $tw;';

try {
$result = eval($cmdstr);
} catch (Throwable $e) {
echo $e; // Error: Undefined variable: tw...
}

echo $three;

Get trace call list of PHP eval or override it manually

is there a possibility to override eval function in PHP to display some trace information, i.e. from which file it was called, on which line did it occur?

Sort of.

You can add eval to disable_functions in php.ini. Then when you call eval you'll get the fatal error function eval not found or such.

Then with a custom error handler.

   set_error_handler(function($errno, $errstr, $errfile, $errline){
if(false !== strpos($errstr,'eval')){
throw new Exception();
}else{
return false; //see below
}
//debug_print_backtrace() - I prefer exceptions as they are easier to work with, but you can use this arcane thing too.
});

Or something like that (untested).

Unfortunately you cannot redefine eval as your own function. Eval is not really a function, its a language construct like isset, empty, include etc... For example function_exists('empty') is always false. Some are just more "function" like then others.

In any case you'll probably have to disable eval, I cant really think of a way around that.

Tip

Don't forget you can do this:

  try{
throw new \Exception;
}catch(\Exception $e){
echo $e->getTraceAsString();
}

Which both suppresses the exception (so execution continues), and gives you a nice stacktrace.

Tip

http://php.net/manual/en/function.set-error-handler.php

It is important to remember that the standard PHP error handler is completely bypassed for the error types specified by error_types unless the callback function returns FALSE

So given the above, you can/should return false for all other errors. Then PHP will report them. I am not sure it really matters much in this case, as this isn't really meant to be in production code, but I felt it worth mentioning.

Hope it helps.

error message evaluation system with PHP eval()

From PHP documentation:

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().

http://php.net/manual/en/function.eval.php

Adding return to your rules keys seems to fix the problem.

    $rules = array
(
'return strlen($value)>1;' => 'Your name is too short.',
'return is_numeric(substr($value,0,1));' => 'Your name has to begin with a character.',
'return has_specchar($value);' => 'Your name contains illegal characters.'
);

How handle eval errors inside of preg-replace-callback

As @user122293 said, the problem is about difference between fatal errors and other errors.

In PHP <7 fatal errors are not catch-able.

Also, there is no any Exception for fatal errors even in PHP >=7

Now we have some ways (from best to worst):

  • A) Making sure we use PHP 7 or higher and change the code to something like this :
$html='Hi it is <b>PAPION</b>. Now timestamp is <?php echo time(); ?>. have a good time. a can be: <?php a=b*2 ?>. and wrong timestamp is <?php xxxxtime(); ?>.';
$html = preg_replace_callback('/(<\?php)(.*?)(\?>)/ims',function($matches){
try {
ob_start();
eval($matches[2]);
return ob_get_clean();
} catch(Throwable $e) {
return "";
}
}, $html);
echo $html;

to output will be:

Hi it is PAPION. Now timestamp is 1570282086. have a good time. a can be: . and wrong timestamp is .

the important part in my case was using Throwable / Error in catch. as the explanation here: https://stackoverflow.com/a/48381661/7514010

  • B) & C) If we have access to exec function. we try the code first, then use it. or even save the file temporary and execute it for test. (Look crazy!)

or

Send the code as a query to a tester and get the result and then run it. (It can not work because some PHP code tags can depend to each other and should evaluate in same script.)
Both explained here : https://stackoverflow.com/a/33531067/7514010

  • D) Using register_shutdown_function + crazy way to force PHP 5 to at last have get the html again or even run next lines.
    Like this example :
    https://3v4l.org/0R7dq

    • +E) Make a function for parsing manually before eval or include using regex parsing and function_exists or method_exists or etc... or any library.


Related Topics



Leave a reply



Submit