Перейти к содержимому
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after a top-up of $10 or more. Until Nov 30.

How it works

Основы OWASP ZAP на локальном Juice Shop

Запустите пассивный baseline-скан ZAP против Juice Shop на своей машине, прочитайте отчёт и вручную проверьте один алерт, прежде чем ему доверять.

Начальный10 мин чтенияАвтор: NotHoly

Что такое ZAP и где он стоит

Zed Attack Proxy (обычно просто ZAP) — это бесплатный инструмент для поиска проблем в веб-приложениях. Для этого руководства важны две его задачи. Он работает как перехватывающий прокси, поэтому вы можете прочитать каждый запрос браузера и каждый полученный ответ. Кроме того, он запускает сканеры, которые ищут в этом трафике слабые места.

Прокси — это посредник. Обычно браузер отправляет запрос прямо на 127.0.0.1:3000. Когда трафик идёт через ZAP, браузер отправляет запрос ему. ZAP пересылает запрос приложению, получает ответ, сохраняет пару в истории и возвращает ответ браузеру. Приложение никаких изменений не замечает. У вас просто появляется копия разговора.

Инструмент полезен именно потому, что стоит посередине. Сканер видит то, что вернул сервер. Прокси показывает, что на самом деле сделал браузер, включая заголовки, cookies, редиректы и загруженный страницей JavaScript. Многие веб-проблемы заметны только при сравнении этих двух картин.

Пассивное и активное сканирование

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

Пассивное сканирование читает трафик, который уже прошёл через ZAP. Каждый ответ проверяется, например, на отсутствие заголовка Content-Security-Policy, cookies без флага HttpOnly или стек-трейсы на страницах ошибок. Пассивные правила не отправляют строки атаки и смотрят только на то, что пришло в ответ.

Активное сканирование отправляет новые запросы, составленные так, чтобы вызвать реакцию. Оно меняет параметры, подаёт странный ввод и следит за ответами приложения. Большинство серьёзных находок даёт именно такое тестирование, и по поведению оно похоже на атаку. Запускайте его только в собственной лаборатории или на системе, для которой у вас есть письменное разрешение и заданный скоуп.

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

Почему алерт ещё ничего не доказывает

Каждый алерт ZAP означает, что сработало правило. Правило может реагировать на отсутствующий заголовок, шаблон в теле ответа или и то и другое. Оно не знает, зачем нужно ваше приложение, какие данные оно защищает и может ли атакующий добраться до важного пути. Алерт подсказывает, куда смотреть. Эксплуатируемость слабости он не подтверждает.

Две ошибки встречаются постоянно. Часть алертов оказывается ложными срабатываниями: правило совпало с чем-то безобидным. Другие проблемы сканер пропускает полностью, потому что не дошёл до страницы или не отправил ввод, который запускает изъян. Зацепка становится находкой, когда у вас есть шаг, который вы можете повторить сами: отправленный запрос, увиденный ответ и простое объяснение, почему это важно. Пока всех трёх частей нет, алерт остаётся в списке с пометкой «проверить».

Помните об этом правиле до конца руководства. Сканер даёт отправную точку, а проверка остаётся за вами.

Перед началом

Понадобятся четыре вещи:

  • Docker. Подойдёт Docker Desktop на Windows или macOS и Docker Engine на Linux. Проверьте его командой docker version.
  • Ваша собственная машина, на которой вы запускаете только описанную здесь лабораторию.
  • Терминал. В примерах используется оболочка POSIX. На Windows я запускал их в Git Bash.
  • Браузер, если хотите выполнить необязательный шаг с настольным прокси.

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

Шаг 1: запустите лабораторию

Запустите Juice Shop в фоне под именем, которое пригодится позже:

bash
docker run -d --name juice-local -p 127.0.0.1:3000:3000 bkimminich/juice-shop

Флаг -d запускает контейнер в отсоединённом режиме. --rm я намеренно не указал, чтобы контейнер оставался на месте, пока вы не остановите и не удалите его по имени. Префикс 127.0.0.1: не пускает порт в вашу сеть.

Проверьте, что приложение отвечает:

bash
curl -i http://127.0.0.1:3000/

На моей машине первые строки ответа выглядели так. Я убрал индикатор прогресса, который curl выводит без флага -s:

text
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Feature-Policy: payment 'self'
X-Recruiting: /#/jobs
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Sun, 08 Nov 2026 09:06:18 GMT
ETag: W/"2700-1a1ba6896a5"
Content-Type: text/html; charset=UTF-8
Content-Length: 9984

Ваши заголовки и даты будут другими. Запишите и версию Juice Shop, потому что приложение меняется от релиза к релизу. Её возвращает этот эндпоинт:

bash
curl -s http://127.0.0.1:3000/rest/admin/application-version

На моей машине он вывел {"version":"20.3.0"}. Занесите это число в заметки рядом с результатами сканирования.

Шаг 2: запустите baseline-скан

Запускайте сканер из папки, где должен появиться отчёт. Вот команда для Docker Desktop:

bash
docker run --rm -t -v "$(pwd)":/zap/wrk ghcr.io/zaproxy/zaproxy:stable zap-baseline.py -t http://host.docker.internal:3000 -r zap-report.html

Части команды:

  • --rm удаляет контейнер сканера после завершения.
  • -v "$(pwd)":/zap/wrk монтирует текущую папку внутрь контейнера, чтобы отчёт записался на ваш диск.
  • -t http://host.docker.internal:3000 задаёт цель. Изнутри контейнера host.docker.internal указывает на ваш компьютер, где слушает лаборатория.
  • -r zap-report.html записывает полный HTML-отчёт в смонтированную папку.

На Linux псевдонима хоста из Docker Desktop по умолчанию нет. Добавьте сопоставление хоста в ту же команду:

bash
docker run --rm -t --add-host=host.docker.internal:host-gateway -v "$(pwd)":/zap/wrk ghcr.io/zaproxy/zaproxy:stable zap-baseline.py -t http://host.docker.internal:3000 -r zap-report.html

Я проверял только вариант для Docker Desktop. На Linux порт, опубликованный на 127.0.0.1, может не принимать соединения, приходящие через адрес моста Docker. Если сканер сообщает об ошибках соединения, сначала прочитайте первые строки его вывода, а уже потом что-либо меняйте.

Две проблемы, с которыми я столкнулся

Первая попытка провалилась, и сканирование даже не началось. В консоли появилось A file based option has been specified but the directory '/zap/wrk' is not mounted, затем справка по использованию, и процесс завершился с кодом 3. Монтирование не сработало в моей сессии Git Bash, и причину я не выяснил. Помог запуск команды с MSYS_NO_PATHCONV=1 и полным путём Windows во флаге -v:

bash
MSYS_NO_PATHCONV=1 docker run --rm -t -v "D:/path/to/your/folder:/zap/wrk" ghcr.io/zaproxy/zaproxy:stable zap-baseline.py -t http://host.docker.internal:3000 -r zap-report.html

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

Как читать вывод в консоли

Сканер печатает по одной строке на правило. Вот важная часть из моего запуска, длинный список пройденных правил сокращён:

text
Using the Automation Framework
Total of 116 URLs
PASS: Vulnerable JS Library (Powered by Retire.js) [10003]
PASS: Cookie No HttpOnly Flag [10010]
...
PASS: CSP [10055]
...
WARN-NEW: Content Security Policy (CSP) Header Not Set [10038] x 5
WARN-NEW: Cross-Domain Misconfiguration [10098] x 5
WARN-NEW: Modern Web Application [10109] x 5
FAIL-NEW: 0	FAIL-INPROG: 0	WARN-NEW: 3	WARN-INPROG: 0	INFO: 0	IGNORE: 0	PASS: 59

WARN-NEW означает, что правило отметило нечто, чего этот скан раньше не видел. PASS означает, что правило отработало и ничего не нашло. Число после названия правила — это его идентификатор в ZAP, по которому можно искать в документации.

В моём запуске команда завершилась с кодом 2. В документации по baseline перечислены коды выхода: 0 означает успех, 1 значит хотя бы один FAIL, 2 значит хотя бы один WARN и ни одного FAIL, 3 значит любой другой сбой. Здесь код 2 в порядке вещей. Он значит лишь, что сканер нашёл кое-что для чтения.

Шаг 3: прочитайте отчёт

Откройте zap-report.html в браузере. В сводке вверху моего отчёта стояло High 0, Medium 2, Low 0 и Informational 1. Весь список состоял из трёх алертов:

  • Content Security Policy (CSP) Header Not Set, Medium. Ответ не содержит заголовка Content-Security-Policy, который сообщает браузеру, какие скрипты и фреймы можно загружать.
  • Cross-Domain Misconfiguration, Medium. Ответ содержит Access-Control-Allow-Origin: *, и любой сайт может прочитать этот ответ в браузере.
  • Modern Web Application, Informational. ZAP заметил, что сайт ведёт себя как одностраничное приложение. Это описание приложения, дефекта тут нет.

У каждого алерта одинаковые части: описание, URL, где он сработал, улики, которые увидел ZAP, дополнительные заметки и предлагаемое исправление. Сначала читайте улики и URL. Описание объясняет, что правило значит в целом. Улики показывают, что произошло именно в этом ответе.

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

Шаг 4: проверьте один алерт вручную

Возьмите алерт CSP. Улика здесь в отсутствии заголовка, так что проверка простая: запросите страницу, поищите заголовок, запишите результат. Эта команда выводит число совпавших строк:

bash
curl -s -i http://127.0.0.1:3000/ -o /dev/null -D - | grep -i -c '^content-security-policy'

На моей машине она вывела 0. В полном списке заголовков, снятом раньше, строки Content-Security-Policy тоже не было. Алерт соответствует тому, что отправляет сервер.

Теперь второй алерт. Уликой служит строка Access-Control-Allow-Origin: *, которую вы уже видели в выводе curl -i. Здесь проверка должна ответить на другой вопрос: есть ли в этом ответе что-то, чего стороннему сайту читать нельзя? Корневая страница лаборатории — это публичный HTML, поэтому сам заголовок проблемой не считается. В тексте ZAP сказано то же самое. Браузеры не дают сторонней странице читать так аутентифицированные ответы. Алерт становится серьёзным, только когда эндпоинт с приватными данными тоже отдаёт wildcard в origin. Если найдёте такой эндпоинт, проверьте его со своим лабораторным аккаунтом и запишите, что он вернул.

Для третьего алерта доказывать нечего. Он описывает тип приложения. Оставьте его в отчёте как контекст и идите дальше.

Записывайте каждый проверенный результат в таблицу или текстовый файл с четырьмя столбцами: алерт, отправленный запрос, что вы увидели, ваш вердикт. Вердикт может быть «подтверждено», «не воспроизведено» или «нужно больше контекста». Эти три ответа покрывают большую часть работы с выводом сканера.

Шаг 5: пропустите браузер через ZAP

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

Настольный вариант (я его не запускал)

Установите ZAP с официального сайта. На странице getting-started есть ссылка на актуальный установщик. Запустите ZAP и выберите Manual Explore: так запустится браузер, который уже отправляет трафик через ZAP. Введите http://127.0.0.1:3000 и покликайте по главной странице. Затем откройте вкладку History. Каждая строка — это пара запроса и ответа с методом, URL и кодом статуса. Сравните эти строки с тем, что видно в инструментах разработчика вашего браузера. Различия покажут, что браузер отправил такого, чего вы не ожидали.

Вариант с демоном (его я запускал)

Запустите ZAP как фоновый прокси на порту 8080:

bash
docker run -d --name zap-proxy-lab -p 127.0.0.1:8080:8080 ghcr.io/zaproxy/zaproxy:stable zap.sh -daemon -host 0.0.0.0 -port 8080 -config api.disablekey=true -config api.addrs.addr.name=.* -config api.addrs.addr.regex=true

Опция api.disablekey=true отключает проверку API-ключа, чтобы команды ниже работали без дополнительной настройки. Оставляйте порт опубликованным на 127.0.0.1 и удалите контейнер, когда закончите. Управлять ZAP может любой, кто доберётся до API.

Убедитесь, что API отвечает:

bash
curl -s http://127.0.0.1:8080/JSON/core/view/version/

Вывод на моей машине:

text
{"version":"2.17.0"}

Отправьте два запроса через прокси. Флаг -x заставляет curl использовать ZAP. Целью указан host.docker.internal, потому что ZAP работает внутри Docker и обращается к вашей машине по имени, которое он может разрешить оттуда:

bash
curl -s -x http://127.0.0.1:8080 -o /dev/null -w 'proxied root http=%{http_code}\n' http://host.docker.internal:3000/
curl -s -x http://127.0.0.1:8080 -o /dev/null -w 'proxied api http=%{http_code}\n' "http://host.docker.internal:3000/rest/products/search?q=apple"

Вывод:

text
proxied root http=200
proxied api http=200

Спросите ZAP, сколько сообщений он записал:

bash
curl -s "http://127.0.0.1:8080/JSON/core/view/numberOfMessages/"

На моей машине он вернул {"numberOfMessages":"5"}. Затем выведите их коротким однострочником на Python, который печатает строку каждого запроса и строку статуса ответа:

bash
curl -s "http://127.0.0.1:8080/JSON/core/view/messages/?start=1&count=5" | python -c "import json,sys; d=json.load(sys.stdin); [print(m['requestHeader'].splitlines()[0], '->', m['responseHeader'].splitlines()[0]) for m in d['messages']]"

Вывод:

text
GET http://host.docker.internal:3000/ HTTP/1.1 -> HTTP/1.1 200 OK
GET http://host.docker.internal:3000 HTTP/1.1 -> HTTP/1.0 0
GET http://host.docker.internal:3000/rest/products/search?q=apple HTTP/1.1 -> HTTP/1.1 200 OK
GET http://host.docker.internal:3000/rest HTTP/1.1 -> HTTP/1.0 0
GET http://host.docker.internal:3000/rest/products HTTP/1.1 -> HTTP/1.0 0

Я отправил через прокси два запроса, а в истории пять записей. У трёх из них нет ответа, что показано как статус 0. Откуда они взялись, я не отслеживал. Не считайте их свидетельством о Juice Shop. Поэтому историю и стоит проверять самому, не полагаясь на счётчик. Если ваш список отличается, запишите, что видите, и выясните причину до того, как делать выводы.

Когда закончите, остановите и удалите прокси:

bash
docker rm -f zap-proxy-lab

Типичные ошибки

Направить сканер на чужую цель. Флаг -t принимает любой URL, и сканер будет слать туда запросы. Оставляйте целью лабораторный псевдоним своей машины.

Опубликовать лабораторию на всех интерфейсах. -p 3000:3000 без адреса слушает на каждом интерфейсе. Всегда пишите 127.0.0.1:3000:3000.

Считать предупреждения оценкой. Отчёт с тремя предупреждениями говорит, что сработали три правила. Проверьте каждое, прежде чем его учитывать.

Не записать версию. Juice Shop меняется от релиза к релизу, правила ZAP тоже. Записывайте обе версии вместе с отчётом. Я использовал Juice Shop 20.3.0 и ZAP 2.17.0.

Оставить прокси запущенным. У демона отключён API-ключ. Удаляйте контейнер после каждой сессии.

Принять столбец статуса за доказательство. Строка со статусом 0 означает, что ответ не записан. Находкой она не считается.

Смонтировать не ту папку. Если отчёта нет, сначала проверьте монтирование -v. Сканер может отработать успешно и всё равно ничего не записать туда, где вы ждёте.

Что дальше

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

Прочитайте руководство по установке Juice Shop, если пропустили его: в этой статье используется та же лаборатория. В следующей статье на ней же объясняется SQL-инъекция. Там лучше видно, зачем алерту сканера ручная проверка: изъян связан с тем, как ввод попадает в запрос, а инструмент может на это только намекнуть.

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

Проект Juice Shop хранит список заданий и заметки по установке в своём репозитории на GitHub. Перед загрузкой образа посмотрите актуальную версию на странице релизов.

Источники

Источники

  1. ZAP documentation
  2. ZAP Baseline Scan (Docker)
  3. ZAP getting started
  4. OWASP Juice Shop (GitHub)

Ещё по теме: Инструменты безопасности

Инструменты безопасности

Toolkit для bug bounty и NotHoly CLI

Небольшой честный toolkit для новичка в bug bounty плюс NotHoly CLI в терминале с записанным скоупом, чтобы команды оставались внутри программы.

Начальный3 мин чтения

Инструменты безопасности

Почему CLI NotHoly, а не обычный помощник в терминале

Агент работает на вашей машине с вашего одобрения, модель берёт работу по безопасности, а engagement проверяет каждую команду против вашего scope.

Средний3 мин чтения

Инструменты безопасности

Зачем расширение NotHoly для VS Code для ревью кода

Ревью файла или git diff прямо в редакторе, с контекстом, который даёт код. Модель берёт вопрос безопасности и превращает находку в тест или правило.

Средний3 мин чтения