Главная / Блог / JWT атаки CTF: алгоритм none, подмена ключа и брутфорс секрета с jwt_tool и CyberChef

12 мин.00

JWT атаки CTF: алгоритм none, подмена ключа и брутфорс секрета с jwt_tool и CyberChef

JWT атаки CTF: алгоритм none, подмена ключа и брутфорс секрета с jwt_tool и CyberChef

JWT атаки CTF: алгоритм none, подмена ключа и брутфорс секрета с jwt_tool и CyberChef

На CTF категории Web задача выглядела стандартно: форма логина, кнопка «Guest Login», в cookie — длинная строка из трёх частей через точку. Три минуты в CyberChef: декодировал header, увидел "alg":"HS256", поменял на "alg":"none", подставил "role":"admin" в payload, убрал подпись — флаг получен. Через два задания встретился тот же JWT, но с RS256 и валидацией подписи. Половина участников зависла на час, потому что не знала про key confusion.

Разберём три вектора атак на JSON Web Token, которые покрывают подавляющее большинство CTF-заданий этой категории: algorithm none, подмену ключа RS256→HS256 и брутфорс слабого HMAC-секрета — с конкретными командами jwt_tool и hashcat.

Структура JSON Web Token: минимум для эксплуатации

JWT — три части, разделённые точками: header, payload, signature. Каждая закодирована в base64url — обычный base64, но без паддинга = и с заменой +/ на -_. Новички часто спотыкаются именно тут: вставляют JWT в стандартный base64-декодер, получают мусор и думают, что токен зашифрован. Нет — он просто закодирован, причём открытым текстом. Подробнее — в нашем статье о атаки на аутентификацию.

Для атакующего критичны два поля. Первое — alg в header. Оно сообщает серверу, каким алгоритмом проверять подпись. Если сервер слепо доверяет этому полю — вот вам входная точка для атак algorithm none и key confusion. Второе — claims в payload: role, username, is_admin, sub и прочие поля, определяющие привилегии. Payload не зашифрован, только закодирован — содержимое видно любому, кто сделает base64url decode.

Пример JWT из CTF-задания. Header после декодирования: {"typ":"JWT","alg":"HS256"}. Payload: {"username":"guest","role":"user"}. Задача — превратить guest в admin и user в admin, но для этого нужно обойти подпись. Подпись — HMAC-SHA256 от base64url(header) + "." + base64url(payload) с секретным ключом. Если ключ неизвестен, подпись не вычислить. Но есть три способа это обойти.

Место JWT-атак в цепочке. По классификации MITRE ATT&CK подделка токена — Forge Web Credentials: Web Cookies (T1606.001, Credential Access). Дальнейшее использование поддельного токена — Application Access Token (T1550.001, Lateral Movement). Брутфорс секрета — Password Cracking (T1110.002, Credential Access). В реальном пентесте JWT-уязвимость на публичном API — Exploit Public-Facing Application (T1190, Initial Access), после чего поддельный токен с role: admin открывает путь к данным или внутренним сервисам. В CTF цепочка короче: decode → forge → submit → flag.

[Применимо: CTF Web-категория, внешний пентест веб-приложений, bug bounty API-скоупы. На внутреннем пентесте — если обнаружен микросервис с JWT-аутентификацией.]

Подготовка окружения

Перед разбором атак — минимальный набор, чтобы повторить каждый шаг:

  • ОС: Kali Linux 2023+, Ubuntu 22.04+, Parrot OS или WSL2 на Windows 10/11
  • RAM: 2 ГБ минимум для jwt_tool; 4+ ГБ если планируете hashcat с GPU
  • VRAM (опционально): 2+ ГБ для GPU-ускорения hashcat; CPU-режим работает без видеокарты, но медленнее в 10–50 раз
  • Python: 3.6+ (jwt_tool написан на Python)
  • jwt_tool: git clone https://github.com/ticarpi/jwt_tool && cd jwt_tool && pip3 install -r requirements.txt — инструмент активно поддерживается, используется в материалах PortSwigger Web Security Academy, OWASP WSTG и HackTricks
  • hashcat: версия 6.0+ (режим -m 16500 для JWT)
  • CyberChef: браузерный инструмент на gchq.github.io/CyberChef, работает offline если скачать HTML-файл
  • Словарь: rockyou.txt для брутфорса (в Kali по умолчанию: /usr/share/wordlists/rockyou.txt)
  • Сеть: все инструменты работают offline, кроме онлайн-версии CyberChef

Algorithm none: обход аутентификации JWT удалением подписи

Атака algorithm none — простейший и самый частый вектор в CTF-заданиях начального уровня. Суть: меняем alg в header с HS256 (или любого другого) на none, убираем подпись, модифицируем payload. Если сервер не проверяет, какой алгоритм указан в токене, он принимает неподписанный JWT как валидный.

По RFC 7519 значение none в поле alg — легитимная часть стандарта, означающая что токен не подписан. Проблема возникает, когда серверная библиотека принимает none без явного разрешения разработчика. По данным OWASP WSTG, это одна из самых распространённых ошибок реализации JWT. Классика на стороне сервера: разработчик в Node.js использует jwt.decode() (которая не проверяет подпись) вместо jwt.verify() — и токен с любым содержимым проходит. По данным PortSwigger Web Security Academy, эта путаница между decode и verify встречается регулярно.

CyberChef для декодирования JWT и ручной сборки токена

Декодирование JWT через CyberChef — первый шаг любого анализа. Рецепт: операция From Base64 с вариантом URL-safe. Пошагово:

  1. Скопируйте header-часть токена (до первой точки), вставьте в Input CyberChef
  2. Добавьте операцию From Base64 — в настройках выберите алфавит A-Za-z0-9-_ (URL-safe)
  3. В Output увидите JSON: {"typ":"JWT","alg":"HS256"}
  4. Повторите для payload (часть между первой и второй точкой)

Для сборки none-токена — обратный процесс. Формируете header {"typ":"JWT","alg":"none"}, кодируете через To Base64 с URL-safe алфавитом, убираете паддинг =. Формируете payload {"username":"admin","role":"admin"}, кодируете аналогично. Склеиваете через точку: <header>.<payload>. — завершающая точка обязательна: подпись пустая, но разделитель должен присутствовать.

Использование jwt_tool для подмены алгоритма

jwt_tool автоматизирует процесс и генерирует несколько вариантов none-токенов одной командой: python3 jwt_tool.py <JWT> -X a. Флаг -X a запускает атаку algorithm none. Инструмент создаёт варианты с разным регистром: none, None, NONE, nOnE. Это не формальность — некоторые библиотеки блокируют none регистрозависимой проверкой, и вариант NoNe проходит фильтр. Каждый сгенерированный токен нужно подставить в cookie или заголовок Authorization: Bearer <token> и отправить запрос.

Если нужно одновременно модифицировать claims, добавляете флаги инъекции: python3 jwt_tool.py <JWT> -I -pc username -pv admin -X a. Флаг -I включает режим инъекции, -pc задаёт имя claim, -pv — новое значение.

Для проверки всех атак разом jwt_tool поддерживает режим полного сканирования: python3 jwt_tool.py -M at -t "https://target.com/api/profile" -rh "Authorization: Bearer <JWT>". Режим -M at (All Tests) прогоняет none, key confusion, инъекции в kid и другие вектора — на CTF, когда время горит, это экономит нервы.

Ограничения: когда none не работает

Атака не сработает в следующих случаях:

  • Серверная библиотека явно запрещает alg: none — все актуальные версии PyJWT, jsonwebtoken (Node.js), java-jwt делают это по умолчанию
  • Сервер использует whitelist допустимых алгоритмов вместо чтения alg из токена
  • В CTF-задании сложность выше начальной: автор намеренно закрыл этот вектор и ожидает key confusion или брутфорс

Если none не прошёл ни в одном варианте написания — не тратьте время, переходите к следующей атаке.

Подмена ключа JWT: key confusion RS256 на HS256

Key confusion — вторая по частоте JWT-атака в CTF и одна из самых опасных в реальных приложениях. Идея: приложение использует RS256 (асимметричный — подпись приватным ключом, проверка публичным), но не валидирует, какой алгоритм указан в header. Атакующий меняет alg на HS256 (симметричный — один ключ для подписи и проверки) и подписывает токен публичным ключом сервера как HMAC-секретом. Серверная библиотека берёт тот же публичный ключ для верификации HMAC-подписи — и подпись совпадает. Красиво, правда?

Механика key confusion и реальные CVE

Уязвимость возникает из-за архитектурного решения: некоторые библиотеки используют единую функцию verify(token, key) для симметричных и асимметричных алгоритмов. Если тип алгоритма не проверяется, публичный ключ RSA превращается в HMAC-секрет.

Это не теория — это подтверждённые CVE. CVE-2016-5431 (CVSS 7.5 HIGH, CWE-327) — библиотека PHP JOSE от Gree Inc. до версии 2.2.1 позволяла обход подписи через подмену алгоритма. CVE-2016-10555 (CWE-20, CWE-310) — npm-пакет jwt-simple до версии 0.3.1 не проверял алгоритм в jwt.decode(). По данным EPSS, вероятность эксплуатации CVE-2016-10555 — 0.049 (Top 10%), что подтверждает актуальность вектора даже спустя годы.

Связанная уязвимость — CVE-2018-0114 (CVSS 7.5 HIGH, CWE-347): библиотека Cisco node-jose до версии 0.11.0 позволяла встроить произвольный публичный JWK прямо в заголовок токена, и сервер доверял этому ключу для верификации. EPSS — 0.4265 (Top 5%), на Exploit-DB доступен публичный эксплоит (EDB-44324).

Эксплуатация подмены ключа через jwt_tool

Предусловия (обязательные): - Приложение использует RS256 или другой асимметричный алгоритм - Публичный ключ доступен атакующему - Серверная библиотека не валидирует соответствие алгоритма ожидаемому

Где искать публичный ключ: в CTF он обычно валяется в открытом доступе — файл public.pem в корне сервера, endpoint /.well-known/jwks.json, исходники задания. В реальном пентесте — вытягиваете из TLS-сертификата: openssl s_client -connect target:443 | openssl x509 -pubkey -noout > public.pem.

Команда jwt_tool для key confusion: python3 jwt_tool.py <JWT> -S hs256 -k public.pem. Для одновременной модификации claims: python3 jwt_tool.py <JWT> -I -pc role -pv admin -S hs256 -k public.pem. Полученный токен подставляете в запрос.

Когда key confusion не работает:

  • Сервер использует whitelist алгоритмов и отклоняет HS256, если ожидает RS256
  • Публичный ключ недоступен или хранится в формате, несовместимом с ожиданиями библиотеки: PEM vs DER vs JWK — формат должен совпадать с точностью до переносов строк и пробелов (да, на этом реально можно зависнуть)
  • Библиотека обновлена: jsonwebtoken для Node.js начиная с версии 9.x требует явного указания допустимых алгоритмов в опциях verify
  • Сервер использует разные ключи для разных алгоритмов и проверяет тип ключа до верификации

Брутфорс JWT секрета с hashcat и jwt_tool

Если none не прошёл и key confusion неприменим — остаётся офлайн-брутфорс HMAC-секрета. Если приложение использует HS256 со слабым секретом (словарное слово, короткая строка, стандартный пароль), секрет подбирается за секунды.

Взлом JWT токена через hashcat и jwt_tool

hashcat поддерживает JWT через режим 16500. Сохраните полный токен (все три части) в файл jwt.txt и запускайте:

hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 jwt.txt wordlist.txt -r /usr/share/hashcat/rules/best64.rule

Вторая команда добавляет правила мутации best64: капитализация, добавление цифр, замена символов — расширяет покрытие словаря без увеличения его размера. На GPU среднего уровня скорость — порядка нескольких миллионов хешей в секунду для HS256.

jwt_tool тоже умеет брутфорсить, но медленнее (чистый Python без GPU): python3 jwt_tool.py <JWT> -C -d /usr/share/wordlists/rockyou.txt. Для быстрой проверки конкретной строки: python3 jwt_tool.py <JWT> -C -p "secret". Стратегия простая: jwt_tool — для проверки подозрительных секретов вручную, hashcat — для полноценного перебора по словарю.

Какой словарь выбрать

В CTF-заданиях секреты обычно предсказуемы: secret, password, admin123, supersecret, название CTF-платформы. rockyou.txt покрывает большинство случаев. Если не помогло — попробуйте JWT-специфичные списки из репозитория SecLists (раздел Passwords/Default-Credentials).

По данным PortSwigger Web Security Academy, в open-source и шаблонных приложениях часто остаются дефолтные HMAC-ключи, которые не меняются после развёртывания — это касается и реальных проектов, не только CTF.

Ограничения брутфорса: если секрет — криптографически случайная строка длиной 256+ бит, перебор невозможен. В CTF такое встречается редко, но в production — норма для правильно настроенных приложений.

Какую JWT атаку пробовать первой в CTF заданиях

Decision tree, который экономит время:

  1. Декодируйте токен — CyberChef или python3 jwt_tool.py <JWT>. Посмотрите alg и claims. Запишите, что нужно изменить.
  2. Проверьте валидацию подписи — поменяйте один символ в payload и отправьте запрос. Если сервер принял — подпись вообще не проверяется, можно менять claims напрямую (и да, такое бывает).
  3. Попробуйте nonepython3 jwt_tool.py <JWT> -X a. Занимает 30 секунд, покрывает задачи начального уровня.
  4. Определите алгоритм — если alg: RS256 или другой асимметричный, ищите публичный ключ. Нашли — пробуйте key confusion. Не нашли — к брутфорсу.
  5. Брутфорсите секрет — если alg: HS256, запускайте hashcat с rockyou.txt. Параллельно проверяйте очевидные строки через jwt_tool.
  6. Проверьте kid — если в header есть поле kid, оно может быть уязвимо к path traversal (../../dev/null) или SQL-инъекции: python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p "".

Сравнение инструментов для JWT-атак в CTF:

Инструмент Сценарий Преимущества Ограничения
jwt_tool Все атаки + kid injection Единый инструмент для полного цикла, автогенерация none-вариантов Python без GPU, медленный брутфорс
hashcat Брутфорс HMAC-секрета GPU-ускорение, правила мутации, высокая скорость перебора Только брутфорс, не умеет none/key confusion
CyberChef Быстрый осмотр и ручная сборка Браузерный, визуальный, без установки Только декодирование, ручная работа
Burp JWT Editor Все атаки через GUI Интеграция с Burp, визуальное редактирование Требует Burp Suite Pro для полной функциональности

От CTF к реальному пентесту: CVE в JWT-библиотеках

JSON Web Token уязвимости в CTF — упрощённая модель реальных проблем. Те же атаки применимы к production-приложениям, и конкретные CVE это подтверждают.

ECDSA «Psychic Signatures» (CVE-2022-21449, CVSS 7.5 HIGH). Oracle Java SE версий 17.0.2 и 18, а также Oracle GraalVM Enterprise Edition 21.3.1 и 22.0.0.2 некорректно валидировали ECDSA-подписи. Вектор CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N — атака не требует ни привилегий, ни взаимодействия пользователя. По данным OWASP WSTG, если JWT подписан алгоритмом ES256 и сервер работает на уязвимой версии Java, подпись заменяется на значение MAYCAQACAQA — и токен проходит проверку. EPSS — 0.4824 (Top 5%), вероятность эксплуатации очень высокая. На GitHub доступен PoC (notkmhn/CVE-2022-21449-TLS-PoC, 121 звезда). Исправления выпущены Debian (DSA-5128-1), Red Hat (RHSA-2022:1436) и Ubuntu (USN-5388-2). Уязвимые продукты по NVD: oracle:jdk, oracle:graalvm, debian:debian_linux.

pac4j-jwt auth bypass (CVE-2026-29000, CVSS 9.3 CRITICAL, CWE-347). В библиотеке pac4j-jwt (пакет org.pac4j:pac4j-jwt в Maven, уязвимая версия introduced 6.0.4.1, исправлена в 6.3.3, а также в ветках 4.5.9 и 5.7.9) атакующий, зная публичный RSA-ключ сервера, создаёт JWE-обёртку вокруг PlainJWT с alg=none и произвольными claims. Сервер расшифровывает JWE, пытается распарсить payload как подписанный JWT — если toSignedJWT() возвращает null, верификация подписи пропускается. По классификации CISA-ADP: Exploitation — poc, Automatable — yes, Technical Impact — total. На GitHub доступны PoC-репозитории (kernelzeroday/CVE-2026-29000, 8 звёзд). По OWASP это попадает под A02:2021 Cryptographic Failures и A05:2021 Security Misconfiguration.

Эти CVE встречаются в CTF-заданиях повышенной сложности и bug bounty программах. Понимание механики — то, что отличает решение задачи «по writeup» от самостоятельного обнаружения уязвимости на реальном скоупе.

Большинство CTF-участников учат JWT-атаки по writeup'ам: копируют команду jwt_tool, получают флаг, переходят к следующей задаче. На практике это формирует опасную иллюзию — человек думает, что умеет ломать JWT, а на самом деле умеет копировать строки из чужого разбора. Когда на реальном пентесте или CTF уровня medium+ алгоритм none закрыт, публичный ключ хранится в нестандартном формате, а секрет не из rockyou — тот, кто учился копированием, застревает.

Ключевое — понимать, что именно проверяет (или не проверяет) серверная библиотека при получении токена. Алгоритм из header — input от клиента, а значит, потенциально враждебный. Claims в payload не зашифрованы, только подписаны. Подпись зависит от секрета или ключа, и если они слабые или доступны — токен подделывается. Механика одинаковая что в PyJWT, что в jsonwebtoken, что в java-jwt — разница в дефолтных настройках и строгости валидации.

Три атаки из этой статьи покрывают основу, но за ними стоит десяток edge-cases: kid injection через path traversal к /dev/null, jku spoofing через open redirect, embedded JWK через CVE-2018-0114, ECDSA psychic signatures. Каждый — отдельный вектор, который не освоишь без ручного разбора. Я встречал CTF-задания, где для решения нужно было одновременно менять алгоритм, инжектить kid и подставлять кастомный JWK — ни один writeup этого не покрывал. Если хочешь разобрать не один кейс, а тридцать — каждый с нарастающей сложностью — на WAPT эту цепочку проходят от базовой JWT-подделки до полного kill chain через веб.

🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».

Поделиться

0 комментариев

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

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