{"id":"CVE-2026-55086","aliases":["EUVD-2026-62741","GHSA-2jwf-f4xq-f24h"],"status":"published","title":"Etherpad: Import/export use Math.random() for temp file paths; predictable paths on shared /tmp enable symlink-based file overwrite","description":"Etherpad is a real-time collaborative editor. Prior to 3.1.0, src/node/handler/ImportHandler.ts and src/node/handler/ExportHandler.ts derive temporary filenames from Math.random() and place them in os.tmpdir(). On a host with a shared world-writable temporary directory, a local unprivileged attacker who predicts a filename can precreate a symbolic link to a file writable by the Etherpad process. Subsequent import or export operations can follow the link through fs.writeFile, fs.rename, or document-conversion output and overwrite the target with partially attacker-controlled content. This issue is fixed in version 3.1.0.","severity":{"version":"3.1","score":4.2,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00138,"percentile":0.03267,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-377","CWE-59"],"published":"2026-08-13T14:11:07Z","modified":"2026-10-28T02:31:10Z","fixes":[{"ecosystem":"npm","package":"ep_etherpad-lite","fixed_in":["3.1.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"ep_etherpad-lite","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"3.0.0"},{"fixed":"3.1.0"}]}]},{"source":"osv","ecosystem":"npm","package":"ep_etherpad-lite","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.1.0"}]}]}],"products":[{"vendor":"ether","product":"etherpad"}],"references":[{"url":"https://github.com/ether/etherpad/security/advisories/GHSA-2jwf-f4xq-f24h","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/ether/etherpad/pull/7784","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/ether/etherpad/commit/8c6104c5d5daf41f0d454acc04d42dffa0e0d996","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/ether/etherpad/releases/tag/v3.1.0","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-2jwf-f4xq-f24h"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/55xxx/CVE-2026-55086.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55086","tags":["advisory"]},{"url":"https://github.com/ether/etherpad","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-55086","title":"Etherpad: Import/export use Math.random() for temp file paths; predictable paths on shared /tmp enable symlink-based file overwrite","description":"Etherpad is a real-time collaborative editor. Prior to 3.1.0, src/node/handler/ImportHandler.ts and src/node/handler/ExportHandler.ts derive temporary filenames from Math.random() and place them in os.tmpdir(). On a host with a shared world-writable temporary directory, a local unprivileged attacker who predicts a filename can precreate a symbolic link to a file writable by the Etherpad process. Subsequent import or export operations can follow the link through fs.writeFile, fs.rename, or document-conversion output and overwrite the target with partially attacker-controlled content. This issue is fixed in version 3.1.0.","status":"published","published":"2026-08-19T19:35:46.851Z","modified":"2026-08-25T13:59:03.366Z","cvss":[{"version":"3.1","score":4.2,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-59","CWE-377"],"updated_at":"2026-11-02T14:20:11.925815Z"}],"euvd":[{"id":"EUVD-2026-62741","description":"Etherpad is a real-time collaborative editor. Prior to 3.1.0, src/node/handler/ImportHandler.ts and src/node/handler/ExportHandler.ts derive temporary filenames from Math.random() and place them in os.tmpdir(). On a host with a shared world-writable temporary directory, a local unprivileged attacker who predicts a filename can precreate a symbolic link to a file writable by the Etherpad process. Subsequent import or export operations can follow the link through fs.writeFile, fs.rename, or document-conversion output and overwrite the target with partially attacker-controlled content. This issue is fixed in version 3.1.0.","status":"published","published":"2026-08-19T19:35:46Z","modified":"2026-08-25T13:59:03Z","cvss":[{"version":"3.1","score":4.2,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T18:01:33.385611Z"}],"ghsa":[{"id":"GHSA-2jwf-f4xq-f24h","title":"ep_etherpad-lite: Import/export uses Math.random() for temp file paths; predictable paths on shared /tmp enable symlink-based file overwrite","description":"## Description\n\n`src/node/handler/ImportHandler.ts` and `src/node/handler/ExportHandler.ts` both compute their temporary working-file paths as:\n\n```ts\nconst randNum = Math.floor(Math.random() * 0xFFFFFFFF);\nconst srcFile = `${os.tmpdir()}/etherpad_export_${randNum}.html`;\nconst destFile = `${os.tmpdir()}/etherpad_export_${randNum}.${type}`;\n```\n\nTwo flaws compound:\n\n1. **`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.\n\n2. **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:\n\n   ```\n   ln -s /etc/etherpad/SESSIONKEY.txt /tmp/etherpad_export_\u003cpredicted\u003e.html\n   ```\n\n   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.\n\nThe 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).\n\n## Severity rationale\n\n- **AV:L** — requires local access to the host that runs Etherpad. Multi-tenant hosts (shared dev boxes, k8s shared-node setups, single-server CI workers) are the realistic threat surface.\n- **AC:H** — attacker needs to predict the temp filename, which requires observing prior names or shared PRNG state.\n- **PR:L** — any unprivileged local account.\n- **UI:N** — no user interaction.\n- **S:C** — scope change from local user to whatever the Etherpad process can write.\n- **C:L / I:L / A:N** — bounded by what the Etherpad process can already touch; confidentiality is via secondary read-paths (the LibreOffice conversion error log can echo bytes from the symlinked file).\n\nSingle-tenant deployments where only the Etherpad operator has shell access on the host are not exposed.\n\n## Affected versions\n\n- All `ep_etherpad-lite` versions through `v3.0.0` (inclusive). The `Math.floor(Math.random() * 0xFFFFFFFF)` pattern is present in `src/node/handler/ImportHandler.ts` and `src/node/handler/ExportHandler.ts` for as far back as the repository history extends — older than the 2024-03-16 snapshot at commit [`107598b`](https://github.com/ether/etherpad/commit/107598b) where it first appears in the current file paths, and predating the v1.x era.\n\n## Patched versions\n\n- `ep_etherpad-lite \u003e= 3.1.0` — the fix is on `develop` HEAD as commit `8c6104c`. Update this field with the actual tagged release version when it ships.\n\n## Proof of concept (sketch)\n\n```\n# Pre-condition: attacker has shell on the same host as Etherpad,\n# has read access to /tmp, and Etherpad's process can write to the\n# chosen target (e.g. runs as root inside a Docker container).\n\n# 1. Observe a temp filename from logs (or guess via prior exports).\nTARGET=/etc/etherpad/SESSIONKEY.txt\nGUESS=/tmp/etherpad_export_$(./predict-next-random)$EXT  # implementation-specific\n\n# 2. Place the symlink before Etherpad creates the file.\nln -s \"$TARGET\" \"$GUESS\"\n\n# 3. Trigger an export from the Etherpad UI (or via the API).\n# Etherpad calls fs.writeFile(srcFile, ...) which open()s the symlink\n# target with O_WRONLY|O_TRUNC and clobbers $TARGET with HTML content.\n```\n\nIn 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.\n\n## Workarounds\n\n- Run Etherpad in a container with a private `/tmp` (Docker: `--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m`, systemd: `PrivateTmp=true`).\n- Ensure the Etherpad process does NOT run as root and has write access only to its own data directory.\n- Set `TMPDIR` to an Etherpad-private directory in the environment.\n\n## Fix\n\nPatched in [`8c6104c`](https://github.com/ether/etherpad/commit/8c6104c) (PR [#7784](https://github.com/ether/etherpad/pull/7784)):\n\n```diff\n-const randNum = Math.floor(Math.random() * 0xFFFFFFFF);\n+const randNum = crypto.randomBytes(16).toString('hex');\n```\n\n128 bits of CSPRNG entropy. Predicting the next filename is no longer feasible.\n\nA 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.\n\n## Resources\n\n- Patched in: https://github.com/ether/etherpad/pull/7784 (squash commit `8c6104c`).\n- `Math.random()` is not appropriate for security-sensitive contexts. See the MDN guidance for [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) and the Node.js recommendation to use [`crypto.randomBytes`](https://nodejs.org/api/crypto.html#cryptorandombytessize-callback) for unpredictable values.\n\n## Credits\n\nReported during an internal security audit by Claude (via @JohnMcLear).","status":"published","published":"2026-08-13T14:11:07Z","modified":"2026-08-13T14:11:08Z","cvss":[{"version":"3.1","score":4.2,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-59","CWE-377"],"updated_at":"2026-11-02T14:41:00.097339Z"}],"nvd":[{"id":"CVE-2026-55086","description":"Etherpad is a real-time collaborative editor. Prior to 3.1.0, src/node/handler/ImportHandler.ts and src/node/handler/ExportHandler.ts derive temporary filenames from Math.random() and place them in os.tmpdir(). On a host with a shared world-writable temporary directory, a local unprivileged attacker who predicts a filename can precreate a symbolic link to a file writable by the Etherpad process. Subsequent import or export operations can follow the link through fs.writeFile, fs.rename, or document-conversion output and overwrite the target with partially attacker-controlled content. This issue is fixed in version 3.1.0.","status":"published","published":"2026-08-19T20:17:17.52Z","modified":"2026-09-09T21:15:59.613Z","cvss":[{"version":"3.1","score":4.2,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-59","CWE-377"],"updated_at":"2026-11-02T14:12:39.962615Z"}],"osv":[{"id":"CVE-2026-55086","title":"Etherpad: Import/export use Math.random() for temp file paths; predictable paths on shared /tmp enable symlink-based file overwrite","description":"Etherpad is a real-time collaborative editor. Prior to 3.1.0, src/node/handler/ImportHandler.ts and src/node/handler/ExportHandler.ts derive temporary filenames from Math.random() and place them in os.tmpdir(). On a host with a shared world-writable temporary directory, a local unprivileged attacker who predicts a filename can precreate a symbolic link to a file writable by the Etherpad process. Subsequent import or export operations can follow the link through fs.writeFile, fs.rename, or document-conversion output and overwrite the target with partially attacker-controlled content. This issue is fixed in version 3.1.0.","status":"published","published":"2026-08-19T19:35:46.851Z","modified":"2026-10-28T02:31:10.160845893Z","cvss":[{"version":"3.1","score":4.2,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-377","CWE-59"],"updated_at":"2026-11-02T15:45:11.22012Z"},{"id":"GHSA-2jwf-f4xq-f24h","title":"ep_etherpad-lite: Import/export uses Math.random() for temp file paths; predictable paths on shared /tmp enable symlink-based file overwrite","description":"## Description\n\n`src/node/handler/ImportHandler.ts` and `src/node/handler/ExportHandler.ts` both compute their temporary working-file paths as:\n\n```ts\nconst randNum = Math.floor(Math.random() * 0xFFFFFFFF);\nconst srcFile = `${os.tmpdir()}/etherpad_export_${randNum}.html`;\nconst destFile = `${os.tmpdir()}/etherpad_export_${randNum}.${type}`;\n```\n\nTwo flaws compound:\n\n1. **`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.\n\n2. **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:\n\n   ```\n   ln -s /etc/etherpad/SESSIONKEY.txt /tmp/etherpad_export_\u003cpredicted\u003e.html\n   ```\n\n   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.\n\nThe 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).\n\n## Severity rationale\n\n- **AV:L** — requires local access to the host that runs Etherpad. Multi-tenant hosts (shared dev boxes, k8s shared-node setups, single-server CI workers) are the realistic threat surface.\n- **AC:H** — attacker needs to predict the temp filename, which requires observing prior names or shared PRNG state.\n- **PR:L** — any unprivileged local account.\n- **UI:N** — no user interaction.\n- **S:C** — scope change from local user to whatever the Etherpad process can write.\n- **C:L / I:L / A:N** — bounded by what the Etherpad process can already touch; confidentiality is via secondary read-paths (the LibreOffice conversion error log can echo bytes from the symlinked file).\n\nSingle-tenant deployments where only the Etherpad operator has shell access on the host are not exposed.\n\n## Affected versions\n\n- All `ep_etherpad-lite` versions through `v3.0.0` (inclusive). The `Math.floor(Math.random() * 0xFFFFFFFF)` pattern is present in `src/node/handler/ImportHandler.ts` and `src/node/handler/ExportHandler.ts` for as far back as the repository history extends — older than the 2024-03-16 snapshot at commit [`107598b`](https://github.com/ether/etherpad/commit/107598b) where it first appears in the current file paths, and predating the v1.x era.\n\n## Patched versions\n\n- `ep_etherpad-lite \u003e= 3.1.0` — the fix is on `develop` HEAD as commit `8c6104c`. Update this field with the actual tagged release version when it ships.\n\n## Proof of concept (sketch)\n\n```\n# Pre-condition: attacker has shell on the same host as Etherpad,\n# has read access to /tmp, and Etherpad's process can write to the\n# chosen target (e.g. runs as root inside a Docker container).\n\n# 1. Observe a temp filename from logs (or guess via prior exports).\nTARGET=/etc/etherpad/SESSIONKEY.txt\nGUESS=/tmp/etherpad_export_$(./predict-next-random)$EXT  # implementation-specific\n\n# 2. Place the symlink before Etherpad creates the file.\nln -s \"$TARGET\" \"$GUESS\"\n\n# 3. Trigger an export from the Etherpad UI (or via the API).\n# Etherpad calls fs.writeFile(srcFile, ...) which open()s the symlink\n# target with O_WRONLY|O_TRUNC and clobbers $TARGET with HTML content.\n```\n\nIn 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.\n\n## Workarounds\n\n- Run Etherpad in a container with a private `/tmp` (Docker: `--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m`, systemd: `PrivateTmp=true`).\n- Ensure the Etherpad process does NOT run as root and has write access only to its own data directory.\n- Set `TMPDIR` to an Etherpad-private directory in the environment.\n\n## Fix\n\nPatched in [`8c6104c`](https://github.com/ether/etherpad/commit/8c6104c) (PR [#7784](https://github.com/ether/etherpad/pull/7784)):\n\n```diff\n-const randNum = Math.floor(Math.random() * 0xFFFFFFFF);\n+const randNum = crypto.randomBytes(16).toString('hex');\n```\n\n128 bits of CSPRNG entropy. Predicting the next filename is no longer feasible.\n\nA 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.\n\n## Resources\n\n- Patched in: https://github.com/ether/etherpad/pull/7784 (squash commit `8c6104c`).\n- `Math.random()` is not appropriate for security-sensitive contexts. See the MDN guidance for [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) and the Node.js recommendation to use [`crypto.randomBytes`](https://nodejs.org/api/crypto.html#cryptorandombytessize-callback) for unpredictable values.\n\n## Credits\n\nReported during an internal security audit by Claude (via @JohnMcLear).","status":"published","published":"2026-08-13T14:11:07Z","modified":"2026-08-13T14:25:58.243775Z","cvss":[{"version":"3.1","score":4.2,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-377","CWE-59"],"updated_at":"2026-11-02T15:46:34.338541Z"}]}}
