Lỗi Race Condition trong Next.js API Route

Một mã giảm giá chỉ sử dụng một lần thì chỉ nên có hiệu lực một lần duy nhất.

Nhưng trong kịch bản này, ba mươi người có thể sử dụng nó cùng một lúc.

Điều này xảy ra do lỗi race condition Time-of-Check Time-of-Use (TOCTOU).

Lỗi này xảy ra qua hai bước:

  • Bước 1: Máy chủ đọc cơ sở dữ liệu để kiểm tra số lần đã sử dụng.
  • Bước 2: Máy chủ gửi lệnh thứ hai để tăng số lần sử dụng đó lên.

Nếu bạn gửi nhiều yêu cầu cùng một lúc, máy chủ sẽ xử lý tất cả chúng vào khoảng thời gian giữa hai bước này.

Nhiều yêu cầu cùng đọc bộ đếm khi nó vẫn đang là 0. Tất cả đều vượt qua bước kiểm tra. Tất cả đều áp dụng giảm giá. Và tất cả đều tăng bộ đếm lên.

Kết quả là gì? Một mã giảm giá vốn chỉ dành cho một người lại được sử dụng tới ba mươi lần.

Lỗi này rất dễ bị bỏ qua trong quá trình review code. Logic trông có vẻ hoàn hảo khi bạn đọc từng dòng một. Lỗi chỉ xuất hiện khi bạn xem xét đến tốc độ và tính đồng thời (concurrency).

Cách khắc phục:

Đừng sử dụng các lệnh đọc và ghi riêng biệt. Thay vào đó, hãy sử dụng một thao tác nguyên tử (atomic operation) duy nhất.

Trong Prisma, bạn có thể sử dụng updateMany với một điều kiện trong mệnh đề WHERE:

• Cơ sở dữ liệu kiểm tra điều kiện và thực hiện cập nhật trong một thao tác duy nhất. • Nếu số lần sử dụng đã đạt giới hạn, việc cập nhật sẽ thất bại ngay lập tức. • Không có yêu cầu nào khác có thể xen vào giữa bước kiểm tra và bước ghi.

Một lựa chọn khác là sử dụng database transaction. Việc này sẽ khóa dữ liệu để không có yêu cầu nào khác có thể tác động vào cho đến khi bạn hoàn tất.

Đối với SQLite, lệnh cập nhật duy nhất là phương pháp đáng tin cậy nhất.

Hãy luôn tự hỏi: điều gì sẽ xảy ra nếu hai yêu cầu cùng chạm vào logic này trong cùng một mili giây?

Source: https://dev.to/oopssec-store/racing-a-nextjs-api-route-coupon-abuse-with-prisma-and-sqlite-3gma