
Path traversal (Tar Slip) no Cornac via _extract_archive (CVE-2026-43637)
Gravidade: Alta, CVSS 4.0 8.8, Crítico, CVSS 3.1 9.1(atribuído pela VulnCheck, a CNA)
Vetor (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
Vetor (v4.0): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
Afetados: cornac < 2.6.0
Corrigido em: 2.6.0
CWE: CWE-22 (Limitação Incorreta de um Nome de Caminho a um Diretório Restrito, 'Path Traversal')
Relatado por: Rahul Karne e Bharath Kumar Reddy Janumpally
CNA: VulnCheck
Publicado: 15 de julho de 2026
Os carregadores de conjuntos de dados do Cornac baixam e descompactam arquivos automaticamente, e o descompactador confiava em todos os caminhos dentro do arquivo.
O Cornac é um framework de aprendizado de máquina para sistemas de recomendação. Seus carregadores de conjuntos de dados integrados buscam arquivos pela rede e os extraem sem nenhuma etapa de confirmação. A rotina de extração _extract_archive() em cornac/utils/download.py chama archive.extractall() sem validação dos caminhos dos membros. Um arquivo TAR cujos membros contenham sequências ../, caminhos absolutos ou entradas de symlink e hardlink, portanto, grava arquivos em locais arbitrários do sistema de arquivos, em qualquer lugar onde o processo em execução possa gravar, totalmente fora do diretório de cache pretendido. Vários carregadores buscam via HTTP simples, portanto um atacante posicionado na rede pode substituir um arquivo malicioso em trânsito e fazer com que ele seja extraído no momento em que um carregador é chamado.
Gravação arbitrária de arquivos com caminho e conteúdo totalmente controlados pelo atacante, limitada apenas pelas permissões do processo que executa o Cornac. Trata-se de uma primitiva de gravação, não de leitura, portanto não há divulgação direta, mas a gravação arbitrária de arquivos é um caminho bem estabelecido para a execução de código e para a negação de serviço:
.py em site-packages, um arquivo de inicialização de shell ou uma entrada de tarefa agendada faz com que o código do atacante seja executado no próximo import, shell ou job.Quem é afetado: qualquer uso de cornac < 2.6.0 que chame um carregador de conjunto de dados que baixe e extraia um arquivo que o atacante possa controlar ou interceptar. Como os carregadores extraem automaticamente, nenhuma etapa além da chamada comum do carregador é necessária.
Quem não é afetado:
2.6.0 ou posterior, onde a extração valida cada caminho de membro.| Métrica | Valor | Fonte |
|---|---|---|
| Downloads, todos os tempos | 4.1M | pepy.tech/projects/cornac |
| Downloads, últimos 30 dias | 67.8K | pepy.tech |
| Uso típico | Pesquisa e ensino de sistemas de recomendação; carregadores de conjuntos de dados buscam automaticamente pela rede, alguns via HTTP simples | inerente ao framework |
_extract_archive() em cornac/utils/download.py extrai entradas ZIP e TAR por meio de um único caminho de código e, no caso do TAR, chama extractall() sem verificação de caminho:
# 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 os nomes dos membros, portanto um membro chamado ../../somewhere/file é resolvido fora de extract_path e é gravado lá.
A cadeia de chamadas é totalmente automática a partir de uma chamada de carregador:
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
O zipfile do Python sanitiza sequências ../ durante a extração, portanto o ramo ZIP desta mesma função não é explorável no Python moderno. O tarfile do Python não sanitiza os caminhos dos membros (antes do 3.12, e somente com um filtro explícito depois disso). Como o Cornac executa ambos os formatos por meio da mesma chamada extractall(), o ramo ZIP é seguro e o ramo TAR é totalmente explorável pelas mesmas linhas. A assimetria é fácil de passar despercebida precisamente porque o código parece uniforme entre os dois formatos.
O Cornac faz download com urllib.request.urlretrieve(), sem fixação de certificado e sem verificação de integridade do arquivo, e vários carregadores integrados usam URLs http://. Um atacante posicionado na rede pode interceptar o download via HTTP simples e retornar um TAR malicioso sem comprometer o servidor upstream, motivo pelo qual o vetor é remoto e não exige interação do usuário além da chamada do carregador.
Um atacante precisa de:
< 2.6.0.Nenhum privilégio no alvo e nenhuma interação do usuário além da chamada do carregador são necessários.
O seguinte foi executado contra o pacote real e não modificado, chamando a própria _extract_archive do Cornac diretamente.
Versão vulnerável (2.3.5). Um TAR contendo um membro ../../ é extraído em um diretório de cache; o membro é gravado fora desse diretório e sobrescreve um arquivo pertencente a um aplicativo separado:
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")