我们最近为 PlanetScale 推出了 webhooks 功能。
构建一个 webhooks 服务最有趣的部分之一,就是如何确保其安全并防止滥用。
当我们开始在公司内部讨论该项目时,PlanetScale 的工程师们纷纷分享了过去自己观察到的有关 webhooks 被滥用或利用的不同方式。这些经历为我们列出了一个很好的需要关注的清单,以便在构建自己的 webhooks 服务时重点处理。
在这篇文章中,我们将详细介绍我们在构建安全的 webhooks 服务时采取的一些主要步骤。


服务器端请求伪造 (SSRF)

任何 webhooks 服务的主要漏洞都是服务器端请求伪造(SSRF)。
SSRF 攻击是指攻击者导致你的服务在内部网络中发出意外的内部请求。
webhooks 正好是这种攻击的理想目标。用户提供一个 URL,然后触发你的应用程序向该 URL 发送请求。
这种请求可能是有害的:例如返回内部的私密信息给攻击者,或者触发你的内部服务代表攻击者执行一些操作。
举个例子,如果一个服务器运行着允许 HTTP POST 请求的内部指标(metrics)端点,攻击者可能会通过 webhook 服务伪造一个请求指向该服务。如果 webhook 服务在用户界面中显示了响应结果,那么攻击者就获得了你内部的指标数据。


缓解 webhooks 的 SSRF 攻击

在构建 webhook 服务时,需要设置两层防护机制来防止 SSRF 攻击:

  1. 限制用户能够设置为 webhook 的 URL。
  2. 通过出口规则或代理限制你的 webhook 服务可以发送 HTTP 请求的目标。

严格验证 webhook URL

对允许的 URL 进行验证主要是为了让用户快速了解到他们输入的 URL 是否可以与你的 webhook 服务正常工作。然而,仅靠 URL 验证并不足以完全防御 SSRF,因为 DNS 可以轻松被更改。
在我们的服务中,我们进行了以下检查:

要求 HTTPS

如今,运行一个没有 SSL 的 web 服务已经非常少见。我们认为,对任何 webhook 请求要求 HTTPS 是一个合理的要求。这不仅限制了潜在的漏洞,还保护了可能通过 webhook 传递的敏感数据。

阻止私有地址和回环 IP

我们通过 Ruby 的 ipaddr 库来判断某个 IP 地址是否是私有地址(内部地址)或回环地址(localhost)。 如果该地址满足任一条件,它将无法通过验证。

uri = URI.parse(url)

host_ip = begin
  IPAddr.new(uri.host)
rescue
  nil
end

return false if host_ip && (host_ip.private? || host_ip.loopback?)
屏蔽我们自己的域名

为了防止用户将流量发送到 PlanetScale 的其他服务,我们设置了一个域名黑名单,其中包含了我们所有的其它公共服务。

uri = URI.parse(url)

if BLOCKED_DOMAINS.any? { |domain| uri.host&.include?(domain) }
  return false
end
DNS 解析测试

一旦 URL 通过基本验证,我们会解析其 DNS 来进一步确认它没有指向任何私有或回环 IP 地址。
请注意,用户可以在通过此检查后随时更改主机的 DNS,因此仅凭此检查也不足以防范 SSRF。

def host_resolves_valid_ips?(host)
  ip_addresses = Resolv.getaddresses(host)
  return false if ip_addresses.none?

  if ip_addresses.any? { |ip| blocked_ip?(IPAddr.new(ip)) }
    return false
  end

  true
end

def blocked_ip?(ip)
  ip.private? || ip.loopback?
end

HTTP 出口规则

无论你的 URL 验证多么严格,都无法完全信任用户提供的 URL。因此,隔离并限制 webhooks 服务可以发送 HTTP 请求的目标非常关键。
具体实现方式取决于你的基础设施。在我们的应用程序中,我们使用 Kubernetes 部署,设置了一个专门用于发送 webhooks 的隔离服务。该服务通过 Envoy Proxy 发送 HTTP 请求,且只允许向网络外部发送请求。此 Proxy 的规则和 URL 验证类似,但是在实际发送 webhook 时执行。
关键规则包括:

  • 阻止任何连接至内部或私有 IP 的请求。
  • 限制流量仅发送到 HTTPS 相关端口。

缓解分布式拒绝服务攻击 (DDoS)

webhooks 服务有可能被攻击者操作以向某个 URL 发送大量流量。为实现这种攻击,攻击者只需要设置一个 webhook,然后找到某种方法大量触发它。

API 层的速率限制

一个简单的保护方法是对 API 层进行合理的速率限制。这限制了攻击者的行为,并防止他们无限制地排队等待发送 webhook。
我们的整个 API 服务对所有端点都进行了通用速率限制。
对于 webhooks 服务,我们有一个用于触发测试 webhook 的测试端点。针对这个特定端点,我们设置了一个限速:每 20 秒仅允许 1 次请求。我们认为这个限制既能满足用户测试 hooks 时的需求,同时也能防止测试 webhook 被滥用。


Webhook 唯一性/锁定机制

我们的 webhook 服务使用 Sidekiq 队列来处理并发送 webhooks。通过 Sidekiq,我们可以针对每个加入队列的 webhook 设置唯一性检查。
快速连续触发的重复 webhook 会被拒绝。这确保了我们的服务仅发送一个唯一的 webhook,同时减少了需要处理的 webhook 数量。


隔离的基础设施

如果其它缓解措施失效,我们会将 webhook 队列运行在隔离的机器上,以保护 PlanetScale 的其他服务不会因为 webhook 滥用而受到影响。
如果 webhook 被滥用,我们也不会允许它影响其他系统的可靠性。在出现事故时,此类服务可以轻松暂停或禁用。


设置严格的超时时间

每次发送 webhook 都会占用资源等待响应。一个潜在的攻击方式是排队大量解析速度非常慢的 webhook。可以通过设置短超时来缓解这种情况。


限制 webhook 数量

我们首次设置每个数据库允许配置 5 个 webhooks。我们认为这个数量足以让用户自动化多个工作流,同时也能防止用户为同一事件触发过多的 hooks。
从 5 个开始相对保守,但这为后续留下了增长的空间。如果用户有更多的实际需求,我们可以允许增加。相比之下,后续增加上限总是比减去限制更容易。


通过上述安全措施,我们的 webhooks 服务能够更好地抵御攻击,同时确保用户体验与系统可靠性。



Webhook 安全性:实践指南插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://choupangxia.com/2025/09/13/webhook-security/