5.5 KiB
5.5 KiB
name, overview, todos
| name | overview | todos | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 修复XPay发货推送伪造漏洞 | 修复 S1:XPay 发货推送在条件流转发放权益前增加微信侧订单真实性反查(QueryOrder),杜绝伪造推送零成本获取权益;S2 不做代码修复,仅在 README 标注道具价格同步由管理后台负责的运维约束。 |
|
用户需求
- 修复 S1(资损漏洞):XPay 发货推送无验签,攻击者可伪造
xpay_goods_deliver_notify推送零成本获取权益。按已确认方案修复:发货前调用带 HMAC-SHA256 签名的微信服务端 APIquery_order反查订单真实支付状态,确认「已支付 + 金额一致 + 道具一致」后才流转发放,推送仅作触发信号。 - S2 仅文档标注:不做道具周期同步代码。用户有自己的管理后台负责道具同步,仅在 README 标注运营约束:运营改价后必须通过管理后台同步道具(或重启服务),否则微信侧道具价格与本地不一致,会导致发货金额校验拒绝(用户付款拿不到权益)或差价资损。
范围边界
- 仅修改虚拟支付链路:发货推送处理(
handleGoodsDeliver)增加真实性反查;普通微信支付 V3 回调链路(官方 SDK 验签)不涉及 - 退款推送伪造风险与 M1(RefundStatus 空串误判)不在本次范围
- 交付物:修复后的代码 + 单测 + README 与审查报告文档更新
技术方案
实现策略
在 handleGoodsDeliver 中,幂等锁内、全部本地校验(pay_channel / product_id / amount / env)通过之后、条件流转(Pending→Paid)之前,插入微信侧订单真实性反查。反查失败或结果不符时返回非 nil 错误码,使 XPayNotify 回 ErrCode:1,微信按推送重试策略重试(fail-closed)。
关键设计
1. 支付状态白名单判断(pkg/wechat 层,与微信枚举集中管理)
- 在
pkg/wechat/virtualpay.go新增导出函数IsOrderPaidStatus(status string) bool与状态常量(如xpayOrderStatusPaid,枚举值以官方文档为准,联调校正) - 未知状态按「未支付」处理并告警,保证 fail-closed
internal/service/virtualpay.go调用该函数,不散落魔法字符串
2. 反查校验函数(service 层)
- 新增
verifyXPayOrderPaid(ctx, order, deliver) *errcode.Error: deliver.OpenID为空 → 直接拒绝(返回新错误码 70009),审计日志- 调用
wechat.QueryOrder(ctx, deliver.OpenID, deliver.OutTradeNo, "") - 接口错误 → 返回非 nil 错误(fail-closed),日志携带 out_trade_no 与 error
!wechat.IsOrderPaidStatus(info.Status)→ 拒绝 + 日志(含 query 返回状态)info.PayFee != order.PayAmount→ 拒绝 + 日志(金额不符,资损线索)info.ProductID != order.XPayProductID→ 拒绝 + 日志(道具不符)- 全部通过 → 返回 nil,继续原有条件流转与
finalizeVirtualPaidOrder发货 - 插入位置:
handleGoodsDeliver中 env 校验之后、OrderUpdateStatusByOrderNo(Pending→Paid)之前
3. 错误码
pkg/errcode/order_code.go新增OrderRealityCheckFailed = NewError(70009, "订单真实性校验失败"),语义用于「反查未通过/异常」,触发微信重试
4. 安全性与一致性
- 反查走
callXPay的 HMAC 签名(激活环境 AppKey),攻击者无法伪造;伪造推送在微信侧无支付记录,反查必然失败 → 拒绝发货 - 真实支付后微信必然推送且反查必可查到已支付,重试机制保证最终发货
- 反查使用激活环境(现网/沙箱),与订单
XPayEnv快照已通过本地校验一致 - 成功路径沿用现有
finalizeVirtualPaidOrder(callback_raw 持久化原始推送),审计闭环不变
性能与稳定性
- 反查仅在幂等锁内执行一次(每笔发货推送一次 HTTP 调用,10s 超时),不引入额外热点
- 沙箱环境
query_order行为需联调确认(代码注释保持与现有「以官方文档为准」风格一致)
文件修改清单
zt-closet-backend/
├── pkg/wechat/virtualpay.go # [MODIFY] 新增 XPay 订单已支付状态常量与 IsOrderPaidStatus 白名单判断函数
├── pkg/wechat/virtualpay_test.go # [MODIFY] 补充 IsOrderPaidStatus 单测:已支付/未支付/未知状态/空串
├── pkg/errcode/order_code.go # [MODIFY] 新增 70009 订单真实性校验失败错误码
├── internal/service/virtualpay.go # [MODIFY] handleGoodsDeliver 插入 verifyXPayOrderPaid 反查(fail-closed)+审计日志
├── README.md # [MODIFY] S2 标注:道具同步由管理后台负责,改价必须同步(风险说明)
└── docs/代码审查报告-二次check.md # [MODIFY] S1 标注「已修复」,S2 标注「已确认由管理后台同步,仅文档标注」