111 lines
7.3 KiB
Markdown
111 lines
7.3 KiB
Markdown
---
|
|
name: 修复XPay发货推送伪造漏洞
|
|
overview: 修复 S1:XPay 发货推送在发放权益前增加微信侧订单真实性反查(QueryOrder),杜绝伪造推送资损;S2:彻底删除服务端自动道具同步逻辑(避免每次启动自动同步),道具同步交由用户管理后台负责,并更新 README 标注。
|
|
todos:
|
|
- id: add-paid-status-helper
|
|
content: 在 pkg/wechat/virtualpay.go 新增已支付状态常量与 IsOrderPaidStatus 白名单判断函数,并在 virtualpay_test.go 补单测(已支付/未支付/未知/空串)
|
|
status: completed
|
|
- id: add-reality-check
|
|
content: 在 pkg/errcode/order_code.go 新增 70009,并在 internal/service/virtualpay.go 的 handleGoodsDeliver 本地校验后插入 verifyXPayOrderPaid 反查(fail-closed,附审计日志)
|
|
status: completed
|
|
dependencies:
|
|
- add-paid-status-helper
|
|
- id: remove-sync-logic
|
|
content: 删除服务端自动道具同步逻辑:main.go 调用、service 同步函数与常量、wechat 道具管理函数/类型/常量、goods.go 的 GoodsListVirtualSync 与 GoodsUpdateByID
|
|
status: completed
|
|
- id: update-docs
|
|
content: 更新 README.md 标注道具同步运营约束并删除后台任务表同步行,同步更新 docs/代码审查报告-二次check.md 中 S1 与 S2 处置状态
|
|
status: completed
|
|
---
|
|
|
|
## 用户需求
|
|
|
|
1. **修复 S1(资损漏洞)**:XPay 发货推送无验签且缺少订单真实性反查,攻击者可伪造 `xpay_goods_deliver_notify` 推送使订单 Pending→Paid 零成本获取权益。修复方式:发货前调用带 HMAC-SHA256 签名的微信服务端 API `query_order` 反查订单真实支付状态,确认「已支付 + 金额一致 + 道具一致」后才流转发放,推送仅作触发信号。
|
|
|
|
2. **S2 删除服务端自动同步逻辑**:删除服务启动时自动执行的虚拟支付道具同步代码,避免每次运行服务端自动同步;道具同步改由用户自己的管理后台负责。同时在 README 标注运营约束:运营改价后必须通过管理后台同步道具(或重启服务),否则微信侧道具价格与本地不一致会导致发货金额校验拒绝(用户付款拿不到权益)或差价资损。
|
|
|
|
## 范围边界
|
|
|
|
- 仅修改虚拟支付链路:发货推送处理(`handleGoodsDeliver`)增加真实性反查;普通微信支付 V3 回调链路(官方 SDK 验签)不涉及。
|
|
- 删除服务端道具同步链路(main 触发、service 同步逻辑、wechat 道具管理函数、同步专用 DAO),保留商品表 `product_id`/`product_status` 字段与 `GoodsProduct*` 状态常量(下单校验依赖)。
|
|
- 退款推送伪造风险与 M1(RefundStatus 空串误判)不在本次范围。
|
|
|
|
## 技术方案
|
|
|
|
### S1:发货推送真实性反查
|
|
|
|
**策略**:在 `handleGoodsDeliver` 中,幂等锁内、全部本地校验(`pay_channel` / `product_id` / `amount` / `env`)通过之后、条件流转(Pending→Paid)之前,插入微信侧订单反查。反查失败或结果不符时返回非 nil 错误码,使 `XPayNotify` 回 `ErrCode:1`,微信按推送重试策略重试(fail-closed)。
|
|
|
|
**关键设计**:
|
|
|
|
1. 支付状态白名单判断(`pkg/wechat` 层集中管理):
|
|
|
|
- 新增「已支付」状态常量与 `IsOrderPaidStatus(status string) bool` 白名单判断函数;未知状态/空串按未支付处理并告警。
|
|
- `virtualpay_test.go` 补充单测:已支付 / 未支付 / 未知状态 / 空串。
|
|
|
|
2. 反查校验函数(service 层):
|
|
|
|
- 新增 `verifyXPayOrderPaid(ctx, order, deliver) *errcode.Error`,校验顺序:
|
|
- `deliver.OpenID` 为空 → 拒绝
|
|
- `wechat.QueryOrder(ctx, deliver.OpenID, deliver.OutTradeNo, "")` 接口错误 → 拒绝
|
|
- `!wechat.IsOrderPaidStatus(info.Status)` → 拒绝
|
|
- `info.PayFee != order.PayAmount` → 拒绝
|
|
- `info.ProductID != order.XPayProductID` → 拒绝
|
|
- 全部通过 → nil
|
|
- 插入位置:`handleGoodsDeliver` 中 env 校验之后、`OrderUpdateStatusByOrderNo(Pending→Paid)` 之前。
|
|
|
|
3. 错误码:
|
|
|
|
- `pkg/errcode/order_code.go` 新增 `OrderRealityCheckFailed = NewError(70009, "订单真实性校验失败")`。
|
|
|
|
4. 安全性与一致性:
|
|
|
|
- 反查走 `callXPay` 的 HMAC 签名(激活环境 AppKey),攻击者无法伪造;伪造推送在微信侧无支付记录,反查必然失败 → 拒绝发货。
|
|
- 真实支付后反查必能查到已支付,微信推送重试保证最终发货。
|
|
- 反查使用激活环境,与订单 `XPayEnv` 快照经本地校验一致。
|
|
- 成功路径沿用现有 `finalizeVirtualPaidOrder`(callback_raw 持久化原始推送),审计闭环不变。
|
|
- 审计日志携带 `out_trade_no` / query 返回状态 / 金额,失败不落库(最小改动)。
|
|
|
|
**性能**:反查仅在幂等锁内执行一次(每笔发货推送一次 HTTP 调用,10s 超时),无额外热点。沙箱环境 `query_order` 行为需联调确认(代码注释保持「以官方文档为准」风格)。
|
|
|
|
### S2:删除服务端自动道具同步逻辑
|
|
|
|
**删除内容**:
|
|
|
|
- `main.go`:`service.StartVirtualGoodsSync()` 调用及注释
|
|
- `internal/service/virtualpay.go`:`StartVirtualGoodsSync` / `SyncVirtualGoods` / `syncOneGoods` / `pollGoodsTask` 及 4 个同步常量
|
|
- `pkg/wechat/virtualpay.go`:`StartUploadGoods` / `QueryUploadGoods` / `StartPublishGoods` / `QueryPublishGoods`、`XPayGoodsItem` / `XPayTaskResult`、4 个道具 path 常量
|
|
- `internal/mysqldao/goods.go`:`GoodsListVirtualSync` / `GoodsUpdateByID`(删除后无引用)
|
|
|
|
**保留内容**:
|
|
|
|
- 商品表 `product_id` / `product_status` 字段(管理后台回填,下单校验依赖)
|
|
- `GoodsProductUnpublished/Publishing/Published/PublishFail` 常量(下单校验与状态语义一致)
|
|
- `setupVirtualPayClient` / `GetVirtualPayEnv` / `QueryOrder`(下单、反查、回调仍需客户端)
|
|
- `GoodsGetByCode` / `GoodsGetByID` / `GoodsList`(商品模块与下单使用)
|
|
|
|
**README 标注**:
|
|
|
|
- 道具同步由管理后台负责,服务端不再自动同步
|
|
- 运营改价后必须通过管理后台同步道具(或重启服务),否则微信侧价格与本地不一致会导致发货金额校验拒绝或差价资损
|
|
- 删除后台任务表中「虚拟支付道具同步」行,同步更新交易与权益流程相关描述
|
|
|
|
### 文件修改清单
|
|
|
|
```
|
|
zt-closet-backend/
|
|
├── pkg/wechat/virtualpay.go # [MODIFY] 新增已支付状态常量与 IsOrderPaidStatus;删除道具管理函数/类型/path 常量
|
|
├── pkg/wechat/virtualpay_test.go # [MODIFY] 补 IsOrderPaidStatus 单测
|
|
├── pkg/errcode/order_code.go # [MODIFY] 新增 70009 错误码
|
|
├── internal/service/virtualpay.go # [MODIFY] 插入 verifyXPayOrderPaid 反查;删除同步逻辑与常量
|
|
├── internal/mysqldao/goods.go # [MODIFY] 删除 GoodsListVirtualSync / GoodsUpdateByID
|
|
├── main.go # [MODIFY] 删除 StartVirtualGoodsSync 调用
|
|
├── README.md # [MODIFY] 标注道具同步运营约束,删除后台任务表同步行
|
|
└── docs/代码审查报告-二次check.md # [MODIFY] S1 标「已修复」,S2 标「已删除自动同步,由管理后台负责」
|
|
```
|
|
|
|
### 实现注意事项
|
|
|
|
- 删除同步逻辑后需确认 `internal/service/virtualpay.go` 的 `time` 包仍被锁 TTL 使用、`wechat` 包仍被 BuildPaymentSign 使用,避免编译错误。
|
|
- 删除 `XPayGoodsItem` 后需确认无其他文件引用(搜索已确认仅同步逻辑引用)。
|
|
- 反查状态白名单的枚举值以微信官方 `query_order` 返回状态为准,实现时集中定义常量并注释「联调校正」。 |