Внедряем DevSecOps в процесс разработки. Часть 4. Этап Test-time Checks, обзор инструментов
Привет! На связи Олег Казаков из Spectr.
В предыдущей части статьи я рассказал о контроле безопасности артефактов сборки в процессе проверки на безопасность. Сегодня поговорим о следующем этапе DevSecOps — Test-time Checks.
Цель Test-time Checks
По итогам предыдущих статей мы можем проверить код на безопасность, собрать безопасные билды, развернуть эти билды на тестовых стендах и столкнуться с новыми уязвимостями.
Цель Test-time Checks: тестирование функционала. Обычно это классические автотесты и ручное тестирование, но можно использовать и другие инструменты. Разберем их подробнее.
DAST
DAST (Dynamic Application Security Testing) — это метод тестирования безопасности приложений. Он используется для выявления уязвимостей в веб-приложениях и сервисах в режиме реального времени. В отличие от статического анализа кода (SAST, о котором писали во второй статье), который анализирует исходный код приложения, DAST проверяет приложение в его работающем состоянии. Он симулирует атаки и проверяет, как оно реагирует на различные типы входных данных.
Это эмуляция действий злоумышленников.
OWASP ZAP
Самый популярный инструмент DAST — OWASP ZAP.
ОWASP
OWASP (Open Web Application Security Project) — всемирная некоммерческая организация, деятельность которой направлена на повышение безопасности ПО.
ZAP
ZAP (Zed Attack Proxy) — open-source инструмент для тестирования на проникновение, а также для поиска уязвимостей в web-приложениях.
DAST есть в GitLab, в его Ultimate-версии, но внутри него используется этот же OWASP ZAP.
Аналогично Gemnasium, про который мы говорили в предыдущей части, этот инструмент находится в открытом доступе, то есть его спокойно можно использовать: https://gitlab.com/gitlab-org/security-products/dast.
Единственная проблема — сложно разобраться со скриптом и тем, как он работает. Но тут на помощь приходит неплохая документация самого сервиса, остается только переложить это на возможности самого GitLab.
Изучив шаблон DAST в GitLab и параметры команды в документации OWASP ZAP, получаем следующее.
За основу мы берем конфигурации из предыдущей статьи (там уже есть стадии build и test). Создаем свою стадию (dast_custom):
stages:
- build
- test
- dast_custom
Создаем свою задачу, чтобы не было конфликта со стандартными у GitLab, указываем образ, указываем артефакты:
dast_custom:
stage: dast_custom
image: registry.gitlab.com/gitlab-org/security-products/dast
variables:
#DAST_FULL_SCAN_ENABLED: "true" # включение полного сканирования (пассивное и активное)
#DAST_BROWSER_SCAN: "true" # использовать браузерный сканер GitLab DAST
tags:
- docker
allow_failure: true
artifacts:
when: always
paths: [report.html]
script:
- mkdir /zap/wrk/
- /analyze -t $APP_LINK -r report.html
- cp /zap/wrk/report.html .
В самом скрипте создается рабочая папка, потом оттуда выносится артефакт. Отличия от задач в предыдущих статьях:
в переменной APP_LINK содержится адрес развернутого стенда, который мы будем «ломать»;
данная утилита уже делает вывод информации о проведенных тестах и найденных уязвимостях внутри job, поэтому не нужно делать дополнительную обработку;
утилита позволяет генерировать наглядный html-отчет, который мы непременно будем использовать.
Можно использовать вместо обёртки GitLab оригинальный образ ZAP, но оставим это на попозже.
Пример настройки в GitLab
Итак, задача написана, надо ее опробовать на чем-то. Для наглядного примера попробуем просканировать сервис от того же OWASP, который называется WebGoat. Это, по сути, Лаборатория пентестера, намеренно небезопасное веб-приложение, которое преследует цель обучения практической безопасности веб-приложения.
Также отдельно хочу отметить, что нередко в тестируемое приложение нельзя попасть без аутентификации, а проверить на уязвимости хочется. Для этого придут на помощь переменные в GitLab, которые позволяют проходить аутентификацию:
Можно просто дополнить блок variables в задаче, но делать так не рекомендую. Лучше вынести в переменные окружения в сам GitLab. Ниже пример заполнения переменных:
variables:
#DAST_FULL_SCAN_ENABLED: "true" # включение полного сканирования (пассивное и активное)
#DAST_BROWSER_SCAN: "true" # использовать браузерный сканер GitLab DAST
DAST_AUTH_URL: "http://webgoat.example.ru/WebGoat/login"
DAST_USERNAME: "test123"
DAST_PASSWORD: "test123"
DAST_USERNAME_FIELD: "id:exampleInputEmail1"
DAST_PASSWORD_FIELD: "id:exampleInputPassword1"
DAST_AUTH_VERIFICATION_SELECTOR: "id:restart-lesson-button"
Здесь указываются: ссылка, на которой находится форма авторизация; логин и пароль; селекторы элементов DOM, куда нужно вводить логин и пароль; селектор, по наличию которого можно понять, что авторизация прошла успешно.
После настройки и запуска видим результат сканирования, который тоже сам выводится в задаче:
Ниже показано, как выглядит html-отчет. Здесь есть вывод количества уязвимостей по уровням, далее — перечисление всех уязвимостей с выводом количества повторений этих уязвимостей и детальная информация по каждой уязвимости: описание, местоположение, ссылки на подробное описание в различных известных перечнях уязвимостей, типа CWE.
Режимы OWASP ZAP
Выше в коде job вы могли заметить закомментированные две переменные — DAST_FULL_SCAN_ENABLED и DAST_BROWSER_SCAN. Они позволяют выбирать режим тестирования. OWASP ZAP позволяет тестировать в двух режимах:
По умолчанию DAST в GitLab сканирует в пассивном режиме. Пассивное сканирование никоим образом не изменяет ни запросы, ни ответы и поэтому безопасно для использования.
Но также есть и активное сканирование, — в этом случае скрипт выполняет «атаки» и потенциально может работать в течение длительного времени, в зависимости от размеров приложения. Поэтому не рекомендую его использовать часто.
Переменная DAST_FULL_SCAN_ENABLED как раз указывает, в пассивном или активном режиме сканировать.
Также GitLab предлагает использовать свой браузерный сканер, для этого нужно установить DAST_BROWSER_SCAN в true.
Подробнее о режимах сканирования в GitLab можно прочитать тут.
Ниже вывод в задаче при полном сканировании:
Ниже отчет полного сканирования — видим, что появилась уязвимость высокого уровня — sql-инъекция:
Таким способом можно тестировать развернутое веб-приложение. Пассивное тестирование можно смело встраивать в свой CI/CD, так как выполняется оно довольно быстро.
Активное же может занимать очень много времени и вряд ли в рамках стандартного CI/CD-пайплайна оно целесообразно, но можно делать такие проверки регулярно, независимо от релизного цикла — либо в отрыве от CI/CD совсем, либо сделать проверки, например, по расписанию, данный функционал в GitLab есть.
DAST API
То, о чем я писал выше, — это, по сути, сканирование фронта. Как нам проверить бэкенд? Тут нам на помощь идет следующий инструмент от того же OWASP — API Scan.
API Scan — инструмент для выполнения сканирования API, определенных в OpenAPI, SOAP или GraphQL, через локальный файл или URL-адрес.
Работает он по аналогии с самим ZAP, более того, примерно год назад ZAP объединили с API Scan в один общий образ, то есть один и тот же образ можно использовать для обеих задач.
Интеграция ZAP — API Scan в GitLab
Приведу пример интеграции ZAP — API Scan в GitLab.
Добавляем стадию:
stages:
- build
- test
- dast_custom
- dast_api_custom
Добавляем задачу:
dast_api_custom:
stage: dast_api_custom
image: ghcr.io/zaproxy/zaproxy
tags:
- docker
artifacts:
when: always
paths: [report.html]
script:
- mkdir /zap/wrk/
- cp examples/dast_api/openapi.json /zap/wrk/openapi.json
- zap-api-scan.py -t openapi.json -f openapi -r report.html -I -d
- cp /zap/wrk/report.html .
В целом очень похоже на OWASP ZAP, отличия только в следующем:
здесь используется образ не от GitLab, а от самого OWASP, так как в GitLab нет его в открытом доступе;
вместо URL стейджа у нас тут можно указать либо URL документации, либо файл с описанием документации. В нашем примере используется чтение документации в формате OpenAPI из файла;
есть интересный параметр «-I». По сути это игнорирование ошибок. По-умолчанию, если будет найдена хоть одна уязвимость, то скрипт завершится с ошибкой и это приведет к падению всей задачи. Но нам нужен отчет, поэтому добавляем этот параметр.
Видим результат проверки: 96 проверок пройдено, найдено 5 уязвимостей:
Смотрим отчет, опять же все очень похоже на DAST:
Вот таким образом можем сканировать уже API бэкенда.
Заключение
Сегодня мы поговорили об одном из завершающих этапов DevSecOps. Все наработки по коду лежат в этом репозитории. А в заключительной части я расскажу о процессе проверки инфраструктуры — Deploy-time Checks: о инструментах и методиках проверки на этом этапе.