跳转到帖子

SSRF(Server-Side Request Forgery,服务端请求伪造)并不是一个只靠“禁止访问内网 IP”就能彻底解决的问题。实际审计中,漏洞往往出现在图片抓取、Webhook、文档转换、在线预览、URL 导入等功能里,风险则取决于请求协议、重定向处理、DNS 解析方式、网络出口策略以及响应内容是否会回显。

本文以代码审计和修复验证为主线,整理一套适合在授权测试环境中使用的方法。示例围绕 Java Web 服务展开,但分析思路同样适用于其他语言和框架。

一、先明确 SSRF 的实际风险

服务端代发请求本身并不一定是漏洞。关键在于攻击者是否能够影响请求目标,以及服务端是否拥有攻击者不应具备的网络可达性或身份权限。

  • 访问云平台元数据服务,获取临时凭证或实例信息。
  • 访问内网管理面板、监控系统、配置中心等仅对内部开放的服务。
  • 利用服务端身份访问其他系统接口,形成权限边界绕过。
  • 探测内网端口或服务类型,并将结果通过响应、错误信息或时间差泄露。
  • 借助 file、gopher 等协议访问本地文件或构造非 HTTP 请求。

风险判断不能只看“是否能请求 127.0.0.1”。还应结合部署环境、容器网络、云厂商元数据接口、服务账号权限和响应回显方式进行评估。

二、代码审计:从输入点追踪到请求汇

审计时不要只搜索包含 URL 的参数名。更可靠的方法是从网络请求汇反向追踪数据流,重点关注 HTTP 客户端、URL 类、文件下载器和第三方抓取组件。

常见的危险汇包括:

RestTemplate.getForObject(url, String.class)
WebClient.create(url).get().retrieve()
HttpClient.execute(new HttpGet(url))
new URL(userInput).openConnection()
Files.copy(new URI(userInput).toURL().openStream(), target)

随后向上追踪参数来源,检查它是否来自以下位置:

  • 查询参数、JSON 字段、表单字段或上传文件中的 URL。
  • 数据库中可被低权限用户修改的配置项。
  • Webhook、回调地址、图片地址、远程模板地址等业务字段。
  • HTTP Header、重定向地址或第三方接口返回的数据。

审计时建议记录一条完整的数据流:输入点、规范化处理、协议判断、DNS 解析、连接建立、重定向处理、响应读取和输出位置。很多“看起来有校验”的实现,只校验了第一步,后续重定向或二次解析仍然可以绕过。

三、常见但不可靠的防护方式

1. 只判断字符串是否包含内网地址

例如检查字符串中是否出现 127.0.0.1、localhost 或 169.254.169.254。这类判断容易受到大小写、尾随点、IPv6、整数形式、URL 解析差异和 DNS 解析结果变化的影响。

2. 只校验 URL 的 hostname

hostname 经过 DNS 解析后可能指向受限地址,而且不同网络库对用户名密码、端口、IPv6 和特殊字符的解析行为并不完全一致。应当基于规范化后的 URI、解析结果和最终连接地址共同判断。

3. 只检查第一次请求,不处理重定向

攻击者可以让公开地址返回 301 或 302,将服务端引向受限网络。因此,下载器应默认禁止跨主机重定向;如果业务必须支持重定向,则每一次跳转都必须重新执行完整校验。

4. 使用域名黑名单代替网络出口控制

黑名单难以覆盖所有内部域名和解析变化。更稳妥的做法是应用层限制目标范围,同时在网络层实施出站访问控制,形成纵深防御。

四、推荐的安全校验流程

一个相对完整的处理流程如下:

  1. 仅允许业务必需的协议,通常只保留 HTTPS,必要时再评估 HTTP。
  2. 解析 URI,并拒绝用户名密码、异常端口和不符合业务规则的路径。
  3. 对主机名进行规范化,移除尾随点,统一大小写,并限制主机名长度。
  4. 解析所有 A 和 AAAA 记录,对每个结果判断是否属于回环、私有、链路本地、保留或其他受限网段。
  5. 建立连接前再次确认目标地址,避免解析结果与实际连接之间出现不一致。
  6. 默认关闭自动重定向;确需支持时,对每个 Location 重新执行校验,并限制跳转次数。
  7. 限制连接超时、读取超时、响应大小和下载文件类型,避免被利用为资源消耗点。
  8. 在网络层限制服务进程的出站范围,禁止无业务必要的内网网段和元数据地址。

需要注意 DNS 解析和连接之间的竞态问题。单纯“先解析并检查,再让 HTTP 库自行解析一次”可能产生 DNS rebinding 风险。较严格的实现应尽量使用已经校验的 IP 建立连接,同时保留正确的 Host 和 TLS SNI;如果底层客户端难以控制连接地址,应通过固定解析器、受控代理或网络出口策略降低风险。

五、一个安全化的 Java 校验示例

下面示例只展示核心校验思路,不建议直接复制到生产环境。生产实现还应结合 HTTP 客户端的连接建立方式、代理配置、IPv6 支持和业务允许的目标范围进行测试。

private static final Set<String> ALLOWED_SCHEMES = Set.of("https");

static boolean isRestricted(InetAddress address) {
    return address.isAnyLocalAddress()
        || address.isLoopbackAddress()
        || address.isLinkLocalAddress()
        || address.isSiteLocalAddress();
}

static URI validateTarget(String raw) throws Exception {
    URI uri = new URI(raw).normalize();

    if (!ALLOWED_SCHEMES.contains(uri.getScheme().toLowerCase(Locale.ROOT))) {
        throw new SecurityException("unsupported scheme");
    }
    if (uri.getUserInfo() != null || uri.getHost() == null) {
        throw new SecurityException("invalid authority");
    }
    if (uri.getPort() != -1 && uri.getPort() != 443) {
        throw new SecurityException("unsupported port");
    }

    for (InetAddress address : InetAddress.getAllByName(uri.getHost())) {
        if (isRestricted(address)) {
            throw new SecurityException("restricted address");
        }
    }
    return uri;
}

这个示例仍有几个工程上的限制:

  • isSiteLocalAddress 不能覆盖所有需要阻断的特殊网段,IPv4、IPv6、云平台保留地址应使用明确的 CIDR 策略补充判断。
  • InetAddress 的解析结果可能被缓存,缓存策略和 DNS TTL 需要纳入设计。
  • 校验通过后,HTTP 客户端不能再次无约束地重新解析域名。
  • 如果允许代理,代理本身可能成为实际发起连接的一方,校验逻辑应放在正确的网络边界上。

六、在授权环境中的验证方法

验证的目标是确认修复是否覆盖完整请求链路,而不是对真实内网进行探测。建议搭建隔离实验环境,准备一个公开测试服务、一个明确标记的拒绝目标和一个能够返回重定向的控制服务。

验证项目至少包括:

  • 合法 HTTPS 目标可以正常访问,业务功能没有被误伤。
  • 回环地址、私有地址、链路本地地址和 IPv6 本地地址会被拒绝。
  • 域名解析到受限地址时会被拒绝,而不是只根据域名字符串判断。
  • 公开地址重定向到受限地址时,请求会在重定向阶段被阻断。
  • 不支持的协议、非默认端口、带用户信息的 URI 会被拒绝。
  • 超大响应、慢响应和过多跳转会触发资源限制。
  • 异常日志中不会记录完整的敏感 URL、凭证或响应内容。

测试时应保存请求 ID、校验结果、最终目标类别和耗时等信息,但不要在生产日志中记录 Authorization、Cookie、完整查询参数或下载内容。

七、日志、监控与网络侧防护

应用层修复并不意味着可以忽略网络配置。建议为执行远程请求的服务单独配置出站策略,只允许访问业务所需的域名或代理。对于云环境,应确认工作负载无法直接访问不必要的元数据接口,并结合云平台提供的元数据访问保护机制。

监控方面可以关注:

  • 短时间内大量不同目标域名或端口的请求。
  • 访问失败率、连接超时和 DNS 异常明显升高。
  • 请求目标集中出现 IP 字面量、内部域名或非常规端口。
  • 重定向次数异常、响应体大小异常或下载类型与业务不符。

日志内容应服务于审计,而不是复现攻击。推荐记录目标的脱敏摘要、解析后的地址类别、阻断原因、调用方和请求 ID,避免保存可能包含凭证的原始 URL。

八、修复评审中的几个容易遗漏点

  • 上传文件解析器、PDF 转换器、图片处理器可能自行发起外部请求,不能只审计主业务控制器。
  • 异步任务和消息队列消费者往往使用另一套 HTTP 客户端,修复需要覆盖所有执行路径。
  • 前端限制不能替代服务端校验,接口调用者可以绕过前端直接提交数据。
  • 如果业务只需要访问固定厂商或固定目录,应优先采用允许列表,而不是通用 URL 访问能力。
  • 禁止访问 IP 不等于禁止内网信息泄露,错误信息、响应头和时间差同样需要控制。
  • 修复后要检查依赖库默认行为,尤其是自动重定向、代理继承、连接池复用和 DNS 缓存。

总结

SSRF 审计的核心不是寻找一个可以替换的 URL 参数,而是还原“用户输入如何影响服务端网络连接”的完整过程。可靠的防护应同时覆盖协议、URI 规范化、DNS 解析、IP 分类、重定向、连接行为、资源限制和网络出口。

从工程实践看,最值得优先落实的是三点:业务上尽量使用目标允许列表;应用层对每一次实际连接进行统一校验;网络层默认拒绝不必要的出站访问。只有把代码审计、修复验证和基础设施策略结合起来,才能降低 SSRF 在不同组件和部署环境中的复发概率。

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

0篇意见

推荐意见

没有意见。

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