Содержание статьи
Начало истории
Представь: обычный будний день аналитика. Он работает со своим инстансом VS Code Server, поднятым в кластере Kubeflow, пишет скрипты на Python и запускает их в Jupyter Notebook с использованием файлов с расширением .. А для анализа визуализирует результаты с помощью библиотеки BokehJS.
Что есть что
- Jupyter Notebook — это среда разработки, где сразу можно видеть результат выполнения кода и его отдельных частей. Отличие от привычной среды разработки состоит в том, что код можно разбить на куски и выполнять их в любом порядке.
-
.— расширение файла формата Jupyter Notebook, который хранит код, текст и результаты выполнения в едином интерактивном документе.ipynb - Kubeflow — это платформа для работы с ML-задачами на Kubernetes. Она предоставляет пользователям удобный интерфейс для создания Jupyter-ноутбуков, запуска экспериментов, обучения и деплоя моделей. Фактически это инфраструктурный слой для data science, где в одной среде объединены сервер VS Code, Jupyter, пайплайны ML и так далее.
- BokehJS — фронтенд‑библиотека на JavaScript, которая отрисовывает интерактивные графики, зачастую незаменима в ML-проектах: она помогает быстро визуализировать данные, отладить модели прямо в Jupyter-ноутбуке.
В тот день в работе у аналитика был массив с User-Agent’ами пользователей, которые заходили на сайт компании. Задача простая — проанализировать этот массив с заголовками и построить график, на котором будет наглядная картина: с каких устройств чаще посещают сайт. Но вместо привычного графика на экране появляются странные строчки кода. Произошла внезапная XSS-инъекция там, где ее совсем не ждешь. То, что начиналось как рутинный анализ данных, внезапно превратилось в историю об уязвимости, где обычная визуализация данных неожиданно помогла распознать атаку на инфраструктуру.
Bug Bounty превращается в исследование
Однажды утром, выпив чашку кофе и посмотрев котиков в интернете, я обнаружил новый репорт в нашей программе Bug Bounty: ресерчер сообщил об уязвимости blind XSS. А конкретнее на его внешний сервер пришел отстук с нашего поддомена, где расположен кластер Kubeflow. У меня моментально возникло несколько вопросов: что послужило источником проблемы? Как не допустить такого в будущем? Но главное — какой максимальный ущерб от эксплуатации уязвимости смог бы нанести злоумышленник, если бы узнал о ней?
Как выяснилось, источник этого отстука оказался совсем обычным. Один из аналитиков, выполняя скрипты в поднятом VS Code Server в Kubeflow, обрабатывал данные — часть которых имела пользовательский ввод. Далее, без ручной очистки опасных символов, данные напрямую подставлялись в функции для генерации графиков. Никто не предполагал, что в этих данных могут скрываться опасные конструкции, тем более что мы все привыкли доверять таким надежным инструментам, как Jupyter или VS Code. Работая с ними, мы не ожидаем угроз безопасности, и кажется, что в таких популярных продуктах XSS вообще невозможны. Но на практике все оказалось иначе...
В этой статье мы не будем рассматривать тему Bug Bounty и все, что было связано с репортом. Вместо этого давай сосредоточимся на общей картине необычного вектора атаки и исследуем ее возможности в контексте кластера Kubeflow.
Проблемная ячейка
Давай воспроизведем полную цепочку атаки в тестовой среде, а начнем мы с простой ячейки в файле Jupyter Notebook.
info
Code cell — ячейка с кодом (например, Python), которую можно запустить отдельно в Jupyter Notebook, результат выполнения отображается прямо под ней.
Вместо обычных чисел в график Bokeh мы передадим XSS-нагрузку (будем учитывать, что мы анализируем заголовок User-Agent, который пользователь может контролировать). Наша XSS-нагрузка будет выглядеть так:
cell.ipynb
# Импортируем модули библиотеки from bokeh.io import show, output_notebook from bokeh.layouts import row from bokeh.plotting import figure opts = dict(width=250, height=250, min_border=0) # Задаем параметры для графика payload = "</script>'">%0a"><video src=//ojb4h3tlwo1mc2pvslxp81broiu9i06p.oastify.com controls='true'> '">%0a%0a">" # Наш User-Agent, который заменили полезной нагрузкой p1 = figure(**opts) # Создаем объект фигуры r1 = p1.circle([1,2,3], [payload], size=20) # Рисуем точки, по оси Y вставляем нагрузку t = show(row(p1)) # Отображаем график p1, обернув его в строку для рендеринга в ячейкеЗапустив ячейку в VS Code с расширением для работы с файлами Jupyter Notebook, замечаем, что вместо отображения графика отработал наш пейлоад.

Мы можем сохранить файл и тем самым увидеть, что хранится в выходной ячейке.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»
