Перейти к основному содержанию

CTF — JWT - Unsecure File Signature

CTF

Всем привет, у меня полночь, так что самое время решить задачку на информационную безопасность web-серверов. Сегодня задачка с портала root-me.org, называется "JWT - Unsecure File Signature". За решение задачки дают 25 баллов, ближе к среднему уровню.

ctf

Из названия становится понятно, что работать будем с JWT, а слабым местом будет... пока непонятно что. Нам уже попадались задачка с JWT, так что базовые понятия нам уже знакомы:

CTF — JSON Web Token (JWT) - Introduction

CTF — JSON Web Token (JWT) - Weak secret

CTF — JWT - Revoked token

JSON Web Token (JWT) — это токен доступа для аутентификации в клиент-серверных приложениях. Представляет собой строку вида "header.payload.signature". Где:

  • header — заголовок. JSON объект в формате Base64, в котором передаются данные для описания самого токена, например, {"typ":"JWT","alg":"HS512"}.
  • payload — полезная нагрузка. JSON объект в формате Base64, в котором передаются данные пользователя, например, {"username":"v.pupkin","state":"superadmin"}.
  • signature — подпись. В формате Base64. Вычисляется из header + payload + secret, где secret — секретный ключ известный web серверу и серверу аутентификации.

JWT выглядит так:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1ZXN0In0.OnuZnYMdetcg7AWGV6WURn8CFSfas6AQej4V9M13nsk

Ссылки

Base64 encode/decode

Решение

Для начала ознакомимся с условием задачи, комментарий весёлый:

Бывший админ Root Me пытается поднять копию сайта после того, как выложил в открытый доступ решения задач. Попытайтесь выяснить, не заныкал ли он где-нибудь парочку флагов на своём новом сайте.

И тут я обратил внимание на описание под баллами: "(K)ind (I)dentification (D)ance". Понятно что речь не про пляски с бубном, а про KID, есть такая штука у JWT, запомним.

Переходим на страницу задания:

http://challenge01.root-me.org:59081/

ctf

Какая красота! Да, у бывшего админа с дизайном большие проблемы. Я глаза сломал, проще сориентироваться в коде страницы.

ctf

У нас имеются следующие странички:

  • /admin (чую, что нужно будет попасть именно сюда)
  • static/challs/htmllecture.html
  • static/challs/irc.html
  • static/challs/obfu6.html
  • static/https://www.youtube.com/watch?v=ZYrmrflWBmY
  • static/challs/samboxv5.html

Начнём с конца.

  • static/challs/samboxv5.html

ctf

Эта страничка должна изображать что-то типа ошибки. Но, во-первых, это статичная HTML страница, во-вторых, код я посмотрел: фигня какая-то. Кручу-верчу, запутать хочу. Исследуем дальше.

  • static/https://www.youtube.com/watch?v=ZYrmrflWBmY

Здесь кривой URL, смысла нет заходить.

  • static/challs/obfu6.html

ctf

Даже я понятия не имею, во что я вляпался

Не интересно, смотрим дальше.

  • static/challs/irc.html

ctf

Зайди на IRC (irc.root-me.org:6697) и напиши "трус" в #root-me

Делать мне больше нечего. Пропускаем.

  • static/challs/htmllecture.html

ctf

Ура, флаг! Но он не работает, флаг, да не тот. К тому же, это было бы слишком просто.

  • /admin

ctf

{"Unauthorized":"You are not admin !"}

Похоже, что это единственная страница, которая хоть как-то работает. Её и будем смотреть. Посмотрим, если ли у нас какая-то сессия в куки:

ctf

Нам выдали токен:

eyJhbGciOiJIUzI1NiIsImtpZCI6ImI5MDFiYjI0LTcwMGItNGNjNi1hNzFhLWNiMjA3YWI2MTMxMyIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ3Vlc3QiLCJpYXQiOjE2ODIyNjc5Njh9.6f_dB-pi-D5D9PpWQI4aXSxvbVHA19pAZbm-4DXoWNU

Это классический JWT. Мы можем прочитать первую и вторую часть токена, header и payload. Для этого нам потребуется Base64 декодер. Я один такой написал:

Base64 encode/decode

ctf

Берём заголовок eyJhbGciOiJIUzI1NiIsImtpZCI6ImI5MDFiYjI0LTcwMGItNGNjNi1hNzFhLWNiMjA3YWI2MTMxMyIsInR5cCI6IkpXVCJ9 и декодируем, поучается:

{"alg":"HS256","kid":"b901bb24-700b-4cc6-a71a-cb207ab61313","typ":"JWT"}

Из заголовка становится понятно, что для получения подписи используется алгоритм шифрования HS256. Но самое интересное, что у нас имеется параметр kid, который в предыдущих задачах нам не встречался. И мы помним описание задачи: "(K)ind (I)dentification (D)ance". Нам явно подсказывают направление поиска. kid (Key ID, идентификатор ключа) — параметр заголовка описан в стандарте RFC-7515, но формат этого поля строго не определен. Поэтому разработчики могут интерпретировать его так, как им хочется, что зачастую приводит к различным ошибкам. Хе-хе.

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

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

Для проверки токена может использоваться ключ с идентификатором 123, который хранится в БД. Это поле может быть уязвимо к SQL-инъекции:

{
 "alg" : "HS256",
 "typ" : "JWT",
 "kid" : "123' UNION SELECT 'SECRET' -- 1"
}

Мы меняем идентификатор ключа с помощью инъекции и задаём в качестве подписи строку SECRET. Далее сможем сгенерировать верный JWT с нашей новой подписью, и настоящая подпись нам даже не понадобится.

Вместо базы данных проверка токена может использовать содержимое какого-либо секретного файла, например, keys/secret.key.

{
 "alg" : "HS256",
 "typ" : "JWT",
 "kid" : "keys/secret.key"
}

Путь к файлу может плохо валидироваться, тогда можно использовать уязвимость вида Path Traversal.

Path Traversal — получение доступа к файлам и каталогам, которые расположены вне пределов, определенных конфигурацией. Путем манипулирования переменными, которые указывают на положение файлов, с помощью последовательностей "../" и их вариаций, может быть получен доступ к любым файлам и каталогам, имеющимся в системе.

{
 "alg" : "HS256",
 "typ" : "JWT",
 "kid" : "../../../images/hello.png"
}

И снова можно подписать JWT с использованием содержимого общедоступного файла hello.png. Или любого другого доступного файла. Если файла нет, то можно использовать пустую подпись, отправив код в /dev/null:

../../../dev/null

Продолжим разбирать имеющийся JWT токен. Берём полезную нагрузку eyJ1c2VyIjoiZ3Vlc3QiLCJpYXQiOjE2ODIyNjc5Njh9 и декодируем, поучается:

{"user":"guest","iat":1682267968}

Ага, в полезной нагрузке передаётся имя пользователя guest. Понятно, что нужно заменить его на admin.

Предположение следующее: нам нужно

  • проверить как работает валидация kid,
  • подменить подпись,
  • сменить имя пользователя на admin,
  • сгенерировать новый JWT,
  • подставить его в куки и посмотреть что получится.

Будем использовать ресурс https://jwt.io.

Полезная нагрузка у нас будет такая:

{"user":"admin","iat":1682267968}

Админ покруче чем гость.

Предполагаем, что kid проверяется через БД, попробуем сделать инъекцию:

{"alg":"HS256","kid":"b901bb24-700b-4cc6-a71a-cb207ab61313' union select 'SECRET' -- 1","typ":"JWT"}

И подпись "SECRET".

ctf

Подставляем полученный JWT в куки.

ctf

И смотрим на страничку /admin.

ctf

Файл не найден. Ага, значит БД не используется. Код дописывает в начале папку keys, мы не знаем ни одного файла в этой папке, и не можем использовать один из тех статических файлов, которые мы знаем. Нам нужно выйти за пределы директории keys. Попробуем Path Traversal и используем /dev/null.

ctf

Проверяем.

ctf

Хм, какая-то защита от Path Traversal стоит. Точки вырезались. Пробуем заменить точку на "%2e".

ctf

Не помогло. Подождите, в прошлый раз вместе с точками вырезались слеши. Это значит, что защита удаляет сочетание "../". Так можно же схитрить, а если мы напишем так:

ctf

Снова файл не найден, стоит защита от /dev/null. НО! Мы смогли сформировать сочетание для Path Traversal! Формируем путь к одной из известных нам страничек, проще взять static/challs/htmllecture.html.

Формируем заголовок:

{"alg":"HS256","kid":"....//static/challs/htmllecture.html","typ":"JWT"}

Полезная нагрузка:

{"user":"admin","iat":1682267968}

Подпись берём из static/challs/htmllecture.html:

FLAG: ROUTEMI{c_le_premier_chall}

ctf

Проверяем.

ctf

Мы успешно залогинились под админом и получили флаг.

Валидируем.

ctf

Флаг подходит, зарабатываем 25 очков. Нам даже не понадобилось искать секретную подпись, мы сформировали её сами.

Безопасность

  • Защититься от подобных атак просто: всегда валидируйте полученные от пользователя данные, даже если они были получены в виде JWT.
  • Вырезание строки "../" от Path Traversal не спасает, всегда можно вставить "....//", тогда после вырезания получится именно то что нужно злоумышленнику. Мне кажется, что и /dev/null можно было использовать, добавив уровней вложенности.
  • Даже если защититься от Path Traversal, но засветить содержимое хоть одного файла в директории с ключами, то злоумышленник сможет повторить фокус.
  • Если на сайте есть уязвимость, позволяющая загрузить файл в папку с ключами, то вы понимаете к чему это приведёт. Даже если файл неисполняемый.

Теги

 

Похожие материалы

CTF — PHP - assert()

Всем привет, сегодня воскресенье, поэтому займёмся чем-нибудь интересным. Например, порешаем задачки на информационную безопасность web-серверов. Сегодня задачка с портала root-me.org, называется "PHP - assert()".

Теги