Привет! На связи Олег Казаков из 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: о инструментах и методиках проверки на этом этапе.