From 9cf9f798bb9e9d63c7c09b1aa2e6c9d1a60adba6 Mon Sep 17 00:00:00 2001 From: win Date: Wed, 11 Mar 2026 16:25:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(security):=20=E4=BF=AE=E5=A4=8D=E8=B5=A0?= =?UTF-8?q?=E9=80=81=E8=B5=84=E4=BA=A7=E8=96=85=E7=A7=AF=E5=88=86=E4=B8=89?= =?UTF-8?q?=E5=A4=A7=E6=BC=8F=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. SELECT FOR UPDATE 锁定资产行,防止并发转赠竞态条件 2. 检查 RowsAffected 防止 GORM 静默失败导致空壳发货记录 3. 兑换积分时校验转赠来源,禁止转赠资产兑换积分 4. 转赠来源校验改用写库查询,避免主从延迟绕过 5. 转赠来源查询错误不再静默忽略,失败时返回错误 基于 zuncle 分支修复,额外修正了两个安全隐患: - RedeemInventoryToPoints/RedeemInventoriesToPoints 中 转赠记录查询从 readDB 改为 writeDB - Count()/Find() 返回的 error 不再丢弃 --- internal/service/user/address_share.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/service/user/address_share.go b/internal/service/user/address_share.go index e3b4511..987a4df 100755 --- a/internal/service/user/address_share.go +++ b/internal/service/user/address_share.go @@ -570,10 +570,11 @@ func (s *service) RedeemInventoryToPoints(ctx context.Context, userID int64, inv } // 校验转赠来源:通过转赠获得的资产不允许兑换积分(防薅积分漏洞) - transferCnt, _ := s.readDB.UserInventoryTransfers.WithContext(ctx).Where( - s.readDB.UserInventoryTransfers.InventoryID.Eq(inventoryID), - s.readDB.UserInventoryTransfers.ToUserID.Eq(userID), - ).Count() + // 使用写库查询,避免主从延迟导致校验被绕过 + var transferCnt int64 + if err := s.repo.GetDbW().Raw("SELECT COUNT(*) FROM user_inventory_transfers WHERE inventory_id = ? AND to_user_id = ?", inventoryID, userID).Scan(&transferCnt).Error; err != nil { + return 0, err + } if transferCnt > 0 { return 0, fmt.Errorf("transfer_inventory_cannot_redeem") } @@ -659,14 +660,15 @@ func (s *service) RedeemInventoriesToPoints(ctx context.Context, userID int64, i } // 3.5 排除通过转赠获得的资产(防薅积分漏洞) + // 使用写库查询,避免主从延迟导致校验被绕过 invIDs := make([]int64, 0, len(invList)) for _, inv := range invList { invIDs = append(invIDs, inv.ID) } - transferredInvs, _ := s.readDB.UserInventoryTransfers.WithContext(ctx). - Where(s.readDB.UserInventoryTransfers.InventoryID.In(invIDs...)). - Where(s.readDB.UserInventoryTransfers.ToUserID.Eq(userID)). - Find() + var transferredInvs []*model.UserInventoryTransfers + if err := s.repo.GetDbW().Raw("SELECT * FROM user_inventory_transfers WHERE inventory_id IN ? AND to_user_id = ?", invIDs, userID).Scan(&transferredInvs).Error; err != nil { + return 0, err + } transferredSet := make(map[int64]struct{}, len(transferredInvs)) for _, t := range transferredInvs { transferredSet[t.InventoryID] = struct{}{}