Сергей Попов
Администратор
- 30.12.2015
- 6 223
- 6 963
- Специализация
- OSINT
- Веб-безопасность
- Статус верификации
- ✓ Verified
На прошлом проекте по пентесту финтех-платформы мне достался REST API с 340 эндпоинтами. Первые два часа я тупо перебирал
user_id в Burp Repeater - копировать запрос, подставить ID, отправить, посмотреть ответ, записать аномалию. К двухсотому запросу глаз замылился настолько, что я пропускал очевидные вещи. Переключился: 20 минут на скрипт с requests, 8 минут на прогон всех 340 эндпоинтов. Скрипт нашёл три IDOR, которые прошли мимо при ручном анализе. Не потому что Python умнее - цикл for просто не устаёт на двухсотой итерации. Дальше - о том, как строить такие скрипты правильно: от HTTP-сессии с нужными заголовками до автоматического обнаружения аномалий в ответах API.Когда скрипт на Python быстрее Burp Suite - и когда нет
Прежде чем открывать IDE, разберёмся с границей: автоматизация пентеста на Python не заменяет Burp Suite. Водораздел проходит по одному критерию - формализуемость проверки. Если задачу можно описать одним предложением («проверь все ID от 1 до 500, покажи те, где пришёл 200 с телом больше 100 байт»), скрипт на requests справится быстрее. Если для описания нужен абзац с ветвлениями - Burp Repeater и Intruder в руках пентестера эффективнее.| Критерий | Python (requests + BeautifulSoup) | Burp Suite Pro |
|---|---|---|
| Преимущества | Полная кастомизация логики. Интеграция в CI/CD. Масштабирование на сотни эндпоинтов. Бесплатный стек | Визуальный интерцепт запросов. Встроенный сканер. Экосистема экстендеров (Autorize, ActiveScan++) |
| Ограничения | Нет визуального интерцепта. Ручная обработка TLS-пиннинга. Логику детектирования пишешь с нуля | Лицензия ~449 USD/год. Тяжело масштабировать. Сложно встраивать в пайплайны |
| Когда использовать | Массовая проверка BOLA/IDOR по диапазону ID. Регрессионные проверки после фиксов. Кастомная бизнес-логика. Фаззинг нестандартных форматов (protobuf, msgpack) | Исследование нового API - маппинг эндпоинтов, понимание авторизации. Multi-step workflow. WebSocket |
| Когда НЕ использовать | Интерактивное исследование незнакомого API. OAuth-потоки с redirect-цепочками. Тестирование SPA | Автоматизация 200+ эндпоинтов с одинаковой логикой. Ночной CI/CD-прогон |
На практике подход комбинированный: разведка начинается в Burp - интерцепт трафика, маппинг эндпоинтов, понимание авторизационной модели. Как только появляется паттерн для массовой проверки - пишется скрипт. Burp - для разведки, Python - для масштабируемой проверки.
Отдельно про попытки построить полноценный аналог BurpSuite на Python: это тупик. Burp - GUI-приложение с прокси-сервером, визуальным анализом трафика и зоопарком плагинов, который развивался годами. Воспроизводить всё это - проект на год. Цель другая: взять одну конкретную проверку из Burp (Intruder-прогон, Repeater-тест) и реализовать её скриптом, который запускается одной командой и пишет результат в stdout. Это Python скрипт для тестирования безопасности API, а не «свой Burp».
Место автоматизации в цепочке пентеста API
Каждый скрипт для тестирования API на уязвимости закрывает задачу конкретного этапа. Привязка к MITRE ATT&CK помогает понять, где именно в цепочке находится каждая проверка и что должно произойти до и после.Reconnaissance - Active Scanning (T1595): скрипты перечисления эндпоинтов. Отправка
requests.get() по списку URL из Swagger-спецификации или по словарю путей - Wordlist Scanning (T1595.003). Парсинг HTML через BeautifulSoup на этом этапе помогает вытянуть ссылки и формы со страниц, расширяя поверхность атаки. Vulnerability Scanning (T1595.002) - автоматизированная проверка известных паттернов уязвимостей по найденным эндпоинтам.Initial Access - Exploit Public-Facing Application (T1190): эксплуатация найденных дыр. Автоматизированная проверка BOLA, инъекции через фаззинг параметров, обход авторизации - работа на получение доступа к данным или функциям, которые не должны быть доступны текущему пользователю.
Credential Access - Brute Force (T1110): автоматизация подбора учётных данных через login-эндпоинты. Скрипт с
requests.Session() поддерживает cookies между попытками и корректно обрабатывает anti-brute-force механизмы. Password Spraying (T1110.003) - когда один пароль проверяется для множества аккаунтов: тут нужна отдельная логика с паузами между аккаунтами и ротацией паролей.Execution - Python (T1059.006): сам факт использования Python для выполнения атакующих действий.
Контекст применения: описанная автоматизация проверок веб-приложений работает и на внешнем пентесте (публичный API, bug bounty), и на внутреннем (корпоративные API за VPN). Различие - в модели угроз. Внешний пентест: основная головная боль - rate limiting и WAF. Cloudflare может отрезать фаззинг-трафик после нескольких десятков запросов. Внутренний пентест: корпоративная аутентификация (NTLM, Kerberos), для которой нужны дополнительные библиотеки вроде
requests_ntlm или requests_kerberos, и нередко - мониторинг со стороны SOC, когда массовые запросы генерируют алерты в SIEM.Настройка рабочего окружения и HTTP-сессии
Требования к окружению
- ОС: Linux (Kali 2024.x / Ubuntu 22.04+) или macOS. Windows через WSL2 работает без ограничений
- Python: 3.10+ (match/case и typing improvements упрощают обработку ответов)
- RAM: 4 ГБ минимум для скриптов; 8 ГБ если параллельно запущен Burp Suite Pro
- Зависимости:
pip install requests beautifulsoup4 urllib3 - Сеть: доступ к целевому API; для внутренних тестов - активное VPN-подключение
- Опционально: локальный прокси (Burp или mitmproxy) на
127.0.0.1:8080для отладки запросов
requests.get() напрямую для каждого запроса. Это создаёт новое TCP-соединение каждый раз, теряет cookies и не позволяет переиспользовать авторизационные заголовки. Правильный путь - requests.Session(). Этот объект сохраняет cookies между запросами, переиспользует TCP через keep-alive и позволяет задать заголовки один раз.Настройка сводится к трём шагам. Первый - задать
User-Agent через session.headers.update(). Дефолтный python-requests/2.x блокируется WAF первым делом; замена на строку реального браузера снижает процент ложных блокировок. Второй - добавить Authorization header с Bearer-токеном, API-ключом или Basic-auth строкой в зависимости от схемы аутентификации API. Третий - опционально прописать session.proxies для пропускания трафика через Burp ({"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}), чтобы видеть все запросы скрипта в Burp HTTP History и перепроверять аномалии руками.Для API с cookie-based авторизацией workflow другой: сначала
session.post() на /login с логином-паролем, затем сессия автоматически подхватывает Set-Cookie из ответа. Все последующие запросы через ту же сессию пойдут с нужными cookies без явной передачи. Это критически важно для requests библиотеки при пентесте - без сессии каждый запрос будет «неавторизованным».Про TLS-верификацию:
session.verify = False - стандартный приём на пентесте с самоподписанными сертификатами. Для подавления предупреждений urllib3 в начало скрипта добавляется urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning). В продакшн-коде такое недопустимо, но при ручном тестировании - норма.Автоматизация проверок BOLA и IDOR через requests
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Критична здесь не отправка запроса, а логика анализа ответа. Проверка
"order_total" in r.text - маркер того, что API вернул реальные данные, а не пустую заглушку. Без неё скрипт нагенерит ложных срабатываний: некоторые API возвращают 200 OK с пустым JSON {} для несуществующих объектов. Для повышения точности добавляется сравнение по размеру ответа - сначала запрос к «своему» объекту формирует baseline, затем каждый ответ сравнивается: если len(r.content) совпадает с baseline с точностью до 50 байт и содержит данные - вероятно, это BOLA.Расширение подхода для проверки Broken Function Level Authorization (API5:2023): тот же принцип, но вместо перебора ID - перебор эндпоинтов с повышенными привилегиями. Авторизуемся как обычный пользователь и пробуем дёрнуть
DELETE /api/users/42, PUT /api/admin/settings, POST /api/reports/generate. Если API возвращает 200 вместо 403 - найдена уязвимость авторизации на уровне функций. Тут Python-скрипт даже эффективнее Burp: список административных эндпоинтов готовится из Swagger-документации и прогоняется одной командой.BeautifulSoup для парсинга форм и извлечения CSRF-токенов
BeautifulSoup в контексте веб-пентеста решает конкретную задачу: вытянуть из HTML-страницы данные, необходимые для формирования следующего HTTP-запроса. Главный сценарий - парсинг HTML для анализа безопасности: CSRF-токены, скрытые поля форм,__VIEWSTATE в ASP.NET-приложениях.Работает если: приложение рендерит HTML на сервере (Django, Rails, Laravel, ASP.NET MVC). CSRF-токен передаётся как скрытое поле
<input type="hidden">. Токен привязан к сессии, а не к fingerprint браузера.Не работает если: CSRF-токен генерируется JavaScript после загрузки DOM (SPA на React, Vue, Angular). Приложение использует double-submit cookie pattern без скрытого поля. Страница за CAPTCHA или JS-challenge (Cloudflare).
[Применимо: внешний пентест, серверно-рендеренные веб-приложения]
Типовая цепочка: GET-запрос на страницу с формой, парсинг HTML для извлечения токена, POST-запрос с токеном и полезной нагрузкой.
Python:
from bs4 import BeautifulSoup
import requests
s = requests.Session()
page = s.get("https://target.com/change-password")
soup = BeautifulSoup(page.text, "html.parser")
token = soup.find("input", {"name": "csrf_token"})["value"]
r = s.post("https://target.com/change-password", data={
"csrf_token": token, "new_password": "Pentest123!"
})
print(f"Status: {r.status_code}, Length: {len(r.content)}")
_token (Laravel), authenticity_token (Rails), __RequestVerificationToken (ASP.NET), csrfmiddlewaretoken (Django). Для универсальности собираются все скрытые поля: soup.find_all("input", {"type": "hidden"}) - и формируется словарь {field["name"]: field["value"]} для автоматической подстановки в запрос.Зачем это нужно в цепочке пентеста: BeautifulSoup парсинг для веб-пентеста - связующее звено между обнаружением уязвимости и её автоматизированной эксплуатацией. Пример: обнаружена BOLA на эндпоинте смены email. Эндпоинт защищён CSRF-токеном, который обновляется при каждом запросе страницы. Без автоматического извлечения токена перед каждой итерацией автоматизация невозможна - придётся вручную копировать токен в Burp Repeater. А это уже не автоматизация, а мучение.
Ещё один сценарий - парсинг страниц ошибок. При фаззинге API сервер иногда возвращает HTML-страницу со stack trace вместо JSON. BeautifulSoup позволяет вытянуть текст ошибки:
soup.find("pre") часто содержит трейсбек с названиями внутренних модулей, путями файлов на сервере и версиями библиотек - ценные данные для развития атаки. Это частный случай Security Misconfiguration (A05:2021 по OWASP Top 10) - verbose error messages, торчащие наружу.Фаззинг API-эндпоинтов и детекция аномалий
Фаззинг API на Python - отправка нестандартных значений в параметры и анализ отклонений от нормы. Это автоматизированное сканирование веб-уязвимостей в миниатюре: не тысячи проверок как у коммерческого сканера, а десятки целевых пейлоадов под конкретный параметр с осмысленным анализом ответов.Работает если: API не защищён WAF с агрессивным rate limiting. Ответы содержат достаточно информации для сравнения (разные статус-коды, разный размер тела, разное время ответа). Параметры передаются в URL, query string или JSON body.
Не работает если: WAF (Cloudflare, AWS WAF, ModSecurity CRS) блокирует после 10-20 запросов с «подозрительными» символами. API возвращает одинаковый ответ при любом вводе (blind-сценарий). Параметры шифруются или подписываются на клиенте перед отправкой.
[Применимо: внешний пентест (с ограничениями по rate limiting), внутренний пентест, bug bounty]
Принцип: сначала «нормальный» запрос формирует baseline. Каждый фаззинг-запрос сравнивается с baseline по трём метрикам: статус-код, длина тела ответа, время ответа.
Python:
import requests, time
s = requests.Session()
s.headers.update({"Authorization": "Bearer <token>"})
payloads = ["' OR 1=1--", "<script>", "../../../etc/passwd", "${7*7}", "{{7*7}}"]
url = "https://api.target.com/v1/search"
baseline = s.get(url, params={"q": "normalvalue"})
for p in payloads:
t0 = time.time()
r = s.get(url, params={"q": p}, timeout=10)
delta = time.time() - t0
if r.status_code != baseline.status_code or abs(len(r.content) - len(baseline.content)) > 50:
print(f"[ANOMALY] payload={p} status={r.status_code} len={len(r.content)} time={delta:.2f}s")
payloads здесь минимальный - для демонстрации. В реальном проекте словарь расширяется до сотен записей из SecLists (каталоги Fuzzing/ и Payloads/). SQLi-пейлоады проверяют Injection (A03:2021 по OWASP Top 10). SSTI-пейлоады ({{7[I]7}}, ${7[/I]7}) проверяют Server-Side Template Injection: если в ответе появляется 49 - шаблонизатор исполнил выражение, и это уже не «аномалия», а RCE на горизонте. Path traversal (../../../etc/passwd) проверяет чтение файлов за пределами webroot.Порог
abs(len(r.content) - len(baseline.content)) > 50 - отправная точка, которую нужно калибровать под конкретный API. Если ответы содержат динамические данные (timestamp, request ID, session token), длина будет плавать. Более устойчивый подход - искать конкретные маркеры в тексте: строки traceback, exception, syntax error, mysql, postgresql. Их наличие при фаззинг-пейлоаде - почти гарантированный индикатор уязвимости.Временная аномалия - отдельная метрика, которую часто упускают. Если baseline приходит за 50 мс, а при пейлоаде
' OR SLEEP(5)-- задержка вырастает до 5 секунд - это индикатор time-based blind SQL injection. Проверка: if delta > baseline_time * 3 или жёсткий порог if delta > 3.0. Для REST API security testing этот подход критичен: «слепые» инъекции не проявляются в статус-коде или теле ответа, только во времени.Ещё деталь: при фаззинге стоит логировать не только аномалии, но и все ответы в файл. Потом этот лог можно загрузить в Burp через
paste from file и просмотреть в Repeater. Связка инструментов пентестера на Python с GUI-анализом: массовая отправка скриптом, точечная верификация в Burp.Ограничения: когда Python-скрипты не работают
Автоматизация наrequests и BeautifulSoup покрывает кучу рутинных проверок, но не серебряная пуля. Вот конкретные границы.WAF и rate limiting. Cloudflare Bot Management, AWS WAF с OWASP CRS, ModSecurity - детектируют массовые запросы с фаззинг-пейлоадами. Обходы (ротация
User-Agent, time.sleep() между запросами, IP-ротация через SOCKS-прокси) усложняют скрипт настолько, что проще взять специализированный инструмент: ffuf для фаззинга с встроенным rate-limit, Nuclei для шаблонизированных проверок.SPA и JavaScript-зависимая логика. Если API-запросы формируются JavaScript-кодом (React, Angular, Vue), BeautifulSoup бесполезен - он видит только исходный HTML до исполнения скриптов. Нужны headless-браузеры: Playwright (быстрее и стабильнее Selenium) или puppeteer через pyppeteer.
WebSocket и gRPC. Библиотека
requests работает только с HTTP/HTTPS. Для WebSocket-эндпоинтов - websockets или socketio. Для gRPC - grpcio с protobuf-генерацией. Burp Suite Pro поддерживает WebSocket из коробки, что делает его предпочтительным для интерактивного анализа таких протоколов.Сложные аутентификационные потоки. OAuth 2.0 с PKCE, SAML SSO, MFA - воспроизведение browser-flow в
requests технически возможно, но трудоёмко. Client Credentials и Password Grant - без проблем. Authorization Code Flow с redirect-цепочкой - задача для Selenium или ручной работы. Broken Authentication (API2:2023) в OAuth-конфигурации чаще находится интерактивным анализом, а не автоматизированным перебором.False positive management. Скрипт находит аномалии, но не понимает бизнес-контекст. API, который возвращает разный размер ответа для пользователей с разными подписками - не BOLA, а feature. API, отвечающий 500 на пустую строку - не injection, а баг валидации. Каждая находка требует ручной верификации. Без этого шага отчёт превращается в мусор.
Написание сканера уязвимостей на Python оправдано для задач с чёткой формализуемой логикой: массовая проверка BOLA, регрессия после фиксов, фаззинг параметров с измеримым baseline, автоматический сбор метаданных (версии серверов, открытые Swagger-эндпоинты, утечка отладочной информации через Security Misconfiguration, A05:2021). Для всего остального - Burp Suite в руках пентестера.
Минилаб для отработки
Требования к окружению
- RAM: 4 ГБ минимум (2 ГБ на Docker-контейнер + 2 ГБ на хостовую систему с Python)
- ОС: любая с Docker Engine - Linux, macOS, Windows (WSL2)
- Зависимости: Docker, Python 3.10+,
pip install requests beautifulsoup4 - Сеть: только localhost, интернет не требуется после скачивания образа
docker run -p 3000:3000 bkimminich/juice-shop. API доступен по http://localhost:3000/api/, документация по http://localhost:3000/api-docs/.Сценарий 1 - BOLA: зарегистрировать двух пользователей через
POST /api/Users/, авторизоваться от первого через POST /rest/user/login, получить Bearer-токен из ответа. Запустить скрипт перебора ID корзин через GET /api/BasketItems/{id} - Juice Shop намеренно не проверяет принадлежность объекта.Сценарий 2 - Демонстрация ограничений BeautifulSoup: открыть главную
GET /, попробовать парсить формы. Juice Shop - SPA на Angular, поэтому BeautifulSoup покажет только пустой каркас без динамического контента. Наглядно показывает границу инструмента: для SPA нужен Playwright.Сценарий 3 - Фаззинг SQLi: отправить SQL-пейлоады в поиск через
GET /rest/products/search?q=PAYLOAD. Juice Shop намеренно уязвим - при пейлоаде ' OR 1=1-- API вернёт все товары вместо пустого результата, а при '))-- покажет stack trace с деталями SQLite-запроса. Тот самый момент, когда в ответе вместо JSON прилетает трейсбек с путями файлов.Альтернативный стенд: crAPI (Completely Ridiculous API) от OWASP - специально для поиска уязвимостей веб-приложений скриптом. Покрывает BOLA, Mass Assignment, Unrestricted Resource Consumption (API4:2023) и другие позиции из OWASP API Security Top 10. Запуск через docker-compose (yaml валяется на GitHub OWASP/crAPI).
Большинство материалов про пентест API на Python начинаются с «установите requests» и заканчиваются отправкой GET-запроса. Это как учить хирургию на примере бинта. Реальная сложность не в HTTP-вызовах, а в логике принятия решений: как отличить BOLA от нормального поведения API, как не нагенерить сотню false positive, как учитывать rate limiting и при этом не положить прод заказчика. Написать
requests.get() может студент второго курса. Написать скрипт с корректным анализом ответов - задача другого уровня.За последние пару лет наблюдаю повторяющийся сценарий: команды начинают с «автоматизируем всё на Python» и через квартал откатываются к Burp Repeater. Проблема не в автоматизации, а в попытке применить её ко всему подряд. Скрипты идеальны для проверок с чёткой формализуемой логикой - одно условие, один цикл, один результат. Исследование нового API, где бизнес-логика ещё не понятна - работа для человека, не для цикла.
Правило, подтверждённое десятками проектов: если задачу описываешь одним предложением - пиши скрипт. Если нужен абзац - работай руками в Burp. И не пытайся строить «универсальный сканер»: каждый хороший пентест-скрипт одноразовый, написан под конкретный API и конкретную уязвимость. Попытка сделать «всё в одном» заканчивается тем, что инструмент не делает хорошо ничего. Если хочешь потренировать эту цепочку на живом стенде - на HackerLab лежат лабы по web-уязвимостям, где BOLA и injection ломаются без риска задеть чужой прод.