跳转到帖子

服务端请求伪造(SSRF)并不只是“让服务器代替用户发起一次请求”。在真实项目中,它往往与 URL 解析、DNS 解析、重定向、代理配置和内网服务信任边界同时相关。单纯增加一个字符串黑名单,通常只能挡住最直观的测试用例,无法形成稳定的安全边界。

本文以代码审计和本地实验环境为主线,介绍如何识别 SSRF 的数据流、构造安全复现、定位绕过原因,并给出更适合生产环境的修复思路。文中的请求目标均限定在本机或专用测试网段,不涉及对第三方系统的探测。

一、问题场景与审计入口

常见的 SSRF 入口包括:根据 URL 抓取远程图片、生成网页快照、导入远程文件、Webhook 测试、PDF 转换、云资源元数据代理以及服务器端代理接口。审计时不应只搜索“ssrf”关键词,而应从外部可控数据进入网络请求库的位置开始追踪。

  • 定位 HTTP 客户端调用,例如 Java 的 URLConnection、HttpClient,Python 的 requests、urllib,Go 的 net/http。
  • 向上追踪请求参数来源,确认是否来自 HTTP 参数、JSON 字段、文件内容或任务队列。
  • 向下检查请求前后的处理逻辑,特别关注重定向、代理、DNS 解析和响应内容回传。
  • 确认访问权限。只有外部用户可以影响目标地址,且服务端确实发起了网络请求,才具备进一步分析价值。

一个典型的高风险代码可能如下:

public byte[] fetch(String target) throws IOException {
    URL url = new URL(target);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setConnectTimeout(3000);
    connection.setReadTimeout(5000);
    return connection.getInputStream().readAllBytes();
}

这段代码的问题不在于使用了某个特定类,而在于它把用户输入直接交给了网络层,没有对协议、主机、端口、解析结果和重定向行为建立约束。

二、先定义安全边界,再设计测试

SSRF 分析的第一步不是构造复杂 URL,而是明确业务到底需要访问哪些目标。如果业务只是下载公司对象存储中的文件,就不应允许任意协议、任意端口和任意互联网地址。安全策略应尽可能从“允许什么”出发,而不是从“禁止什么”出发。

建议在测试环境准备一个本地目标服务,并记录服务端是否收到请求。例如:

python3 -m http.server 18080 --bind 127.0.0.1

然后在被测应用中使用仅指向本机测试服务的地址进行验证。测试结果至少应记录以下信息:

  • 应用是否发起了 TCP 连接;
  • 目标服务是否收到 HTTP 请求;
  • 请求使用的 Host、端口和路径是否符合预期;
  • 应用是否跟随了 30x 重定向;
  • 应用是否把响应正文或错误信息返回给调用者。

如果只是验证“是否能出网”,可以在本地搭建一个受控回显服务,不要把测试请求发送到不属于自己的公网地址。对生产系统进行验证时,应先取得授权,并优先采用日志、网络流量和模拟响应进行确认。

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

很多修复方案使用字符串匹配,例如判断 URL 是否以某个域名开头,或者禁止出现 127.0.0.1。这种做法的问题是字符串表示和实际网络目标不是一回事。

第一,必须限制协议。通常只允许业务所需的 https,必要时再允许 http,并明确拒绝 file、ftp、gopher 等不需要的协议。协议校验应在标准解析之后完成,不要仅依赖字符串前缀。

第二,必须对主机名进行规范化和解析。域名可能解析到 IPv4、IPv6 或多个地址,应用不能只检查域名文本而忽略最终连接的 IP。对内网、回环、链路本地和未指定地址,应按业务需求统一拒绝。

第三,必须限制端口。即使目标地址是允许的域名,也不应默认允许访问任意端口。对固定服务场景,最好采用固定端口或明确的端口白名单。

第四,必须处理重定向。初始 URL 通过校验后,服务端仍可能跟随 302 跳转到不允许的目标。更稳妥的做法是默认关闭自动重定向,或者对每一次跳转重新执行完整校验,并限制跳转次数。

第五,解析和连接之间存在时间窗口。程序先解析域名再建立连接时,DNS 记录可能发生变化。高风险场景下,应尽量使用经过校验的解析结果建立连接,并避免让底层客户端再次根据原始域名自由解析。具体实现需要结合语言和 HTTP 客户端能力验证,不能只停留在业务层判断。

四、一个相对完整的校验流程

下面是适合代码审计和单元测试的逻辑示例。它不是可以直接复制到所有项目的通用库,但可以作为检查清单:

def validate_target(raw_url):
    parsed = urlparse(raw_url)

    if parsed.scheme not in {'https'}:
        raise ValueError('unsupported scheme')
    if not parsed.hostname:
        raise ValueError('missing host')
    if parsed.username or parsed.password:
        raise ValueError('userinfo is not allowed')
    if parsed.port not in {443}:
        raise ValueError('unsupported port')

    host = parsed.hostname.rstrip('.').lower()
    if host not in ALLOWED_HOSTS:
        raise ValueError('host is not allowed')

    addresses = resolve_all_addresses(host)
    for address in addresses:
        if is_loopback(address) or is_private(address):
            raise ValueError('destination is not public')

    return parsed

这段逻辑仍有几个前提:ALLOWED_HOSTS 必须是精确匹配,而不是简单的后缀匹配;resolve_all_addresses 需要同时处理 IPv4 和 IPv6;is_private 等判断应覆盖回环、私有、链路本地、保留和未指定地址;实际连接时还要考虑 DNS 变化和重定向。

如果业务确实需要访问内部服务,不建议通过“允许所有内网地址”来实现。更好的方式是为目标服务建立固定的服务端代理:代理只接受业务需要的资源标识,服务端将标识映射到固定地址,并在网络层通过防火墙或服务网格限制可达范围。

五、审计和复现时的关键观察点

在漏洞复现中,状态码不是唯一证据。有些应用会吞掉响应正文,但网络请求已经发生;有些应用返回统一错误页面,却在后台异步访问了目标。因此建议同时观察应用日志、目标服务访问日志和出口流量。

  • 应用日志:记录经过脱敏的目标主机、解析结果、最终连接地址、状态码和失败原因。
  • 目标日志:确认请求是否到达,以及是否发生了多次请求或重定向。
  • 网络侧日志:确认连接的实际目的 IP,避免只看 URL 字符串。
  • 资源限制:观察响应大小、下载时间、连接数和并发任务数,避免 SSRF 与资源消耗问题叠加。

如果应用支持自定义请求头,还要检查是否会把用户可控的认证头、内部令牌或代理凭据转发给目标。服务端请求功能原则上不应直接继承当前用户的 Cookie、Authorization 或内部身份凭据。

六、修复方案的工程化落地

生产修复最好分为应用层、网络层和监控层三部分。

  • 应用层:使用成熟 URL 解析器;限制协议、主机、端口、响应大小和超时时间;关闭或严格校验重定向;不把内部错误和响应内容无条件返回给用户。
  • 网络层:让执行抓取任务的容器或进程使用独立网络身份;默认禁止访问云平台管理地址、回环地址、私有网段和不必要的内部端口;通过出口代理实施域名和地址策略。
  • 监控层:记录拒绝原因、解析地址、重定向链和异常访问频率;对短时间内大量不同目标、非标准端口和频繁失败的请求设置告警。

修复后应补充回归测试,而不是只验证一个被拒绝的地址。测试集合至少应包括:合法 HTTPS 地址、未授权域名、回环地址、私有地址、IPv6 地址、带端口地址、带用户信息的 URL、解析到多个地址的域名、重定向到禁止目标的地址,以及超大响应和超时场景。

七、常见错误做法

  • 只判断字符串中是否包含 127.0.0.1,忽略 IPv6 回环地址和其他等价表示。
  • 只校验初始 URL,不校验重定向后的地址。
  • 使用“以 example.com 结尾”判断可信域名,导致相似后缀或控制字符引发误判。
  • 在应用层解析一次 DNS,底层 HTTP 客户端再次独立解析,造成校验结果与实际连接目标不一致。
  • 为了方便调试,把完整响应正文、请求头和内部异常直接返回给前端。
  • 只在代码中增加黑名单,没有在容器、防火墙或代理出口层设置第二道限制。

八、总结

分析 SSRF 的核心不是寻找某个“神奇绕过写法”,而是还原从输入、URL 解析、DNS 解析、连接建立到重定向和响应回传的完整链路。有效修复也不是增加一条字符串规则,而是建立明确的目标白名单、稳定的地址校验、可控的重定向策略、最小化的网络权限和可审计的日志。

对于代码审计人员,建议把 SSRF 检查固化为数据流规则和回归测试;对于开发人员,建议将远程抓取能力封装成统一组件,避免每个业务自行实现 URL 校验;对于运维和安全团队,则应通过网络出口策略限制应用即使出现逻辑缺陷时的实际影响范围。这样才能把一次漏洞修复,转化为长期可维护的安全控制。

服务器与云环境安全示意图
服务器、云资产与权限边界梳理示意

0篇意见

推荐意见

没有意见。

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