Need PHP Hacker Help
mctrivia
Posts: 3,772
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
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.
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 ' </td><td>Pass'; } else { echo ' </td><td>Fail'; } echo '</td></tr>'; } echo '</table>'; ?>
Comments
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.
http://www.php.net/manual/en/security.variables.php
As will this...
http://www.php.net/manual/en/security.globals.php
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
unless $someVar has been pre-processed, because what if the visitor injected a value like
If you have something like
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.
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)
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
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.
as for how i pass it runs through the following
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.