{"schema":"threatline.record/2","record":{"id":"GHSA-3rqf-7h69-gg5v","aliases":["GHSA-3rqf-7h69-gg5v"],"summary":"rclone WebDAV backend leaks custom credential headers to a different host on redirect","details":"## Summary\r\n\r\nThe 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.\r\n\r\n## Root Cause\r\n\r\nThe 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.\r\n\r\n```text\r\nbackend/webdav/webdav.go:\r\n  NewFs                -> client.CheckRedirect = rest.RefuseHTTPSDowngradeRedirectFn (line 524)\r\n  NewFs                -> addHeaders(opt.Headers)                                   (lines 540-542)\r\n  (*Fs).addHeaders     -> f.srv.SetHeader(key, value)                              (lines 604-609)\r\nlib/rest/rest.go:\r\n  RefuseHTTPSDowngradeRedirectFn (GET/Open)  -> refuses downgrade only, no Header.Del (lines 283-291)\r\n  PreserveMethodRedirectFn       (PROPFIND)  -> refuses downgrade only, no Header.Del (lines 267-278)\r\n  (*Client).Call    -> req.Header.Add(k, v) for each stored header                  (lines 404-414)\r\nfs/fshttp/http.go:\r\n  Transport.headers = ci.Headers  (global --header only)                            (line 364)\r\n  Transport.RoundTrip strips only t.headers on redirectLeavesHost                   (lines 623-631)\r\n```\r\n\r\n## Impact\r\n\r\nAn 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.\r\n\r\n## Proof of Concept\r\n\r\n`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.`\r\n\r\nReproduction steps:\r\n\r\n1. Start two HTTP(S) listeners on different hostnames (for example `host-a.example` and `host-b.example`). `host-a` answers a WebDAV PROPFIND or GET with a `302`/`307` redirect to the same path on `host-b`; `host-b` records the request headers it receives.\r\n2. Configure a WebDAV remote pointing at `host-a` with a custom credential header, for example `--webdav-headers \"X-Auth-Token,SECRET-CREDENTIAL\"`.\r\n3. Run any listing or read operation (for example `rclone lsd webdavremote:` or `rclone cat webdavremote:file`).\r\n4. Observe that `host-b` received `X-Auth-Token: SECRET...AL`, while a standard `Authorization` header (if also set) is stripped by Go on the cross-domain hop.\r\n\r\n```text\r\nhost-b received headers:\r\n  X-Auth-Token: ***      <- leaked custom credential header\r\n  (Authorization: ***              <- stripped by Go across registrable domains\r\n```\r\n\r\n## Attack Chain\r\n\r\n1. **Exposure:** The operator sets a custom credential header via the documented `headers` option on a WebDAV remote whose URL commonly points at a third-party host. `NewFs` calls `addHeaders`, which stores the header on the shared `rest.Client` through `SetHeader` (`backend/webdav/webdav.go:540-542,604-609`).\r\n2. **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).\r\n3. **Path:** `rest.Client.Call` copies the stored header onto every request via `req.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.\r\n4. **Guard:** The WebDAV redirect handlers `RefuseHTTPSDowngradeRedirectFn` and `PreserveMethodRedirectFn` refuse only an HTTPS to HTTP downgrade and delete no headers (`lib/rest/rest.go:267-291`). The transport-level strip in `RoundTrip` iterates only the global-header set `t.headers`, bound to `ci.Headers` at construction (`fs/fshttp/http.go:364,623-631`), so it cannot delete the per-client WebDAV header.\r\n5. **Primitive:** The outbound request to the attacker-influenced host carries the operator's custom credential header.\r\n6. **Result:** The credential is disclosed cross-host and can be replayed against the original WebDAV service.\r\n\r\n## Bypass Evidence\r\n\r\nThis 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.\r\n\r\nDisproof attempts, each performed against the source at the examined revision:\r\n\r\n1. Hypothesis: the transport strip removes the header. Refuted. `Transport.headers` is set to `ci.Headers` (the global `--header` flag) at `fs/fshttp/http.go:364`, and the redirect branch of `RoundTrip` deletes only entries in `t.headers`. The WebDAV header is installed via `rest.Client.SetHeader` and lives in the per-client header map, which the loop never iterates, so it is not deleted.\r\n2. 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 calls `Header.Del`. A cross-host non-downgrade redirect is followed with the header intact.\r\n3. 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` (`checkRedirect` deletes 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.\r\n4. 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-in `auth_redirect` option (default `false`, `backend/webdav/webdav.go:189`).\r\n\r\nIndependent 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.\r\n\r\n## Affected Versions\r\n\r\n- `Ecosystem: go`\r\n- `Package: github.com/rclone/rclone`\r\n- `Confirmed affected range: <= v1.75.1`\r\n- `Latest release checked: v1.75.1 (canonical Go module proxy latest; released 2026-09-04)`\r\n- `Fix status: not fixed as of the latest published release`\r\n\r\nThe 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.\r\n\r\n## Suggested Fix\r\n\r\nEnforce 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.\r\n\r\n---\r\n\r\nReported by zx (GitHub: @manus-pi).","categories":["Unclassified"],"product":"github.com/rclone/rclone","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/rclone/rclone/security/advisories/GHSA-3rqf-7h69-gg5v","source_modified_at":"2026-10-09T10:56:16Z","fetched_at":"2026-10-26T12:48:13.248886Z","package":{"ecosystem":"go","name":"github.com/rclone/rclone"},"range":">= v1.56.0, <= v1.75.1"}],"fixed_versions":[],"cvss":3.7,"severity":"Low","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-09T10:56:16Z","modified_at":"2026-10-09T10:56:16Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/rclone/rclone/security/advisories/GHSA-3rqf-7h69-gg5v","fetched_at":"2026-10-26T12:48:13.248886Z","source_modified_at":"2026-10-09T10:56:16Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-09T10:56:16Z","content_hash":"8ecc56b457ba7bac087c406af8ffc30ab2c3583f91bffa0d3ced4e2d7be19421"}}
