Безопасность JSON Web Token: распространённые уязвимости и их устранение
JWT являются стандартом де-факто для аутентификации API, но за их кажущейся простотой скрываются реальные проблемы безопасности. Неправильно настроенные JWT приводили к обходу аутентификации, повышению привилегий и утечкам данных. В этом руководстве рассматриваются наиболее критичные уязвимости и способы их предотвращения.
Краткий обзор структуры JWT
JWT состоит из трёх частей, закодированных в Base64URL и разделённых точками:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.signature
- Заголовок (Header): алгоритм и тип токена
- Полезная нагрузка (Payload): утверждения (данные пользователя, срок действия и т.д.)
- Подпись (Signature): криптографическая проверка
Изучите структуру JWT с помощью нашего JWT-кодировщика/декодировщика.
Для фундаментального понимания структуры JWT смотрите наше руководство JWT-токены: объяснение.
Критические уязвимости
1. Атака подмены алгоритма
Самая опасная уязвимость JWT. Если сервер принимает заголовок alg из токена без проверки, злоумышленник может:
Атака: изменить алгоритм с RS256 (асимметричный) на HS256 (симметричный) и подписать поддельный токен публичным ключом сервера:
// Attacker's forged token
header: { "alg": "HS256", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
// Signed with the server's PUBLIC key as the HMAC secret
Если сервер проверяет HS256-токены, используя публичный ключ в качестве секрета, поддельный токен проходит верификацию.
Исправление: всегда явно указывайте ожидаемый алгоритм:
// WRONG - accepts whatever algorithm the token specifies
jwt.verify(token, key);
// CORRECT - enforce specific algorithm
jwt.verify(token, key, { algorithms: ['RS256'] });
2. Атака с алгоритмом none
Некоторые библиотеки принимают "alg": "none" — токен без подписи:
// Forged token with no signature
header: { "alg": "none", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
signature: "" // empty
Исправление: никогда не допускайте алгоритм none в продакшене. Явно задавайте список разрешённых алгоритмов.
3. Слабые секреты подписи
JWT на основе HMAC (HS256/HS384/HS512) надёжны ровно настолько, насколько надёжен секрет:
// TERRIBLE - can be brute-forced in seconds
secret = "password123"
// WEAK - dictionary attack vulnerable
secret = "my-jwt-secret"
// STRONG - 256+ bits of randomness
secret = "a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0"
Исправление: используйте минимум 256 бит криптографической случайности для HMAC-секретов. Ещё лучше — применяйте асимметричные ключи (RS256, ES256), где ключ подписи не нужно разделять.
4. Отсутствие срока действия
Токены без срока действия никогда не истекают — украденный токен предоставляет постоянный доступ:
// WRONG - no expiration
{ "sub": "user123", "role": "admin" }
// CORRECT - short expiration
{
"sub": "user123",
"role": "admin",
"exp": 1705312800,
"iat": 1705309200,
"nbf": 1705309200
}
Лучшая практика: токены доступа истекают через 15-60 минут. Используйте refresh-токены (хранящиеся безопасно) для длительных сессий.
5. Конфиденциальные данные в полезной нагрузке
Полезная нагрузка JWT закодирована в Base64URL, но не зашифрована. Любой может её декодировать:
echo "eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ" | base64 -d
# {"sub":"123","role":"admin"}
Никогда не включайте: пароли, API-ключи, номера кредитных карт, персональные данные (номер паспорта, медицинские записи) или любые секреты в полезную нагрузку JWT.
6. Хранение токенов (XSS vs CSRF)
| Хранилище | Уязвимо к XSS | Уязвимо к CSRF |
|---|---|---|
| localStorage | Да | Нет |
| Cookie (без флагов) | Да | Да |
| HttpOnly Cookie | Нет | Да |
| HttpOnly + SameSite Cookie | Нет | Нет |
Рекомендация: храните JWT в HttpOnly, Secure, SameSite=Strict cookie. Это предотвращает доступ через JavaScript (защита от XSS) и межсайтовые запросы (защита от CSRF).
Set-Cookie: token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
Чеклист безопасности
- Явно указывайте алгоритмы — никогда не принимайте из заголовка токена
- Отклоняйте алгоритм
none— всегда требуйте подпись - Используйте надёжные секреты — 256+ бит для HMAC, 2048+ бит для RSA
- Устанавливайте короткий срок действия — 15-60 минут для токенов доступа
- Валидируйте все утверждения —
exp,iss,aud,nbf - Храните в HttpOnly cookie — не в localStorage
- Периодически ротируйте секреты — планируйте ротацию ключей
- Никогда не храните конфиденциальные данные в полезной нагрузке
- Используйте асимметричные ключи для распределённых систем (RS256 или ES256)
- Реализуйте отзыв токенов — чёрный список или короткий срок действия + refresh-токены
Асимметричная vs симметричная подпись
| Аспект | HS256 (Симметричная) | RS256 (Асимметричная) |
|---|---|---|
| Ключ | Общий секрет | Пара приватный/публичный ключ |
| Кто может подписывать | Любой, у кого есть секрет | Только владелец приватного ключа |
| Кто может проверять | Любой, у кого есть секрет | Любой, у кого есть публичный ключ |
| Распространение ключей | Секрет нужно безопасно передавать | Передаётся только публичный ключ |
| Производительность | Быстрее | Медленнее |
| Лучше подходит для | Единого сервиса | Микросервисов, распределённых систем |
Рекомендация: используйте ES256 (ECDSA) для новых приложений — он обеспечивает асимметричную безопасность с производительностью, близкой к HMAC.
Стратегии отзыва токенов
JWT являются stateless по своей сути — встроенного способа их отозвать нет. Стратегии:
- Короткий срок действия: если токены истекают через 15 минут, окно использования украденного токена ограничено
- Ротация refresh-токенов: выпуск нового refresh-токена при каждом использовании; если refresh-токен использован дважды — отзыв всех токенов
- Чёрный список: хранение ID отозванных токенов (утверждения jti) и проверка при каждом запросе
- Версионирование токенов: включение номера версии в утверждения; инкремент версии пользователя при выходе из системы
FAQ
Что лучше использовать для аутентификации: JWT или сессионные cookie?
Сессионные cookie проще и безопаснее для традиционных веб-приложений — сервер контролирует жизненный цикл сессии, и отзыв происходит мгновенно. JWT лучше подходят для stateless API, микросервисов и мобильных приложений, где серверное хранение сессий непрактично. Если вы выбираете JWT, реализуйте меры безопасности из этого руководства.
Какой идеальный срок действия JWT?
Для токенов доступа: 15-60 минут. Меньший срок безопаснее, но требует более частого обновления. Для refresh-токенов: 1-30 дней, хранение в HttpOnly cookie. Для одноразовых токенов (подтверждение email, сброс пароля): 1-24 часа.
Связанные ресурсы
- JWT-кодировщик/декодировщик — Безопасная инспекция и декодирование JWT
- JWT-токены: объяснение — Понимание структуры и потока JWT
- Энтропия паролей — Надёжность секретов подписи JWT