Files
zt-closet-backend/.codebuddy/plans/修复XPay发货推送伪造漏洞_9bf015a6.md
T
2026-09-11 01:41:52 +08:00

7.3 KiB

name, overview, todos
name overview todos
修复XPay发货推送伪造漏洞 修复 S1:XPay 发货推送在发放权益前增加微信侧订单真实性反查(QueryOrder),杜绝伪造推送资损;S2:彻底删除服务端自动道具同步逻辑(避免每次启动自动同步),道具同步交由用户管理后台负责,并更新 README 标注。
id content status
add-paid-status-helper 在 pkg/wechat/virtualpay.go 新增已支付状态常量与 IsOrderPaidStatus 白名单判断函数,并在 virtualpay_test.go 补单测(已支付/未支付/未知/空串) completed
id content status dependencies
add-reality-check 在 pkg/errcode/order_code.go 新增 70009,并在 internal/service/virtualpay.go 的 handleGoodsDeliver 本地校验后插入 verifyXPayOrderPaid 反查(fail-closed,附审计日志) completed
add-paid-status-helper
id content status
remove-sync-logic 删除服务端自动道具同步逻辑:main.go 调用、service 同步函数与常量、wechat 道具管理函数/类型/常量、goods.go 的 GoodsListVirtualSync 与 GoodsUpdateByID completed
id content status
update-docs 更新 README.md 标注道具同步运营约束并删除后台任务表同步行,同步更新 docs/代码审查报告-二次check.md 中 S1 与 S2 处置状态 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 补充单测:已支付 / 未支付 / 未知状态 / 空串。
  1. 反查校验函数(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) 之前。
  1. 错误码:
  • pkg/errcode/order_code.go 新增 OrderRealityCheckFailed = NewError(70009, "订单真实性校验失败")。
  1. 安全性与一致性:
  • 反查走 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 返回状态为准,实现时集中定义常量并注释「联调校正」。