SSRF(Server-Side Request Forgery,服务器端请求伪造)并不是“让服务端代替用户发一次请求”这么简单。它真正危险的地方在于:应用通常拥有比普通用户更高的网络可达性,攻击者一旦能够影响请求目标,就可能借助应用访问内网管理面、云平台元数据服务、仅绑定本机的调试接口,甚至把内部响应带回到外部。
在实际审计中,SSRF 往往不以明显的 url 参数出现,也可能隐藏在图片抓取、Webhook、文件导入、Markdown 预览、第三方接口代理、PDF 生成等功能中。本文以防守和代码审计为主线,整理一套适用于研发自查、漏洞复现和修复验证的方法。
一、先明确问题边界:哪些功能值得优先检查
判断一个功能是否可能存在 SSRF,核心不是看参数名,而是确认服务端是否会根据外部输入发起网络请求。以下场景通常风险较高:
- 根据用户提交的 URL 下载头像、图片、附件或远程文档。
- 提供 URL 预览、网页截图、网页转 PDF、RSS 抓取等能力。
- 允许配置 Webhook、回调地址、通知地址或第三方集成地址。
- 服务端作为代理访问外部 API,并将响应转发给浏览器。
- 解析 SVG、XML、Office 文档或其他包含外部资源引用的文件。
- 后台任务根据数据库中的地址定时同步内容。
审计时可以从“出网能力”倒推输入点。先搜索项目中的 HTTP 客户端调用,再追踪目标地址是否来自请求参数、数据库字段、消息队列或用户可编辑配置。常见调用包括 Java 的 HttpClient、RestTemplate、WebClient,Python 的 requests、urllib,Node.js 的 axios、fetch 等。
二、典型漏洞形成条件
一个可利用的 SSRF 通常同时具备三个条件:
- 攻击者能够影响请求目标,或者影响目标的一部分。
- 服务端会解析目标并发起网络连接。
- 连接结果能够造成安全影响,例如读取敏感响应、触发内部操作、探测网络状态,或产生高额资源消耗。
下面是一段简化的危险代码。问题不在于使用了某个特定库,而在于没有对目标进行策略校验:
String target = request.getParameter("url");
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(target))
.GET()
.build();
HttpResponse<String> response = client.send(
httpRequest,
HttpResponse.BodyHandlers.ofString()
);
return response.body();即使增加了“只允许 HTTP 和 HTTPS”的判断,也不能视为完成修复。攻击者仍可能利用内网地址、域名解析变化、重定向、非标准 IP 表示方式、IPv6、代理配置或解析器差异绕过简单过滤。
三、代码审计时应关注的关键细节
1. URL 解析是否只做字符串匹配
诸如 startsWith(" 之类的判断不可靠。它们无法正确区分主机名、用户名信息、端口和路径,也无法处理编码、大小写及规范化问题。
审计时应确认程序是否使用标准 URL 解析器,并且明确检查:
- 协议是否属于业务需要的最小集合。
- 主机名是否经过规范化和解析。
- 端口是否属于允许范围,是否禁止显式端口。
- 用户名、密码、片段等不必要字段是否被拒绝。
- 最终连接的 IP 是否属于允许的网络范围。
2. DNS 解析和实际连接是否使用同一结果
只在校验阶段解析一次域名,然后把原始域名交给 HTTP 客户端连接,可能产生 DNS rebinding 风险:校验时得到的是公网地址,真正连接时解析结果却已经变化。
更稳妥的做法是由受控的解析逻辑完成解析和连接,并确保校验过的 IP 被实际使用;或者将出网流量交给统一的网络代理,在代理层执行域名和 IP 策略。对于高风险服务,不建议让业务代码直接拥有任意出网能力。
3. 是否跟随重定向
初始 URL 可能是允许的公网地址,但服务端跟随 30x 重定向后进入受限网络。因此需要限制或关闭自动重定向,并对每次跳转重新执行完整校验。
// 伪代码:每一次跳转都重新校验,不信任 Location 头部
for (int i = 0; i < MAX_REDIRECTS; i++) {
ValidatedTarget target = policy.validate(currentUrl);
HttpResponse response = client.request(target);
if (!response.isRedirect()) {
return response;
}
currentUrl = response.location();
}
throw new SecurityException("redirect limit exceeded");4. 响应处理是否存在二次风险
SSRF 不只关注“能不能发请求”,还要看响应如何被处理。如果服务端把内部响应原样返回给用户,风险通常更直接;如果响应会被解析为 XML、SVG、图片或压缩包,还需要继续检查 XXE、路径遍历、解压缩炸弹和内容类型混淆等问题。
建议限制响应大小、读取超时时间、允许的内容类型和最大重定向次数,并避免把内部错误信息、响应头和详细网络异常直接返回给前端。
四、推荐的防御策略
1. 优先使用目标标识,而不是让用户提交任意 URL
如果业务只需要抓取固定供应商或固定站点,不要让用户提交完整 URL。可以让用户选择一个服务标识,再由服务端从配置中心映射到预先登记的地址。这样能显著缩小攻击面,也便于变更和审计。
2. 采用正向允许列表
黑名单很难覆盖所有地址表现形式。更合理的策略是仅允许业务确实需要的域名、协议、端口和路径,并在请求前后都进行校验。允许列表至少应包含:
- 协议:通常只保留业务需要的 HTTPS。
- 域名:使用精确匹配或经过明确设计的子域名匹配。
- 端口:默认只允许 443,必要时逐项登记。
- 路径:限制到具体 API 前缀,避免访问同一站点上的管理路径。
- 响应类型:只接受业务需要的 MIME 类型。
3. 对地址进行规范化和网络范围校验
无论是 IPv4、IPv6 还是域名,都应在连接前解析为实际 IP,并拒绝回环地址、链路本地地址、私有地址、未指定地址以及企业内部明确禁止的网段。不能只检查字符串是否以某个前缀开头。
需要注意的是,是否允许私网地址取决于业务。如果服务本身确实要访问内部服务,应采用精确的服务注册和网络策略,而不是简单地对所有内网开放。对于云环境,还应在网络层限制访问云平台元数据服务,并根据云厂商建议启用更严格的元数据访问机制。
4. 在网络层设置第二道防线
应用层校验容易因新功能、依赖升级或边界遗漏而失效。建议将出网流量经过 egress gateway、正向代理或防火墙策略,默认拒绝不必要的内网访问,只开放登记过的目标。
容器和虚拟机环境中,不要因为“服务运行在隔离网络里”就忽略 SSRF。网络隔离配置经常随着部署变化,应用本身仍应遵循最小权限原则。
五、安全验证:如何在不触碰真实内网的情况下复现
验证修复时,应使用自建的测试环境,不要探测第三方网络或生产内部地址。可以准备两个本地测试服务:
- 允许目标:返回固定文本,并记录请求方法、请求头和访问次数。
- 拒绝目标:模拟回环地址、私有地址、重定向地址和超大响应。
测试重点不是“能否访问某个敏感服务”,而是确认策略是否稳定执行。建议覆盖以下用例:
- 正常的允许域名和不允许域名。
- 域名大小写、尾部点号和国际化域名的规范化。
- 显式端口、非默认端口和缺失端口。
- 重定向到禁止地址,以及多次连续重定向。
- IPv4、IPv6、域名解析失败和解析到多个地址的情况。
- 超时、连接失败、响应过大和内容类型不匹配。
- 并发请求下的连接池、DNS 缓存和限流行为。
一个合格的测试结果应同时记录:原始输入、规范化后的目标、解析结果、最终连接目标、策略判定原因以及是否发生重定向。这样既便于定位绕过问题,也方便后续审计。
六、日志与监控:不要只在漏洞修复后停止
SSRF 的检测价值很大程度上取决于日志质量。建议在不记录敏感响应内容的前提下,保留以下字段:
- 调用功能和业务用户标识。
- 目标域名、端口、协议及规范化后的地址。
- 策略判定结果和拒绝原因。
- DNS 解析结果、连接耗时、响应状态和响应大小。
- 重定向次数、最终目标类别以及异常类型。
监控方面,可以关注短时间内大量请求不同域名、频繁命中拒绝规则、连接超时集中出现、响应体积异常增大等行为。日志中的 URL 参数应进行脱敏,避免把访问令牌、签名参数或用户隐私直接写入集中式日志系统。
七、常见修复误区
- 只过滤几个字符串。 这类修复通常无法覆盖解析差异和重定向问题。
- 只禁止 127.0.0.1。 回环、私网、链路本地和 IPv6 地址都需要纳入策略。
- 校验域名后直接请求原始 URL。 这会留下 DNS 变化和重定向带来的风险。
- 只在前端限制输入。 前端校验不能替代服务端校验。
- 发现问题后直接删除功能。 如果业务确实需要远程访问,应通过固定目标、代理和网络策略实现,而不是留下不受控的通用请求能力。
- 忽略非 HTTP 协议。 如果底层库支持文件、FTP 或其他协议,必须显式限制协议集合。
八、落地检查清单
| 检查项 | 建议结果 |
|---|---|
| 目标来源 | 固定配置或明确的允许列表,避免任意 URL |
| 协议与端口 | 只开放业务必需项,默认拒绝其他组合 |
| DNS 与 IP | 规范化解析,并校验实际连接地址 |
| 重定向 | 默认关闭,或逐跳重新执行策略 |
| 响应控制 | 限制超时、大小、类型和读取时间 |
| 网络边界 | 通过代理、防火墙或 egress 策略二次限制 |
| 审计与告警 | 记录判定原因、最终目标和异常模式 |
总结
SSRF 的本质是“不受控的服务端出网能力”,而不是某个特殊 payload 或单一过滤规则。审计时应从输入点追踪到 DNS 解析、连接建立、重定向、响应处理和网络出口;修复时则应采用正向允许列表、规范化解析、重定向控制、响应限制以及网络层隔离的组合方案。
对于研发团队,最值得长期保留的做法是封装统一的安全 HTTP 客户端,禁止业务代码各自实现 URL 校验,并在代码审查和自动化测试中固定覆盖地址解析、重定向、超时和响应边界。这样才能把一次漏洞修复转化为可持续的工程能力。
推荐意见