GHSA-mwxc-m426-3f78: ApostropheCMS has Arbitrary File Write (Zip Slip / Path Traversal) in Import-Export Gzip Extraction
**Reported:** 2026-03-08  
**Status:** patched and released in version 3.5.3 of `@apostrophecms/import-export`Product| Field | Value |
|---|---|
| Repository | `apostrophecms/apostrophe` (monorepo) |
| Affected Package | `@apostrophecms/import-export` |
| Affected File | `packages/import-export/lib/formats/gzip.js` |
| Affected Function | `extract(filepath, exportPath)` — lines ~132–157 |
| Minimum Required Permission | **Global Content Modify** (any editor-level user with import access) |Vulnerability SummaryThe `extract()` function in `gzip.js` constructs file-write paths using:`path.join()` does **not** resolve or sanitise traversal segments such as `../`. It concatenates them as-is, meaning a tar entry named `../../evil.js` resolves to a path **outside** the intended extraction directory. No canonical-path check is performed before the write stream is opened.This is a textbook **Zip Slip** vulnerability. Any user who has been granted the **Global Content Modify** permission — a role routinely assigned to content editors and site managers — can upload a crafted `.tar.gz` file through the standard CMS import UI and write attacker-controlled content to **any path the Node.js process can reach on the host filesystem**.Security ImpactThis vulnerability provides **unauthenticated-equivalent arbitrary file write** to any user with content editor permissions. The full impact chain is:1. Arbitrary File WriteWrite any file to any path the Node.js process user can access. Confirmed writable targets in testing:Any path the CMS process has permission to2. Static Web Directory — Defacement & Malicious Asset InjectionApostropheCMS serves `<project-root>/public/` via Express static middleware:A traversal payload targeting `public/` makes any uploaded file **directly HTTP-accessible**:This enables:Full site defacementServing phishing pages from the legitimate CMS domainInjecting malicious JavaScript served to all site visitors (stored XSS at scale)3. Persistent Backdoor / RCE (Post-Restart)If the traversal targets any `.js` file loaded by Node.js on startup (e.g., a module `index.js`, a config file, a routes file), the payload becomes a **persistent backdoor** that executes with the CMS process privileges on the next server restart. In container/cloud environments, restarts happen automatically on deploy, crash, or health-check failure — meaning the attacker does not need to manually trigger one.4. Credential and Secret File OverwriteOverwrite `.env`, `app.config.js`, database seed files, or any config file to:Exfiltrate database credentials on next loadRedirect authentication to an attacker-controlled backendDisable security controls (rate limiting, MFA, CSRF)5. Denial of ServiceOverwrite any critical application file (`package.json`, `node_modules` entries, etc.) with garbage data, rendering the application unbootable.Required Permission**Global Content Modify** — this is a standard editor-level permission routinely granted to content managers, blog editors, and site administrators in typical ApostropheCMS deployments. It is **not** an administrator-only capability. Any organisation that delegates content editing to non-technical staff is exposed.Proof of ConceptTwo PoC artifacts are provided:| File | Purpose |
|---|---|
| `tmp-import-export-zip-slip-poc.js` | Automated Node.js harness — verifies the write happens without a browser |
| `make-slip-tar.py` | Attacker tool — generates a real `.tar.gz` for upload via the CMS web UI |PoC 1 — Automated Verification (`tmp-import-export-zip-slip-poc.js`)**Run:****Observed output (confirmed):**The file `zip-slip-pwned.txt` is written **two directories above** the extraction root, confirming path traversal.PoC 2 — Web UI Exploitation (`make-slip-tar.py`)**Script (`make-slip-tar.py`):**Steps to Reproduce (Web UI — Real Exploitation)Step 1 — Create the payload fileCreate a file with the content you want to write to the server. For a static web directory write:Step 2 — Generate the malicious archiveUse the traversal path that reaches the CMS `public/` directory. The number of `../` segments depends on where the CMS stores its temporary extraction directory relative to the project root — typically 2–4 levels up. Adjust as needed:This creates `evil-slip.tar.gz` containing:`aposDocs.json` — empty, required by the importer`aposAttachments.json` — empty, required by the importer`../../../../<project-root>/public/injected.html` — the traversal payloadStep 3 — Upload via CMS Import UILog in to the CMS with any account that has **Global Content Modify** permission.Navigate to **Open Global Settings → More Options → Import**.Select `evil-slip.tar.gz` and click **Import**.The CMS accepts the file and begins extraction — no error is shown.Step 4 — Confirm the writeExpected response:The file is now being served from the CMS's own domain to all visitors.Video POC : https://drive.google.com/file/d/1bbuQnoJv_xjM_uvfjnstmTh07FB7VqGH/view?usp=sharing
CVSS v3.1: 9.9