Overview
WP Engine is a managed WordPress hosting platform. When it comes to security, WP Engine seems to offer a lot of protection. For example many attacks that would allow an attacker to compromise the server and install backdoors on the operating system level are stopped by the hardened configuration.
As a part of the Uber bug hunt in 2016 I had a look at the WP Engine platform which hosts Uber’s WordPress sites. I found that despite the special configuration it was still possible to escalate WordPress administrator account compromise to operating system level code execution. WP Engine has since since fixed the issues in 2016.
Details
Under default WordPress configuration XSS vulnerabilities can be exploited to achieve server-side code execution. Especially stored XSS’s in the Dashboard panels of various plugins are quite often susceptible to this. Some XSS’s have been found in the WordPress core too. A demo of such attack can be seen in this YouTube video.
One way to achieve server-side code execution is to inject JavaScript that uses the plugin or theme editor functions to insert malicious code in existing PHP files on the server. The injected script would run in a WordPress administrator’s web browser and send AJAX requests to the editor endpoints in the background.
From XSS to arbitrary PHP evaluation
Disabling the plugin and theme editors stops this particular attack. I found that on WP Engine, one possibility to achieve the same thing is to target the WP Engine control panel. The platform has a “HTML Post-Processing” function. Site administrators can specify regular expressions to perform pattern matching and replacing for all contents of the website.
For example, specifying /abc/ => “def” in the HTML Post-Processing option in WP Engine general settings would cause any occurrence of “abc” to be replaced with “def” anywhere on the site.
A malicious user who compromises the administrator account or is able to inject JavaScript in their web browser could add a replacement pattern such as /abc/e => error_log(“test”). The e modifier in the regular expression causes PHP to treat the replacement string as code to be evaluated for each occurrence.
Any time the site generates content containing “abc”, the error_log code would be executed. If the site doesn’t have that content, the attacker could trigger the code by e.g. entering “abc” in the site’s search box and hitting the search button.
In this way an XSS vulnerability could be escalated to arbitrary PHP evaluation. But as there is some hardening in place, it was still not entirely clear if this allowed arbitrary code execution on the operating system level. WP Engine has disabled most of PHP’s “dangerous” functions such as exec() and system() in php.ini.
From PHP to arbitrary code execution
After some experimenting I found a way to execute Linux binaries on the server. This happens by compiling a shared library, uploading it on the WP engine server, setting the LD_PRELOAD environment variable with the PHP putenv() function, and triggering a shared binary execution. One way to force the PHP interpreter to execute an external binary is to call the PHP mail() function which executes /usr/sbin/sendmail. The init function of the attacker-supplied library binary would be executed by the dynamical linker when preparing to execute the sendmail tool.
Example code:
<?php putenv("LD_PRELOAD=/nas/wp/www/sites/jouko/test1/libtest.so"); mail("jouko@iki.fi","hello","world"); ?>
A suitable library can be created with a command like
gcc -shared libtest.c -o libtest.so -fPIC -Wl,-init,libtest_init
The dynamic linker on the WP Engine server would call the C-language function called libtest_init() during sendmail startup.
Access to /server-status
In addition to code execution, it was possible for a WP Engine administrator to manipulate their own .htaccess file to enable access to the special /server-status path provided by the Apache mod_status module. Example .htaccess content:
<Files "stat"> SetHandler server-status </Files>
The server-status output contains sensitive information, for example IP addresses and URLs requested by users of other WordPress sites hosted on the same server.
Vendor response
The issues were reported to WP Engine in April 2016. They indicated that they started working on addressing the problems. I haven’t been kept up to date with the fixes, but in July 2016 WP Engine sent a notification to all customers who had installed software that used the putenv() function:
On July 20, 2016, we will be hardening the PHP function putenv based on industry best practices for all PHP applications.
You are receiving this message because we have detected the use of putenv on your WordPress install(s) / file path(s) listed below:
…
After Wednesday, July 20, 2016, you will no longer be able to set system-level environment variables using putenv. Any environment variables you set with putenv will be available in your WordPress site’s code as it normally would.
Bug bounty
The motivation for this investigation was the Uber bug bounty program. Under the core uber.com domain there were at least eight WordPress sites hosted on WP Engine, affected by these issues. I had reported many admin panel stored XSS’s and other bugs on those sites. They were mostly considered medium or low impact issues because the response team didn’t agree with me on the RCE impact. In Uber’s view it was impossible for an attacker to execute code on the servers because of WP Engine’s hardened setup and disabled plugin and theme editors.
This became a factor in most of my bug reports so I decided to rent a WP Engine instance on my own expense to check if the rationale was actually valid. It turns out it wasn’t, and it was possible to achieve server-side code execution with the previously reported bugs on many Uber servers.
I notified Uber of these problems in April 2016. The bug report remained under investigation for about a year. In April 2017 a bounty of $500 was awarded (this is currently the minimum payout of the program; at the time of reporting the policy page simply stated that the payout for medium issues like basic reflected XSS was $3,000, and $10,000 for critical issues like remote code execution). Investigating the bugs cost me about $100 in WP Engine charges of the testing period. Although half a dozen earlier bugs turned out to be remote code execution vulnerabilities after all, as I had originally reported, they were not re-evaluated.
Credits
The vulnerabilities were found by Jouko Pynnönen of Klikki Oy, Finland.