Exploits Explained: ZIP embedding attack on Google Chrome extensions

Growtika / Unsplash

Editor's note: In this installment of Exploits Explained, security researcher Malcolm Stagg recounts his discovery of CVE-2024-0333, a vulnerability in Google Chrome that could have been exploited to enable the installation of malicious extensions. Be sure to follow README on LinkedIn to keep up with future additions to this series.

Google Chrome is the most widely used browser in the world. It has been carefully designed with multiple layers of protections to mitigate most vulnerabilities, and those protections have been carefully reviewed to ensure their efficacy.

One of those protections includes the requirement that any internet protocols used in browser component updates, even when strong encryption is present, shouldn’t be absolutely trusted. Otherwise a government or employer with control over a device’s root certificates could tamper with components or extensions before they are installed, which would represent a significant threat to the browser’s users.

Chromium prevents such attacks by validating cryptographic signatures for each installed file. All extensions and most browser component updates use the CRX file format, which is effectively a ZIP file prepended with a header containing signatures and other metadata to guarantee file integrity.

I started looking at the CRX file format after working on a Synack special project to find privilege escalations on a virtual machine target. On a similar target the previous year, I performed binary static analysis and found three zero-day vulnerabilities, including CVE-2022-32427 and CVE-2022-32972, on three different background services. These vulnerabilities varied in cause and complexity to exploit, but they all had one thing in common: insufficient input validation when data was sent from a low-privileged executable to the high-privileged service, allowing the low-privileged user to take control of the computer.

Looking at the services running on the virtual machine, one caught my eye: the Google Chrome Elevation Service. The name of this service implies that it is designed to take a low-privileged executable file and run it at a higher privilege, as might be necessary for installing certain updates. Although I realized it would be difficult to find a flaw in code as carefully reviewed as Chromium, I was curious to see how the service was designed to ensure that only trusted executable files were elevated.

As expected, the service followed a well-designed procedure to prevent tampering: it would first copy a user-specified CRX file to a trusted location, perform signature validation on the file, decompress it, then finally run the extracted executable. A low-privileged user would not have permission to inject any additional files into the trusted location, and any attempt to tamper with the compressed executable would invalidate the signature and prevent it from running.

It seemed impossible to elevate a malicious executable file using this service – unless the CRX signature verification could somehow be bypassed. After researching the CRX file format, I realized that injecting extra data into the header might be possible. The header is what contains signatures for the rest of the file, so most of the data contained within the header itself is not subject to any verification.

I first tried injecting a full ZIP archive into the CRX header. Nothing happened – my injected data had no effect. To find out why, I began to research the ZIP file format, and specifically the EOCD (end of central directory) token which is present at the end of the ZIP file, interestingly enough as a remnant of the days when ZIP files would span many floppy disks. By placing this token at the end of the archive, files could be added to the archive without the need to modify a previous floppy disk. Since the ZIP file format requires the search for the EOCD token to start at the end of the archive and go backwards, a CRX file header can be prepended to the archive without affecting its contents.

The Minizip library is used by Chromium to decompress ZIP archives. I started looking at its unzOpenInternal function, which will first open the ZIP archive and find any EOCD tokens. I noticed something interesting while reviewing this code: Minizip uses two separate functions, unz64local_SearchCentralDir64 and unz64local_SearchCentralDir, to find EOCD tokens.

In my research of the ZIP file format I had found that ZIP64 archives, which are an extension of the ZIP format to support file sizes above 4GB, use a different EOCD token referred to as EOCD64. It appeared that Minizip would first attempt to find an EOCD64 token, and then only if that failed, find an EOCD token as a fallback.

This gave me an idea: maybe I could inject an EOCD64 token into the CRX file header. When Minizip decompresses the file, it might first find my malicious EOCD64 token instead of the intended EOCD token. There was one limitation, though, which was that Minizip only searches the last 64kB of the archive file for either token. My attack only had a chance of working with an archive under 64kB in size.

chrome diagrams

Figure 1: A malicious Chrome extension could be injected inside a valid Chrome extension (left) to create a malicious extension with a valid signature (right)

Nevertheless, I now had a plan. I created a Python script to inject a ZIP payload, containing a valid EOCD64 token, into a CRX file header, and created a small Chrome extension to attack. This extension simply displays a banner on any page visited. Next, I created the ZIP payload corresponding to a malicious extension that displays a different banner. The attack worked! I could embed my payload into the CRX file and change the banner.

Although this looked like an interesting attack, I needed to figure out its potential impact. Could this still be used for local privilege escalation? What about attacking Chrome Web Store extensions? Or internal Chrome components?

For a local privilege escalation, I would need a valid, signed CRX file containing a ZIP archive under 64kB. I began to search the internet for any files signed with that specific key. The smallest I could find was 73kB. Close call, but roughly 7000 bytes too large for my attack. It also looked like Chrome components and Chrome Web Store extensions were quite well protected. When Chrome components are updated, the Client Update Protocol signs each request and response. Even if an attacker could intercept HTTPS traffic, they could not tamper with these signed responses.

I did find one area that was largely unprotected. Chrome allows companies to define policies for their enterprise extensions. These extensions can be automatically installed and updated from an internal company update server. I came up with an attack scenario for these extensions involving a coffee shop.

In this scenario, an employee heads over to a coffee shop with their laptop. A local attacker, also in this coffee shop, sets up their IP or host name to match the company's internal update server. The next time Chrome runs extension updates on the employee's device, it will download the maliciously modified extension from the attacker's device. Because it still contains a valid signature, the employee's laptop will upgrade to the malicious extension. The attacker could use this extension to perform a variety of attacks, including monitoring the employee’s web activity and modifying visited pages to steal passwords and session cookies, potentially gaining access to private servers and devices on the company’s internal network.

Here's a video walkthrough showcasing the vulnerability:

After proving that this scenario could be exploited using a local HTTP update server, I finished my report to Google describing the issue. Within 24 hours, they had patched the Chromium source code to fix the issue and soon released the fix publicly in the 120.0.6099.216 browser release. Although I never got the privilege escalation working, it led me on a path to find a very interesting Chromium vulnerability which had gone unnoticed for over six years.