Взламываем шоп по продаже СС

HideMe

ProCrd
Регистрация
27.04.17
Сообщения
41
Реакции
71
Баллы
18
Думаю многим хоть раз было интересно, каким же все-таки способом люди опустошают целые шопы.
На анализ был дан код магазина по продаже сс. Нужно было выяснить, каким путем были уведены из шопа деньги.
di-B02TI4.png

После анализа, оказалось, что способов украсть деньги достаточно много. И описывать каждый из них - дело долгое и неблагодарное. Поэтому я сделал "кратенький" отчет в достаточно упрощенной форме, который вы и имеете удовольствие читать в данный момент:

Forced Browsing & Leak info
После того, как я развернул локальную копию сайта, я собрал структуру файлов/папок и просканировал ее с помощью Burp Intruder. И практически сразу - наиудивительнейшие открытия.

При простом обращении к файлам:
  • ./core/1.php
  • ./core/3.php
Мы можем увидеть следующее:
di-E0VEYC.png

Не так важно раскрытие пути, это практически ничего не дает (SQL-инъекций и LFI нету). На скриншоте замазано значение key из массива. Забегая вперед, для работы с сервисом, который предоставляет карты для продажи, нужно знать только API-ключ и url куда отправлять запросы. И вот key - это то самое значение API-ключа. Т.е. для человека, который знает, как делать шопы привязанные к конкретному API, достаточно знать только этот ключ, для того, чтобы слить баланс шопа.

Файл ./core/4.php позволяет делать поиск доступных городов по штатам:
di-PFKCDF.png

Файлы:
  • ./core/chat/show.php
  • ./core/chat/messages.txt
Отображают содержимое чата. Т.е. можно читать, о чем пишут в чатике, не заходя в магазин и даже не регистрируясь.

Активная XSS и прочие веселья в чате
Взглянем на содержимое файла ./core/chat/send.php:
<?php

require 'config.php';

$sender = trim($_POST['sender']);
$text = trim($_POST['message']);
PHP:
PHP:
<?php
require 'config.php';

$sender = trim($_POST['sender']);
$text = trim($_POST['message']);

$date = date("H:i:s d.m.Y", time());

if($sender == 'admin'){$admin_sender = 'style="color:red";';}
$message = '<br><span style="color: #ccc">'.$date.'</span> <b '.$admin_sender.'>'.$sender.':</b> '.$text;
$file = fopen($filename, 'a');
fwrite($file, $message);
fclose($file);
Мы видим, что во первых - не нужно даже быть зарегистрированным, чтобы отправить сообщение. А во вторых - нет проверки, что именно тот пользователь что указан в sender и отправил сообщение.

Достаточно отправить post-запрос с параметрами sender и message:
di-YK2CSK.png

И мы можем увидеть в чате:
di-CMLOBB.png

Но нам этого недостаточно. Попробуем отправить html-код.
В качестве параметров указываем:
HTML:
sender=kroba&message=hello+world!<script>alert(document.cookie)%3b</script>
Заходим в магазин:
di-Q2FMHQ.png

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

Брутфорс и подбор логинов пользователей
Немного отвлечемся и обратим наше внимание на форму логина:
di-0HXBQU.png

Сразу можем отметить, что нет капчи - можно брутить сколько угодно. Попробуем зайти под заведомо несуществующим пользователем abcdef1234567890:
di-MIB02K.png

А теперь под пользователем admin:
di-GUYG0Y.png

Как видите, перед тем как брутфорсить, мы можем точно определить, зарегистрирован пользователь или нет. Более того, обращаясь к файлу ./core/chat/show.php можно собрать логины пользователей, не привлекая внимания.

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

Активная XSS при регистрации пользователя
Попробуем зарегистрировать пользователя:
di-NXA4FU.png

В качестве логина указываем следующую строку:
Код:
hacker" class="btn btn-warning"><script>alert(document.cookie)</script><input type="hidden" kek="
di-INB0PV.png

Что важно - верстка не бьется, и если не alert(), то понять, что что-то произошло довольно сложно. С данной XSS можно угнать куки админа, не тратя время на обычных пользователей.

Тикеты с XSS
Создаем тикет:
di-W90W8D.png

Админ открывает тикет, чтобы прочитать и...
di-ROH7NQ.png

Опять таки, угоняем куки, подставляем себе и т.д.

Делаем свой аккаунт админом
Используя украденные куки админа, заходим в шоп, выбираем пункт "пользователи", выбираем заранее зарегистрированный нами аккаунт и меняем баланс на тот, который нас более устраивает:
di-TIY78P.png

Великолепно. Но это совсем не всё, на что мы можем влиять. Если посмотрим в html-код страницы, то можем увидеть:
di-TIY78P.png

Как мы видим, часть функционала убрали в комментарии. Но нам ничто не мешает просто отредактировать html с помощью firebug или другого подобного инструмента. Вдобавок, мы допишем еще один пункт в меню:
di-6IAWQW.png

Жмем OK, заходим под пользователем l33t_hacker:
di-6IAWQW.png

Мы не только добавили денег на баланс, но и расширили свои полномочия. Теперь мы можем красть деньги не привлекая внимания:

  • находим пользователей с большим балансом
  • запоминаем баланс
  • удаляем пользователя
  • регистрируемся с таким же ником
  • ставим кол-во денег, как у прошлого пользователя
И никаких подозрений - сколько занесли пользователи, столько мы и тратим. К тому же, мы можем свободно редактировать любые статьи в магазине. И можем добавить еще парочку активных xss, так сказать впрок.

Ошибка в логике: покупаем сс по своей цене
Допустим, админ редко заходит в магазин, везде пользуется noscript или поставил флаг httponly у сессионных cookie. Выходит, что у нас нет возможности стырить его куки, а вот получить пачку свежих сс и при этом заплатить как можно меньше - очень хочется. Что же делать?

Алгоритм действий простой.

Включаем burp suite. Добавляем в корзину карту, которую хотим купить. Сохраняем http-запрос добавляющий карту:
di-KUU2KG.png

Теперь переходим в корзину и удаляем карту из корзины. Повторяем запрос, но меняем последнее значение:
di-AFZ7TV.png

Обновляем страницу с корзиной:
di-GWXWOZ.png

Такая цена нас уже устраивает. Но всегда хочется еще чего-то большего. Вместо "0.0001" поставим "-1000000":
di-4LMTD8.png

Еще лучше, да и после покупки в плюсе остаемся!

Ошибка в логике: баланс * кол-во сессий = профит
Даже если закрыть уязвимость, связанную с изменением цены, остается возможность покупать карты, загоняя баланс в минус. Взглянем на код скрипта ./components/com_shop/shop.php, а именно, на функцию buy():
Код:
function buy(){
    if(count($_SESSION['shop']['product']) >= 1){
          
          if($_SESSION['shop']['total_sum'] > $_SESSION['user_info']['balance']){
            header("Location: /");
            exit;
        }
        //////////////////
        // пропускаем
        //////////////////
        $sql  = 'UPDATE `'.DB_PREFIX.'users` SET `balance` = `balance` - '.$_SESSION['shop']['total_sum'].' WHERE `id` = ?';           
        $user_balance = DB::prepare($sql)->execute([$_SESSION['user_info']['id']]);
        //////////////////
        // пропускаем
        //////////////////
 
      }
}
Нам разрешают покупать, если сумма покупки меньше, чем баланс пользователя. При этом и сумма покупки и баланс хранятся в сессии. Далее, после покупки баланс в БД меняет значение. Но (!) непосредственно перед покупкой нет проверки на то, сколько в данный момент в самой БД у пользователя денег.

Поэтому возможен такой трюк:

  1. Открываем 5 разных браузеров
  2. В каждом браузере заходим в один и тот же аккаунт, на котором лежит, к примеру, 100 баксов.
  3. В каждом браузере добавляем в корзину разных карт на 100$.
  4. В каждом браузере переходим на страницу с оплатой.
  5. В каждом браузере нажимаем "Buy".
  6. Получаем карт на 500$ и аккаунт, загнанный в -400$
  7. Регаем новый аккаунт, добавляем 100 баксов на счет.
  8. Повторяем пункты 1-7.
При этом кол-во браузеров может быть не 5, а 50. Или 500
Дерзайте и успехов в ваших начинаниях ;)




Автор krober, статься взята с u***abs.pro
 
Большинство багов в коде показанном исправляется банальными проверками и использованием аякса.
 
Назад
Верх Низ