Атаки на JWKS: подмена ключа через jku и x5u

JWKS и модель доверия в JWT-инфраструктуре строятся вокруг идеи, что подпись токена проверяется по публичному ключу, заранее известному или полученному из доверенного источника. В реальных системах часто используется динамическая загрузка ключей через JWKS (JSON Web Key Set), где сервер аутентификации публикует набор публичных ключей, а потребители токенов подтягивают их по мере необходимости.

В библиотеке JOSE (JavaScript Object Signing and Encryption) поддержка JWS, JWE и JWK реализована с акцентом на строгую криптографическую модель, но безопасность всей схемы резко зависит не только от криптографии, но и от того, как устроена работа с внешними ключами и метаданными заголовков токена.

В спецификации JSON Web Signature предусмотрены поля заголовка, которые позволяют указывать источник ключа:

  • jku (JWK Set URL) — URL, по которому можно загрузить набор JWK
  • x5u (X.509 URL) — URL, указывающий на сертификат или цепочку сертификатов

Идея выглядит удобной: токен может сам «подсказать», где взять ключ для проверки подписи. В распределённых системах это снижает необходимость жёсткой конфигурации ключей на стороне сервисов.

Однако именно эти поля становятся критической точкой атаки.

Суть атаки через подмену jku

Если приложение принимает токен и без строгой проверки использует значение jku, происходит разрыв модели доверия: источник ключа становится частью входных данных, контролируемых потенциально атакующим.

Сценарий выглядит следующим образом:

  1. Атакующий создаёт JWT

  2. В заголовке указывает:

    • jku: https://attacker.example/keys.json
  3. Подписывает токен произвольным ключом, который соответствует опубликованному JWKS на его сервере

  4. Сервер-валидатор:

    • извлекает jku
    • выполняет HTTP-запрос
    • получает ключи атакующего
    • успешно проверяет подпись

Критическая ошибка здесь — доверие к произвольному URL из токена.

В случае использования JOSE это особенно опасно при неправильной конфигурации jwtVerify, когда разработчик передаёт функцию, автоматически разрешающую загрузку ключей по jku.

SSRF как побочный эффект обработки x5u

Поле x5u несёт ещё более опасный характер, так как изначально предназначено для загрузки X.509 сертификатов по URL.

Атака через x5u часто превращается в классический SSRF (Server-Side Request Forgery):

  1. В токене указывается:

    • x5u: http://127.0.0.1:8080/admin
  2. Сервер проверки JWT выполняет HTTP-запрос к этому адресу

  3. Внутренние сервисы становятся доступными из контекста JWT-валидации

Даже если результат запроса не используется как ключ, сам факт сетевого обращения уже создаёт риск:

  • утечка внутренних метаданных
  • обход сетевых ограничений
  • сканирование внутренних портов

В некоторых реализациях ошибка усугубляется тем, что ответ интерпретируется как PEM-сертификат без строгой валидации содержимого.

Почему JOSE не защищает от архитектурных ошибок

Библиотека JOSE корректно реализует криптографические примитивы, но не может определить, какой источник ключей является доверенным. Основная проблема возникает на уровне интеграции:

  • разработчик передаёт jku как допустимый источник без whitelist
  • отсутствует фиксированный JWKS endpoint
  • разрешены HTTP-запросы к произвольным доменам
  • не ограничены протоколы (http/https)

Таким образом, атака не является уязвимостью криптографии, а является ошибкой доверительной модели.

Типовые ошибки реализации проверки JWT

На практике уязвимые интеграции имеют несколько повторяющихся паттернов.

1. Автоматическая загрузка ключей без фильтрации

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.

2. Разрешение любых схем URL

Даже если проверяется домен, часто забывают о протоколе:

  • http вместо https
  • file:// (в некоторых окружениях)
  • внутренняя адресация

3. Отсутствие закрепления доверенного JWKS

Правильная модель должна опираться на фиксированный источник:

  • https://auth.company.com/.well-known/jwks.json

Любое отклонение от этого источника превращает систему в доверяющую входным данным.

Эксплуатация через цепочку подмены ключа

Комбинированные атаки часто используют несколько механизмов одновременно:

  1. jku указывает на контролируемый JWKS
  2. ключ подписан алгоритмом, который сервер допускает (например, RS256)
  3. сервер не проверяет соответствие issuer/audience
  4. происходит успешная аутентификация

В более сложных сценариях атакующий может:

  • переключать ключи через ротацию JWKS
  • подменять ключи в реальном времени
  • использовать CDN для маскировки источника

Алгоритмическая путаница как усилитель атаки

Хотя основная тема связана с jku и x5u, часто атака усиливается через смешение алгоритмов:

  • RS256 vs HS256 confusion
  • использование симметричного ключа вместо асимметричного
  • fallback на небезопасный алгоритм при ошибке загрузки ключа

В таких случаях даже частично повреждённая модель доверия может приводить к полному обходу проверки подписи.

Защитная модель: фиксация источника ключей

Безопасная архитектура работы с JOSE должна исключать любые динамические источники ключей из токена.

Ключевые принципы:

Жёсткое закрепление JWKS

  • только один доверенный endpoint
  • отсутствие загрузки по URL из токена
  • кеширование ключей на стороне сервиса

Игнорирование опасных заголовков

  • jku должен быть полностью отключён
  • x5u не должен обрабатываться
  • любые расширенные заголовки JWS игнорируются

Контроль сетевых запросов

Если загрузка JWKS всё же используется:

  • запрет внутренних IP диапазонов
  • запрет HTTP (только HTTPS)
  • ограничение доменов через allowlist
  • таймауты и лимиты ответа

Пример безопасной конфигурации

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 источника полезно дополнительно валидировать заголовок токена до криптографической проверки:

  • отклонение токенов с jku
  • отклонение токенов с x5u
  • проверка допустимого набора алгоритмов (alg whitelist)

Этот слой снижает риск обхода через нестандартные расширения.

Контекст реальных атак

В реальных инцидентах подмена ключа через jku и SSRF через x5u часто становится частью более крупной цепочки:

  • компрометация API gateway
  • получение доступа к внутренним сервисам
  • эскалация привилегий через поддельные токены
  • обход SSO-инфраструктуры

Основная причина — доверие к данным, которые по модели JWT должны считаться недоверенными до верификации подписи, но используются до неё.