Featured image of post newapi Stripe Webhook 充值漏洞深度分析 - 空密钥绕过支付验证

newapi Stripe Webhook 充值漏洞深度分析 - 空密钥绕过支付验证

深度分析 newapi 充值漏洞:攻击者如何利用空 Stripe 密钥绕过 HMAC-SHA256 签名验证,实现零成本任意金额充值

逍遥微信二维码

联系逍遥

扫码添加微信,或点击下方按钮复制联系方式。

进微信群请联系博主,各位觉得文章对你有帮助的话可否打赏一些呀~



⚠️ 时效性提醒(2026-07 更新):该漏洞已在 newapi v0.12.10 中修复。如果你正在使用 newapi,请确认已升级到修复版本。本文保留仅供支付安全设计参考。


漏洞概述

2026 年 4 月 16 日,LINUX DO 社区用户披露了 newapi 项目中的一个严重充值漏洞。攻击者可在未配置 Stripe 密钥的情况下,通过伪造签名绕过支付验证,实现零成本任意金额充值

影响版本:v0.12.10 之前 修复版本:v0.12.10 漏洞类型:签名验证绕过 / 支付逻辑缺陷 风险等级:🔴 严重(直接资金损失)


攻击流程详解

第一阶段:信息收集

1. 发现目标暴露了 Stripe Webhook 端点 /api/stripe/webhook
2. 通过初始审计(或黑盒探测)确认服务端使用了空的 StripeWebhookSecret
3. 获取或猜测 client_reference_id 的格式(如 USR + 数字 + 随机字符串 + 时间戳)

攻击者首先识别出目标的 Webhook 端点,并探测到服务端未正确配置 webhook_secret(空字符串)。这是漏洞利用的关键前提。

第二阶段:伪造签名

Stripe 的签名机制:

signed_payload = "{timestamp}.{json_body}"
v1 = HMAC-SHA256(webhook_secret, signed_payload)
Header = "t={timestamp},v1={v1}"

当 webhook_secret = “"(空字符串)时:

  • HMAC-SHA256 不会报错,正常计算
  • 任何人都能对任意 payload 算出与服务端一致的签名
  • 服务端调用 webhook.ConstructEventWithOptions() 验签 → 通过 ✅

这是整个漏洞的核心:空密钥仍能通过 HMAC 计算,导致签名验证形同虚设

第三阶段:构造恶意事件

伪造一个 checkout.session.completed 事件:

{
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "client_reference_id": "目标用户/订单 ID",
      "status": "complete",
      "payment_status": "paid",
      "amount_total": 任意金额
    }
  }
}

攻击者可指定任意 client_reference_idamount_total,实现定向充值和任意金额。

第四阶段:发送请求

向 Webhook 端点发送带有伪造签名的 POST 请求,服务端处理流程:

收到请求
用空密钥验签 → 通过 ✅
解析事件类型 → checkout.session.completed
读取 client_reference_id 查找订单
标记订单已支付,给用户充值

第五阶段:结果

项目说明
用户余额被充入攻击者指定的金额
Stripe 实际收款$0(从未发生真实支付)
Stripe Dashboard无对应交易记录
服务端日志看起来像正常的 Webhook 回调

技术原理分析

为什么空密钥能通过验签?

HMAC-SHA256 算法本身不验证密钥长度。当密钥为空字符串时:

import hmac
import hashlib

# 空密钥仍能计算
signature = hmac.new(
    b"",  # 空密钥
    b"timestamp.body",
    hashlib.sha256
).hexdigest()
# 结果:有效的 64 字符十六进制字符串

服务端代码若未检查 webhook_secret 是否为空,就会用空密钥验签,导致攻击者可以:

  1. 获取相同的 payload
  2. 用空密钥计算相同的签名
  3. 伪造出"合法"的请求

正确的防御姿势

# ❌ 错误:未检查密钥是否为空
if not webhook_secret:
    webhook_secret = ""  # 危险!

# ✅ 正确:强制要求配置密钥
if not webhook_secret or len(webhook_secret) < 32:
    raise ConfigurationError("Stripe webhook secret must be configured")

影响范围

直接受影响

  • 所有使用 newapi v0.12.10 之前版本
  • 未配置或错误配置 StripeWebhookSecret 的实例
  • 依赖 Stripe Webhook 进行充值确认的业务

潜在风险

  • 资金损失:攻击者可零成本获取服务额度
  • 数据污染:虚假交易记录污染财务数据
  • 合规风险:支付审计无法通过

修复建议

立即措施

  1. 升级版本:立即升级到 v0.12.10 或更高版本
  2. 检查配置:确认 StripeWebhookSecret 已正确配置(非空)
  3. 审计日志:检查历史 Webhook 记录,排查异常充值

长期加固

  1. 启动检查:服务启动时强制验证关键配置项
  2. 签名验证增强:除 HMAC 外,增加时间戳窗口校验
  3. 对账机制:定期与 Stripe Dashboard 对账,发现不一致立即告警
  4. 监控告警:对大额充值、频繁充值设置阈值告警

代码修复示例

v0.12.10 的修复逻辑(推测):

// 修复前
func (s *Service) VerifyWebhook(payload []byte, signature string) (*Event, error) {
    secret := s.config.StripeWebhookSecret  // 可能为空
    event, err := webhook.ConstructEvent(payload, signature, secret)
    return &event, err
}

// 修复后
func (s *Service) VerifyWebhook(payload []byte, signature string) (*Event, error) {
    if s.config.StripeWebhookSecret == "" {
        return nil, errors.New("stripe webhook secret not configured")
    }
    event, err := webhook.ConstructEvent(payload, signature, s.config.StripeWebhookSecret)
    return &event, err
}

总结

这是一个典型的配置缺陷 + 逻辑漏洞组合:

  1. 配置缺陷:允许空密钥存在
  2. 逻辑漏洞:未验证密钥有效性即进行签名计算

教训:安全配置项必须在上层进行有效性校验,不能依赖底层库的"隐式行为”。空字符串不是"未配置",而是"配置了空值"——这两者有本质区别。


参考资料


本文基于社区披露信息进行分析,仅供安全研究参考。请合法合规使用,未经授权不得对任何系统进行测试。

站长推荐
Hello World | 开启技术博客之旅
从这里开始阅读本站的网络安全、AI 与技术实践内容。
查看文章 →
订阅更新
通过 RSS 阅读本站
不依赖平台算法,第一时间获取新文章。
订阅 RSS →
站长推荐
Hello World | 开启技术博客之旅
从这里开始阅读本站的网络安全、AI 与技术实践内容。
查看文章 →
订阅更新
通过 RSS 阅读本站
不依赖平台算法,第一时间获取新文章。
订阅 RSS →