Статья JWT Attacks: подделка токенов и эксплуатация уязвимостей

1771811842843.webp

Как часто ты встречал JWT в своей работе? Честно? Этот стандарт так плотно вошёл в веб-приложения, что его используют везде: от API до аутентификации и микросервисов. Но если бы всё было так просто... Все эти токены на самом деле могут быть твоими врагами, если не уметь правильно с ними работать. И тут начинается настоящая проблема - ошибки в реализации JWT... Это уязвимости, которые могут привести к серьёзным последствиям: от захвата аккаунтов до эскалации привилегий и обхода авторизации.

Проблема в том, что в 2026 году всё стало ещё хуже. Мы говорим не только о традиционном вебе, но и о современных микросервисах, которые только ускоряют движение атаки. JWT теперь можно использовать не только для того, чтобы обойти одну точку входа, но и перемещаться по всей архитектуре, постепенно расширяя доступ к сервисам. И это уже не просто ошибка в авторизации - это дорога к хаосу. Всё это стало возможным благодаря не всегда очевидным уязвимостям, которые, как мы знаем, всегда появляются в самых неожиданных местах.

А теперь давай смотреть по-серьёзному. За этим маленьким и красивым токеном скрывается целая вселенная угроз, и если ты не знаешь, как правильно с ним работать, ты либо уже стал жертвой атаки, либо будешь ею в ближайшее время. Вопрос только в том, насколько ты готов к этому. Поехали разбираться, как можно обойти, что можно сломать и как это всё защитить.

Структура JWT: Header, Payload, Signature​

Как бы не хотелось верить в магию, но всё, что ты видишь в JWT, сводится к трём частям. И это не просто так. Это три элемента, которые, если понять их структуру, позволят тебе не только быстро разобраться, что идёт не так, но и подстроить свой инструмент атаки или защиты. Так вот, JWT - это Header, Payload и Signature, где каждая из этих частей решает свою задачу.

Header - заголовок, который скрывает всё​

Эта часть токена отвечает за два основных параметра: тип токена и алгоритм подписи. На самом деле ничего сверхсложного. В основном ты увидишь поле alg, которое определяет, какой алгоритм использован для подписи (например, HMAC или RSA). Это поле задаёт направление, по которому сервер будет проверять подпись. То есть если сервер ожидает определённый алгоритм, а ты, подменив его, отправишь токен с другим значением в alg, то вся проверка подписи слетит.

Вот пример этого заголовка:

JSON:
{
  "alg": "HS256",
  "typ": "JWT"
}
Взглянув на это, видно, что на подпись используется алгоритм HS256, а сам токен - это JWT. Заголовок очень важен для проверки подлинности, и его легко использовать в атаке, если он не валидируется должным образом.

Payload - то, что ты скрываешь​

Payload - это суть токена. Тут хранятся все данные, которые сервер передаёт клиенту или которые клиент посылает на сервер. Тут может быть всё: от идентификатора пользователя до роли, срока действия токена или других специфичных данных. На этом этапе токен уже выглядит как нечто большее, чем просто случайная строка. Но в этой части, по сути, нет никакой защиты. Всё можно подделать.

Например, вот так может выглядеть payload:
JSON:
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
Здесь ты видишь sub (subject - идентификатор пользователя), его имя и время создания токена. Всё, что надо, чтобы подделать роль или, например, повысить привилегии - просто поменять эти значения. Ребята, которые занимаются небрежной обработкой данных, могут попасться на это. Обычная подделка payload, и ты в системе как админ. Вуаля!

Signature - подпись, которая вроде как защищает, но не всегда​

Подпись, или Signature, должна обеспечивать проверку целостности токена и подтверждать, что данные в нём не были изменены. Тут всё, что нужно сделать - взять данные из Header и Payload, применить к ним алгоритм, который указан в заголовке, и получить подпись, зашифрованную с использованием секретного ключа. По идее, сервер должен взять этот токен, пересчитать подпись с его помощью и проверить, совпадает ли она с тем, что пришло. Если нет - всё ломается, и токен становится недействительным. Это, в принципе, весь защитный механизм.
Код:
<Header>.<Payload>.<Signature>
Как это работает в реальной жизни? Ну, если твой секретный ключ не слишком хлипкий или если на сервере по каким-то причинам не валидируют алгоритм или payload, ты, скорее всего, получишь access, даже если всё с подписью не так. Подпись, как показали многочисленные тесты и атаки, можно обойти с помощью правильного подбора алгоритма или brute-force. Особенно если секрет слабый или если сервер уязвим.

None Algorithm Attack​

Не всё, что блестит - золото. Так и с JWT. Вроде бы всё проверяется, всё подписи есть, но вот ты забываешь один момент, и вдруг оказываешься в очень неприятной ситуации. Алгоритм None - это как подарок для атакующего, если сервер не проверяет правильность алгоритма в заголовке. При этом токен становится валидным, даже если его подпись отсутствует. Легко понять, почему это такой больной момент.

Механика: alg=none bypass​

Злоумышленник подменяет поле alg в заголовке на none. Почему это так эффективно? Потому что это всё, что ему нужно. Сервер, не проверяя этот момент, сразу пропустит токен, не пытаясь верифицировать его подпись. То есть ты приходишь к серверу с фальшивым токеном, он его принимает как валидный, и ты можешь использовать его, чтобы получить доступ к защищённому ресурсу. Понимаешь, какая боль? Подпись как бы есть, а её и нет, потому что алгоритм стоит none.

Простой пример:

JSON:
{
  "alg": "none",
  "typ": "JWT"
}
Этот токен проходит валидацию без какой-либо подписи. Мелочь? Конечно. Но вот этот момент в настройках сервера может позволить атакующему обходить все проверки. Представь, ты сгенерировал такой токен и отправил его серверу, а он тебя пропустил как своего. Всё, ты в системе. Доступ получен.

Key Confusion (Algorithm Confusion)​

Уязвимость с подменой алгоритма, или как её называют ещё - key confusion, позволяет атакующему использовать публичный ключ вместо секретного для алгоритма HMAC. Картинка, конечно, не самая приятная, но давай разбираться.

подмена RS256 → HS256

Суть атаки проста: сервер использует алгоритм RS256 для подписи (вроде бы, всё как положено, RSA с публичным/приватным ключом), но из-за ошибки в валидации этого алгоритма атакующий может подменить алгоритм на HS256 (HMAC с секретным ключом). И вот тут и появляется проблемка - злоумышленник может использовать публичный ключ как секрет для HMAC, что откроет ему возможность для генерации подписи, которая будет действительна для сервера.
Если сервер не проверяет, что алгоритм действительно тот, который он ожидает, то получается забавная штука: публичный ключ, который используется для RSA, оказывается вполне себе подходящим для HMAC. И ты можешь просто подделать подпись с этим публичным ключом.
Примерно так это выглядит:
  1. У тебя есть публичный ключ, который сервер считает частью подписи.
  2. Ты меняешь алгоритм на HS256.
  3. И с помощью этого публичного ключа как секрета генерируешь свою подпись.
  4. Токен проходит валидацию, потому что сервер думает, что подпись правильная.
Если сервер не проверяет, что алгоритм должен быть только RS256, ты фактически подменяешь логику и проходишь мимо системы.

Использование public key как HMAC secret

Представь, что ты в руках держишь публичный ключ, который сервер использует для подписи. И вот ты можешь использовать этот ключ как секрет для HMAC, не парясь о том, что ты нарушаешь все правила.
Буквально. Сервер будет думать, что ты использовал корректный HMAC-секрет, но на самом деле ты просто использовал публичный ключ, который на стороне клиента был, собственно, всегда в открытом доступе.
Процесс выглядит так:
  1. Ты меняешь алгоритм на HS256 в заголовке.
  2. Вместо секрета передаёшь публичный ключ.
  3. И генерируешь подпись с этим ключом. Это всё.
  4. Сервер принимает такой токен как действительный.
Довольно странно, да? Публичный ключ, который вроде как должен быть только для проверки подписи в RS256, вдруг оказывается твоим секретом для HMAC. Это - реально прикольная уязвимость, если сервер не валидирует алгоритм подписи.
Не всегда это прямо очевидно, но в реальности такие атаки довольно опасны.

Если хочется увидеть, как наша теория про JWT‑уязвимости превращается в практические результаты, стоит заглянуть в разбор конкретной CTF‑задачи, где шаг за шагом показана эксплуатация JSON Web Token - от анализа заголовков до обхода проверки подписи и эскалации привилегий. Подробнее...

Сравнение алгоритмов RS256 и HS256
ХарактеристикаRS256HS256
АлгоритмRSA (публичный/приватный ключ)HMAC (секретный ключ)
Тип подписиАссиметричнаяСимметричная
Уязвимость для атакЕсли алгоритм не проверяется, возможно использование публичного ключа как секрета для HMACЕсли сервер неправильно валидирует алгоритм, то можно использовать публичный ключ как секрет
Как это может быть использовано?Подмена алгоритма на HS256 с использованием публичного ключаПодделать подпись, используя публичный ключ как секретный

Weak Secret Attacks​

Секреты. Они должны быть секретами, но на деле часто оказываются такими же слабенькими, как старый пароль на почте. Вроде как их никто не должен знать, но в реальности даже секрет может быть угадан за пару минут, если он слишком простой или очень удобный. А теперь представь, что этот секрет - это тот самый ключ, который используется для подписи JWT. И если ты выбрал его неудачно, твоя безопасность начинает работать по принципу «как бы не сломали, но уязвимость где-то есть».

Brute-force: hashcat mode 16500

Брутфорс, как вы все знаете, это когда злоумышленник пытается перебрать все возможные варианты. Для JWT, если ключ слишком слаб, ты даже не заметишь, как его вскроют. Для этого используют инструменты, типа hashcat, которые предназначены именно для перебора паролей или секретов. В случае с JWT можно настроить hashcat mode 16500, чтобы атаковать именно подпись в формате JWT.
Как это работает?
  1. Вначале ты захватываешь токен.
  2. Извлекаешь из него зашифрованную подпись.
  3. Запускаешь brute-force атаку с применением hashcat, чтобы подобрать секретный ключ для подписи.
Пример команды для запуска атаки с помощью hashcat:
Bash:
hashcat -m 16500 -a 3 jwt.txt ?l?l?l?l?l?l
Эта команда говорит hashcat'у начать перебирать все возможные комбинации из букв в нижнем регистре (от a до z) в длине 6 символов. На самом деле для слабых секретов это может быть довольно быстрым процессом. Легко подсчитать, сколько времени уйдёт на перебор этих самых слабых комбинаций.
Если секрет был что-то вроде «abc123» - атака может быть успешной уже через несколько минут.

JWK/JKU Injection​

Вот тебе ещё одна трещина в системе, через которую может пролезть атакующий. JWK (JSON Web Key) и JKU (JSON Web Key Set URL) - это механизмы, с помощью которых сервер может получать публичные ключи для проверки подписи JWT. Казалось бы, всё должно быть окей, токен подписан с использованием определённого ключа, а сервер этот ключ извлекает из JWK или JKU. Но если сервер не валидирует, что за ключ он на самом деле забирает, можно натворить дел.
Атакующий может внедрить собственный публичный ключ через заголовок jwk или манипулировать URL в jku.

Embedded JWK attack

Когда сервер использует JWK для валидации подписи, то, по сути, ему нужно верифицировать ключ, который приходит из токена. Проблема возникает, когда сервер не проверяет этот ключ на подлинность, и атакующий может вставить свой собственный.
Например, если в заголовке токена присутствует параметр jwk, то злоумышленник может вставить туда свой собственный ключ, который будет использован для проверки подписи.
Пример:
JSON:
{
  "jwk": {
    "kty": "RSA",
    "kid": "fake-key-id",
    "n": "fake-modulus",
    "e": "AQAB"
  }
}
В этой ситуации вместо того, чтобы использовать корректный ключ для верификации, сервер возьмёт чужой и использует его для проверки подписи. Если сервер плохо проверяет, что за ключ он реально должен использовать, твой токен станет валидным, и система будет думать, что всё окей. А на самом деле ты только что запустил атаку с подменой ключа.
Вот что нужно делать:
  1. Проверяй, что ключ действительно принадлежит тому серверу, с которого ты его получаешь.
  2. Периодически обновляй ключи и не полагайся на незащищённые механизмы.

JKU URL manipulation

Теперь переходим ко второй части - это манипуляция с URL для jku. Если сервер находит ключи для подписи токенов через URL (например, через публичный сервис), то злоумышленник может подставить свой URL, указывая на сервер, который ему выгоден.
К примеру, вместо правильного URL для получения ключа (например, https://valid-key-server.com/.well-known/jwks.json), атакующий может подставить ссылку на свой сервер, и вот тебе, пожалуйста, уже ключи не от сервера, а от злого пользователя.
Пример:
JSON:
{
  "jku": "http://attacker-server.com/fake-jwks.json"
}
Если сервер не проверяет, что за URL он использует для получения публичного ключа, атакующий может создать собственный JSON Web Key Set (JWKS), который будет полностью контролироваться им, и в результате сервер использует эти ключи для проверки подписи.
Пример содержимого фальшивого JWKS, который может подставить атакующий:
JSON:
{
  "keys": [
    {
      "kty": "RSA",
      "kid": "attacker-key-id",
      "n": "attacker-modulus",
      "e": "AQAB"
    }
  ]
}
После того как сервер применит этот ключ для подписи, атакующий получит полный контроль, даже не требуя компрометации секретов. Всё, что нужно - манипулировать URL и подставить свой JWKS.

Проблемы с JWK/JKU Injection

УязвимостьПроблемаКак это эксплуатировать
Embedded JWKПодмена публичного ключа внутри токенаАтакующий вставляет свой ключ в заголовок и получает контроль
JKU URL ManipulationПодмена URL для получения ключаАтакующий манипулирует URL и заставляет сервер использовать свой ключ

5.3. Как защититься?

Как обычно, защита от таких атак - это проверка, проверка и ещё раз проверка. Используй правильные механизмы для проверки ключей и URL:
  1. Проверка источника ключа: убедись, что URL для JKU или JWK всегда указывает на доверенный сервер, и что ты точно знаешь, что именно там за ключи.
  2. Регулярное обновление ключей: не полагайся на один и тот же ключ вечно. Пусть твои ключи обновляются регулярно.
  3. Подписи на сервере: старайся минимизировать использование внешних публичных ключей. Применяй внутренние системы для проверки токенов, если это возможно.

Claim Manipulation​

Когда мы говорим про JWT, то помимо всего прочего, важную роль играют claims - данные, которые передаются в payload. Эти данные могут быть такими, как идентификатор пользователя (sub), его роль (role), издатель токена (iss), аудитория (aud) и другие параметры, которые могут определить, что пользователь может делать в системе. Это тебе не просто случайные строчки - это конкретная информация, которая используется для управления доступом.
А теперь представь, что мы эту информацию просто подменяем. Мы можем поменять sub, повысить роль, а может быть, даже изменить iss или aud в многопользовательской системе. Это и будет манипуляция claims. Простая штука, но если сервер не валидирует эти данные должным образом, система становится уязвимой. И вот тебе сразу два пути атаки: или захват аккаунта, или повышение привилегий.

Sub/Role Escalation

Один из самых очевидных вариантов манипуляции - это подделка поля sub. Злоумышленник может просто изменить идентификатор пользователя на тот, который ему нужен, и сервер ничего не заметит. Получается, что вместо того, чтобы иметь доступ только к своему аккаунту, атакующий может получить доступ к чужому.
Другой вариант - role escalation. Например, в claims может быть указана роль пользователя, и если у нас есть доступ к токену, мы можем спокойно изменить его роль на admin или что-то ещё, что даст нам административные привилегии в системе.
Пример токена:
JSON:
{
  "sub": "12345",
  "role": "user",
  "iat": 1609459200
}
Ты видишь тут sub, который отвечает за идентификатор пользователя, и role, который говорит, что это обычный пользователь. Но что если изменить роль на admin?
JSON:
{
  "sub": "12345",
  "role": "admin",
  "iat": 1609459200
}
Всё, сервер думает, что перед ним администратор, и открывает двери для выполнения операций, доступных только привилегированным пользователям.

Iss/Aud Confusion в Multi-Tenant

Если система работает в многопользовательской среде (multi-tenant), то манипуляции с полями iss (issuer) и aud (audience) могут привести к серьёзным последствиям. Эти поля должны определять, какой сервис создал токен и для кого он предназначен.
Допустим, у нас есть несколько разных пользователей или клиентов в одной системе, и каждый имеет свой собственный набор токенов, которые проверяются на основе этих данных. Но если злоумышленник манипулирует полем iss или aud, он может выдать свой токен за действительный для другого клиента. Например, изменить iss на другой сервис или aud на другую аудиторию и получить доступ к данным, которые ему не принадлежат.
Пример:
JSON:
{
  "sub": "67890",
  "role": "user",
  "iss": "serviceA",
  "aud": "serviceB"
}
Здесь iss указывает на сервис A, а aud - на сервис B. А теперь подменим iss:
JSON:
{
  "sub": "67890",
  "role": "user",
  "iss": "serviceB",
  "aud": "serviceA"
}
Система теперь будет думать, что токен выдан для serviceA и с учётом неправильной аудитории пропустит его, предоставив доступ к данным другого клиента. И вот тебе, пожалуйста, дыры в безопасности.

Манипуляции с claims

Тип манипуляцииОписаниеКак это используется
Sub EscalationИзменение идентификатора пользователяДоступ к чужому аккаунту
Role EscalationПодделка роли пользователяПовышение привилегий, доступ к админ-функциям
Iss ConfusionИзменение поля iss (issuer)Токен становится действительным для другого сервиса
Aud ConfusionИзменение поля aud (audience)Доступ к данным другого клиента

Как защититься?

Самое важное при защите от манипуляций с claims - это валидация. Валидировать каждый claim, особенно в многопользовательских системах, критично. Проблемы начинаются, когда сервер не проверяет правильность данных в claims, доверяя тому, что пришло в токене.
Как защититься:
  1. Проверяй sub: не доверяй только тому, что пришло. Идентификатор пользователя должен быть чётким и проверенным на стороне сервера.
  2. Используй строгие правила для role: роли должны быть жёстко прописаны и валидированы. Не давай системе менять роль без явного подтверждения на сервере.
  3. Проверяй iss и aud: убедись, что эти данные соответствуют нужному сервису или клиенту, иначе злоумышленник может воспользоваться твоими «ошибками» для доступа к чужим данным.
Когда речь идёт о безопасности API и аутентификации, полезно не ограничиваться только протоколами и токенами, а посмотреть, как строится полноценная защита интерфейсов - от тестирования до предотвращения BOLA и Broken Authentication. Читать здесь...

Подведем итоги​

JWT - это всего лишь подписанный JSON. Не серебряная пуля, не встроенная безопасность, не автоматический контроль доступа. Всё, что он реально гарантирует, - целостность. Всё остальное - на совести архитектуры. И вот тут, честно говоря, чаще всего и начинается бардак: алгоритм принимается из header, ключ годами лежит без ротации, токен живёт вечность, claims используются как источник истины. Формально всё работает. По факту - поверхность атаки.

В микросервисной среде токен перестаёт быть просто маркером сессии. Он становится частью доверительной цепочки между сервисами. Один сервис валидирует криво - и дальше по цепочке уже не проверяют. Горизонтальное перемещение, privilege escalation, доступ к чужим tenant-данным - всё это вырастает из маленьких, скучных логических упрощений.

Нормальная реализация JWT - это дисциплина. Жёстко зафиксированный алгоритм. Контролируемая ротация ключей. Короткий TTL. Проверка iss, aud, role, sub не для галочки, а реально, с привязкой к backend-логике. И при необходимости - отзыв через jti. Не сложно. Просто нужно делать до конца, без компромиссов.

Если смотреть на JWT как на формат - он удобный. Если смотреть на него как на часть security boundary - он требовательный. И вот от этого взгляда, по сути, и зависит, станет ли он нормальным механизмом аутентификации или тихой точкой входа для того, кто копает глубже, чем принято ожидать.
 
Последнее редактирование:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab