Kestrel
대시보드로 돌아가기
CVE-2026-88046MEDIUM· 5.3MITRENVDGHSA대응게시일: 2026. 09. 10.수정일: 2026. 09. 10.

rclone: source object names can escape the configured root on upload

Path-Traversal

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
5.3medium

이론적 심각도 점수

EPSS

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

권장 대응 기한60일 이내CISA SSVC 기준

계획된 패치 주기 내 조치(60일 이내)

외부 노출· KEV 미등재 · 자동화 어려움 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도높음
필요 권한불필요
사용자 상호작용필요
범위불변
영향
기밀성 영향없음
무결성 영향높음
가용성 영향없음
버전별 점수
CVSS 3.15.3MODERATE
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N

상세 설명

Summary

Multiple backends, when given a specially crafted object to copy, can escape the backend confinement.

BackendKeep/ClosePer-backend severity
sftpMediumReal filesystem escape, fires under default encoding.
smbLow-MediumEscapes to a different SMB share the credential can reach.
ftpLowReal, leading-.. overshoot PoC is partly neutralized by encoding; escape bounded to at/below the login base.
webdavLowServer-side ACLs are the real boundary.
b2LowSame-account sibling bucket crossing on a flat keyspace.
swiftLowSame, container.
qingstorLowSame.
oracleobjectstorageLowSame.
internetarchiveLowIA items are owner-writable only; confined to user's own items.
storjLowCan retarget a different bucket in the same access grant.
fileluLowConfined to the user's own account.
shadeLowConfined to the user's own drive.
siaLowsiad 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

sql
1# Step 1 - attacker, using NATIVE S3 tooling (NOT rclone) on a source the victim ingests from:
2aws s3api put-object --bucket shared-drop --key '../../victim-backups/pwned.txt' --body evil.txt
3
4# Step 2 - victim's ordinary ingest:
5rclone copy s3-drop:shared-drop b2:victim-uploads/incoming
6# path.Join("victim-uploads/incoming", "../../victim-backups/pwned.txt") = "victim-backups/pwned.txt"
7# -> 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.

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.