
“填写图片 URL 生成缩略图”“输入回调地址测试 Webhook”都是常见功能。危险之处在于,真正发起请求的是位于可信网络里的服务器:攻击者只要控制目标地址,就可能借它访问云元数据、管理后台或只监听内网的服务。这类问题称为服务端请求伪造(SSRF),防线不能只靠一条 URL 正则。
风险来自信任边界被绕过
服务器通常拥有用户浏览器没有的网络权限。一次看似普通的抓图请求,可能沿着另一条路径抵达敏感目标:
1 | 用户输入 URL -> 业务服务 -> DNS -> HTTP 客户端 -> 目标地址 |
SSRF 的结果不只是“读到一个页面”。即使响应内容没有回显,状态码、耗时和连接结果也能帮助探测端口;如果内部接口以网络位置代替身份认证,还可能触发写操作。
| 薄弱做法 | 为什么不够 |
|---|---|
禁止 URL 中出现 127.0.0.1 |
可改用 IPv6、整数形式或域名 |
| 只检查域名后缀 | DNS 结果仍可能指向私网地址 |
| 首次请求通过后自动跟随跳转 | 重定向目标可能进入内网 |
| 只在应用代码里校验 | 新入口或第三方组件容易绕过 |
把 URL 当结构解析
第一层防线是缩小功能边界。若业务只需下载 HTTPS 图片,就只允许 https,明确拒绝 file:、ftp: 等协议;禁止用户名、密码和异常端口,并限制 URL 长度。不要先解码字符串再用前缀或包含关系判断,应该交给标准 URL 解析器取得协议、主机和端口。
解析出主机后,要解析它的全部 A 和 AAAA 记录。只要任一结果属于回环、私网、链路本地、组播、保留或未指定地址,就整体拒绝:
1 | import ipaddress |
这段代码只展示地址分类,不是完整下载器。生产实现还要正确处理 IPv4 映射的 IPv6 地址,并确保实际建连使用刚刚审核过的地址。
防住 DNS 重绑定和跳转换目标

如果校验阶段解析一次域名,HTTP 客户端建连时又解析一次,攻击者可以让两次 DNS 查询得到不同结果:先返回公网地址通过检查,再返回内网地址完成连接。这就是典型的检查与使用之间竞态。
可靠做法是把“解析、审核、连接”收拢在同一受控组件中:连接固定到审核后的 IP,同时保留原始主机名用于 TLS 证书校验和 HTTP Host。若客户端库无法安全固定解析结果,应通过专用出站代理统一执行策略,而不是自行拼接底层请求。
重定向也必须逐跳处理。关闭客户端的自动跳转,每收到一个 Location 就重新解析、重新检查协议与地址,并设置很小的最大跳数。不能因为第一跳可信,就默认后续目标也可信。
让网络层成为最后一道闸门
应用校验会随代码演进而出现遗漏,因此基础设施还要实行默认拒绝的出站策略。下载服务可部署在隔离网络,只允许访问 DNS、受控出站代理和必要公网端口,并明确阻断内网网段、节点管理接口及云元数据地址。敏感内部服务仍需强认证,不能把“只有内网能访问”当作唯一凭据。
专用抓取器还应限制响应大小、连接与读取超时、并发数和压缩后展开量,只接受业务所需的内容类型。响应先进入隔离存储,经格式识别后再交给业务,避免把 SSRF 修成了内存耗尽或恶意文件入口。
上线时验证真正的失败边界
测试集至少包含公网域名、回环与私网 IP、IPv6、指向私网的域名、从公网跳转到内网,以及 DNS 答案变化的场景。日志记录规范化主机、最终连接 IP、拒绝原因、跳转次数和请求 ID,但不要保存 URL 中的凭据或敏感查询参数。
SSRF 防护的核心不是维护一份越来越长的黑名单,而是让每一次出站请求都经过同一条可审计链路:限制输入能力,审核解析结果,把连接固定到已验证地址,逐跳检查重定向,再用网络策略兜底。这样即使某一层判断失误,服务器也不会轻易变成攻击者伸向内网的代理。