Двойной пост запрос или как это сделали?

116
03 сентября 2019, 08:40

Всём привет. Есть сайт на котором хранятся виртуальные финансы для сайта и система зачисления, вывода, перевода между пользователями и т. д. Ближе к теме, один человек смог каким-то образом вывести сумму два раза одну и ту же, все это дело произошло в один момент, дата двух транзакций одинаковая, сумма первой транзакции полная стоимость его финансов на сайте, а вторая получается транзакция которая создавалась она не должна была создаться так как сумма на счету 0. Все защиты от F5 и тому подобное присутствуют. Вопрос: как это сделали?

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

Вот кусок кода который отвечает за создание заявки:

$paySys = Paymsys::findByName($this->tasys_in, $this->amount_in)->setPersent(true);
    if ($paySys->sysAbbr == null) {
        Yii::$app->session->setFlash('error', "<div class=\"alert alert-danger text-center\">Направильное имя системы</div>");
        return false;
    }
    $this->amount_in_origin = $paySys->sum;
    $this->tasys_in = $paySys->sys;
    $this->user_real_sum = UserMoney::getSum($paySys->sysAbbr);
    $money = OperationsMoney::start($paySys)->moneyDeduct();
    if (!$money->save()) {
        Yii::$app->session->setFlash('alert', $money->getError() . "&nbsp" . $money->getSum() . '&nbsp' . $paySys->sysAbbr);
        return false;
    }

Дата хранится в обычном формате 2016-08-22 00:21:51

Answer 1

Рассмотрим два параллельных потока (1) и (2)

  1. Исходный остаток: 10
  2. (1) Запрос на снятие 8
  3. (2) Запрос на снятие 7
  4. (1) Сумма не превышает 10. Снимаем. В итоге получим 10 - 8 = 2
  5. (2) Сумма не превышает 10. Снимаем. В итоге получим 10 - 7 = 3
  6. (1) Запишем 2 в базу
  7. (2) запишем 3 в базу
  8. Итог: сняли 8 + 7 = 15, текущий остаток 3

Способ решения: на 4 этапе блокировать таблицу. Тогда получим такую картину:

  1. Исходный остаток: 10
  2. (1) Запрос на снятие 8
  3. (2) Запрос на снятие 7
  4. (1) Заблокировали запись. Сумма не превышает 10. Снимаем. В итоге получим 10 - 8 = 2
  5. (2) Попытались заблокировать запись. Она уже заблокирована. Ждем
  6. (1) Запишем 2 в базу
  7. (1) Разблокировали запись
  8. (2) Заблокировали запись. Сумма превышает 2. Отлуп
  9. (2) Разблокировали запись
  10. Итог: сняли 8, текущий остаток 2

В коде это будет выглядеть так

mysqli_autocommit(false);
$res = mysqli_query("SELECT money FROM my_table WHERE user_id = $id LOCK FOR UPDATE");
$row = mysqli_fetch_row($res);
mysqli_free_result($res)
if ($row[0] >= $val)
  mysqli_query("UPDATE my_table SET money = GREATEST(money - $val, 0) WHERE user_id = $id");
else
  echo "Облом!";
mysqli_commit();

Параметры и обработку ошибок добавить по вкусу

Answer 2

дата двух транзакций одинаковая

На сколько точная дата? mysql для timestamp и datetime хранит как самую малую единицу всего лишь секунду. Одна секунда - это очень много времени. За такое время даже человек может умудриться сделать несколько запросов. А автоматика с лёгкостью.

Классический race condition в вашем коде. Надо внимательно смотреть код и думать, что может пойти не так при конкурентном доступе.

СУБД вполне неплохое место для сериализации транзакции. Но про конкурентный доступ надо будет всё равно думать. Просто сказать begin/commit недостаточно.

Все защиты от F5 и тому подобное присутствуют

Очевидный вывод - эти проверки были реализованы некорректно или только для частных случаев.

Без кода сказать ничего нельзя, кроме как "race condition". С кодом - скорей всего его довольно много и просто так вычитывать желающих может не найтись.

Answer 3

Внедрение транзакций в готовый код может оказаться очень сложным делом. Если у вас немного таких критических мест, то можно использовать семафоры http://php.net/manual/ru/ref.sem.php Или организовать подобное на memcache http://php.net/manual/ru/memcached.add.php

READ ALSO
Как настроить webpack dev server на обновление php файлов?

Как настроить webpack dev server на обновление php файлов?

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

117
Как правильно создать ЧПУ из заголовка новости (title)?

Как правильно создать ЧПУ из заголовка новости (title)?

Нашел функцию преобразующую заголовки на русском и казахском языках в английские буквы

99
Подпись запроса при валидации apple pay c backend PHP

Подпись запроса при валидации apple pay c backend PHP

Ситуация такая Apple Pay и tinkoff После создания платежной сессии

81
Как протестировать метод?

Как протестировать метод?

Подскажите какой можно написать тест PhpUnit на следующий метод(метод парсит ресурс и сохраняет ссылки в csv)

100