# 【微信支付 V3】WxPayValidator 未校验应答时间戳,缺少重放攻击防护(Wechatpay-Timestamp 未做新鲜度校验) > 漏洞类型:重放攻击防护缺失 > 影响组件:`weixin-java-pay` / `WxPayValidator` > 风险场景:链路窃听、中间人重放合法应答 ## 1. 环境 | 项目 | 内容 | | --- | --- | | SDK 版本 | `binarywang/WxJava` develop 分支(4.8.x 范围内),含 `WxPayValidator` 的所有已发布 4.x 版本均受影响 | | 模块 | `weixin-java-pay` | | Java 版本 | `JDK 8+` | | 接入方式 | 微信支付 V3(`WxPayService` + `AutoUpdateCertificatesVerifier` / `CertificatesVerifier`,`WxPayV3HttpClientBuilder` 构建的 `CloseableHttpClient`) | ## 2. 复现步骤 1. 按官方方式初始化微信支付 V3 的 `WxPayService`,使用 `WxPayV3HttpClientBuilder` 拿到 `HttpClient`(内部通过 `SignatureExec` 对 2xx 应答做 `WxPayValidator.validate`)。 2. 调用任意 V3 接口(如“查询订单”),正常拿到应答。 3. 攻击者截获该次应答,包含: - `Wechatpay-Timestamp` - `Wechatpay-Nonce` - `Wechatpay-Signature` - `Wechatpay-Serial` - JSON body 4. 在数分钟、数小时或任意时间后,将此应答原样重放给调用方,或由链路上的中间人重放。 ## 3. 期望行为 依据微信支付 V3 签名验证规范,商户侧应校验 `Wechatpay-Timestamp` 与本地时间之差不超过 5 分钟;超时的应答应判定为验签失败,以提供重放攻击防护。 ## 4. 实际行为 `WxPayValidator.validate(...)` 只校验四个头部是否存在,并做密码学验签,从不校验时间戳新鲜度。 方法内明确留有: ```java // todo: check timestamp ``` 但未实现。 因此,重放的旧应答仍被判定为验签通过,`SignatureExec` 不会抛出异常,调用方拿到被重放的数据。 ## 5. 根因分析 - 文件:`weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java` - 方法:`validate(CloseableHttpResponse response)`(约第 27–34 行) - 相关代码: ```java // todo: check timestamp if (timestamp == null || nonce == null || serialNo == null || sign == null) { return false; } String message = buildMessage(response); return verifier.verify(serialNo.getValue(), message.getBytes(StandardCharsets.UTF_8), sign.getValue()); ``` - 原因: - `verifier.verify(...)` 仅使用平台证书对 `timestamp\nnonce\nbody\n` 做 RSA 验签。 - 时间戳本身被当作“参与签名的字符串”,而不是“需校验的时间量”。 - 缺失类似 `|当前秒 - Long.parseLong(timestamp)| <= 300` 的新鲜度判断,导致重放攻击防护缺位。 ## 6. 影响 在存在链路窃听或中间人的场景下,合法应答可被重放并被判为有效,违反微信支付 V3 防重放要求。 同时,`validate` 对 `Content-Type` 非 `application/json` 的 2xx 应答直接 `return true` 跳过验签(同方法首行),进一步放大风险。 ## 7. 建议修复方向 在 `validate` 中补充时间戳新鲜度校验,例如: ```java long wechatTimestamp = Long.parseLong(timestamp.getValue()); long diff = Math.abs(System.currentTimeMillis() / 1000 - wechatTimestamp); if (diff > 300) { log.warn("微信支付应答时间戳超时,疑似重放攻击:{} 秒", diff); return false; } ``` 并建议将首行的“非 JSON 直接 `return true`”改为: - 对未知或非 JSON 的 2xx 应答默认拒绝;或 - 至少记录告警,避免绕过验签。 ## 8. 参考 - 微信支付 V3 签名验证规范:应答/回调需校验 `Wechatpay-Timestamp` 与本地时间差不超过 5 分钟,防止重放。 - 相关代码路径:`SignatureExec.executeWithSignature(...)` -> `validator.validate(response)`。
Activity
[-]【Bug】weixin-java-pay:WxPayValidator 未校验应答时间戳,存在重放攻击风险[/-][+]【安全】WxPayValidator 未校验微信支付 V3 应答时间戳(Wechatpay-Timestamp),缺少重放攻击防护[/+]sisyfy-Zhang commentedon Oct 9, 2026
您好,我想认领处理这条issue。
方案是给 WxPayValidator 加一个时间戳容差,默认 5 分钟,和微信官方 SDK 的做法保持一致:在做验签之前先比较 Wechatpay-Timestamp 和本地时间,差值超出容差就直接判为验签失败,并打一条明确的日志方便排查。现有的单参构造器不动,默认行为就是 5 分钟;另外提供一个可以传容差的构造器,容差传 0 表示关闭这项校验,给时钟同步确实有问题的环境留一个显式的口子,总比用户因为验签失败去把整个 Validator 换掉要好。
会补上单元测试覆盖正常、超前、滞后、非数字、缺头和关闭校验这几种情况。今天内把 PR 提到 develop。
binarywang commentedon Oct 9, 2026
可以,谢谢
🐛 binarywang#4133 【微信支付】拒绝负数应答时间戳,避免溢出绕过新鲜度校验
🐛 #4133 【微信支付】V3 应答验签增加时间戳新鲜度校验,防止重放