
Recorrido de rutas (Tar Slip) en Cornac a través de _extract_archive (CVE-2026-43637)
Gravedad: Alta, CVSS 4.0 8.8, Crítica, CVSS 3.1 9.1(asignada por VulnCheck, la CNA)
Vector (v4.0): CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N
Vector (v4.0): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
Afectado: cornac < 2.6.0
Corregido en: 2.6.0
CWE: CWE-22 (Limitación incorrecta de un nombre de ruta a un directorio restringido, 'Path Traversal')
Reportado por: Rahul Karne y Bharath Kumar Reddy Janumpally
CNA: VulnCheck
Publicado: 15 de julio de 2026
Los cargadores de conjuntos de datos de Cornac descargan y descomprimen archivos automáticamente, y el descompresor confiaba en cada ruta dentro del archivo.
Cornac es un framework de aprendizaje automático para sistemas de recomendación. Sus cargadores de conjuntos de datos integrados obtienen archivos a través de la red y los extraen sin ningún paso de confirmación. La rutina de extracción _extract_archive() en cornac/utils/download.py llama a archive.extractall() sin validación de las rutas de los miembros. Por lo tanto, un archivo TAR cuyos miembros contienen secuencias ../, rutas absolutas o entradas de enlaces simbólicos y enlaces duros escribe archivos en ubicaciones arbitrarias del sistema de archivos, en cualquier lugar donde el proceso en ejecución pueda escribir, completamente fuera del directorio de caché previsto. Varios cargadores obtienen datos por HTTP sin cifrar, por lo que un atacante con acceso a la red puede sustituir un archivo malicioso en tránsito y lograr que se extraiga en el momento en que se llama a un cargador.
Escritura arbitraria de archivos con ruta y contenido totalmente controlados por el atacante, limitada únicamente por los permisos del proceso que ejecuta Cornac. Se trata de una primitiva de escritura, no de lectura, por lo que no hay divulgación directa de información, pero la escritura arbitraria de archivos es una vía bien establecida hacia la ejecución de código y la denegación de servicio:
.py en site-packages, un archivo de inicio de shell o una entrada de tarea programada hace que el código del atacante se ejecute en la siguiente importación, shell o tarea.Quién está afectado: Cualquier uso de cornac < 2.6.0 que llame a un cargador de conjuntos de datos que descargue y extraiga un archivo que el atacante pueda controlar o interceptar. Dado que los cargadores extraen automáticamente, no se requiere ningún paso adicional más allá de la llamada habitual al cargador.
Quién no está afectado:
2.6.0 o una versión posterior, donde la extracción valida cada ruta de miembro.| Métrica | Valor | Fuente |
|---|---|---|
| Descargas, total histórico | 4.1M | pepy.tech/projects/cornac |
| Descargas, últimos 30 días | 67.8K | pepy.tech |
| Uso típico | Investigación y docencia de sistemas de recomendación; los cargadores de conjuntos de datos obtienen automáticamente datos por red, algunos por HTTP sin cifrar | inherente al framework |
_extract_archive() en cornac/utils/download.py extrae entradas ZIP y TAR a través de una única ruta de código y, para el caso TAR, llama a extractall() sin verificación de rutas:
# cornac/utils/download.py — _extract_archive(), lines 50-71 (v2.3.5)
def _extract_archive(file_path, extract_path="."):
"""Extracts an archive."""
for archive_type in ["zip", "tar"]:
if archive_type == "zip":
open_fn = zipfile.ZipFile
is_match_fn = zipfile.is_zipfile
elif archive_type == "tar":
open_fn = tarfile.open
is_match_fn = tarfile.is_tarfile
if is_match_fn(file_path):
with open_fn(file_path) as archive:
try:
archive.extractall(extract_path) # <-- no member-path validation
except (tarfile.TarError, RuntimeError, KeyboardInterrupt):
if os.path.exists(extract_path):
if os.path.isfile(extract_path):
os.remove(extract_path)
else:
shutil.rmtree(extract_path)
raise
Nada restringe los nombres de los miembros, por lo que un miembro llamado ../../somewhere/file se resuelve fuera de extract_path y se escribe allí.
La cadena de llamadas es totalmente automática a partir de una llamada al cargador:
cornac.datasets.<name>.load_feedback()
-> cornac.utils.download.cache() # downloads via urllib.request.urlretrieve
-> cornac.utils.download._extract_archive()
-> tarfile.extractall() # writes attacker-named paths
El zipfile de Python sanea las secuencias ../ durante la extracción, por lo que la rama ZIP de esta misma función no es explotable en Python moderno. El tarfile de Python no sanea las rutas de los miembros (antes de 3.12, y solo con un filtro explícito después de esa versión). Dado que Cornac procesa ambos formatos mediante la misma llamada a extractall(), la rama ZIP es segura y la rama TAR es totalmente explotable a través de las mismas líneas. La asimetría es fácil de pasar por alto precisamente porque el código parece uniforme entre los dos formatos.
Cornac descarga con urllib.request.urlretrieve(), sin anclaje de certificados ni verificación de integridad del archivo, y varios cargadores integrados usan URL http://. Un atacante con acceso a la red puede interceptar la descarga por HTTP sin cifrar y devolver un TAR malicioso sin comprometer el servidor upstream, por lo que el vector es remoto y no requiere interacción del usuario más allá de la llamada al cargador.
Un atacante necesita:
< 2.6.0.No se requieren privilegios en el objetivo ni interacción del usuario más allá de la llamada al cargador.
Lo siguiente se ejecutó contra el paquete real sin modificar, llamando directamente a la propia función _extract_archive de Cornac.
Versión vulnerable (2.3.5). Un TAR que contiene un miembro ../../ se extrae en un directorio de caché; el miembro termina fuera de ese directorio y sobrescribe un archivo que pertenece a una aplicación separada:
import cornac.utils.download as d
import io, os, tarfile, tempfile