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. Алгоритм фіксується конфігурацією ресурс-сервера (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-перевірки на рівень ресурсу.