Nuclei кастомные шаблоны API: пишем YAML-детект логических уязвимостей от нуля до срабатывания

На Bug Bounty одного финтех-сервиса я вручную проверял IDOR в пяти API-эндпоинтах — подменял user_id в GET-запросах, сравнивал ответы, нашёл одну BOLA, получил баунти. Потом запустил Nuclei со стандартными шаблонами по тому же скоупу — ноль находок. Стандартные шаблоны из репозитория ProjectDiscovery заточены под известные CVE и мисконфигурации, а логические уязвимости конкретного API для них невидимы: эндпоинт отдаёт 200 OK с валидным JSON, формально всё в порядке. Масштабировать такие проверки на сотни эндпоинтов без кастомного YAML-шаблона, который учитывает бизнес-логику приложения, не получится. Дальше — путь от пустого файла до работающего BOLA-детекта.
Место Nuclei в цепочке атаки на API
Прежде чем писать YAML, стоит разобраться, на каком этапе Nuclei включается в работу и что происходит до и после.
В терминологии MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1190 — её идентификаторы) запуск сканера уязвимостей — техника T1595.002 «Vulnerability Scanning» на этапе разведки (reconnaissance). Кастомные шаблоны помогают найти цели, подверженные T1190 «Exploit Public-Facing Application» на этапе initial access — первоначального проникновения в систему.
Типичная цепочка при тестировании API:
- Сбор эндпоинтов — Swagger/OpenAPI-спецификация, перехват трафика через Burp Suite, сканирование с помощью
katanaилиffuf. На выходе: список URL-ов с методами (GET, POST, PUT, DELETE). - Автоматизированное сканирование — запуск Nuclei с кастомными шаблонами. Этот этап мы и разбираем.
- Ручная верификация — каждое срабатывание проверяется руками: реально ли это BOLA, или сервер отдаёт одинаковые данные всем пользователям by design.
- Отчёт и эксплуатация — документирование impact, написание отчёта для Bug Bounty или заказчика.
Nuclei не заменяет ручной анализ — он масштабирует его. Вместо перебора 200 эндпоинтов вручную запускаете шаблон и получаете shortlist из 5–10 подозрительных точек для детального исследования.
Кастомные шаблоны Nuclei для API-логики работают и на внешнем пентесте (публичный API в интернете), и на внутреннем (микросервисы за VPN). Разница — в аутентификации: внешний API чаще защищён OAuth2/JWT, внутренний может обходиться без токенов или с базовой HTTP-аутентификацией.
Написание шаблонов Nuclei: анатомия YAML для API-тестирования
Nuclei (проект ProjectDiscovery, GitHub: более 20 000 звёзд, стабильные релизы несколько раз в год, активный мейнтенинг на 2025 год) использует YAML-шаблоны как единый формат описания проверок. Каждый шаблон — текстовый файл .yaml, который описывает три вещи: что за проверка, какой HTTP-запрос отправить и как интерпретировать ответ.
Обязательные секции шаблона:
id— уникальный идентификатор без пробелов. По нему Nuclei маркирует находку в консоли и отчёте.info— метаданные: имя, автор, severity (критичность: info / low / medium / high / critical), теги для фильтрации при запуске.http— описание HTTP-запросов: метод, путь, заголовки, тело.matchers— правила, по которым Nuclei решает: сработал шаблон или нет. Здесь описывается разница между «уязвимым» и «нормальным» ответом.
Опциональные:
extractors— извлечение данных из ответа (токены, ID, значения полей). Нужны для multi-step шаблонов, где результат одного запроса идёт в следующий.payloads— словари для подстановки, аналог Burp Intruder.
Динамические переменные — механизм подстановки реальных адресов при запуске. Основные: {{BaseURL}} заменяется на полный URL цели (например, http://target.com), {{Hostname}} — хост с портом. Переменные заключены в двойные фигурные скобки и чувствительны к регистру.
Поиск логических уязвимостей API: пишем BOLA-детект
Требования к окружению
Перед практикой убедитесь, что среда готова:
- ОС: Linux (Kali, Ubuntu 22.04+), macOS или WSL2. Nuclei — бинарник на Go, работает на всех платформах.
- Nuclei v3.0+: проверка —
nuclei -version. Установка:go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latestили через менеджер инструментовpdtmот ProjectDiscovery. - RAM: 2 ГБ минимум, 4 ГБ рекомендуется для параллельного сканирования.
- Тестовая цель: VAmPI — специально уязвимое API-приложение. Запуск:
git clone https://github.com/erev0s/VAmPI && cd VAmPI && docker compose up, инициализация БД:curl http://127.0.0.1:5002/createdb(в ответ придёт{"message": "Database populated."}). - Текстовый редактор с подсветкой YAML (VS Code с расширением YAML или любой аналог).
Что такое BOLA и почему стандартные шаблоны слепы
BOLA (Broken Object Level Authorization) — уязвимость, при которой API не проверяет, имеет ли текущий пользователь право доступа к запрашиваемому объекту. По классификации OWASP API Security Top 10 (2023) — реестра десяти критичных рисков для API — это угроза номер один: API1:2023. Старое название той же проблемы — IDOR (Insecure Direct Object Reference), и под этим именем вы встретите её в большинстве старых отчётов.
Как выглядит на практике: пользователь A отправляет GET /api/users/42 и видит свой профиль. Меняет ID на 43 — видит профиль пользователя B. Сервер не проверил, что A имеет право запрашивать данные B.
Стандартные шаблоны Nuclei из публичного репозитория не детектируют BOLA по трём причинам:
- Сервер возвращает 200 OK — нет аномального статус-кода.
- Ответ содержит валидный JSON — нет ошибок парсинга или stack trace.
- Контент отличается только данными — сканер не знает, какие данные «чужие», а какие «свои».
Для обнаружения нужен шаблон, учитывающий контекст конкретного API.
Пишем первый шаблон: шаг за шагом
Сценарий: VAmPI имеет эндпоинт GET /users/v1/{username}, возвращающий данные пользователя. Проверяем, отдаёт ли он чужой профиль без авторизации.
Шаг 1. Изучить эндпоинт. Отправьте запрос curl http://127.0.0.1:5002/users/v1/admin через Burp Suite или терминал. Посмотрите, какие поля есть в JSON-ответе. Для VAmPI это username и email. Запомните: эти поля станут критерием для matchers.
Шаг 2. Создать файл шаблона. Создайте файл bola-user-profile.yaml:
id: bola-user-profile
info:
name: BOLA - доступ к чужому профилю
author: yourname
severity: high
tags: api,bola,owasp-api1
http:
- method: GET
path:
- "{{BaseURL}}/users/v1/admin"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
part: body
words:
- "admin"
- "email"
condition: and
Разберём построчно:
id: bola-user-profile— имя, которое Nuclei покажет в консоли при срабатывании. Используйте осмысленные имена: при 50 шаблонахtemplate-1превратится в хаос.severity: high— BOLA с доступом к чужим данным = нарушение конфиденциальности, высокая критичность.tags: api,bola,owasp-api1— позволяют запускать группу шаблонов командойnuclei -tags bola.matchers-condition: and— ВСЕ matchers должны сработать одновременно. БезandNuclei засчитает совпадение ЛЮБОГО matcher, и вы утонете в ложных срабатываниях.- Первый matcher проверяет HTTP-статус 200 — запрос успешен, сервер не вернул 401 или 403.
- Второй matcher ищет в теле ответа (
part: body) словаadminиemailодновременно (condition: and). Оба найдены — сервер отдал данные профиля admin.
Шаг 3. Запустить. Команда: nuclei -t bola-user-profile.yaml -u http://127.0.0.1:5002. Ожидаемый результат: строка с [bola-user-profile] и меткой [high] в выводе — оба matcher сработали, сервер вернул 200 OK с данными admin.
Если вывод пустой — шаблон не сработал. Добавьте флаг -debug: nuclei -t bola-user-profile.yaml -u http://127.0.0.1:5002 -debug покажет полный HTTP-запрос и ответ. По ним увидите, почему matchers не совпали — может, поле называется не email, а mail, или сервер вернул 403.
Matchers и multi-step шаблоны Nuclei для автоматизации поиска уязвимостей
Типы matchers для логики API
В примере выше использованы два типа matchers — status и word. Nuclei поддерживает шесть типов, но для логических уязвимостей API регулярно нужны три:
status — проверка HTTP-кода ответа. Базовый фильтр: 200 = данные отданы, 403 = доступ запрещён. Используется почти в каждом шаблоне.
word — поиск строки в ответе. Параметр part задаёт область поиска: body (тело), header (заголовки), all (везде). Параметр condition: and требует наличия ВСЕХ перечисленных слов, condition: or — хотя бы одного.
dsl — выражения на встроенном мини-языке Nuclei (DSL — Domain Specific Language, язык для описания условий). Позволяют проверять длину ответа, комбинировать условия, делать арифметику. Пример: status_code == 200 && contains(body, "email") && !contains(body, "error") — статус 200, в теле есть email, но нет error.
Типичная ошибка новичка: шаблон с единственным matcher type: status, status: 200 сработает на ЛЮБОЙ странице, отвечающей 200 OK. Бесполезно — сотни ложных срабатываний. Всегда комбинируйте status с word или dsl через matchers-condition: and.
Вторая частая ошибка: слишком общие слова в word matcher. Слово "user" встретится в ответе почти любого API. Берите специфичные маркеры: конкретные имена полей ("phone_number", "balance", "ssn"), значения конкретного пользователя, уникальные строки, которых не будет при ошибке авторизации.
Аутентифицированный BOLA-детект с extractors
Простой шаблон выше проверяет неаутентифицированный доступ. Большинство реальных API требуют токен. Для проверки BOLA в таком API нужен multi-step шаблон: сначала логин и извлечение токена, затем запрос к чужому ресурсу с этим токеном.
Nuclei поддерживает цепочки запросов через raw-формат — запросы описываются целиком, включая заголовки и тело, и выполняются последовательно:
id: bola-authenticated
info:
name: BOLA - authenticated access
author: yourname
severity: high
tags: api,bola,auth
http:
- raw:
- |
POST {{BaseURL}}/users/v1/login HTTP/1.1
Content-Type: application/json
{"username":"attacker","password":"attacker123"}
- |
GET {{BaseURL}}/users/v1/admin HTTP/1.1
Authorization: Bearer {{token}}
cookie-reuse: true
extractors:
- type: regex
name: token
part: body
regex:
- '"auth_token":\s*"([^"]+)"'
internal: true
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
part: body
words:
- "admin"
- "email"
condition: and
Что здесь происходит по шагам:
- Первый запрос —
POST /users/v1/loginс кредами пользователяattacker. Nuclei отправляет его и получает JSON с токеном. - Extractor с
type: regexвыхватывает значение токена из ответа по регулярке.internal: trueозначает: сохранить в переменнуюtokenдля следующих запросов, но не показывать в финальном отчёте. - Второй запрос —
GET /users/v1/adminс заголовкомAuthorization: Bearer {{token}}. Nuclei подставляет извлечённый токен и запрашивает данные пользователяadminот имениattacker. - Matchers проверяют результат второго запроса: статус 200 и наличие слов
admin+emailв теле. Оба условия выполнены — авторизация сломана, BOLA подтверждена.
cookie-reuse: true сохраняет куки между запросами в цепочке — пригодится для API, использующих сессионные куки вместо JWT.
Запуск: nuclei -t bola-authenticated.yaml -u http://127.0.0.1:5002. Ожидаемый вывод — строка [bola-authenticated] [high] с адресом цели.
Ограничения кастомных шаблонов при API security тестировании
Кастомные шаблоны Nuclei — инструмент с конкретными границами. Знать эти границы не менее ценно, чем уметь писать шаблоны.
| Критерий | Кастомные шаблоны Nuclei | Ручной тест (Burp Suite) | Коммерческие сканеры |
|---|---|---|---|
| Скорость на 100+ эндпоинтах | Высокая | Низкая | Средняя |
| Обнаружение BOLA/IDOR | Да (при шаблоне под API) | Да | Частично (эвристики) |
| Race conditions, state-баги | Нет | Да | Частично |
| Стоимость | Бесплатно | Бесплатно / платно (Pro) | Платно |
| Время подготовки | 15–60 мин на шаблон | Минуты на запрос | Минуты на настройку |
Когда кастомные шаблоны Nuclei НЕ работают:
- Сложные схемы авторизации. OAuth2 с PKCE, mTLS или HMAC-подписи запросов — настройка аутентификации в шаблоне превращается в мучение. Проще перехватить токен через Burp и передать в Nuclei через переменную окружения или заголовок (
-H "Authorization: Bearer <token>"). - UUID-идентификаторы. Если API использует UUID вместо числовых ID, подстановка «чужого» идентификатора невозможна без знания конкретного UUID. Шаблон не сгенерирует валидный UUID другого пользователя — нужна предварительная разведка.
- Rate limiting и WAF. Nuclei по умолчанию шлёт до 150 запросов в секунду. API с лимитами (OWASP API4:2023 — Unrestricted Resource Consumption: без лимитов атакующий может вызвать DoS или накрутить расходы) заблокирует сканер. Используйте флаг
-rl 3для ограничения до 3 запросов в секунду. - Бинарные или зашифрованные ответы. Если API отдаёт не JSON, а protobuf, gRPC или зашифрованный payload — matchers
wordбесполезны. Придётся работать сdslи проверкой длины ответа, а это повышает долю false positive. - Многошаговая бизнес-логика. Уязвимости, завязанные на состояние (race conditions, TOCTOU), на порядок вызовов нескольких микросервисов или на тайминги — Nuclei не покрывает. Тут только ручной пентест.
По данным Verizon DBIR 2025, веб-атаки составили 26% всех подтверждённых нарушений безопасности в 2024 году. Кастомные шаблоны Nuclei закрывают ту часть этих атак, которая поддаётся формализации: типовые BOLA, IDOR, утечки данных через предсказуемые эндпоинты — то, что чаще всего находят на Bug Bounty.
По моим наблюдениям, соотношение примерно 60 на 40. Шесть из десяти багов в API, которые я нахожу, поддаются автоматизации через Nuclei после первого ручного обнаружения. Остальные четыре — сложная логика, которую видишь только при ручной работе в Burp. Но даже 60% — это десятки сэкономленных часов на каждом крупном скоупе.
Есть мнение, что шаблонные сканеры — инструмент для начинающих, а «настоящие пентестеры» работают исключительно руками. Я вижу ситуацию иначе: написание кастомного шаблона требует более глубокого понимания API-логики, чем разовая ручная проверка. Вы не напишете работающий matcher, пока не разберётесь, как API авторизует запросы, какие поля возвращает и чем «свой» ответ отличается от «чужого». Шаблон — это формализация экспертного знания. Если не получается описать баг в YAML, значит, вы его не до конца понимаете.
Именно поэтому для тех, кто переходит в API-пентест, первый кастомный шаблон — не упражнение на автоматизацию, а тест на понимание. Возьмите одну конкретную уязвимость (BOLA подходит идеально), напишите шаблон, добейтесь срабатывания на тестовом стенде. Разница между «запускаю чужие шаблоны» и «написал свой и нашёл баг» — тот самый порог, который отделяет оператора сканера от исследователя. На курсе WAPT эту цепочку — от ручного обнаружения BOLA до автоматизации через кастомный шаблон — проходят в модулях с лабами, где можно сломать и починить всё без последствий.
Эту тему и смежные навыки разбирают на практике в курсе «Тестирование веб-приложений на проникновение (WAPT)» Codeby Academy.