Подписанный токен, который сервер выдаёт вам после входа, — это и есть JWT. Разберём, как он работает, и ключевые моменты безопасного использования (без шагов атаки).
Как это работает (три части)
JWT — это header.payload.signature, соединённые через .. Заголовок и полезная нагрузка просто закодированы через base64url (не зашифрованы); только подпись создаётся с помощью секретного (или общего) ключа.
1. header
метаданные вроде алгоритма подписи. читает кто угодно
2. payload
claims вроде id пользователя и срока действия. читает кто угодно = никаких секретов
3. signature
создаётся с помощью ключа. гарантирует отсутствие подделки
Ключевой момент — содержимое «просто читаемо». Поэтому полезная нагрузка — не место для паролей или персональных данных. Ценность JWT в том, что, проверив подпись, сервер может подтвердить «я выдал этот токен, и он не был изменён».
Меры для безопасного использования
Всегда проверяйте подпись и фиксируйте alg (самое важное)
Проверяйте подпись на сервере каждый раз. Зафиксируйте принимаемый алгоритм подписи (alg) на ожидаемом значении и отклоняйте alg:none (без подписи). Не доверяйте вслепую alg, записанному в заголовке токена.
RFC с лучшими практиками JWT (RFC 8725) требует, чтобы библиотеки позволяли вызывающему коду задать набор поддерживаемых алгоритмов и не использовали никаких других, а также чтобы все криптографические операции в JWT проверялись, а весь JWT отклонялся, если хоть одна из них не прошла.
Блокируйте смену алгоритма, а не только alg:none
Злоупотребление alg — это не только удаление подписи. RFC 8725 описывает, как замена RS256 на HS256 заставляет некоторые библиотеки проверять подпись через HMAC-SHA256, используя открытый ключ RSA как общий секрет (путаница алгоритмов). Открытый ключ по определению публичен — поэтому если это срабатывает, атакующий может сам создать действительную подпись.
Защита та же: не доверяйте alg из заголовка; фиксируйте алгоритм, который ожидает сервер. Сначала решите, какой алгоритм, и только потом проверяйте, а не выбирайте процедуру проверки по переданному вам ключевому материалу.
Проверяйте, для кого токен и до какого времени
Действительная подпись говорит лишь о том, что токен подлинный. Предназначен ли он вам и действует ли ещё — отдельная проверка.
RFC 7519 устанавливает, что если claim aud присутствует, а обрабатывающая сторона не находит в нём своего значения, JWT должен быть отклонён, а exp задаёт момент, начиная с которого JWT нельзя принимать к обработке (nbf работает так же с другого конца). RFC 8725 добавляет, что приложение должно проверять, что использованные ключи принадлежат издателю. Если ваш сервис принимает токен, законно выданный для другого сервиса, — вот как выглядит пропуск этих проверок.
Не кладите секреты в полезную нагрузку
Считайте, что декодировать содержимое может кто угодно. Держите секреты — пароли, персональные данные, ключи API — вне неё. Включайте только claims, которые не катастрофичны при просмотре, вроде идентификаторов и разрешений.
Держите короткий срок действия и имейте способ отзыва
Делайте токены доступа короткоживущими. Слишком долгий срок действия растягивает окно ущерба при краже токена. Если нужен отзыв, сочетайте короткоживущий токен доступа с управляемым на сервере refresh/сеансом.
Используйте надёжный ключ и храните его там, где его не украдут
Сделайте ключ подписи надёжным, не переиспользуйте его и храните в безопасном месте на сервере. Защищайте и сам токен — берегите его от кражи через XSS, носите в куке с безопасными атрибутами (→ остерегайтесь кражи через XSS).
Мнение этого сайта: JWT — не механизм сеансов на все случаи
JWT часто используют, чтобы «держать состояние входа», но у него есть слабость: отзыв затруднён. Если подпись действительна и токен не истёк, сервер по сути ему доверяет, поэтому сделать так, чтобы выход или аннулирование вступали в силу немедленно, неудобно.
Наша позиция: не используйте JWT как долгоживущую замену сеанса. Сочетайте короткоживущий токен доступа с серверным, отзываемым refresh/сеансом — и вы получите и плюс JWT (дешёвую проверку), и лёгкий отзыв.
Осторожно: «оно декодировалось» — не «оно действительно»
Поскольку содержимое читаемо, кто угодно может вставить JWT в декодер JWT и осмотреть заголовок и полезную нагрузку. Но декодирование (чтение содержимого) и проверка (подтверждение подлинности) — совершенно разные вещи. Возможность декодировать ничего не гарантирует о действительности. Что решает, подлинный ли токен, — это всегда проверка подписи на сервере. Читать содержимое для отладки удобно — просто не путайте «я смог прочитать» с «оно действительно».
JWT и серверные сеансы (cookie)
JWT — лишь один из способов хранить состояние входа, и в одних случаях он подходит лучше серверного сеанса, а в других — хуже.
| Пункт | Серверный сеанс (cookie) | JWT |
|---|---|---|
| Где хранится состояние | На сервере (БД или память); в cookie только ID | Внутри токена; сервер лишь проверяет подпись |
| Выход / блокировка | Удаление на сервере действует сразу | Принимается до истечения срока, если отдельно не построить отзыв |
| Видимость содержимого | Только ID, читать нечего | Любой может декодировать и прочитать |
| Где уместен | Обычное веб-приложение на одном домене | Несколько сервисов или API проверяют один и тот же токен |
Для веб-приложения, живущего на одном домене, серверного сеанса часто достаточно. Если же вы выбираете JWT, выдача нового токена при входе — тот же принцип, что и защита от фиксации сессии.
Реальный случай: библиотека, не проверявшая алгоритм
Смена алгоритма — не только теория. Широко используемая JWT-библиотека для Node.js jsonwebtoken в версиях 4.2.1 и ниже не проверяла должным образом алгоритм, указанный в заголовке токена, что позволяло обойти проверку (CVE-2015-9235, исправлено в 4.2.2). Токен, который должен был проверяться открытым ключом, можно было подписать алгоритмом с общим ключом (семейство HS), и он всё равно проходил — ровно та путаница алгоритмов, что описана выше.
Два урока:
- Держите библиотеку обновлённой. Проверить зависимости на известные уязвимости можно, вставив lock-файл в сканер уязвимостей зависимостей
- Не полагайтесь на значения библиотеки по умолчанию; явно указывайте принимаемый алгоритм в своём коде
Как проверить свои токены
Вставьте токен, выданный вашим собственным сервисом, в декодер JWT и посмотрите на следующее. Декодирование выполняется в вашем браузере, токен не отправляется на сервер. И всё же для действующего токена из продакшена разумно затем выйти из системы, чтобы он стал недействительным.
- Совпадает ли
algв заголовке с ожидаемым (неnone)? - Есть ли
exp, и подходит ли срок жизни отiat(момента выдачи) для этого применения? - Есть ли
audиiss, и указывают ли они на ваш сервис? - Нет ли в полезной нагрузке того, что вы не хотели бы показывать, — адресов почты, телефонов, ключей API?
Проверку на сервере проверяйте тестами в среде разработки. Токен с одним изменённым символом подписи, токен с другим alg, просроченный токен и токен для другого сервиса (неверный aud) должны отклоняться — держите это как автоматический тест.
Источники (первичные)
- IETF RFC 8725, «JSON Web Token Best Current Practices» — datatracker.ietf.org (фиксация алгоритмов, проверка каждой криптографической операции, путаница алгоритмов, принадлежность ключей издателю)
- IETF RFC 7519, «JSON Web Token (JWT)» — datatracker.ietf.org (нормативные определения
exp,nbfиaud) - GitHub Advisory Database, «Verification Bypass in jsonwebtoken» (CVE-2015-9235) — github.com/advisories (затронуты 4.2.1 и ниже; исправлено в 4.2.2)
- OWASP HTML5 Security Cheat Sheet — cheatsheetseries.owasp.org (не хранить идентификаторы сеанса в localStorage)
Читать дальше
- Инструмент: Декодер / инспектор JWT (проверить содержимое, заметить alg:none или срок действия)
- Глоссарий: что такое XSS (классический путь кражи токена) / что такое CSRF / что такое фиксация сессии / что такое криптография с открытым ключом
- Обучение: как правильно выбрать MFA
- Проектирование: ошибки в проектировании сброса пароля (частые ошибки на стороне, выпускающей токены)