Shop OBEX P1 Docs P2 Docs Learn Events
Need PHP Hacker Help — Parallax Forums

Need PHP Hacker Help

mctriviamctrivia Posts: 3,772
edited 2011-03-02 09:54 in General Discussion
I have posted this question on phpfreaks but have not gotten any responses and I know there is a lot of super smart people here so I am asking here also.

I a trying to write a php script that uses the extremely dangerous line
if (eval('return (' . $rule . ');')) {

the user has control over the $rule variable so if I am not careful the user could put malicious code into the variable and hack my server. To attempt to prevent this I have written the following code which should find and fail any code that could possible be used to hack my server. My question is can you assign a value to $rules neer the end of the script which will pass the test but would be bad for my server either in the testing process or when the code above is tested. Any feed back would be greatly appreciated.
<?php

      $whitefunc=array(
'abs()','acos()','acosh()','asin()','asinh()','atan()','atan2()','atanh()',
'base_convert()','bindec()',
'ceil()','cos()','cosh()',
'decbin()','dechex()','decoct()','deg2rad()',
'exp()','expm1()',
'floor()','fmod()',
'getrandmax()',
'hexdec()','hypot()',
'is_finite()','is_infinite()','is_nan()',
'lcg_value()','log()','log10()','log1p()',
'max()','min()','mt_getrandmax()','mt_rand()','mt_srand()',
'octdec()',
'pi()','pow()',
'rad2deg()','rand()','round()',
'sin()','sinh()','sqrt()','srand()',
'tan()','tanh()'
      );





function breakBracket($code,$a="(",$b=")") {
  $parts=array();
  $end=strpos($code,$b);
  while (!($end===false)) {

    //find first start bracket before point
    $start=strrpos(substr($code,0,$end),$a);

    //find first end bracket after point
    $end=strpos($code,$b,$start);

    //break inner brackets out
    $parts[]=substr($code,$start+1,$end-$start-1);
    $code=substr($code,0,$start) . '|~-~|' . substr($code,$end+1);

    //find next search point
    $end=strrpos($code,$b);
  }
  $parts[]=$code;

  //replace '|~-~|' with brackets
  foreach ($parts as &$part) {
    $part=preg_replace("/\|~-~\|/", "()", $part);
  }

  return $parts;
}

function check_syntax($code) {
    return @eval('return true;' . $code);
}

function testRule($rule) {    
  //replace $1,$2,$3... in rules with tag code parts
  $rule=preg_replace_callback(
	'/\$[0-9]+/',
	create_function(
		// single quotes are essential here,
		// or alternative escape all $ as \$
		'$matches',
		'return "53";'
	),
	$rule
  );

  //check for ilegal characters ;'"
  $good=(!(preg_match("/([;'\"])/",$rule)>0));

  //check for assingments
  if ($good) {
    $temp=preg_replace('/([=!]==)/','',$rule);
    $temp=preg_replace('/([=<>!]=)/','',$temp);
    $good=(!(preg_match("/(=)/",temp)>0));
  }

  //see if rule is valid
  if ($good) {
    $good=check_syntax('(' . $rule . ');');
  }

  //check if rule only contains good functions, numbers, brackets, and math operators
  if ($good) {

    //break inside of () out of outsides
    $ruleparts=breakBracket($rule);

    //combine all parts together into 1 string
    $str='';
    foreach ($ruleparts as $part) {
      $str.=$part . ' ';
    }

    //remove all valid functions
    global $whitefunc; 				//array of lower case functions user may use with () after name
    $str=str_replace($whitefunc,' ',$str); 

    //remove all valid characters
    $str=preg_replace('|([0-9\(\)\+\-\*\/\%=\&\|,])|',' ',$str);

    //if anything left call error
    if (preg_match_all("/([^ ])/",$str,$out)) {
      $good=false;
    }
  }

  return $good;
}







$rules=array(
'acos(56)+(ceil(56)-3+5)',
'sin(45)-35',
'a+$1*4',
'1==1',
'hacked=true',
'(1==1)&&(5==9)',
'pow(2,5)',
"file_put_contents('.htaccess','deny from all')"
);
echo '<table border="1">';
foreach($rules as $rule) {
  echo '<tr><td>' . $rule . '</td><td>';
  if (testRule($rule)) {
    echo '&nbsp;</td><td>Pass';
  } else {
    echo '&nbsp;</td><td>Fail';
  }
  echo '</td></tr>';
}
echo '</table>';
?>

Comments

  • ZootZoot Posts: 2,227
    edited 2011-02-28 06:56
    Speaking as a professional web developer and PHP programmer, your script will only allow injections IF your php config (php.ini or similar) allows for things like URL opens and if you have register globals on, etc. Turn all those off in php.ini. At that point, the only way to get outside values into your variables is for get/post/requests that you parse in the script, e.g.:
    if ( $_REQUEST['someVar'] == "1" ) {
      // do something
    }
    

    At this point, you should always treat $_REQUEST as "dangerous" and strip out anything that isn't needed. Really, in this context, the only thing that would have to be stripped out (if you are using "eval") is stuff like <? and quotes, as these could be used to insert some nasty code into an eval context. I generally use preg_replace to strip out anything that is unneeded for whatever is passed to the variable.

    It's also a good idea to make sure the value is a string (or number, or whatever) and not just whatever is passed from outside the script, e.g.
    $val = preg_replace("/[^0-9a-zA-Z\-_]/","","$_REQUEST[someVar]");
    // make sure $val is a string, then strip out junk
    
    
  • ZootZoot Posts: 2,227
    edited 2011-02-28 07:00
  • mctriviamctrivia Posts: 3,772
    edited 2011-02-28 07:49
    Zoot thanks for advice. The script I have put up is to filter the users input. I have not posted the entire code as it is very complex. I can't just use our preg replace as it strips characters I need. My script should check for the characters I believe would be dangerous and then filter to make sure they have no tried to use bad functions as it will go through a eval statement.
  • ZootZoot Posts: 2,227
    edited 2011-02-28 09:26
    preg replace as it strips characters I need

    It is a good practice. You can choose strip out ONLY "bad" characters or strings, e.g. php open/close code tags with preg_replace. This would still leave things intact, but strip out the nasties for evals. If you don't need single or double quotes, strip those out too (or escape them -- again, this will help with not allowing injected values to be parsed into fileopens, functions, etc., that a cracker could use to read your server files, upload their own malware scripts, etc.).

    *Any* variable that may be passed to the php script from the outside (generally via CGI) really *must* be pre-processed to prevent crackers from exploiting vulnerabilities. Obviously, some passed values can be more pre-processed than others. If you have long realtext strings, functions like html_entities is also a way to convert characters that could be used to inject PHP functions from the outside into "text".

    Regular expressions on a passed variable that has been typecast to a string is really the best way to go. You don't want to be checking for states on the string like
    if ( strtolower(substr(0,4,$someVar)) == "func" ) {
      // do something
    }
    

    unless $someVar has been pre-processed, because what if the visitor injected a value like
    script.php?someVar=eval(' ?>your site sucks eggs<?php ')
    
    

    If you have something like
    $someVar = html_entities("$_REQUEST[someVar]") ; // the quotes on the request var are crucial -- treats it as a string, not evaluated code
    
    

    Anyway, there are lots of ways to pre-process depending on what the variables to be passed look like. It can sometimes be helpful to split variables up for easier pre-processing and then do a little more work on the front-end side so that the visitor has the same experience.
    // if you know the value should be a whole number, make it a number after treating input as string
    $someNum = intval("$_REQUEST[someNum]");
    // above won't work above large values; see docs... so for really, really big integers:
    $someNum = preg_replace("/[^0-9]/","","$_REQUEST[someNum]");
    
    


    Last but not least, remember:

    - register globals off. This is very important. Some hosting setups have it on by default, some off. If you do not have access to your php.ini file, and you are hosted on a commercial setup, the hosting control panel may allow you to edit config items. If not, contact your hosting company.

    - url opens off (unless you absolutely require them for certain scripting purposes; not that uncommon really)
  • mctriviamctrivia Posts: 3,772
    edited 2011-02-28 16:48
    Thanks for all your generic sugestions on how to secure any code. This is not just any code though.
    if (eval('return (' . $rule . ');')) {
    

    is extremely powerful but extremely dangerous. It lets the user enter any php valid comparison and lets me determine if it is true or false.

    For example
    $rule='sin(deg2rad(60))<0.9';
    

    will result in a true

    the danger is if I do not restrict use the user can do all kinds of bad things.

    $userid="God";
    eval(inject code to upload a file or delete files)
    exit

    or the user could type something that is invalid and cause a php error.

    To solve these problems I have writen:
    check_syntax($code) - to see if the user has enterd a valid command
    breakBracket($code) - to break up the codes encapsulated brackets making it easy to find what functions the user is try to use
    $whitefunc - a list of safe functions the user can use
    testRule($rule) - to combine everything together and return a pass fail grade on whether the code can be executed safely.

    I believe that testRule will remove all hacking attempts but I am not sure so I would like user input if there is anything I have missed. The code at present:

    1) checks for any dangerous characters semicolan, single and double quotes. will add question mark
    2) check if user is trying to write to a variable instead of just doing comparison
    3) check it is valid syntax
    4) check that only functions in the safe list, numbers, brackets, commas, and math operators are used

    If any one test fails then all test after it do not run and a fail is returned. If all tests pass then a pass is returned.

    What i need to know is can anything pass the tests that is bad.
  • ZootZoot Posts: 2,227
    edited 2011-02-28 18:29
    No, your script is pretty open to injection, at least it seems to be. Not sure how you are passing $code to the func check_syntax, but right there $code will be evaluated in the context of your function, before it's even "checked" by your function. I would cast the user input to a string, preg_replace all single quotes, backslashes and <? and ?>. That would get you pretty safe before you run $code through any moves.
  • mctriviamctrivia Posts: 3,772
    edited 2011-02-28 19:08
    The check syntex is executed only after checking checking there are no single quotesm. Only testRule calls it.
  • mctriviamctrivia Posts: 3,772
    edited 2011-02-28 20:23
    Zoot wrote: »
    Not sure how you are passing $code to the func check_syntax, but right there $code will be evaluated in the context of your function, before it's even "checked" by your function..

    as for how i pass it runs through the following
    //check for ilegal characters ;'"
      [B]$good=(!(preg_match("/([;'\"\?])/",$rule)>0));[/B]
    
      //check for assingments
      if ($good) {
        $temp=preg_replace('/([=!]==)/','',$rule);
        $temp=preg_replace('/([=<>!]=)/','',$temp);
        $good=(!(preg_match("/(=)/",temp)>0));
      }
    
      //see if rule is valid
      if ($good) {
        $good=check_syntax('(' . $rule . ');');
      }
    
    

    As you can see I am disallowing the question mark sign now as it is not needed. I use preg match instead of replace because I don't want to filter it out I just want the test to fail if it is found period.
  • ZootZoot Posts: 2,227
    edited 2011-03-02 09:54
    My point is the *original* value that you pass to any of your checking function must itself be "quarantined" as much as possible -- by casting to a string, by escaping or stripping characters that lead to nasty evals, etc. If you html_ent or escape chars, and it's clean, you can always come back and re-escape them in a different context if you need those characters for parsing further down in your script.
Sign In or Register to comment.