Как стать автором
Поиск
Написать публикацию
Обновить

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

Время на прочтение11 мин
Количество просмотров6.4K
image

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

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

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

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

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

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

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


image

Ввиду постановки задачи возьмем не полнофункциональный 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 имеются на сайте:

Вот так выглядит отчёт в консоли...
Total of 272 URLs
PASS: Cookie Without Secure Flag [10011]
PASS: Cross-Domain JavaScript Source File Inclusion [10017]
PASS: Content-Type Header Missing [10019]
PASS: X-Frame-Options Header Scanner [10020]
PASS: Information Disclosure - Debug Error Messages [10023]
PASS: Information Disclosure - Sensitive Information in URL [10024]
PASS: Information Disclosure - Sensitive Information in HTTP Referrer Header [10025]
PASS: HTTP Parameter Override [10026]
PASS: Information Disclosure - Suspicious Comments [10027]
PASS: Open Redirect [10028]
PASS: Cookie Poisoning [10029]
PASS: User Controllable Charset [10030]
PASS: User Controllable HTML Element Attribute (Potential XSS) [10031]
PASS: Viewstate Scanner [10032]
PASS: Directory Browsing [10033]
PASS: Heartbleed OpenSSL Vulnerability (Indicative) [10034]
PASS: Strict-Transport-Security Header Scanner [10035]
PASS: Server Leaks Information via "X-Powered-By" HTTP Response Header Field(s) [10037]
PASS: X-Backend-Server Header Information Leak [10039]
PASS: Secure Pages Include Mixed Content [10040]
PASS: HTTP to HTTPS Insecure Transition in Form Post [10041]
PASS: HTTPS to HTTP Insecure Transition in Form Post [10042]
PASS: User Controllable JavaScript Event (XSS) [10043]
PASS: Big Redirect Detected (Potential Sensitive Information Leak) [10044]
PASS: Insecure Component [10046]
PASS: Content Cacheability [10049]
PASS: Retrieved from Cache [10050]
PASS: X-ChromeLogger-Data (XCOLD) Header Information Leak [10052]
PASS: CSP Scanner [10055]
PASS: X-Debug-Token Information Leak [10056]
PASS: Username Hash Found [10057]
PASS: X-AspNet-Version Response Header Scanner [10061]
PASS: PII Disclosure [10062]
PASS: Base64 Disclosure [10094]
PASS: Timestamp Disclosure [10096]
PASS: Hash Disclosure [10097]
PASS: Cross-Domain Misconfiguration [10098]
PASS: Weak Authentication Method [10105]
PASS: Reverse Tabnabbing [10108]
PASS: Modern Web Application [10109]
PASS: Private IP Disclosure [2]
PASS: Session ID in URL Rewrite [3]
PASS: Script Passive Scan Rules [50001]
PASS: Insecure JSF ViewState [90001]
PASS: Java Serialization Object [90002]
PASS: Charset Mismatch [90011]
PASS: Application Error Disclosure [90022]
PASS: Loosely Scoped Cookie [90033]
WARN-NEW: In Page Banner Information Leak [10009] x 1
https://blog.tyutin.net/favicon.ico (404 Not Found)
WARN-NEW: Cookie No HttpOnly Flag [10010] x 5
https://blog.tyutin.net/wp-login.php (200 OK)
https://blog.tyutin.net/wp-login.php?reauth=1&redirect_to=https%3A%2F%2Fblog.tyutin.net%2Fwp-admin%2F (200 OK)
https://blog.tyutin.net/wp-login.php?action=lostpassword (200 OK)
https://blog.tyutin.net/wp-login.php (200 OK)
https://blog.tyutin.net/wp-login.php?action=lostpassword (200 OK)
WARN-NEW: Incomplete or No Cache-control and Pragma HTTP Header Set [10015] x 114
https://blog.tyutin.net/ru/ (200 OK)
https://blog.tyutin.net/robots.txt (200 OK)
https://blog.tyutin.net/ (200 OK)
https://blog.tyutin.net/blog/category/uncategorized/ (200 OK)
https://blog.tyutin.net/blog/2020/03/03/hello-world/ (200 OK)
WARN-NEW: X-Content-Type-Options Header Missing [10021] x 161
https://blog.tyutin.net/ru/ (200 OK)
https://blog.tyutin.net/robots.txt (200 OK)
https://blog.tyutin.net/ (200 OK)
https://blog.tyutin.net/blog/category/uncategorized/ (200 OK)
https://blog.tyutin.net/blog/2020/03/03/hello-world/ (200 OK)
WARN-NEW: Server Leaks Version Information via "Server" HTTP Response Header Field [10036] x 188
https://blog.tyutin.net/ru/ (200 OK)
https://blog.tyutin.net/robots.txt (200 OK)
https://blog.tyutin.net/sitemap.xml (404 Not Found)
https://blog.tyutin.net/ (200 OK)
https://blog.tyutin.net/wp-admin/ (302 Found)
WARN-NEW: Content Security Policy (CSP) Header Not Set [10038] x 47
https://blog.tyutin.net/ru/ (200 OK)
https://blog.tyutin.net/sitemap.xml (404 Not Found)
https://blog.tyutin.net/ (200 OK)
https://blog.tyutin.net/wp-admin/admin-ajax.php (400 Bad Request)
https://blog.tyutin.net/blog/category/uncategorized/ (200 OK)
WARN-NEW: Cookie Without SameSite Attribute [10054] x 5
https://blog.tyutin.net/wp-login.php (200 OK)
https://blog.tyutin.net/wp-login.php?reauth=1&redirect_to=https%3A%2F%2Fblog.tyutin.net%2Fwp-admin%2F (200 OK)
https://blog.tyutin.net/wp-login.php?action=lostpassword (200 OK)
https://blog.tyutin.net/wp-login.php (200 OK)
https://blog.tyutin.net/wp-login.php?action=lostpassword (200 OK)
WARN-NEW: Feature Policy Header Not Set [10063] x 76
https://blog.tyutin.net/ru/ (200 OK)
https://blog.tyutin.net/sitemap.xml (404 Not Found)
https://blog.tyutin.net/ (200 OK)
https://blog.tyutin.net/wp-admin/admin-ajax.php (400 Bad Request)
https://blog.tyutin.net/blog/category/uncategorized/ (200 OK)
WARN-NEW: Source Code Disclosure - PHP [10099] x 11
https://blog.tyutin.net/ru/%d0%b7%d0%b0%d0%bf%d0%b8%d1%81%d0%ba%d0%b8-devsecopsa-sast-%d0%b8%d0%bb%d0%b8-%d0%bf%d0%be%d0%b3%d1%80%d1%83%d0%b6%d0%b5%d0%bd%d0%b8%d0%b5-%d0%b2-%d0%b8%d1%81%d1%85%d0%be%d0%b4%d0%bd%d1%8b%d0%b9/ (200 OK)
https://blog.tyutin.net/ru/feed/ (200 OK)
https://blog.tyutin.net/ru/category/devsecops/feed/ (200 OK)
https://blog.tyutin.net/ru/wp-content/plugins/syntaxhighlighter/syntaxhighlighter3/scripts/shBrushPhp.js?ver=3.0.9b (200 OK)
https://blog.tyutin.net/ru/wp-content/uploads/sites/2/2020/03/SSLLabs_blog_03_Aplus.png (200 OK)
WARN-NEW: Absence of Anti-CSRF Tokens [10202] x 22
https://blog.tyutin.net/sitemap.xml (404 Not Found)
https://blog.tyutin.net/sitemap.xml (404 Not Found)
https://blog.tyutin.net/ (200 OK)
https://blog.tyutin.net/blog/category/uncategorized/ (200 OK)
https://blog.tyutin.net/blog/2020/03/03/hello-world/ (200 OK)
WARN-NEW: Sub Resource Integrity Attribute Missing [90003] x 37
https://blog.tyutin.net/ru/ (200 OK)
https://blog.tyutin.net/sitemap.xml (404 Not Found)
https://blog.tyutin.net/ (200 OK)
https://blog.tyutin.net/blog/category/uncategorized/ (200 OK)
https://blog.tyutin.net/blog/2020/03/03/hello-world/ (200 OK)
FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 11 WARN-INPROG: 0 INFO: 0 IGNORE: 0 PASS: 48


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

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


image

Как было обозначено ранее, результаты сканирования сохраняются в виде 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


image

Подопытным кроликом у нас выступает не корпоративный ресурс, и даже не стартап-проект, а простой личный блог, поэтому мы не будем выполнять интеграцию с 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 нашего сайта


image

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

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

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

image

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

image

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

#!/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
# -----------------------------------------

За сим прощаюсь и благодарю дочитавших этот объёмный опус. Вопросы, пожелания, критика принимаются в комментариях и в личке. Всем добра!
Теги:
Хабы:
Всего голосов 9: ↑9 и ↓0+9
Комментарии0

Публикации

Ближайшие события