
Traversée de chemin (Tar Slip) dans Cornac via _extract_archive (CVE-2026-43637)
Sévérité : Élevée, CVSS 4.0 8.8, Critique, CVSS 3.1 9.1 (attribué par VulnCheck, la CNA)
Vecteur (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
Vecteur (v4.0) : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
Versions concernées : cornac < 2.6.0
Corrigé dans : 2.6.0
CWE : CWE-22 (Limitation incorrecte d'un nom de chemin à un répertoire restreint, 'Path Traversal')
Signalé par : Rahul Karne et Bharath Kumar Reddy Janumpally
CNA : VulnCheck
Publié : 15 juillet 2026
Les chargeurs de jeux de données de Cornac téléchargent et décompressent automatiquement les archives, et le décompresseur faisait confiance à chaque chemin contenu dans l'archive.
Cornac est un framework d'apprentissage automatique pour les systèmes de recommandation. Ses chargeurs de jeux de données intégrés récupèrent des archives sur le réseau et les extraient sans aucune étape de confirmation. La routine d'extraction _extract_archive() de cornac/utils/download.py appelle archive.extractall() sans validation des chemins des membres. Une archive TAR dont les membres contiennent des séquences ../, des chemins absolus ou des entrées de type lien symbolique et lien physique écrit donc des fichiers à des emplacements arbitraires du système de fichiers, partout où le processus en cours peut écrire, entièrement en dehors du répertoire de cache prévu. Plusieurs chargeurs récupèrent les données via du HTTP non chiffré ; un attaquant positionné sur le réseau peut donc substituer une archive malveillante en transit et la faire extraire dès qu'un chargeur est appelé.
Écriture de fichier arbitraire avec chemin et contenu entièrement contrôlés par l'attaquant, limitée uniquement par les permissions du processus exécutant Cornac. Il s'agit d'une primitive d'écriture, et non de lecture, donc il n'y a pas de divulgation directe, mais l'écriture de fichier arbitraire est une voie bien établie vers l'exécution de code et le déni de service :
.py dans site-packages, d'un fichier de démarrage de shell ou d'une entrée de tâche planifiée fait exécuter le code de l'attaquant lors de l'importation, du shell ou de la tâche suivants.Qui est concerné : toute utilisation de cornac < 2.6.0 qui appelle un chargeur de jeu de données qui télécharge et extrait une archive que l'attaquant peut contrôler ou intercepter. Comme les chargeurs extraient automatiquement, aucune étape au-delà de l'appel ordinaire du chargeur n'est requise.
Qui n'est pas concerné :
2.6.0 ou une version ultérieure, où l'extraction valide chaque chemin de membre.| Métrique | Valeur | Source |
|---|---|---|
| Téléchargements, au total | 4.1M | pepy.tech/projects/cornac |
| Téléchargements, 30 derniers jours | 67.8K | pepy.tech |
| Utilisation typique | Recherche et enseignement sur les systèmes de recommandation ; les chargeurs de jeux de données récupèrent automatiquement les données sur le réseau, certains en HTTP non chiffré | inhérent au framework |
_extract_archive() de cornac/utils/download.py extrait les entrées ZIP et TAR via un chemin de code unique et, pour le cas TAR, appelle extractall() sans vérification des chemins :
# 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
Rien ne contraint les noms des membres ; un membre nommé ../../somewhere/file se résout donc en dehors de extract_path et y est écrit.
La chaîne d'appels est entièrement automatique à partir d'un appel de chargeur :
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
Le module zipfile de Python assainit les séquences ../ lors de l'extraction ; la branche ZIP de cette même fonction n'est donc pas exploitable sur Python moderne. Le module tarfile de Python n'assainit pas les chemins des membres (avant la 3.12, et uniquement avec un filtre explicite ensuite). Comme Cornac fait passer les deux formats par le même appel extractall(), la branche ZIP est sûre et la branche TAR est entièrement exploitable via les mêmes lignes. L'asymétrie est facile à manquer précisément parce que le code paraît uniforme entre les deux formats.
Cornac télécharge avec urllib.request.urlretrieve(), sans épinglage de certificat ni vérification de l'intégrité de l'archive, et plusieurs chargeurs intégrés utilisent des URL http://. Un attaquant positionné sur le réseau peut intercepter le téléchargement en HTTP non chiffré et renvoyer un TAR malveillant sans compromettre le serveur en amont ; c'est pourquoi le vecteur est distant et ne requiert aucune interaction de l'utilisateur au-delà de l'appel du chargeur.
Un attaquant a besoin de :
< 2.6.0.Aucun privilège sur la cible et aucune interaction de l'utilisateur au-delà de l'appel du chargeur ne sont requis.
Ce qui suit a été exécuté contre le paquet réel, non modifié, en appelant directement la fonction _extract_archive de Cornac.
Version vulnérable (2.3.5). Un TAR contenant un membre ../../ est extrait dans un répertoire de cache ; le membre atterrit en dehors de ce répertoire et écrase un fichier appartenant à une application distincte :
import cornac.utils.download as d
import io, os, tarfile, tempfile