Привет! На связи Олег Казаков из Spectr.
В предыдущей части я рассказал о тестировании функционала на уязвимость до его попадания на продакшн. По итогам предыдущих статей мы можем проверить код на безопасность, собрать безопасные билды, проверить функционал на наличие уязвимостей. Теперь нам ничего не мешает развернуть данное приложение на продакшне.
Кажется, что проблем с безопасностью у нас нет, однако нужно не забывать, чтобывают уязвимости за пределами приложения, например на самом окружении.
Сегодня я расскажу о заключительном этапе DevSecOps — Deploy-time Checks.
![](https://habrastorage.org/getpro/habr/upload_files/023/e09/066/023e090664e9070a544108f5e6a02b66.png)
Цель этапа Deploy-time Checks
Цель этапа: контроль безопасности развернутого приложения.
В первую очередь сюда входит проверка самой инфраструктуры на безопасность.
Инфраструктура бывает разная:
это могут быть выделенные сервера или VDS;
ПО может быть установлено нативно, а может использоваться Docker;
приложение может быть развернуто через Docker Compose, а может — в Kubernetes.
Поэтому сложно придумать способ универсальной проверки инфраструктуры. Самый главный совет тут — использовать IaC (Infrastructure-as-Code).
IaC (Infrastructure as Code) — это метод управления и автоматизации IT-инфраструктуры, в котором конфигурация среды описывается и управляется через программный код. Вместо ручного управления серверами, сетями и другими элементами инфраструктуры администраторы и разработчики могут использовать IaC для создания, изменения и удаления инфраструктурных ресурсов автоматически.
С точки зрения безопасности IaC даёт следующие преимущества:
позволяет создавать одинаковую инфраструктуру в разных средах — dev, stage, prod, а это значит, что можно проверять безопасность инфраструктуры еще до попадания на продакшн;
код конфигураций является естественной документацией, что делает понимание текущего состояния инфраструктуры более прозрачным, т. е. нет каких-то недокументированных возможностей, которые могут быть в том числе и уязвимостями;
код конфигураций можно анализировать на наличие уязвимостей с помощью инструментов статического анализа (SAST), которые уже рассматривали во 2-й части статьи.
Kubesec
Вернемся вновь в инструменту SAST, среди большого списка инструментов можно увидеть Kubesec.
![](https://habrastorage.org/getpro/habr/upload_files/42d/119/865/42d1198650e65187036b0a88d95b3078.png)
То есть если в коде есть манифест кубера, то можно его анализировать с помощью Kubesec.
Кажется, все должно быть по аналогии с тем, что мы настраивали на этапе SAST, но есть нюанс: анализатор запускается тоже не под рутом, как и у phpcs-security-audit. Об этой проблеме я писал во 2-й части в блоке «Сложности при использовании SAST в Gitlab».
![](https://habrastorage.org/getpro/habr/upload_files/5a7/3b3/702/5a73b37028584137dc164d1658245ab6.png)
Это значит, что при использовании данного инструмента мы не можем устанавливать свои библиотеки и менять пользователя, т. к. на это нет прав.
И тут два решения:
сделать свой образ;
использовать только то, что входит в alpine linux.
В первом случае нужно поддерживать этот образ, реагировать на изменения. Во втором случае сложность только в том, что нет удобных инструментов для обработки json. И я пошёл по второму пути и написал скрипт с использованием AWK:
#!/bin/awk -f
BEGIN {
FS=":"
RS=",|\n"
}
/^\s*"vulnerabilities"/ {
vulnerabilities=1
}
/^\s*"severity"/ && vulnerabilities {
severity[++count]=$2
gsub(/"/, "", severity[count])
}
/^\s*"name"/ && vulnerabilities {
name[count]=$2
gsub(/"/, "", name[count])
}
/^\s*"location".*"file"/ && vulnerabilities {
file[count]=$NF
gsub(/"/, "", file[count])
}
END {
if (vulnerabilities) {
printf "| Severity | Name | File |\n"
printf "|------------------|--------------|--------------|\n"
for (i=1; i<=count; i++) {
printf "| %s | %s | %s |\n", gensub(/"/, "\"\"", "g", severity[i]), gensub(/"/, "\"\"", "g", name[i]), gensub(/"/, "\"\"", "g", file[i])
}
printf "Number of vulnerabilities: %d\n", count
}
exit count
}
Кстати, данный код мне сгенерировал Chat GPT, самому писать обработку json на AWK, конечно, то еще удовольствие. Пришлось долго отлаживать, но в итоге все получилось.
Далее все делаем по аналогии с тем, как делали во второй части:
включаем шаблон Security/SAST.gitlab-ci.yml;
добавляем задачу SAST;
добавляем обработку артефакта.
include:
- template: Security/SAST.gitlab-ci.yml
.sast-analyzer:
extends: sast
allow_failure: false
script:
- /analyzer run
- if [[ -f "$FILE_REPORT" ]]; then awk -f .gitlab/scripts/sast_kubesec.awk $FILE_REPORT; fi
sast:
variables:
SAST_EXCLUDED_ANALYZERS: "semgrep,bandit,gosec,flawfinder"
SCAN_KUBERNETES_MANIFESTS: "true"
FILE_REPORT: gl-sast-report.json
tags:
- docker
Отличия тут в том, что:
нам нужно вместо bash-скрипта вызывать awk-скрипт в задаче;
-
также тут есть параметры:
уже знакомый нам SAST_EXCLUDED_ANALYZERS, который отключает все другие SAST инструменты, если их не нужно использовать;
SCAN_KUBERNETES_MANIFESTS, который включает Kubesec, то есть для данного анализатора отдельный параметр включения и по умолчанию он выключен.
По итогу все работает — при наличии манифеста кубера с уязвимостями.
По итогу мы видим такой список:
![](https://habrastorage.org/getpro/habr/upload_files/617/2f1/dba/6172f1dbae3b70c8ed0fea151d068839.png)
Но кроме кубера есть и другие решения — как быть с ними?
Тут нам на помощь приходит решение, которое мы уже затрагивали в рамках 3-й части — IaC Scanning.
IaC Scanning
Здесь «под капотом» используется CheckMarx KICS, его можно применять в любой версии, и он умеет сканировать довольно много разных конфигурационных файлов:
![](https://habrastorage.org/getpro/habr/upload_files/16e/5a0/1ec/16e5a01ecc7861023e331179fc7bc936.png)
Включаем точно так же, как и в 3-й части.
Добавляем скрипт обработки файла:
#!/bin/bash
vulnerability_count=$(cat $FILE_REPORT | jq --raw-output '.vulnerabilities | length')
if [ ${vulnerability_count} -gt 0 ]; then
echo "| severity | name | location |"
echo "|------------------|--------------|------------------|"
_jq() {
echo ${row} | base64 --decode | jq -r ${1}
}
for row in $(cat $FILE_REPORT | jq -r '.vulnerabilities[] | @base64'); do
echo '|' $(_jq ".severity") '|' $(_jq ".identifiers[0].name") '|' $(_jq ".location.file")':'$(_jq ".location.start_line") '|'
done
fi
exit $vulnerability_count
Добавляем шаблон и задачу:
include:
- template: Security/SAST-IaC.latest.gitlab-ci.yml
kics-iac-sast:
variables:
FILE_REPORT: gl-sast-report.json
NAME_OF_CI_SCRIPT: "sast_iac"
tags:
- docker
allow_failure: false
script:
- !reference [.analyzer_run, script]
Смотрим результат, тут уязвимости Dockerfile, Kubernetes и Terraform:
![](https://habrastorage.org/getpro/habr/upload_files/947/f0d/95f/947f0d95f44c01cc0d4934a766809985.png)
Добавил файл OpenApi из 4-й статьи, — теперь появились уязвимости и в нем:
![](https://habrastorage.org/getpro/habr/upload_files/0f5/5b4/0f1/0f55b40f10aa498bcfb1616889c22d53.png)
На первом скрине есть пункты с уровнем Info, которые часто не являются уязвимостями. Например, в Dockerfile найдена info уязвимость Healthcheck Instruction Missing, то есть отсутствует инструкция HEALTHCHECK:
![](https://habrastorage.org/getpro/habr/upload_files/45a/ae9/865/45aae986589b45f58c4e22e2ba963a04.png)
Данная инструкция не является обязательной, и на нее мы уже натыкались в рамках 3-й части — тогда мы просто добавили ее, чтобы не было срабатывания. В рамках настройки сканера в GitLab есть возможность отключить некоторые правила, однако эта функциональность доступна только в версии Ultimate. Если в версиях ниже добавить файл sast-ruleset.toml по инструкции, то GitLab видит этот файл, но говорит, что кастомизация правил не включена:
![](https://habrastorage.org/getpro/habr/upload_files/2f2/29d/316/2f229d316c6f084558e0c1b32fa95d25.png)
Как же быть? Тут нам поможет тот факт, что мы сами обрабатываем артефакт, то есть мы можем просто игнорировать те уязвимости, которые мы не считаем таковыми.
Можно придумать свой формат для указания исключений, а можно использовать тот же файл, что используется стандартно. Я решил пойти по второму варианту.
Для начала добавляем файл sast-ruleset.toml в папку .gitlab, как сказано в документации:
[kics]
[[kics.ruleset]]
disable = true
[kics.ruleset.identifier]
type = "kics_id"
value = "b03a748a-542d-44f4-bb86-9199ab4fd2d5"
Дорабатываем задачу kics-iac-sast:
kics-iac-sast:
variables:
FILE_REPORT: gl-sast-report.json
NAME_OF_CI_SCRIPT: "sast_iac"
tags:
- docker
allow_failure: false
script:
- echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories;
- apk add toml2json
- !reference [.analyzer_run, script]
Добавились две строки (до строки !reference), тут я ставлю либу toml2json (для удобной обработки toml-файлов) и добавляю тестовый репозиторий, т. к. либа toml2json находится только там.
Ну а самое большое изменение в скрипте обработки артефакта:
#!/bin/bash
EXCLUSION_FILE=".gitlab/sast-ruleset.toml"
if [ -f "$EXCLUSION_FILE" ]; then
EXCLUSION_LIST=$(toml2json < "$EXCLUSION_FILE" | jq -c '.kics.ruleset[]')
else
EXCLUSION_LIST=""
fi
echo $EXCLUSION_LIST
# Функция проверки, является ли уязвимость исключением
is_excluded() {
local type="$1"
local value="$2"
for exclusion in $EXCLUSION_LIST; do
exclude_type=$(echo "$exclusion" | jq -r '.identifier.type')
exclude_value=$(echo "$exclusion" | jq -r '.identifier.value')
if [[ "$exclude_type" == "$type" && "$exclude_value" == "$value" ]]; then
return 0
fi
done
return 1
}
# Подсчет уязвимостей с учетом исключений
vulnerability_count=0
for row in $(cat $FILE_REPORT | jq -r '.vulnerabilities[] | @base64'); do
_jq() {
echo ${row} | base64 --decode | jq -r ${1}
}
identifier_type=$(_jq ".identifiers[0].type")
identifier_value=$(_jq ".identifiers[0].value")
if ! is_excluded "$identifier_type" "$identifier_value"; then
((vulnerability_count++))
fi
done
# Вывод уязвимостей, если они есть
if [ ${vulnerability_count} -gt 0 ]; then
echo "| severity | name | location |"
echo "|------------------|--------------|------------------|"
for row in $(cat $FILE_REPORT | jq -r '.vulnerabilities[] | @base64'); do
_jq() {
echo ${row} | base64 --decode | jq -r ${1}
}
identifier_type=$(_jq ".identifiers[0].type")
identifier_value=$(_jq ".identifiers[0].value")
if ! is_excluded "$identifier_type" "$identifier_value"; then
echo '|' $(_jq ".severity") '|' $(_jq ".identifiers[0].name") '|' $(_jq ".location.file")':'$(_jq ".location.start_line") '|'
fi
done
fi
exit ${vulnerability_count}
Тут я сначала перегоняю toml в json, чтобы удобно манипулировать как json-строкой.
Далее идет функция is_excluded, которая принимает на вход тип сканера и id-уязвимости, ищет их в файле.
В самом конце блоки — для подсчета количества уязвимостей и для вывода списка уязвимостей в задаче с использованием функции is_excluded.
Подобным образом можно исключать какие-либо уязвимости и в других сканерах.
Таким образом мы можем проверять инфраструктуру в CI/CD. Все наработки по коду лежат в этом репозитории.
За пределами CI/CD
На этом инструменты, которые можно встроить в CI/CD-пайплайн, закончились, но, к сожалению, не заканчиваются уязвимости.
Какие еще проблемы могут быть?
Уязвимость нулевого дня — могут появляться новые уязвимости в ЯП, инфраструктуре, различных зависимостях и могут быть ситуации, когда сканеры еще не знают об этих уязвимостях.
Тут главный совет — всегда обновлять используемое ПО, т. к. разработчики постоянно внедряют различные патчи для предотвращения уязвимостей.Существуют атаки, против которых зачастую сложно бороться на уровне приложений, например DDOS, MITM, и с ними проще бороться на сетевом уровне.
Для защиты используются различные межсетевые экраны и IDPS-инструменты (Системы обнаружения и предотвращения вторжений).Человеческий фактор. Люди зачастую самое слабое звено в безопасности, социальная инженерия может творить чудеса, к сожалению. Поэтому очень важно не давать пользователям/сотрудникам слишком много прав и тщательно следить за этим, особенно если это касается инфраструктуры.
Для защиты можно повсеместно использовать IAM- и OPA-инструменты для ограничения доступов и управления этими ограничениями, а также для сотрудников проводить различные тренинги/обучения защите от социальной инженерии и психологических манипуляций.
Еще несколько инструментов, которые можно использовать для обнаружения уязвимостей:
Существует много организаций на рынке, которые предоставляют услугу пентеста на заказ, можно периодически проводить такой аудит.
Внедрять программы Bug Bounty, то есть поощрять людей за нахождение уязвимостей.
Также важно именно своевременно реагировать на уязвимости, а для этого нужно иметь план реагирования на инциденты. Здесь помогут IRP (Incident Response Platform) — инструменты, которые позволяют автоматически выполнять действия на разных этапах реагирования на угрозы, и SIEM-решения (например, OSSIM, MozDef, Wazuh), которые помогут в сборе и анализе информации о событиях безопасности.
Думаю, это не полный список инструментов, — пишите в комментариях, что можно еще использовать для защиты приложений и инфраструктуры.
Заключение
Сегодня мы поговорили о заключительном этапе DevSecOps, на этом данный цикл статей завершен. Надеюсь, эта информация была полезна и поможет избавиться от большого количества уязвимостей в ваших продуктах.
Напоминаю, что все наработки по всем частям собраны в этом репозитории.
Vermut666
Я ожидал на этом этапе DAST, а не ещё один SAST. Никогда не понимал, как его можно нормально в пайплайн втыкать. Думал вот в статье за меня кто-то разобрался.
OlegSpectr Автор
Про DAST есть в предыдущей части 4