Автоматизация сбора эндпоинтов API на Python: скрипты, грабли и вопросы на собеседовании AppSec

На внутреннем пентесте API финтех-компании документация описывала 14 эндпоинтов. Python-скрипт на 40 строк — парсинг Swagger-спецификации плюс JavaScript-бандлы фронтенда — нашёл 47. Из «невидимых» 33 эндпоинтов три позволяли читать чужие транзакции без авторизации. Классический BOLA — Broken Object Level Authorization (OWASP API1:2023): подменяешь ID объекта в запросе и получаешь доступ к чужим данным. Разрыв между документированным и реально работающим API — ядро проблемы OWASP API9:2023 Improper Inventory Management: устаревшая документация, открытые дебаг-эндпоинты, живые deprecated-версии, про которые все забыли. Именно этот сценарий любят на собеседованиях AppSec-инженера: «Как вы находите эндпоинты, которых нет в документации?»
Зачем автоматизировать разведку API: бизнес-логика атаки
Прежде чем писать код — стоит понять, что ищет атакующий. Не «все эндпоинты подряд», а конкретные категории.
Дебаг-пути без авторизации: /debug, /healthcheck, /metrics, /actuator. Разработчики оставляют их в продакшене «на время», а потом забывают. Через /metrics вытаскиваются внутренние имена сервисов и версии зависимостей. Через /actuator/env (Spring Boot) — иногда пароли к базам прямым текстом. Я видел это на реальном проекте, и нет, это не редкость.
Эндпоинты вне поля зрения WAF. WAF (Web Application Firewall — фильтр, который блокирует вредоносные HTTP-запросы) настраивается на документированные маршруты. Всё, что WAF «не знает» — пропускает без проверки.
Устаревшие версии API. /api/v1/users может не проверять права доступа, хотя в /api/v2/users это давно исправлено. Оба работают параллельно. Такие эндпоинты называют zombie API — deprecated-версии, которые никто не отключил.
Все три случая — варианты shadow API: эндпоинты, которые существуют в инфраструктуре, но не зарегистрированы в документации и не контролируются командой безопасности. По разным отчётам, заметная часть вредоносных API-транзакций направлена именно на такие неуправляемые пути. OWASP API Security Top 10 (список десяти критичных рисков для API) выделяет эту проблему отдельной строкой — API9:2023 Improper Inventory Management.
В цепочке атаки автоматизация сбора эндпоинтов API — фаза разведки. Без неё атакующий тыкается в задокументированные пути, которые обычно защищены. С ней — получает карту «мёртвых зон», где WAF молчит и авторизация не настроена. Ручной поиск — открыть DevTools, покликать приложение, записать пути — работает на 10 эндпоинтов. На 500 — уже нет. Python решает задачу масштаба.
Три источника для автоматизации сбора эндпоинтов API
Каждый источник покрывает свой пласт. Swagger описывает «как задумывалось». JS-бандлы показывают, что фронтенд реально вызывает. HAR-файлы фиксируют, что летало по сети при работе приложения. Пересечение трёх множеств даёт полную картину; разница между ними — потенциальные shadow API.
| Источник | Что находит | Что пропускает | Предпосылки |
|---|---|---|---|
| OpenAPI / Swagger | Зарегистрированные эндпоинты с методами и параметрами | Shadow API, динамически формируемые пути | Спецификация доступна по URL (статус 200, 401 или 403) |
| JS-бандлы | Захардкоженные пути фронтенда, включая admin-панели | URL, собранные из переменных; серверные эндпоинты | Python 3.8+, библиотеки requests и BeautifulSoup |
| HAR-файлы | Реальные запросы за конкретную сессию | Эндпоинты, не вызванные при записи | Ручное прокликивание приложения в DevTools |
Парсинг Swagger и OpenAPI-спецификаций
OpenAPI (ранее Swagger) — стандарт описания REST API в формате JSON или YAML. Если разработчики подключили Swagger UI, спецификация обычно лежит по одному из предсказуемых путей: /swagger.json, /openapi.json, /v2/api-docs, /swagger/v1/swagger.json, /api-docs. На багбаунти-проектах я первым делом перебираю эти пути через httpx или простой цикл на requests.
Спецификация содержит все зарегистрированные пути, HTTP-методы, параметры и иногда примеры запросов. Парсить её просто — обычный JSON:
import requests
def extract_openapi_endpoints(spec_url):
"""Забирает эндпоинты из OpenAPI-спецификации."""
resp = requests.get(spec_url, timeout=10)
if resp.status_code != 200:
print(f"Spec unavailable, status: {resp.status_code}")
return []
spec = resp.json()
for path, methods in spec.get("paths", {}).items():
for method in methods:
if method.upper() in ("GET", "POST", "PUT", "DELETE", "PATCH"):
print(f"{method.upper():6s} {path}")
return list(spec.get("paths", {}).keys())
Скрипт делает GET-запрос к URL спецификации, разбирает JSON-ответ и выводит все пути с HTTP-методами. Статус 200 и список путей — спецификация найдена. 401 или 403 — спецификация, вероятно, есть, но закрыта авторизацией. Тогда подставь заголовок Cookie или Authorization из легитимной сессии: DevTools → Network → правый клик на любой запрос → Copy as cURL → вытащи оттуда значение токена.
Предпосылка: Python 3.8+, установленный requests (pip install 'requests>=2.32.0' — в версии 2.32.0 исправлена CVE-2024-35195: если первый запрос в Session шёл с verify=False, все последующие к тому же хосту игнорировали verify=True и не проверяли TLS-сертификат).
JS-бандлы: requests и BeautifulSoup для разведки эндпоинтов
Фронтенд-приложения на React, Vue или Angular компилируются в JS-бандлы — файлы вроде app.a1b2c3.js или chunk-vendors.js. Внутри захардкожены строки API-путей: /api/admin/users, /internal/reports, /api/v1/payments/refund. Разработчики обычно не задумываются, что эти строки видны любому, кто откроет DevTools → Sources.
Подход: скачиваем HTML главной страницы, вытаскиваем ссылки на JS-файлы через BeautifulSoup (Python-библиотека для парсинга HTML) и прогоняем каждый бандл через регулярку, ищущую строки, похожие на API-пути:
import re, requests, urllib.parse
from bs4 import BeautifulSoup
def extract_js_endpoints(base_url):
soup = BeautifulSoup(requests.get(base_url, timeout=10).text, "html.parser")
rx = re.compile(r'["\'](/api/[a-zA-Z0-9/_\-{}]+)["\']')
found = set()
for s in soup.find_all("script", src=True):
url = urllib.parse.urljoin(base_url, s["src"])
try:
found.update(rx.findall(requests.get(url, timeout=10).text))
except requests.exceptions.RequestException:
continue # CDN-ссылка недоступна — пропускаем
return found
BeautifulSoup находит все теги <script src="/..."> на странице. Скрипт загружает каждый JS-файл и ищет подстроки, начинающиеся с /api/ — буквы, цифры, слеши. На выходе — set уникальных путей.
Ожидаемый результат: строки типа /api/v1/users, /api/admin/dashboard, /api/internal/config. Пустое множество — либо фронтенд не использует /api/ префикс (расширь паттерн на /v[0-9]/ или конкретный домен), либо пути обфусцированы.
Нюанс, о который спотыкаются: SPA (Single Page Application) загружают JS-чанки лениво, по мере навигации. Начальный HTML содержит только main.js, а ещё 30 чанков подгрузятся при переходе по роутам. Решение — пройтись по нескольким страницам приложения или использовать playwright для программной загрузки.
HAR-файлы: реальный трафик приложения
HAR (HTTP Archive) — стандартный формат дампа сетевых запросов из DevTools. Порядок: открой приложение → DevTools → Network → прокликай все экраны и формы → правый клик → Save all as HAR. Файл содержит каждый HTTP-запрос с URL, заголовками и телом ответа.
HAR — тоже JSON. Ключевой массив: log.entries. Внутри каждого объекта — request.url. Логика парсинга: json.load() → цикл по entries → urllib.parse.urlparse(entry["request"]["url"]).path → добавление в set(). Код тривиален и повторяет структуру предыдущих скриптов.
Ценность HAR — в другом. Он показывает реальные запросы, включая те, что JS-бандл делает через fetch() с динамически собранным URL. Если фронтенд строит путь как baseUrl + userId + "/transactions", в JS-бандле ты увидишь только шаблон с переменной, а в HAR — конкретный вызов /api/v1/users/42/transactions с подставленным ID. Именно эти конкретные пути потом проверяются на BOLA: можно ли подменить 42 на 43 и увидеть чужие данные.
Python-скрипт для сканирования API: делай раз, делай два, делай три
Полный цикл от нуля до верифицированного списка эндпоинтов.
Предпосылки: Linux или macOS (на Windows работает, но пути могут отличаться), Python 3.8+, библиотеки requests и beautifulsoup4. Для третьего шага — ffuf (быстрый фаззер на Go). Установка: go install github.com/ffuf/ffuf/v2@latest — если команда не срабатывает, проверь актуальный путь модуля в README репозитория ffuf, он может отличаться: go install github.com/ffuf/ffuf@latest. Нужен установленный Go; альтернатива — готовый бинарник с GitHub Releases.
Делай раз — подготовка окружения. python3 -m venv venv && source venv/bin/activate && pip install 'requests>=2.32.3' beautifulsoup4. Проверка: python3 -c "import requests; print(requests.__version__)" — должен вывести номер версии без ошибок. ModuleNotFoundError — повтори pip install и убедись, что в строке терминала есть (venv).
Делай два — сбор из трёх источников. Создай файл recon.py, добавь функции extract_openapi_endpoints() и extract_js_endpoints() из секций выше плюс парсинг HAR. В main() вызови все три и объедини: all_paths = set(openapi_paths) | js_paths | har_paths. Запусти: python3 recon.py > endpoints.txt. Каждая строка — один путь. Ожидаемо от 20 до 200 строк в зависимости от размера приложения. Пустой файл — проверяй URL таргета и доступность ресурсов (Swagger может отдавать 403, JS-файлы могут грузиться с CDN другого домена).
Делай три — верификация находок. Сырой список содержит мусор: статические файлы, несуществующие пути, плейсхолдеры ({id}, {userId}). Прогони каждый путь через ffuf. Сначала убери ведущий слеш: sed 's#^/##' endpoints.txt > endpoints_clean.txt — иначе подстановка даст двойной слеш в URL. Команда: ffuf -u https://TARGET/FUZZ -w endpoints_clean.txt -mc 200,401,403,405. -mc — фильтр по кодам ответа, FUZZ — место подстановки пути. Код 200 — эндпоинт открыт. 401 или 403 — существует, но закрыт авторизацией (самое интересное для проверки BOLA). 405 — метод не поддерживается, но путь есть; попробуй другой HTTP-метод.
Грабли автоматизации разведки API
Сырой результат автоматизации — 30% полезных путей и 70% шума. Вот конкретные проблемы и как их решать.
Rate limiting. 500 запросов за 10 секунд — и WAF или API Gateway возвращает 429 (Too Many Requests) или банит IP. В скрипте: time.sleep(0.3) между запросами. В ffuf: флаг -rate 10 (10 запросов в секунду). На внутреннем пентесте согласуй окно для сканирования с заказчиком. На багбаунти — читай policy программы: многие явно запрещают автоматизированный перебор без согласования.
Обфускация JS-бандлов. Webpack, Vite и esbuild минифицируют код. Строка /api/v1/users превращается в конкатенацию переменных: a="/api", b="/v1/users", c=a+b. Регулярка это не поймает. Решение: ищи не полные пути, а фрагменты (/v1/, /api/, /admin/, /internal/) и комбинируй вручную. Source maps (файлы .js.map) — золотая жила: содержат исходный неминифицированный код. На продакшене их обычно отключают, но проверить стоит. Забывают чаще, чем хотелось бы.
Ложные срабатывания. Паттерн /api/[a-zA-Z0-9/_\-{}]+ ловит строки вроде /api/placeholder из UI-библиотек или /api/example из комментариев. Веди «стоп-лист» из ~20 слов: example, test, placeholder, mock, sample, dummy, lorem. Фильтруй результаты перед верификацией — экономит время и трафик.
Разные домены для статики и API. Фронтенд на app.example.com, API на api.example.com. JS-бандлы содержат относительные пути /v1/users без домена. Нужно знать, куда подставлять. Проверяй переменные окружения в JS — ищи строки API_URL, BASE_URL, API_HOST регуляркой r'(API|BASE)_URL["\s:=]+["\'](https?://[^"\']+)'.
Вопросы на собеседовании AppSec-инженера о разведке API
Когда я проходил собеседования на AppSec-роль после перехода из разработки, вопросы по автоматизации и энумерации эндпоинтов всплывали регулярно. Пять из них — с ответами, которые проходили.
«Как вы находите undocumented-эндпоинты?» Ожидают не «запускаю Burp Suite», а методологию. Рабочий ответ: «Начинаю с пассивной разведки — парсю Swagger/OpenAPI по предсказуемым путям, затем скачиваю JS-бандлы фронтенда и извлекаю API-пути регулярками, параллельно записываю HAR при ручном прохождении приложения. Три источника дают пересекающиеся множества: объединение — полная картина, разница между ними — потенциальные shadow API. Верифицирую через ffuf с фильтром по кодам ответа.»
«Что такое OWASP API9:2023 и как вы его проверяете?» API9:2023 Improper Inventory Management — проблема устаревшей документации и бесконтрольных версий API. Ответ: «Сравниваю эндпоинты из Swagger с реально обнаруженными. Если в Swagger 14 путей, а нашёл 47 — разницу в 33 пути показываю заказчику как инвентаризационный gap. Каждый «лишний» эндпоинт проверяю на авторизацию, rate limiting и наличие чувствительных данных в ответе.»
«Чем опасны shadow API и как их находить?» Ответ: «Shadow API — эндпоинты, не зарегистрированные в документации и не контролируемые ИБ-командой. Опасны тем, что WAF и мониторинг на них не настроены. Нахожу тремя способами: JS-бандлы (фронтенд знает про эндпоинт, даже если бэкенд-команда забыла его задокументировать), HAR-дампы (реальный трафик не врёт) и перебор типовых путей через ffuf с кастомным вордлистом.»
«Какие инструменты для API reconnaissance и почему именно они?»
Ожидают не список из 15 названий, а связку «задача → инструмент → почему». Ответ, который работает: «requests + BeautifulSoup для кастомных парсеров — каждый таргет уникален и готовые сканеры дают неполное покрытие. ffuf для верификации путей — быстрый, гибкий в фильтрации по кодам ответа и размеру. Burp Suite для ручного анализа — Repeater (инструмент для повторной отправки и модификации запросов) незаменим при проверке авторизации на каждом найденном эндпоинте.»
«Как автоматизация помогает в AppSec за пределами пентеста?» Вопрос-ловушка — проверяют, видишь ли ты дальше «нашёл дырку → написал отчёт». Ответ: «Скрипт сбора эндпоинтов встраивается в CI/CD (конвейер непрерывной интеграции и доставки кода). При каждом деплое текущий инвентарь эндпоинтов сравнивается с предыдущим. Новый эндпоинт без записи в API-каталоге — повод для ревью до релиза. Это сдвиг от реактивного «нашли на пентесте через полгода» к проактивному «поймали на этапе code review».»
Общий паттерн: интервьюера интересует не знание конкретного инструмента, а способность решить задачу, когда готового решения нет. Python-скрипт, который парсит нестандартный формат данных конкретного таргета, — демонстрация инженерного мышления.
Опыт перехода из разработки в AppSec подтвердил одну неочевидную вещь: на собеседованиях не спрашивают абстрактную теорию из методичек. Спрашивают, как вы решали конкретную задачу и какие грабли поймали. Скрипт на 40 строк, парсящий Swagger и JS-бандлы, — не учебное упражнение. Это рабочий артефакт, который говорит о кандидате больше, чем список сертификатов в резюме. Проблема большинства тех, кто переходит в ИБ из разработки, — попытка заучить инструменты вместо того, чтобы научиться решать задачи, для которых эти инструменты создавались. Swagger-спецификация — это JSON. JS-бандл — текст. HAR — тоже JSON. Если вы умеете парсить JSON и текст на Python, вы уже умеете находить shadow API. Осталось делать это системно, а не по случаю. Через год скрипты обрастают фильтрами, стоп-листами, интеграцией с Jira — и превращаются во внутренний инструмент команды. Но начинается всё с requests.get() и цикла for по ключам словаря. Если хотите пройти этот путь структурно — на codeby.school есть IB Basics, где подобные задачи разбирают от старта до первых рабочих кейсов.
Эту тему и смежные навыки разбирают на практике в курсе «Python для пентестера» Codeby Academy.