Skip to content

【安全】WxPayValidator 未校验微信支付 V3 应答时间戳(Wechatpay-Timestamp),缺少重放攻击防护 #4133

Description

@konlue

【微信支付 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(...) 只校验四个头部是否存在,并做密码学验签,从不校验时间戳新鲜度。

方法内明确留有:

// todo: check timestamp

但未实现。

因此,重放的旧应答仍被判定为验签通过,SignatureExec 不会抛出异常,调用方拿到被重放的数据。

5. 根因分析

  • 文件:weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java
  • 方法:validate(CloseableHttpResponse response)(约第 27–34 行)
  • 相关代码:
// 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 中补充时间戳新鲜度校验,例如:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions