
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
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
Risultato verificato:
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
Il membro benigno ha scritto all'interno della cache come previsto; il membro ../../ ha scritto nella directory dell'applicazione separata e l'importazione di quell'applicazione ora fallisce.
Dimostrazione completa dell'attacco. poc_exploit.py in questo repository esegue l'intera catena end-to-end: un server HTTP dell'attaccante consegna il TAR malevolo, un loader che replica la catena di chiamate cache() più _extract_archive() di Cornac lo scarica ed estrae automaticamente a partire da una singola chiamata load_feedback(), e due file vengono sovrascritti nella directory di un'applicazione separata. La _extract_archive() vulnerabile è ripresa testualmente dal sorgente di Cornac.
Versione corretta (2.6.0). Lo stesso archivio di traversal viene rifiutato prima che venga scritto qualsiasi cosa:
ValueError: Blocked path traversal attempt in archive: ../victim/app.py
victim app.py after: def run(): return 'healthy' (unchanged)
Aggiorna a cornac 2.6.0 o versioni successive:
pip install --upgrade "cornac>=2.6.0"
2.6.0 sostituisce la extractall() senza protezioni con un helper _safe_extract() che risolve ogni percorso dei membri tramite os.path.realpath e rifiuta qualsiasi destinazione che non rimanga all'interno della directory di estrazione, consentendo solo file regolari e directory e bloccando le voci di symlink, hardlink e dispositivi.
Se non puoi aggiornare immediatamente: non chiamare loader di dataset che scaricano da canali non affidabili o in HTTP in chiaro e non estrarre archivi TAR da fonti che non controlli. Il download tramite HTTPS riduce il rischio di intercettazione durante il trasferimento, ma non elimina il rischio derivante da un server a monte malevolo o compromesso.
VulnCheck (il CNA) ha assegnato 8.8 (Alto) secondo 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'archivio malevolo viene consegnato tramite la rete e diversi loader scaricano in HTTP in chiaro senza controllo di integrità.AC:L: creare un TAR di traversal è banale e l'attacco funziona in modo deterministico a ogni invocazione.PR:N: l'attaccante non necessita di alcun account né di un punto d'appoggio sul target.UI:N: il loader scarica ed estrae automaticamente; una singola chiamata del loader innesca l'intera catena senza alcuna fase di conferma.VC:N: la primitiva è solo di scrittura, quindi non viene dichiarato alcun impatto sulla riservatezza.VI:H / VA:H: l'attaccante controlla completamente il contenuto scritto e può distruggere i file su cui il processo ha diritti di scrittura, quindi l'integrità e la disponibilità del sistema interessato sono elevate.SC:N / SI:N / SA:N: il CNA ha valutato l'impatto come limitato all'autorità del sistema vulnerabile, piuttosto che a un sistema successivo distinto.L'8.8 assegnato dal CNA è il valore autorevole per questa vulnerabilità.
| Data | Evento |
|---|---|
| 3 maggio 2026 | Vulnerabilità identificata |
| 4 maggio 2026 | Segnalata (divulgazione coordinata) |
| 14 luglio 2026 | Correzione integrata (PR #709, commit 8a50be7) |
| 15 luglio 2026 | Rilasciata la versione corretta 2.6.0 |
| 15 luglio 2026 | CVE-2026-43637 pubblicata da VulnCheck |
Scoperta e segnalata da Rahul Karne (ricercatore di sicurezza e IEEE Senior Member) e Bharath Kumar Reddy Janumpally, coordinata tramite VulnCheck. Le divulgazioni correlate di Rahul includono CVE-2026-65321 (SQL injection in PyAthena) e CVE-2026-63720 (code injection in datamodel-code-generator).
Contatto: [email protected] · GitHub: rahulreddykarne
8a50be7: https://github.com/PreferredAI/cornac/commit/8a50be72c11569b6747c6b96d6e31a0a1962f1a8Richieste dei media: [email protected]. PoC completo (server dell'attaccante, archivio di traversal, applicazione vittima) e ulteriori dettagli tecnici disponibili su richiesta.