Добавить в корзинуПозвонить
Найти в Дзене
ПОИБЭ Channel

Используем Zap Baseline Scan для непрерывного сканирования сайта на уязвимости

Некоторое время назад возникло желание реинкарнировать свой Wordpress-блог. Параллельно возникло желание упорядочить и систематизировать накопленные знания для сдачи экзамена ECSA. Все это привело меня к развертыванию блога на отдельно стоящем сервере. Через некоторый промежуток времени ожидаемо возникли вопросы безопасности сайта, использующего один из самых популярных (потому и вечно уязвимых) движков.
В результате изысканий появилось это руководство по организации непрерывного сканирования сайта на уязвимости, которым и спешу поделиться с вами, дорогие читатели.
Большую часть материала можно использовать в том числе и для внедрения в CI/CD пайплайны.
Прежде всего нужно оценить границы проекта и ресурсы, которые мы готовы на это затратить. В данном конкретном случае нет задачи объять необъятное, кроме того бюджет проекта околонулевой. Планируемое регулярное сканирование должно выполнять функцию беглого взгляда, с помощью которого мы понимаем, как выглядит сайт со стороны мимопрох
Оглавление

Некоторое время назад возникло желание реинкарнировать свой Wordpress-блог. Параллельно возникло желание упорядочить и систематизировать накопленные знания для сдачи экзамена ECSA. Все это привело меня к развертыванию блога на отдельно стоящем сервере. Через некоторый промежуток времени ожидаемо возникли вопросы безопасности сайта, использующего один из самых популярных (потому и вечно уязвимых) движков.

В результате изысканий появилось это руководство по организации непрерывного сканирования сайта на уязвимости, которым и спешу поделиться с вами, дорогие читатели.

Большую часть материала можно использовать в том числе и для внедрения в CI/CD пайплайны.

Прежде всего нужно оценить границы проекта и ресурсы, которые мы готовы на это затратить. В данном конкретном случае нет задачи объять необъятное, кроме того бюджет проекта околонулевой. Планируемое регулярное сканирование должно выполнять функцию беглого взгляда, с помощью которого мы понимаем, как выглядит сайт со стороны мимопроходящих товарищей со злыми намерениями. Просто чтобы не выполнять эти проверки ежедневно вручную.

Нам понадобятся:

  • сторонний сервер на базе ОС Linux для выполнения заданий по расписанию;
  • образ ZAP Baseline Scan;
  • навыки написания Bash-скриптов и использования командной строки Linux.

Настраиваем автоматическое сканирование сайта сканером OWASP ZAP

Ввиду постановки задачи возьмем не полнофункциональный ZAP, а облегченный скрипт ZAP Baseline Scan. Запускать будем из образа Docker: это удобно, стильно, модно, молодёжно.
Есть несколько вариантов образа:

  • owasp/zap2docker-bare — минимальный образ, содержащий только необходимые зависимости (по заверениям OWASP, идеально подходит для интеграции с CI);
  • owasp/zap2docker-weekly — еженедельная сборка (которая почему то всегда «Updated a month ago»);
  • owasp/zap2docker-stable — наисвежайший стабильный образ;
  • owasp/zap2docker-live — наисвежайший, возможно, нестабильный образ.

Так как у меня есть некоторая свобода выбора и оперативный простор для экспериментов, я выбрал owasp/zap2docker-live (а вдруг повезет, и я исправлю какой-нибудь баг). Для более серьезных и денежных проектов, конечно стоит выбрать стабильную версию.

Эмпирическим путем были подобраны оптимальные параметры запуска (более подробно можно почитать в Wiki проекта здесь и здесь):

docker run -v /tmp/zap/:/zap/wrk/:rw -t owasp/zap2docker-live zap-baseline.py -t https://blog.tyutin.net/ru/ -j -a -m 5 -r blog_tyutin_net-$(date "+%Y-%m-%d").html -J blog_tyutin_net-$(date "+%Y-%m-%d").json

Досконально разберем её опции:

  • docker run — сканирование запускается в Docker — удобно, модно, стильно, молодёжно;
  • -v /tmp/zap/:/zap/wrk/:rw — монтируем каталог для сохранения файлов отчетов;
  • -t — предоставляем сканеру терминал для вывода информации на экран;
  • owasp/zap2docker-live — образ, который будет использован для сканирования, live содержит самые свежие обновления;
  • zap-baseline.py — непосредственно скрипт сканирования;
  • -t blog.tyutin.net/ru — цель сканирования;
  • -j — запуск ajax-паука: пусть бегает, жалко что ли;
  • -a — дополнительные правила, подробнее о которых можно почитать здесь;
  • -m 5 — даем пауку 5 минут на то, чтобы обежать сайт (по умолчанию это значение равно 1);
  • -r blog_tyutin_net-$(date «+%Y-%m-%d»).html — сохранение отчета в html-файл, включив в имя файла название сайта и дату сканирования (этот файл можно скинуть в Telegram или отправить по электронной почте);
  • -J blog_tyutin_net-$(date «+%Y-%m-%d»).json — сохранение отчета в html-файл, включив в имя файла название сайта и дату сканирования (этот файл удобно парсить для передачи в Telegram консолидированной информации).

В результате выполнения этой команды мы получим довольно интересный отчёт, который покажет нам проблемы безопасности, которые по мнению OWASP ZAP имеются на сайте:

Вот так выглядит отчёт в консоли...
Проблема этого отчета в том, что он большой. Необходимость ежедневно вчитываться в него убьёт все желание это делать. Поэтому переходим к следующему разделу.

Консолидируем данные из отчета OWASP ZAP

Как было обозначено ранее, результаты сканирования сохраняются в виде html и json. Для получения консолидированных данных хорошо подходит формат json, а с помощью великолепной утилиты jq мы можем манипулировать json-объектами всеми мыслимыми и немыслимыми способами.

Для регулярного обозрения и понимания ситуации с безопасностью сайта в нашем конкретном случае будет достаточно отформатированного списка проблем, упорядоченного по степени критичности в порядке убывания.

С помощью вот этого набора команд мы успешно сожмем наш json до варианта, позволяющего понять суть одним беглым взглядом:

cat blog_tyutin_net--$SCANDATE.json | \
jq -c '.site[].alerts[]' | \
jq -r -s -c 'sort_by(.riskcode, .confidence)| reverse | .[] | "(.riskdesc)\t|\t(.alert)"')

Вывод команды в консоли получается таким:

Medium (High) | Sub Resource Integrity Attribute Missing
Medium (Medium) | Source Code Disclosure - ActiveVFP
Medium (Medium) | Source Code Disclosure - PHP
Low (High) | In Page Banner Information Leak
Low (High) | Server Leaks Version Information via "Server" HTTP Response Header Field
Low (High) | Server Leaks Version Information via "Server" HTTP Response Header Field
Low (Medium) | Cookie No HttpOnly Flag
Low (Medium) | Cookie Without SameSite Attribute
Low (Medium) | Content Security Policy (CSP) Header Not Set
Low (Medium) | X-Content-Type-Options Header Missing
Low (Medium) | Feature Policy Header Not Set
Low (Medium) | Incomplete or No Cache-control and Pragma HTTP Header Set
Low (Medium) | Absence of Anti-CSRF Tokens
Informational (Medium) | Modern Web Application
Informational (Medium) | Storable but Non-Cacheable Content
Informational (Medium) | Base64 Disclosure
Informational (Medium) | Storable and Cacheable Content
Informational (Medium) | Storable and Cacheable Content
Informational (Low) | Charset Mismatch
Informational (Low) | User Controllable HTML Element Attribute (Potential XSS)
Informational (Low) | Timestamp Disclosure - Unix
Informational (Low) | Information Disclosure - Suspicious Comments

Передаём отчет о сканировании OWASP ZAP в Telegram

Подопытным кроликом у нас выступает не корпоративный ресурс, и даже не стартап-проект, а простой личный блог, поэтому мы не будем выполнять интеграцию с Jira (хотя по приведенным здесь мануалам это вполне можно сделать), а просто будем отправлять результаты проверки в Telegram.

Вопрос создания Telegram бота в интернетах обмусолен уже давно, поэтому обойдём его стороной и приступим к делу. В данном проекте нас интересуют ежедневное получение консолидированной информации и наличие возможности сразу же увидеть дополнительные сведения по заинтересовавшему моменту.

Поэтому после каждого сканирования мы будем отправлять два сообщения:

  • обзорная информация по обнаружениям;
  • подробный html-отчет в виде файла.

Скрипт отправки в Telegram текстового сообщения:

#!/bin/bash
TGCHATID="$1"
TGMESSAGE="$2"
TGTOKEN="$3"

# Send message to TG chat
curl -m 20 -s \
--header 'Content-Type: application/json' \
--request 'POST' \
--data "{\"disable_web_page_preview\":true,\"parse_mode\":\"Markdown\",\"chat_id\":\"${TGCHATID}\",\"text\":\"${TGMESSAGE}\"}" "https://api.telegram.org/bot${TGTOKEN}/sendMessage" \
1 > /dev/null

Скрипт отправки в Telegram файла отчета OWASP ZAP:

#!/bin/bash
TGCHATID="$1"
FILETOSEND="$2"
TGTOKEN="$3"

# Send file to TG chat
curl -m 20 \
-F "chat_id=${TGCHATID}" \
-F document=@${FILETOSEND} \
https://api.telegram.org/bot${TGTOKEN}/sendDocument

Собственно, на этом мы закончили изготовление кирпичей. Пора сложить из них стену.

Запускаем continuous security scanning нашего сайта

В листинге приведен итоговый скрипт, готовый для использования в cron. Параметры запуска скрипта:

  • -target — начальный URL для сканирования OWASP ZAP;
  • -resdir — каталог для хранения отчетов о сканировании.

В логике скрипта предполагается сканирование сайта один раз в сутки. Повторный запуск при наличии отчетов сканирования за «сегодня» просто отправит нам данные из этих отчетов. И вот как это будет выглядеть:

HTML-отчет на экране мобильного устройства смотрится довольно прилично:

А вот и виновник торжества — скрипт автоматического пассиного сканирования сайта на уязвимости:

#!/bin/bash

# -----------------------------------------
# ------ Get input parameters ---------
# -----------------------------------------
for i in "$@"
do
case $i in
-target=*)
TARGET="${i#*=}"
shift
;;
-resdir=*)
RESDIR="${i#*=}"
shift
;;

esac
done
# -----------------------------------------

# -----------------------------------------
# ------ Check input parameters ---------
# -----------------------------------------
if [[ -z $TARGET ]]; then
echo "-target parameter is not set. Exiting." && exit
fi
if [[ -z $RESDIR ]]; then
echo "-resdir parameter is not set. Exiting." && exit
fi
if [[ ! -d ${RESDIR} ]]; then
echo "Path ${RESDIR} does not exist. Creating..."
mkdir -p $RESDIR
chown 1000:1000 $RESDIR
if [ $? -ne 0 ]; then
echo "Error creating ${RESDIR} directory. Exiting."
exit
fi
fi
# -----------------------------------------

TGTOKEN=```PUT_YOUR_TOKEN_HERE```
TGCHATID=```PUT_YOUR_ID_HERE```


SCANDATE=$(date "+%Y-%m-%d")
SCANFILE=$(echo $TARGET | sed -e 's|/|_|g' | sed -e 's|:|_|g' | sed -e 's|\.|_|g')

# -----------------------------------------
# ------ Perform scan ---------
# -----------------------------------------
if [[ ! -f $RESDIR/${SCANFILE}-${SCANDATE}.json ]]; then
docker run \
-v $RESDIR/:/zap/wrk/:rw \
-t owasp/zap2docker-live \
zap-baseline.py \
-t $TARGET \
-j -a -m 5 \
-r $SCANFILE-$SCANDATE.html \
-J $SCANFILE-$SCANDATE.json
fi
# -----------------------------------------

# -----------------------------------------
# ------ Interpret results ---------
# -----------------------------------------
RESULT=$(cat $RESDIR/$SCANFILE-$SCANDATE.json | \
jq -c '.site[].alerts[]' | \
jq -r -s -c 'sort_by(.riskcode, .confidence)| reverse | .[] | "\(.riskdesc)\t|\t\(.alert)_NEWLINE_"')

MESSAGE="*SECURITY REPORT FOR "$TARGET"*\n\n"

MESSAGE=$MESSAGE$(echo $RESULT | sed -e 's|"|\\"|g' | sed -e 's/|/\\t|\\t/g' | sed -e 's|_NEWLINE_|\\n|g' | sed -e 's|\\n |\\n|g' )

echo $MESSAGE
# -----------------------------------------

# -----------------------------------------
# ------ Send text report ---------
# -----------------------------------------
curl -m 20 -s \
--header 'Content-Type: application/json' \
--request 'POST' \
--data "{\"disable_web_page_preview\":true,\"parse_mode\":\"Markdown\",\"chat_id\":\"${TGCHATID}\",\"text\":\"${MESSAGE}\"}" "https://api.telegram.org/bot${TGTOKEN}/sendMessage" \
1 > /dev/null
# -----------------------------------------

# -----------------------------------------
# ------ Send html report ---------
# -----------------------------------------
curl -m 20 \
-F "chat_id=${TGCHATID}" \
-F document=@${RESDIR}/${SCANFILE}-${SCANDATE}.html \
https://api.telegram.org/bot${TGTOKEN}/sendDocument
# -----------------------------------------