跳转到帖子

背景

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 最容易出现在“看起来只是工具函数”的地方,比如图片下载、链接预览、文件转存。代码审计时把这类能力单独拉清单,比单纯搜索漏洞关键字更有效。
代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

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

SiteMap HACKHAT © 2026 All rights reserved.