medium

CVE-2026-55086

npm · ep_etherpad-lite

Summary

ep_etherpad-lite: Import/export uses Math.random() for temp file paths; predictable paths on shared /tmp enable symlink-based file overwrite

Severity
medium
CVSS
4.2
EPSS
0.1% (p3)
CWE
CWE-59, CWE-377
Also known as
GHSA-2jwf-f4xq-f24h
Published
2026-08-13
Updated
2026-08-13

Advisory details

Description

src/node/handler/ImportHandler.ts and src/node/handler/ExportHandler.ts both compute their temporary working-file paths as:

const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
const srcFile = `${os.tmpdir()}/etherpad_export_${randNum}.html`;
const destFile = `${os.tmpdir()}/etherpad_export_${randNum}.${type}`;

Two flaws compound:

  1. Math.random() is not crypto-secure. It yields at most ~32 bits of entropy and is predictable across calls within the same Node process (V8 shares PRNG state between consecutive Math.random() invocations). An attacker on the same host who observes any earlier temp-file name from logs or other side channels can predict subsequent names.

  2. The paths land in os.tmpdir(). On a typical Linux system this is /tmp — a shared world-writable directory. An unprivileged local attacker can pre-create a symbolic link at a predicted path pointing at any file the Etherpad process can write:

    ln -s /etc/etherpad/SESSIONKEY.txt /tmp/etherpad_export_<predicted>.html
    

    When ExportHandler calls fs.writeFile(srcFile, html) (or ImportHandler calls fs.rename(srcFile, destFile) / soffice writes its converted output to the path), the open syscall follows the symlink and either reads from or overwrites the linked target. For deployments where the Etherpad process runs as a privileged user (notably some Docker base images that run as root, snap confinement edge cases, or hand-rolled systemd units), this becomes arbitrary file overwrite.

The Import path is more impactful in practice: the file content the attacker can land in the symlink target is partially attacker-controlled (the post-soffice/post-mammoth conversion output of the uploaded document).

Severity rationale

Single-tenant deployments where only the Etherpad operator has shell access on the host are not exposed.

Affected versions

Patched versions

Proof of concept (sketch)

# Pre-condition: attacker has shell on the same host as Etherpad,
# has read access to /tmp, and Etherpad's process can write to the
# chosen target (e.g. runs as root inside a Docker container).

# 1. Observe a temp filename from logs (or guess via prior exports).
TARGET=/etc/etherpad/SESSIONKEY.txt
GUESS=/tmp/etherpad_export_$(./predict-next-random)$EXT  # implementation-specific

# 2. Place the symlink before Etherpad creates the file.
ln -s "$TARGET" "$GUESS"

# 3. Trigger an export from the Etherpad UI (or via the API).
# Etherpad calls fs.writeFile(srcFile, ...) which open()s the symlink
# target with O_WRONLY|O_TRUNC and clobbers $TARGET with HTML content.

In practice the exact reproduction depends on the host's PRNG state predictability and the deployment's process-owner privileges. A reliable exploit chain needs setpriv-style host access or a sibling tenant.

Workarounds

Fix

Patched in 8c6104c (PR #7784):

-const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
+const randNum = crypto.randomBytes(16).toString('hex');

128 bits of CSPRNG entropy. Predicting the next filename is no longer feasible.

A bigger follow-up — per-request mkdtemp subdirectories with O_EXCL/O_NOFOLLOW semantics — is deferred to a later release; the immediate window (predictable 32-bit collisions across processes) is what the patch closes.

Resources

Credits

Reported during an internal security audit by Claude (via @JohnMcLear).

References

Related advisories

Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.

Check my repo

Summarize with AI

ChatGPTClaudePerplexity

Sources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.