背景SSRF(Server-Side Request Forgery)在实际代码审计和漏洞复现中很常见,尤其出现在图片抓取、Webhook 回调、URL 预览、PDF 生成、文件导入等功能里。它的危险点不在于“能不能请求一个外部 URL”,而在于请求是由服务端发起的,可能触达普通用户访问不到的内网地址、云厂商元数据接口或本机管理端口。这篇文章整理一次典型 SSRF 风险的分析思路,重点放在安全验证、代码审计和修复方案上。内容不涉及非法攻击目标,也不提供对第三方系统的攻击引导,适合作为内部安全评审或研发自查参考。常见场景在业务里,以下功能比较容易引入 SSRF:用户提交图片 URL,服务端下载后转存到对象存储。文章链接预览,服务端抓取标题、描述、favicon。Webhook 配置,系统主动向用户填写的地址发送通知。PDF/截图服务,后端渲染用户提供的页面地址。远程文件导入,例如通过 URL 导入 CSV、XML、JSON。这些功能本身没有问题,问题通常出在“把用户输入的 URL 原样交给服务端 HTTP 客户端请求”,并且缺少协议、地址、重定向、DNS 解析等限制。问题样例下面是一段简化后的伪代码,很多 SSRF 都能在类似逻辑里看到影子:public byte[] fetchRemoteFile(String url) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build();
HttpResponse<byte[]> response = client.send(
request,
HttpResponse.BodyHandlers.ofByteArray()
);
return response.body();
}这里存在几个明显问题:没有限制协议,可能允许 file、gopher、ftp 等非预期协议,具体取决于客户端实现。没有校验目标地址是否为内网、环回地址、链路本地地址。没有处理 DNS 重绑定风险。没有限制跳转后的地址。没有限制响应大小、超时时间和 Content-Type。从审计角度看,只要看到“用户可控 URL + 服务端请求 + 结果被返回或被后续处理”,就应该进入 SSRF 检查流程。技术分析1. URL 解析差异不同语言、不同库对 URL 的解析并不完全一致。常见问题包括用户名密码段、特殊字符、IPv6、十进制/八进制 IP、尾随点域名等。例如,有些校验逻辑只做字符串前缀判断:if (url.startsWith(\" {
fetch(url);
}这种写法很容易因为 URL 结构解析不一致而出问题。安全做法是使用标准 URI/URL 解析库拆分 scheme、host、port,再基于解析后的字段做校验,不要直接对原始字符串做安全判断。2. DNS 与 IP 校验只校验域名后缀不够,因为域名最终会解析到 IP。SSRF 防护里比较关键的一步是:解析域名后检查所有 IP 是否落在禁止网段。至少应拦截以下地址范围:127.0.0.0/8:本机环回地址。10.0.0.0/8、172.16.0.0/12、192.168.0.0/16:私有地址。169.254.0.0/16:链路本地地址,云环境里尤其敏感。::1/128:IPv6 环回地址。fc00::/7、fe80::/10:IPv6 私有或链路本地地址。还要注意 DNS 重绑定。一次校验通过后,实际请求时域名可能解析到另一个地址。更稳妥的方式是解析后固定 IP 发起连接,同时校验连接目标,或者在网络层做出站访问控制。3. 重定向风险很多 HTTP 客户端默认跟随 301、302、307、308 跳转。如果初始 URL 是合法外部地址,但跳转到内网地址,依然会造成 SSRF。因此需要关闭自动跳转,手动检查每一次 Location 目标。// Java HttpClient 示例:不自动跟随重定向
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NEVER)
.connectTimeout(Duration.ofSeconds(3))
.build();如果业务确实需要跟随跳转,应限制最大跳转次数,并对每一跳重新执行协议、域名、IP、端口校验。4. 端口与协议限制业务如果只是下载图片或网页,通常只需要允许 http 和 https,并限制端口为 80、443 或明确的业务端口。不要允许用户控制任意协议和任意端口。允许:
- />
-
拒绝:
- file:///etc/passwd
- ftp://example.com/file
- />
- HTTP 客户端和网络请求方法。例如:Java:
- HttpURLConnection
- HttpClient
- OkHttpClient
- RestTemplate
- WebClient
Python:
- requests.get/post
- urllib.request
- aiohttp.ClientSession
Node.js:
- axios
- node-fetch
- request
- http.request
- https.request
Go:
- http.Get
- http.Client.Do重点关注参数是否来自用户输入、配置项、数据库字段、消息队列或第三方回调内容。很多漏洞不是直接来自表单,而是来自“用户先保存 URL,后台任务稍后请求”。步骤二:确认数据流是否可控需要明确 URL 从哪里来,经过哪些处理,最终进入哪个请求函数。建议记录以下信息:入口参数名和接口路径。是否有鉴权要求。是否经过 URL 解码、拼接、模板渲染。是否有白名单或黑名单校验。是否会返回响应内容、状态码、错误信息。如果请求结果会回显,风险通常更高;即使不回显,也可能通过耗时、错误码、日志、后续任务状态形成侧信道。步骤三:做安全范围内的验证内部授权测试时,不建议直接探测敏感内网服务。可以准备一个受控的测试地址,用于确认服务端是否发起请求。例如在测试环境搭建一个简单 HTTP 服务:python3 -m http.server 8000然后让业务功能请求该测试地址,观察访问日志:Serving HTTP on 0.0.0.0 port 8000 ...
10.10.2.15 - - [08/Oct/2026 10:21:33] \"GET /test.png HTTP/1.1\" 200 -这样可以确认请求是否由服务端发起、源 IP 是什么、User-Agent 是什么、是否跟随重定向。验证到这里通常已经足够支撑修复,不需要继续扩大测试范围。步骤四:检查云环境元数据风险云服务器场景下,169.254.169.254 这类链路本地地址尤其敏感。不同云厂商的元数据服务实现不同,但共同点是:它不应该被普通业务 URL 抓取功能访问到。修复时不要只在应用层写一个字符串黑名单,因为攻击面不只这一条地址。更合理的方案是:应用层禁止私有地址、链路本地地址、环回地址。容器或主机层限制到元数据地址的访问。云平台侧启用更严格的元数据访问策略,例如要求 token 或绑定实例角色最小权限。修复建议1. 建立统一的安全请求组件不要让每个业务自己写 URL 校验。建议封装一个统一组件,所有“请求用户提供 URL”的功能都必须走这层。组件至少包含:仅允许 http、https。限制端口范围。解析域名并校验所有 IP。禁止访问内网、环回、链路本地、多播地址。关闭自动重定向,或逐跳校验。设置连接超时、读取超时、最大响应体积。限制 Content-Type,例如图片抓取只允许 image/*。2. 白名单优先于黑名单如果业务场景固定,例如只允许回调合作方域名,优先使用白名单。黑名单容易遗漏各种地址表示方式和解析差异。推荐思路:
- 固定允许域名列表:api.partner.example
- 固定协议:https
- 固定端口:443
- 证书校验必须开启
- 禁止跳转到非白名单域名如果必须允许用户输入任意公网 URL,就要使用更完整的公网地址校验和网络隔离策略。3. 网络层做兜底应用层校验很重要,但不能作为唯一防线。更可靠的方式是在网络层限制业务服务的出站访问范围。生产环境默认禁止访问内网管理网段。容器环境通过 NetworkPolicy、iptables 或安全组限制 egress。需要访问外部网络的服务走统一代理,由代理做策略控制和审计。对访问云元数据地址设置显式拒绝规则。这样即使某个业务漏掉了校验,也不至于直接打到高敏感目标。4. 日志与监控SSRF 修复不只是代码修改,还要补充可观测性。建议记录以下字段:原始 URL 和规范化后的 URL。解析后的目标 IP。最终请求端口和协议。是否发生重定向。拦截原因。调用业务和用户标识。日志里要注意脱敏,不要记录敏感 token、完整签名参数或隐私数据。一个相对稳妥的校验流程下面给一个语言无关的流程,用于设计安全请求组件:1. 解析 URL
2. 检查 scheme 是否为 http/https
3. 检查 host 是否存在,拒绝空 host
4. 规范化 host,处理大小写、尾随点、IDN
5. 检查端口,只允许业务需要的端口
6. 解析 DNS,获取全部 A/AAAA 记录
7. 检查每个 IP 是否属于禁止网段
8. 建立连接时绑定已校验 IP 或确保连接目标未变化
9. 关闭自动重定向
10. 如需跳转,对每一跳重复 1-9
11. 限制超时、响应大小、Content-Type
12. 记录审计日志注意点不要用正则表达式单独判断 URL 安全性,正则适合格式校验,不适合作为 SSRF 防护核心。不要只判断字符串里是否包含 127.0.0.1 或 localhost,地址表示方式很多。不要信任 X-Forwarded-For 等请求头,它们不能证明目标地址安全。不要在生产环境对未知内网地址做探测验证,验证应在授权测试环境完成。不要把错误信息完整返回给用户,避免暴露内网服务指纹。不要忘记异步任务,很多 SSRF 请求发生在后台 worker 中。复盘中的几个经验从多次 SSRF 相关问题看,真正容易被忽略的不是“没有想到要过滤 127.0.0.1”,而是下面几类细节:预览功能上线很快,没有纳入统一出站请求组件。测试只覆盖了普通 URL,没有覆盖跳转和 IPv6。应用层修了,但容器仍然可以访问元数据地址。业务认为“不回显响应内容就没风险”,忽略了盲 SSRF。日志只记录请求成功失败,没有记录解析 IP,后续难以排查。所以修复 SSRF 最好不要只提交一个 if 判断,而是推动形成统一能力:统一组件、统一策略、统一日志和网络层兜底。总结SSRF 的本质是服务端代替用户发起了不受控请求。安全处理的核心不是简单过滤几个关键词,而是把“用户可控 URL”当成高风险输入,完整处理协议、解析、DNS、IP、端口、重定向和网络出口。如果只做一次性修补,后续类似功能很容易再次引入问题。更稳妥的做法是把安全请求能力沉淀成公共组件,并在基础设施层限制出站访问。这样对研发成本影响不大,但能明显降低内网探测、云元数据泄露和盲 SSRF 带来的风险。
推荐意见