Небезопасный JWT: alg none и защита от него

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

JWT состоит из трёх частей:

  • Header (заголовок)
  • Payload (полезная нагрузка)
  • Signature (подпись)

JWT = HMAC;or;RSA;Signature;over(Header + “.” + Payload)

В заголовке указывается алгоритм подписи:

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

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

Алгоритм none и его смысл в спецификации

В ранних версиях стандарта JSON Web Token предусматривалась возможность отсутствия подписи. Для этого использовался алгоритм:

{
  "alg": "none"
}

В этом случае третья часть JWT либо пустая, либо отсутствует. Формально это выглядело так:

header.payload.

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

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

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

  • доверяет значению alg из токена
  • не запрещает алгоритм none
  • не проверяет подпись при его использовании

В этом случае атакующий может:

  1. Сформировать произвольный payload
  2. Установить alg: none
  3. Убрать подпись
  4. Отправить токен как валидный

Пример поддельного токена:

Header:
{
  "alg": "none",
  "typ": "JWT"
}

Payload:
{
  "user": "admin",
  "role": "administrator"
}

И итоговая строка:

base64(header).base64(payload).

Если сервер принимает такой токен, он фактически полностью отключает механизм аутентификации.

Почему это до сих пор встречается

Основная причина — ошибки реализации:

  • использование устаревших библиотек
  • ручная проверка JWT через decode вместо verify
  • доверие к полю alg из токена
  • отсутствие строгой конфигурации допустимых алгоритмов

Особенно опасно, когда разработчики считают JWT «самодостаточной защитой», не учитывая необходимость строгой криптографической проверки.


Проверка JWT в Jsrsasign и правильная настройка

Библиотека Jsrsasign предоставляет инструменты для работы с JWT через KJUR.jws.JWS.

Базовая проверка выглядит так:

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

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

Уязвимая ошибка при использовании Jsrsasign

Ошибочная реализация:

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

или даже:

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

В таком случае библиотека может:

  • не ограничить список алгоритмов
  • принять none
  • либо неявно довериться заголовку JWT

Это открывает возможность подделки токена.


Защита от alg: none в Jsrsasign

1. Жёсткое ограничение алгоритмов

Всегда необходимо явно задавать список разрешённых алгоритмов:

const result = KJUR.jws.JWS.verifyJWT(token, publicKey, {
  alg: ['HS256', 'RS256']
});

Алгоритм none при этом автоматически исключается.


2. Проверка заголовка перед валидацией

Дополнительный уровень защиты — ручная проверка header:

const headerObj = KJUR.jws.JWS.parse(token).headerObj;

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

Это предотвращает даже попытку обхода логики проверки.


3. Отказ от автоматического доверия к alg

Главное правило безопасности JWT:

алгоритм никогда не должен определяться на основе входного токена

Сервер должен:

  • фиксировать алгоритм на своей стороне
  • игнорировать значение alg из токена

4. Использование только проверенных ключей

При работе с RSA:

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

Если используется HMAC:

alg: ['HS256']

Любые другие алгоритмы должны быть явно запрещены.


5. Полное отключение none на уровне логики

Даже если библиотека технически поддерживает none, его нужно исключить архитектурно:

const allowedAlgs = new Set(['HS256', 'RS256']);

if (!allowedAlgs.has(headerObj.alg)) {
  throw new Error('Алгоритм не разрешён');
}

Типичные сценарии атаки

Подмена роли пользователя

Если сервер доверяет JWT без проверки подписи:

{
  "user": "attacker",
  "role": "admin"
}

Атака позволяет получить административный доступ.


Обход авторизации API

В REST API часто используется проверка:

if (decoded.role === 'user') allow();

При alg: none атакующий просто подменяет payload.


Комбинированные атаки с подделкой алгоритма

Некоторые системы сначала читают alg, а затем:

  • выбирают ключ
  • или отключают проверку

Это критическая ошибка архитектуры.


Внутреннее поведение JWT-библиотек

В Jsrsasign разбор JWT включает:

  • декодирование header
  • определение алгоритма
  • выбор метода проверки

Если алгоритм не ограничен, библиотека может:

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

Это делает важным явное указание параметров verifyJWT.


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

Безопасная схема должна включать несколько слоёв:

  1. Жёсткий список алгоритмов
  2. Проверка header до валидации
  3. Игнорирование alg из токена
  4. Использование только verifyJWT, а не decode

Пример:

function verify(token, key) {
  const header = KJUR.jws.JWS.parse(token).headerObj;

  if (header.alg === 'none') {
    throw new Error('Запрещённый алгоритм');
  }

  return KJUR.jws.JWS.verifyJWT(token, key, {
    alg: ['RS256']
  });
}

Частые ошибки разработчиков

  • использование decodeJWT вместо verifyJWT
  • отсутствие проверки подписи вообще
  • доверие полю alg
  • поддержка none «для тестов» в продакшене
  • отсутствие whitelist алгоритмов

Принцип минимального доверия к токену

JWT не должен рассматриваться как доверенный объект до момента полной криптографической проверки.

Любые поля, включая:

  • alg
  • typ
  • kid

должны рассматриваться как потенциально поддельные.


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

Корректная реализация всегда предполагает:

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

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