针对未认证的 Hyperswitch Worldpayxml webhook 漏洞(CWE-345/306)的概念验证,该漏洞可伪造 SETTLED XML,从而在未经 PSP 捕获的情况下将支付标记为成功。
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · hyperswitch-unsigned-webhook
技术分析文章:https://abraxaslabs.tech/research/hyperswitch-unsigned-webhook
Hyperswitch 2026.09.21.0 - Juspay
Worldpayxml::verify_webhook_source 返回 Ok(true),并附有一条注释称验证通过 mTLS 完成。但该路由上并没有 mTLS。该函数忽略请求体和请求头。incoming.rs 随后信任 source_verified,并在没有 PSync 的情况下走 HandleResponse。LastEvent::Settled 映射为 PaymentIntentSuccess。默认的 IncomingWebhook 算法是 NoAlgorithm。BitPay 和 Shift4 继承该算法。Worldpayxml 甚至走不到那一步。它直接短路返回。
未经认证的伪造 SETTLED XML 即可将支付标记为成功。没有 Worldpay 捕获。没有客户端证书。
| ID | 暂无 CVE |
| CWE | CWE-345, CWE-306 |
| CVSS | 高危:7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N |
| 产品 | Hyperswitch |
| 受影响版本 | 截至 2026.09.21.0 的 Worldpayxml 入站 webhook |
| 认证 | 无需认证;需要 merchant_id 和 orderCode |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅限 127.0.0.1 |
需要 merchant_id(出现在每个 Hyperswitch 仪表盘 URL 中)以及与该次尝试的 connector_transaction_id 相等的 orderCode。向 /webhooks/{merchant_id}/worldpayxml POST 一个未签名的 SETTLED。如果 source_verified 为 false,incoming 会走 Trigger 并对连接器执行 PSync。Worldpayxml 永远不会走这条路径。支付意图变为 Charged。实验环境:status=succeeded。伪造 XML。没有 PSP。
猜测支付 ID 不是漏洞所在。为已经见过的 ID 伪造 SETTLED 才是。这不是 shell。也不是真实的 Worldpay 结算。
同一产品的另一个漏洞:payout confirm unbound client_secret。
我读了 Ok(true),然后是 HandleResponse 与 Trigger 的对比,再是 Settled -> PaymentIntentSuccess。注释说用了 mTLS。而实验环境中的 POST 没有客户端证书。
第一个注意到这一点的客户端会针对 requires_confirmation 状态 POST webhook。incoming 不会将尚未离开确认状态的支付标记为 Charged。要么给该次尝试打上标记,要么等到 requires_capture / processing。实验环境做了标记。该标记是实验环境的测试夹具。真实环境中的尝试已经带有连接器 ID。而 webhook 没有。
已记录的弯路:在真正执行验证的连接器上使用签名 HMAC(本路径并不验证);把 webhook 的 HTTP 200 当作判定依据(应轮询 GET /payments/{id} 直到 status=succeeded);orderCode 不等于 connector_transaction_id;反弹 shell。都是做戏。
然后:商户、API key、worldpayxml MCA、以 confirm=false 创建支付、打上 connector_transaction_id 标记、未签名 SETTLED、查询。
cd lab
./run.sh
仅针对 http://127.0.0.1:18082。run.sh 会将标签 2026.09.21.0 克隆到 lab/hyperswitch-src,用于配置和迁移。
IOC status_before=requires_confirmation
IOC webhook status=200 unsigned lastEvent=SETTLED
IOC poll status=succeeded
SUCCESS Hyperswitch unsigned Worldpayxml webhook Charged
验证 Worldpay 通知(签名 / mTLS,即该路由实际终止的机制)。不要对从空操作返回 Ok(true) 的连接器调用 HandleResponse。未签名的 SETTLED 不得将 intent 推进为 succeeded。
worldpayxml.rs · transformers.rs · incoming.rs · webhooks.rs · crypto.rsGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。不提供任何担保。