Burp Suite JWT атаки: от подмены claim до перебора секретов через Turbo Intruder

На пентесте API логистической SaaS-платформы JWT-токен отдал полный доступ к админ-панели за четыре минуты. Секрет подписи — слово secret. Шесть символов. Замена "role":"user" на "role":"admin" в payload, пересчёт подписи с найденным секретом, один запрос через Burp Repeater — сервер ответил 200 OK вместо 403 Forbidden. Вся эскалация привилегий уместилась в три запроса.
Дальше — пошаговый разбор, как воспроизвести каждый шаг: от ручной подмены claim-значений до автоматизированного перебора слабых секретов через Turbo Intruder. С акцентом на то, как отличить успешную атаку от неудачной по ответу сервера — потому что без этого навыка все остальные техники бесполезны.
Место JWT-атак в цепочке веб-пентеста
JWT (JSON Web Token) — компактный формат токена для передачи подписанных данных между клиентом и сервером. Используется в большинстве современных API для управления сессиями. Если вы видели строку вида eyJhbGci... в заголовке запроса — это он.
В цепочке атаки JWT-уязвимости появляются после получения начального доступа:
- Initial Access — получение учётных данных обычного пользователя (фишинг, перебор, утечки)
- Authentication — вход в систему, получение JWT-токена
- JWT-атака — модификация токена для эскалации привилегий (здесь работаем)
- Impact — доступ к данным других пользователей, админ-функции, горизонтальное перемещение по API
Бизнес-логика атаки проста: вместо перебора пароля администратора достаточно подменить одно поле в уже полученном токене обычного пользователя. Если сервер не проверяет подпись или использует слабый секрет — аутентификация рассыпается целиком.
По классификации OWASP Top 10, JWT-атаки пересекаются с A01:2021 — Broken Access Control (приложение не проверяет, имеет ли пользователь право выполнять действие) и A02:2021 — Cryptographic Failures (криптографические ошибки, включая слабые секреты подписи).
Требования к окружению
Прежде чем переходить к практике, проверьте, что всё на месте:
- ОС: Kali Linux 2024.1+, Ubuntu 22.04+ или Windows 10/11 с WSL2. macOS работает, но hashcat без дискретного GPU будет медленным
- RAM: минимум 4 ГБ, рекомендуется 8 ГБ (Burp Suite + браузер + учебный стенд одновременно)
- Burp Suite: Community Edition достаточно для Repeater и расширений. Professional Edition — для Intruder без ограничения скорости
- Расширения Burp: JWT Editor (доступен в BApp Store — встроенный магазин расширений Burp, вкладка Extender → BApp Store), Turbo Intruder (там же, бесплатно)
- hashcat: версия 6.0+ (предустановлен в Kali; для GPU-ускорения нужны драйверы NVIDIA или AMD)
- Стенд для практики: PortSwigger Web Security Academy — бесплатные лабы по JWT, регистрация без кредитной карты. Или собственный Docker-контейнер с уязвимым приложением
- Словарь секретов: репозиторий jwt-secrets от Wallarm на GitHub — типичные слабые ключи (
secret,password,changemeи ещё несколько тысяч)
JWT глазами пентестера: что важно для Burp Suite JWT атаки
JWT-токен — три части через точку: header.payload.signature. Каждая закодирована в base64url (вариант base64, безопасный для URL — декодируется в читаемый JSON).
Header сообщает серверу, каким алгоритмом подписан токен. Типичный пример: {"alg":"HS256","typ":"JWT"}. HS256 — это HMAC-SHA256, симметричный алгоритм: один и тот же секрет используется и для создания подписи, и для проверки. Секрет утёк — любой может подписать любой токен.
Payload содержит claim-значения — утверждения о пользователе: {"sub":"1234","name":"user","role":"user"}. Поле role определяет уровень привилегий, sub — идентификатор пользователя. Именно эти поля становятся целью подмены.
Signature — HMAC-SHA256 от склейки закодированных header и payload с секретным ключом. Формула: HMAC-SHA256(base64url(header) + "." + base64url(payload), secret_key). Вся безопасность JWT держится на этой подписи: если она не проверяется или секрет можно угадать — токен превращается в записку, которую составит кто угодно.
Где искать JWT в Burp: вкладка Proxy → HTTP history, ищите запросы с заголовком Authorization: Bearer eyJ... или Cookie с eyJ. Правый клик → Send to Repeater — и можно работать. С установленным JWT Editor в Repeater появится вкладка JSON Web Token с разобранным содержимым.
Подмена JWT claim в Burp Repeater: пошаговая эскалация привилегий
Самая простая и при этом часто результативная атака — изменение claim-значений без пересчёта подписи. Если сервер не проверяет подпись, он примет любые данные в payload. По документации PortSwigger Web Security Academy, эта ошибка возникает, когда бэкенд вызывает decode() вместо verify() при обработке токена. Характерно для ранних реализаций на Node.js с библиотекой jsonwebtoken.
Предусловие: у вас есть валидный JWT обычного пользователя (залогинились через веб-форму, токен перехвачен в Burp Proxy).
Шаг 1. Найдите запрос с JWT в Burp Proxy → HTTP history. Правый клик → Send to Repeater (Ctrl+R).
Шаг 2. Переключитесь на вкладку JSON Web Token в панели запроса. Токен разобран на header и payload в читаемом JSON.
Шаг 3. Измените claim, отвечающий за привилегии. Варианты для проверки:
— "role":"user" → "role":"admin"
— "isAdmin":false → "isAdmin":true
— "sub":"1234" → "sub":"1" (ID первого пользователя — нередко это аккаунт администратора)
Шаг 4. Нажмите Send. Подпись не пересчитывайте — именно это и тестируется.
Как читать результат:
— Сервер вернул 200 OK с данными, недоступными обычному пользователю → подпись не проверяется, уязвимость подтверждена
— Сервер вернул 401 Unauthorized или 403 Forbidden → подпись проверяется, переходите к следующей технике
Когда работает: кастомные реализации JWT без code review, устаревшие версии Node.js библиотек, проекты на микрофреймворках без middleware валидации. Когда не работает: Spring Security, ASP.NET Core Identity, Flask-JWT-Extended и вообще любые библиотеки с дефолтной верификацией подписи — а это большинство современных фреймворков.
Атака alg:none — обход подписи JWT-токена
Прямая подмена claim не сработала — следующий шаг: атака с алгоритмом none. Идея: заменить алгоритм в header на none и отправить токен вообще без подписи. Некоторые серверные библиотеки принимают это как валидный вариант.
Шаг 1. В Burp Repeater на вкладке JSON Web Token измените header: "alg":"HS256" → "alg":"none".
Шаг 2. Измените нужный claim в payload — например, "role":"admin".
Шаг 3. Удалите третью часть токена (всё после последней точки — это signature), но оставьте саму точку. Результат: eyJ...header.eyJ...payload. — токен заканчивается точкой без подписи.
Шаг 4. Отправьте запрос.
Как читать результат:
— 200 OK с расширенными данными → сервер принимает alg:none, критическая уязвимость
— 401/403 → сервер блокирует none
Варианты обхода: некоторые библиотеки фильтруют none в нижнем регистре, но пропускают None, NONE или nOnE. Попробуйте 3–4 регистровых вариации — иногда этого хватает.
Когда работает: legacy-сервисы на устаревших JWT-библиотеках, конфигурации без whitelist допустимых алгоритмов. Когда не работает: актуальные версии PyJWT (>= 2.0), jsonwebtoken (>= 9.0), java-jwt (>= 4.0) — все блокируют none по умолчанию. Серверы с явно указанным algorithms=["HS256"] в настройках тоже не пропустят.
JWT brute force: перебор слабого секрета HS256
Подпись проверяется корректно, alg:none заблокирован — остаётся путь через секрет. Для HS256 (симметричный алгоритм) секрет нередко оказывается слабым: secret, password, changeme, название компании, дата запуска проекта. Перебор таких секретов — вполне реалистичная атака.
Выбор инструмента: hashcat, Turbo Intruder или Burp Intruder
| Критерий | Burp Intruder | Turbo Intruder | hashcat (mode 16500) |
|---|---|---|---|
| Тип перебора | Онлайн (запросы к серверу) | Онлайн (запросы к серверу) | Офлайн (локальный крекинг) |
| Скорость | ~100 req/s (Community), ~1000 (Pro) | До 30 000 req/s к одному хосту | Миллионы хешей/с на GPU |
| Преимущества | GUI без кода, встроен в Burp | Python-скрипты, гибкие фильтры ответов | GPU-ускорение, огромные словари |
| Ограничения | Throttle в Community Edition | Нужна предварительная генерация JWT | Не проверяет реакцию сервера |
| Когда использовать | Быстрая проверка 10–50 секретов | Онлайн-проверка 1000+ кандидатов | Серьёзный перебор с GPU |
| Когда НЕ использовать | Большие словари — медленно | Офлайн-крекинг | Нужно видеть ответ сервера |
Я начинаю с hashcat. Он быстрее, работает офлайн и не оставляет логов на целевом сервере. Turbo Intruder (расширение Burp для массовой отправки HTTP-запросов с управлением через Python-скрипты, автор — James Kettle из PortSwigger) подключаю, когда нужно проверить реакцию сервера на конкретные токены или протестировать серию модифицированных claim-значений.
Офлайн-перебор через hashcat
Зачем: hashcat перебирает секреты локально, без единого запроса к серверу — быстро, тихо, не попадает в логи WAF (Web Application Firewall — межсетевой экран уровня приложений, который может заблокировать IP за аномальную активность).
Шаг 1. Скопируйте полный JWT из Burp (все три части через точки).
Шаг 2. Запустите команду:
hashcat -a 0 -m 16500 "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" /path/to/jwt-secrets.txt
Здесь -a 0 — атака по словарю, -m 16500 — режим JWT. Файл jwt-secrets.txt — словарь слабых секретов.
Если секрет найден, hashcat выведет строку формата токен:секрет. На среднем GPU (NVIDIA GTX 1070+) словарь из 10 000 секретов обрабатывается за секунды. Без GPU — за десятки секунд на CPU.
Шаг 3. С найденным секретом возвращайтесь в Burp Repeater: модифицируйте claim в payload, пересчитайте подпись через JWT Editor (кнопка Sign → ввод секрета → алгоритм HS256). Отправьте запрос и проверьте ответ.
Онлайн-перебор через Turbo Intruder
Зачем: когда нужно не просто найти секрет, а проверить, какие именно токены сервер принимает. Или когда hashcat недоступен, а список кандидатов компактный (до 10 000 значений).
Предусловие: Turbo Intruder установлен из BApp Store (Extender → BApp Store → поиск «Turbo Intruder» → Install). Расширение активно поддерживается PortSwigger, поддерживает HTTP/2 single-packet attack.
Workflow — два этапа: генерация JWT-кандидатов на рабочей машине и массовая отправка через Turbo Intruder.
Шаг 1. Подготовьте файл с JWT-кандидатами. На рабочей машине (не внутри Burp) напишите Python-скрипт: берёте header и payload из перехваченного токена (всё до второй точки), для каждого секрета из словаря вычисляете HMAC-SHA256 подпись и собираете полный JWT. Сохраните результат в jwt-candidates.txt — по одному токену на строку. Модули hmac, hashlib, base64 — три строки логики в цикле. На выходе: файл с тысячами строк, каждая — полноценный JWT, подписанный одним из секретов-кандидатов.
Шаг 2. В Burp найдите запрос с JWT, выделите значение токена (после Bearer в заголовке Authorization), правый клик → Extensions → Send to Turbo Intruder. Выделенная область заменится на %s — маркер для подстановки.
Шаг 3. В окне Turbo Intruder замените стандартный скрипт:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=10, engine=Engine.BURP2)
for line in open('/path/to/jwt-candidates.txt'):
engine.queue(target.req, line.rstrip())
def handleResponse(req, interesting):
if req.status != 401 and req.status != 403:
table.add(req)
concurrentConnections=10 — число параллельных соединений. Уменьшите до 3–5, если сервер начинает отвечать ошибками или включает rate-limiting. Функция handleResponse фильтрует ответы: в таблицу попадают только запросы, где сервер НЕ вернул 401 или 403 — то есть принял токен.
Шаг 4. Запустите атаку (Ctrl+Enter). Следите за таблицей: строка со статусом 200 OK и отличающимся Content-Length — это токен, подписанный правильным секретом.
На стенде PortSwigger Web Security Academy (лаба «JWT authentication bypass via weak signing key») при словаре из нескольких тысяч кандидатов секрет находится за 2–5 секунд.
Как читать ответы сервера: фиксируем разницу при JWT-атаках
Главный навык при тестировании JWT уязвимостей — умение отличить успешную атаку от неудачной по ответу сервера. Без этого все предыдущие техники — стрельба вслепую.
Статус-код. Переход с 403 Forbidden или 401 Unauthorized на 200 OK — самый очевидный признак. Но не единственный: некоторые API возвращают 200 всегда, а результат кодируют в теле ответа.
Content-Length. Даже при одинаковом статусе длина ответа может измениться. В Turbo Intruder колонка Length позволяет быстро найти аномалию: отсортируйте по ней и ищите строки, которые выбиваются из основной массы.
Тело ответа. Появились ли данные, недоступные обычному пользователю? Поля admin_panel_url, all_users, расширенный JSON с конфиденциальной информацией — верный признак эскалации привилегий.
Заголовки ответа. Отдельные серверы возвращают X-User-Role: admin или устанавливают дополнительные cookies при аутентификации с повышенными привилегиями. Сравните заголовки до и после подмены claim.
Время ответа. Разница во времени обработки (timing side-channel) может указывать на то, что сервер пошёл по другой ветке кода — подгружает данные администратора вместо данных обычного пользователя.
Для сравнения ответов в Burp Repeater используйте Comparer: отправьте оригинальный запрос, затем модифицированный, правый клик по каждому ответу → Send to Comparer → режим Words или Bytes. Burp подсветит изменившиеся строки — это и есть доказательство для отчёта.
Ограничения JWT-атак в современных средах
Каждая техника имеет конкретные границы. Без понимания этих границ тестирование JWT безопасности превращается в перебор техник наугад.
Подмена claim без пересчёта подписи. Работает только при отсутствии верификации подписи на бэкенде — ошибка, характерная для кастомных реализаций и учебных проектов. Spring Security, Django REST framework с PyJWT, Express.js с актуальным jsonwebtoken — все проверяют подпись по умолчанию. Вероятность находки на продакшен-API крупной компании: низкая. На стартапах с самописной аутентификацией: заметно выше.
Атака alg:none. Заблокирована в актуальных версиях всех популярных JWT-библиотек. Шанс обнаружить на legacy-сервисе, который не обновлялся с 2019–2020 года — реальный. На свежих стеках — близок к нулю.
Перебор секрета HS256. Не применим к RS256/RS512 (асимметричные алгоритмы — общего секрета нет, подпись создаётся приватным ключом). Не эффективен, если секрет сгенерирован криптографически (256+ бит случайных данных — перебор нереалистичен даже на топовом GPU). Работает против человекочитаемых секретов: слова из словаря, короткие строки (< 20 символов), значения из дефолтных конфигураций.
Turbo Intruder при онлайн-переборе. Rate-limiting на сервере ограничивает скорость. WAF может заблокировать IP после серии аномальных запросов. На серверах с HTTP/1.1 максимальная скорость ниже, чем на HTTP/2 (single-packet attack — отправка десятков запросов в одном TCP-пакете — работает только через HTTP/2).
Общее ограничение: JWT-токены с коротким сроком жизни (exp — поле expiration, обычно от 5 до 30 минут) сужают окно атаки. Токен истёк до завершения перебора — нужно получить свежий и начать заново.
Чеклист JWT-проверок на пентесте
Этот список — шпаргалка при тестировании любого API с JWT-аутентификацией. Каждый пункт — одно действие.
-
Перехват и декодирование. Залогиниться, найти JWT в Burp Proxy (заголовок Authorization или Cookie). Декодировать payload на jwt.io или во вкладке JSON Web Token. Записать алгоритм из header и все claim-значения из payload.
-
Подмена claim без подписи. Изменить
role,isAdmin,subили аналогичный claim. Отправить без пересчёта подписи. Сравнить ответы —200 OKзначит, что подпись не проверяется. -
Тест alg:none. Заменить
algнаnone, удалить signature (оставить точку). Попробовать вариации:None,NONE,nOnE. Сервер принимает — критическая уязвимость. -
Algorithm confusion. Если сервер использует RS256, попробовать подменить на HS256 и подписать публичным ключом как HMAC-секретом. Публичный ключ иногда доступен по эндпоинту
/.well-known/jwks.json. -
Перебор секрета. Сохранить полный токен, запустить
hashcat -m 16500со словарём слабых секретов. При нахождении — пересчитать подпись для модифицированного payload через JWT Editor. -
Проверка expiration. Отправить токен с заведомо истёкшим
exp. Сервер принимает — проблема с валидацией срока действия. -
Горизонтальная эскалация. Подменить
subна ID другого пользователя. Подписать корректным секретом (если известен). Проверить доступ к чужим данным. -
JWK/JKU injection. Проверить, принимает ли сервер параметры
jwk(ключ в header) илиjku(URL для загрузки ключа) — если да, можно подставить свой ключ для подписи. -
kid path traversal. Если в header есть
kid(Key ID), попробовать path traversal:"kid":"../../dev/null"— сервер может использовать содержимое файла как секрет, а пустой файл = пустой секрет. -
Документирование. Для каждой уязвимости зафиксировать оригинальный запрос, модифицированный запрос, diff ответов. Скриншоты из Burp Comparer — убедительное доказательство для отчёта.
Большинство руководств по JWT-атакам делают акцент на экзотике — algorithm confusion, JWK injection, kid path traversal. Красиво, элегантно, впечатляет в writeup. На практике — из примерно 20 API-проектов с JWT, которые прошли через руки за последние два года, в 14 случаях секрет подобрался словарём. Не RS→HS, не kid traversal — банальный hashcat -m 16500 со стандартным wordlist.
Разработчики ставят SECRET_KEY = "mysecret" в конфиге при первом деплое, а потом это значение мигрирует через docker-compose и .env-файлы годами. Алгоритм подписи может быть сколь угодно криптостойким — если ключ password123, стойкость равна нулю.
Поэтому первый шаг на пентесте JWT — не поиск сложных уязвимостей в логике валидации, а пять минут с hashcat и словарём из репозитория Wallarm. Этот шаг чаще определяет весь дальнейший ход тестирования, чем часы экспериментов с header injection. Если хочешь не просто статьи, а пройти базу системно — IB Basics на codeby.school закрывает фундамент за пару месяцев, включая работу с реальными инструментами.
Эту тему и смежные навыки разбирают на практике в курсе «Тестирование веб-приложений на проникновение (WAPT)» Codeby Academy.