GHSA-38xv-hf3p-h7mq: Path Traversal
Summary
Multiple backends, when given a specially crafted object to copy, can escape the backend confinement.
| Backend | Keep/Close | Per-backend severity | |---|---|---| | sftp | Medium | Real filesystem escape, fires under default encoding. | | smb | Low-Medium | Escapes to a different SMB share the credential can reach. | | ftp | Low | Real, leading-.. overshoot PoC is partly neutralized by encoding; escape bounded to at/below the login base. | | webdav | Low | Server-side ACLs are the real boundary. | | b2 | Low | Same-account sibling bucket crossing on a flat keyspace. | | swift | Low | Same, container. | | qingstor | Low | Same. | | oracleobjectstorage | Low | Same. | | internetarchive | Low | IA items are owner-writable only; confined to user's own items. | | storj | Low | Can retarget a different bucket in the same access grant. | | filelu | Low | Confined to the user's own account. | | shade | Low | Confined to the user's own drive. | | sia | Low | siad API password already grants full-daemon access. |
Root cause
rclone core does not sanitize .. in a source object's Remote() - verified: nothing in fs/march, fs/sync, fs/list, or fs/operations rejects .. segments before the name reaches the destination backend's Put/Update/Mkdir. Confinement is therefore each backend's responsibility, and these backends join root + remote without a check.
This divides into two classes:
- Bucket based backends - bucket.Split(path.Join(f.root, rootRelativePath)): - backend/b2/b2.go:404, backend/swift/swift.go:464, backend/qingstor/qingstor.go:198, backend/oracleobjectstorage/oracleobjectstorage.go:245, backend/internetarchive/internetarchive.go:1016, backend/smb/smb.go:885, backend/storj/fs.go:289. - path.Join collapses .. on the standard (ASCII) form before encoding is applied (e.g. FromStandardPath(path.Join(...)) at backend/b2/b2.go:1641), so EncodeDot never gets the chance to neutralize the ... - lib/bucket.Join does not clean paths (keeps .. as a literal key segment); path.Join does. backend/s3, backend/azureblob, backend/googlecloudstorage already use bucket.Join and are therefore not affected.
- Path based backends - path.Join(root, remote) onto a real path: - sftp: remotePath = path.Join(f.absRoot, f.opt.Enc.FromStandardPath(remote)) (backend/sftp/sftp.go:2497). Default encoding is encoder.Display (== Standard), and FromStandardPath short-circuits to a pass-through in that mode, so .. survives; f.absRoot is absolute, so path.Join("/home/user/root", "../../../../etc/passwd") -> /etc/passwd. - webdav: filePath at backend/webdav/webdav.go:426-432. - ftp: path.Join(f.root, remote) at ~14 sites (e.g. backend/ftp/ftp.go:1247). - filelu, shade, sia: analogous joins.
Precondition that limits reachability
For any of these to fire, a source must hand rclone a Remote() containing raw ... That is only possible when:
1. the source is a flat-keyspace object store (not a filesystem - a local/sftp/smb source cannot represent ../../x as one directory entry), and 2. the offending key was written with native, non-rclone tooling - rclone's own writer applies EncodeDot and rewrites a .. segment to fullwidth .., so you cannot create such a key through rclone.
rclone's source-side listing does pass a natively-planted raw .. key through unchanged (verified for b2: remote := file.Name[len(prefix):] after ToStandardPath, backend/b2/b2.go:858,867). The reports never establish this precondition; it is the same omission across every member of the class.
Example attack
bash Step 1 - attacker, using NATIVE S3 tooling (NOT rclone) on a source the victim ingests from: aws s3api put-object --bucket shared-drop --key '../../victim-backups/pwned.txt' --body evil.txt
Step 2 - victim's ordinary ingest: rclone copy s3-drop:shared-drop b2:victim-uploads/incoming path.Join("victim-uploads/incoming", "../../victim-backups/pwned.txt") = "victim-backups/pwned.txt" -> lands in the victim's victim-backups bucket instead of under incoming/
The blast radius is the victim's own account (a bucket/share/path the configured credential already reaches) - integrity misdirection, not a cross-tenant or confidentiality breach. sftp/smb are the exception in reach (server filesystem / other share), still bounded by the login's own permissions.
Precedent
This is the same class as the already-fixed local backend advisory https://github.com/rclone/rclone/security/advisories/GHSA-7p4m-qxvv-g567, which added (Fs).localPath returning errPathEscapes for names resolving outside the root (backend/local/local.go:819-826). That fix was justified because the destination was the operator's own OS filesystem; the same reasoning extends (at lower severity) to sftp/smb.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/rclone/rcloneto a version that resolves this vulnerability.Fixed in 1.75.1
Event History
Frequently Asked Questions
Which backend deployments have the most significant exposure?
SFTP has the highest listed impact because the escape reaches the real filesystem and occurs under the default encoding. SMB can escape into another SMB share reachable by the configured credential; the remaining listed backends are generally limited to resources within the same account, grant, or service-specific boundary.
What must happen for exploitation?
A user must be given a specially crafted source object and copy it. The vulnerability relies on rclone not sanitizing `..` in the source object's `Remote()` value, and the vector indicates user interaction is required.
Are default SFTP settings affected?
Yes. The advisory states that the SFTP filesystem escape fires under the default encoding.
What is the likely security impact?
The supplied severity vector indicates integrity impact only, with no stated confidentiality or availability impact. The practical effect depends on the backend boundary: for example, SFTP can write outside its intended filesystem confinement, while several object-storage backends are limited to sibling buckets or containers accessible within the same account or grant.