跳转到帖子

SSRF(Server-Side Request Forgery,服务端请求伪造)并不是“发现一个可控 URL”这么简单。真正影响风险等级的因素包括:请求由哪个组件发起、是否能访问内网地址、是否跟随重定向、是否携带敏感凭据,以及应用对响应内容如何处理。本文以代码审计和修复验证为主线,整理一套适用于 Web 服务的 SSRF 分析方法,重点放在可复现、可解释和可持续修复上。

一、问题背景与典型场景

常见的 SSRF 入口包括图片抓取、Webhook、在线文档转换、URL 预览、远程文件导入、第三方回调测试等功能。它们通常具有相似的数据流:

  • 用户提交一个 URL 或主机名;
  • 服务端对其进行 DNS 解析、建立连接并发送请求;
  • 服务端将响应保存、转换、解析或回显给用户。

风险并不只来自访问云平台元数据。即使没有云环境,服务端请求能力也可能触达管理后台、内部监控、数据库代理、调试接口或仅绑定在回环地址上的服务。因此审计时应先识别“请求能力”和“网络边界”,再判断具体影响。

二、审计时先画清数据流

建议不要一开始就围绕某个 Payload 反复尝试,而是先回答以下问题:

  • 输入点在哪里?是完整 URL、主机名、路径,还是由多个参数拼接而成?
  • URL 在哪里被解析?是否存在二次解析、重定向或代理转发?
  • 实际发起请求的库是什么?是否默认支持 HTTP、HTTPS 以外的协议?
  • 请求使用什么网络身份?是否带有内部 Token、Cookie 或云环境凭据?
  • 响应是否会回显、落盘、进入 XML/JSON/图片解析器?
  • 请求是否受到连接超时、响应大小和并发数量限制?

以 Java 服务为例,审计重点通常不是单个调用点,而是从 Controller、Service 到 HTTP 客户端的完整链路。可以优先搜索 URL 参数的流向,以及常见客户端调用,例如 URLConnection、HttpClient、OkHttp、WebClient、RestTemplate 等。

String target = request.getParameter('url');
return httpClient.get(target);

如果中间只做了空值判断、后缀判断或简单字符串替换,通常不能视为有效防护。URL 的语义解析不能依赖字符串前缀匹配,因为端口、凭据、编码、重定向和 DNS 解析都会改变最终连接目标。

三、URL 校验中最容易被忽略的细节

1. 解析结果与原始字符串不一致

安全判断应基于标准 URL 解析器得到的结构化字段,而不是对原始字符串执行 startsWith 或 contains。至少需要分别处理协议、主机名、端口、用户名信息和路径。

2. DNS 解析与连接之间存在时间窗口

即使校验阶段确认域名解析到公网地址,真正建立连接时也可能得到不同结果。尤其是在允许用户控制域名或存在 DNS 变化的场景中,仅在校验阶段解析一次并不可靠。更稳妥的方式是使用受控的出站代理,在代理侧统一完成解析、连接和目标地址校验。

3. 重定向会绕过一次性校验

初始 URL 可能指向允许访问的地址,但服务端跟随 30x 跳转后进入内部网络。因此应默认关闭自动重定向;如果业务必须支持,应对每一次跳转重新执行协议、地址范围和端口策略校验,并限制最大跳转次数。

4. IPv4、IPv6 与特殊地址范围

不能只拦截 127.0.0.1。实际策略应覆盖回环地址、私有地址、链路本地地址、未指定地址、组播地址、保留地址以及 IPv6 对应范围。还要注意 IPv4 映射的 IPv6 地址,以及不同文本形式在解析后的等价性。

四、推荐的防护实现

业务允许的情况下,最安全的方案不是“允许所有地址,再维护黑名单”,而是建立严格的出站白名单:限定协议为 HTTPS,限定端口,限定目标域名后缀或完整域名,并通过独立的 egress 代理访问外部资源。

如果确实需要支持用户输入任意公网 URL,可以将校验拆分为几个步骤:

  • 只允许业务需要的协议,例如 HTTPS;
  • 拒绝 URL 中的用户名和密码字段;
  • 明确处理默认端口与显式端口;
  • 解析所有 A 和 AAAA 记录,并拒绝解析到受限地址的目标;
  • 建立连接前再次确认最终地址;
  • 关闭或严格控制重定向;
  • 限制连接超时、读取超时、响应体大小和下载时间;
  • 不把内部响应原样回显给普通用户。

下面是一个用于说明策略的 Python 伪代码。实际项目中应结合框架的网络库和统一出站代理实现,不能直接复制后视为完整安全组件。

from urllib.parse import urlparse
import ipaddress
import socket

def is_public_address(address):
    ip = ipaddress.ip_address(address)
    return not (ip.is_private or ip.is_loopback or ip.is_link_local or
                ip.is_reserved or ip.is_multicast or ip.is_unspecified)

def validate_target(raw_url):
    parsed = urlparse(raw_url)
    if parsed.scheme not in ('https',):
        raise ValueError('scheme is not allowed')
    if not parsed.hostname or parsed.username or parsed.password:
        raise ValueError('invalid host information')
    if parsed.port not in (None, 443):
        raise ValueError('port is not allowed')

    addresses = socket.getaddrinfo(parsed.hostname, 443, type=socket.SOCK_STREAM)
    for item in addresses:
        address = item[4][0]
        if not is_public_address(address):
            raise ValueError('resolved address is not allowed')
    return parsed

这段示例仍然存在 DNS 竞态问题,因此更适合作为审计思路展示。生产环境应让请求经过固定的出站网关,由网关在同一网络路径中完成解析、连接和策略检查,应用自身只接收经过策略过滤的结果。

五、如何设计安全的复现与验证

漏洞验证应在授权测试环境中完成,优先使用自建的测试服务,不要直接探测生产内网、云平台管理地址或第三方系统。建议准备以下组件:

  • 一个可记录请求方法、Host、路径和来源地址的外部测试服务;
  • 一个仅绑定在测试机回环地址的模拟内部服务;
  • 一个返回重定向的测试端点;
  • 一个解析较大响应或延迟响应的测试端点,用于验证超时和大小限制。

验证重点不是获取敏感数据,而是确认策略是否真正生效:

  • 访问明确允许的测试域名时,请求能够正常完成;
  • 访问回环、私有和链路本地地址时,请求在连接前被拒绝;
  • 域名解析到受限地址时,请求被拒绝;
  • 跳转到受限地址时,第二跳被阻断;
  • 超时和超大响应不会长期占用工作线程或内存;
  • 错误信息不会泄露内部 IP、完整响应头或网络拓扑。

可以在测试服务端记录如下字段,便于判断防护发生在什么阶段:

timestamp
request_id
source_service
requested_host
resolved_address
scheme
port
redirect_count
blocked_reason
response_size
elapsed_ms

六、代码审计中的常见误区

  • 只检查 URL 是否以 https 开头,却没有检查解析后的主机和端口。
  • 只拦截 127.0.0.1,没有覆盖 IPv6、私有网段和链路本地地址。
  • 校验一次后允许客户端自动跟随重定向。
  • 使用黑名单维护所有内部网段,却忽略新的网络环境和地址表示方式。
  • 把 SSRF 仅当作读取文件的问题,忽略内部管理接口上的状态修改操作。
  • 只修复一个 Controller,没有排查项目内其他 URL 抓取功能。
  • 修复后只做正向测试,没有验证超时、异常、重定向和多地址解析。

七、纵深防御与监控

应用层校验不是唯一防线。生产环境建议同时配置出站网络策略,默认禁止业务容器访问不必要的内网网段和管理端口。对于确需访问外部资源的服务,使用独立的网络身份和最小权限凭据,避免请求进程能够直接访问云环境敏感接口。

日志方面,建议记录请求 ID、目标域名、解析地址、最终连接地址、阻断原因、响应大小和耗时,但不要记录完整 Authorization、Cookie 或响应正文。监控上可以关注短时间内大量不同目标、频繁解析失败、访问受限网段、异常长连接和重定向次数突增等信号。

八、修复验收清单

检查项验收标准
协议仅允许业务必需协议,禁止不必要的本地文件或特殊协议
主机使用结构化解析和明确白名单,禁止凭据字段
地址覆盖 IPv4、IPv6 及所有受限地址范围
解析校验与实际连接路径一致,避免 DNS 竞态
重定向默认关闭,开启时逐跳校验并限制次数
资源设置连接、读取、响应大小和并发限制
网络通过出站策略限制容器或主机可访问范围
日志可审计但不记录敏感凭据和完整响应内容

总结

SSRF 的核心不是某个固定绕过技巧,而是服务端把用户输入转化为网络请求后,边界控制是否完整。高质量的审计应沿着“输入解析—地址解析—连接建立—重定向—响应处理—网络权限”逐层检查;高质量的修复则应结合应用白名单、出站代理、网络隔离、资源限制和审计监控。把验证重点放在安全边界是否被突破,而不是敏感数据是否被取出,通常更容易形成可复用、可验收的长期防护方案。

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

0篇意见

推荐意见

没有意见。

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