GHSA-945V-V9P3-V5XW

Vulnerability from github – Published: 2026-08-05 20:03 – Updated: 2026-08-05 20:03
VLAI
Summary
rclone local `--metadata` applies attacker-controlled mode/uid - setuid binary planted from an untrusted remote
Details

Summary

When writing an object with metadata, the local backend applies the source-supplied mode, uid, and gid verbatim: it parses mode as an octal integer and passes it straight into os.Chmod(o.path, os.FileMode(umode)), and passes uid/gid straight into os.Chown. The value is never masked to permission bits, so any value with Go's ModeSetuid (1<<23) or ModeSetgid (1<<22) bit set causes the setuid/setgid bit to be applied. Because both the file content and its metadata come from the (attacker-controlled) source remote, an attacker stores a binary of their choosing with mode = 40000755 (and uid = 0); when the victim runs rclone copy -M <remote>: /dest, rclone writes the attacker's binary and makes it setuid. If the victim runs rclone as root (typical for system backup/restore), the uid=0 chown plus setuid produces a root-owned setuid binary with attacker content — any local user then escalates to root. When rclone runs as a non-root service user, the planted setuid binary is owned by that user, giving any local user that user's privileges (lateral escalation / persistent backdoor).

Details

backend/local/metadata.go, writeMetadataToFile():

uid, hasUID := o.parseMetadataInt(m, "uid", 10)
gid, hasGID := o.parseMetadataInt(m, "gid", 10)
if hasUID {
    ...
    err = os.Chown(o.path, uid, gid)        // source-controlled owner, no same-uid guard
}
mode, hasMode := o.parseMetadataInt(m, "mode", 8)
if hasMode && mode >= 0 {
    umode := uint(mode)
    if umode <= math.MaxUint32 {
        err = os.Chmod(o.path, os.FileMode(umode))   // <-- raw value; ModeSetuid/ModeSetgid NOT masked off
    }
}

os.Chmod/os.FileMode honor ModeSetuid/ModeSetgid/ModeSticky. There is no &^ (os.ModeSetuid|os.ModeSetgid) mask and no check that the source is trusted, so an attacker-chosen mode string sets those bits on the freshly written, attacker-controlled file. (Note: a legitimate local source reports mode in unix st_mode layout e.g. 0106755, whose bit 1<<23 is unset, so honest copies happen to drop setuid — but the attacker supplies the Go-FileMode layout 40000755 directly, which sets it.)

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 a payload (the attacker-controlled binary content) and copy it with the malicious mode metadata:

mkdir -p msrc mdst && cp /bin/true msrc/payload
./rclone copy -M --metadata-set mode=40000755 msrc mdst       # 40000755 = Go FileMode setuid|0755

3) Observe — the destination file is now setuid:

stat -c '%A %a' mdst/payload
-rwsr-xr-x 4755                                               # 's' = setuid bit SET on attacker binary

Variants: mode=20000755 → setgid (-rwxr-sr-x); mode=60000755 → both (-rwsr-sr-x). With rclone run as root and the source object also carrying uid=0/gid=0, the file is chown'd root:root, yielding a root-owned setuid binary executable by any local user.

Impact

A victim performing a metadata-preserving copy/restore (-M) from an untrusted or compromised remote installs an attacker-chosen executable with the setuid/setgid bit set. Run as root (system backup/restore, the common case for --metadata), this is a root-owned setuid root backdoor executable by any local user → local privilege escalation to root. Run as a non-root user, it is a setuid backdoor for that service account. The companion uid/gid application lets a root-run transfer also reassign ownership of written files arbitrarily.

Remediation

Mask special bits before applying mode from metadata — os.Chmod(o.path, os.FileMode(umode).Perm()) (or umode & 0o777) — and do not honor setuid/setgid/sticky from source metadata; gate uid/gid/setuid application behind an explicit opt-in (e.g. --local-metadata-set-ownership) that is off by default, and document that -M from untrusted remotes must not restore privileged bits. Regression test: copying an object with mode=40000755/uid=0 must produce a non-setuid, caller-owned file unless the opt-in is set.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.74.3"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/rclone/rclone"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.74.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-732"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-05T20:03:14Z",
    "nvd_published_at": null,
    "severity": "LOW"
  },
  "details": "### Summary\nWhen writing an object with metadata, the local backend applies the source-supplied `mode`, `uid`, and `gid` verbatim: it parses `mode` as an octal integer and passes it straight into `os.Chmod(o.path, os.FileMode(umode))`, and passes `uid`/`gid` straight into `os.Chown`. The value is never masked to permission bits, so any value with Go\u0027s `ModeSetuid` (1\u003c\u003c23) or `ModeSetgid` (1\u003c\u003c22) bit set causes the setuid/setgid bit to be applied. Because both the file content and its metadata come from the (attacker-controlled) source remote, an attacker stores a binary of their choosing with `mode = 40000755` (and `uid = 0`); when the victim runs `rclone copy -M \u003cremote\u003e: /dest`, rclone writes the attacker\u0027s binary and makes it setuid. If the victim runs rclone as root (typical for system backup/restore), the `uid=0` chown plus setuid produces a root-owned setuid binary with attacker content \u2014 any local user then escalates to root. When rclone runs as a non-root service user, the planted setuid binary is owned by that user, giving any local user that user\u0027s privileges (lateral escalation / persistent backdoor).\n\n### Details\n`backend/local/metadata.go`, `writeMetadataToFile()`:\n```go\nuid, hasUID := o.parseMetadataInt(m, \"uid\", 10)\ngid, hasGID := o.parseMetadataInt(m, \"gid\", 10)\nif hasUID {\n    ...\n    err = os.Chown(o.path, uid, gid)        // source-controlled owner, no same-uid guard\n}\nmode, hasMode := o.parseMetadataInt(m, \"mode\", 8)\nif hasMode \u0026\u0026 mode \u003e= 0 {\n    umode := uint(mode)\n    if umode \u003c= math.MaxUint32 {\n        err = os.Chmod(o.path, os.FileMode(umode))   // \u003c-- raw value; ModeSetuid/ModeSetgid NOT masked off\n    }\n}\n```\n`os.Chmod`/`os.FileMode` honor `ModeSetuid`/`ModeSetgid`/`ModeSticky`. There is no `\u0026^ (os.ModeSetuid|os.ModeSetgid)` mask and no check that the source is trusted, so an attacker-chosen `mode` string sets those bits on the freshly written, attacker-controlled file. (Note: a legitimate local source reports `mode` in unix `st_mode` layout e.g. `0106755`, whose bit 1\u003c\u003c23 is unset, so honest copies happen to drop setuid \u2014 but the attacker supplies the Go-`FileMode` layout `40000755` directly, which sets it.)\n\n### PoC\n1) Get the official stable binary:\n```\ncurl -fsSLO https://downloads.rclone.org/v1.74.3/rclone-v1.74.3-linux-amd64.zip\nunzip -j rclone-v1.74.3-linux-amd64.zip \u0027*/rclone\u0027 -d .      # ./rclone -\u003e v1.74.3\n```\n2) Create a payload (the attacker-controlled binary content) and copy it with the malicious `mode` metadata:\n```\nmkdir -p msrc mdst \u0026\u0026 cp /bin/true msrc/payload\n./rclone copy -M --metadata-set mode=40000755 msrc mdst       # 40000755 = Go FileMode setuid|0755\n```\n3) Observe \u2014 the destination file is now setuid:\n```\nstat -c \u0027%A %a\u0027 mdst/payload\n-rwsr-xr-x 4755                                               # \u0027s\u0027 = setuid bit SET on attacker binary\n```\nVariants: `mode=20000755` \u2192 setgid (`-rwxr-sr-x`); `mode=60000755` \u2192 both (`-rwsr-sr-x`). With rclone run as root and the source object also carrying `uid=0`/`gid=0`, the file is chown\u0027d root:root, yielding a root-owned setuid binary executable by any local user.\n\n\n### Impact\nA victim performing a metadata-preserving copy/restore (`-M`) from an untrusted or compromised remote installs an attacker-chosen executable with the setuid/setgid bit set. Run as root (system backup/restore, the common case for `--metadata`), this is a root-owned setuid root backdoor executable by any local user \u2192 local privilege escalation to root. Run as a non-root user, it is a setuid backdoor for that service account. The companion `uid`/`gid` application lets a root-run transfer also reassign ownership of written files arbitrarily.\n\n### Remediation\nMask special bits before applying mode from metadata \u2014 `os.Chmod(o.path, os.FileMode(umode).Perm())` (or `umode \u0026 0o777`) \u2014 and do not honor setuid/setgid/sticky from source metadata; gate `uid`/`gid`/setuid application behind an explicit opt-in (e.g. `--local-metadata-set-ownership`) that is off by default, and document that `-M` from untrusted remotes must not restore privileged bits. Regression test: copying an object with `mode=40000755`/`uid=0` must produce a non-setuid, caller-owned file unless the opt-in is set.",
  "id": "GHSA-945v-v9p3-v5xw",
  "modified": "2026-08-05T20:03:14Z",
  "published": "2026-08-05T20:03:14Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/rclone/rclone/security/advisories/GHSA-945v-v9p3-v5xw"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rclone/rclone/commit/e58f09739a35774ca82b5211d2377ac0f2051500"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/rclone/rclone"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rclone/rclone/releases/tag/v1.74.4"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "rclone local `--metadata` applies attacker-controlled mode/uid - setuid binary planted from an untrusted remote"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…