Many WordPress blogs at risk from image-based zero-day vulnerability

Many WordPress blogs at risk from image-based zero-day vulnerability

Bilocating technology blogger Mark Maunder – he claims to live in Seattle and Cape Town concurrently, though I suspect he means consecutively, and I’ll wager he wisely avoids winter in both of them – recently wrote about an intrusion to his WordPress site.

It turns out the backdoor was a previously-unexploited, or at least a previously-undocumented, flaw in a useful little WordPress addon, shared by many WordPress themes, called timthumb.

Timthumb is an 864-line PHP script which assists with automatic image resizing, thumbmailing and so forth. (It doesn’t squeeze the image manipulation code into those 864 lines, but uses the third-party GD library.)

If you run WordPress and you have a file named timthumb.php, sometimes renamed to thumb.php, in your installation, you may be at risk.

Tracking down the mechanism behind his intrusion, Maunder identified three main problems with timthumb.php: poor default settings; poor verification of input data; and poor choice of file permissions for temporary files.

By default, the vulnerable version of timthumb allowed images from external sites to be accessed from your server. The default list is probably unsurprising:

// external domains that are allowed to be displayed on your website
$allowedSites = array (
	'flickr.com',
	'picasa.com',
	'img.youtube.com',
	'upload.wikimedia.org',
);

But a better default would be an empty list, so that users who want to allow external files to be sourced by their own servers need to take steps to enable that capability.

If you use WordPress and timthumb and you don’t need this capability, Maunder suggests simply editing the timthumb.php code to say $allowedSites = array(); in order to prevent remote file trickery.

Secondly, timthumb.php checked the sanity of remote URLs – to verify they really were in the list of allowed sites – by looking for the permitted domains somewhere in the hostname part of the URL, rather than making sure they were the hostname part:

$isAllowedSite = false;
foreach ($allowedSites as $site) {
        if (strpos (strtolower ($url_info['host']), $site) !== false) {
                $isAllowedSite = true;
        }
}

This code meant that a dodgy website name such as picasa.com.badsite.example would pass the test, simply because it contains the string picasa.com. Clearly, that is not what was intended.

Lastly, timthumb.php stored the files it generated in a cache directory which is inside the PHP directory tree. This is bad, because files generated from untrusted external content – files only ever intended to be displayed – needlessly became executable.

So if the cached file isn’t an innocent image, but a remote access PHP Trojan (in Maunder’s case, the attacker used a malicious remote console tool called Alucar), you’re owned.

If you are a web developer:

* Don’t trust externally-sourced content by default. Force your users to think about what they really want.

* Check, test, check, test, check and test again your URL sanitisation code. Build a decent test suite and verify your code against it every time you release an update.

* Keep files which are only ever supposed to be used as data – especially remotely-sourced files – outside the directory tree where your server-side executable code lives.

If you run a WordPress installation:

Check if any of the blogs you host use timthumb.php, and upgrade to the latest version. The dodgy strpos above has been replaced with a tighter match based on a regular expression, like this:

$isAllowedSite = false;
foreach ($allowedSites as $site) {
	if (preg_match ('/(?:^|\.)' . $site . '$/i', $url_info['host'])) {
		$isAllowedSite = true;
	}
}

This doesn’t fix all of the issues Maunder describes, but it’s better than having a known hole in your site.

Many thanks to Mr Maunder for turning an attack on his site into a training tool to help the rest of us avoid a similar problem!



PS. WordPress.com VIP, which hosts Naked Security, doesn’t use timthumb.