Главная / Блог / Уязвимости JWT токенов в CTF: от alg none до RCE

13 мин.00

Уязвимости JWT токенов в CTF: от alg none до RCE

Уязвимости JWT токенов в CTF: от alg none до RCE

На ноябрьском CTF от Intigriti задача AquaCommerce решалась цепочкой: подделка JWT через alg:none, эскалация до роли admin, SSTI в Jinja2 и удалённое выполнение кода на сервере. Вся цепочка — 15 минут, если знаешь порядок проверок. Остальные застревали на первом шаге: не проверили, принимает ли backend токен без подписи вообще. Уязвимости JWT токенов в CTF повторяют одни и те же паттерны из сезона в сезон — декорации меняются, базовые ошибки валидации остаются. Здесь — полный набор техник эксплуатации 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 это флаг; в реальном мире — компрометация всей системы авторизации.

Атака alg none JWT: обход подписи токена

Самая простая и самая частая уязвимость JWT на CTF-площадках. Значение "alg": "none" по спецификации означает Unsecured JWT — токен без подписи. Если сервер принимает такие токены в production-режиме, подпись можно просто выкинуть, а claims переписать как душе угодно.

По данным PortSwigger Web Security Academy, уязвимость возникает из-за того, что сервер доверяет значению alg из самого токена для выбора метода верификации. Фундаментальный дефект: решение о безопасности принимается на основе данных, которые контролирует клиент.

Пошаговая эксплуатация вручную

Четыре шага:

  1. Декодируй header из base64url и замени alg на none. Обязательно попробуй варианты регистра: None, NONE, nOnE — часть библиотек фильтрует только строчное написание.
  2. Декодируй payload и измени целевые claims: "role": "user""role": "admin", или "isAdmin": false"isAdmin": true.
  3. Закодируй оба объекта обратно в base64url. Именно base64url, не стандартный base64 — разница в символах (+/-_) и отсутствии паддинга =.
  4. Собери токен в формате <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».

Почему библиотеки пропускают none

Как отмечает PortSwigger, многие JWT-библиотеки предоставляют два метода: verify() и decode(). Разработчики путают их и вызывают decode() вместо verify(), полностью отключая проверку подписи. Второй сценарий — библиотека корректно отклоняет none, но разработчик не ограничил белый список допустимых алгоритмов на стороне сервера. Оба варианта — следствие A05:2021 Security Misconfiguration по OWASP.

Частая ошибка новичков (и на CTF сжирает кучу времени): забывают оставить завершающую точку после payload. Токен должен иметь формат header.payload. — с точкой, а не header.payload без неё. Без точки серверы отклоняют токен как синтаксически невалидный. Атака не срабатывает по формальной причине, а не из-за защиты.

Brute force секрета JWT: hashcat и словарные атаки

Если 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 — переходи к следующему разделу.

JWT Confusion атака: подмена RS256 на HS256

Algorithm Confusion (Key Confusion) — одна из самых элегантных атак на уязвимости JWT токенов. Суть: сервер использует RS256 (асимметричный — подпись приватным ключом, проверка публичным), а атакующий меняет alg на HS256 (симметричный — один ключ для подписи и проверки) и подписывает токен публичным ключом сервера как HMAC-секретом. Красиво, правда?

Механика на уровне библиотеки

При RS256 сервер хранит публичный ключ для верификации подписи. Если библиотека не контролирует, что заявленный алгоритм соответствует ожидаемому типу ключа, она берёт тот же публичный ключ — но теперь использует его как HMAC-секрет для HS256. Публичный ключ RSA — информация, доступная любому клиенту. Атакующий получает полный контроль над «секретом» подписи и может штамповать валидные токены с произвольными claims.

Пошаговая эксплуатация

  1. Найди публичный ключ сервера. Типичные места: /.well-known/jwks.json, /jwks.json, /api/keys, заголовки HTTP-ответов, JavaScript-файлы фронтенда. На CTF ключ обычно валяется в открытом доступе — это часть задания.

  2. Конвертируй ключ в PEM. Если ключ получен в формате JWK (JSON-объект с полями n, e, kty), используй OpenSSL или jwt_tool для конвертации в PEM-файл.

  3. Подпиши токен с подменённым алгоритмом:

jwt_tool <token> -X k -pk public.pem

Флаг -X k запускает Key Confusion attack. Инструмент автоматически сменит alg на HS256 и подпишет токен содержимым файла public.pem как HMAC-секретом.

  1. Отправь токен на сервер. Если библиотека уязвима — подпись пройдёт верификацию.

Нюанс, на котором застревают: некоторые библиотеки ожидают ключ в строго определённом формате — PEM с переносами строк \n, без них, в чистом base64 или с определённым окончанием строки. Если атака не работает с первой попытки — пробуй варианты форматирования. Проверь, нет ли у PEM-файла лишнего символа переноса строки в конце. На CTF эта мелочь сжирает больше времени, чем сама атака.

Algorithm confusion работает только когда сервер допускает переключение между семействами алгоритмов (RSA → HMAC). Если на стороне сервера жёстко зафиксирован RS256 через белый список и библиотека игнорирует alg из клиентского токена — не пройдёт.

Kid injection JWT: от path traversal до SQLi

Параметр kid (Key ID) в JWT header manipulation — один из самых опасных и недооценённых векторов. По спецификации kid — просто строка-идентификатор ключа, но реализации нередко используют его как путь к файлу, параметр SQL-запроса или аргумент команды. Это превращает kid в универсальную точку инъекции.

Path traversal через 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.

SQLi через kid

Когда значение 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 в чистом виде. Атакующий генерирует валидные токены для любого пользователя системы.

Command injection через kid

В редких случаях значение kid передаётся в системную команду (вызов внешнего скрипта для получения ключа). Работают стандартные пейлоады: "kid": "key1 | cat /flag.txt" или "kid": "key1; id". На CTF этот вектор встречается реже path traversal и SQLi, но проверять стоит — особенно когда другие инъекции не дали результата.

Подделка JWT токена через JWK и JKU Injection

JWK (JSON Web Key) и JKU (JWK Set URL) — параметры header, через которые токен может нести информацию о ключе верификации. Если сервер доверяет этим параметрам из входящего токена без дополнительной валидации — атакующий подставляет собственный ключ и подписывает токен им.

JWK Injection и CVE-2018-0114

Атакующий генерирует собственную пару 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 автоматически.

JKU Injection

Вместо встраивания ключа атакующий указывает 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, контролируемый клиентом.

Манипуляция claims JWT для обхода аутентификации

Все описанные атаки — способы обойти проверку подписи. После обхода начинается главное: изменение данных в 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 web CTF заданий

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 web CTF заданий

Когда встречаешь JWT в задании, иди по списку сверху вниз до первого срабатывания:

  1. Декодируй токен, изучи header и payload
  2. Измени claims без изменения подписи — сервер может не проверять её вообще
  3. Атака alg:none — убери подпись, смени алгоритм
  4. Brute force секрета по словарю (hashcat -m 16500 или jwt_tool -C)
  5. Найди публичный ключ → попробуй algorithm confusion RS256→HS256
  6. Проверь kid на path traversal, SQLi, command injection
  7. Попробуй jwk/jku injection — встраивание своего ключа
  8. Меняй каждый claim в payload по отдельности, наблюдай за ответом сервера

Большинство задач решаются на шагах 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 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...

Читайте также

Format string уязвимость: от %p до shell

15 мин.

5

Format string уязвимость: от %p до shell

Разбор format string в CTF: поиск offset, утечка libc через %p, побайтовая запись через %hhn, GOT overwrite с pwntools — полная цепочка эксплуатации

15 СЕНТЯБРЬ, 2026

Command Injection в CTF: поиск и эксплуатация

11 мин.

7

Command Injection в CTF: поиск и эксплуатация

Пошаговый разбор OS command injection в CTF-задачах: от обнаружения уязвимости до reverse shell. Обход фильтров, blind injection, ANSI-C нотация, payload'ы.

15 СЕНТЯБРЬ, 2026

Стеганография для начинающих: чек-лист CTF

9 мин.

11

Стеганография для начинающих: чек-лист CTF

Пошаговый чек-лист разбора стего-тасков на CTF: от file и strings до LSB-анализа. Команды binwalk, steghide, zsteg, exiftool с реальными примерами.

14 СЕНТЯБРЬ, 2026