Всем привет, у меня полночь, так что самое время решить задачку на информационную безопасность web-серверов. Сегодня задачка с портала root-me.org, называется "JWT - Unsecure File Signature". За решение задачки дают 25 баллов, ближе к среднему уровню.
Из названия становится понятно, что работать будем с JWT, а слабым местом будет... пока непонятно что. Нам уже попадались задачка с JWT, так что базовые понятия нам уже знакомы:
CTF — JSON Web Token (JWT) - Introduction
CTF — JSON Web Token (JWT) - Weak secret
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
Ссылки
Решение
Для начала ознакомимся с условием задачи, комментарий весёлый:
Бывший админ Root Me пытается поднять копию сайта после того, как выложил в открытый доступ решения задач. Попытайтесь выяснить, не заныкал ли он где-нибудь парочку флагов на своём новом сайте.
И тут я обратил внимание на описание под баллами: "(K)ind (I)dentification (D)ance". Понятно что речь не про пляски с бубном, а про KID, есть такая штука у JWT, запомним.
Переходим на страницу задания:
http://challenge01.root-me.org:59081/
Какая красота! Да, у бывшего админа с дизайном большие проблемы. Я глаза сломал, проще сориентироваться в коде страницы.
У нас имеются следующие странички:
- /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
Эта страничка должна изображать что-то типа ошибки. Но, во-первых, это статичная HTML страница, во-вторых, код я посмотрел: фигня какая-то. Кручу-верчу, запутать хочу. Исследуем дальше.
- static/https://www.youtube.com/watch?v=ZYrmrflWBmY
Здесь кривой URL, смысла нет заходить.
- static/challs/obfu6.html
Даже я понятия не имею, во что я вляпался
Не интересно, смотрим дальше.
- static/challs/irc.html
Зайди на IRC (irc.root-me.org:6697) и напиши "трус" в #root-me
Делать мне больше нечего. Пропускаем.
- static/challs/htmllecture.html
Ура, флаг! Но он не работает, флаг, да не тот. К тому же, это было бы слишком просто.
- /admin
{"Unauthorized":"You are not admin !"}
Похоже, что это единственная страница, которая хоть как-то работает. Её и будем смотреть. Посмотрим, если ли у нас какая-то сессия в куки:
Нам выдали токен:
eyJhbGciOiJIUzI1NiIsImtpZCI6ImI5MDFiYjI0LTcwMGItNGNjNi1hNzFhLWNiMjA3YWI2MTMxMyIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ3Vlc3QiLCJpYXQiOjE2ODIyNjc5Njh9.6f_dB-pi-D5D9PpWQI4aXSxvbVHA19pAZbm-4DXoWNU
Это классический JWT. Мы можем прочитать первую и вторую часть токена, header и payload. Для этого нам потребуется Base64 декодер. Я один такой написал:
Берём заголовок 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".
Подставляем полученный JWT в куки.
И смотрим на страничку /admin.
Файл не найден. Ага, значит БД не используется. Код дописывает в начале папку keys, мы не знаем ни одного файла в этой папке, и не можем использовать один из тех статических файлов, которые мы знаем. Нам нужно выйти за пределы директории keys. Попробуем Path Traversal и используем /dev/null.
Проверяем.
Хм, какая-то защита от Path Traversal стоит. Точки вырезались. Пробуем заменить точку на "%2e".
Не помогло. Подождите, в прошлый раз вместе с точками вырезались слеши. Это значит, что защита удаляет сочетание "../". Так можно же схитрить, а если мы напишем так:
Снова файл не найден, стоит защита от /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}
Проверяем.
Мы успешно залогинились под админом и получили флаг.
Валидируем.
Флаг подходит, зарабатываем 25 очков. Нам даже не понадобилось искать секретную подпись, мы сформировали её сами.
Безопасность
- Защититься от подобных атак просто: всегда валидируйте полученные от пользователя данные, даже если они были получены в виде JWT.
- Вырезание строки "../" от Path Traversal не спасает, всегда можно вставить "....//", тогда после вырезания получится именно то что нужно злоумышленнику. Мне кажется, что и /dev/null можно было использовать, добавив уровней вложенности.
- Даже если защититься от Path Traversal, но засветить содержимое хоть одного файла в директории с ключами, то злоумышленник сможет повторить фокус.
- Если на сайте есть уязвимость, позволяющая загрузить файл в папку с ключами, то вы понимаете к чему это приведёт. Даже если файл неисполняемый.