Подмена алгоритма none в JWT

JWT (JSON Web Token) представляет собой компактный формат передачи утверждений, состоящий из трёх частей: заголовка, полезной нагрузки и подписи. Ключевая идея безопасности JWT — невозможность незаметно изменить содержимое токена без знания секретного ключа или приватного ключа асимметричной криптографии.

Структура JWT и роль алгоритма подписи

Заголовок JWT содержит информацию о типе токена и алгоритме подписи:

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

Поле alg определяет, каким способом формируется и проверяется подпись. На практике используются:

  • HMAC-алгоритмы (HS256, HS512)
  • RSA (RS256, RS512)
  • ECDSA (ES256 и др.)

Подпись защищает токен от подделки. При проверке сервер пересчитывает подпись и сравнивает её с полученной.

Суть уязвимости alg: none

Исторически в спецификации JWT существовал режим, при котором подпись отсутствует:

{
  "alg": "none"
}

В этом случае токен считается «неподписанным», а поле подписи либо пустое, либо игнорируется.

Критическая проблема возникает тогда, когда сервер или библиотека:

  • доверяет значению alg, пришедшему из токена
  • не фиксирует допустимые алгоритмы
  • допускает none как валидный вариант

Это приводит к ситуации, когда злоумышленник может:

  • изменить payload (например, роль пользователя)
  • установить alg: none
  • удалить подпись
  • отправить токен как «валидный»

Подмена алгоритма в контексте jsrsasign

Библиотека jsrsasign предоставляет функциональность для работы с JWT через объект KJUR.jws.JWS.

Пример генерации токена:

const header = { alg: "HS256", typ: "JWT" };
const payload = { user: "alice", role: "user" };
const secret = "secret-key";

const token = KJUR.jws.JWS.sign("HS256", header, payload, secret);

Проверка:

const isValid = KJUR.jws.JWS.verifyJWT(token, "secret-key", {
  alg: ["HS256"]
});

Проблема возникает, если:

  • разработчик не ограничивает список алгоритмов
  • допускается автоматическое определение alg
  • используется устаревшая или небезопасная конфигурация

Эксплуатация через none

Злоумышленник может сформировать JWT вручную:

const header = {
  alg: "none",
  typ: "JWT"
};

const payload = {
  user: "admin",
  role: "administrator"
};

Токен формируется без подписи:

base64(header).base64(payload).

Если проверяющая сторона использует некорректную логику:

KJUR.jws.JWS.verifyJWT(token, null);

или не ограничивает алгоритмы, возможна ситуация, когда:

  • подпись не проверяется вообще
  • алгоритм none принимается как допустимый

Ошибки конфигурации проверки

Наиболее опасные паттерны:

1. Отсутствие whitelist алгоритмов

KJUR.jws.JWS.verifyJWT(token, key, {});

В этом случае библиотека может принять alg из заголовка без ограничений.

2. Доверие полю header.alg

Если система делает выбор алгоритма на основе входного токена:

const alg = decodedHeader.alg;

это создаёт возможность подмены механизма проверки.

3. Игнорирование подписи при none

Некоторые реализации исторически интерпретировали none как «пропустить проверку».

Как происходит атака

Сценарий эксплуатации обычно включает:

  1. Получение валидного JWT

  2. Декодирование payload

  3. Изменение критичных полей (роль, доступ)

  4. Установка:

    { "alg": "none" }
  5. Удаление подписи

  6. Отправка токена на сервер

Если сервер не проверяет алгоритм строго — токен принимается.

Защитные механизмы в jsrsasign

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

const isValid = KJUR.jws.JWS.verifyJWT(token, secret, {
  alg: ["HS256"]
});

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

  • явный whitelist алгоритмов
  • запрет none
  • отказ от динамического выбора алгоритма из токена

Почему none особенно опасен

Алгоритм none разрушает саму модель безопасности JWT:

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

В сочетании с ошибками в библиотеке или конфигурации это превращается в прямой обход авторизации.

Типичные ошибки разработчиков

  • использование JWT «как есть» без проверки алгоритма
  • копирование примеров без ограничения alg
  • доверие сторонним токенам без валидации структуры
  • отключение проверки подписи в тестовой среде, перенесённое в продакшн

Строгая модель проверки

Корректная модель всегда предполагает:

  • фиксированный набор допустимых алгоритмов
  • явное указание ключа
  • обязательную проверку подписи вне зависимости от alg
const options = {
  alg: ["HS256"]
};

KJUR.jws.JWS.verifyJWT(token, secret, options);

Любое отклонение от этой модели создаёт поверхность атаки, в которой alg: none становится наиболее простым способом обхода защиты.