[Web] Web · 작성: 2026-07-20 03:10:34 · 수정: 2026-07-20 03:11:15 · 조회 5
"검사할 때(Time of Check)"와 "사용할 때(Time of Use)" 사이에 시간차가 있으면, 그 틈에 상태를 바꿔서 검사 결과를 무의미하게 만드는 게 TOCTOU(Race Condition) 취약점이다. 웹에서는 동시에 여러 요청을 거의 같은 타이밍에 보내서, 서버의 "확인 → 처리" 로직 사이를 파고든다.
// 취약한 흐름
const coupon = await db.get('SELECT used FROM coupons WHERE code = ?', code);
if (coupon.used) return res.status(400).send('이미 사용됨');
// ↑↓ 이 사이에 같은 요청이 동시에 여러 개 들어오면, 전부 "아직 안 씀"으로 통과한다
await db.run('UPDATE coupons SET used = 1 WHERE code = ?', code);
await applyDiscount(code);
동일한 쿠폰 코드로 요청을 수십 개 동시에 쏘면(Burp Suite의 Turbo Intruder 같은 도구로 타이밍을 맞춰서), 여러 요청이 "아직 사용 안 됨" 상태를 동시에 읽고 지나가서 쿠폰이 여러 번 적용된다. 잔액 차감, 재고 확인, 초대 코드 사용 같은 로직에서 실제로 자주 발견되는 패턴이다.
확인과 처리를 애플리케이션 코드에서 두 단계로 나누지 말고, DB의 원자적(atomic) 연산 하나로 합친다.
-- "아직 안 쓴 경우에만" 조건과 업데이트를 한 문장으로 묶는다
UPDATE coupons SET used = 1 WHERE code = ? AND used = 0;
-- 이 쿼리가 영향을 준 행(affected rows)이 0이면 "이미 사용됨"으로 처리
이렇게 하면 여러 요청이 동시에 들어와도 DB의 행 잠금(row lock) 덕분에 단 하나만 성공한다.