When Assetnote Continuous Security (CS) monitors your attack surface, one of the things it looks for are instances of WebPageTest. WebPageTest is a website performance testing tool that lets you test network related metrics for any given URL/host.
Although basic authentication can be enabled by modifying the settings.ini file, and is recommended to prevent any anonymous access. Most deployments of WebPageTest that Assetnote CS identifies are unauthenticated, and the array of testing tools provided by WebPageTest can be used offensively to gain access to internal resources by server-side request forgery (commonly known as SSRF, but for WebPageTest, it is a feature).
In November 2017, Assetnote CS discovered the following assets on Mozilla’s AWS environment:
- wpt-vpn.stage.mozaws.net
- wpt1.dev.mozaws.net
Both of these were instances of WebPageTest did not require authentication, and it was the first time Assetnote CS had detected it for a bug bounty. Working with Mathias, we audited the source code, and in just a few hours we were able to create an attack chain that led to remote-code execution.
While it was a zero day at the time of discovery, we worked with the Mozilla and WebPageTest team on getting the vulnerability fixed upstream. The commit which patches the bugs outlined in this blog post was pushed in this commit, on the 17th of January, 2018.
The first thing in the codebase that caught our attention was the ability to upload and extract arbitrary Zip files via /www/work/workdone.php
. This script contained some logic to restrict access from sources other than 127.0.0.1, as seen from the code snippet below:
...
!strcmp($_SERVER['REMOTE_ADDR'], "127.0.0.1")
...
We’ll come back to that later.
In the same file, we found another potential vector – logic to upload an arbitrary Zip and have it extracted to a known location:
Lines 133 – 136: /www/work/workdone.php
if (isset($_FILES['file']['tmp_name'])) {
ExtractZipFile($_FILES['file']['tmp_name'], $testPath);
CompressTextFiles($testPath);
}
If we could spoof our IP to come from 127.0.0.1, it seems like we could get code execution through this vector.
However, we found that it’s not as straightforward as we thought it was, due to Line 321 in /www/work/workdone.php:
SecureDir($testPath);
The logic of the SecureDir
function can be found on Lines 2322 – 2347 in /www/common_lib.inc:
/**
* Make sure there are no risky files in the given directory and make everything no-execute
*
* @param mixed $path
*/
function SecureDir($path) {
$files = scandir($path);
foreach ($files as $file) {
$filepath = "$path/$file";
if (is_file($filepath)) {
$parts = pathinfo($file);
$ext = strtolower($parts['extension']);
if (strpos($ext, 'php') === false &&
strpos($ext, 'pl') === false &&
strpos($ext, 'py') === false &&
strpos($ext, 'cgi') === false &&
strpos($ext, 'asp') === false &&
strpos($ext, 'js') === false &&
strpos($ext, 'rb') === false &&
strpos($ext, 'htaccess') === false &&
strpos($ext, 'jar') === false) {
@chmod($filepath, 0666);
} else {
@chmod($filepath, 0666); // just in case the unlink fails for some reason
unlink($filepath);
}
} elseif ($file != '.' && $file != '..' && is_dir($filepath)) {
SecureDir($filepath);
}
}
}
Since the SecureDir function occurs later during the code flow, there is an exploitable race condition where the PHP files that are extracted to the webserver were accessible for a short period of time before being deleted.
The first pre-requesite of the chain was rather easy, as a valid test ID was obtained by running a Traceroute on https://google.com
through the WebPageTest interface on wpt-vpn.stage.mozaws.net
:
Running a traceroute using WebPageTest |
After running the traceroute, WebPageTest redirected us to a URL that contained the test ID used in later steps:
- http://wpt-vpn.stage.mozaws.net/result/171124_GW_9/
But we still needed to somehow spoof that we are 127.0.0.1
in order to access the vulnerable functions in this script.
We were able to meet this condition by exploiting the following logic:
Line 70: /www/common.inc
if (isset($_SERVER["HTTP_FASTLY_CLIENT_IP"]))
$_SERVER["REMOTE_ADDR"] = $_SERVER["HTTP_FASTLY_CLIENT_IP"];
This allowed us as remote users to arbitrarily set $_SERVER["REMOTE_ADDR"]
by sending a FASTLY-CLIENT-IP
request header set to 127.0.0.1
.
Combining all of these elements together, we were able to set up two Burp Intruder attacks to finally get code execution.
One Burp Intruder attack was used to upload a malicious Zip file, and another attempted to access the extracted PHP file, while it existed on the system. Our solution to exploiting the race condition at the time was to simply up Burp Intruder’s threads to ~200.
Today, using tools such as Turbo Intruder, due to the speed of the requests being sent, it’s possible to make this exploit much more reliable.
We were able to use this technique to achieve code execution on Mozilla as seen in the screenshot below:
phpinfo() output from wpt-vpn.stage.mozaws.net |
The Bugzilla report in which we first reported this vulnerability is now public, and can be viewied here. The report contains thorough reproduction steps that should be sufficient for testers wishing to recreate these bugs.
We were awarded $500 as a part of Mozilla’s bug bounty program.