XPC to the Rescue: Uncovering Two Vulnerabilities in Zscaler Client Connector (MacOS)

chiajh
CSIT tech blog
Published in
7 min readAug 28, 2024
Photo from securing.pl

Introduction

TL;DR: In this post, we dive briefly into Cross-Process Communication (XPC) and how CSIT uncovered two vulnerabilities in Zscaler Client Connector (ZCC). We will share the methodology used and how CSIT’s work contributes to the strengthening of Singapore’s cybersecurity landscape.

Background

CSIT recently evaluated products within the Zscaler suite for Zero Trust Network Access, in collaboration with GovTech as part of a Whole-of-Government (WoG) effort.

During our research, we found two vulnerabilities in the macOS security software:

1. Unprivileged users can disable the product’s internet security function

2. Unprivileged users can execute scripts as root user.

As part of our responsible disclosure commitment, we shared our findings with Zscaler, who has since released the necessary patches to address the identified vulnerabilities.

What is XPC?

Before jumping into the vulnerabilities, we will cover some background on XPC (Cross Process Communication), an Apple’s specific implementation of IPC (Inter Process Communication).

In macOS, the launchd system daemon manages XPC services, activating them when necessary, deactivating them during idle periods, and automatically restarting them in case of a crash¹.

Utilizing XPC services over other IPC mechanisms such as pipes and sockets have several advantages :

  • Consolidating tasks from various processes or regulating access to a shared resource.
  • Assigning tasks in a manner that ensures they persist beyond the lifespan of a client.
  • Implementing privilege isolation to restrict access according to functionalities.

Applications employing these services depend on peer-to-peer XPC connections to facilitate communication across process boundaries (referred to as Application Sandboxes in Apple’s terminology). Each connection comprises of :

  1. A sender, responsible for dispatching connection and task execution requests through XPC message.
  2. A receiver, tasked with executing these requests, and providing feedback to the sender via XPC messages

Vulnerability 1 (Dylib Injection)

This vulnerability involves the injection of a dynamic library (dylib), Apple’s file format for storing shared libraries through the DYLD_INSERT_LIBRARIES environment variable² to send an XPC message to disable the product’s internet security.

First, we will dive into the architecture of the Zscaler Client Connecter (ZCC) running on macOS.

The Zscaler Client Connecter operates multiple XPC service including ZscalerService, ZscalerTunnel and ZscalerTray. Through static analysis, we identified the code for establishing connections (by pinpointing calls to Apple’s NSXPCConnection APIs) and the Objective-C protocols (class methods) associated with these XPC services.

The following screenshots from our static analysis show the steps to setup the NSXPCConnection and the different protocols used in Zscaler Client Connecter.

Code snippet to connect to XPC Service
XPCServiceProtocol
TunnelXPCProtocol
TrayXPCProtocol

Reviewing the “XPCServiceProtocol” protocol, the “exit” method caught our attention. This was presumably responsible for shutting down the client’s functionality.

Could we establish a connection to the ZscalerService process, dispatch an XPC message to trigger the “exit” protocol, thus terminating the ZscalerTunnel Process and the protection it offers?

Code Signature Check

Despite replicating the steps to setup the connection, we could not connect to the ZscalerService process from our own process. We were stopped by a code signature validation routine that is implemented in the XPC Connection delegate function. This routine calls the function PlatformUtils::validateTeamSignature to verify the signature of the XPC message sender.

Digging deeper, we found that a process needs a code signature that contains the TeamID of Zscaler Client Connecter to connect and send messages to its XPC services.

Dylib Injection weakness in XPC Service

Tackling the problem head on by defeating code signatures is difficult. Is there a simpler way to send a message? Perhaps by getting one of Zscaler Client Connecter’s processes to send XPC messages on our behalf? This should work as these processes have the code signature containing Zscaler Client Connecter’s TeamID,

One good candidate is the ZscalerTunnel process as it had the entitlement “com.apple.security.cs.disable-library-validation³”. This meant that a 3rd party dynamic library (dylib) could be loaded by an unprivileged user into its process through the DYLD_INSERT_LIBRARIES environment variable without signature validation.

Let’s give it a shot. We crafted a custom dylib, which contains the code to send XPC message that triggers the exit protocol function in ZscalerService. Fingers crossed, we loaded it into our controlled ZscalerTunnel, and sent a message.

Voila, it worked! We managed to unload the original ZscalerTunnel process, thereby disabling the internet security function of Zscaler Client Connecter.

Disabling internet security function
Exit function in ZscalerService
Code snippet of –[ZSAdapterInfoInterface unloadTunnel]

Vulnerability 2
(Local Privilege Escalation CVE 2024–23482)

After finding vulnerability 1, the team proceeded to enumerate the various protocols in search of other weaknesses. In one of the protocols — “XPCServiceProtocol” — we came across the method “-[prepareLogs:reply:]”and decided to dig deeper to identify any weaknesses in the client’s logging mechanism.

This path led us to the discovery of the second vulnerability: code injection through filename could lead to privilege escalation.

In the Graphical User Interface (GUI) of the Zscaler Client Connecter, the “Export Logs” function will send an XPC message to one of the XPC services to invoke the method “-[prepareLogs:reply:]”. This will subsequently trigger the code block below.

block function called by -[prepareLogs:reply:]

In this code block, the ZscalerService process will do the following actions as a root-owned process:

  • Look for files that contain “bitrock” and “installbuilder_installer” in “/private/var/tmp/
  • Copy them to “/Library/Application Support/Zscaler/
  • If copy is successful, insert read permission to the files by calling “chmod +r <filename>” by passing it to the method BundleUtils::runCommandToShell

We observe that “/private/var/tmp” is a user-writable folder and there are no checks when passing the argument to “BundleUtils::runCommandToShell”. We also observe that arbitrary shell commands may be injected by closing the initial “ ‘ “ in the command @”/bin/chmod +r ‘%s’ with another “ ‘ “ in the “%s” argument (supposedly the filename) to chmod.

With these 2 observations, we tried to run an arbitrary script as root.

Renaming bitrock.sh to bitrock.sh’;”${PWD}tmp${PWD}script.sh”;’, we found that we were able to get the shell to run the following command.

/bin/sh -c /bin/chmod +r ‘/Library/Application Support/Zscaler /bitrock.sh’;”${PWD}tmp${PWD}script.sh”;’

Specifically, /bin/sh ran:

  1. A legitimate chmod command on bitrock.sh.
  2. An arbitrary execution of /tmp/script.sh as root.

For our Proof-of-Concept (POC), we got script.sh to execute a bind shell listening on port 8080. Consequently, a non-privileged user could connect to the bind shell and execute commands interactively as root user.

#!/bin/bash 

mkfifo /tmp/myfifo
nc -l 127.0.0.1 8080 < /tmp/myfifo | /bin/bash -i > /tmp/myfifo 2>&1
Interactive shell as root
Interactive shell as root

Conclusion

These 2 vulnerabilities were reported to the vendor.

1. The dylib injection vulnerability was found and patched by the vendor during their internal code review.

2. The local privilege escalation bug was assigned a CVE⁴ and tracked. You may refer to the vendor’s website⁵ for more details.

Our key takeaway is that we need to consider the importance of OS specific protections such as entitlements in macOS and sanitize all strings when auditing software. This will greatly reduce the attack surface for attackers.

Big shoutout to you for reading this article 👏. We hope you enjoyed the read and gained something valuable from it 😄!

PS: If you are passionate about Cybersecurity, CSIT is hiring! Find out more about our positions for hire 👨‍💻👩‍💻.

Disclosure timeline

  • 22 November 2023 — Reported the two vulnerabilities to the Zscaler team
  • 30 November 2023 —Zscaler team acknowledged the findings; however, only the local privilege escalation vulnerability was assigned a CVE, as the dylib injection weakness was found and patched during an internal code.
  • 14 December 2023 — Zscaler Client Connector 4.2.0.241 was released that fixes CVE-2024–23482
  • 11 January 2024 — Zscaler team informed the team that CVEs have been reserved.
  • 26 March 2024 — Zscaler team publicly disclosed the CVEs (https://trust.zscaler.com/private.zscaler.com/posts/18226)

References

[1] https://developer.apple.com/documentation/xpc?language=objc

[2]https://theevilbit.github.io/posts/dyld_insert_libraries_dylib_injection_in_macos_osx_deep_dive/
[3] https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation?language=objc

[4] https://nvd.nist.gov/vuln/detail/CVE-2024-23482

[5] https://trust.zscaler.com/private.zscaler.com/posts/18226

--

--