跳转到帖子

背景SSRF(Server-Side Request Forgery)在代码审计里经常被低估。它不像 SQL 注入那样直观,也不像文件上传那样容易通过界面复现,但一旦服务端具备主动发起网络请求的能力,风险边界就会扩大到内网服务、云元数据接口、管理端口以及依赖服务。这篇文章整理的是我在审计业务系统中比较常见的一类场景:后端提供“URL 预览、图片抓取、Webhook 回调、文件转存、远程导入”等功能。文章重点放在识别思路、代码层面的风险点和加固方案,不提供针对真实目标的攻击操作。常见场景SSRF 通常出现在“用户可控 URL + 服务端请求”的组合里。典型入口包括:文章或商品详情页的远程图片抓取。站点信息预览,例如输入 URL 后自动抓取标题、描述、图标。Webhook 配置,由服务端向用户填写的地址推送事件。文件导入功能,支持从远程 URL 下载文件。PDF/截图服务,服务端访问指定页面后生成结果。这些功能本身都是合理业务能力,问题在于服务端如果没有限制目标地址、协议、解析结果和跳转链路,就可能被利用去访问原本不应该暴露的资源。审计时优先看的代码形态不同语言写法不同,但风险形态很相似。审计时我一般先全局检索以下关键点:curl_exec
file_get_contents
fopen
fsockopen
HttpClient
RestTemplate
OkHttpClient
requests.get
urllib.request
http.Get
http.Client
axios
fetch
URL.openConnection如果这些调用的 URL 来自用户输入、数据库中用户可编辑字段、消息队列内容或第三方回调参数,就需要继续跟踪数据流。下面是一个简化后的风险示例,常见于远程图片抓取或站点预览功能:// 示例:存在风险的写法,仅用于说明审计点
String url = request.getParameter(\"url\");
String body = httpClient.get(url);
return body;这类代码的问题不只是“没有校验 URL”。更准确地说,是没有校验协议、主机、解析后的 IP、端口、重定向目标、响应大小、超时时间和内容类型。技术分析:为什么简单黑名单不可靠很多项目会做类似下面的校验:if (url.contains(\"127.0.0.1\") || url.contains(\"localhost\")) {
throw new IllegalArgumentException(\"invalid url\");
}这种方式基本不够用。原因包括:主机名可能经过 DNS 解析后指向内网地址。URL 中可能包含用户信息、端口、大小写、编码等容易误判的部分。短链接、重定向链路可能把请求带到非预期地址。IPv6、十进制/八进制等非常规 IP 表达会绕开简单字符串判断。DNS 解析存在变化,校验时和连接时可能不是同一个结果。更稳妥的思路是:先使用标准 URL 解析器解析,再基于解析后的结构做白名单限制,并在实际连接前后都确认目标地址。关键步骤:一套相对可靠的防护流程下面这套流程适合大多数“服务端访问用户提供 URL”的业务场景。1. 限制协议只允许业务确实需要的协议。大多数情况下只需要 HTTP 和 HTTPS,其他协议应直接拒绝。allowedSchemes = [\"http\", \"https\"]不要允许 file、gopher、ftp、dict 等协议。即使当前 HTTP 客户端不支持,也建议显式拒绝,避免后续组件替换带来风险。2. 使用白名单,而不是黑名单如果业务形态允许,优先使用域名白名单。例如 Webhook 只允许公司内部已登记的回调域,图片抓取只允许对象存储或可信 CDN 域名。allowedDomains = [
\"static.example.com\",
\"img.example-cdn.com\"
]如果业务必须访问任意公网 URL,也应至少限制内网地址、保留地址、链路本地地址、回环地址和云元数据常见地址段。3. 校验 DNS 解析结果不能只检查字符串里的 host,需要解析 host 对应的所有 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
0.0.0.0/8
::1/128
fc00::/7
fe80::/10注意:这里只是常见范围,实际还应根据部署环境补充内部网段、容器网段、服务网格地址段和管理网段。4. 控制重定向很多 SSRF 问题发生在重定向阶段。第一跳 URL 可能是合法公网地址,但 302 后进入内网地址。因此建议关闭自动重定向,手动处理每一跳,并对每个 Location 重新执行完整校验。maxRedirects = 3
followRedirects = false

for each redirect:
parse Location
validate scheme
validate host
resolve DNS
validate IP range
continue request重定向次数也要限制,避免循环跳转造成资源消耗。5. 限制端口默认只允许 80 和 443,除非业务明确需要其他端口。很多管理面、缓存、数据库、调试服务都运行在非标准端口,端口限制是低成本高收益的防护。allowedPorts = [80, 443]6. 设置超时和响应大小SSRF 不只会带来信息泄露,也可能造成服务端资源耗尽。必须设置连接超时、读取超时和最大响应体大小。connectTimeout = 3000ms
readTimeout = 5000ms
maxBodySize = 2MB对于图片抓取、文件导入等功能,还要限制 Content-Type,并在下载过程中做流式计数,不能只依赖响应头里的 Content-Length。7. 请求在隔离网络中执行如果系统对安全要求较高,可以把“远程抓取”能力放到独立 worker 或代理服务里,并通过网络策略限制它只能访问公网。这样即使上层校验出现遗漏,也能降低访问内网资产的可能性。建议的网络策略:
业务应用 -> 抓取服务
抓取服务 -> 仅允许公网 HTTP/HTTPS
抓取服务 -X 内网管理网段
抓取服务 -X 云元数据地址
抓取服务 -X 数据库/缓存/消息队列网段Java 场景中的加固示例下面是伪代码级别的示例,重点体现校验流程,不建议直接复制到生产环境使用。生产环境需要结合具体 HTTP 客户端、DNS 解析策略和业务白名单实现。URI uri = new URI(inputUrl);

if (!List.of(\"http\", \"https\").contains(uri.getScheme())) {
throw new IllegalArgumentException(\"unsupported scheme\");
}

String host = uri.getHost();
if (host == null || host.isBlank()) {
throw new IllegalArgumentException(\"invalid host\");
}

int port = uri.getPort();
if (port == -1) {
port = \"https\".equals(uri.getScheme()) ? 443 : 80;
}

if (port != 80 && port != 443) {
throw new IllegalArgumentException(\"unsupported port\");
}

InetAddress[] addresses = InetAddress.getAllByName(host);
for (InetAddress address : addresses) {
if (isPrivateOrReserved(address)) {
throw new IllegalArgumentException(\"forbidden address\");
}
}

// 关闭自动跳转,手动校验每一跳
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.followRedirects(HttpClient.Redirect.NEVER)
.build();其中 isPrivateOrReserved 不应只判断 isSiteLocalAddress,还需要覆盖回环、链路本地、保留地址、IPv6 私有地址以及企业内部自定义网段。日志与监控建议SSRF 防护不只是代码校验,运行期监控同样重要。建议记录以下字段:原始 URL、解析后的 scheme、host、port。DNS 解析结果,但注意避免记录敏感业务数据。是否发生重定向,以及每一跳目标。拦截原因,例如 forbidden_ip、unsupported_scheme、redirect_limit。请求耗时、响应大小、Content-Type。这些日志可以帮助定位误拦截,也能在出现异常访问时快速回溯入口。测试与验证上线前建议准备一组安全测试用例,覆盖正常业务和边界场景。测试重点不是“打穿系统”,而是验证防护逻辑是否按预期工作。测试项预期结果合法 HTTPS 公网地址允许访问非 HTTP/HTTPS 协议拒绝回环地址拒绝内网地址拒绝链路本地地址拒绝公网地址重定向到内网地址拒绝超大响应体中断下载超时连接按超时策略退出如果项目使用容器或云环境,还要额外验证抓取服务是否能访问云元数据地址和内部服务网段。理想结果是:即使应用层校验失效,网络层也不允许访问。容易忽略的细节不要在校验前对 URL 做不规范的手工拼接,容易引入解析差异。不要用正则完整解析 URL,优先使用语言标准库或成熟组件。校验 host 后仍要校验解析 IP,二者缺一不可。禁止自动携带内部 Cookie、Token 或代理认证信息访问外部 URL。远程下载文件时不要直接使用原始文件名写入本地路径。对于 Webhook,可采用“用户登记域名 + 后台审核 + 签名回调”的方式降低风险。总结SSRF 的核心不是某个危险函数,而是服务端网络访问能力被用户输入影响。审计时要沿着“输入来源、URL 解析、协议限制、DNS 解析、IP 判断、端口限制、重定向处理、响应控制、网络隔离”这条链路逐项确认。实践中最有效的方案通常不是单点修补,而是多层约束:业务白名单优先,代码层做严格校验,HTTP 客户端关闭自动跳转并限制资源消耗,网络层隔离内网和元数据地址。这样即使某一层出现遗漏,也不至于直接暴露关键内部资源。

代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…