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

Перехват gRPC трафика через Burp Suite и mitmproxy: настройка связки и поиск первого IDOR

Перехват gRPC трафика через Burp Suite и mitmproxy: настройка связки и поиск первого IDOR
Время чтения: 14 мин.

На пентесте финтех-платформы с тремя десятками микросервисов выяснилось: 70% межсервисных вызовов идут через gRPC. Burp Suite показывал бинарную кашу вместо читаемых параметров, Intruder не мог зацепиться за protobuf-поля, Scanner молча пропускал эндпоинты. Пришлось строить связку из двух прокси: mitmproxy перехватывает и декодирует gRPC-трафик через Python-аддон, Burp Suite берёт на себя REST-часть и даёт GUI для анализа. За вечер настройки эта цепочка вытащила рабочий IDOR (Insecure Direct Object Reference — подмена идентификатора объекта в запросе открывает доступ к чужим данным) в эндпоинте GetUserProfile. Разработчики были уверены, что бинарный формат их защищает. Не защищает.

Почему Burp Suite не справляется с перехватом gRPC трафика в одиночку

Burp Suite — стандарт для тестирования веб-приложений, но с gRPC (фреймворк удалённого вызова процедур от Google, использующий бинарный формат Protocol Buffers и транспорт HTTP/2) у него две архитектурные проблемы, которые расширениями не закрываются.

Первая — ограниченная поддержка trailing headers в HTTP/2. Trailing headers — заголовки, которые сервер отправляет после тела ответа. gRPC через них передаёт статус вызова (grpc-status) и сообщения об ошибках (grpc-message). В Burp Suite Community и в старых версиях Professional (до 2023.10) trailing headers для gRPC не поддерживались вообще. В свежих Professional (2023.10+) добавили экспериментальную поддержку gRPC, но для streaming и сложных сценариев mitmproxy по-прежнему надёжнее.

Вторая — бинарный формат protobuf (Protocol Buffers — способ сериализации данных в компактный бинарный вид, в отличие от текстового JSON или XML). Когда Burp перехватывает gRPC-запрос, вместо читаемого {"user_id": 42} вы видите набор байтов. Без расширений работать с этим — как редактировать JPEG в текстовом редакторе.

Для gRPC-Web (упрощённый вариант gRPC, адаптированный для браузеров, работает через HTTP/1.1 без trailing headers) расширения Burp — protobuf-magic, bRPC-Web от Compass Security, Blackboxprotobuf от NCC Group — задачу декодирования решают. Но для «голого» gRPC между микросервисами, где полноценный HTTP/2 с trailing headers и streaming, Burp в одиночку буксует — особенно Community Edition. Тут в цепочку встаёт mitmproxy.

Архитектура связки Burp Suite и mitmproxy для анализа gRPC трафика

Идея простая: два прокси в цепочке, каждый делает то, в чём силён.

Поток трафика: Приложение/клиент → mitmproxy (порт 8080) → Burp Suite (порт 8082) → целевой сервер.

mitmproxy стоит первым. Три задачи: перехват HTTP/2-трафика с полной поддержкой gRPC (включая trailing headers), декодирование protobuf-сообщений через Python-скрипт, пересылка трансформированного трафика дальше.

Burp Suite стоит вторым и работает с уже обработанным потоком: REST-запросы (JSON, XML) проходят без изменений; gRPC-данные, декодированные аддоном mitmproxy, видны в HTTP History; Repeater и Intruder работают штатно с извлечёнными параметрами.

Такая архитектура закрывает типичную ситуацию: приложение использует REST для одних функций (авторизация, загрузка файлов) и gRPC для других (бизнес-логика, межсервисное взаимодействие).

В терминах MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1557 — её идентификаторы) прокси-цепочка реализует технику Adversary-in-the-Middle (T1557, тактики credential-access и collection) — мы встаём между клиентом и сервером для перехвата трафика. Установка CA-сертификата прокси на клиенте — Subvert Trust Controls: Install Root Certificate (T1553.004, тактика defense-evasion). На стороне защиты перехват gRPC относится к Network Sniffing (T1040, тактики credential-access и discovery). Понимание этих техник пригодится и при написании detection-правил.

Место в цепочке атаки

Перехват gRPC-трафика — этап разведки и подготовки. В полной цепочке пентеста gRPC-приложения шаги выстраиваются так:

  1. Энумерация сервисов — выявление доступных gRPC-методов через reflection или анализ .proto-файлов
  2. Настройка перехвата — конфигурация прокси-цепочки (вы здесь)
  3. Анализ бизнес-логики — изучение структуры запросов, метаданных авторизации, потоков данных
  4. Эксплуатация — поиск IDOR, injection, нарушений авторизации (Exploit Public-Facing Application, T1190)
  5. Документирование — фиксация находок для отчёта

Без рабочего перехвата к шагам 3-4 не перейти. Настройка связки — не факультативный бонус, а обязательное условие пентеста gRPC-сервисов.

Требования к окружению

Прежде чем выполнять первую команду — проверьте готовность рабочей среды.

Компонент Минимум Рекомендуется
ОС Ubuntu 22.04+ / Kali Linux / macOS Kali Linux 2024.x
RAM 4 ГБ 8 ГБ
Burp Suite Community Edition 2024.x Professional (Scanner, Intruder без ограничений)
mitmproxy 10.1.0+ (в 10.0 — уязвимость GHSA-22gh-3r9q-xf38) 11.x (актуальная стабильная линия, используйте >= 11.0.0)
Python 3.10+ 3.12+
protoc 3.x (компилятор protobuf) Последняя stable
grpcurl 1.8+ Последняя stable
Сеть Online (установка зависимостей)
Права Обычный пользователь (root — только для transparent mode)

Установка Python-зависимостей одной командой: pip install "mitmproxy>=10.1.0" blackboxprotobuf grpcio-tools. Пакет blackboxprotobuf нужен для декодирования protobuf без .proto-файла — ситуация, типичная для black-box пентеста (тестирование без доступа к исходному коду).

Настройка mitmproxy для перехвата Protobuf запросов

Установка и запуск в режиме upstream-прокси

mitmproxy — open-source MITM-прокси (man-in-the-middle — встаёт между клиентом и сервером для перехвата трафика), написанный на Python. В отличие от Burp, он нативно поддерживает HTTP/2 со всеми особенностями, включая trailing headers. В комплекте три утилиты: mitmproxy (интерактивный терминальный интерфейс), mitmweb (веб-интерфейс) и mitmdump (скриптовый режим без GUI). Для нашей задачи нужен mitmdump — к нему подключаются Python-скрипты для трансформации трафика на лету.

Запуск mitmproxy в режиме upstream-прокси (upstream — следующее звено цепочки, куда mitmproxy перенаправляет обработанный трафик):

mitmdump \
  --mode upstream:http://127.0.0.1:8082 \
  --listen-port 8080 \
  --set ssl_insecure=true \
  -s grpc_decoder.py

Что делает каждый флаг: --mode upstream:http://127.0.0.1:8082 направляет весь трафик в Burp Suite (порт 8082); --listen-port 8080 — порт, на который настраивается клиент или приложение; --set ssl_insecure=true отключает проверку TLS-сертификата upstream-прокси (Burp использует самоподписанный сертификат, без этого флага mitmproxy его отвергнет); -s grpc_decoder.py подключает Python-аддон для декодирования gRPC.

Ожидаемый результат: в терминале появится Proxy server listening at http://*:8080. Если видите Address already in use — порт 8080 занят другим процессом (часто Burp Suite, если вы не меняли его дефолтный порт). Укажите другой, например --listen-port 8081.

Импорт CA-сертификата и решение проблем с TLS

gRPC в продакшене почти всегда работает через TLS. Чтобы перехватить зашифрованный трафик, нужно установить корневой сертификат mitmproxy в систему или передать его тестируемому приложению.

После первого запуска mitmproxy генерирует CA-сертификат в ~/.mitmproxy/. Системная установка на Linux: скопируйте файл командой sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy.crt, затем обновите хранилище: sudo update-ca-certificates. В выводе должно появиться 1 added — сертификат принят системой.

Для тестирования конкретного gRPC-клиента на Python или Go можно передать сертификат через переменную окружения: export SSL_CERT_FILE=~/.mitmproxy/mitmproxy-ca-cert.pem (Go 1.18+ на Linux/macOS; работает только для клиентов, использующих x509.SystemCertPool() — если приложение задаёт tls.Config.RootCAs явно, нужна модификация кода или подмена системного trust store), export REQUESTS_CA_BUNDLE=~/.mitmproxy/mitmproxy-ca-cert.pem (Python с библиотекой requests).

Если приложение реализует certificate pinning (жёсткая привязка к конкретному серверному сертификату, практика из OWASP MASVS-NETWORK), перехват через прокси невозможен без обхода пиннинга. Для мобильных приложений это отдельная история (Frida, objection); для серверных gRPC-клиентов — обычно не проблема, если есть доступ к конфигурации клиента.

Подключение Burp Suite к цепочке перехвата Protobuf запросов

Настройка Burp как upstream-сервера и проверка связки

Откройте Burp Suite. По умолчанию его Proxy listener висит на порту 8080, но мы уже заняли его mitmproxy. Идём в Settings → Tools → Proxy → Proxy Listeners, выбираем активный listener и меняем порт на 8082 (или другой свободный). Убедитесь, что он слушает на 127.0.0.1:8082.

Проверка работоспособности цепочки: настройте браузер на прокси 127.0.0.1:8080 (mitmproxy). Откройте любой HTTPS-сайт. В терминале mitmdump должны побежать строки с URL запросов. Параллельно в Burp → Proxy → HTTP History должны появиться те же запросы. Видите трафик в обоих местах — цепочка работает.

Для gRPC-клиента, который не использует системные настройки прокси (а большинство gRPC-клиентов на Go, Java и Python их игнорируют), трафик перенаправляется через iptables. В этом случае mitmproxy должен работать в transparent mode (mitmdump --mode transparent --showhost -s grpc_decoder.py), а не в upstream mode — iptables перенаправляет raw TLS-трафик, а не HTTP CONNECT-запросы. Нужны права root. Команда sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 127.0.0.1:8080 перенаправляет весь исходящий HTTPS-трафик на mitmproxy. Проксирование далее в Burp в transparent mode настраивается отдельно (через скрипт или конфиг mitmproxy). Применяйте с осторожностью: правило затронет все TLS-соединения системы.

Для изоляции (как рекомендует Synacktiv в исследовании по mitmproxy) создайте отдельное сетевое пространство (network namespace) командой ip netns add mitm_lab и запускайте тестируемое приложение внутри него через ip netns exec mitm_lab ./grpc_client. Так iptables-правила не затронут остальную систему.

Ограничение связки: bidirectional streaming (когда клиент и сервер одновременно шлют потоки сообщений) через цепочку двух прокси работает нестабильно — сообщения могут теряться. Unary вызовы (один запрос — один ответ) и server streaming работают корректно. Если приложение активно использует bidirectional streaming, анализируйте трафик через mitmproxy напрямую, без Burp в цепочке.

Скрипт mitmproxy для декодирования gRPC трафика без .proto-файла

Ключевая часть связки — Python-аддон для mitmproxy, который конвертирует бинарный protobuf в читаемый JSON. На black-box пентесте .proto-файла (описание структуры сообщений) обычно нет — берём библиотеку blackboxprotobuf для «слепого» декодирования по бинарной структуре wire format.

Каждое gRPC-сообщение внутри HTTP/2-фрейма упаковано в gRPC Length-Prefixed Message: первый байт — флаг сжатия (0 = без сжатия, 1 = gzip), следующие 4 байта — длина сообщения (big endian), далее само protobuf-сообщение. Скрипт сначала распаковывает фрейм, потом декодирует содержимое.

import blackboxprotobuf
from mitmproxy import http
import struct, json

class GRPCDecoder:
    def _decode_bytes(self, obj):
        if isinstance(obj, bytes):
            return obj.decode("utf-8", errors="replace")
        if isinstance(obj, dict):
            return {k: self._decode_bytes(v) for k, v in obj.items()}
        if isinstance(obj, list):
            return [self._decode_bytes(i) for i in obj]
        return obj

    def response(self, flow: http.HTTPFlow):
        ct = flow.response.headers.get("content-type", "")
        if "grpc" not in ct or len(flow.response.content) < 5:
            return
        messages = []
        offset = 0
        data = flow.response.content
        while offset + 5 <= len(data):
            # flag = data[offset]  # 0=uncompressed, 1=gzip
            length = struct.unpack(">I", data[offset+1:offset+5])[0]
            if offset + 5 + length > len(data):
                break
            msg = data[offset+5:offset+5+length]
            decoded, typedef = blackboxprotobuf.decode_message(msg)
            messages.append(self._decode_bytes(decoded))
            offset += 5 + length
        # NB: для больших payload'ов лучше писать в flow.metadata или файл,
        # т.к. HTTP-заголовки обычно ограничены 8-16 КБ
        if len(json.dumps(messages, default=str, ensure_ascii=False)) < 8000:
            flow.response.headers["x-grpc-decoded"] = json.dumps(
                messages, default=str, ensure_ascii=False)
        else:
            flow.metadata["grpc_decoded"] = messages

addons = [GRPCDecoder()]

Что тут происходит: метод response вызывается для каждого ответа, проходящего через прокси. Проверка "grpc" in ct отсеивает обычные HTTP-ответы — декодируем только gRPC. struct.unpack(">I", ...) извлекает длину protobuf-сообщения из 4 байт (big endian). blackboxprotobuf.decode_message(msg) декодирует protobuf без .proto-файла: возвращает Python-словарь с данными и определённую структуру типов. Результат записывается в кастомный заголовок x-grpc-decoded — его увидит Burp Suite в HTTP History.

Ожидаемый результат: после прохождения gRPC-запроса через цепочку в Burp HTTP History у ответа появится заголовок x-grpc-decoded вида [{"1": "john_doe", "2": 42, "3": {"1": "john@example.com"}}] (массив, каждый элемент — одно gRPC-сообщение в потоке). Числовые ключи — номера полей protobuf (field numbers). Без .proto-файла имена полей неизвестны, но значения читаемы. Для обнаружения IDOR этого хватает.

Чтобы обрабатывать исходящие запросы (не только ответы), добавьте метод request по аналогии, заменив flow.response на flow.request. Для gRPC-Web с base64-кодированием (Content-Type application/grpc-web-text) перед распаковкой фрейма нужен base64.b64decode(flow.response.content).

Поиск IDOR в gRPC API: пошаговая методика

IDOR — уязвимость из категории A01:2021 Broken Access Control по классификации OWASP (94% приложений в выборке OWASP Top 10 2021 тестировались на эту категорию — максимальное покрытие среди всех категорий). Суть: приложение использует идентификатор объекта в запросе напрямую и не проверяет, имеет ли текущий пользователь право доступа.

В gRPC-сервисах IDOR встречается чаще, чем в REST API. Причина — ложное чувство безопасности от бинарного формата. Исследователь Kayssel формулирует точно: «many developers assume gRPC is ‘internal only’ so they skip security controls». Разработчики считают gRPC внутренним и пропускают проверки авторизации на уровне отдельных методов. Я видел это на нескольких проектах — и каждый раз находились IDOR’ы именно там, где «всё равно никто снаружи не вызовет».

Энумерация gRPC-сервисов и подмена идентификаторов

Предусловия: работающая цепочка mitmproxy → Burp Suite; доступ к двум учётным записям тестируемого приложения (пользователь A и пользователь B); gRPC-сервер доступен по сети. Сценарий: grey box (есть учётные данные обоих пользователей).

Шаг 1 — энумерация методов. Если на сервере включена gRPC reflection (механизм самодокументирования — клиент может запросить список доступных сервисов и методов), используйте grpcurl. Запустите grpcurl -plaintext target.com:50051 list. Ожидаемый вывод — список сервисов: myapp.UserService, myapp.OrderService. Далее grpcurl -plaintext target.com:50051 list myapp.UserService покажет методы: GetUser, UpdateUser, DeleteUser. Флаг -plaintext означает подключение без TLS — в продакшене убирайте его и подключайтесь через TLS.

Если reflection отключена — ищите .proto-файлы: в публичных репозиториях проекта, в мобильном приложении (декомпиляция APK/IPA), в JavaScript-бандлах gRPC-Web клиента. Для JS-бандлов извлеките gRPC-эндпоинты регулярным выражением: grep -oE '/[a-zA-Z]+\.[A-Z][a-zA-Z]+/[A-Z][a-zA-Z]+' main.js — покажет пути вида /myapp.UserService/GetUser. Для более глубокого анализа структур сообщений ищите вызовы proto. и serializeBinary в бандле. Для Go-бинарей protobuf-дескрипторы часто встроены и извлекаются через strings client_binary | grep -i "\.proto".

Шаг 2 — идентификация кандидатов на IDOR. В декодированном protobuf (заголовок x-grpc-decoded в Burp) ищите числовые поля, похожие на последовательные автоинкрементные ID. Маркеры кандидатов: метод типа Get/Read/Fetch*, возвращающий данные одного объекта; запрос содержит одно-два числовых поля; значение похоже на последовательный ID (1042, 1043), а не UUID.

Шаг 3 — подмена идентификатора. Авторизуйтесь как пользователь A. Вызовите GetUser со своим ID (например, {"1": 1042}). Запомните ответ: email, имя, другие данные. Измените ID на чужой: {"1": 1043} (пользователь B). Для gRPC-Web в Burp с расширением bRPC-Web или protobuf-magic: переключитесь на вкладку расширения в Repeater, измените значение поля, отправьте запрос — расширение перекодирует JSON обратно в protobuf автоматически.

Шаг 4 — анализ ответа. Сервер вернул данные пользователя B с токеном пользователя A — IDOR подтверждён. Фиксируйте в отчёте: эндпоинт (/myapp.UserService/GetUser), уязвимый параметр (field 1, предположительно user_id), тип уязвимости — BOLA (Broken Object Level Authorization, синоним IDOR в контексте API, термин из OWASP API Security Top 10), импакт — несанкционированный доступ к данным произвольных пользователей. Сервер вернул PERMISSION_DENIED (grpc-status: 7) — авторизация на этом методе работает; переходите к следующему кандидату.

На практике проверяйте не только «чтение» (GetUser), но и «запись» (UpdateUser, DeleteUser) — IDOR на модификацию данных критичнее, чем на чтение. Также проверяйте методы, не требующие аутентификации: если gRPC reflection открыта в продакшене, это уже находка класса Security Misconfiguration (A05:2021 по OWASP).

Инструменты тестирования gRPC безопасности: когда что применять

Инструмент Преимущества Ограничения Когда использовать Когда не использовать
Burp + protobuf-magic / bRPC-Web GUI, Scanner, Intruder Нет trailing headers HTTP/2, не работает с голым gRPC gRPC-Web из браузера Чистый gRPC без Web-обёртки
mitmproxy + Python-аддон Полный HTTP/2, скриптуемость, transparent mode Нет GUI для ручного fuzzing, нужен Python Голый gRPC, автоматизация перехвата Быстрый ручной анализ
grpcurl / grpcui Нативная работа с gRPC, reflection, .proto Нет MITM, только клиент Энумерация, ручной вызов методов Перехват в реальном времени
Burp + mitmproxy (связка) REST и gRPC в одном потоке Два процесса, сложнее настроить Смешанный REST + gRPC трафик Простые REST-only приложения
buf curl Нативный gRPC-Web, JSON-ввод Только gRPC-Web, не MITM CLI-тестирование gRPC-Web Голый gRPC

Контекст применимости: все инструменты из таблицы работают при внешнем и внутреннем пентесте, на любых gRPC-серверах (Go, Java, Python, Node.js). Ограничение по ОС: mitmproxy и grpcurl кроссплатформенны (Linux, macOS, Windows); transparent mode mitmproxy через iptables — только Linux.

Статус расширений Burp: protobuf-magic от Deiteriy Lab, bRPC-Web от Compass Security и Blackboxprotobuf от NCC Group — все три с открытым кодом на GitHub. Перед установкой проверяйте дату последнего коммита: расширения, не обновлявшиеся более года, могут не работать с текущей версией Burp.

Два года работы с gRPC на пентестах дали одну закономерность: чем новее микросервисная архитектура, тем хуже авторизация на уровне отдельных gRPC-методов. В REST API проверка прав привязана к маршрутам и реализуется middleware — фреймворк заставляет разработчика задуматься о контроле доступа. В gRPC авторизацию нужно вызывать вручную внутри каждого handler’а — и в каждом пятом-десятом вызове её забывают. IDOR в gRPC — не экзотика, а системная проблема бинарных протоколов, где отсутствие удобных инструментов тестирования создаёт иллюзию безопасности. Связка Burp + mitmproxy разрушает эту иллюзию за вечер настройки. Но настоящая работа начинается после: когда protobuf-поля расшифрованы и нужно системно перебрать каждый метод каждого сервиса. Автоматизация этого перебора через Python-аддоны mitmproxy — задача, к которой индустрия пока только подбирается.

Если хочешь отработать весь путь от энумерации gRPC-сервисов до эксплуатации IDOR на живом стенде — на HackerLab.pro есть категории web и forensics с задачами разного уровня сложности.

Эту тему и смежные навыки разбирают на практике в курсе «Тестирование веб-приложений на проникновение (WAPT)» Codeby Academy.