GH GambleHub

JWT: структура и уязвимости

1) Что такое JWT и где он используется

JWT — компактный самодостаточный контейнер утверждений (claims) в формате `Base64Url(header).Base64Url(payload).Base64Url(signature)`.

Используется для:
  • JWS (подписанные токены — подлинность/целостность),
  • JWE (зашифрованные токены — конфиденциальность),
  • OIDC/OAuth2 как access/ID токены, а также service-to-service аутентификации.

Плюсы: автономность, кэшируемость, малые накладные расходы. Минусы: риск неправильной валидации, сложные кейсы отзыва.

2) Структура JWT

2.1 Заголовок (header, JSON)

Минимально: алгоритм и идентификатор ключа.

json
{ "alg": "ES256", "kid": "jwt-2025-10", "typ": "JWT" }

`alg`: алгоритм подписи/шифрования (RS256/ES256/PS256/HS256 и т. д.).
`kid`: указатель на ключ (для JWKS-ротации).
Опциональные источники ключей: `jku`, `x5u` (см. уязвимости §6.3).

2.2 Полезная нагрузка (payload, JSON)

Стандартизованные клеймы:
  • `iss` (issuer), `aud` (audience), `sub` (subject)
  • `exp` (время истечения), `nbf` (не раньше), `iat` (выдан)
  • `jti` (идентификатор токена, пригоден для отзывов)
  • domain-клеймы: `scope/roles`, `tenant`, `kyc_level` и т. п.

2.3 Подпись (signature)

JWS = `sign(base64url(header) + "." + base64url(payload), private_key)`

Проверка: строго соответствующим публичным ключом и ровно тем алгоритмом, который сервер ожидает.

3) Базовые инварианты проверки

1. Алгоритм фиксируется конфигурацией ресурc-сервера (allow-list), а не доверяется содержимому `header.alg`.
2. Проверять `iss` и `aud` на точное совпадение, `exp/nbf` — с учетом небольшого `clock_skew` (±30–60s).
3. Отказывать токены без `kid` только если единственный ключ и нет ротации; иначе — требовать `kid`.
4. Не доверять никаким клеймам без авторизации на объектном уровне (BOLA-first).
5. Парсинг — после криптопроверки; базовые проверки размера до декодирования.

4) JWS vs JWE

JWS: подписан, но читаем. Не помещайте в payload PII/секреты.
JWE: шифрует payload; сложнее интеграция, ключевая модель критична.
В большинстве API достаточно JWS + запрет на чувствительные данные в payload.

5) Жизненный цикл токена

Access: короткий срок (5–30 минут).
Refresh: длительнее (7–30 дней), rotate-on-use (одноразовый), хранить «черный список» `jti/sid`.
Revocation: списки `jti` с TTL, интроспекция для opaque-токенов, сокращение `exp` при инцидентах.
Ротация ключей: JWKS с перекрытием (старый+новый), см. статью «Ротация ключей».

6) Частые уязвимости и как их закрывать

6.1 `alg = none` / подмена алгоритма

Суть: сервер доверяет полю `alg` и принимает неподписанный токен.
Защита: жесткий allow-list алгоритмов на сервере; отклонять `none` и неожиданные значения.

6.2 RS256→HS256 swap (симметризация)

Суть: злоумышленник подменяет `alg` на HS256 и использует публичный ключ как HMAC-секрет.
Защита: привязать ключ к алгоритму конфигурацией; не смешивать симметричные/асимметричные провайдеры в одном `kid`.

6.3 Инъекция ключей (`kid/jku/x5u`)

Сценарии:
  • `jku` указывает на контролируемый злоумышленником JWKS (подсунет свой ключ).
  • `x5u`/`x5c` злоупотребление внешними сертификатами.
  • `kid` с инъекцией пути/SQL (`"../../privkey.pem"` или `"' OR 1=1 --"`).
Защита:
  • Игнорировать удаленные `jku/x5u` либо фильтровать по строгому allow-list доменов.
  • `kid` использовать только как ключ в локальном каталоге (таблица/кеш), без файловых путей/SQL конкатенаций.
  • JWKS грузить с доверенных URL, короткий TTL, подпись/пинning канала.

6.4 Слабые секреты HS256 (брутфорс)

Суть: HMAC-секрет короткий/утек → подмена подписи.
Защита: использовать асимметрию (RS/ES/PS) или длину секрета ≥ 256 бит, секреты — только в KMS.

6.5 Отсутствующие/невалидные клеймы

Нет `aud`/`iss`/`exp` → токен кросс-сервисно пригоден или бесконечен.
Слишком длинный `exp` → риск компрометации.
Защита: требовать полный набор клеймов, `exp` короткий, `nbf`/`iat` валидировать с `clock_skew`.

6.6 Replay и кража токена

Суть: перехват/повтор токена (утечка в логах, XSS, MitM без TLS).

Защита:
  • TLS везде, `Secure`+`HttpOnly` cookie, SameSite=Lax/Strict.
  • DPoP/PoP (привязка токена к клиентскому ключу) и/или mTLS для партнеров.
  • Короткие `exp`, refresh-ротация, device-binding.

6.7 Утечки через XSS/хранилище

Суть: хранение JWT в `localStorage`/`sessionStorage` → доступен JS.
Защита: хранить access-токены в HttpOnly-cookie (если возможна cookie-модель) + строгая CSP/Trusted Types.
Для SPA без cookie — изолировать токен в памяти, минимально жить, защищать от XSS.

6.8 CSRF

Суть: при cookie-сессиях запросы от стороннего сайта.
Защита: SameSite, anti-CSRF токены (double submit), `Origin/Referer` проверка, Fetch-Metadata фильтры.

6.9 Oversize/злоупотребление размером

Суть: огромные payload/заголовки, DoS на парсинг.
Защита: лимиты размеров заголовков/тела, early-reject 431/413, фикс-набор клеймов.

6.10 Подмена `typ`/`cty`

Суть: путаница типов (`typ: "JWT"`), вложенные JOSE-объекты.
Защита: игнорировать `typ/cty` для безопасности, полагаться на фиксированные алгоритмы и схему клеймов.

7) Хранение и передача токенов

7.1 Серверные API (machine-to-machine)

mTLS/HMAC, желательно асимметрия для JWT, каналы — через mesh.
Хранилище ключей — KMS/HSM, ротация по расписанию, JWKS с перекрытием.

7.2 Браузерные клиенты

HttpOnly Secure Cookie для access/refresh; короткие TTL; обновление — через «silent refresh» с `SameSite=None` только под HTTPS.
Строгая CSP, Trusted Types, защитить от XSS; для SPA — по возможности не хранить токен на диске.

8) JWKS, ротация и отзыв

JWKS публикуется авторизатором; потребители кэшируют на 5–15 минут.
План ротации: добавить новый `kid` → начать им подписывать → через N дней удалить старый из JWKS → purge.
Отзыв: списки `jti/sid` c TTL; при инциденте временно сократить `exp` и форс-logout (инвалидировать refresh).

9) Multi-tenant и минимизация данных

Включать `tenant`/`org` в токен только если нужно по архитектуре; иначе подтягивать атрибуты из PDP по `sub`.
Никаких PII; минимальный набор клеймов → меньше риск утечек и корелляции.

10) Практический чек-лист валидации (сервер ресурса)

  • Парсим только после проверки подписи и базовых лимитов размера.
  • `alg` из конфигурации; отклонять неожиданные.
  • Проверить `iss` ∧ `aud` ∧ `exp` ∧ `nbf` ∧ `iat` (с `clock_skew`).
  • Проверить `kid` по локальному каталогу/JWKS (короткий TTL).
  • Отфильтровать/нормализовать внешние указатели (`jku/x5u` — только allow-list).
  • Ограничить длину/состав клеймов (схема).
  • Применить объектную авторизацию на ресурс (BOLA-first).
  • Логировать `kid`, `sub`, `aud`, `iss`, `jti`, `exp`, `tenant`, `trace_id` (без PII).
  • Метрики ошибок подписи, просрочек, аудита ротации.

11) Примеры безопасных политик (псевдо)

11.1 Конфигурация ожиданий алгоритма

yaml jwt:
expected_issuer: "https://auth. example. com"
expected_audience: ["wallet-service"]
allowed_algs: ["ES256"] # fix the jwks_url: "https ://auth. example. com/.well-known/jwks. json"
jwks_cache_ttl: 600s clock_skew: 60s required_claims: ["iss","aud","sub","exp","iat"]

11.2 Отказ от удаленных `jku/x5u`

yaml reject_untrusted_key_sources: true allowed_jku_hosts: ["auth. example. com"] # if absolutely necessary

11.3 Пример списка отзывов (Redis)

pseudo if redis. exists("revoke:jti:" + jti) then deny()
if now() > exp then deny()

12) Наблюдаемость и форензика

Метрики: `jwt_verify_fail_total{reason}`, `jwt_expired_total`, `jwks_refresh_total`, доля `kid`.
Логи (структурированные): `iss/aud/sub/kid/jti/exp/tenant/trace_id`, причина отказа.
Дашборды: «истекающие скоро», всплеск ошибок валидации, распределение по регионам/клиентам.
Алерты: рост `verify_fail` (подпись/алгоритм), ошибок JWKS, доли просроченных токенов.

13) Антипаттерны

Доверять `alg` из токена; поддерживать `none`.
Долгоживущие access-токены и отсутствие refresh-ротации.
HS256 с коротким секретом/секрет в ENV без KMS.
Принимать `jku/x5u` с любого домена; динамически загружать JWKS без allow-list.
Класть PII/секреты в payload JWS.
Хранить токены в `localStorage` при наличии XSS-рисков.
Заглушать ошибки валидации (возвращать 200 с «soft error»).
Отсутствие BOLA-проверок и полагание только на `scope/role`.

14) Специфика iGaming/финансов

Клеймы: `kyc_level`, `risk_tier`, `tenant`, строгие `aud`.
Короткие TTL для write-операций (депозиты/выводы), PoP/DPoP или mTLS для критичных маршрутов.
Регуляторный аудит: неизменяемые журналы входов/отказов, хранение логов в границах региона.
У партнеров/PSP — отдельные ключи/`aud`, пер-тенант ключи и отдельные JWKS.

15) Чек-лист prod-готовности

  • Жесткий allow-list алгоритмов; `none` запрещен.
  • `iss/aud/exp/nbf/iat/jti` проверяются; короткие `exp`.
  • JWKS с перекрытием, короткий TTL кэша; мониторинг долей `kid`.
  • Refresh — rotate-on-use; списки `jti/sid` с TTL; плейбук отзывов.
  • PoP/DPoP или mTLS на критичных маршрутах.
  • HttpOnly-cookies для браузера, CSP/Trusted Types против XSS; CSRF-защита.
  • Без PII в payload; минимальный набор клеймов.
  • Метрики/логи по валидации и отказам; алерты JWKS/verify_fail.
  • Тесты негативных сценариев: RS→HS swap, `kid`-инъекции, `jku` spoofing, oversize, clock-skew.

16) TL;DR

Фиксируйте алгоритм и ключи на стороне сервера, не доверяйте `alg` из токена, требуйте `iss/aud/exp/nbf/iat/jti`. Ротируйте ключи через JWKS с перекрытием, держите токены короткоживущими, а refresh — одноразовыми. Храните токены безопасно (HttpOnly-cookie для веба), минимизируйте клеймы, не кладите PII. Закрывайте `jku/x5u/kid`-векторы, избегайте HS256 с слабыми секретами, добавляйте PoP/DPoP или mTLS на критичных путях и всегда делайте BOLA-проверки на уровень ресурса.

Contact

Свяжитесь с нами

Обращайтесь по любым вопросам или за поддержкой.Мы всегда готовы помочь!

Telegram
@Gamble_GC
Начать интеграцию

Email — обязателен. Telegram или WhatsApp — по желанию.

Ваше имя необязательно
Email необязательно
Тема необязательно
Сообщение необязательно
Telegram необязательно
@
Если укажете Telegram — мы ответим и там, в дополнение к Email.
WhatsApp необязательно
Формат: +код страны и номер (например, +380XXXXXXXXX).

Нажимая кнопку, вы соглашаетесь на обработку данных.