New-API 爆高危漏洞:可伪造任意充值

行业快讯

开源 AI 中转站项目 New-API 被曝 Stripe Webhook 验签绕过漏洞,攻击者可伪造任意金额充值。官方已发布 v0.12.10 修复,所有站长应立即升级并排查历史订单。

New-API 爆高危漏洞:Stripe 验签形同虚设,攻击者可伪造任意金额充值

如果你用 New-API 搭了 AI 中转站并开启了 Stripe 收款——停下手里的活,先升级。

4 月 17 日,安全社区披露了开源项目 New-API 的一个高危漏洞:在特定配置下,攻击者可以完全绕过 Stripe Webhook 的签名校验,向系统伪造任意金额的充值事件。换句话说,不花一分钱,余额想充多少充多少。

项目维护者已在 v0.12.10 中修复了这个问题。但考虑到 New-API 在国内 AI 中转站圈子里的普及程度,这件事值得每一个站长认真对待。


漏洞到底出在哪?

要理解这个漏洞,先快速回顾一下 Stripe Webhook 的工作机制。

当用户在你的中转站完成付款后,Stripe 不会直接告诉你的前端"钱到了"——它走的是异步通知。Stripe 服务器会向你预先配置好的 Webhook URL 发送一个 POST 请求,里面包含支付事件的 JSON 数据(比如 checkout.session.completed)。你的服务端收到后,需要做一件至关重要的事:验签

验签的逻辑很简单:Stripe 在发送请求时,会用你在 Dashboard 里生成的 Webhook Signing Secret(通常以 whsec_ 开头)对请求体进行 HMAC 签名,放在 Stripe-Signature 请求头里。你的服务端用同一个 secret 重新计算签名,两边一比对,就能确认这个请求确实来自 Stripe,而不是某个攻击者伪造的。

问题就出在 New-API 对"secret 为空"这个边界情况的处理上。

在旧版本的代码中,逻辑大致是这样的:

// 伪代码,还原漏洞逻辑
func handleStripeWebhook(c *gin.Context) {
    payload, _ := io.ReadAll(c.Request.Body)
    sigHeader := c.GetHeader(\"Stripe-Signature\")
    
    // 从配置中读取 Webhook Secret
    endpointSecret := config.StripeWebhookSecret
    
    // 关键问题:即使 endpointSecret 为空,仍然尝试验签
    event, err := webhook.ConstructEvent(payload, sigHeader, endpointSecret)
    if err != nil {
        // 验签失败,但这里的行为取决于 Stripe SDK 对空 secret 的处理
        // ...
    }
    
    // 继续处理支付事件,计入余额...
}

正常情况下,如果站长开启了 Stripe 支付但没有配置 Webhook Signing Secret(或者配置为空字符串),代码应该直接拒绝一切 Webhook 请求——因为没有密钥就无法验证任何签名,放行等于裸奔。

但旧版 New-API 没有做这个前置判断。它仍然会把空的 secret 传给 Stripe SDK 的验签函数。而在某些条件下,这个验签过程可以被绕过,导致签名校验的安全边界完全失效

New-API Stripe Webhook 漏洞原理示意图,展示攻击者如何伪造支付事件绕过验签

攻击者只需要:

  1. 找到目标中转站的 Webhook 端点(通常是固定路径,比如 /api/stripe/webhook
  2. 构造一个伪造的 Stripe 支付成功事件 JSON
  3. 直接 POST 过去

系统就会老老实实地把"充值"记到对应账户的余额里。

这不是什么复杂的攻击链,甚至不需要什么高深的安全知识。只要知道 Stripe Webhook 的数据格式,写个 curl 命令就能完成。


影响范围有多大?

先说结论:不是所有 New-API 用户都受影响,但受影响的那部分很危险。

触发条件有两个,缺一不可:

  1. 启用了 Stripe 作为充值方式
  2. 没有正确配置 StripeWebhookSecret(为空或未设置)

如果你的中转站只用了其他支付方式(比如易支付、虎皮椒),或者你虽然用了 Stripe 但严格按照文档配置了 Webhook Secret,那这个漏洞对你没有直接影响。

但现实是,New-API 是目前国内 AI 中转站生态里最流行的开源项目之一。很多站长是"照着教程抄作业"搭建的,对 Stripe 的配置细节未必了如指掌。尤其是一些小站长,可能在测试阶段跳过了 Webhook Secret 的配置,上线后也没回头补上。

更麻烦的是,这类漏洞的利用不会留下特别明显的痕迹。伪造的充值在系统里看起来就是正常的 Stripe 回调,除非你逐笔核对 Stripe Dashboard 里的实际收款记录和系统里的充值记录,否则很难发现异常。


修复方案:v0.12.10 做了什么

在最新发布的 New-API v0.12.10 中,开发者的修复策略非常直接——在入口处加了一道硬门槛

// 修复后的逻辑(伪代码)
func handleStripeWebhook(c *gin.Context) {
    endpointSecret := config.StripeWebhookSecret
    
    // 新增:显式判断密钥是否为空
    if endpointSecret == \"\" {
        c.JSON(403, gin.H{\"error\": \"Stripe webhook secret not configured\"})
        return  // 直接终止,不再继续处理
    }
    
    payload, _ := io.ReadAll(c.Request.Body)
    sigHeader := c.GetHeader(\"Stripe-Signature\")
    
    event, err := webhook.ConstructEvent(payload, sigHeader, endpointSecret)
    if err != nil {
        c.JSON(400, gin.H{\"error\": \"Invalid signature\"})
        return
    }
    
    // 验签通过,继续处理...
}

逻辑很简单:如果你开了 Stripe 支付但没配 secret,系统直接返回 403 Forbidden 并终止处理。不给任何绕过的机会。

这是一个典型的"防御性编程"修复——与其信任下游函数对异常输入的处理,不如在最上层就把非法状态拦截掉。


站长现在应该做什么

如果你在用 New-API 运营中转站,以下是一份优先级从高到低的行动清单:

1. 立即升级到 v0.12.10 或更高版本

这是最紧急的一步。不管你有没有开 Stripe,升级本身没有副作用,先升了再说。

# 如果你是 Docker 部署
docker pull calciumion/new-api:latest
docker compose down && docker compose up -d

# 如果你是源码部署
git pull origin main
go build -o new-api
# 重启服务

2. 检查 Stripe Webhook Secret 配置

登录你的 Stripe Dashboard → Developers → Webhooks,确认你的 Webhook 端点配置正确,并且 Signing Secret 已经复制到 New-API 的配置中。

如果你之前没配,现在配上。如果你根本不用 Stripe,考虑在 New-API 后台关闭 Stripe 支付选项。

3. 排查历史充值记录

这一步最费时间,但可能最重要。

  • 导出 New-API 数据库中近期所有通过 Stripe 渠道完成的充值记录
  • 与 Stripe Dashboard 中的实际收款记录逐一比对
  • 重点关注那些金额异常偏大充值时间密集、或者对应用户消费模式突然变化的记录

如果发现有充值记录在 Stripe 端找不到对应的实际付款,那大概率就是被利用了。

4. 检查 Webhook 端点的网络暴露情况

理想情况下,你的 Webhook 端点应该只接受来自 Stripe IP 段的请求。虽然验签本身是最后一道防线,但在网络层做一层过滤(比如 Nginx 配置 IP 白名单)可以进一步降低风险。

Stripe 官方有公布其 Webhook 请求的 IP 范围,可以参考其文档进行配置。


这件事的更大启示

这个漏洞本身的技术含量不高,修复也很简单。但它暴露了一个在开源 AI 基础设施领域越来越突出的问题:很多项目在快速迭代功能的同时,安全性的优先级被严重低估了。

New-API 这类中转站项目,本质上是在做一个涉及资金流转的 SaaS 系统。用户充值、消费、余额管理——这些都是金融级别的操作。但很多开源项目的安全审计力度,远远配不上它所承担的责任。

这不是在指责 New-API 的开发者。作为一个开源项目,能做到这个完成度已经很不容易。问题在于,当一个开源项目被大量用于生产环境时,社区需要有更系统化的安全审查机制,而不是等漏洞被发现甚至被利用后才亡羊补牢。

类似的问题在 Webhook 验签场景中其实屡见不鲜。不只是 Stripe,微信支付、支付宝的回调验签也出过类似的"空密钥绕过"问题。核心教训就一条:永远不要信任外部输入,永远显式校验前置条件。

对于正在使用各类 AI 中转站服务的开发者来说,这件事也是一个提醒:选择中转站服务时,除了看价格和模型覆盖度,平台的安全能力和运维成熟度同样重要。像 OpenAI Hub 这样的商业化聚合平台,在支付安全和 API 密钥管理上会有更完善的保障体系,这也是自建方案和托管服务之间的一个核心差异点。


Webhook 安全的几条通用原则

趁这个机会,整理几条 Webhook 安全的通用最佳实践,不限于 Stripe,适用于所有涉及异步回调的支付集成:

  1. 签名密钥不能为空:在代码层面显式检查,空密钥直接拒绝请求
  2. 使用最新的签名方案:Stripe 的 v1 签名使用 HMAC-SHA256,确保你没有用已废弃的旧方案
  3. 校验时间戳容差:Stripe 签名中包含时间戳,应设置合理的容差窗口(通常 5 分钟),防止重放攻击
  4. 幂等处理:同一个事件 ID 不应被重复处理,用数据库唯一约束或 Redis 去重
  5. 网络层过滤:在 Nginx/CDN 层限制 Webhook 端点只接受支付平台的 IP
  6. 日志与告警:对所有 Webhook 请求记录详细日志,对验签失败的请求设置告警
  7. 定期轮换密钥:Stripe 支持同时激活多个 Webhook Secret,可以实现无缝轮换

这些原则看起来都是常识,但在实际项目中,尤其是快速迭代的开源项目里,常识往往是最先被省略的东西。


写在最后

这次 New-API 的漏洞事件,从发现到修复的响应速度还是比较快的。v0.12.10 在漏洞披露后很快发布,开发者也在 changelog 中明确说明了问题和修复内容。这是开源社区应有的透明度。

但"快速修复"不等于"没有损失"。在漏洞被公开之前,它已经存在了多久?有多少站点在不知情的情况下暴露在风险中?这些问题可能永远没有答案。

所以,如果你是 New-API 的用户:现在就去升级,现在就去查账。

不要等到下一篇文章的标题变成"某 AI 中转站因漏洞被薅数万元"的时候,才发现主角是自己。


参考来源