跳到正文

先看地址,再开连接

2026年9月15日 3 分钟读完 Paymos Tech Paymos Tech
一条橙色虚线从白墙的开口进去,在墙后绕一圈再从原口出来,墙后两台服务器方块没有动过——Paymos SSRF 插图

要点速览

webhook SSRF 说的是:客户注册回调 URL,由一台站在你网络里面的服务器去取它。 拦得住的检查跑在 URL 解析出来的地址上,不看 URL 文本,而且要能挺过一次 在放行之后才到的重定向。OWASP 在 2025 年不再给 SSRF 单独的标题,把 CWE-918 归进失效的访问控制,这个位置正好说中要害:请求是真的,够得着什么,取决于谁发的。

webhook 发送方要去取别人填的 URL。服务端请求伪造就这么点事。支付平台还是故意把它做出来了,因为回调正是商户系统得知账单已付的那条路。

让功能变成攻击的,是这次请求的出发点。投递跑在你的边界里面,那台机器有一张路由表,有公网上任何浏览器都没有的出口身份。有人填上地址,你的平台替他连过去,按计划,带重试。

拦得住的检查站在解析出来的地址上:连接建立之前查一次,重定向想在放行之后换目标时再查一次。下面讲这个检查能站在哪儿,以及守住它会让另一头的商户付出什么。

webhook SSRF 是什么?

攻击方挑你的服务器把请求发去哪儿,入口正是那个专门用来收目标地址的表单字段。

常见的 SSRF 都出自没人打算做出来的功能。PDF 渲染器跟着图片 URL 走;头像导入替用户去抓一张图。没有人打算把 HTTP 客户端交给陌生人,事后读起来像疏忽。webhook 发送方是有意为之的那一类:字段写在文档里,去取它的那个 worker 没有别的活干。

OWASP 在 2025 年挪了这个类别。SSRF 在 2021 年的榜单上是独立的 A10,2025 版的十大里它不再是标题,CWE-918 归进 A01 失效的访问控制,列在重点弱点里。新位置比旧名字更准地说出了损害。请求是真的。是你的基础设施发出去的,而它够得着的东西之所以够得着,正因为是它发的。

拿不到响应,攻击照样成立

投递 worker 读一个状态码,body 扔掉。什么都不会回显到控制台上,所以填地址的人每次尝试只学到一个比特:连上了,或者没连上。这就是盲打那一类。把 SSRF 当数据泄露来推理,就会低估它。一个比特,重复足够多次,能把一张网络画出来。

资产是位置。在边界里面,worker 够得到那些从来没打算回应外人的主机,其中不少来自「在内网就等于通过认证」的年代。在边界外面,平台的出口地址往往躺在某个合作方的白名单上,对方同意接你的流量,不接别人的。

支付平台比 PDF 渲染器危险的地方在重试梯子。按退避计划重试的发送方,把一次注册变成反复触发的任务,跑在别人维护的机器上。

检查该站在哪一步?

域名解析之后,连接建立之前。这个位置在多数 HTTP 客户端库里都很难落脚。

最先写出来的总是字符串过滤,因为它是唯一不用离开请求处理函数就能写的。它失败在结构上,不是输给什么巧思:主机名只是指针,它指向哪里由一条 DNS 记录说了算,而那条记录可能归填 URL 的人所有。读文本对数据包去哪儿一无所知。OWASP 的防护指南写得很明确,应用必须把域名后面的地址取出来,A 和 AAAA 都取,再对取回来的东西套同一套地址规则。

难办正是天真版本一直在发布的原因。客户端库想做的是收一个 URL、还你一个响应,而你要的钩子夹在它视作自家内务的两步之间。够到那里意味着自己解析域名,或者自己挂 socket,两条路都比在表单字段上写个正则费事。

哪些地址段必须拒绝?

比多数工程师凭记忆写出来的那张单子长。回环和 RFC 1918 私网段是明摆着的。链路本地也属于这里,云厂商的实例元数据就答在这个范围上,够到它的后果是凭证。剩下两条比人们脑子里那套模型年轻。

共享地址空间 100.64.0.0/10,RFC 6598 在 2012 年 4 月为运营商级 NAT 保留。RFC 自己记了一笔:它和 RFC 1918 私有地址空间不是一回事,因为它是给服务提供商网络用的。拿 10.192.168. 做前缀匹配的过滤器对它没有意见,顺带还漏掉半个 RFC 1918。

IPv6 唯一本地地址 fc00::/7 来自 RFC 4193,那份文档把它定义成不在全球互联网上路由、只在站点这类更有限的范围里路由。没有哪个 webhook 该发到符合这个描述的目标上,而只按 IPv4 写的清单根本不会提到它们。

解析通过了,重定向又把目标换掉

你校验过的 URL,不再是你取的那个 URL。

worker 解析主机名,拿到公网地址,放行,开连接。服务器回 302,带上新的 Location。客户端如果跟随重定向,第二次请求压根没走过检查,因为那次检查只发生在第一次请求上。.NET 的 HttpClient 和 Python 的 requests 不特别交代就都跟随。

防护指南一句话关掉这条路:把 web 客户端的重定向支持关掉。话糙理正。接收回调的端点没有理由把回调转交给第二个地址,搬了家的商户可以把新地址注册上来。

还有一个更安静的版本,哪怕什么都不重定向也在那儿。DNS 应答有过期时间,而你放行的是当时看到的那个答案。关掉这一个靠客户端怎么接线,不是往校验器里再加一条规则,所以它往往是发送方最后长出来的那块。

Paymos 这边具体做了什么?

主机名只解析一次。取第一个通过检查的地址,直接连到那个地址上;检查和连接之间没有第二次 DNS 查询,重绑定的窗口就是这样关掉的。

拒绝的地址段按实现列出来:0.0.0.0/810/8127/8、运营商级 NAT 100.64/10、链路本地 169.254/16172.16/12192.168/16,三段文档用地址 192.0.2/24198.51.100/24203.0.113/24,以及 IPv6 唯一本地地址 fc00::/7。回环和 IPv6 链路本地另外单独判一次,云元数据地址落在链路本地范围里,不是单独一条规则。

还有三条。IPv4 映射过来的 IPv6 地址先归一化再判段,经典那种绕法在这里不成立。自动 HTTP 重定向一律不跟随。端点 URL 必须是 HTTPS,创建和修改时都挡。

这些加起来覆盖一组目标地址和一个客户端设置,范围窄。窄的安全声明比宽的值钱,因为窄的能核。

时间戳不是我们的时间窗

有件事容易顺手写反。出站投递上 Paymos 只在 X-Webhook-Timestamp 里盖时间戳,自己不设时间窗;验签时容忍多大偏差,由接收端自己定。

所以「签名 5 分钟内有效」这句话写进你自己的接收端文档是对的,写成平台行为就错了。选多长要权衡:窗口太紧,重试梯子后面那几级会因为时钟漂移全挂;窗口太松,能重放的时间就变长。

地址段拦发送方的目标,验签拦接收方的来源,两件事互相替代不了。X-Webhook-Signature 的格式和哈希顺序在验签页里。

失败要关得住,还不能被拖垮

关得住靠顺序。校验不通过的目标在任何连接尝试之前就拒掉,所以一次拦下来的注册不花发送方一点出站容量。

另一半是资源上限。做的人常常跳过它,因为看上去像可靠性的活;在 Paymos 的投递上它们本来就是为可靠性建的,顺带把这件事也框住了:

  • 每次尝试 10 秒。没有截止时间,握了手然后不说话的目标会占住一个 worker,直到别的东西先垮;这种目标多几个,队列就排不空。
  • 11 次尝试关掉一个事件:首发一次加十次重试,间隔从 1 分钟排到 8 小时,横跨约 16 小时。重试不封顶,等于给别人挑好的目标送去一个会自己续杯的流量源。
  • 一个商户环境 10 个端点。注册数量上限,是拦在单个账号和无限出站容量之间的那道东西。

这些次数框住的是事件,不是端点。周期走完只把事件标记为失败,注册原样留着,躺了一天的接收端回来,面对的是一串可以重放的事件。

这道防护让商户付出什么?

真实端点会撞上它,而撞上去的样子像故障,不像控制。

回调不能指向 localhost,也不能指向办公室局域网里的机器,所以本地开发要拿隧道在处理器前面摆一个公网主机名。每个集成第一个下午就会遇到这一条。

不跟随重定向,意味着注册上来的必须是最终地址。CDN 把端点统一 301 到规范域名,去年搬了家的路径至今还在弹,两种都投不进去,而同一个地址在浏览器里返回的页面好端端的。链接包装器和短网址摆在回调前面,是同一个问题换了件衣服。

最不显眼的是一条 DNS 记录指错了地方。端点的 A 记录填成路由器在内网那一侧看到的地址,不是外面够得着的那个,目标就落进拒绝段里,什么都出不了门。

这几种都不声张。商户看不到回调,开了一张工单说 webhook 收不到;先去调试的总是处理器,而答案在注册的那个 URL 里。

该问处理商哪几个问题?

问检查站在哪一步,别的答案都从这一条推出来。真在校验解析后地址的厂商,不用你追问就说得出来,而且报得出地址段。回环和 RFC 1918 是容易的一半;能说到共享地址空间和 IPv6 唯一本地地址的答案,来自读过现行 RFC 的人,不是抄旧清单的人。再问自动重定向跟不跟随。再问,一次拒绝会占掉平台上其他商户多少投递容量。

然后读一遍厂商的安全页,看它是不是停在工程停下的地方。一道防护,页面把它说得比执行组件更宽,那是另一种风险,而且是你从外面就能审的那一种。

最后一句留给做发送方的人。这道控制工作时一声不响,触发时长得和 bug 一模一样,所以拒绝消息要点名撞上了哪条规则,而且要在接口返回里说给商户听,不是只写进自己的日志。省下这句话的时间,会在工单里加倍还回来。

回调地址检查能站在哪儿,各自漏掉什么(2026年9月)
检查站的位置它看得见什么还能溜过去的
对 URL 字符串商户在表单里敲进去的文本DNS 记录指向内网的主机名
对主机名这个名字你认不认识任何你不认识的名字,无论它解析到哪儿
对解析出来的地址名字后面所有 A 和 AAAA 记录响应里回来的重定向
对每次连接,每次尝试这次请求马上要够到的地址地址段清单漏掉的东西

常见问题

webhook SSRF 是什么?

从回调 URL 字段进来的服务端请求伪造。目标由客户挑,去连它的是你网络里面的后台 worker,于是那次请求拿到了客户自己没有的网络位置。

测试时能把 webhook 地址指到 localhost 吗?

在拦回环的发送方上不行。Paymos 拒绝回环、私网、链路本地、运营商级 NAT 和 IPv6 唯一本地地址,本地开发要用隧道,在处理器前面摆个公网主机名。

Paymos 投递会跟随重定向吗?

不跟随。自动 HTTP 重定向一律不跟,所以注册上来的必须是最终地址,不能是一条 跳过去的地址。另外端点 URL 必须是 HTTPS。

拦掉 127.0.0.1 就够了吗?

不够。主机名解析到哪儿由它的 DNS 记录说了算,所以检查要跑在解析出来的地址上, 拒绝清单还得够到私网、链路本地、运营商级 NAT 和 IPv6 唯一本地地址。

换了回调地址之后 webhook 就收不到了,为什么?

落进拒绝段的目标,连都不连。回重定向的目标连得上,3xx 记一次失败尝试。核对主机名 解析到的是公网地址,而且它就是最终地址。

参考来源

  1. 1. OWASP Top 10:2025 — A01 Broken Access Control (accessed 2026-09-15)
  2. 2. OWASP — Server Side Request Forgery Prevention Cheat Sheet (accessed 2026-09-15)
  3. 3. RFC 6598 — IANA-Reserved IPv4 Prefix for Shared Address Space (accessed 2026-09-15)
  4. 4. RFC 4193 — Unique Local IPv6 Unicast Addresses (accessed 2026-09-15)
  5. 5. Microsoft Learn — HttpClientHandler.AllowAutoRedirect (accessed 2026-09-15)
  6. 6. Paymos 文档 — 安全 (accessed 2026-09-15)

最近复核:2026年9月15日

#webhook#SSRF#安全#回调地址
分享