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

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

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

На внутреннем пентесте 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() → цикл по entriesurllib.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.