Skip to main content

Chương 20: Race Condition

Khái niệm

Race Condition (Điều kiện đua tranh) xảy ra khi outcome của một operation phụ thuộc vào timing của các concurrent requests. Hacker khai thác bằng cách gửi nhiều requests đồng thời để bypass business logic.

Mức độ nguy hiểm: Trung bình-Cao — Race conditions có thể bypass rate limiting, sử dụng coupon nhiều lần, overdraft tài khoản.


TOCTOU — Time-of-Check to Time-of-Use

Check → ... (time gap) ... → Use

Thread 1: Check balance (100$) → OK → (waiting) → Withdraw 100$
Thread 2: → Check balance (100$) → OK → Withdraw 100$
→ Cả hai withdraw 100$ mặc dù chỉ có 100$ trong tài khoản!

Các loại Race Condition

1. Limit Overrun

# Vulnerable: Coupon chỉ dùng 1 lần
def apply_coupon(user_id, coupon_code):
coupon = db.get_coupon(coupon_code)

if coupon.is_used:
return "Already used"

# Race condition: hai requests đều check và thấy coupon chưa dùng
db.apply_discount(user_id, coupon.discount)
coupon.is_used = True
db.save(coupon)
Request 1: Check is_used=False → OK
Request 2: Check is_used=False → OK (cùng lúc với Request 1)
Request 1: Apply discount → Save is_used=True
Request 2: Apply discount → Save is_used=True
→ Discount áp dụng 2 lần!

2. Multi-Endpoint Race Condition

Deposit + Withdraw đồng thời:
1. Deposit $100 → balance: $0 → $100
2. Simultaneously: Withdraw $100 → check $100 OK

Nếu race condition:
Check: $100 available (before deposit committed)
→ Cả hai operations commit → balance âm!

Single-Packet Attack (Burp Turbo Intruder)

HTTP/2 cho phép gửi nhiều requests trong cùng một TCP packet → giảm timing uncertainty → race condition dễ trigger hơn.

# Burp Turbo Intruder script cho race condition
def queueRequests(target, wordlists):
engine = RequestEngine(
endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=20, # Nhiều requests trong 1 connection
pipeline=True, # HTTP pipelining
)

for i in range(20):
engine.queue(target.req)

def handleResponse(req, interesting):
table.add(req)

Kịch bản tấn công: Gift Card Abuse

Target: Áp dụng gift card (chỉ 1 lần)

1. Intercept request: POST /apply-gift-card
2. Send to Turbo Intruder với 50 concurrent requests
3. Tất cả requests check card chưa used → tất cả apply discount
4. 1 gift card → 50x discount

Phòng chống

# Database transaction với SERIALIZABLE isolation
def apply_coupon_safe(user_id, coupon_code):
with db.transaction(isolation='SERIALIZABLE'):
coupon = db.get_coupon_for_update(coupon_code) # SELECT FOR UPDATE

if coupon.is_used:
return "Already used"

db.apply_discount(user_id, coupon.discount)
coupon.is_used = True
db.save(coupon)
# Transaction commit → atomic

# Redis atomic operations cho counter/lock
import redis

def check_and_use_coupon(coupon_code):
r = redis.Redis()

# SETNX: SET if Not eXists → atomic
key = f"coupon:{coupon_code}:used"
if not r.setnx(key, "1"):
return False # Đã used

r.expire(key, 86400) # 24h TTL
return True # First use

Tóm tắt

  • Race condition: concurrent requests trong timing gap → bypass business logic.
  • TOCTOU: thay đổi state giữa check và use.
  • Khai thác: Turbo Intruder, single-packet attack (HTTP/2).
  • Phòng chống: database transactions (SERIALIZABLE), atomic operations (Redis SETNX), database-level locking (SELECT FOR UPDATE).

Câu hỏi ôn tập

  1. TOCTOU là gì? Cho ví dụ cụ thể trong web application.
  2. Tại sao single-packet attack với HTTP/2 dễ trigger race condition hơn?
  3. Làm thế nào SELECT FOR UPDATE ngăn race condition?
  4. Redis SETNX giải quyết race condition như thế nào?
  5. Race condition khác application logic bug ở điểm nào?