Skip to content

fix: refuse object keys that resolve outside the local destination (#872) - #886

Open
terryrankine wants to merge 1 commit into
peak:masterfrom
terryrankine:fix/path-traversal-upstream
Open

terryrankine wants to merge 1 commit into
peak:masterfrom
terryrankine:fix/path-traversal-upstream

Conversation

@terryrankine

Copy link
Copy Markdown

Context

Fixes #872. S3 object keys are arbitrary strings and may contain .. segments. cp and sync built the local destination from the raw key with path.Join, which resolves .. and lands outside the chosen directory: downloading s3://bucket/* where an attacker wrote one object with key data/../../../.bashrc overwrites the caller's shell profile. Confirmed on master with a crafted key: both cp and sync wrote the file two levels above the destination.

Fix

URL.JoinInside joins and then checks the cleaned result is still under the destination. It is used at every local sink: batch downloads in prepareLocalDestination, the trailing-slash and directory cases in prepareDownloadDestination, and sync's generateDestinationURL. An offending object is reported as an error and skipped; the rest of the run continues and the exit code is non-zero. Remote destinations are unchanged (keys are opaque there). --flatten and single-object copies use Base() and were never affected.

Tests

  • storage/url: TestJoinInside (11 cases: inside, a/../b, .., ../x, . base, / base, remote pass-through).
  • e2e: TestCopyS3ObjectsToLocalWithPathTraversalKey and TestSyncS3ObjectsToLocalWithPathTraversalKey — a data/../../escape.txt key alongside a safe one: exit 1, escapes destination on stderr, safe object copied, nothing outside the destination. Both fail on master (they write escape.txt outside the directory).

Linux, Go 1.24, go test -count=1 -race ./...: all packages ok. gofmt, go vet, staticcheck, unparam: clean.

CI note: the test matrix fails before running anything — bitnami/minio:2023.7.18 no longer exists on Docker Hub (fixed by #840). All build and qa jobs pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Et7MCFVnqJFF8NLkA1VJrP

S3 object keys are arbitrary strings and may contain ".." segments.
cp and sync built the local destination from the raw key with
path.Join, which resolves ".." and lands outside the chosen directory:
downloading "s3://bucket/*" where an attacker wrote one object with
key "data/../../../.bashrc" overwrites the caller's shell profile.
Reported upstream as peak#872.

Add URL.JoinInside, which joins and then checks the cleaned result is
still under the destination, and use it at every local sink: batch
downloads in prepareLocalDestination, the trailing-slash and directory
cases in prepareDownloadDestination, and sync's generateDestinationURL.
Offending objects are reported as errors and skipped; the rest of the
run continues and the exit code is non-zero. Remote destinations are
unchanged (keys are opaque there). --flatten and single-object copies
use Base() and were never affected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Et7MCFVnqJFF8NLkA1VJrP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Path Traversal in s5cmd S3→Local Downloads (cp/sync) Leads to Arbitrary File Write & RCE

1 participant