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
- TOCTOU là gì? Cho ví dụ cụ thể trong web application.
- Tại sao single-packet attack với HTTP/2 dễ trigger race condition hơn?
- Làm thế nào
SELECT FOR UPDATEngăn race condition? - Redis
SETNXgiải quyết race condition như thế nào? - Race condition khác application logic bug ở điểm nào?