github.com/rclone/rclone: rclone WebDAV backend leaks custom credential headers to a different host on redirect
What fixes it
No source in this snapshot names a fixed version.
Description
Summary
The WebDAV backend forwards operator-configured custom credential headers to a different host when the configured server responds with an HTTP 3xx redirect. When a WebDAV remote is created with the headers option (--webdav-headers), each configured header is installed on the backend's shared HTTP client and attached to every outgoing request. If the server returns a cross-host redirect, rclone follows it and re-sends the request, including the operator's custom header, to the new host. Go's standard library strips only four well-known sensitive header names (Authorization, Www-Authenticate, Cookie, Cookie2) and only across a different registrable domain, so a custom-named credential header such as X-Auth-Token or X-Api-Key is disclosed verbatim to the redirect target. An attacker who can influence the server's redirect response (an open redirect, a compromised or malicious endpoint, or an on-path position for a plaintext or same-host hop) harvests the operator's credential and can reuse it against the original service.
Root Cause
The WebDAV backend violates the invariant that operator-configured secret headers must not leave the configured host on a redirect. The backend installs each configured header onto the shared rest.Client via SetHeader, and those per-client headers are copied onto every request. The client's redirect handlers refuse only an HTTPS to HTTP downgrade; they delete no headers. A separate transport-level strip that does remove headers on a host-leaving redirect operates only over the global --header option set, not over per-client headers installed by a backend, so it never sees the WebDAV credential header. The same-backend fix that the project applied to the HTTP backend (deleting each configured header on a host-leaving redirect) and to the S3 backend was never propagated to WebDAV.
backend/webdav/webdav.go: NewFs -> client.CheckRedirect = rest.RefuseHTTPSDowngradeRedirectFn (line 524) NewFs -> addHeaders(opt.Headers) (lines 540-542) (*Fs).addHeaders -> f.srv.SetHeader(key, value) (lines 604-609) lib/rest/rest.go: RefuseHTTPSDowngradeRedirectFn (GET/Open) -> refuses downgrade only, no Header.Del (lines 283-291) PreserveMethodRedirectFn (PROPFIND) -> refuses downgrade only, no Header.Del (lines 267-278) (*Client).Call -> req.Header.Add(k, v) for each stored header (lines 404-414) fs/fshttp/http.go: Transport.headers = ci.Headers (global --header only) (line 364) Transport.RoundTrip strips only t.headers on redirectLeavesHost (lines 623-631)
Impact
An operator who configures a custom credential header on a WebDAV remote (the documented mechanism for auth material, per the headers option help) leaks that credential to an unintended host whenever the configured server emits a cross-host redirect. The disclosed value is a full, reusable credential header (CWE-200, CWE-522; CWE-319 for a plaintext hop). The attacker prerequisite is the ability to influence the server's redirect response, which is realistic for an open redirect, a compromised or malicious endpoint, or an on-path position on a cleartext or same-host hop. Confidentiality impact is high because a complete credential is disclosed. As a plausible downstream consequence, the recovered credential can be replayed to gain authenticated write access to the victim's WebDAV account; this is a credential-reuse consequence, not a directly demonstrated write in the reviewed evidence. The default configuration is affected: the leak does not depend on the opt-in auth_redirect option.
Proof of Concept
Requires a WebDAV remote configured with a custom credential header and a server that issues a cross-host, non-downgrade redirect. The Go toolchain was unavailable in the review environment, so the accompanying Go tests were reviewed against the source trace and Go net/http redirect-copy semantics rather than executed.
Reproduction steps:
- Start two HTTP(S) listeners on different hostnames (for example
host-a.exampleandhost-b.example).host-aanswers a WebDAV PROPFIND or GET with a302/307redirect to the same path onhost-b;host-brecords the request headers it receives. - Configure a WebDAV remote pointing at
host-awith a custom credential header, for example--webdav-headers "X-Auth-Token,SECRET-CREDENTIAL". - Run any listing or read operation (for example
rclone lsd webdavremote:orrclone cat webdavremote:file). - Observe that
host-breceivedX-Auth-Token: SECRET...AL, while a standardAuthorizationheader (if also set) is stripped by Go on the cross-domain hop.
host-b received headers: X-Auth-Token: *** <- leaked custom credential header (Authorization: *** <- stripped by Go across registrable domains
Attack Chain
- Exposure: The operator sets a custom credential header via the documented
headersoption on a WebDAV remote whose URL commonly points at a third-party host.NewFscallsaddHeaders, which stores the header on the sharedrest.ClientthroughSetHeader(backend/webdav/webdav.go:540-542,604-609). - Control: The victim supplies the credential value; the attacker supplies or influences the redirect target (open redirect, compromised or malicious server, or on-path for a cleartext or same-host hop).
- Path:
rest.Client.Callcopies the stored header onto every request viareq.Header.Add(lib/rest/rest.go:404-414), covering PROPFIND (List and metadata) and GET (Object.Open). The configured server returns a cross-host 3xx and Go follows it, copying the custom-named header to the new hop. - Guard: The WebDAV redirect handlers
RefuseHTTPSDowngradeRedirectFnandPreserveMethodRedirectFnrefuse only an HTTPS to HTTP downgrade and delete no headers (lib/rest/rest.go:267-291). The transport-level strip inRoundTripiterates only the global-header sett.headers, bound toci.Headersat construction (fs/fshttp/http.go:364,623-631), so it cannot delete the per-client WebDAV header. - Primitive: The outbound request to the attacker-influenced host carries the operator's custom credential header.
- Result: The credential is disclosed cross-host and can be replayed against the original WebDAV service.
Bypass Evidence
This is an incomplete-fix and unfixed-sibling-backend condition relative to CVE-2026-88013 (GHSA-486v-q2wf-fp2r), which fixed exactly this leak in the HTTP backend, and relative to GHSA-h4mf-4v27-hggj, which fixed only the WebDAV same-host HTTPS to HTTP downgrade case.
Disproof attempts, each performed against the source at the examined revision:
- Hypothesis: the transport strip removes the header. Refuted.
Transport.headersis set toci.Headers(the global--headerflag) atfs/fshttp/http.go:364, and the redirect branch ofRoundTripdeletes only entries int.headers. The WebDAV header is installed viarest.Client.SetHeaderand lives in the per-client header map, which the loop never iterates, so it is not deleted. - Hypothesis: the WebDAV redirect handlers delete the header. Refuted. Both handlers, read in full at
lib/rest/rest.go:267-291, only cap the redirect count and refuse a downgrade; neither callsHeader.Del. A cross-host non-downgrade redirect is followed with the header intact. - Hypothesis: this is a duplicate of the HTTP-backend fix or of the WebDAV downgrade fix. Refuted. The HTTP-backend fix lives only at
backend/http/http.go:528(checkRedirectdeletes each configured header on a host-leaving hop); the WebDAV backend has no header-deleting redirect handler. The WebDAV downgrade fix addresses only a same-host HTTPS to HTTP downgrade and does nothing for a cross-host, non-downgrade redirect of a custom-named header. - Hypothesis: it requires disabling a security control, so it is not a real issue. Refuted. Setting a credential header is the documented mechanism for WebDAV auth material (
backend/webdav/webdav.go:127-138), not the disabling of a control. The leak occurs in default configuration and does not depend on the opt-inauth_redirectoption (defaultfalse,backend/webdav/webdav.go:189).
Independent basis for the leak, beyond the reviewed tests: Go's net/http redirect copier strips only Authorization, Www-Authenticate, Cookie, and Cookie2, and only across a different registrable domain (hostname-based, port ignored). Custom header names are copied to any redirect target unconditionally.
Affected Versions
Ecosystem: goPackage: github.com/rclone/rcloneConfirmed affected range: <= v1.75.1Latest release checked: v1.75.1 (canonical Go module proxy latest; released 2026-09-04)Fix status: not fixed as of the latest published release
The vulnerable code was verified present in the exact published v1.75.1 module artifact (the current canonical Go proxy latest release): the WebDAV backend wires RefuseHTTPSDowngradeRedirectFn/PreserveMethodRedirectFn and calls addHeaders with no host-change header strip, and the transport strip still covers only the global header set. No released fix exists for this cross-host custom-header vector. The introduction boundary could not be proven against every earlier released artifact: the reviewed local checkout is post-release development (its VERSION marker names an unreleased v1.76.0) with squashed history and no tags, so the earliest affected release was not established. The WebDAV headers option and both redirect handlers are unchanged by the HTTP-backend fix, so the leak plausibly affects every release line, but that lower boundary is not proven against released module artifacts. The confirmed affected range is therefore stated with an open lower bound and an incomplete-confidence introduction boundary.
Suggested Fix
Enforce the same header-stripping invariant for WebDAV that the HTTP and S3 backends already enforce: on any redirect hop that leaves the configured host (comparing scheme, hostname, and effective port, as rest.SameHost already does), delete every operator-configured header before the request is re-sent. The most direct approach is a WebDAV CheckRedirect that both refuses the HTTPS to HTTP downgrade (preserving the current behavior) and deletes each configured header name when the hop leaves the host, mirroring backend/http/http.go checkRedirect. Because per-client headers installed via rest.Client.SetHeader are invisible to the global transport strip, the fix must live in the backend or in the lib/rest redirect handler, not in the global fs/fshttp transport. As an interim mitigation, operators can avoid placing reusable secret headers on WebDAV remotes that point at servers capable of redirecting to other hosts, and can pin remotes to endpoints that do not emit cross-host redirects.
---
Reported by zx (GitHub: @manus-pi).
Text from GitHub Advisory Database
Affected versions 1
- github.com/rclone/rclone, go>= v1.56.0, <= v1.75.1GitHub