Привет, Хабр!
С вами Анастасия Березовская, инженер по безопасности процессов разработки приложений в Swordfish Security. В этой статье мы разберемся, как пройти авторизацию в DAST-сканере с помощью прокси. Почему мы решили взяться за эту тему? Сейчас расскажем.
Предыстория
Представьте: на дворе 2024 год, вы работаете AppSec-специалистом в российской компании и решаете приобрести готовый сканер DAST, который запускается одной кнопкой. Идет импортозамещение в IT-отрасли, отечественные вендоры создали несколько решений на замену зарубежным. Самые популярные – Solar appScreener, Positive Technologies Black Box и SolidWall DAST. Все они включают опцию прохождения аутентификации по форме на странице.
И тут возникает проблема: у вас SSO или другая user-friendly многостраничная авторизация. А в настройках страница обязательно должна содержать два поля для ввода логина и пароля и одну кнопку. Конечно, можно просто подставить валидные cookie или токен, но тогда готовое решение каждый раз будет просить вас повторно пройти аутентификацию и обновить необходимую информацию для доступа. Согласитесь, выглядит как отличная задача для стажеров вашего отдела.
“А что, если стажеров мало? И мы вообще-то платим деньги за готовый сканер!” – подумаете вы и решите написать поставщику. Диалог получится примерно такой:
— Здравствуйте! У нас есть двухэтапная авторизация, при которой для каждого поля рендерится отдельная страница в браузере. Сделайте так, чтобы вторым фактором была дата рождения моей бабушки.
— Добрый день! Пожалуйста, заполните заявку на добавление функции в нашем публичном roadmap, и, если она соберет 300 000 уникальных подписей, мы возьмем ее в работу к 2125 году.
TL;DR: рассказываем, как решить данную проблему.
Локально:
Устанавливаем Playwright и запускаем Codegen с запоминанием скрипта на TypeScript и локального хранилища браузера;
Проходим аутентификацию в веб-сервисе;
Получаем файл JSON с данными хранилища и пишем небольшую утилиту на любимом языке программирования, чтобы из файла распарсить значения заголовков или cookie для аутентификации.
На прокси-сервере запускаем:
Скрипт, сгенерированный инструментом Codegen;
Парсер JSON-файла;
Сервер nginx, настроенный как прокси на проверяемый веб-сервис с прокидыванием полученных заголовков.
Запускаем сканирование на адрес прокси-сервера.
Profit! Теперь вы можете поручить стажеру более существенные задачи.
Мотивация
Примерно такой вымышленный сценарий послужил мотивацией для создания доработки готового проекта. Давайте подробнее поговорим о том, как решить проблему аутентификации. Примеры взяты из опыта работы с российскими DAST-сканерами Solar appScreener и PT Black Box.
Настройки аутентификации в обеих программах стандартны: токен, cookie, ключ в запросе, логин/пароль для HTTP Basic и автоматическая аутентификация по форме. Мы условно разделяем их на статические и динамические. Первые настройки используют подготовленные заранее данные доступа (токен, cookie и ключ), вторые подразумевают, что сканер получит эту же информацию, сыграв роль пользователя в браузере.
Проблемы начинаются со свойства времени жизни. Это не касается аутентификации по API-ключу и HTTP Basic. Сложности возникают с токенами и cookie при автоматическом запуске сканера в CI/CD или по расписанию. Недостатки аутентификации по форме видны из настроек: логин и пароль должны располагаться на одной странице с действием "Нажать на кнопку". И не шагом больше (или меньше).
Установить многофакторную аутентификацию для сканирования тоже не получится. Отметим, что в решении от Positive Technologies есть опция "Последовательность авторизаций", но она ограничена набором предыдущих вариантов получения доступа.
Вдохновение
Концепция решения проблемы авторизации заключается в установке прокси между DAST-инструментом и сканируемым сервисом. В этом случае аутентификация происходит перед началом сканирования. Однако нам нужно создать способ “универсальной” авторизации, который будет работать и в тех случаях, где стандартные настройки доступа сканеров неприменимы.
В ходе исследования мы обратили внимание на сканеры NetSparker (Invicti) и OWASP ZAP. В интерфейсе первого есть опция "Записать действия пользователя". Для этого в веб-браузере нужно выполнить те же самые шаги, которые клиент обычно совершает для аутентификации. Netsparker автоматически отслеживает и записывает ввод логина и пароля, нажатие клавиш и переход по страницам. При сканировании веб-приложения он использует сохраненные действия клиента для авторизации в системе.
Похожий способ есть и в Open Source-сканере OWASP ZAP. Во встроенном браузере мы также выполняем аутентификацию. По ее завершении записанные действия сохраняются в виде Zest-скрипта. OWASP ZAP повторяет все шаги и выполняет аутентификацию автоматически.
Поиск
Не углубляясь в дискуссию о границе между плагиатом и вдохновением, мы решили применить аналогичный подход. Подобный метод записи действий пользователя встречается в области тестирования веб-приложений. Легким движением руки По запросу в Google нашелся плагин Selenium IDE. Но его форма расширения для браузера и интерфейс показались нам неудобными, а дизайн сайта и вовсе наводил тоску.
Давний опыт Fullstack-разработки автора напомнил о фреймворке Playwright от Microsoft. Его преимущества не ограничиваются удобным сайтом и хорошей документацией. Фреймворк работает на более низком уровне, чем Selenium IDE, что позволяет обходить многие ограничения, связанные с внутренней архитектурой и реализацией браузеров. Он кажется более производительным за счет асинхронного выполнения действий. В нашей задаче это не основной, но приятный бонус.
Самая главная возможность Playwright лично для нас — это Playwright Codegen. Данный инструмент перехватывает взаимодействие пользователя с браузером через Playwright API и генерирует код, который повторяет те же шаги: щелчок мышью или написание текста в поле ввода на сайте. Codegen представляет собой интерфейс командной строки, он самостоятельно запускает выбранный браузер. Это позволяет удобно обернуть решение в контейнер. Инструкция по его использованию ограничивается строкой запуска без прописывания шагов по установке всего окружения (и тем более расширений для браузера).
Настройка
При запуске Codegen есть опция сохранения конечного состояния браузера и его хранилища в JSON-формате. Их обрабатывает утилита jq, извлекая нужную для аутентификации информацию из хранилища и файлов cookie. После отработки скрипта эти данные автоматически вставляются в конфигурационный файл прокси-сервера.
Модуль универсальной аутентификации разделяется на две части: пользовательскую и серверную. Первая отвечает за запуск Codegen, а вторая – за запуск сгенерированного скрипта и преобразование сохраненных данных доступа в конфигурационный файл прокси-сервера.
Как упоминалось выше, для удобства запуска Codegen все компоненты обернуты в контейнер. Если вы еще не работали с графикой в контейнерах (как и автор до этой задачи), то могут возникнуть сложности. Чтобы запустить Playwright Inspector и браузер из контейнера, можно применить хостовую графическую систему, например монтировать в контейнер файлы X Server-а ОС.
В процессе тестирования мы столкнулись с непредвиденной ситуацией: простой перезапуск сгенерированного скрипта не всегда приводил к ожидаемому результату. Например, страница не успевала прогрузиться, из-за этого веб-хранилище браузера заполнялось не до конца – нужных токенов просто не оказывалось в сохраненных файлах. Нам приходилось вручную добавлять методы ожидания загрузки страницы или элементов в скрипт. В некоторых случаях значения токена или других учетных данных находились в сессионном хранилище, которое Playwright не умеет сохранять – тогда мы сами дописывали несколько строк кода.
Чтобы решить эту проблему, мы разделили процесс создания скрипта на несколько этапов. В начале запустили генератор, но с сохранением тестового файла. Далее исполняется только что созданный и предварительно подготовленный тест, который ожидает полной загрузки страницы, записывает URL успешного входа и сессионное хранилище браузера в файл. При компоновке этих тестов создается конечный скрипт, который будет запускаться на сервере. Этот шаг может показаться излишним, но по-другому все равно пришлось бы "вставлять" адрес успешной авторизации. Кроме того, целостный скрипт для запуска более понятный и удобный для инженера, так как позволяет добавлять в него дополнительные сигнальные строки или элементы успешной аутентификации (как это часто представлено в оригинальных настройках авторизации DAST-сканеров).
Улучшения
Так как автор является инженером по информационной безопасности, мы не можем оставить запуск произвольного кода на сервере без контроля. Поэтому в реализацию добавляем этап с собственными правилами для ESLint. Он написан по принципу "запрещаем всё, что не разрешено". Допускается только:
Импортирование пакетов Playwright и fs;
Вызов функции require;
Ограниченный набор вызываемых методов для объекта browser;
Вызов методов для объектов browser и page.
Конечно, это не защищает приложение от всех возможных действий злоумышленников, но точно усложняет им задачу. Да и DAST с нашим прокси запускаются в тестовом окружении.
Отправлять файлы на сервер через SCP — моветон, добавили в реализацию часть выгрузки скрипта из файлового хранилища MinIO. Этот способ может быть заменен на другой.
Тестирование
Мы протестировали авторизацию через прокси на приложении Testsparker. В нем есть одна XSS-инъекция, которая видна только залогиненному пользователю. И да, метод с прокси действительно работает. Пруфы, не фотошоп :)
Результат
Разработанное прокси значительно облегчает прохождение аутентификации в DAST-сервисах. Достаточно один раз в полуавтоматическом режиме создать скрипт, который в дальнейшем автоматически будет повторять необходимые действия и подставлять параметры авторизации к каждому отправляемому запросу на всех сканированиях без участия человека.
Есть кейсы, когда и такие костыли не помогают. В SPA-приложениях авторизация пользователя часто определяется по наличию параметров в хранилище – их добавление в запросы ничего не дает. Один из таких примеров – главная страница приложения Juice Shop.
Незначительный минус: для сканера все пути теперь доступны с аутентификацией. Поэтому обнаружить, какие ручки не прикрыты, можно только двумя запусками.
Автор: Анастасия Березовская