[security]KVE-2026-0882 주문 포인트 차감 Race Condition (Double Spend) 수정
shop/orderformupdate.php / mobile/shop/orderformupdate.php 의 포인트 검증과 차감 사이에 TOCTOU race 가 존재하여, 동일 회원이 여러 세션으로 동시에 주문 제출 시 같은 포인트 잔액을 반복 검증 통과 → 다중 차감으로 mb_point 가 음수가 되는 double spend 가 가능했음. insert_point() 의 기존 named lock 은 (mb_id, rel_table, rel_id, rel_action) 조합 키 기반이라 서로 다른 od_id 주문 간에는 lock 이 다르고, 주문 차감 호출에 rel_* 가 전달되지 않아 lock 미적용 상태였음. 조치: KVE-2026-0687 (쇼핑몰 쿠폰) 와 동일한 회원 단위 MySQL GET_LOCK 패턴을 적용. lock 획득 후 g5_point SUM 으로 잔액을 재조회하여 - 충분하면 정상 차감 - race 로 부족하면 사용 가능한 만큼만 차감 + 부족분은 od_receipt_point 보정 + od_misu(미수금) 으로 반영하여 매장 손실 방지. MyISAM 정책상 트랜잭션 금지이므로 named lock 으로 직렬화하는 방식이 가장 적합. 데스크톱·모바일 동일 패턴 적용. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
6b2f9e094c
commit
56b1958d37
@@ -704,8 +704,33 @@ if(!$result) {
|
||||
}
|
||||
|
||||
// 회원이면서 포인트를 사용했다면 테이블에 사용을 추가
|
||||
if ($is_member && $od_receipt_point)
|
||||
insert_point($member['mb_id'], (-1) * $od_receipt_point, "주문번호 $od_id 결제");
|
||||
// 동시 주문 race condition 방지 — 회원 단위 GET_LOCK + 잔액 재조회
|
||||
if ($is_member && $od_receipt_point) {
|
||||
$point_lock_key = 'g5pt_order_'.md5($member['mb_id']);
|
||||
$lock_row = sql_fetch(" select get_lock('$point_lock_key', 5) as lk ");
|
||||
|
||||
if (!empty($lock_row['lk'])) {
|
||||
$current_point = (int) get_point_sum($member['mb_id']);
|
||||
|
||||
if ($current_point >= $od_receipt_point) {
|
||||
insert_point($member['mb_id'], (-1) * $od_receipt_point, "주문번호 $od_id 결제");
|
||||
} else {
|
||||
// race condition 으로 잔액 부족 — 사용 가능한 만큼만 차감하고 부족분은 미수금 처리
|
||||
$actual_point = $current_point > 0 ? $current_point : 0;
|
||||
$shortage = $od_receipt_point - $actual_point;
|
||||
|
||||
if ($actual_point > 0) {
|
||||
insert_point($member['mb_id'], (-1) * $actual_point, "주문번호 $od_id 결제");
|
||||
}
|
||||
sql_query(" update {$g5['g5_shop_order_table']}
|
||||
set od_receipt_point = '$actual_point',
|
||||
od_misu = od_misu + '$shortage'
|
||||
where od_id = '$od_id' ");
|
||||
}
|
||||
|
||||
sql_query(" do release_lock('$point_lock_key') ");
|
||||
}
|
||||
}
|
||||
|
||||
$od_memo = nl2br(htmlspecialchars2(stripslashes($od_memo))) . " ";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user