
CVE-2025-4138 / CVE-2025-4517 — Bypass del filtro de Symlink PATH_MAX en Python tarfile
Escritura arbitraria de archivos mediante extracción con filter="data" / filter="tar"
Una vulnerabilidad crítica en el módulo tarfile de Python permite a un atacante omitir los filtros de extracción ("data" y "tar") y escribir archivos arbitrarios fuera del directorio de extracción previsto. Cuando un proceso privilegiado (p. ej., un script de copia de seguridad de nivel root, un pipeline de CI/CD o un instalador de paquetes) extrae un archivo tar controlado por el atacante usando el parámetro supuestamente seguro filter="data", este exploit consigue escritura arbitraria completa de archivos como ese usuario privilegiado — normalmente escalando a root.
La causa raíz es una peculiaridad de comportamiento en os.path.realpath(): deja de resolver silenciosamente los enlaces simbólicos una vez que la ruta completamente expandida supera PATH_MAX (4096 bytes en Linux, 1024 en macOS). El filtro de tarfile se basa en realpath() para las comprobaciones de seguridad, pero el kernel resuelve los enlaces simbólicos de forma independiente durante la extracción, lo que crea una brecha TOCTOU (Time-of-Check-to-Time-of-Use) que permite escapar del directorio.
| Campo | Valor |
|---|---|
| CVE IDs | CVE-2025-4138, CVE-2025-4517 |
| CVSS v3.1 | 9.4 (Crítico) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L |
| CWE | CWE-22 — Limitación incorrecta de una ruta a un directorio restringido |
| Tipo de vulnerabilidad | Recorrido de directorios mediante enlaces simbólicos / Omisión del filtro |
| Impacto | Escritura arbitraria de archivos → escalada de privilegios, escape de sandbox, manipulación de datos |
| Vector de ataque | Entregar un archivo tar malicioso a cualquier aplicación que utilice tarfile.extractall() con filtros |
| Afectados | Python 3.12.0 – 3.12.10, 3.13.0 – 3.13.3 |
| Corregido en | Python 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4 |
| Parche | CPython PR #135037 |
| Aviso | GHSA-hgqp-3mmf-7h8f |
| Informador | 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 🔓
---
## Versiones Afectadas
| Rama de Python | Rango Vulnerable | Versión Corregida | Estado |
|:--|:--|:--|:--|
| 3.13 | 3.13.0 – 3.13.3 | **3.13.4** | ✅ Corregido |
| 3.12 | 3.12.0 – 3.12.10 | **3.12.11** | ✅ Corregido |
| 3.11 | 3.11.4 – 3.11.12 | **3.11.13** | ✅ Corregido |
| 3.10 | 3.10.12 – 3.10.17 | **3.10.18** | ✅ Corregido |
| 3.9 | 3.9.17 – 3.9.22 | **3.9.23** | ✅ Corregido |
| 3.8 | 3.8.17 – 3.8.20 | — | ❌ Fin de vida útil |
| 3.14+ | Filtro predeterminado cambiado a `"data"` | Consultar la última versión | ⚠️ Mayor exposición |
> **Nota:** Python 3.14+ cambió el parámetro `filter` predeterminado de sin filtrado a `"data"`, lo que significa que las aplicaciones que anteriormente no tenían filtro (y por lo tanto ya eran inseguras) ahora usan el filtro vulnerable por defecto.
---
## Patrón de Código Afectado
Cualquier aplicación que haga esto en una versión vulnerable de Python es explotable:```python
import tarfile