背景
SSRF(Server-Side Request Forgery,服务端请求伪造)一直是 Web 安全里比较容易被低估的问题。它不像 SQL 注入那样直接,也不像文件上传那样容易被肉眼识别,但一旦出现在具备内网访问能力的服务端组件上,影响面往往会被放大。
这类问题常见于图片抓取、URL 预览、Webhook 回调、文件转存、在线文档解析、第三方接口代理等场景。开发侧通常认为“只是服务端帮用户请求一个 URL”,但安全风险正是来自这个“帮用户请求”。
本文从代码审计和漏洞验证的角度,整理一套相对克制、可落地的 SSRF 分析方法。重点放在识别风险点、构造安全验证、定位根因和修复方案,不涉及对真实内网资产的未授权探测。
典型场景
在一次业务代码审计中,遇到过类似功能:用户提交一个图片 URL,后端下载图片后上传到对象存储,再返回新的图片地址。简化后的逻辑如下:
public String uploadByUrl(String imageUrl) {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(5000);
InputStream inputStream = conn.getInputStream();
String objectKey = ossClient.upload(inputStream);
return cdnDomain + "/" + objectKey;
}表面上看,这段代码只是下载图片。但从审计角度看,它满足 SSRF 的几个典型条件:
- 用户可控 URL 进入服务端请求逻辑。
- 服务端直接发起网络请求。
- 缺少协议、域名、IP、跳转、响应大小等限制。
- 请求结果虽然不一定完整回显,但可能通过状态码、耗时、错误信息形成侧信道。
如果该服务部署在云环境或内网环境中,还需要额外关注是否能访问元数据服务、内部管理后台、未暴露的服务端口等敏感目标。
风险点识别思路
代码审计时,建议先从“外部输入是否参与服务端请求”这个问题入手,而不是只搜某一个函数名。不同语言和框架中,危险点可能分散在 HTTP 客户端、文件读取、图片处理、XML 解析、浏览器渲染等模块里。
常见关键字包括:
Java:
new URL(...).openConnection()
HttpURLConnection
OkHttpClient.newCall(...)
RestTemplate.getForObject(...)
WebClient.create(...)
Go:
http.Get(...)
http.Post(...)
client.Do(req)
Python:
requests.get(...)
urllib.request.urlopen(...)
httpx.get(...)
Node.js:
axios.get(...)
fetch(...)
http.request(...)
request(...)审计时要重点追踪以下数据流:
- URL 是否完全由用户传入,还是拼接固定前缀后生成。
- 是否只校验了字符串前缀,例如 startsWith("http")。
- 是否允许 30x 跳转,跳转后的目标是否重新校验。
- 是否解析域名后校验 IP,还是只校验了域名文本。
- 是否存在 DNS 重绑定风险。
- 是否限制响应大小、Content-Type、下载时间。
- 是否存在错误信息、响应内容或耗时差异回显。
安全验证方式
验证 SSRF 时,建议使用自建的测试服务或本地可控服务,避免对未授权目标进行探测。比如可以在测试机上启动一个简单 HTTP 服务,用于观察后端是否发起请求。
python3 -m http.server 8000然后在业务入口提交如下测试 URL:
302 到另一个可控地址,观察后端是否继续请求。这里仍然建议使用自有测试地址,不要指向真实内网系统。HTTP/1.1 302 Found
Location: 修复失败,很多时候不是因为完全没做限制,而是限制做得不完整。下面几个点比较值得注意。1. 只校验字符串是不够的
例如只判断 URL 是否以 开头,很容易被特殊格式、用户信息字段、编码差异等影响。更稳妥的做法是使用标准 URL 解析库,取出 scheme、host、port 等字段后分别判断。
URI uri = new URI(inputUrl);
String scheme = uri.getScheme();
String host = uri.getHost();
int port = uri.getPort();
同时应拒绝非预期协议,例如 file、gopher、ftp、dict 等。多数业务场景只需要 http 和 https。
2. 域名解析后还要校验 IP
只判断 host 不在黑名单里并不可靠。域名最终解析到哪里,需要在服务端解析后判断。建议拒绝以下地址范围:
- 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、fc00::/7、fe80::/10 等 IPv6 本地或内网地址。
- 云厂商元数据服务常用地址,例如 169.254.169.254。
如果业务只需要访问少数可信域名,优先使用白名单,而不是维护一长串黑名单。
3. 跳转后必须重新校验
HTTP 客户端默认跟随跳转是常见行为。安全做法是关闭自动跳转,手动处理 Location,并在每一次跳转前重新执行完整校验。
OkHttpClient client = new OkHttpClient.Builder()
.followRedirects(false)
.followSslRedirects(false)
.build();
如果确实需要支持跳转,可以限制最大跳转次数,并对每个新 URL 重新校验 scheme、host、port、解析 IP。
4. DNS 重绑定问题
DNS 重绑定会让同一个域名在不同时间解析到不同 IP。简单来说,第一次校验时解析到公网 IP,请求时可能变成内网 IP。应尽量做到:
- 解析后使用校验通过的 IP 建立连接。
- 请求前后不要重复依赖不受控 DNS 结果。
- 在网络层配合 egress 策略限制服务可访问网段。
单靠应用层校验很难覆盖所有边界,网络出口策略是非常重要的兜底。
5. 响应内容也要限制
即使 SSRF 不能访问敏感地址,恶意用户仍可能利用该功能下载超大文件、慢响应资源或非图片内容,造成资源消耗。建议限制:
- 连接超时和读取超时。
- 最大响应大小。
- 允许的 Content-Type。
- 文件头魔数校验。
- 并发和频率。
Content-Type: image/png
Content-Length: 102400
不要只相信 Content-Type,必要时应读取文件头进行二次判断。
修复建议
结合实际项目,比较推荐的修复策略是“白名单 + URL 标准解析 + IP 校验 + 禁止自动跳转 + 网络出口限制”。示例流程如下:
- 解析 URL,拒绝非法格式。
- 仅允许 http、https。
- 校验 host 是否在业务白名单内。
- 解析 host 对应 IP,并拒绝内网、回环、链路本地、元数据地址。
- 禁止自动跳转;如需跳转,逐次校验。
- 限制端口,一般只允许 80、443 或业务指定端口。
- 限制超时、响应大小、Content-Type。
- 在容器、主机或网关层配置出站访问策略。
一个简化后的校验伪代码如下:
boolean validateUrl(String input) {
URI uri = new URI(input);
if (!Set.of("http", "https").contains(uri.getScheme())) {
return false;
}
String host = uri.getHost();
if (!allowDomains.contains(host)) {
return false;
}
List<InetAddress> addresses = resolve(host);
for (InetAddress address : addresses) {
if (isPrivateOrLocalAddress(address)) {
return false;
}
}
int port = uri.getPort();
if (port != -1 && !Set.of(80, 443).contains(port)) {
return false;
}
return true;
}
需要注意,白名单域名也应该谨慎选择。如果白名单域名本身存在开放跳转、用户可控 DNS、用户可上传 HTML 等问题,仍可能成为风险入口。
日志与监控
修复之外,建议为这类功能补充必要的安全日志。日志不需要记录完整敏感数据,但至少应包含:
- 提交方用户或调用方标识。
- 原始 URL 的 host、scheme、port。
- 最终请求目标 IP。
- 是否发生跳转。
- 响应大小、耗时、失败原因。
- 拦截原因,例如命中内网地址或非白名单域名。
这些信息对后续排查异常调用很有帮助。比如短时间内大量请求不同端口、频繁触发内网地址拦截、请求耗时异常,都值得关注。
审计清单
实际代码审计时,可以按下面这张表快速过一遍:
检查项 风险表现 建议 用户可控 URL 外部输入直接进入 HTTP 客户端 追踪数据流,确认是否可控 协议限制 允许 file、ftp 等非预期协议 仅允许 http/https 域名校验 只做字符串前缀判断 标准解析后白名单校验 IP 校验 可解析到内网或本地地址 拒绝私有、回环、链路本地地址 跳转处理 自动跟随 30x 关闭自动跳转或逐次校验 响应限制 可下载大文件或慢响应 限制大小、超时、类型 出口访问 服务可访问任意内网 配置网络层 egress 策略
总结
SSRF 的本质不是某个函数危险,而是服务端根据用户输入访问了不应访问的资源。审计时不要只看是否有回显,也要关注网络位置、请求能力、跳转、DNS、响应差异这些细节。
从修复角度看,单点过滤很容易遗漏边界。更可靠的方式是应用层做严格白名单和解析校验,HTTP 客户端禁用危险默认行为,基础设施层限制出口网络。这样即使某一层出现疏漏,也不至于直接扩大成严重问题。
个人经验里,SSRF 最容易出现在“看起来只是工具函数”的地方,比如图片下载、链接预览、文件转存。代码审计时把这类能力单独拉清单,比单纯搜索漏洞关键字更有效。

代码审计、调用链与关键函数定位示意
推荐意见