Exploiting GlobalProtect for Privilege Escalation, Part Two: Linux and macOS

open padlock over hands on a keyboard

This is the second blog in a two-part series covering the exploitation of the Palo Alto Networks GlobalProtect VPN client running on Linux and macOS. The first blog covered this exploitation on Windows.

To recap, the CrowdStrike® Intelligence Advanced Research Team discovered two distinct vulnerabilities in the Windows, Linux and macOS versions of the Palo Alto Networks GlobalProtect VPN client (CVE-2019-17435, CVE-2019-17436). The vulnerabilities allowed unprivileged users to reliably escalate to root or SYSTEM on machines where the GlobalProtect software is used. Fixed versions were released on October 15, 2019, by Palo Alto Networks. We would like to thank Palo Alto Networks for handling and addressing the reported issues in a timely and professional manner.

Quick Facts

Affected Vendor: Palo Alto Networks
Affected Software: GlobalProtect for Linux (verified on Ubuntu 18.04.2 LTS)
                                   GlobalProtect for macOS (verified on Mojave version 10.14.5)
Affected Version: 5.0.4 (and earlier), 4.1.12 (and earlier)
Fixed Version: 5.0.5, 4.1.13
Vulnerability Type: Arbitrary Privileged File Write
Estimated Risk: High (Local Privilege Escalation to UID 0)
Identifiers: CVE-2019-17436 / GPC-8945 / PAN-SA-2019-0037
Vendor Reference: https://security.paloaltonetworks.com/CVE-2019-17436

The CVE-2019-17436 Vulnerability

The Linux GlobalProtect client consists of three executable files:

  • PanGPS: The PanGPS daemon is started once at boot time. PanGPS is responsible for negotiating VPN connections, and it configures network devices, routes, etc. It is started as the user root. The daemon listens for TCP connections on 127.0.0.1:4767.
  • PanGPA: The PanGPA daemon is automatically started once per log-in session and runs with the privileges of the logged-in user. It relays commands and responses between globalprotect and PanGPS via a TCP connection to 127.0.0.1:4767.
  • globalprotect: This executable implements a command-line interface (CLI). It is used by unprivileged users to interface with the other services (e.g., to trigger connecting to or disconnecting from the VPN, retrieve VPN status, etc.).

Configuration Files

It was observed that, under certain conditions, PanGPS for Linux creates root-owned files in the .GlobalProtect directory that resides inside the home directory of the logged-in user:

screenshot of directory

These files are created on at least two occasions:

  • If the configuration retrieval step during VPN connection negotiation fails, PanGPS tries to restore the file PanPortalCfg_<hash>.dat from the default configuration file PanPortalCfg.dat.
  • If the configuration retrieval is successful, the configuration is written to PanPortalCfg_<hash>.dat. Furthermore, the files {PanPCD,PanPUAC}_<hash>.dat are written as well.

Analysis of PanGPS for Linux revealed that the <hash> token represents the hexadecimal MD5 digest computed from the ASCII-encoded string <portal>_<username> and that the *.dat files are all encrypted with AES-256-CBC, using a fixed key and initialization vector:

screenshot of code

During our testing, PanPCD_* contained a single AES-encrypted null byte. PanPUAC_* contained an AES-encrypted authentication cookie received during negotiation. PanPortalCfg_* contained an AES-encrypted XML document that resembles a portal configuration file. Further analysis of PanGPS confirmed that the *.dat files are accessed in such a way that symbolic links are resolved, and the files pointed to are truncated before they are overwritten.It is therefore possible to create and/or overwrite arbitrary files with root permissions by replacing the *.dat files with symbolic links and causing PanGPS to attempt to write to them. However, in order to leverage the full potential of this vulnerability, one also needs to be able to control the data that gets written into these *.dat files.

Exploiting GlobalProtect on Linux

To exploit this behavior for local privilege escalation (LPE), we focused on the restoration of PanPortalCfg_<hash>.dat after a failed VPN connection attempt. We found that this route would be most effective as it does not require any network connectivity or interacting with a VPN server. After evaluating potential targets that could be overwritten, the file /etc/ld.so.preload, which is interpreted by the dynamic linker, was chosen. This file contains a newline-separated list of paths to shared objects that will be preloaded into any newly created process of a dynamically linked executable. This includes processes that are created from executable files with the SUID bit set. Furthermore, the dynamic linker is rather forgiving when parsing ld.so.preload files that contain only a single valid path that is embedded within seemingly invalid binary data.

Overwriting ld.so.preload

During our testing, we used the portal 127.0.0.1 and the username johndoe. Thus, the <hash> that is part of the configuration file names can be computed as follows:

$ echo -n "127.0.0.1_johndoe" | md5sum
1662c17069ca30beb328f3ccdffe14fe  -

When triggering a failing VPN connection to 127.0.0.1 as user johndoe, PanGPS will attempt to restore the portal configuration file PanPortalCfg_1662c17069ca30beb328f3ccdffe14fe.dat from the default file PanPortalCfg.dat. Therefore, we created a symlink named PanPortalCfg_1662c17069ca30beb328f3ccdffe14fe.dat that points to /etc/ld.so.preload in advance, which detours this write operation, and also created a file PanPortalCfg.dat containing the data that we would like to be written:

$ ln -s /etc/ld.so.preload .GlobalProtect/PanPortalCfg_1662c17069ca30beb328f3ccdffe14fe.dat
$ echo "hello world!" > .GlobalProtect/PanPortalCfg.dat
$ ls -la ~/.GlobalProtect/
total 240
drwxrwxr-x  2 x    x      4096 Jul 15 04:19 .
drwxr-xr-x 25 x    x      4096 Jul 15 03:32 ..
-rw-rw-r--  1 x    x    196869 Jul 15 03:32 PanGPA.log
-rw-rw-r--  1 x    x     12850 Jul 15 03:05 PanGPI.log
[...]
lrwxrwxrwx  1 x    x        18 Jul 15 04:19 PanPortalCfg_1662c17069ca30beb328f3ccdffe14fe.dat -> /etc/ld.so.preload
-rw-rw-r--  1 x    x        13 Jul 15 04:19 PanPortalCfg.dat
[...]

Next, a VPN connection attempt is triggered using the aforementioned portal address and username:

$ globalprotect connect -p 127.0.0.1 -u johndoe

However, this did not yet yield the desired result — in other words, the file /etc/ld.so.preload was not created. The log file PanGPS.log shows that there was a problem with decrypting the default configuration file:

$ tail -n 30 /opt/paloaltonetworks/globalprotect/PanGPS.log
[...]
pan_read_text_from_file(): Failed to decrypt file /home/user/.GlobalProtect/PanPortalCfg.dat
cannot restore last portal config from file /home/user/.GlobalProtect/PanPortalCfg.dat.
[...]

Further analysis revealed that the PanGPS daemon verifies that the decrypted content of PanPortalCfg.dat is indeed a semi-valid XML-based portal configuration file before overwriting the destination file. Knowing the crypto scheme (see above) that is used for the *.dat files, it is possible to write a valid, encrypted configuration into PanPortalCfg.dat. Then, when trying to bring up a VPN connection again, the encrypted content from PanPortalCfg.dat does get copied to /etc/ld.so.preload:

$ ls -la /etc/ld.so.preload
ERROR: ld.so: object '-º0´MëöS' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
ERROR: ld.so: object '9¼IJ·m?窆' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
-rw-r--r-- 1 root root 7728 Jul 15 04:50 /etc/ld.so.preload

As evident from the lines starting with “ERROR:” the dynamic linker now tries to interpret /etc/ld.so.preload when the ls command is executed. As the file only contains AES-encrypted data, no meaningful paths to shared objects can be extracted by the dynamic linker.

In summary, the following conditions must be fulfilled to write meaningful content to /etc/ld.so.preload:

  • PanPortalCfg.dat must be encrypted according to the crypto scheme described earlier.
  • The plaintext of PanPortalCfg.dat must be an XML document that is parsable by PanGPS as a portal configuration file. Our research shows that it does not necessarily have to contain valid XML, but it must at least contain a minimal amount of configuration-specific tags.
  • The ciphertext of PanPortalCfg.dat gets copied to /etc/ld.so.preload. Therefore, the ciphertext must be parsable by the dynamic linker, and it must contain the path to an attacker-controlled shared object. Of course, any crafted ciphertext must decrypt to a plaintext XML document that PanGPS can successfully parse.

Crafting Configuration Files

In order to fulfill these requirements, we first focused on crafting an XML document, and then we determined how to insert a ciphertext block that contains the path to a shared object file without breaking any of the requirements. The initial test used a real portal configuration file, which was approximately 8 KB in size. By successively removing tags, encrypting the resulting XML and letting PanGPS decide whether the presented configuration is still acceptable, it was determined that the simplest XML configuration that PanGPS would still accept as valid is the following:

<?xml version="1.0" encoding="UTF-8" ?>
<policy>
    <gateways></gateways>
    <agent-ui></agent-ui>
</policy>

For encryption, PanGPS uses the AES block cipher. The AES algorithm has a block size of 16 bytes, meaning that any attempts to influence the ciphertext are limited to changing one or more full 16-byte blocks. The XML declaration in the first line of the above XML document is already 39 bytes in size (encoded as ASCII, not counting the newline character) — in other words, it is contained within the first three ciphertext blocks. As the XML declaration is absolutely mandatory for PanGPS, the earliest 16-byte block boundary on which a tampered block could be inserted is at the next block that follows the XML declaration, at position 3*16 = 48. If a user-controlled shared object is created as /tmp/ldp.so, a single tampered 16-byte ciphertext block like the following would be sufficient:

code in red

Since the dynamic linker parses /etc/ld.so.preload line by line, this block contains the path /tmp/ldp.so, delimited by newline characters and followed by three space characters as padding. The dynamic linker will parse the file and successfully interpret the line, even if it is embedded into seemingly invalid binary data. As any plaintext that would result in the aforementioned human-readable ciphertext after encryption would very likely not be meaningful in the XML document, we therefore tested and verified that it is possible to insert a bogus <f> element with arbitrary binary data as a second root element into the document, just after the XML declaration, even if the resulting document is technically no longer valid XML. This made it possible to craft an XML document with an <f> tag at the end of Block 3 (just after the XML declaration), a manipulated Block 4, and a closing </f> tag at the beginning of Block 5. The rest of Block 5 as well as the following blocks are used for the <policy> tag. 

As PanGPS uses the AES block cipher in a Cipher Block Chaining (CBC) mode of operation, it is not possible to simply encrypt Blocks 1 through 3 and Blocks 5 through 8 individually and insert a manipulated block into position 4. Instead, Block 1 through 3 must be encrypted in sequence using the null byte initialization vector, as described earlier. Next, the ciphertext of Block 3 (CBC feedback) is used as an initialization vector for CBC decrypting the block that is to be inserted into the ciphertext (highlighted in red). The result of this decryption (highlighted in green) is inserted into the plaintext as Block 4. Then, the CBC encryption can continue normally until Block 8. The plaintext XML file as seen by PanGPS is shown below (every line corresponds to a single plaintext block of 16 bytes):

code in black with one green line

CBC encryption of that plaintext yields the following ciphertext (in which every line corresponds to a single ciphertext block of 16 bytes):

code in black with one red line

If the above content is written to PanPortalCfg.dat, and the symbolic link from PanPortalCfg_1662c17069ca30beb328f3ccdffe14fe.dat to /etc/ld.so.preload is in place, a VPN connection attempt to 127.0.0.1 with the username johndoe will result in the desired write operation to /etc/ld.so.preload. From the subsequent execution of the ls command, it is evident that the dynamic linker is now trying to load /tmp/ldp.so when a dynamically linked program is executed (highlighted in red):

code in black with two lines in red

The Payload

Since the dynamic linker will now attempt to preload /tmp/ldp.so for all dynamically linked executables, even those that have the SUID bit set (like umount), an unprivileged user can now create /tmp/ldp.so to implement and execute any privileged operation. As a proof of concept, we wrote a simple shared library in C:

code in black with red and green

When loaded into a root-owned process, the library creates a copy of the /bin/bash executable and sets the SUID bit on that copy (highlighted in red). To prevent this from being executed by all processes (since the effect of /etc/ld.so.preload is global), the code verifies that it is being called from the umount executable (highlighted in green). As the umount binary is owned by root and has the SUID set by default, it is always executed with the privileges of the root user and is therefore an ideal target for injecting the shared library. The library can be compiled using GCC as follows:

$ gcc -shared -fPIC -o /tmp/ldp.so ldp.c

In the following listing, it can be seen that the root-owned bash2 SUID binary has been created after invoking umount:

code in black with some red

Finally, the bash2 SUID binary can be invoked with the parameter -p, which prevents it from dropping its privileges, and the newly gained effective user ID 0 can be verified (highlighted in red):

code in black with some red

In total, this vulnerability allows local, unprivileged users to escalate their privileges to UID 0 (root). The whole process has been automated in a Python script. Instead of using the globalprotect binary to initiate the VPN connection attempt, the Python script interfaces directly with the PanGPS process via a socket that PanGPS opens on 127.0.0.1:4767. Its execution is shown below:

blue, yellow, green and red code on black background

Exploiting GlobalProtect on macOS

Analyzing the macOS implementation of the GlobalProtect client showed that it is affected by the same vulnerability. However, the exploit must be adapted to address some specifics of the macOS client. Similar to the Linux version, PanGPS for macOS creates the same kind of root-owned *.dat files in the following directory:

${HOME}/Library/Application Support/PaloAltoNetworks/GlobalProtect

Encryption Password

In contrast to the Linux version, the macOS client uses an AES key that is derived from a randomly generated application password (GP_PASS) to encrypt the *.dat files. That password is stored in the user’s keychain. It is individually generated for each user when the GlobalProtect client is started for the first time. The password of the current user can be retrieved with the following command:

$ security find-generic-password -ws GlobalProtectService
485db861598a87071d0b86ba232aa9bd

Retrieving the application password in this way requires entering the user’s log-in password twice. Key derivation in the GlobalProtect VPN client on macOS works as follows:

code in black with some red and green

The same AES key is used to encrypt the communication between the PanGPA GUI application and PanGPS.

Targeting root’s Crontab

Unlike on Linux, the dynamic linker on macOS does not interpret /etc/ld.so.preload, so a different approach must be used to escalate privileges on that operating system. One such possibility is to create a cron job for the root user by writing to /private/var/at/tabs/root. That cron job can invoke an attacker-controlled shell script to create a root-owned SUID bash executable. Before PanGPS overwrites the root crontab file, a popup message prompting the user for confirmation is shown. Like the dynamic linker on Linux, cron is very forgiving when interpreting crontabs. Any line in the correct format will be interpreted, even if it is surrounded by invalid binary data.

The following listing shows the job that will be placed in Blocks 4 and 5 of the ciphertext (highlighted in red):

code in red

The corresponding plaintext XML file, as seen by PanGPS, is shown below (every line corresponds to a single plaintext block of 16 bytes, and the actual bytes depend on the user-specific AES key):

black code with some green

CBC encryption of this XML file results in the following ciphertext, which is accepted by the cron daemon as a valid crontab:

black code with some red

It should be noted that the injected plaintext blocks representing the crontab entry must not contain XML syntax elements (such as angular brackets) to avoid parsing failures by PanGPS. Depending on the user-specific AES key, the injected blocks may therefore have to be adapted by changing them to an equivalent form, e.g., by specifying a different file name for the shell script to be invoked. The exploitation procedure for macOS has been automated as well and the execution of the exploit is shown below:

white, red, blue, green code on black background

Conclusion

In this blog, we explained the issues we had to overcome to successfully exploit an otherwise fairly standard vulnerability within GlobalProtect for Linux and macOS. To recap:

  • The GlobalProtect agent consists of several components, among which PanGPS runs with elevated privileges so that it can perform privileged operations.
  • Most notably, PanGPS creates files in a user’s home directory under some circumstances, e.g., when user-initiated VPN connection attempts fail.
  • These files are created with root privileges, and PanGPS will follow symbolic links when creating them, potentially allowing an unprivileged user to overwrite or create files in arbitrary filesystem locations.
  • The files are copied from a source file that can be manipulated by the unprivileged user, but the files are encrypted, and PanGPS validates some aspects of the plaintext before copying them.
  • Knowing the encryption scheme, it is possible to craft files whose plaintext passes that validation, but whose ciphertext can be sufficiently controlled to allow for escalation of privilege when creating the right file:
    • On Linux, we demonstrated overwriting /etc/ld.so.preload to preload a user-controlled shared object when any dynamically linked executable is started. This allows for elevation of privilege when the root user starts such executables, but more conveniently, when the unprivileged user specifically starts an executable like umount that is owned by root and has the SUID bit set.
    • On macOS, we demonstrated overwriting a crontab for root to spawn a user-controlled shell script with root privileges.

Additional Resources

Related Content