Archives June 2020

Pulse Secure Client for Windows <9.1.6 TOCTOU Privilege Escalation (CVE-2020-13162)

In the midst of the coronavirus pandemic we have observed an incredible boost in the diffusion of configurations allowing people to work from home. Being able to quickly identify vulnerabilities in the components of these infrastructures has become, more than before, a priority for many businesses. So the lenient Red Timmy has thought: “it would be good to kill some 0day while we go through this hard time“.

After careful evaluation of the options available, we have decided to proceed with a deep inspection of the Pulse Secure VPN client. Why? Beyond the large installation base in the Fortune 500 market, there are plenty of medium-size companies out there adopting the Pulse Secure products. Try to imagine a business distributing laptops to its employees through which they can connect remotely, via VPN, to the employer’s infrastructure and comfortably work from home. Of course the operating system running on these laptops is hardened in order to disallow the installation of arbitrary software, disable the antivirus or the other monitoring/security agents and more in general to avoid the execution from employees of any actions that would normally require admin rights.

Now imagine an employee (might be a malicious insider) escalating to “NT_AUTHORITY\SYSTEM” in one of these laptops before or after having established a connection to the company VPN network, with security measures, configurations and software disabled or tampered, with the possibility to install any programs or hide hacking tools in the system without restrictions, with the aim to lower down the threat detection and identification capabilities of SOC. How bad would it be? This is what could have happened by exploiting the vulnerability on Pulse Secure client we are going to talk about today, before the vendor patched it. We have registered CVE-2020-13162 for that.

High-Level Overview and Impact

Pulse Secure Client for Windows suffers of a local privilege escalation vulnerability in the “PulseSecureService.exe” service. Exploiting this issue allows an attacker to trick “PulseSecureService.exe” into running an arbitrary Microsoft Installer executable (“.msi”) with SYSTEM privileges, granting them administrative rights.

The vulnerability lies in the “dsInstallerService” component, which provides non-administrative users the ability to install or update new components using installers provided by Pulse Secure. While “dsInstallerService” performs a signature verification on the content of the installer, it has been found that it’s possible to bypass the check providing the service with a legit Pulse Secure installer and swapping it with a malicious one after the verification (see Analysis paragraph below).

The vulnerability is a TOCTOU (time-of-check to time-of-use) and can be exploited reliably using an exclusive opportunistic lock.

This bug can be exploited in default configuration and the tester is not aware of any available configuration preventing exploitation. All the versions we have tested < 9.1R6 (including the branch 5.x as the version 5.3 R70 released on January 2017) have been successfully exploited during our tests. The checks were conducted both on a fresh install of Windows 10 1909 and Windows 8.1.

The bug was discovered by Giuseppe Calì (@gsepcali). A full exploit, which will be released in the near future, has been written by Marco Ortisi (more about the exploit in the notes below).


The server component of the Pulse Secure solution, Pulse Secure Connect, provides installers to deploy on client machines. Some of these, such as “Pulse Secure Installer Service (.exe)” and “Host Checker (.exe)“, can be installed without admin privileges (see Figure 1).

Figure 1: description of one of the observed installers provided in the PSC admin interface.

Despite stating that the packages “can be deployed with limited user privileges if a previous version of the Installer Service is running“, it has been found that a default install of Pulse Secure client allows to run the installers. The installers are available at “https://<pcs-host>/dana-admin/sysinfo/installers.cgi “.

Since these installers need to perform tasks requiring administrative right (such as creating files into the “Program Files (x86)” directory), we have decided to investigate how the corresponding non privileged processes are allowed to perform privileged tasks.

The installers are self-extracting executables (see Figure 2).

Figure 2: the extracted content of the “Pulse Secure Installer Service (.exe)” package.

Pulse Secure Installer Service (.exe)” works in the following way:

  1. Extracts its content to %TEMP%.
  1. Instructs “PulseSecureService.exe“, which runs as SYSTEM, to initiate the install process. This is done via a OLE interface exposed by the service.
  1. PulseSecureService.exe” verifies the signature of “VerifyAndInstall.exe” using the “CryptQueryObject()” WinAPI function.
  1. If the signature is valid, “PulseSecureService.exe” copies “VerifyAndInstall.exe” to “C:\Windows\Temp\PulseXXXXXXXX\“, where “XXXXXXXX” is a hex-encoded timestamp.
  1. PulseSecureService.exe” runs “VerifyAndInstall.exe“.
  1. VerifyAndInstall.exe” runs a verification routine on “%TEMP%\PulseSecureInstallerService.msi
  1. If the verification succeds, “PulseSecureInstallerService.msi” is copied to “C:\ProgramData\Pulse Secure\Installers\verified_PulseSecureInstallerService.msi“.
  2. PulseSecureInstallerService.msi” runs “C:\Windows\system32\msiexec.exe” as SYSTEM with “C:\ProgramData\Pulse Secure\Installers\verified_PulseSecureInstallerService.msi” as an argument and the installation starts.

The installation process can be observed using “Procmon” with the file names above as filters.

C:\ProgramData” is writable by non privileged users, that can create or modify files they own but not those created by other users.

In order to reproduce the issue and run code as SYSTEM, it is necessary to:

  1. Create an empty “C:\ProgramData\Pulse Secure\Installers\verified_PulseSecureInstallerService.msi
  1. Set an exclusive opportunistic lock on “C:\Windows\System32\msiexec.exe” (for example using this tool: with the command “SetOpLock.exe C:\Windows\System32\msiexec.exe x“).
  1. Start the legit installer downloaded from the “Pulse Secure Connect” appliance.
  1. When the oplock is triggered, swap “C:\ProgramData\Pulse Secure\Installers\verified_PulseSecureInstallerService.msi” with a malicious “.msi” file.
  1. Release the oplock.

As a result, the malicious “.msi” file is executed with SYSTEM privileges.

Detection guidance

In order to detect this issue, it is sufficient to look for a non-SYSTEM process creating or writing to “C:\ProgramData\Pulse Secure\Installers\“. As the original files are always created and written to by the PulseSecure service, non-privileged writes to this location are always non-standard behavior and a possible exploitation attempt.

Different installers install to different locations. Another file path to watch for non privileged writes is “C:\ Users\<user>\AppData\Roaming\Pulse Secure“.


We have developed two different exploits for this vulnerability. See one of them in action below.

However, we are not going to release the code immediately, but it will be published in our github soon. Why not now? Well, we have realized that is very difficult in practice, for Pulse Secure customers, to understand when the release of a new version of the VPN client provides a security bug fix or just a feature update. In fact, in the moment we write, both the release notes of the VPN client version 9.1R6 (the only one not affected) and the Security Advisories published in the PulseSecure website contain no mention to the fact that the previous versions of the product were affected by CVE-2020-13162.

The natural consequence of this reasoning is that there are very high chances that nobody has really updated the client with the latest version available because nobody is really aware of the fact that it provides a bug fix and not a feature update.

Update: 24 hours after we disclosed this vulnerability, Pulse Secure has released a security advisory related to it.

That’s all for today. Remember to follow us on twitter and while you are there connect to @gsepcali. More vulnerabilities and Red Team stories are coming…stay tuned!

Disclosure timeline

Vulnerability discovered: April 13th, 2020
Vendor contacted: April 15th, 2020
Vendor’s reply: April 17th, 2020
Patch released: May 22nd, 2020
Red Timmy Disclosure: June 16th, 2020
Security Advisory released by Pulse Secure: June 17th, 2020

Exploit release: date to be confirmed

How to hack a company by circumventing its WAF for fun and profit – part 3

One of the tools we are more proud of in absolute is Richsploit that we have released few months ago. It allows the exploitation of some EL injection and java deserialization vulnerabilities on multiple versions of RichFaces, an infamous Java library included with many JSF frameworks. Anyway the reason of our pride is not simply connected to the pleasure of being able to execute arbitrary code in a remote system. Also the good time spent to make Richsploit work in certain environments is something that we take into consideration and that is at the basis of the insecurity stories with WAF, and other security devices at layer 7, we are going to share with you today.

To scope or not to scope, that’s the problem

As part of our preparation process for a security test or exercise we normally go through a scoping document together with the customer in order to gain as much information as possible about the environment we will have to test. This is done because sometimes from the list of components, middleware solutions, libraries, etc… so gained, we are able to spot in advance elements useful for the definition of a potential attack path, which saves us a lot of time during the actual security test. The starting point of our story is exactly this. From the scoping document of one of our tests we have identified a component that used the Richfaces 3.x library known to be vulnerable, even though no public exploit existed for it. So, what does the little sympathetic Red Timmy normally do in these cases? Of course he installs that component and with it the exact version of the Richfaces vulnerable library in his local lab, in order to develop and test the exploit over there. Eventually that’s what we have done. Basically, two weeks before the official task’s start date, we had in our hand a full-fledged exploit!

Then it comes the day of the security test in the real customer environment. Same component…same version of Richfaces, but this time the target application is put at the very end of a chain of proxies, load balancers and firewalls of which we knew nothing about. And of course the exploit developed and tested in our lab refuses to work there…

An ASCII character will save us all

Immediately we have started to search the root cause for that, and here what we have discovered. Richfaces implements a custom base64 encoder that does something a normal base64 encoder would never dream to do: it produces in output an encoded object (a string to simplify…) containing one or more exclamation marks. Something like this:

To exploit the vulnerability, the malicious encoded object generated with the Richfaces custom base64 encoder must be passed to the server in the URI of an HTTP GET request, along with the ‘!’ symbol(s). Something like that:

However, for some reason the target application had no intention to serve requests coming with an exclamation mark in the path. Weird! In our local lab everything worked fine. To put it simple, something was preventing our payload to turn out to a beautiful RCE primitive. We had never hated the exclamation mark symbol so much before. A possible explanation for that was that one of the front-end network devices was unhappy with such type of requests. But honestly we will never know, as the case was not investigated at the end of the activity.

Meantime we noticed that the target application used a semicolon character to separate path and querystring, like this:;jsessionid=DC3DE8B0 [...]

According to RFC 3986a semicolon is often used to delimit parameters and parameter values”.

What if we put a semicolon ‘;’ character in front of the Richfaces encoded object as shown below?

  • The server interprets the encoded object (in purple) not as part of the path but of the query string where the symbol ! is instead allowed. So WAF, load balancers and proxies have nothing to complain.
  • The Richfaces base64 decoding will be still successful, because the semicolon is a non-base64 character, and according to RFC 2045 “Any characters outside of the base64 alphabet are to be ignored”.
  • Because of what the RFC above mandates, the Richfaces base64 decoder will not terminate or throw an error when encounters the semicolon, but skip over it and continue the decoding process as if the character is not there.
  • The decoded malicious payload is then correctly decoded and processed by Richfaces.
  • The payload is executed and RCE condition achieved. Victory!  \o/

The last resort

When one moves from exploiting Richfaces 3.x to Richfaces 4.x, the endpoint to shoot on is instead different compared to the one seen in the previous paragraph. With Richfaces 4.x it is something similar to:<malicious_encoded_payload>

The request can be sent either as GET or POST: Richfaces handles both methods.

One day the astute Red Timmy faced an unusual (until that moment) situation for him. The application under analysis presented the traces of some resources normally decorating the Richfaces front-end elements, but a F5 Big-IP device blocked any access to the “MediaOutputResource” component.

The device had the Application Security Manager (ASM) and Local Traffic Manager (LTM) modules enabled and a set of custom iRules deployed. The WAF policy was configured in blocking mode, meaning that an error message with a support ID was returned back in case of attack detection.

The custom rules checked that:

  1. the path of each HTTP request did not point to the URL of the “MediaOutputResource” component and the URI did not contain the “do” parameter.
  2. In case of POST request, in addition to the check on the path, the presence of the parameter “do” was verified in the request’s body and not in the URI.

Specifically the rule “1” was applicable to all HTTP methods, not only GET. If the checks were not passed, the request was blocked and an error message returned back to the client.

In addition, differently than how seen in another of our blog posts, no percent-decoding misconfigurations could be appreciated. Everything looked fine. With such a kind configuration in place nothing can go wrong, right?

However, at a certain point we noticed that our target application was exposing the servlet “auth”. It was reachable via POST and took in input the parameter “res” that in the request we intercepted with Burp was pointing to a URL inside the target domain.

For example if “res=https://someserver.somedomain/path?querystring” is provided in the request’s body, as consequence of that the servlet issues a subsequent request like this:

HEAD /path?querystring HTTP/1.1
Host: someserver.somedomain

Fundamentally, it sends out an HTTP HEAD request based on the content of the parameter “res”. It means we are totally in control of the path, the query string and the target server. We cannot control the HTTP method (all the requests are sent out with the HEAD method by default) but that’s not a problem, as Richfaces 4.x can be exploited through GET which is syntactically similar to HEAD. The only difference is the fact that with HEAD the HTTP body is not returned in the reply, but we will survive to that.

Honestly we are not even “totally” in control of the target server. There is a restriction based on the domain. We could not go outside of the domain we were targeting in that moment (somedomain in the example above). But that was not a problem too, as the Richfaces instance we wanted to exploit was just living into that domain.

Given these premises, what if we set the parameter “res” to the value “https://allowedHost.somedomain/rfRes/org.richfaces.resource.MediaOutputResource.faces?do=<malicious_encoded_payload>” ?

The request crosses the chain of load balancers, firewalls, proxies, etc… completely untouched. Why untouched? Because rule “1” and “2” are not applicable in this case. According to these rules:

  • The path must not point to the URL of MediaOutputResource -> indeed it is set to “/auth”.
  • The URI must not contain the “do” parameter -> in fact there is no “do” parameter.
  • In case of POST request (…and we have a match here) the “do” parameter must not be present in the request’s body. And indeed in the request body there is only the parameter “res”. The value “do” exists only as part of the content pointed to by the “res” parameter (and not as a parameter per se) which is not sufficient condition to block the request.

So, now that we are convinced the request passes through, let’s see what comes next. The request finally lands to the “auth” servlet in the web application server. Here the content of the “res” parameter is extracted and the subsequent HTTP request is issued:

HEAD /rfRes/org.richfaces.resource.MediaOutputResource.faces?do=<malicious_encoded_payload> HTTP/1.1
Host: allowedHost.somedomain

Due to the WAF configuration mentioned before, this request would be blocked *if* sent from the internet. Indeed the rule “1” would be triggered (the path points to the MediaOutputResource component and the “do” parameter is present in the URI now). However when a malicious request is reflected via SSRF, like in our case, it normally comes from inside the targeted network, ideally localhost if the SSRF endpoint shares the same vhost with the Richfaces component or even a different vhost but configured in the same server. In such cases the request does not cross the WAF(s), load balancers or other security appliances anymore, which are all bypassed (see the picture below).

Indeed after our payload has triggered the vulnerability and we have managed to compromise the target system, a grep for “” in the web application server’s log files has revealed the following thing:

In simple terms, the HEAD request to “/rfRes/org.richfaces.resource.MediaOutputResource.faces?do=<malicious_encoded_payload>” was clearly coming from localhost, where the vulnerable Richfaces component resided as well, without leaving the targeted system.

And that was another great victory. \o/ \o/

The bottom line

As the Red Timmy’s grandfather always said: “When you need to bypass WAF, the last resort is SSRF”.

These and other WAF policies and signatures bypass techniques (like shell globbing, undefined variables, self-reference folder, etc…) will be taught, along with additional updated content, in our Practical Web Application Hacking Advanced Training on 3-4 August. This year the course will be totally delivered in virtual format. Follow the news in the main page!

And don’t forget to follow us on twitter too: