Конкурс «Большой ку$h» на PHDays — райтап

Буквально пару дней назад завершился PHDays, познакомился со многими интересными людьми, в целом форум оставил самые позитивные впечатления, за что огромный респект организаторам, компании Positive Technologies. Уже появилось довольно много отчетов (shr, d0znpp, toxa, asintsov), напишу лишь о том, что больше всего запомнилось лично для меня — о конкурсе Snatch, он же «Большой ку$h».

За день до конкурса всем участникам выдали по флешке с PHP-исходниками системы дистанционного банковского обслуживания, нужно было найти в них уязвимости и на следующий день, используя баги, слить как можно больше денег со счетов в этой системе. Причем, обналичка украденных полученных средств производилась на реальном банкомате с реальными картами. В итоге, удалось занять лишь третье место, первое досталось Gifts’у, а второе — Глебу Чербову из Digital Security.

Никаких SQL-инъекций, LFI/RFI и прочих традиционных для PHP уязвимостей в коде не было — присутствовали баги, которые, по словам организаторов, встречаются в ходе пентеста реальных ДБО. Система была подготовлена очень грамотно, разработчики потрудились на славу. Всего присутствовало три уязвимости, самую жирную успел использовать Gifts благодаря своему многопоточному скрипту на Python, который мне впоследствии удалось посмотреть, за что спасибо cyber-punk’у. Вторую уязвимость нашли все, так как была самой простой, и, соответственно, давала меньше всего денег. Третий баг позволял получать доступ к аккаунтам с помощью подбора сессии. Как ни странно парни, занявшие первое и второе место, как впоследствии выяснилось, ее не использовали. Буквально за час до конкурса я нашел этот баг, но использовать его мне удалось, до сих пор очень обидно.

1. Уязвимость через helpdesk

helpdesk/pswdcheck.php
helpdesk/index.php

$user = $auth->checkSession();
if(!$user) {
/* ... */
}

class/HelpdeskAuth.php

public function checkSession() {
    
if(isset($_SERVER["HTTP_BANKOFFICEUSER"])) {
    $userId = base64_decode($_SERVER["HTTP_BANKOFFICEUSER"]);
    $userInfo = $this->user->getUserInfoById($userId);
    $this->user->setupUserInfo($userInfo);
    return $this->user;
}

При отправке HTTP-хэдера BANKOFFICEUSER с любым значением можно было обойти авторизацию и получить доступ к скрипту, который выводит пользователей со слабыми числовыми паролями. Для брута юзера с 5-символьным паролем требовалось 100 тысяч запросов. Была captcha, но она нисколько не мешала, так как был обход:

login.php

if($_POST["code"] != $captcha->decodeCaptchaCode($_POST["_code"])) {

class/Captcha.php

public function decodeCaptchaCode($code) {
    return @base64_decode(@strrev(@base64_decode($code)));
}

Здесь все просто, для обхода нужно было отправлять в POST, например, такие параметры: code=»12345″ и _code=»PVVETnpJVE0=».

Получив доступ к аккаунту, для перевода денег на свой счет было необходимо использовать еще одну уязвимость. На одних аккаунтах транзакции могли проводиться без защиты, а на других с помощью одноразовых паролей. Эти одноразовые пароли генерились следующим алгоритмом:

protected function generateOTP() {
    $OTPs = array();
    $s = 44553 + $this->userInfo["id"];
    for($n = 10; $n < 24; $n++) {  
        $OTP = "";
        $j = rand(20,39);
        $j = substr($j, 0, 1);
        $OTP = $n*$s*$j;
        $OTP = substr($OTP, 0, 5);
        $OTPs[] = $OTP;
    }
    return $OTPs;
}

Понятно, что сбрутить тут очень просто — всего 19 вариантов всего 2 варианта (thx BECHED). Был еще один способ защиты транзакций, но сгенерированный OTP было невозможно подобрать при переходе на третий шаг авторизации OTP не проверялся (thx Gifts).

2. Уязвимость в восстановлении пароля

Нужно было лишь отправить запрос на сброс пароля и брутить код, который генерился следующим нехитрым образом:

$key = md5($login.rand(1, 250));

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

3. Слабые сессии

class/Auth.php

private function getSpecialHash($password) {
    $hash = sprintf("%u", crc32($password));
    if(strlen($hash) > 4) {
        $hash = substr($hash, 0, 4);
    }
    if(strlen($hash) < 4) {
        while(strlen($hash) < 4) {
            $hash .= "1";
        }
    }
    return $hash;
}

У некоторых пользователей, для которых в БД поле type соответствовало значению «contractor», можно было сбрутить сессию, так как рандомный хэш состоял лишь из 4 символов, т.е. требовалось лишь 10 тысяч запросов. Почему я не догадался попробовать уязвимость на аккаунтах, на которых Дима Евтеев демонстрировал для публики возможности ДБО, для меня остается загадкой.

Помимо конкурса на PHDays понравилась настоящая хек-атмосфера, интересные доклады и халявный Гиннес в баре :) Остается поблагодарить Positive Technologies за организацию мероприятия, было очень круто.


16 comments:

  1. cyber-punk, 3. Июнь 2012, 15:03

    Опередил меня =( Я тоже хотел написать врайтап, с рахбором исходников :)

     
  2. Gifts, 3. Июнь 2012, 16:40

    Ты забыл описать уязвимости OTP и самую любопытную из них — обход сгенерированных ключей.

    Как это ты писал на ПХП, тоже интересно. Без воркеров это тот еще финт ушами

    З.Ы. и вообще я обижен то ли на организаторов, то ли на Ребза — мой ник неправильно написали =)

     
  3. Raz0r, 3. Июнь 2012, 17:10

    Точно, обновил) Я написал по отдельному скрипту для каждой уязвимости, использовал RollingCurl (http://code.google.com/p/rolling-curl/).

     
  4. Gifts, 3. Июнь 2012, 17:47

    [quote]Понятно, что сбрутить тут очень просто – всего 19 вариантов. Был еще один способ защиты транзакций, но сгенерированный OTP было невозможно подобрать.[/quote]

    Вот похоже почему я больше всего сорвал денег, там же три отп:

    1) Без проверки, переходим на третий шаг — профит
    2) Генерируемый список ключей — брутим комбинации — профит
    3) В базе обозначен SmartCard вроде как — достаточно не переходить на 3 шаг, а сразу идти на 4 — там нету проверки подтвержденности транзакции (столбец otp_check)

    Причем, если перейти на третий шаг — транзакция сбрасывалась.

     
  5. Raz0r, 3. Июнь 2012, 17:53

    Теперь понятно)

     
  6. IAD, 3. Июнь 2012, 18:05

    хы, чувствую здорово развлеклись =)
    Завидую

     
  7. cyber-punk, 3. Июнь 2012, 21:18

    Gifts, мой ник тоже написали не правильно :(

     
  8. chipik, 4. Июнь 2012, 10:37

    «Слабые сессии»
    багу нашли, но почему-то решили её не использовать.

    Вот с OPT для SmartCard косячнули слегка, уязвимость нашли, а вот то, что на 3 шаг ходить не стоит не проверили :(

    А от атакующих никто не защищался?

     
  9. Raz0r, 4. Июнь 2012, 12:09

    Гифтс сделал, причем очень основательно) Я считаю, что обычные аккаунты не было смысла защищать, так как после слива денег они были бесполезны. А защита своего аккаунта — просто не заходить в него, чтобы не назначалась сессия.

     
  10. BECHED, 4. Июнь 2012, 20:49

    Задание было на грамотную реализацию и толику везения =)
    Я сделал отвратительнейшую реализацию, на которую мне самому стыдно смотреть.
    Поэтому ничего и не снял =)
    Кстати, у меня была настолько убогая реализация, что брут сессии в баге с контрактными акками обрывался на 502 ошибке серва, которая регулярно скакала из-за ддос-эффекта.

     
  11. Rebz, 18. Июнь 2012, 16:21

    Gifts, Cyber — ники писал не я, поэтому хз кто их такими вписывал, я давал нормальные :)
    Разор, за подробный райтап отдельное спасибо, было интересно читать.

     
  12. BECHED, 22. Июнь 2012, 11:53

    Ещё стоит отметить, что в функции generateOTP() было достаточно проверить всего 2 варианта, поскольку текущий номер одноразового пароля можно было напарсить, а случайна здесь лишь строка $j (2 или 3).

     
  13. Raz0r, 22. Июнь 2012, 12:05

    Точно, там же substr()

     
  14. Raz0r, 22. Июнь 2012, 12:10

    Обновил пост

     
  15. Rebz, 29. Июнь 2012, 10:18

    Парни, если подвести итог, что же в итоге не хватило, чтобы вывести весь куш на кошельки участников? Обвал сети, нехватка времени или что-то ещё?

     
  16. Raz0r, 2. Июль 2012, 0:44

Write a comment: