alg confusion атаки в JWT

Алгоритмическая путаница (alg confusion) в JWT возникает в момент, когда система некорректно обрабатывает параметр alg в заголовке токена и допускает подмену алгоритма подписи. Это одна из наиболее критичных ошибок реализации проверки JWT, особенно в связке симметричных (HMAC) и асимметричных (RSA/ECDSA) схем.

JWT состоит из трёх частей: заголовка, полезной нагрузки и подписи.

Заголовок обычно выглядит так:

{
  "alg": "RS256",
  "typ": "JWT"
}

Полезная нагрузка содержит утверждения (claims), например:

{
  "sub": "user123",
  "role": "admin",
  "iat": 1710000000
}

Подпись формируется на основе заголовка и payload с использованием секретного ключа или приватного ключа, в зависимости от алгоритма.

Ключевая деталь: поле alg не просто информативное — многие библиотеки используют его для выбора алгоритма проверки подписи.


Суть alg confusion атаки

Алгоритмическая путаница возникает, когда сервер доверяет значению alg из самого JWT и использует его при верификации.

Типичный сценарий уязвимости:

  • Сервер ожидает RS256 (RSA, асимметричная криптография)
  • Проверяет подпись с помощью публичного ключа
  • Но атакующий подменяет alg на HS256 (HMAC, симметричный алгоритм)
  • Сервер ошибочно использует публичный ключ как HMAC secret

В результате публичный ключ превращается в секрет HMAC, и атакующий может самостоятельно подписывать токены.


Критический сценарий эксплуатации

Предположим, сервер принимает JWT и использует библиотеку, которая автоматически выбирает алгоритм из заголовка:

const jwt = header + "." + payload;
const signature = HMAC(publicKey, jwt); // ошибка логики

Если атакующий подменяет:

{
  "alg": "HS256"
}

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

HMAC(publicKey, data)

сервер успешно проверяет подпись, так как делает то же самое.


Почему это работает

Проблема возникает из-за смешивания двух моделей:

  • RS256: подпись = RSA(privateKey, data), проверка = RSA(publicKey, data)
  • HS256: подпись = HMAC(secret, data)

Если библиотека не фиксирует алгоритм на серверной стороне, она может переключиться на HS256 автоматически.


Jsrsasign и контроль алгоритмов

Библиотека Jsrsasign предоставляет низкоуровневые криптографические функции и инструменты для работы с JWT, включая строгую проверку алгоритмов.

Пример проверки JWT с RS256:

const KJUR = require('jsrsasign');

const publicKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`;

const jwt = "header.payload.signature";

const isValid = KJUR.jws.JWS.verifyJWT(jwt, publicKey, {
  alg: ["RS256"]
});

Ключевой момент: явное указание допустимых алгоритмов.


Уязвимая реализация (антипаттерн)

Следующий подход приводит к alg confusion:

KJUR.jws.JWS.verifyJWT(jwt, publicKey, {
  alg: ["HS256", "RS256"] // опасная практика
});

или ещё хуже — отсутствие ограничения:

KJUR.jws.JWS.verifyJWT(jwt, publicKey);

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


Правильная модель проверки JWT

Безопасная архитектура всегда фиксирует алгоритм вне токена:

const isValid = KJUR.jws.JWS.verifyJWT(jwt, publicKey, {
  alg: ["RS256"],
  verifyAt: KJUR.jws.IntDate.getNow()
});

Дополнительно важно:

  • Не доверять alg из заголовка как источнику истины
  • Жёстко привязывать тип ключа к алгоритму
  • Разделять обработку RSA и HMAC на уровне кода

Типовая ошибка при миграции систем

Алгоритмическая путаница часто появляется при переходе:

  • от HS256 (общий секрет)
  • к RS256 (пара ключей)

Если старый код остаётся совместимым с несколькими алгоритмами, сервер может продолжать принимать HS256-токены, используя публичный ключ как секрет.


Проверка структуры JWT перед верификацией

Jsrsasign позволяет разобрать токен до проверки:

const parsed = KJUR.jws.JWS.parse(jwt);

console.log(parsed.headerObj.alg);

На этом этапе можно принудительно отклонять неожиданные значения:

if (parsed.headerObj.alg !== "RS256") {
  throw new Error("Недопустимый алгоритм");
}

Подмена алгоритма через header injection

Атакующий может модифицировать только заголовок:

{
  "alg": "HS256",
  "typ": "JWT"
}

При этом payload остаётся неизменным, а подпись пересчитывается под HMAC.

Если сервер не фиксирует алгоритм — проверка проходит успешно.


Роль ключей в контексте атаки

Слабые реализации часто допускают:

  • использование одного ключа для разных алгоритмов
  • передачу публичного ключа в HMAC-функцию
  • динамическое определение алгоритма без whitelist

В корректной модели:

  • RS256 всегда использует только RSA-ключи
  • HS256 всегда использует только shared secret
  • ключи никогда не взаимозаменяемы

Защитная стратегия при использовании Jsrsasign

Jsrsasign позволяет строить строгую модель проверки:

function verifyToken(jwt, publicKey) {
  const header = KJUR.jws.JWS.parse(jwt).headerObj;

  if (header.alg !== "RS256") {
    return false;
  }

  return KJUR.jws.JWS.verifyJWT(jwt, publicKey, {
    alg: ["RS256"]
  });
}

Дополнительно может применяться контроль ключевого типа:

  • RSA ключи только для RS256/PS256
  • HMAC секреты только для HS256

Особенности атак в реальных системах

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

  • микросервисных архитектурах
  • системах с несколькими провайдерами OAuth
  • приложениях с обратной совместимостью JWT

Особенно опасны ситуации, где:

  • токены принимаются от внешних источников
  • библиотека автоматически выбирает алгоритм
  • отсутствует централизованный JWT validator

Модель угроз и логическая ошибка доверия

Основная проблема alg confusion — это не криптографическая уязвимость, а ошибка доверия:

  • доверие заголовку вместо конфигурации сервера
  • смешивание алгоритмов в одной точке проверки
  • отсутствие жёсткой схемы валидации

JWT становится безопасным только тогда, когда алгоритм определяется системой, а не токеном.


Поведение Jsrsasign при разных алгоритмах

Библиотека поддерживает множество алгоритмов:

  • HS256 / HS512 (HMAC)
  • RS256 / RS512 (RSA)
  • ES256 (ECDSA)

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

alg: ["RS256"]

Любое расширение списка увеличивает поверхность атаки.


Практическая модель безопасной проверки

Строгая схема работы с JWT обычно включает:

  • фиксированный алгоритм на уровне конфигурации
  • жёсткую проверку alg
  • разделение ключей по типу криптографии
  • отказ от динамического выбора алгоритма

Jsrsasign в этой модели выступает как криптографический слой, а не как логика принятия решений.


Итоговая структура безопасной проверки

Типичная корректная последовательность:

  1. Парсинг JWT
  2. Проверка alg до криптографии
  3. Выбор строго определённого алгоритма
  4. Верификация подписи с фиксированным ключом
  5. Проверка claims (exp, iat, aud, iss)

Любое отклонение от этой последовательности создаёт условия для alg confusion атаки.