别让服务器替攻击者探路:SSRF 防护与出站访问控制

SSRF 防护与受控出站访问

“填写图片 URL 生成缩略图”“输入回调地址测试 Webhook”都是常见功能。危险之处在于,真正发起请求的是位于可信网络里的服务器:攻击者只要控制目标地址,就可能借它访问云元数据、管理后台或只监听内网的服务。这类问题称为服务端请求伪造(SSRF),防线不能只靠一条 URL 正则。

风险来自信任边界被绕过

服务器通常拥有用户浏览器没有的网络权限。一次看似普通的抓图请求,可能沿着另一条路径抵达敏感目标:

1
2
用户输入 URL -> 业务服务 -> DNS -> HTTP 客户端 -> 目标地址
\-> 内网、回环地址、云元数据

SSRF 的结果不只是“读到一个页面”。即使响应内容没有回显,状态码、耗时和连接结果也能帮助探测端口;如果内部接口以网络位置代替身份认证,还可能触发写操作。

薄弱做法 为什么不够
禁止 URL 中出现 127.0.0.1 可改用 IPv6、整数形式或域名
只检查域名后缀 DNS 结果仍可能指向私网地址
首次请求通过后自动跟随跳转 重定向目标可能进入内网
只在应用代码里校验 新入口或第三方组件容易绕过

把 URL 当结构解析

第一层防线是缩小功能边界。若业务只需下载 HTTPS 图片,就只允许 https,明确拒绝 file:、ftp: 等协议;禁止用户名、密码和异常端口,并限制 URL 长度。不要先解码字符串再用前缀或包含关系判断,应该交给标准 URL 解析器取得协议、主机和端口。

解析出主机后,要解析它的全部 A 和 AAAA 记录。只要任一结果属于回环、私网、链路本地、组播、保留或未指定地址,就整体拒绝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import ipaddress
import socket

def resolve_public(hostname, port=443):
results = socket.getaddrinfo(hostname, port, type=socket.SOCK_STREAM)
addresses = {ipaddress.ip_address(item[4][0]) for item in results}
if not addresses:
raise ValueError("hostname has no address")

forbidden = lambda ip: (
ip.is_private or ip.is_loopback or ip.is_link_local
or ip.is_multicast or ip.is_reserved or ip.is_unspecified
)
if any(forbidden(ip) for ip in addresses):
raise ValueError("non-public destination")
return addresses

这段代码只展示地址分类,不是完整下载器。生产实现还要正确处理 IPv4 映射的 IPv6 地址,并确保实际建连使用刚刚审核过的地址。

防住 DNS 重绑定和跳转换目标

从输入校验到网络出口的多层 SSRF 防线

如果校验阶段解析一次域名,HTTP 客户端建连时又解析一次,攻击者可以让两次 DNS 查询得到不同结果:先返回公网地址通过检查,再返回内网地址完成连接。这就是典型的检查与使用之间竞态。

可靠做法是把“解析、审核、连接”收拢在同一受控组件中:连接固定到审核后的 IP,同时保留原始主机名用于 TLS 证书校验和 HTTP Host。若客户端库无法安全固定解析结果,应通过专用出站代理统一执行策略,而不是自行拼接底层请求。

重定向也必须逐跳处理。关闭客户端的自动跳转,每收到一个 Location 就重新解析、重新检查协议与地址,并设置很小的最大跳数。不能因为第一跳可信,就默认后续目标也可信。

让网络层成为最后一道闸门

应用校验会随代码演进而出现遗漏,因此基础设施还要实行默认拒绝的出站策略。下载服务可部署在隔离网络,只允许访问 DNS、受控出站代理和必要公网端口,并明确阻断内网网段、节点管理接口及云元数据地址。敏感内部服务仍需强认证,不能把“只有内网能访问”当作唯一凭据。

专用抓取器还应限制响应大小、连接与读取超时、并发数和压缩后展开量,只接受业务所需的内容类型。响应先进入隔离存储,经格式识别后再交给业务,避免把 SSRF 修成了内存耗尽或恶意文件入口。

上线时验证真正的失败边界

测试集至少包含公网域名、回环与私网 IP、IPv6、指向私网的域名、从公网跳转到内网,以及 DNS 答案变化的场景。日志记录规范化主机、最终连接 IP、拒绝原因、跳转次数和请求 ID,但不要保存 URL 中的凭据或敏感查询参数。

SSRF 防护的核心不是维护一份越来越长的黑名单,而是让每一次出站请求都经过同一条可审计链路:限制输入能力,审核解析结果,把连接固定到已验证地址,逐跳检查重定向,再用网络策略兜底。这样即使某一层判断失误,服务器也不会轻易变成攻击者伸向内网的代理。