
На ноябрьском CTF от Intigriti задача AquaCommerce решалась цепочкой: подделка JWT через alg:none, эскалация до роли admin, SSTI в Jinja2 и удалённое выполнение кода на сервере. Вся цепочка — 15 минут, если знаешь порядок проверок. Остальные застревали на первом шаге: не проверили, принимает ли backend токен без подписи вообще. Уязвимости JWT токенов в CTF повторяют одни и те же паттерны из сезона в сезон — декорации меняются, базовые ошибки валидации остаются. Здесь — полный набор техник эксплуатации JWT в CTF с конкретными командами, пейлоадами и разбором механики на уровне библиотеки-валидатора.
Прежде чем ломать токен — прочитай его. JWT состоит из трёх частей, разделённых точками: header.payload.signature. Header и payload — base64url-кодированные JSON-объекты. Signature — криптографическая подпись, вычисленная по формуле HMAC/RSA(base64url(header) + "." + base64url(payload), secret). Подпись — единственное, что обеспечивает целостность токена. Без неё payload превращается в открытку, содержимое которой перепишет кто угодно. Подробнее — в нашем статье о пентест веб-приложений.
Первое действие при обнаружении JWT на CTF — декодировать. Вставь токен на jwt.io или выполни в терминале echo '<header>' | base64 -d. В header смотри на три параметра:
alg — заявленный алгоритм подписи (HS256, RS256, ES256, none)kid — идентификатор ключа, если присутствует (потенциальная точка инъекции)jwk, jku, x5u — ссылки на внешние ключи (вектор подмены ключа верификации)В payload ищи claims для модификации: sub, role, isAdmin, username, user_id, exp. Payload не зашифрован — это base64url-кодирование, не шифрование. Шифрование — это JWE (JSON Web Encryption), а стандартный JWT в формате JWS (JSON Web Signature) только подписывает данные. Любой читает содержимое токена без ключа.
Для автоматизации разведки запусти jwt_tool <token> — инструмент выведет декодированные header и payload, укажет алгоритм и подскажет потенциальные векторы атаки. Режим полного сканирования jwt_tool <token> -M at прогоняет все известные проверки за один запуск. Запускай его первым делом, пока вручную ковыряешь приложение.
С точки зрения MITRE ATT&CK, эксплуатация JWT попадает под технику T1606 Forge Web Credentials (Credential Access) — атакующий создаёт поддельные учётные данные для несанкционированного доступа. Мотивация проста: успешная подделка JWT = полный обход аутентификации = доступ к любому аккаунту или привилегиям. На CTF это флаг; в реальном мире — компрометация всей системы авторизации.
Самая простая и самая частая уязвимость JWT на CTF-площадках. Значение "alg": "none" по спецификации означает Unsecured JWT — токен без подписи. Если сервер принимает такие токены в production-режиме, подпись можно просто выкинуть, а claims переписать как душе угодно.
По данным PortSwigger Web Security Academy, уязвимость возникает из-за того, что сервер доверяет значению alg из самого токена для выбора метода верификации. Фундаментальный дефект: решение о безопасности принимается на основе данных, которые контролирует клиент.
Четыре шага:
alg на none. Обязательно попробуй варианты регистра: None, NONE, nOnE — часть библиотек фильтрует только строчное написание."role": "user" → "role": "admin", или "isAdmin": false → "isAdmin": true.+/ → -_) и отсутствии паддинга =.<new_header>.<new_payload>. — точка в конце обязательна, подписи после неё нет.На CTF AquaCommerce от Intigriti именно этот приём открыл доступ к панели администратора: смена "role": "user" на "role": "admin" с алгоритмом none и пустой подписью. После получения админ-доступа в административной панели обнаружилась SSTI в Jinja2, через которую участники добились RCE.
Через jwt_tool всё делается одной командой: jwt_tool <token> -X a. Флаг -X a запускает атаку alg:none и генерирует несколько вариантов токена с разным регистром слова «none».
Как отмечает PortSwigger, многие JWT-библиотеки предоставляют два метода: verify() и decode(). Разработчики путают их и вызывают decode() вместо verify(), полностью отключая проверку подписи. Второй сценарий — библиотека корректно отклоняет none, но разработчик не ограничил белый список допустимых алгоритмов на стороне сервера. Оба варианта — следствие A05:2021 Security Misconfiguration по OWASP.
Частая ошибка новичков (и на CTF сжирает кучу времени): забывают оставить завершающую точку после payload. Токен должен иметь формат header.payload. — с точкой, а не header.payload без неё. Без точки серверы отклоняют токен как синтаксически невалидный. Атака не срабатывает по формальной причине, а не из-за защиты.
Если alg:none не прошёл — проверяй стойкость секрета для HMAC-подписи (HS256/HS384/HS512). На CTF слабые секреты встречаются постоянно: secret, password, 123456, имя приложения, строка из описания задачи. Эта уязвимость подписи JWT попадает под A02:2021 Cryptographic Failures по OWASP.
Для перебора — hashcat с режимом 16500, специально заточенным под JWT:
hashcat -m 16500 jwt.txt -a 0 /usr/share/wordlists/rockyou.txt
В файл jwt.txt помещаешь полный токен (все три части через точки), -a 0 — словарная атака. На видеокарте уровня RTX 3070 перебор по rockyou (14 млн записей) занимает несколько минут.
Альтернатива — jwt_tool <token> -C -d rockyou.txt. У jwt_tool есть встроенный словарь популярных JWT-секретов — команда jwt_tool <token> -C без указания файла прогонит его за секунды. Для олдскульщиков работает John the Ripper: john jwt.txt --format=HMAC-SHA256.
После нахождения секрета пересобирай токен с изменёнными claims и правильной подписью. В jwt_tool: jwt_tool <token> -T -S hs256 -p '<found_secret>' — флаг -T открывает интерактивный редактор claims, -S hs256 задаёт алгоритм, -p — найденный секрет.
Типичная ошибка: пробовать brute force для RS256. RSA-ключи не перебираются словарём — это криптографические пары фиксированной длины (2048/4096 бит). Brute force применим только к симметричным алгоритмам (HS256/HS384/HS512) с текстовыми секретами. Если видишь RS256 — переходи к следующему разделу.
Algorithm Confusion (Key Confusion) — одна из самых элегантных атак на уязвимости JWT токенов. Суть: сервер использует RS256 (асимметричный — подпись приватным ключом, проверка публичным), а атакующий меняет alg на HS256 (симметричный — один ключ для подписи и проверки) и подписывает токен публичным ключом сервера как HMAC-секретом. Красиво, правда?
При RS256 сервер хранит публичный ключ для верификации подписи. Если библиотека не контролирует, что заявленный алгоритм соответствует ожидаемому типу ключа, она берёт тот же публичный ключ — но теперь использует его как HMAC-секрет для HS256. Публичный ключ RSA — информация, доступная любому клиенту. Атакующий получает полный контроль над «секретом» подписи и может штамповать валидные токены с произвольными claims.
Найди публичный ключ сервера. Типичные места: /.well-known/jwks.json, /jwks.json, /api/keys, заголовки HTTP-ответов, JavaScript-файлы фронтенда. На CTF ключ обычно валяется в открытом доступе — это часть задания.
Конвертируй ключ в PEM. Если ключ получен в формате JWK (JSON-объект с полями n, e, kty), используй OpenSSL или jwt_tool для конвертации в PEM-файл.
Подпиши токен с подменённым алгоритмом:
jwt_tool <token> -X k -pk public.pem
Флаг -X k запускает Key Confusion attack. Инструмент автоматически сменит alg на HS256 и подпишет токен содержимым файла public.pem как HMAC-секретом.
Нюанс, на котором застревают: некоторые библиотеки ожидают ключ в строго определённом формате — PEM с переносами строк \n, без них, в чистом base64 или с определённым окончанием строки. Если атака не работает с первой попытки — пробуй варианты форматирования. Проверь, нет ли у PEM-файла лишнего символа переноса строки в конце. На CTF эта мелочь сжирает больше времени, чем сама атака.
Algorithm confusion работает только когда сервер допускает переключение между семействами алгоритмов (RSA → HMAC). Если на стороне сервера жёстко зафиксирован RS256 через белый список и библиотека игнорирует alg из клиентского токена — не пройдёт.
Параметр kid (Key ID) в JWT header manipulation — один из самых опасных и недооценённых векторов. По спецификации kid — просто строка-идентификатор ключа, но реализации нередко используют его как путь к файлу, параметр SQL-запроса или аргумент команды. Это превращает kid в универсальную точку инъекции.
Если сервер читает ключ из файла по пути, указанному в kid, можно направить его на файл с известным (или пустым) содержимым:
{
"alg": "HS256",
"typ": "JWT",
"kid": "../../../../dev/null"
}
/dev/null возвращает пустые данные. Подпиши токен пустой строкой ("") — подпись совпадёт с «ключом», прочитанным из /dev/null. Альтернативы: /proc/sys/kernel/hostname или любой файл с предсказуемым содержимым, если знаешь его наперёд.
Команда через jwt_tool: jwt_tool <token> -I -hc kid -hv "../../../../dev/null" -S hs256 -p "". Здесь -I — injection mode, -hc kid — целевой параметр в header, -hv — подставляемое значение, -p "" — пустой секрет подписи.
По данным PortSwigger, kid path traversal — одна из наиболее распространённых инъекций в JWT header. Атака соответствует A03:2021 Injection по OWASP.
Когда значение kid используется в SQL-запросе для получения ключа из базы данных (SELECT key FROM keys WHERE kid = '<kid_value>'), открывается вектор SQL-инъекции. Типичный пейлоад: "kid": "1' UNION SELECT 'controlled-secret' -- ". Запрос вернёт строку controlled-secret вместо реального ключа, и токен подписывается этим значением.
Как описывает Red Team Pentesting, SQLi через kid позволяет контролировать ключ верификации — это session hijacking через JWT в чистом виде. Атакующий генерирует валидные токены для любого пользователя системы.
В редких случаях значение kid передаётся в системную команду (вызов внешнего скрипта для получения ключа). Работают стандартные пейлоады: "kid": "key1 | cat /flag.txt" или "kid": "key1; id". На CTF этот вектор встречается реже path traversal и SQLi, но проверять стоит — особенно когда другие инъекции не дали результата.
JWK (JSON Web Key) и JKU (JWK Set URL) — параметры header, через которые токен может нести информацию о ключе верификации. Если сервер доверяет этим параметрам из входящего токена без дополнительной валидации — атакующий подставляет собственный ключ и подписывает токен им.
Атакующий генерирует собственную пару RSA-ключей, подписывает токен своим приватным ключом и встраивает публичный ключ прямо в header через параметр jwk. Уязвимая библиотека берёт этот встроенный ключ для верификации — и подпись проходит. По сути, токен сам приносит ключ, которым его надо проверять. Абсурд, но работает.
Каноничный пример — CVE-2018-0114 в библиотеке Cisco node-jose (npm-пакет node-jose, версии до 0.11.0). По данным NVD, уязвимость имеет CVSS 7.5 (HIGH), вектор CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N — эксплуатация по сети, с низкой сложностью, без аутентификации и без взаимодействия пользователя, с высоким воздействием на целостность. Корень проблемы — CWE-347 (Improper Verification of Cryptographic Signature): библиотека следовала стандарту JWS, допускающему передачу JWK в header, и доверяла этому ключу без ограничений.
По данным EPSS, вероятность эксплуатации CVE-2018-0114 в течение 30 дней составляет 42.65% (percentile 98.63% — top 5% всех CVE), CISA классифицирует статус как «PoC available, automatable». Публичный PoC доступен в репозитории zi0Black/POC-CVE-2018-0114 на GitHub (26 звёзд).
Через jwt_tool подделка JWT токена с JWK injection выполняется командой jwt_tool <token> -X i — флаг -X i генерирует новую пару ключей, подписывает токен и встраивает JWK в header автоматически.
Вместо встраивания ключа атакующий указывает URL к своему серверу через параметр jku. Сервер обращается по указанному адресу, загружает набор ключей (JWKS) и использует один из них для верификации. Атакующий хостит свой JWKS-файл с публичным ключом, соответствующим приватному, которым подписан токен.
На CTF реализуется через Burp Collaborator, ngrok или локальный Python-сервер: устанавливаешь "jku": "https://attacker.com/.well-known/jwks.json" в header, подписываешь токен своим приватным ключом, публичный ключ размещаешь по указанному URL в стандартном формате JWKS. Если сервер не ограничивает домены для jku — подделка проходит. Побочный эффект: это ещё и вектор SSRF (A10:2021 по OWASP) — сервер совершает запрос на произвольный URL, контролируемый клиентом.
Все описанные атаки — способы обойти проверку подписи. После обхода начинается главное: изменение данных в payload для эскалации привилегий. Прямое попадание под A01:2021 Broken Access Control по OWASP — ограничения доступа для аутентифицированных пользователей не проверяются серверной логикой независимо от содержимого токена.
Типичные claims для модификации на CTF:
| Claim | Оригинал | Подмена | Эффект |
|---|---|---|---|
role |
"user" |
"admin" |
Эскалация через роль |
isAdmin |
false |
true |
Булевый флаг |
sub |
"regular_user" |
"administrator" |
Подмена субъекта |
user_id |
7 |
1 |
Первый ID часто admin |
exp |
1600000000 |
9999999999 |
Токен до 2286 года |
Не ограничивайся стандартными полями. На CTF контроль доступа бывает завязан на нестандартные claims — group, tier, plan, scope, permissions. Декодируй токен и меняй каждое поле по очереди, наблюдая за реакцией сервера. Иногда достаточно изменить одно числовое значение, чтобы получить доступ к скрытому API или админке.
Отдельный паттерн, о котором упоминает PortSwigger: часть серверов проверяет подпись только при наличии определённых условий (конкретное значение alg, определённые заголовки запроса), а в остальных случаях просто декодирует payload через decode() вместо verify(). Прежде чем тратить время на обход подписи — попробуй изменить claims и отправить токен без каких-либо изменений в signature. Иногда это срабатывает без единого дополнительного действия. Серьёзно — просто меняешь "role": "admin" и отправляешь.
jwt_tool — центральный инструмент при работе с JWT на CTF. Покрывает все описанные атаки и автоматизирует рутину. Порядок действий, который работает на большинстве заданий:
Шаг 1 — разведка. jwt_tool <token> выводит декодированные header и payload, определяет алгоритм и предлагает потенциальные векторы.
Шаг 2 — автоматический скан. jwt_tool <token> -M at прогоняет все тесты: alg:none в нескольких регистрах, пустая подпись, algorithm confusion, базовые инъекции. На CTF с ограниченным временем этот шаг критичен — пока ты вручную изучаешь приложение, скан уже нашёл дыру.
Шаг 3 — целевая атака. В зависимости от результатов скана:
- alg:none: -X a
- Brute force секрета: -C -d rockyou.txt
- Key confusion: -X k -pk public.pem
- JWK injection: -X i
- kid injection: -I -hc kid -hv "../../dev/null" -S hs256 -p ""
Шаг 4 — модификация claims. После нахождения рабочего метода подписи: jwt_tool <token> -T открывает интерактивный редактор claims, после чего токен подписывается выбранным способом.
Дополнительно держи под рукой: расширение JWT Editor для Burp Suite (визуальное редактирование и автоподпись токенов прямо в перехваченных запросах), CyberChef для ручного base64url-кодирования, jwt.io для быстрой визуализации структуры.
Когда встречаешь JWT в задании, иди по списку сверху вниз до первого срабатывания:
hashcat -m 16500 или jwt_tool -C)Большинство задач решаются на шагах 2-4. Шаги 5-7 — для заданий повышенной сложности. Шаг 8 — для нестандартных случаев, где уязвимость не в подписи, а в бизнес-логике обработки claims.
Три приёма закрывают 80% JWT-задач на CTF: alg:none, слабый секрет, algorithm confusion. Но порядок проверок важнее количества известных атак. И вот в чём штука: CTF-игроки выучивают эти три трюка, решают таски — и считают, что понимают JSON Web Token безопасность целиком.
На реальном пентесте всё иначе. Нет подсказки в названии задания. Нет гарантии, что JWT вообще уязвим. Зато есть цепочки: JWT как входная точка → доступ к внутреннему API → SSRF через значение claim → чтение конфигов с секретами соседних сервисов. На CTF ломается токен. На пентесте ломается архитектура, где токен — один из элементов.
CTF оттачивают скорость распознавания паттернов — увидел JWT, запустил jwt_tool, получил флаг. Пентест требует понимания бизнес-логики приложения, архитектуры микросервисов, маршрутов данных между компонентами. Kid injection на CTF — подставить ../../dev/null и забрать флаг. Kid injection в продакшене — разобраться, откуда приложение тянет ключи, какая СУБД стоит за запросом, какие символы режет WAF. WAPT для тех, кому writeups мало — нужны лабы с прогрессом и ментором.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...