This backdoor was installed through a zero-day exploit of timthumb.php a common tool used by many Word Press templates. It was patched a long time ago but there are older versions out there and people are still hunting for them. This site owner didn't bother updating anything because "it might break". Ok, fair enough.
I found one instance of weird stuff in the logs going to this file. How the injection worked is the default configuration of timthumb.php which many themes use allow files to be remotely loaded and resized from the following domains (from the old insecure version of timthumb.php:
Code: Select all
$ allowedSites = array (
'flickr.com',
'staticflickr.com',
'picasa.com',
'img.youtube.com',
'upload.wikimedia.org',
'photobucket.com',
'imgur.com',
'imageshack.us',
'tinypic.com',
);
Where:
Code: Select all
foreach ($allowedSites as $site) {
if(strpos (strtolower ($url_info['host']), $site) !== false) {
$isAllowedSite = true;
}
}
The $url_info['host] is pulled from $_SERVER['HTTP_HOST']. This is the one parameter that can be spoofed by injecting interesting things in the host request header and even though that spoofed string doesn't match an actual host on the server, chances are the server will serve up pages for the default host or first virtual host and pass that bogus host header to anything running on that site.
This makes $url_info['host'] suspect. The problem is the way the developer checks which domain he’s fetching from. He uses the PHP
function and if the domain string appears anywhere in the hostname, the program will allow that file to be fetched. So easy to make it think you're a trusted allowed site and then upload your shell script. In general HTTP_HOST is not secure from tampering and in this case, the site was compromised by it.
Lesson here: don't trust external data! Note the patched version among other things looks like now:
Code: Select all
if ((strtolower(substr($this->url['host'],-strlen($site)-1)) === strtolower(".$site")) || (strtolower($this->url['host'])===strtolower($site)))
The attacker faked a HTTP_HOST header and got the template to to include a remote PHP file instead of an image and save it on the server.
Boom! They're in. So all they have to do is put in yoursite.com/path/to/shell/attack.php and you're p0wnd.
How was it found? Well fortunately there are sites that check other sites for malicious code. So when the back door was installed, it wasn't enough just to own the site (because they probably found nothing interesting to steal), they started sticking malicious javascripts and pharmaceutical spam (hard to believe the drug companies still think this is a good thing to be paying for spam hackers these days).
The site was flagged by browsers as malware so someone asked for help. So I got SSH access and looked around at the tampered files. Things that stuck out were 3 or 4 template files that had different dates than all the others. These template file had obviously been manipulated from the inside because the javascript was inserted in the templates right before the </head> and the spam content was inserted right after the <body> tags.
What was interesting about this spam injection is the javascript turned off the visibility of the spam content so the owner of the site and the users most likely would never see it (unless they are running with javascript off). Only bots who index the page would pick all that spam stuff about drugs, etc. I guess the term for this type of spam is SEO Spam where users don't see it but the search bots do.
I also found the injected code had be placed there almost a year and a half ago (Feb. 2012)! That back door was open a long time. These template files were also easy to spot because they had all been modified shortly after the backdoor tools had be inserted.
So how did I find the back door? That took about 30 seconds with grep. Using SSH:
[text]grep -r eval\(base64 *[/text] quickly listed any suspicious activity. I found no less than 2 of the 4 domains had backdoors installed.
Other backdoors don't necessarily need base64 encoding either, so another check with grep and a careful sifting of the results revealed nothing malicious:
[text]grep -r eval\( *[/text]
An idea occurred to me that a less obvious way to hide a backdoor would be to avoid exec and base64 junk and leverage include() and php://input to allow the attacker to execute arbitrary PHP code on the infected server. But this is hard to do through a header injection and would require more steps of uploading/deleting.
Let's look at the back door. It was in a file called wp-admin/images/wp-canto-impart.php (Odd to see php scripts in an images directory, no?)
It looks like this:
Code: Select all
<?php eval(base64_decode("JGs9MTQzOyRtPWV4cGxvZGUoIjsiLCIyM...on and on and on...”);
?>
I changed this to:
Code: Select all
var_dump(base64_decode("JGs9MTQzOyRtPWV4cGxvZGUoIjsiLCIyM....blah blah”);
I was surprised to get something still cryptic. I guess this is an attempt to avoid automatic detection.
Code: Select all
$k=143;
$m=explode(";","234;253;253;224;253;208; on and on and on for 1,000's of bytes.....;");$z="";
foreach($m as $v) if ($v!="")$z.=chr($v^$k);
eval($z);
I was thinking this is looking interesting, but then I decoded it saw the first two lines:
Code: Select all
error_reporting(E_ERROR | E_WARNING | E_PARSE);
ini_set('display_errors', "0");
This doesn't seem so clever. Why enable errors then turn them off? The code continues by looking for a posted value ($_POST[“p”]) and setting this up as a cookie. Then if it checks if the md5 of this value is NOT "8b291cbed412718742b370a46b95ceb4" and presents a form (which posts the “p” value via a 50 char text box and a “check” button) and stops. I made a few guesses at what hashes to this value, but I didn't have any luck. Perhaps a dictionary search might reveal something.
Assuming you get past the “check” form. The script presents a new form and supports the following commands from $_POST[“action”]: upload, sql, runphp. Upload allows anything to be uploaded remotely. SQL is a to hack at the database. What's interesting is the connection info is also pulled remotely so it looks like there is another automated tool out there designed to access these backdoors:
Code: Select all
$lnk = mysql_connect($_POST["server"], $_POST["user"], $_POST["pass"]) or die ('Not connected : ' . mysql_error());
I see they aren't using mysqli so this must be a pretty old backdoor right?
And the last action 'runphp' allows an exec() to execute a remote command to php.
So far pretty basic. Then it gets more interesting with the $_POST["cmd"] values if you don't send an “action”. It checks for disabled functions then it works through a variety of options to send a shell commands (via another form that uses javascript to base64 encode/decode messages) (notice all the message suppression):
I've removed most of the code to just show some of the things it tries:
Code: Select all
@exec($cmd, $result);
@system($cmd);
@passthru($cmd);
elseif(is_resource($fp = @popen($cmd, "r"))) {
$result = "";
while (!feof($fp)) {
$result .= @fread($fp, 1024);
}
@pclose($fp);
I've never seen a resource piped through like that.
This backdoor obviously is also designed to interface with some automated tools using POST. This part of the code looks like it is used manually to poke around in the system, probably try to find some of your database access passwords, maybe user information, and then exploit it.
It was interesting that both backdoors that were installed used different filenames, probably pulled from a dictionary, but the code was the same even though they were installed months apart. Whoever did it must not have bothered to explore the system enough to discover the other sub-domains which would have only taken about 3 minutes to locate.
It would have been interesting to make a honey-pot version of this script and replace it. But the site owner wasn't interested and wanted it gone.