
CVE-2025-4138 / CVE-2025-4517 — Python tarfile PATH_MAX Symlink Filter Bypass
Écriture arbitraire de fichiers via l'extraction avec filter="data" / filter="tar"
Une vulnérabilité critique dans le module tarfile de Python permet à un attaquant de contourner les filtres d'extraction ("data" et "tar") et d'écrire des fichiers arbitraires en dehors du répertoire d'extraction prévu. Lorsqu'un processus privilégié (par exemple, un script de sauvegarde au niveau root, un pipeline CI/CD ou un installateur de paquets) extrait une archive tar contrôlée par l'attaquant en utilisant le paramètre supposé sûr filter="data", cette exploitation réalise une écriture arbitraire complète de fichiers en tant que cet utilisateur privilégié — en escaladant généralement vers root.
La cause profonde est une bizarrerie comportementale dans os.path.realpath() : elle cesse silencieusement de résoudre les liens symboliques une fois que le chemin complètement développé dépasse PATH_MAX (4096 octets sur Linux, 1024 sur macOS). Le filtre tarfile repose sur realpath() pour les vérifications de sécurité, mais le noyau résout les liens symboliques indépendamment lors de l'extraction — créant une faille TOCTOU (Time-of-Check-to-Time-of-Use) qui permet l'évasion du répertoire.
| Champ | Valeur |
|---|---|
| Identifiants CVE | CVE-2025-4138, CVE-2025-4517 |
| CVSS v3.1 | 9.4 (Critique) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L |
| CWE | CWE-22 — Limitation inappropriée d'un nom de chemin à un répertoire restreint |
| Type de vulnérabilité | Traversée de chemin via lien symbolique / contournement de filtre |
| Impact | Écriture arbitraire de fichiers → escalade de privilèges, évasion de sandbox, falsification de données |
| Vecteur d'attaque | Livrer une archive tar malveillante à toute application utilisant tarfile.extractall() avec des filtres |
| Affecté | Python 3.12.0 – 3.12.10, 3.13.0 – 3.13.3 |
| Corrigé dans | Python 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4 |
| Correctif | CPython PR #135037 |
| Avis de sécurité | GHSA-hgqp-3mmf-7h8f |
| Rapporteur | Caleb Brown — Google Security Research |
┌───────────────────────────────────────────┐
│ Malicious Tar Structure │
└───────────────────────────────────────────┘
Stage 1 ── Build symlink chain that inflates the resolved path past PATH_MAX
ddd...ddd/ (directory, 247 chars)
a → ddd...ddd (symlink, 1 char name → 247 char dir)
ddd...ddd/ddd...ddd/ (nested directory)
b → ddd...ddd (symlink)
... ×16 levels
Short path (symlinks): a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p ~31 chars
Resolved path (dirs): ddd…/ddd…/ddd…/ddd…/ddd…/ddd…/… ~3968 chars
↑ nearing PATH_MAX
Stage 2 ── Final symlink exceeds PATH_MAX → realpath() stops resolving
a/b/c/…/p/lll…lll → ../../../../../../../../../../../../../../../../..
(16 levels of ".." — traverses back to extraction root)
┌─────────────────────────────────────────────────────────────────┐
│ os.path.realpath() CANNOT expand this → filter says "OK" ✓ │
│ Linux kernel DOES follow chain → actually escapes ✗ │
└─────────────────────────────────────────────────────────────────┘
Stage 3 ── Escape symlink resolves to arbitrary filesystem path
escape → <overflow_link>/../../../../../../../root
Stage 4 ── Create intermediate directories through the escape
escape/.ssh/ (directory, mode 0700 — created by tar extraction)
Stage 5 ── Write payload through the escaped symlink
escape/.ssh/authorized_keys → writes to /root/.ssh/authorized_keys 🔓
---
## Versions concernées
| Branche Python | Plage vulnérable | Version corrigée | Statut |
|:--|:--|:--|:--|
| 3.13 | 3.13.0 – 3.13.3 | **3.13.4** | ✅ Patched |
| 3.12 | 3.12.0 – 3.12.10 | **3.12.11** | ✅ Patched |
| 3.11 | 3.11.4 – 3.11.12 | **3.11.13** | ✅ Patched |
| 3.10 | 3.10.12 – 3.10.17 | **3.10.18** | ✅ Patched |
| 3.9 | 3.9.17 – 3.9.22 | **3.9.23** | ✅ Patched |
| 3.8 | 3.8.17 – 3.8.20 | — | ❌ End of Life |
| 3.14+ | Default filter changed to `"data"` | Check latest | ⚠️ Higher exposure |
> **Note :** Python 3.14+ a modifié le paramètre `filter` par défaut, passant d'aucun filtrage à `"data"`, ce qui signifie que les applications qui n'avaient auparavant aucun filtre (et étaient donc déjà non sécurisées) utilisent désormais le filtre vulnérable par défaut.
---
## Motif de code affecté
Toute application effectuant cela sur une version vulnérable de Python est exploitable :