𝗥𝗮𝗰𝗶𝗻𝗴 𝗮 𝗡𝗲𝘅𝘁.𝗷𝘀 𝗔𝗣𝗜 𝗥𝗼𝘂𝘁𝗲

Одноразовий купон має працювати лише один раз.

Але в цьому сценарії тридцять людей можуть скористатися ним одночасно.

Це стається через стан гонитви (race condition) типу Time-of-Check Time-of-Use (TOCTOU).

Помилка виникає у два етапи:

  • Крок 1: Сервер зчитує базу даних, щоб перевірити кількість використань.
  • Крок 2: Сервер надсилає другу команду для інкременту цього лічильника.

Якщо надіслати багато запитів одночасно, сервер обробить їх усі в проміжку між цими двома кроками.

Багато запитів зчитують лічильник, поки він все ще дорівнює нулю. Усі вони проходять перевірку. Усі вони застосовують знижку. Усі вони інкрементують лічильник.

Результат? Купон, призначений для однієї людини, використовується тридцять разів.

Це легко пропустити під час перегляду коду. Логіка виглядає бездоганною, коли ви читаєте її рядок за рядком. Помилка з'являється лише тоді, коли ви враховуєте швидкість і паралелізм.

Як це виправити:

Припиніть використовувати окремі команди читання та запису. Замість цього використовуйте одну атомарну операцію.

У Prisma ви можете використовувати updateMany з умовою у блоці WHERE:

• База даних перевіряє умову та виконує оновлення одним цілісним рухом. • Якщо лічильник уже досяг ліміту, оновлення негайно завершиться невдачею. • Жоден інший запит не зможе «проскочити» між перевіркою та записом.

Інший варіант — використання транзакції бази даних. Це блокує дані, тому жоден інший запит не зможе їх змінити, поки ви не завершите роботу.

Для SQLite найбільш надійним методом є одна команда update.

Завжди запитуйте себе: що станеться, якщо два запити потраплять у цю логіку в ту саму мілісекунду?

Джерело: https://dev.to/oopssec-store/racing-a-nextjs-api-route-coupon-abuse-with-prisma-and-sqlite-3gma