1. 排除商城直购(source_type=1):GMV和成本过滤条件从IN(1,2,3,4)改为IN(2,3,4) 2. 排除次卡免费使用订单(actual_amount=0):避免购买次卡和使用次卡双重计入GMV - source_type=4 一番赏使用次卡:1578单 44032元重复 - source_type=3 对对碰使用次卡:422单 7042元重复 - 合计去除51074元虚增GMV(29.1%) 3. 成本过滤条件同步修正:source_type IN(2,3,4),total_amount>0 修正后:GMV从175600降至124527元,毛利率从37.4%回到真实的11.8%
163 lines
7.1 KiB
Markdown
163 lines
7.1 KiB
Markdown
# 赠送资产漏洞核查报告
|
||
|
||
> 数据源: dev_game 数据库 | 核查日期: 2026-03-11
|
||
|
||
---
|
||
|
||
## 一、结论摘要
|
||
|
||
| 项目 | 结论 |
|
||
|------|------|
|
||
| 并发漏洞 | **确实存在**,已修复(SELECT FOR UPDATE + RowsAffected 检查) |
|
||
| 实际货物损失(一份发两份) | **0 元** — 18 个重复发货资产中,没有一个真正被两方都发了货 |
|
||
| 积分重复兑换 | **0 元** — 没有任何资产被多人兑换积分,也没有同一资产被兑换多次 |
|
||
| 发送方转赠后又兑换同一资产 | **0 笔** — 发送方没有在转赠后兑换过同一资产 |
|
||
| 转赠后接收方兑换积分 | 91 笔 / 12,088.80 元 — **合法行为**,资产转赠后归接收方所有 |
|
||
|
||
**总实际损失: 0 元**
|
||
|
||
---
|
||
|
||
## 二、漏洞技术分析
|
||
|
||
### 2.1 Bug 描述
|
||
|
||
文件: `internal/service/user/address_share.go`
|
||
|
||
| Bug | 位置 | 描述 | 后果 |
|
||
|-----|------|------|------|
|
||
| readDB 竞态 | 原 L116-133 | 反重复检查使用从库(readDB),主从延迟 10-100ms 内并发请求可绕过 | 同一资产产生重复转赠记录和重复发货记录 |
|
||
| RowsAffected 未检查 | 原 L181-189 | `Updates()` 返回 0 行影响时不报错,后续操作继续执行 | 资产状态未变但发货记录已创建 |
|
||
|
||
### 2.2 修复方案(已合并 zuncle 分支)
|
||
|
||
| 修复 | 方式 |
|
||
|------|------|
|
||
| 竞态条件 | 在事务内使用 `SELECT FOR UPDATE` 锁行 + 写库查询发货记录 |
|
||
| RowsAffected | 转赠和原主发货两个分支都检查 `result.RowsAffected == 0` 后回滚 |
|
||
|
||
### 2.3 关于"转赠资产禁止兑换积分"
|
||
|
||
zuncle 分支原本还包含第三个修复:禁止通过转赠获得的资产兑换积分。经核实,**这是业务策略而非修漏洞**:
|
||
- 资产转赠后归接收方所有,接收方有权决定发货或兑换积分
|
||
- 发送方没有对已转赠的资产做任何兑换操作
|
||
- 不存在"一份资产两边都兑换"的情况
|
||
|
||
**已回退此限制。**
|
||
|
||
---
|
||
|
||
## 三、数据核查
|
||
|
||
### 3.1 全局概况
|
||
|
||
| 指标 | 数值 |
|
||
|------|------|
|
||
| 总转赠记录数 | 157 条 |
|
||
| 涉及资产数 | 135 个 |
|
||
| 涉及用户数 | 15 人 |
|
||
| 多次转赠资产 | 13 个(并发 bug 导致的重复记录) |
|
||
| 两方都产生发货记录的资产 | 18 个 |
|
||
|
||
### 3.2 重复发货资产明细(18 个)
|
||
|
||
> 同一资产在发送方和接收方名下都产生了发货记录
|
||
|
||
#### 仅接收方有效发货,发送方全取消(8 个)— 无损失
|
||
|
||
| 资产ID | 价值(元) | 发送方 | 接收方 | 接收方状态 | 发送方状态 | 兑换积分 |
|
||
|--------|---------|--------|--------|-----------|-----------|---------|
|
||
| 49426 | 1,315.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消1 | 全取消(2) | 否 |
|
||
| 47096 | 900.00 | 9305(新人) | 9116(非洲人) | 有效1/取消1 | 全取消(3) | 否 |
|
||
| 51038 | 605.00 | 9210(非酋) | 9116(非洲人) | 有效1/取消3 | 全取消(1) | 否 |
|
||
| 44153 | 375.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消2 | 全取消(1) | 否 |
|
||
| 44152 | 375.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消4 | 全取消(1) | 否 |
|
||
| 44151 | 375.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消2 | 全取消(1) | 否 |
|
||
| 44150 | 375.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消2 | 全取消(1) | 否 |
|
||
|
||
小计: 4,320 元,全部仅接收方 9116 有效发货,**无实际损失**。
|
||
|
||
#### 双方全取消(10 个)— 无损失
|
||
|
||
| 资产ID | 价值(元) | 发送方 | 接收方 | 接收方兑换积分 |
|
||
|--------|---------|--------|--------|------------|
|
||
| 42746 | 375.00 | 9336(有冰的帝君) | 9116(非洲人) | 是 |
|
||
| 42757 | 375.00 | 9336(有冰的帝君) | 9116(非洲人) | 是 |
|
||
| 42758 | 375.00 | 9336(有冰的帝君) | 9116(非洲人) | 是 |
|
||
| 43304 | 375.00 | 9305(新人) | 9116(非洲人) | 是 |
|
||
| 42761 | 375.00 | 9116(非洲人) | 9305(新人) | 是 |
|
||
| 46445 | 375.00 | 9230(巨欧小肥龙) | 9116(非洲人) | 是 |
|
||
| 46446 | 375.00 | 9230(巨欧小肥龙) | 9116(非洲人) | 是 |
|
||
| 46447 | 375.00 | 9230(巨欧小肥龙) | 9116(非洲人) | 是 |
|
||
| 46506 | 375.00 | 9209(程c) | 9116(非洲人) | 是 |
|
||
| 46507 | 375.00 | 9209(程c) | 9116(非洲人) | 是 |
|
||
| 52338 | 12.50 | 9094(范巴斯滕) | 9449(古利特) | 是 |
|
||
|
||
小计: 双方发货全取消,接收方后续兑换了积分 — 这属于**接收方对自有资产的合法操作**。
|
||
|
||
### 3.3 积分兑换核查
|
||
|
||
| 核查项 | 结果 |
|
||
|--------|------|
|
||
| 同一资产被多个用户兑换积分 | **0 个** |
|
||
| 同一资产被同一用户多次兑换积分 | **0 个** |
|
||
| 发送方在 points_ledger 中兑换已转赠资产 | **0 笔** |
|
||
| 发送方的已转赠资产 remark 含 redeemed | **1 笔**(用户 9116 转出给 9305 的资产 43304,后又转回 9116 兑换,属于正常来回转赠) |
|
||
|
||
### 3.4 转赠后接收方兑换积分明细
|
||
|
||
> 以下为合法行为,资产转赠后归接收方所有,接收方有权兑换
|
||
|
||
| 用户ID | 昵称 | 兑换笔数 | 兑换金额(元) | 性质 |
|
||
|--------|------|---------|------------|------|
|
||
| 9116 | 非洲人 | 30 | 10,737.00 | 合法 — 接收转赠后兑换 |
|
||
| 9110 | 极品官方内部号 | 24 | 446.60 | 合法 |
|
||
| 9305 | 新人 | 1 | 375.00 | 合法 |
|
||
| 9209 | 程c | 9 | 220.00 | 合法 |
|
||
| 9222 | 嗯?!!!! | 15 | 180.00 | 合法 |
|
||
| 9336 | 有冰的帝君 | 1 | 60.00 | 合法 |
|
||
| 9094 | 范巴斯滕救了一个美女 | 2 | 25.00 | 合法 |
|
||
| 9210 | 非酋 | 3 | 22.50 | 合法 |
|
||
| 9449 | 古利特使出了佛怒火莲 | 1 | 12.50 | 合法 |
|
||
| 9248 | 不出last退了 | 3 | 8.60 | 合法 |
|
||
| 9365 | 未命名 | 1 | 1.00 | 合法 |
|
||
| 9230 | 巨欧小肥龙 | 1 | 0.60 | 合法 |
|
||
| **合计** | | **91** | **12,088.80** | **全部合法** |
|
||
|
||
### 3.5 多次转赠记录(并发 bug 产生的脏数据)
|
||
|
||
| 资产ID | 转赠次数 | 路径 | 说明 |
|
||
|--------|---------|------|------|
|
||
| 42746 | 4 | 9336→9116 x4 | 同一操作并发触发4次 |
|
||
| 46668 | 4 | 9210→9116 x4 | 同上 |
|
||
| 43304 | 3 | 9305→9116, 9116→9305, 9305→9116 | 正常来回转赠 |
|
||
| 44152 | 3 | 9248→9116 x3 | 并发触发3次 |
|
||
| 46445 | 3 | 9230→9116 x3 | 并发触发3次 |
|
||
| 46667 | 3 | 9210→9116 x3 | 并发触发3次 |
|
||
| 51038 | 3 | 9210→9116, 9116→9210, 9210→9116 | 正常来回转赠 |
|
||
| 其他 6 个 | 2 | — | 并发触发2次 |
|
||
|
||
这些重复记录是脏数据,但未造成资产复制或积分重复。
|
||
|
||
---
|
||
|
||
## 四、修复状态
|
||
|
||
| 修复项 | 状态 | Commit |
|
||
|--------|------|--------|
|
||
| SELECT FOR UPDATE 防并发转赠 | ✅ 已合并 main | `8229b41` |
|
||
| RowsAffected 检查防静默失败 | ✅ 已合并 main | `8229b41` |
|
||
| ~~禁止转赠资产兑换积分~~ | ❌ 已回退 | `749464c`,属业务策略非 bug 修复 |
|
||
|
||
---
|
||
|
||
## 五、结论
|
||
|
||
1. **并发 bug 确实存在**:readDB 竞态 + RowsAffected 未检查,导致同一资产产生重复转赠记录和重复发货记录
|
||
2. **实际经济损失为 0 元**:
|
||
- 没有任何资产被真正发了两份货(重复发货记录中,发送方一侧全部已取消)
|
||
- 没有任何资产被重复兑换积分
|
||
- 发送方没有在转赠后又兑换同一资产的积分
|
||
3. **转赠后兑换积分是合法行为**:资产转赠后归接收方,接收方有权兑换
|
||
4. **Bug 已修复**,防止未来可能的真正损失
|