JWKS и модель доверия в JWT-инфраструктуре строятся вокруг идеи, что подпись токена проверяется по публичному ключу, заранее известному или полученному из доверенного источника. В реальных системах часто используется динамическая загрузка ключей через JWKS (JSON Web Key Set), где сервер аутентификации публикует набор публичных ключей, а потребители токенов подтягивают их по мере необходимости.
В библиотеке JOSE (JavaScript Object Signing and Encryption) поддержка JWS, JWE и JWK реализована с акцентом на строгую криптографическую модель, но безопасность всей схемы резко зависит не только от криптографии, но и от того, как устроена работа с внешними ключами и метаданными заголовков токена.
В спецификации JSON Web Signature предусмотрены поля заголовка, которые позволяют указывать источник ключа:
jku (JWK Set URL) — URL, по которому можно загрузить
набор JWKx5u (X.509 URL) — URL, указывающий на сертификат или
цепочку сертификатовИдея выглядит удобной: токен может сам «подсказать», где взять ключ для проверки подписи. В распределённых системах это снижает необходимость жёсткой конфигурации ключей на стороне сервисов.
Однако именно эти поля становятся критической точкой атаки.
Если приложение принимает токен и без строгой проверки использует
значение jku, происходит разрыв модели доверия: источник
ключа становится частью входных данных, контролируемых потенциально
атакующим.
Сценарий выглядит следующим образом:
Атакующий создаёт JWT
В заголовке указывает:
jku: https://attacker.example/keys.jsonПодписывает токен произвольным ключом, который соответствует опубликованному JWKS на его сервере
Сервер-валидатор:
jkuКритическая ошибка здесь — доверие к произвольному URL из токена.
В случае использования JOSE это особенно опасно при неправильной
конфигурации jwtVerify, когда разработчик передаёт функцию,
автоматически разрешающую загрузку ключей по jku.
Поле x5u несёт ещё более опасный характер, так как
изначально предназначено для загрузки X.509 сертификатов по URL.
Атака через x5u часто превращается в классический SSRF
(Server-Side Request Forgery):
В токене указывается:
x5u: http://127.0.0.1:8080/adminСервер проверки JWT выполняет HTTP-запрос к этому адресу
Внутренние сервисы становятся доступными из контекста JWT-валидации
Даже если результат запроса не используется как ключ, сам факт сетевого обращения уже создаёт риск:
В некоторых реализациях ошибка усугубляется тем, что ответ интерпретируется как PEM-сертификат без строгой валидации содержимого.
Библиотека JOSE корректно реализует криптографические примитивы, но не может определить, какой источник ключей является доверенным. Основная проблема возникает на уровне интеграции:
jku как допустимый источник без
whitelistТаким образом, атака не является уязвимостью криптографии, а является ошибкой доверительной модели.
На практике уязвимые интеграции имеют несколько повторяющихся паттернов.
const { jwtVerify } = require('jose');
const { payload } = await jwtVerify(token, async (header) => {
const res = await fetch(header.jku);
const jwks = await res.json();
return importJWK(jwks.keys[0]);
});
Критическая проблема — отсутствие проверки
header.jku.
Даже если проверяется домен, часто забывают о протоколе:
Правильная модель должна опираться на фиксированный источник:
https://auth.company.com/.well-known/jwks.jsonЛюбое отклонение от этого источника превращает систему в доверяющую входным данным.
Комбинированные атаки часто используют несколько механизмов одновременно:
jku указывает на контролируемый JWKSВ более сложных сценариях атакующий может:
Хотя основная тема связана с jku и x5u,
часто атака усиливается через смешение алгоритмов:
В таких случаях даже частично повреждённая модель доверия может приводить к полному обходу проверки подписи.
Безопасная архитектура работы с JOSE должна исключать любые динамические источники ключей из токена.
Ключевые принципы:
jku должен быть полностью отключёнx5u не должен обрабатыватьсяЕсли загрузка JWKS всё же используется:
import { jwtVerify, createRemoteJWKSet } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://auth.example.com/.well-known/jwks.json')
);
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://auth.example.com',
audience: 'api-service'
});
В этой модели отсутствует любая зависимость от jku и
x5u, а ключи загружаются только из фиксированного
источника.
Даже при использовании безопасного JWKS источника полезно дополнительно валидировать заголовок токена до криптографической проверки:
jkux5ualg
whitelist)Этот слой снижает риск обхода через нестандартные расширения.
В реальных инцидентах подмена ключа через jku и SSRF
через x5u часто становится частью более крупной
цепочки:
Основная причина — доверие к данным, которые по модели JWT должны считаться недоверенными до верификации подписи, но используются до неё.