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

Лабораторные работы
Если вы уже знакомы с базовыми концепциями атак на JWT и просто хотитепопрактиковаться в их эксплуатации на реалистичных, намеренно уязвимых целях, вы можете получить доступ ко всем лабораторным работам по этой теме по ссылке ниже.
Что такое JWT?
JSON Web Token — стандартизованный формат передачи криптографически подписанных JSON‑данных между системами. Теоретически они могут содержать любые данные, но чаще всего используются для передачи сведений о пользователях в рамках механизмов аутентификации, обработки сессий и контроля доступа.
В отличие от классических сессионных токенов, все данные, которые нужны серверу, хранятся на стороне клиента — внутри самого JWT. Это делает JWT популярным выбором для распределённых сайтов, где пользователям нужно бесшовно взаимодействовать с несколькими серверными узлами.
Формат JWT
JWT состоит из трёх частей: заголовка (header), полезной нагрузки (payload) и подписи (signature). Они разделены точками, как показано в примере ниже:
Части header и payload — это просто закодированные в base64url JSON‑объекты. Заголовок содержит метаданные о самом токене, а полезная нагрузка о пользователе. Например, можно декодировать пейлоад из токена выше и увидеть следующие утверждения:
В большинстве случаев эти данные легко прочитать или изменить любому, у кого есть доступ к токену. Поэтому безопасность любого механизма на базе JWT в значительной степени опирается на криптографическую подпись.
Подпись JWT
Сервер, выпускающий токен, обычно генерирует подпись путём хеширования заголовка и полезной нагрузки. В некоторых случаях полученный хеш также шифруется. В любом случае процесс включает секретный ключ подписи. Этот механизм позволяет серверам убедиться, что данные в токене не были изменены после выпуска:
Поскольку подпись вычисляется напрямую из остальных частей токена, изменение хотя бы одного байта в header или payload приводит к несовпадению подписи.
Не зная секретного ключа сервера, невозможно сгенерировать корректную подпись для данных header/payload.
JWT vs JWS vs JWE
Спецификация JWT на самом деле очень ограничена. Она определяет только формат представления информации как JSON‑объекта, передаваемого между двумя сторонами. На практике JWT редко используются сами по себе. Спецификацию JWT расширяют спецификации JSON Web Signature (JWS) и JSON Web Encryption (JWE), которые определяют конкретные способы реализации JWT.

Иными словами, JWT обычно является либо JWS, либо JWE токеном. Когда говорят «JWT», почти всегда имеют в виду JWS токен. JWE очень похожи, за исключением того, что содержимое токена шифруется, а не просто кодируется.
Что такое атаки на JWT?
Атаки на JWT предполагают отправку пользователем модифицированных JWT на сервер для достижения злонамеренной цели. Как правило, целью является обход аутентификации и контроля доступа путём выдачи себя за другого, уже аутентифицированного пользователя.
Каковы последствия атак на JWT?
Влияние атак на JWT обычно весьма серьёзно. Если злоумышленник может создавать собственные валидные токены с произвольными значениями, он может повысить свои привилегии или выдавать себя за других пользователей, получая полный контроль над их аккаунтами.
Как возникают уязвимости в JWT?
Уязвимости JWT обычно возникают из‑за некорректной обработки JWT в самом приложении. Разные спецификации, связанные с JWT, по замыслу достаточно гибкие, предоставляя разработчикам свободу в деталях реализации. Это может привести к ненамеренным уязвимостям даже при использовании проверенных библиотек.
Как правило, изъяны реализации означают, что подпись JWT проверяется некорректно. Это позволяет атакующему подменять значения, передаваемые приложению через payload токена. Даже если подпись проверяется надёжно, её доверенность целиком зависит от того, остаётся ли секретный ключ сервера секретным. Если ключ был раскрыт, либо может быть угадан или подобран перебором, атакующий способен генерировать валидную подпись для любого токена, компрометируя весь механизм.
Работа с JWT в Burp Suite
Если вы ранее не работали с JWT, рекомендуем перед лабораторными ознакомиться с соответствующими возможностями Burp Suite.
Эксплуатация некорректной проверки подписи JWT
По замыслу серверы обычно не хранят информацию о выданных JWT. Каждый токен — самодостаточная сущность. У этого есть плюсы, но и фундаментальная проблема: сервер не знает исходного содержимого токена и даже его исходной подписи. Поэтому если сервер неправильно проверяет подпись, ничто не мешает атакующему произвольно менять остальные части токена.
Например, рассмотрим JWT со следующими содержимым:
Если сервер идентифицирует сессию по значению username, то его изменение может позволить атакующему выдавать себя за других пользователей. Аналогично, если значение isAdmin используется для контроля доступа, это даёт простой вектор повышения привилегий.
В лабораторных работах вы увидите примеры того, как такие уязвимости выглядят в реальных приложениях.
Принятие произвольных подписей
Библиотеки JWT обычно предоставляют один метод для верификации токенов и другой — только для их декодирования. Например, библиотека Node.js jsonwebtoken имеет методы verify() и decode().
Иногда разработчики путают эти методы и передают входящие токены лишь в decode(). Фактически это означает, что приложение вовсе не проверяет подпись.
Принятие токенов без подписи
Среди прочего, заголовок JWT содержит параметр alg. Он сообщает серверу, какой алгоритм использовался для подписи токена и, следовательно, какой алгоритм нужно применить для проверки подписи.
Это по своей природе проблематично, поскольку сервер вынужден доверять контролируемому пользователем вводу из токена, который на этом этапе никак не проверен. Иными словами, атакующий может напрямую влиять на то, как сервер проверяет доверенность токена.
JWT могут быть подписаны разными алгоритмами, но также могут вообще не иметь подписи. В этом случае alg устанавливается в none, что означает так называемый небезопасный JWT. Из‑за очевидных рисков серверы обычно отклоняют токены без подписи. Однако такие фильтры опираются на строковый разбор, поэтому иногда их удаётся обойти классическими техниками обфускации, вроде смешанного регистра и неожиданных кодировок.
Brute force секретных ключей
Некоторые алгоритмы подписи, такие как HS256 (HMAC + SHA‑256), используют произвольную строку в качестве секретного ключа. Как и для пароля, критично, чтобы этот секрет было трудно угадать или подобрать перебором. Иначе злоумышленник сможет создавать JWT с любым header/payload и затем пересчитывать подпись с помощью ключа, получая валидную подпись.
При реализации приложений с JWT разработчики иногда забывают сменить значения по умолчанию для секретов. Они могут даже скопировать пример кода из сети и забыть заменить пример захардкоженного секрета. В подобных случаях атакующий сможет легко подобрать секретный ключ с помощью словаря известных секретов.
Подбор секретов с помощью hashcat
Мы рекомендуем использовать hashcat для подбора секретных ключей. Его можно установить вручную, также он предустановлен в Kali Linux.
Вам нужен валидный, подписанный JWT от целевого сервера и словарь известных секретов. Затем выполните команду, передав JWT и словарь аргументами:
Hashcat подписывает header и payload JWT каждым секретом из словаря и сравнивает полученную подпись с исходной подписью сервера. Если подписи совпадают, hashcat выводит найденный секрет в формате:
Поскольку hashcat работает локально и не шлёт запросы на сервер, процесс крайне быстрый даже с огромными словарями.
Определив секретный ключ, вы можете использовать его для генерации валидной подписи для любого JWT с нужным вам содержимым. О том, как пересчитать подпись изменённого JWT в Burp Suite, см. раздел Editing JWTs.
Если сервер использует крайне слабый секрет, его можно подобрать посимвольно, без словаря.
Инъекции параметров заголовка JWT
Согласно спецификации JWS, обязательным параметром заголовка является только alg. На практике заголовки JWT (также известные как JOSE‑заголовки) часто содержат и другие параметры. Особенно интересны для атакующих:
jwk(JSON Web Key) — встраивает JSON‑объект, представляющий ключ.jku(JSON Web Key Set URL) — указывает URL, откуда сервер может получить набор ключей, содержащий нужный ключ.kid(Key ID) — идентификатор ключа, который помогает серверу выбрать правильный ключ, если их несколько. В зависимости от формата ключа может соответствовать параметруkidв самом ключе.
Как видно, эти контролируемые пользователем параметры сообщают серверу, какой ключ использовать для проверки подписи. Ниже рассмотрим как можно эксплуатировать это, чтобы внедрять модифицированные JWT, подписанные произвольным ключом вместо серверного.
Внедрение самоподписанных JWT через параметр jwk
Спецификация JSON Web Signature (JWS) описывает опциональный параметр заголовка jwk, позволяющий серверам встраивать свой открытый ключ прямо в токен в формате JWK.
Пример заголовка:
Идеально, если сервер проверяет подписи только с ограниченным белым списком открытых ключей. Однако из‑за неверной конфигурации серверы могут использовать любой ключ, встроенный через jwk.
Вы можете эксплуатировать это, подписав модифицированный JWT своим приватным RSA‑ключом и встроив соответствующий открытый ключ в jwk заголовок.
Хотя jwk можно добавить/изменить вручную в Burp, расширение JWT Editor предлагает удобную функцию для тестирования этой уязвимости:
После загрузки расширения перейдите на вкладку Burp: JWT Editor Keys.
Отправьте запрос, содержащий JWT, в Burp Repeater.
В редакторе сообщений переключитесь на вкладку JSON Web Token и измените пейлоад как вам нужно.
Нажмите Attack и выберите Embedded JWK. При запросе укажите сгенерированный RSA‑ключ.
Отправьте запрос и посмотрите на ответ сервера.
Атаку можно выполнить и вручную, добавив jwk самостоятельно. Но возможно, понадобится обновить параметр заголовка kid, чтобы он совпадал со значением kid встроенного ключа. Расширение позволяет делать это автоматически.
Внедрение самоподписанных JWT через параметр jku
Вместо встраивания открытых ключей напрямую через jwk некоторые серверы позволяют использовать параметр заголовка jku (JWK Set URL), указывающий на JWK Set с ключами. При проверке подписи сервер получает нужный ключ по этому URL.
Такие наборы ключей иногда публикуются по стандартной конечной точке /.well-known/jwks.json.
Более защищённые сайты получают ключи только с доверенных доменов, но иногда можно воспользоваться расхождениями в разборе URL, чтобы обойти такую фильтрацию. Некоторые примеры разбирались в теме про SSRF.
Внедрение самоподписанных JWT через параметр kid
Сервер может использовать несколько криптографических ключей для подписания разных типов данных, не только JWT. Поэтому заголовок JWT может содержать параметр kid (Key ID), помогающий серверу определить, какой ключ использовать для проверки подписи.
Ключи верификации часто хранятся как JWK Set. В таком случае сервер может просто искать JWK с тем же kid, что и в токене. Однако спецификация JWS не определяет структуру этого идентификатора — это произвольная строка на усмотрение разработчика. Например, kid может ссылаться на запись в базе данных или даже имя файла.
Если параметр уязвим к обходу каталогов, атакующий может заставить сервер использовать произвольный файл из файловой системы как ключ верификации.
Это особенно опасно, если сервер также поддерживает JWT, подписанные симетричным алгоритмом. Тогда атакующий может направить kid на предсказуемый статичный файл и подписать JWT секретом, совпадающим с содержимым этого файла.
Теоретически можно использовать любой файл, но один из простейших методов — задействовать /dev/null, присутствующий на большинстве систем Linux. Поскольку это пустой файл, чтение из него возвращает пустую строку. Следовательно, подпись токена пустой строкой даст валидную подпись.
Если сервер хранит ключи верификации в базе данных, параметр заголовка kid также может быть вектором для атак SQL‑инъекции.
Другие интересные параметры заголовка JWT
cty(Content Type) — иногда используется для объявления типа содержимого в пейлоаде JWT. Обычно опускается в заголовке, но библиотека парсинга может его поддерживать. Если вам удалось обойти проверку подписи, можно попробовать внедрить заголовокcty, изменив тип содержимого наtext/xmlилиapplication/x-java-serialized-object, что потенциально открывает новые векторы для атак XXE и десериализации.x5c(X.509 Certificate Chain) — иногда используется для передачи X.509‑сертификата открытого ключа или цепочки сертификатов ключа, которым был подписан JWT. Этот параметр можно использовать для внедрения самоподписанных сертификатов, аналогично атакам с внедрением заголовка jwk, описанными выше. Из‑за сложности формата X.509 и его расширений их парсинг также может порождать уязвимости. Подробнее: CVE-2017-2800 и CVE-2018-2633.
Путаница алгоритмов JWT
Даже если сервер использует надёжные секреты, которые нельзя подобрать, иногда всё ещё можно подделать валидные JWT, подписав токен алгоритмом, который разработчики не предусмотрели. Это называется атакой путаницы алгоритмов (algorithm confusion).
Как предотвратить атаки на JWT
Защитить сайты от многих описанных атак можно следующими мерами верхнего уровня:
Используйте актуальную библиотеку для обработки JWT и убедитесь, что разработчики понимают принцип её работы и связанные риски. Современные библиотеки уменьшают шансы ненамеренно внедрить небезопасную логику, но это не панацея из‑за гибкой спецификаций.
Проводите надёжную проверку подписи всех получаемых JWT, учитывая крайние случаи, например токены, подписанные неожиданными алгоритмами.
Принудительно ограничьте белым списком допустимые хосты для заголовка
jku.Убедитесь, что вы не уязвимы к обходу каталогов или SQL‑инъекциям через параметр заголовка
kid.
Дополнительные лучшие практики работы с JWT
Хотя это не строго необходимо для предотвращения уязвимостей, рекомендуем соблюдать следующие практики при использовании JWT:
Всегда устанавливайте срок истечения для любых выдаваемых токенов.
По возможности избегайте передачи токенов в параметрах URL.
Включайте объявление
audили аналогичное, чтобы указать предполагаемого получателя токена. Это предотвращает его использование на других сайтах.Позвольте серверу‑эмитенту отзывать токены (например, при выходе из системы).
Last updated