背景
SSRF(Server-Side Request Forgery)在实战和代码审计中都很常见,尤其出现在图片抓取、Webhook 回调、URL 预览、文件导入、云存储同步、PDF 转换等功能里。它的特点是入口看起来不敏感,但一旦服务端可以代用户发起请求,就可能访问到原本外部不可达的内网资源。
这篇文章不讨论如何攻击真实目标,主要整理一个偏防守视角的复现与审计方法:如何搭建安全测试环境、如何识别风险代码、如何验证问题边界,以及如何做相对可靠的修复。
典型场景
一个常见业务功能是“通过 URL 上传图片”。用户提交一个图片地址,后端下载后保存到对象存储或本地目录。伪代码大致如下:
POST /api/image/import
{
"url": "
}后端逻辑可能会做一次 HTTP 请求:
resp = httpClient.get(userInputUrl)
if resp.statusCode == 200:
save(resp.body)如果没有对目标地址做限制,服务端可能会请求任意 URL。风险不只在于访问内网地址,还包括探测内部服务、读取云环境元数据、触发内部管理接口、访问本机服务等。
安全复现环境
建议只在本地或授权靶场中复现。可以准备三个组件:
- 业务服务:模拟存在 URL 抓取功能的后端。
- 内部服务:只监听本机或内网地址,用于观察是否被服务端访问。
- 日志服务:记录请求来源、路径和 Header,便于确认请求由谁发出。
下面是一个最小化的内部服务示例,仅用于本地验证:
python3 -m http.server 8081 --bind 127.0.0.1如果业务服务接收用户传入的 URL,并且可以访问:
解析、重定向处理、协议限制和网络出口限制上。代码审计关注点
审计 SSRF 时,我通常先从“能够发起服务端请求”的代码入口入手,而不是只搜索 SSRF 关键字。常见危险函数或组件包括:
- Java:HttpURLConnection、OkHttp、Apache HttpClient、RestTemplate、WebClient。
- Go:http.Get、http.Client.Do、第三方下载库。
- Python:requests、urllib、aiohttp。
- Node.js:axios、node-fetch、request、got。
- PHP:curl、file_get_contents、Guzzle。
重点看这些调用是否直接使用了用户输入,例如:
// 风险示例:未限制协议、主机、重定向和响应大小
String url = request.getParameter("url");
Response response = httpClient.newCall(new Request.Builder()
.url(url)
.build()).execute();
如果入口来自 JSON 参数、表单参数、数据库字段、消息队列、Webhook 配置,也都需要纳入检查。有些 SSRF 不是即时触发,而是保存配置后由异步任务执行,这类问题容易被漏掉。
容易被忽略的细节
1. 只判断字符串前缀不可靠
很多修复会写成只允许 http 或 https:
if (!url.startsWith("http")) {
throw new IllegalArgumentException("invalid url");
}
这类判断过于粗糙。应使用标准 URL 解析库解析 scheme、host、port,再做明确判断。不同语言的 URL 解析行为存在差异,审计时也要关注编码、大小写、用户信息字段、端口解析等情况。
2. DNS 解析与请求发起之间存在时间差
如果先解析域名再请求,期间 DNS 结果可能变化。比较稳妥的做法是:解析后校验 IP,实际连接时固定使用校验过的 IP,同时保留 Host Header;或者在底层连接阶段再次校验目标 IP。高风险业务还应结合网络层出口策略。
3. 重定向可能绕过初始校验
有些代码只校验第一次输入的 URL,但 HTTP 客户端默认跟随 301/302。若跳转后的地址未校验,风险依旧存在。建议关闭自动重定向,手动处理每一次 Location,并对每个跳转目标重复完整校验。
// 以 OkHttp 为例:关闭自动重定向
OkHttpClient client = new OkHttpClient.Builder()
.followRedirects(false)
.followSslRedirects(false)
.build();
4. 响应大小和超时需要限制
即使目标地址合法,也可能造成资源消耗。URL 抓取类功能应设置连接超时、读取超时、最大响应体大小,并避免一次性将响应全部读入内存。
connectTimeout: 3s
readTimeout: 5s
maxBodySize: 5MB
allowedContentTypes: image/png,image/jpeg,image/gif
验证思路
在授权环境中验证 SSRF,不需要对真实内网做扫描。更推荐构造可控服务来确认请求链路:
- 确认服务端是否会请求用户提交的 URL。
- 观察请求来源 IP 是否为目标后端服务器。
- 检查是否携带内部 Header、Cookie 或服务端默认认证信息。
- 测试是否自动跟随重定向。
- 测试是否允许访问本机地址、私有地址、链路本地地址。
- 测试是否允许非 HTTP 协议,或被库隐式支持的协议。
私有地址段至少应纳入拦截范围:
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
fc00::/7
fe80::/10
云环境中还要特别关注元数据服务地址。这里不展开利用方式,防护上可以在应用层拦截,同时在主机或容器网络层禁止业务进程访问元数据服务。
相对可靠的修复方案
SSRF 的修复不建议只靠一层判断。比较稳妥的组合是“业务白名单 + URL 严格解析 + IP 校验 + 禁止自动重定向 + 网络出口控制”。
措施 说明 域名白名单 只允许访问业务明确需要的域名,适合 Webhook、对象存储回调等场景。 协议限制 仅允许 http、https,拒绝 file、gopher、ftp 等非预期协议。 IP 范围拦截 解析域名后拒绝私有地址、回环地址、链路本地地址、保留地址。 重定向复检 关闭自动跳转,手动校验每一次 Location。 超时与大小限制 限制连接时间、读取时间、响应体大小,降低 DoS 风险。 网络层隔离 通过防火墙、安全组、Kubernetes NetworkPolicy 限制出口。
一个简化的校验流程如下:
1. 解析 URL,拒绝解析失败的输入
2. 检查 scheme 是否仅为 http/https
3. 检查 host 是否在业务允许范围内,优先使用白名单
4. 解析 host 对应 IP
5. 拒绝私有地址、回环地址、链路本地地址、保留地址
6. 发起请求时禁用自动重定向
7. 如出现 Location,重复 1-6
8. 限制超时、响应大小和 Content-Type
审计中的经验点
- 不要只搜“url”参数,很多入口命名为 callback、endpoint、avatar、image、webhook、link、target、source。
- 异步任务里的 SSRF 更隐蔽,尤其是导入任务、定时同步、消息消费逻辑。
- 如果系统支持插件、模板、Markdown 渲染、PDF 生成,也要检查资源加载逻辑。
- 云原生环境下,应用层修复之外,出口访问控制很关键。
- 日志中应记录被拒绝的目标 host、解析 IP 和原因,便于排查误拦截,但不要记录敏感 Token。
实际修复时,最容易出问题的是“看似有黑名单,但业务一变就被绕过”。如果业务允许,优先用白名单;如果必须支持用户任意外链,则需要把校验、资源限制和网络隔离都补齐。
总结
SSRF 的本质是服务端替用户访问了不该访问的资源。它不像注入类漏洞那样总是有明显的危险函数,更多时候隐藏在正常业务能力里。审计时应围绕“服务端出网请求”建立检查清单,重点覆盖 URL 解析、DNS、重定向、协议、IP 范围、超时和网络隔离。
从长期维护角度看,建议把 SSRF 防护封装成统一的安全 HTTP 客户端,业务代码只调用受控接口,不要各个模块各写一套校验。这样后续规则调整、日志审计和安全测试都会更可控。

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