Содержание статьи
- Аутентификация и авторизация
- Куки и токены
- Куки
- Токены
- Что такое JWT
- Формат JWT
- Header
- Payload
- Signature
- JWT vs JWS vs JWE
- Атаки на JWT-токены
- Зачем атаковать
- Последствия атак
- Почему токены уязвимы
- Burp Suite и JWT
- Неправильная проверка подписей
- Лаба: JWT authentication bypass via unverified signature
- Алгоритм подписи None
- Лаба: JWT authentication bypass via flawed signature verification
- Брутфорс секретов
- Лаба: JWT authentication bypass via weak signing key
- Инъекции в параметры заголовка
- Инъекция через параметр jwk
- Лаба: JWT authentication bypass via jwk header injection
- Инъекция через параметр jku
- Лаба: JWT authentication bypass via jku header injection
- Инъекция через параметр kid
- Лаба: JWT authentication bypass via kid header path traversal
- Атаки algorithm confusion
- Лаба: JWT authentication bypass via algorithm confusion
- Лаба: JWT authentication bypass via algorithm confusion with no exposed key
- Другие интересные векторы
- Чек-лист проверок
- Расширенные проверки
- Автоматизация
- Как защититься
- Выводы
Аутентификация и авторизация
Прежде чем мы перейдем непосредственно к JWT, давай повторим основы и вспомним главные термины, которые неразрывно связаны с современным вебом и информационной безопасностью, а именно — аутентификация и авторизация. Эти знания нам необходимы, чтобы понимать весь дальнейший материал.
Представим, что мы хотим войти в свой аккаунт Facebook. Веб‑приложению нужно понять, что мы владеем этим аккаунтом. Для этого оно просит нас предоставить корректные учетные данные. Обычно это логин и пароль, которые мы вводим на странице входа.
Приложение проверяет предоставленные данные, и, если они верны, мы входим в собственный аккаунт. Этот процесс называется аутентификацией.
info
Аутентификация — проверка подлинности предъявленного пользователем идентификатора, в процессе которой пользователь доказывает, что он действительно тот, за кого себя выдает. Сравнение пароля, введенного пользователем, с паролем, который сохранен в базе данных сервера, — один из примеров аутентификации.
После успешной аутентификации мы заходим на страницу со своим профилем. Веб‑приложение понимает, что пользователь — тот, за кого себя выдает, то есть мы успешно аутентифицированы.
Теперь мы можем отредактировать собственный профиль, изменить имя и фамилию или поменять возраст. Мы не можем редактировать чужой профиль, поэтому прежде, чем разрешить редактирование, приложению нужно убедиться, что запрос пришел от владельца профиля. Процесс проверки прав на осуществление действий называется авторизацией.
info
Авторизация — это процесс проверки, подтверждения или предоставления разрешений, прав доступа и привилегий на выполнение определенных действий. Пример авторизации: пользователь хочет прочитать документ на сайте, приложение проверяет, имеет ли он право читать его.
Аутентификация проходит один раз, когда ты вводишь логин и пароль. Авторизация происходит при каждом запросе после аутентификации, поэтому если допустить ошибку в этой системе, то злоумышленники смогут выполнять действия от имени других пользователей. Например, сделать покупку от их имени или узнать конфиденциальную информацию. Важно знать безопасные и проверенные способы как аутентификации, так и авторизации.
Куки и токены
Сессионные cookie и токены — два важных и взаимозаменяемых механизма безопасности в современном вебе, которые используются в процессах аутентификации и авторизации.
После того как пользователь аутентифицировался, они служат фактором, на основе которого сервер понимает, что запрос отправил определенный пользователь. Без необходимости повторно вводить логин и пароль.
Куки
HTTP Cookie — это небольшие записи, которыми обмениваются клиент (твой браузер) и сервер. Всякий раз при обращении к соответствующему сайту эти данные пересылаются серверу в составе HTTP-запроса.
Куки придуманы давно, в начале девяностых годов. В июне 1994 года Лу Монтулли, сотруднику Netscape Communications, пришла идея использовать их при веб‑соединении.
Куки используются в веб‑приложениях:
- для управления сеансами пользователя и проверки его прав;
- для отслеживания активности и персонализации контента (специальные «рекламные» куки).
Процесс использования кук выглядит следующим образом:
- Браузер отправляет приложению запрос с логином и паролем.
- Приложение проверяет логин и пароль пользователя. Если они совпадают с теми, что хранятся в базе (вместо паролей сравниваются их хеши), то приложение формирует сессию, генерирует сессионную куку и отправляет ее браузеру пользователя.
- Браузер сохраняет куку и отправляет ее вместе с каждым запросом в приложение.
- Приложение получает куку, расшифровывает ее и, если она валидная, выполняет действие от имени авторизованного пользователя.
В итоге нам не нужно вводить пароль каждый раз. Благодаря кукам сервер понимает, что мы уже прошли этот процесс.
info
Сервер не должен хранить куки пользователей ни в базе данных, ни в файловой системе. Они должны использоваться только в рантайме, чтобы ими не могли воспользоваться злоумышленники, которые получат доступ к серверу.
Для присвоения куки пользователю сервер отправляет такой заголовок:
Set-Cookie: <имя cookie>=<значение cookie>
Браузер, в свою очередь, — такой:
Cookie: <имя cookie>=<значение cookie>
Каждый из этих заголовков поддерживает множество атрибутов, которые позволяют снизить риски при возникновении уязвимостей на стороне приложения.
Токены
В современном вебе используются не только куки, но и токены. Это альтернативный и более современный механизм, который имеет свои плюсы и минусы по сравнению с привычными «печеньками».
Например, когда ты хочешь аутентифицироваться на сайте через другой сайт, на котором ты уже аутентифицирован (например, войти в Facebook через Gmail), используются OAuth-токены для межсерверной аутентификации между приложением и провайдером данных.
OAuth — не единственные токены, существует много других форматов:
-
Simple Web Token — набор пар имя — значение в формате кодирования HTML Form, описывает стандартные ключи
Issuer
,Audience
,ExpiresOn
иHMACSHA256
; - Security Assertion Markup Language (SAML) — определяет токены в XML-формате, включающем информацию об эмитенте и субъекте, а также условия для валидации токена. Подпись осуществляется при помощи асимметричной криптографии. Содержат механизм для подтверждения владения токеном;
- JSON Web Token, JWT — третий вид токенов, который мы сейчас подробно рассмотрим.
Что такое JWT
Аббревиатура JWT расшифровывается как JSON Web Token. Стандарт RFC 7519 описывает отправку криптографически подписанных JSON данных (значений key-value) между системами. Теоретически стандарт RFC 7519 допускает отправку любых данных, но в современном вебе чаще используется, чтобы передавать информацию о пользователях для аутентификации, обработки сеансов и контроля доступа.
info
JWT (JSON Web Token) — это специальный формат токена, который позволяет безопасно передавать данные между клиентом и сервером. В качестве клиента может выступать браузер пользователя или мобильное приложение, сервера — виртуальная машина или выделенный компьютер с запущенным веб‑приложением.
JWT-токены были придуманы гораздо позже, чем куки. В 2011 году была сформирована группа JOSE (JSON Object Signing and Encryption group), призванная стандартизировать механизм защиты целостности, шифрования, а также формат ключей и алгоритмов идентификации для обеспечения совместимости служб безопасности, использующих формат JSON. К 2013 году в открытом доступе появились неофициальные наброски и примеры использования идей этой группы. Официально был стандартизован группой IETF в мае 2015 года.
Токены, как и сессионные куки, создаются сервером либо в начале взаимодействия пользователя с сайтом или приложением, либо после аутентификации пользователя в приложении. Затем они отправляются пользователю как часть ответа сервера — либо в HTTP-заголовке Set-Cookie
, либо в заголовке Bearer
. От того, где будет записан токен, зависит, как строится дальнейшая модель безопасности.
Аутентификация и авторизация с использованием JWT-токена устроена следующим образом:
- Браузер отправляет запрос приложению с логином и паролем.
- Приложение проверяет логин и пароль и, если они верны, генерирует JWT-токен, который затем отправляет браузеру. При генерации JWT-токена веб‑приложение ставит подпись секретным ключом, который хранится только в приложении. Может использоваться как симметричное шифрование, так и асимметричное.
- Браузер сохраняет JWT-токен и отправляет его вместе с каждым запросом в приложение.
- Приложение проверяет JWT-токен и, если он валидный (то есть он правильно сформирован и данные не были изменены), выполняет действие от имени авторизованного пользователя.
Все данные, которые нужны серверу, хранятся на стороне клиента в самом JWT. Это делает JWT популярным выбором для высокораспределенных веб‑сайтов, где пользователям необходимо беспрепятственно взаимодействовать с несколькими внутренними серверами.
Безопасность коммуникации между веб‑браузером и веб‑приложением строится на том, что токены генерируются и подписываются только со стороны веб‑приложения. Злоумышленник в теории не сможет подделать токен, так как не знает секретный ключ, который используется для подписи токена.
При подписи токена используется шифрование. С помощью подписи веб‑приложение проверяет, что токен действительно был сгенерирован им. Алгоритмы шифрования могут быть разными, например HS256 — HMAC с SHA-256.
Формат JWT
JWT-токен состоит из трех частей, которые разделены точкой:
- Header (заголовок) — информация о токене, тип токена и алгоритм шифрования;
- Payload (полезные данные) — данные, которые мы хотим передать в токене. Например, имя пользователя, его роль, истекает ли токен. Эти данные представлены в виде JSON-объекта;
- Signature (подпись) — подпись токена, которая позволяет проверить, что токен не был изменен.
Формат токена:
header.payload.signature
Каждая из этих частей обычно кодируется в Base64 для передачи без повреждений по сети. Помимо этого, используется облегченный вариант URL-кодирования. Свойственный Base64 знак равенства усекается. Плюс заменяется минусом, а слеш (/
) — подчеркиванием, чтобы не возникло коллизий.
Пример токена:
eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA
Header
Заголовок обычно состоит из JSON-объекта с двумя свойствами:
- тип токена, в нашем случае — JWT;
- алгоритм шифрования, в нашем случае — HMAC SHA-256.
Далее этот JSON-объект хешируется с помощью Base64URL-кодирования, чтобы представить его в виде компактной строки.
Таким образом, в нашем примере заголовок JWT-токена имеет следующее значение:
{ "kid": "9136ddb3-cb0a-4a19-a07e-eadf5a44c8b5", "alg": "RS256"}
Также напомню, что это не шифрование, поэтому его можно раскодировать из консоли Linux:
$ echo eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 | base64 -d {"typ":"JWT","alg":"HS256"}
Или из консоли JavaScript в браузере:
>> atob("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9") "{"typ":"JWT","alg":"HS256"}"
Вариант расшифровки через PowerShell:
PS C:\> [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9")) {"typ":"JWT","alg":"HS256"}
Можно даже из CMD, но чуть сложнее:
C:\>echo eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 > file.txt && certutil -decode -f file.txt outfile.txt && type outfile.txt
Input Length = 40
Output Length = 27
CertUtil: -decode command completed successfully.
{"typ":"JWT","alg":"HS256"}
Payload
Вторая часть токена — это полезная нагрузка в виде JSON-объекта. Она содержит данные об авторизованном пользователе. Значение этой части JWT-токена разное в разных веб‑приложениях. Мы можем записать здесь любые публичные данные, которые могут быть полезны при авторизации.
Как и заголовок JWT-токена, полезная нагрузка хешируется с помощью Base64URL-кодирования для представления в виде компактной строки.
В нашем примере полезная нагрузка JWT-токена имеет следующее значение:
{ "iss": "portswigger", "exp": 1648037164, "name": "Carlos Montoya", "sub": "carlos", "role": "blog_author", "email": "carlos@carlos-montoya.net", "iat": 1516239022}
Названия некоторых полей могут показаться непонятными с первого взгляда. Здесь говорится о том, кто выписал токен (iss
), на кого он выписан (sub
и name
) и каков его срок жизни (exp
), по прошествии которого сервер должен считать его невалидным. Эти данные может изменить любой человек, затем закодировать в Base64 и вставить на место изначальных. Поэтому вся безопасность зависит напрямую от криптографической подписи.
www
При составлении полей полезной нагрузки разработчики стараются учитывать имена из документации IANA (Internet Assigned Numbers Authority), чтобы избежать конфликтов имен с общепринятыми нормами.
Основная причина, почему названия полей в полезной нагрузке JWT-токена пишутся сокращенно, — это уменьшение размера токена после шифрования.
Signature
Чтобы создать подпись, мы должны взять закодированный в Base64 заголовок, закодированную в Base64 полезную нагрузку, секретную строку и зашифровать эти данные. При этом нужно использовать тот же алгоритм шифрования, который указан в заголовке JWT-токена.
Вот пример для HS256:
HMACSHA256( base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
Процесс создания Signature предполагает наличие секретного ключа подписи. Подпись позволяет серверам убедиться в том, что данные, содержащиеся в токене, не были подделаны кем‑то другим с момента его выпуска.
www
jwt.io — онлайн‑дебаггер, который автоматизирует декодирование, проверку и генерацию JWT-токенов. Его можно использовать для собственных экспериментов.
Поскольку подпись напрямую зависит от остальной части токена, изменение одного байта заголовка или полезной нагрузки приводит к тому, что она перестает быть валидной. Не зная секретного ключа, невозможно сгенерировать правильную подпись для токена.
JWT vs JWS vs JWE
Исходная спецификация JWT очень ограниченна. Она определяет только формат представления информации в виде объекта JSON, который может быть передан между двумя сторонами. На практике JWT практически не используется как отдельная сущность.
Спецификация JWT была расширена спецификациями JSON Web Signature (JWS) — RFC 7515 и JSON Web Encryption (JWE) — RFC 7516, которые определяют конкретные способы реализации JWT.
Другими словами, когда мы говорим про JWT-токены в контексте веба, мы на самом деле имеем в виду либо JWS-токены, либо токены JWE. Токены JWE и JWS очень похожи, первые зашифрованы, а вторые закодированы.
Атаки на JWT-токены
Зачем атаковать
Атаки на JWT-токены подразумевают отправку злоумышленником измененных токенов на сервер для достижения своей цели. Как правило, эта цель заключается в том, чтобы обойти аутентификацию и контроль доступа, выдавая себя за другого пользователя, который уже прошел аутентификацию.
Последствия атак
Последствия JWT-атак обычно серьезны. Если злоумышленнику удается создать собственные действительные токены с произвольными значениями, он может повысить свои привилегии или выдать себя за другого пользователя, получив полный контроль над его учетной записью.
Почему токены уязвимы
Уязвимости, позволяющие подделывать JWT-токены, обычно возникают из‑за несовершенной обработки этих самых токенов в самом приложении. Разные спецификации, связанные с JWT-токенами, относительно гибки по своей конструкции и позволяют разработчикам сайтов самостоятельно определять многие детали реализации. В результате разработчики могут допустить уязвимость даже при использовании библиотек с усиленной защитой.
Недостатки реализации, как правило, подразумевают, что подпись у токенов не проверяется должным образом. Это позволяет злоумышленникам подделывать подписанные данные.
Даже если подпись проверена надежно, можно ли ей доверять, в значительной степени зависит от того, действительно ли никому не известен секретный ключ сервера. Если этот ключ каким‑то образом утекает либо его можно угадать или перебрать, злоумышленник может сгенерировать действительную подпись для любого произвольного токена, что поставит под угрозу все приложение.
Burp Suite и JWT
Сейчас мы перейдем к рассмотрению большинства уязвимостей, которые могут возникнуть при работе с JWT-токенами. Мы будем проверять их на практике, поэтому советую зарегистрироваться на сайте PortSwigger и решать лаборатории вместе со мной.
info
PortSwigger WebSecurity Academy — бесплатная академия разработчиков Burp Suite (популярного инструмента, используемого пентестерами) для обучения безопасности.
Кроме того, качай Burp Suite и ставь плагин JWT Editor, который поможет нам подписывать JWT-токены и реализовать некоторые атаки при прохождении.
Неправильная проверка подписей
Вместо одной функции вызвать другую — вот первая ошибка, которую можно допустить при проверке подписи. Звучит нелепо, но ее действительно легко совершить — особенно если ты только что познакомился с токенами и работаешь с ними впервые.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»