Skip to content

--root traversal escape, REST write + markdown read (incomplete fix) #474

Description

@dilipk5

A path-traversal vulnerability lets a user escape the --root sandbox and read/write arbitrary files the server process can access. The GET listing path is confined, but the REST file-operation endpoints and the markdown renderer resolve user paths without the same containment check, so ../ sequences escape the configured root — enabling arbitrary file write (rename/copy/move → potential RCE) and arbitrary file read.

Details

root() (server/root.js) is just mellow.webToWin(dir, root), which is path.join(root, dir) and normalizes .. — e.g. webToWin('../../../../etc/passwd', '/tmp/sandbox') === '/etc/passwd'. It does not contain traversal.

The GET listing path compensates for this in server/route.js:81 — if (fullPath.indexOf(config('root'))) return sendError('...beyond root') — proving --root is intended as a confinement boundary. But these sinks call root() with no such post-check:

  • server/rest/index.js — rename (277-280), move (219-222), copy (237-240), pack (289), extract (308) → fs.rename / moveFiles / copymitter / archive write outside root.
  • server/markdown/index.js (34-43) — the ?relative branch returns DIR_ROOT + shortName with no root() call at all → arbitrary file read rendered via markdown-it. Both raw ../ and URL-encoded ..%2f bypass (no normalization middleware).

This is an incomplete fix of the original route path-traversal patch, which only hardened the GET path. Confirmed on latest v19.20.1; no existing CVE.

PoC

Run a hardened instance confined to a sandbox:
mkdir -p /tmp/cc-sandbox /tmp/cc-outside
echo 'SECRET-OUTSIDE-CANARY' > /tmp/cc-outside/secret.md
echo hi > /tmp/cc-sandbox/r.txt
cloudcmd --root /tmp/cc-sandbox --no-config-dialog --port 8000
Arbitrary write (escapes root):
curl -X PUT 'http://127.0.0.1:8000/api/v1/rename'
-H 'Content-Type: application/json'
-d '{"from":"/r.txt","to":"../../../../tmp/cc-outside/x.txt"}'
-> /tmp/cc-outside/x.txt is created, outside the sandbox
Arbitrary read (escapes root):
curl 'http://127.0.0.1:8000/api/v1/markdown/..%2f..%2f..%2f..%2ftmp%2fcc-outside%2fsecret.md?relative'
-> < p >SECRET-OUTSIDE-CANARY

Impact

Path traversal (CWE-22): arbitrary file write/move/copy (RCE-capable via overwriting app files, cron, authorized_keys — up to CVSS 9.6) and arbitrary file read (CVSS 7.7), both escaping the --root confinement. Impacts operators who rely on --root to sandbox cloudcmd (e.g. exposing a single shared directory). Precondition, stated honestly: the default config (root:"/", auth:false) already exposes the whole filesystem, and a restricted --root can also be reset via PATCH /api/v1/config {"root":"/"} when the config dialog is enabled — so these findings add real value specifically in hardened deployments that set a restricted --root and disable the config dialog (and/or enable auth). Fix: apply the indexOf(config('root')) !== 0 containment check after every root() call in server/rest/index.js and server/markdown/index.js.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions