layout | title | tags |
---|---|---|
col-document |
WSTG - Latest |
WSTG |
{% include breadcrumb.html %}
ID |
---|
WSTG-SESS-10 |
JSON Web Token (JWT) — это токен JSON с криптографической подписью, предназначенный для обмена утверждениями (англ.: claims) между системами. Они часто используются в качестве токенов аутентификации или сессии, особенно в REST API.
JWT являются распространённым источником уязвимостей, как с точки зрения того, как они реализованы в приложениях, так и в базовых библиотеках. Поскольку они используются для аутентификации, уязвимость может легко привести к полной компрометации приложения.
- Определить, раскрывают ли JWT чувствительную информацию.
- Определить, можно ли подделать или модифицировать JWT.
JWT состоит из трёх компонентов:
- Заголовок
- Полезная нагрузка (или тело)
- Подпись
Каждый компонент закодирован в Base64, и они разделены точками (.
). Обратите внимание, что кодировка Base64, используемая в JWT, удаляет знаки равенства (=
), поэтому вам может потребоваться вернуть их для декодирования.
Заголовок определяет тип токена (обычно JWT
) и алгоритм, используемый для подписи. Пример декодированного заголовка показан ниже:
{
"alg": "HS256",
"typ": "JWT"
}
Есть три основных типа алгоритмов, которые используются для подписи:
Алгоритм | Описание |
---|---|
HSxxx | HMAC с использованием секретного ключа и SHA-xxx. |
RSxxx и PSxxx | Подпись с открытым ключом с использованием RSA. |
ESxxx | Подпись с открытым ключом с использованием ECDSA. |
Есть также множество других алгоритмов, которые можно использовать для зашифрованных токенов (JWE), хотя они менее распространены.
Полезная нагрузка JWT содержит сами данные. Пример полезной нагрузки показан ниже:
{
"username": "admininistrator",
"is_admin": true,
"iat": 1516239022,
"exp": 1516242622
}
Полезная нагрузка обычно не шифруется, поэтому проанализируйте её, чтобы определить, нет ли в ней чувствительных или, возможно, неуместных для токена данных.
Данный JWT включает имя пользователя и его статус администратора, а также два стандартных утверждения (iat
и exp
). Эти утверждения определены в RFC 5719, их краткое изложение приведено в таблице ниже:
Утверждение | Наименование | Описание |
---|---|---|
iss |
Issuer | Идентификатор стороны, выпустившей токен. |
iat |
Issued At | Unix-время, указывающее на то, когда был выпущен токен. |
nbf |
Not Before | Unix-время самой ранней даты, начиная с которой токен может быть использован. |
exp |
Expires | Unix-время, когда срок действия токена истечёт. |
Подпись вычисляется с использованием алгоритма, указанного в заголовке JWT, а затем кодируется в Base64 и добавляется к токену. Изменение любой части JWT должно привести к тому, что подпись станет недействительной, а токен будет отклонен сервером.
Помимо того, что JWT сам по себе является криптографически защищённым, он также должен безопасно храниться и передаваться. Необходимо удостовериться, что:
- Он всегда передаётся через зашифрованные (HTTPS) соединения.
- Если он хранится в cookie, то он должен иметь соответствующие атрибуты.
Достоверность JWT также необходимо проверить по утверждениям iat
, nbf
и exp
, чтобы убедиться в том, что:
- JWT имеет разумный срок действия.
- Токены с истекшим сроком действия отклоняются.
Одна из наиболее серьёзных уязвимостей, встречающихся в JWTs, заключается в том, что приложению не удаётся проверить правильность подписи. Обычно это происходит, когда разработчик использует, например, такую функцию, как jwt.decode()
в NodeJS, которая просто декодирует тело JWT, не верифицируя перед этим подпись с помощью функции jwt.verify()
.
Это можно легко проверить, изменив тело JWT, но ничего не меняя в заголовке или подписи, и отправив его в запросе, чтобы узнать, принимает ли его приложение.
Помимо алгоритмов с открытым ключом и на основе HMAC, спецификация JWT также определяет алгоритм подписи, называемый none
. Как следует из названия, это означает, что в JWT нет подписи, что позволяет изменять его.
Это можно проверить, изменив алгоритм подписи (alg
) в заголовке JWT на none
, как показано в примере ниже:
{
"alg": "none",
"typ": "JWT"
}
Затем заголовок и полезная нагрузка повторно кодируются с помощью Base64, а подпись удаляется (оставляя завершающую точку). Используя заголовок выше и полезную нагрузку, указанную в разделе Полезная нагрузка это дало бы следующий JWT:
eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0K.eyJ1c2VybmFtZSI6ImFkbWluaW5pc3RyYXRvciIsImlzX2FkbWluIjp0cnVlLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0MjYyMn0.
Некоторые реализации пытаются избежать этого, явно блокируя использование алгоритма none
. Если это делается без учёта регистра, можно попробовать указать алгоритм как NoNe
.
В версиях Java с 15 по 18 была выявлена уязвимость, из-за которой при некоторых обстоятельствах они неправильно проверяли подписи ECDSA (CVE-2022-21449, известная как «психоподпись», отсылка к «психобумаге»). Если одна из этих уязвимых версий используется для анализа JWT с алгоритмом ES256
, с её помощью можно полностью обойти проверку подписи путём подделки тела и последующей замены подписи на значение
MAYCAQACAQA
В результате получается JWT, который выглядит примерно так:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6InRydWUifQ.MAYCAQACAQA
Если JWT подписан с использованием алгоритма, основанного на HMAC (например, HS256), стойкость подписи полностью зависит от энтропии секретного ключа HMAC.
Если приложение использует программное обеспечение (приобретённое или с открытым исходным кодом), первым шагом должно быть исследование его исходного кода и проверка того, не используется ли ключ подписи HMAC по умолчанию.
Если значения по умолчанию нет, то ключ можно взломать, угадать или перебрать. Самый простой способ сделать это — использовать скрипт crackjwt.py, которому нужен только сам JWT и файл словаря.
Более серьёзный вариант — преобразовать JWT в формат, понятный John the Ripper, скриптом jwt2john.py. Затем можно использовать Джона для проведения гораздо более сложных атак на ключ.
Если JWT большой, он может превышать максимально допустимый размер, поддерживаемый Джоном. Это можно обойти, увеличив значение переменной SALT_LIMBS
в /src/hmacSHA256_fmt_plug.c
(или аналогичном ему для других форматов HMAC) и перекомпилировав Джона, как описано в дискуссии на GitHub.
Если можно получить этот ключ, то можно создавать и подписывать произвольные JWT, что обычно приводит к полной компрометации приложения.
Если приложение использует JWT с подписями на основе открытого ключа, но не проверяет корректность выбора алгоритма, это потенциально может быть использовано в атаке со смешением типов подписей. Для того чтобы это стало возможным, необходимо выполнить следующие условия:
- Приложение должно ожидать, что JWT будет подписан алгоритмом на основе открытого ключа (т.е.
RSxxx
илиESxxx
). - Приложение не должно проверять, какой алгоритм JWT фактически использует для подписи.
- Открытый ключ, используемый для проверки JWT, должен быть доступен злоумышленнику.
Если все эти условия выполняются, злоумышленник может применить открытый ключ для подписи JWT к алгоритму на основе HMAC (HS256
). Например, в библиотеке Node.JS jsonwebtoken и для открытого ключа, и для токенов на основе HMAC используется одна и та же функция, как показано в примере ниже:
// Проверка JWT, подписанного с помощью RS256
jwt.verify(token, publicKey);
// Проверка JWT, подписанного с помощью HS256
jwt.verify(token, secretKey);
Это означает, что если JWT подписан publicKey
в качестве секретного ключа для алгоритма HS256
, подпись будет считаться действительной.
Чтобы эксплуатировать это, необходимо получить открытый ключ. Чаще всего это происходит, если приложение повторно использует один и тот же ключ как для подписи JWT, так и в составе TLS-сертификата. В этом случае ключ можно загрузить с сервера с помощью такой команды:
openssl s_client -connect example.org:443 | openssl x509 -pubkey -noout
Также открытый ключ может храниться в общедоступном файле на сайте в типичном каталоге, например /.well-known/jwks.json
.
Чтобы проверить это, измените содержимое JWT, а затем возьмите ранее полученный открытый ключ для подписи JWT с использованием алгоритма HS256
. Сделать это часто бывает трудно при тестировании без доступа к исходному коду или деталям реализации, поскольку формат ключа должен быть идентичен тому, который используется сервером, поэтому такие проблемы, как кодировка пустых строк или CRLF, могут привести к несоответствию ключей.
Стандарт JSON Web Signature (JWS) (который определяет заголовок и подписи в JWT), позволяет встраивать ключ для подписи токена, в заголовок. Если библиотека проверки токена поддерживает это и не проверяет ключ по списку утверждённых, то злоумышленник может подписать JWT произвольным ключом.
Существует множество скриптов, которые можно использовать для этого, например, jwk-node-jose.py или jwt_tool.
- Тестирование на наличие конфиденциальной информации, передаваемой по незашифрованным каналам.
- Тестирование атрибутов Cookie.
- Тестирование хранилищ браузера.
- Используйте безопасную и актуальную библиотеку для обработки JWT.
- Убедитесь, что подпись действительна и что она использует ожидаемый алгоритм.
- Используйте криптостойкий ключ HMAC или уникальный закрытый ключ для подписи.
- Убедитесь, что в полезной нагрузке нет чувствительной информации.
- Убедитесь, что JWT защищены при хранении и передаче.
- См. Памятка OWASP по JSON Web Token.