Инструменты безопасности
Toolkit для bug bounty и NotHoly CLI
Небольшой честный toolkit для новичка в bug bounty плюс NotHoly CLI в терминале с записанным скоупом, чтобы команды оставались внутри программы.
Запустите пассивный baseline-скан ZAP против Juice Shop на своей машине, прочитайте отчёт и вручную проверьте один алерт, прежде чем ему доверять.
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 version.Лаборатория остаётся на вашей машине. Публикуйте её порт только на 127.0.0.1. Порт, опубликованный без этого префикса, может слушать на всех сетевых интерфейсах, а Juice Shop специально сделан так, чтобы его ломали.
Запустите Juice Shop в фоне под именем, которое пригодится позже:
docker run -d --name juice-local -p 127.0.0.1:3000:3000 bkimminich/juice-shopФлаг -d запускает контейнер в отсоединённом режиме. --rm я намеренно не указал, чтобы контейнер оставался на месте, пока вы не остановите и не удалите его по имени. Префикс 127.0.0.1: не пускает порт в вашу сеть.
Проверьте, что приложение отвечает:
curl -i http://127.0.0.1:3000/На моей машине первые строки ответа выглядели так. Я убрал индикатор прогресса, который curl выводит без флага -s:
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, потому что приложение меняется от релиза к релизу. Её возвращает этот эндпоинт:
curl -s http://127.0.0.1:3000/rest/admin/application-versionНа моей машине он вывел {"version":"20.3.0"}. Занесите это число в заметки рядом с результатами сканирования.
Запускайте сканер из папки, где должен появиться отчёт. Вот команда для Docker Desktop:
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 по умолчанию нет. Добавьте сопоставление хоста в ту же команду:
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:
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, и на моём соединении это заняло почти двенадцать минут. Последующие запуски стартуют за секунды.
Сканер печатает по одной строке на правило. Вот важная часть из моего запуска, длинный список пройденных правил сокращён:
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: 59WARN-NEW означает, что правило отметило нечто, чего этот скан раньше не видел. PASS означает, что правило отработало и ничего не нашло. Число после названия правила — это его идентификатор в ZAP, по которому можно искать в документации.
В моём запуске команда завершилась с кодом 2. В документации по baseline перечислены коды выхода: 0 означает успех, 1 значит хотя бы один FAIL, 2 значит хотя бы один WARN и ни одного FAIL, 3 значит любой другой сбой. Здесь код 2 в порядке вещей. Он значит лишь, что сканер нашёл кое-что для чтения.
Откройте zap-report.html в браузере. В сводке вверху моего отчёта стояло High 0, Medium 2, Low 0 и Informational 1. Весь список состоял из трёх алертов:
Content-Security-Policy, который сообщает браузеру, какие скрипты и фреймы можно загружать.Access-Control-Allow-Origin: *, и любой сайт может прочитать этот ответ в браузере.У каждого алерта одинаковые части: описание, URL, где он сработал, улики, которые увидел ZAP, дополнительные заметки и предлагаемое исправление. Сначала читайте улики и URL. Описание объясняет, что правило значит в целом. Улики показывают, что произошло именно в этом ответе.
Не сортируйте отчёт только по метке риска. ZAP назначает уровень риска исходя из правила и не учитывает данные вашего приложения. Medium на странице без чувствительных данных может оказаться менее важным, чем Low на эндпоинте, который работает с аккаунтами.
Возьмите алерт CSP. Улика здесь в отсутствии заголовка, так что проверка простая: запросите страницу, поищите заголовок, запишите результат. Эта команда выводит число совпавших строк:
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. Если найдёте такой эндпоинт, проверьте его со своим лабораторным аккаунтом и запишите, что он вернул.
Для третьего алерта доказывать нечего. Он описывает тип приложения. Оставьте его в отчёте как контекст и идите дальше.
Записывайте каждый проверенный результат в таблицу или текстовый файл с четырьмя столбцами: алерт, отправленный запрос, что вы увидели, ваш вердикт. Вердикт может быть «подтверждено», «не воспроизведено» или «нужно больше контекста». Эти три ответа покрывают большую часть работы с выводом сканера.
На этом шаге вы наблюдаете настоящие запросы, и именно это делает прокси полезным за пределами сканирования. Подойдёт настольное приложение или контейнер с ZAP в режиме демона. Я описываю оба варианта, но запускал только демон. Настольный вариант я не выполнял, поэтому вывода от него ниже не будет.
Установите ZAP с официального сайта. На странице getting-started есть ссылка на актуальный установщик. Запустите ZAP и выберите Manual Explore: так запустится браузер, который уже отправляет трафик через ZAP. Введите http://127.0.0.1:3000 и покликайте по главной странице. Затем откройте вкладку History. Каждая строка — это пара запроса и ответа с методом, URL и кодом статуса. Сравните эти строки с тем, что видно в инструментах разработчика вашего браузера. Различия покажут, что браузер отправил такого, чего вы не ожидали.
Запустите ZAP как фоновый прокси на порту 8080:
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 отвечает:
curl -s http://127.0.0.1:8080/JSON/core/view/version/Вывод на моей машине:
{"version":"2.17.0"}Отправьте два запроса через прокси. Флаг -x заставляет curl использовать ZAP. Целью указан host.docker.internal, потому что ZAP работает внутри Docker и обращается к вашей машине по имени, которое он может разрешить оттуда:
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"Вывод:
proxied root http=200
proxied api http=200Спросите ZAP, сколько сообщений он записал:
curl -s "http://127.0.0.1:8080/JSON/core/view/numberOfMessages/"На моей машине он вернул {"numberOfMessages":"5"}. Затем выведите их коротким однострочником на Python, который печатает строку каждого запроса и строку статуса ответа:
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']]"Вывод:
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. Поэтому историю и стоит проверять самому, не полагаясь на счётчик. Если ваш список отличается, запишите, что видите, и выясните причину до того, как делать выводы.
Когда закончите, остановите и удалите прокси:
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. Перед загрузкой образа посмотрите актуальную версию на странице релизов.
Инструменты безопасности
Небольшой честный toolkit для новичка в bug bounty плюс NotHoly CLI в терминале с записанным скоупом, чтобы команды оставались внутри программы.
Инструменты безопасности
Агент работает на вашей машине с вашего одобрения, модель берёт работу по безопасности, а engagement проверяет каждую команду против вашего scope.
Инструменты безопасности
Ревью файла или git diff прямо в редакторе, с контекстом, который даёт код. Модель берёт вопрос безопасности и превращает находку в тест или правило.