Hacking 27% of the Web via WordPress Auto-Update
At Wordfence, we continually look for security vulnerabilities in the third party plugins and themes that are widely used by the WordPress community. In addition to this research, we regularly examine WordPress core and the related wordpress.org systems. Recently we discovered a major vulnerability that could have caused a mass compromise of the majority of WordPress sites.
The vulnerability we describe below may have allowed an attacker to use the WordPress auto-update function, which is turned on by default, to deploy malware to up to 27% of the Web at once.
Choosing the most damaging target to attack
The server api.wordpress.org (or servers) has an important role in the WordPress ecosystem: it releases automatic updates for WordPress websites. Every WordPress installation makes a request to this server about once an hour to check for plugin, theme, or WordPress core updates. The response from this server contains information about any newer versions that may be available, including if the plugin, theme or core needs to be updated automatically. It also includes a URL to download and install the updated software.
Compromising this server could allow an attacker to supply their own URL to download and install software to WordPress websites, automatically. This provides a way for an attacker to mass-compromise WordPress websites through the auto-update mechanism supplied by api.wordpress.org. This is all possible because WordPress itself provides no signature verification of the software being installed. It will trust any URL and any package that is supplied by api.wordpress.org.
WordPress powers approximately 27% of all websites on the Internet. According to the WordPress documentation: “By default, every site has automatic updates enabled for minor core releases and translation files.”. By compromising api.wordpress.org, an attacker could conceivably compromise more than a quarter of the websites worldwide in one stroke.
Below we describe the technical details of a serious security vulnerability that we uncovered earlier this year that could compromise api.wordpress.org. We reported this vulnerability to the WordPress team via HackerOne. They fixed the vulnerability within a few hours of acknowledging the report. They have also awarded Wordfence lead developer Matt Barry a bounty for discovering and reporting it.
Technical Details of the api.wordpress.org vulnerability
api.wordpress.org has a GitHub webhook that allows WordPress core developers to sync their code to the wordpress.org SVN repository. This allows them to use GitHub as their source code repository. Then, when they commit a change to GitHub it will reach out and hit a URL on api.wordpress.org which then triggers a process on api.wordpress.org that brings down the latest code that was just added to GitHub.
The URL that GitHub contacts on api.wordpress.org is called a ‘webhook’ and is written in PHP. The PHP for this webhook is open source and can be found in this repository. We analyzed this code and found a vulnerability that could allow an attacker to execute their own code on api.wordpress.org and gain access to api.wordpress.org. This is called a remote code execution vulnerability or RCE.
When a request arrives at api.wordpress.org, presumably from GitHub, the api.wordpress.org webhook verifies that it is in fact GitHub making the request by using a shared secret and hashing algorithm. The way this works is that, as GitHub is about to send JSON data, it combines the data it’s about to send with a secret value that has been pre-shared with api.wordpress.org. It then hashes the combination and it sends that hash along with the JSON data to api.wordpress.org.
When api.wordpress.org receives the request, it takes the JSON data, also combines it with the shared secret and computes a hash of its own. If its hash matches the hash that GitHub just sent, then GitHub must know the shared secret and that proves that GitHub is allowed to make the request it’s making.
GitHub uses SHA1 to generate the hash and supplies the signature in a header: X-Hub-Signature: sha1={hash}
. The webhook extracts both the algorithm, in this case ‘sha1’, and the hash to verify the signature. The vulnerability here lies in the fact the code will use the hash function supplied by the client, normally github. That means that, whether it’s GitHub or an attacker hitting the webhook, they get to specify which hashing algorithm is used to verify the message authenticity, as you can see in the code below.
1 |
function verify_github_signature() { |
2 |
if ( empty ( $_SERVER [ 'HTTP_X_HUB_SIGNATURE' ] ) ) |
5 |
list( $algo , $hash ) = explode ( '=' , $_SERVER [ 'HTTP_X_HUB_SIGNATURE' ], 2 ); |
8 |
$hmac = hash_hmac( $algo , file_get_contents ( 'php://input' ), FEATURE_PLUGIN_GH_SYNC_SECRET ); |
10 |
return $hash === $hmac ; |
If we can bypass the webhook authentication mechanism, there is a POST parameter for the GitHub project URL that is passed unescaped to shell_exec
which allows us to execute shell commands on api.wordpress.org. This allows us to compromise the server. You can see the shell_exec
call in the code sample below:
1 |
$repo_name = $_POST [ 'repository' ][ 'full_name' ]; |
2 |
$repo_url = $_POST [ 'repository' ][ 'git_url' ]; |
4 |
if ( ! $this ->verify_valid_plugin( $repo_name ) ) { |
5 |
die ( 'Sorry, This Github repo is not configured for WordPress.org Plugins SVN Github Sync. Please contact us.' ); |
8 |
$svn_directory = $this ->whitelisted_repos[ $repo_name ]; |
10 |
$this ->process_github_to_svn( $repo_url , $svn_directory ); |
1 |
function process_github_to_svn( $github_url , $svn_directory ) { |
3 |
putenv( 'PHP_SVN_USER=' . FEATURE_PLUGIN_GH_SYNC_USER ); |
4 |
putenv( 'PHP_SVN_PASSWORD=' . FEATURE_PLUGIN_GH_SYNC_PASS ); |
6 |
echo shell_exec( __DIR__ . "/feature-plugins.sh $github_url $svn_directory 2>&1" ); |
8 |
putenv( 'PHP_SVN_USER' ); |
9 |
putenv( 'PHP_SVN_PASSWORD' ); |
The challenge here is to somehow fool the webhook into thinking that we know the shared secret that GitHub knows. That means that we need to send a hash with our message that ‘checks out’. In other words it appears to be a hash of the message we’re sending and the secret value that only api.wordpress.org and GitHub know – the shared secret.
As we pointed out above, the webhook lets us choose our own hashing algorithm. PHP provides a number of non-cryptographically secure hashing functions like crc32
, fnv32
and adler32
, which generate a 32bit hash vs the expected 160 bit hash generated by SHA1
. These hashing functions are checksums which are designed to catch data transmission errors and be highly performant with large inputs. They are not designed to provide security.
If we can find a weak enough hashing algorithm, it lets us brute force attack the webhook. We just need to send a series of hashes, trying to guess the hash value of the shared secret and the data we’re sending, until we get it right and api.wordpress.org allows the request.
By using any one of these weak hashing algorithms, we reduce the brute force space, or number of guesses we have to make, significantly (2^160 down to 2^32). But it is still not feasible to launch this attack over the network to api.wordpress.org without it being incredibly noisy as we make a large number of guesses.
Of these weak algorithms, the one that stood out the most was adler32
, which is actually two 16 bit hashing functions with their outputs concatenated together. It also has a known implementation flaw when used on short messages. When combined with PHP’s hash_hmac
function, the second round of hashing will only ever pass 68 bytes of data to adler32
which severely limits the amount of possible hashes that can be generated given any input at the start of the hashing process.
Not only are the total number of hashes limited, but there’s also significant non-uniformity in the hash space. This results in many hashes being the same even though they were supplied with different inputs. The distribution of possible checksum values are similar to rolling dice where 7 is the most likely outcome (the median value), and the probability of rolling any value in that range would work its way out from the median value (6 and 8 would have the next highest probability, and on it goes to 2 and 12).
The proof of concept supplied in the report utilizes the non-uniformity by creating a profile of most common significant bytes in each 16 bit hash generated. Using this, we were able to reduce the amount of requests from 2^32 to approximately 100,000 to 400,000 based on our tests with randomly generated keys.
This is a far more manageable number of guesses that we would need to send to the webhook on api.wordpress.org which could be made over the course of a few hours. Once the webhook allows the request, the attack executes a shell command on api.wordpress.org which gives us access to the underlying operating system and api.wordpress.org is compromised.
From there an attacker could conceivably create their own update for all WordPress websites and distribute a backdoor and other malicious code to more than one quarter of the Web. They would also be able to disable subsequent auto-updates so that the WordPress team would lose the ability to deploy a fix to affected websites.
The CVSS vulnerability score for this vulnerability is:
Are You at Risk?
We confidentially reported this vulnerability on September 2nd to Automattic and they pushed a fix to the code repository on September 7th. Presumably the same fix had been deployed to production before then.
We still consider api.wordpress.org a single point of failure when distributing WordPress core, plugins and theme updates. We have made attempts to start a conversation with members of Automattic’s security team about improving the security posture of the automatic update system, but we have not yet received a response.
Starting three years ago, there has been some discussion about providing an authentication mechanism for the code that the WordPress servers distribute. So far there has been no progress on this. The issue came up again this week on a security mailing list.
Should api.wordpress.org or a similarly critical server be compromised, it is possible that an attacker could compromise WordPress sites en-masse across the web in a very short amount of time and simultaneously remove the ability for the WordPress/Automattic security team to push out a security update or a fix.
This would clearly be a disaster for the Web and the online community. The phrase “too big to fail” comes to mind. With 27% market share, WordPress powers over a quarter of all websites. A failure of this magnitude would be catastrophic for the Web. Furthermore, it would provide a massive attack platform for the attacker, who would control millions of web hosting accounts from which they could launch further attacks.
Disclosure Timeline
- 2016-09-02 21:08 (-0400) – Initial report submitted to Automattic via Hackerone.
- 2016-09-06 19:48 (-0400) – Automattic acknowledges the report.
- 2016-09-07 02:02 (-0400) – A fix for the vulnerability is pushed to the repository.
- 2016-09-21 17:17 (-0400) – The report is resolved and closed.
- 2016-10-29 05:21 (-0400) – Bounty is awarded by Automattic.
- 2016-11-22 – Public disclosure.
Credits
We would like to congratulate Lead Wordfence Developer Matt Barry for this spectacular research. Matt tackled this project in his spare time and handled the disclosure of this via HackerOne to the Automattic team. Matt is also the technical author of this post with some minor assistance from the rest of the Wordfence team.