
Path traversal (Tar Slip) in Cornac tramite _extract_archive (CVE-2026-43637)
Gravità: Alta, CVSS 4.0 8.8, Critica, CVSS 3.1 9.1(assegnato da VulnCheck, il CNA)
Vettore (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
Vettore (v4.0): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
Versioni interessate: cornac < 2.6.0
Corretta in: 2.6.0
CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory, 'Path Traversal')
Segnalata da: Rahul Karne e Bharath Kumar Reddy Janumpally
CNA: VulnCheck
Pubblicata: 15 luglio 2026
I loader di dataset di Cornac scaricano e decompongono automaticamente gli archivi, e il decompressore si fidava di ogni percorso all'interno dell'archivio.
Cornac è un framework di machine learning per sistemi di raccomandazione. I suoi loader di dataset integrati recuperano gli archivi dalla rete e li estraggono senza alcuna fase di conferma. La routine di estrazione _extract_archive() in cornac/utils/download.py chiama archive.extractall() senza alcuna validazione dei percorsi dei membri. Un archivio TAR i cui membri contengono sequenze ../, percorsi assoluti o voci di symlink e hardlink scrive pertanto file in posizioni arbitrarie del filesystem, ovunque il processo in esecuzione abbia diritti di scrittura, completamente al di fuori della directory di cache prevista. Diversi loader scaricano tramite HTTP in chiaro, quindi un attaccante in posizione di rete può sostituire un archivio malevolo durante il trasferimento e farlo estrarre nel momento stesso in cui viene chiamato un loader.
Scrittura arbitraria di file con percorso e contenuto completamente controllati dall'attaccante, limitata solo dai permessi del processo che esegue Cornac. Si tratta di una primitiva di scrittura, non di lettura, quindi non c'è divulgazione diretta di dati, ma la scrittura arbitraria di file è una via ben consolidata verso l'esecuzione di codice e il denial of service:
.py in site-packages, un file di avvio della shell o una voce di attività pianificata fa sì che il codice dell'attaccante venga eseguito alla successiva importazione, all'avvio della shell o all'esecuzione dell'attività.Chi è interessato: Qualsiasi utilizzo di cornac < 2.6.0 che richiami un loader di dataset che scarica ed estrae un archivio che l'attaccante può controllare o intercettare. Poiché i loader estraggono automaticamente, non è richiesto alcun passaggio oltre alla normale chiamata del loader.
Chi non è interessato:
2.6.0 o versioni successive, dove l'estrazione valida ogni percorso dei membri.| Metrica | Valore | Fonte |
|---|---|---|
| Download, complessivi | 4.1M | pepy.tech/projects/cornac |
| Download, ultimi 30 giorni | 67.8K | pepy.tech |
| Utilizzo tipico | Ricerca e didattica sui sistemi di raccomandazione; i loader di dataset scaricano automaticamente dalla rete, alcuni tramite HTTP in chiaro | insito nel framework |
_extract_archive() in cornac/utils/download.py estrae sia input ZIP che TAR tramite un unico percorso di codice e, nel caso TAR, chiama extractall() senza alcun controllo dei percorsi:
# 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
Nulla vincola i nomi dei membri, quindi un membro chiamato ../../somewhere/file viene risolto al di fuori di extract_path e lì scritto.
La catena di chiamate è completamente automatica a partire da una chiamata del loader:
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
Lo zipfile di Python neutralizza le sequenze ../ durante l'estrazione, quindi il ramo ZIP di questa stessa funzione non è sfruttabile su Python moderno. Il tarfile di Python non neutralizza i percorsi dei membri (prima della 3.12, e successivamente solo con un filtro esplicito). Poiché Cornac fa passare entrambi i formati attraverso la stessa identica chiamata extractall(), il ramo ZIP è sicuro e il ramo TAR è completamente sfruttabile attraverso le stesse righe. L'asimmetria è facile da trascurare proprio perché il codice appare uniforme tra i due formati.
Cornac scarica con urllib.request.urlretrieve(), senza certificate pinning e senza controllo di integrità dell'archivio, e diversi loader integrati utilizzano URL http://. Un attaccante in posizione di rete può intercettare il download in HTTP in chiaro e restituire un TAR malevolo senza compromettere il server a monte; è per questo che il vettore è remoto e non richiede alcuna interazione dell'utente oltre alla chiamata del loader.
Un attaccante ha bisogno di:
< 2.6.0.Non sono richiesti privilegi sul target né interazione dell'utente oltre alla chiamata del loader.
Quanto segue è stato eseguito sul pacchetto reale e non modificato chiamando direttamente _extract_archive di Cornac.
Versione vulnerabile (2.3.5). Un TAR contenente un membro ../../ viene estratto in una directory di cache; il membro finisce al di fuori di tale directory e sovrascrive un file appartenente a un'applicazione separata:
import cornac.utils.download as d
import io, os, tarfile, tempfile