跳转到帖子

SSRF(Server-Side Request Forgery,服务端请求伪造)并不只是“后端可以访问一个外部 URL”这么简单。真正难处理的地方在于:请求由服务端发起,网络边界、解析链路和权限上下文都发生了变化。攻击者提交的一个地址,可能被后端解析、重定向、代理、缓存,最终触达原本不应暴露的内网服务。

在实际代码审计中,SSRF 往往隐藏在图片抓取、Webhook 调试、URL 预览、远程文件导入、PDF 转换、代码仓库同步等功能里。本文以防守视角整理一套分析流程,重点覆盖漏洞定位、影响判断、修复设计和回归验证,示例均应在自有测试环境或经过授权的靶场中进行。

一、先明确问题边界

判断一个功能是否存在 SSRF,不能只看参数名是否叫 url。更可靠的做法是沿着数据流追踪三个环节:

  • 输入源:URL 是否来自用户请求、数据库字段、消息队列或第三方回调。
  • 请求 sink:是否进入 HTTP 客户端、图片解析器、代理组件、命令行下载器或其他网络访问接口。
  • 响应处理:服务端是否把响应内容返回给用户、写入文件、解析 XML 或 JSON,或者只记录状态码。

其中,服务端是否回显响应内容决定了漏洞是否属于“可读型 SSRF”,而只返回状态码、长度或错误信息的情况,也可能形成盲 SSRF。盲 SSRF 仍然可能影响内网服务可达性、触发回调或造成资源消耗。

审计时不要把“只能访问 HTTP”当成安全边界。风险不仅来自协议,还来自目标地址解析、重定向、DNS 变化、IPv4 与 IPv6 表示形式,以及下游组件的二次解析。

二、从代码中定位高风险调用点

以常见的 Java 服务为例,以下模式值得优先检查。代码本身不代表完整漏洞,是否可利用还要结合输入可控性、网络策略和响应处理逻辑判断。

String target = request.getParameter("target");
URL url = new URL(target);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(3000);
connection.setReadTimeout(5000);
return readBody(connection.getInputStream());

这类实现的问题不只是缺少域名校验,还包括协议限制、重定向处理、连接超时、响应大小和 DNS 解析时机等多个维度。即使后续增加了字符串黑名单,也很容易因为地址格式差异或组件行为差异出现绕过。

除了直接调用 HTTP 客户端,还应搜索以下代码特征:

  • Java 中的 URL、URI、HttpClient、URLConnection、RestTemplate、WebClient。
  • Python 中的 requests、urllib、httpx、aiohttp。
  • Go 中的 net/http、url.Parse、http.Get。
  • 命令执行中的 curl、wget、fetch、浏览器无头模式或办公文档转换工具。
  • 图片、PDF、Markdown、SVG、XML 解析器中的远程资源加载功能。

审计工具可以帮助快速建立调用图,但最终仍然需要人工确认:用户输入能否到达请求函数,是否经过了可信代理,是否存在重定向,以及请求进程运行在哪个网络区域。

三、影响分析不能只看“能否访问内网”

SSRF 的影响通常可以分为四个层次:

层次典型表现审计关注点
外部代理服务端代替用户访问任意公网地址凭证泄露、滥用带宽、访问受限站点
内网探测能够连接应用所在网段的其他服务网络分区、端口暴露、服务鉴权
敏感数据读取响应正文被返回或写入可下载文件响应回显、缓存、日志和错误信息
服务端请求伪造链请求结果被继续解析或触发其他动作解析器漏洞、命令执行、内部管理接口

影响判断应结合部署环境,而不是仅凭理论推测。建议记录请求进程的网络权限、容器网络模式、出口代理、DNS 配置、云平台身份权限以及内部服务的认证方式。对于云环境,重点是确保业务进程不具备不必要的实例身份权限,同时不要在文章、日志或测试记录中复制任何真实凭证。

四、构建安全的验证环境

验证 SSRF 时,优先使用本地或隔离网络中的两个测试服务:一个作为业务应用,另一个作为可观察的接收端。接收端只返回固定字符串并记录请求时间、路径和 User-Agent,不提供敏感数据,也不连接真实内网。

例如,可以在本机启动一个只监听回环地址的健康检查服务,然后让业务应用在测试配置下访问它:

python3 -m http.server 18080 --bind 127.0.0.1
curl --max-time 3  Mock 服务生成固定响应。测试记录中只保留状态码、耗时、响应长度等非敏感指标,避免把真实内部地址、令牌或生产日志带入缺陷报告。

验证目标主要包括:

  • 是否只允许预期的协议,例如仅允许 HTTPS。
  • 域名解析后的所有地址是否都符合网络策略。
  • 是否禁用或严格限制自动重定向。
  • 是否限制连接超时、读取超时、响应体大小和最大跳转次数。
  • 异常信息是否泄露目标地址、内部主机名或网络拓扑。

五、为什么常见黑名单方案不可靠

以下写法经常出现在临时修复中,但不适合作为长期方案:

if (target.contains("127.0.0.1") || target.contains("localhost")) {
    throw new IllegalArgumentException("blocked");
}

原因包括:字符串可能经过 URL 编码;域名解析结果可能与输入字符串不同;同一地址可能存在 IPv4、IPv6、整数形式或其他等价表示;域名还可能在校验与真正连接之间发生解析变化。更重要的是,黑名单只能覆盖已知目标,无法表达业务真正允许访问的范围。

如果业务只需要访问少数固定站点,应优先使用正向允许列表。例如,配置允许的主机名、端口和路径前缀,并在请求前进行规范化解析:

URI uri = URI.create(input);
String scheme = uri.getScheme();
String host = uri.getHost();
int port = uri.getPort() == -1 ? 443 : uri.getPort();

if (!"https".equalsIgnoreCase(scheme)) {
    throw new IllegalArgumentException("scheme not allowed");
}
if (host == null || !allowedHosts.contains(host.toLowerCase(Locale.ROOT))) {
    throw new IllegalArgumentException("host not allowed");
}
if (port != 443) {
    throw new IllegalArgumentException("port not allowed");
}

上面的示例只是第一层校验,不能直接视为完整防护。生产实现还应使用可靠的 DNS 解析流程,并对解析得到的每个地址执行网络范围校验。特别是 IPv4 私有地址、回环地址、链路本地地址、IPv6 本地地址以及保留地址,都应根据企业网络策略处理。

六、解析与连接必须保持一致

一个容易被忽略的问题是 DNS rebinding:校验阶段解析到一个允许的公网地址,真正建立连接时却解析到了不允许的地址。要降低这类风险,通常需要让“解析、校验、连接”尽可能在同一套受控逻辑中完成。

工程上可以采用以下思路:

  • 由受控解析器完成域名解析,记录所有 A 和 AAAA 结果。
  • 对每个解析地址执行 CIDR、回环、链路本地和保留地址判断。
  • 使用固定的解析结果建立连接,而不是再次交给系统默认解析。
  • 禁用自动重定向,或对每次跳转重新执行完整校验。
  • 在网络层使用出口代理或 egress 防火墙进行第二道限制。

如果使用第三方 HTTP 客户端,应确认其连接池、代理、DNS 缓存和重定向行为。不要只根据方法名或默认配置推断安全属性,最好通过单元测试和集成测试验证真实行为。

七、一个更完整的防护策略

SSRF 防护适合采用纵深防御,而不是依赖单个过滤函数。可以按以下层次设计:

  • 输入层:限制 URL 长度、协议、端口、字符集和用户可控字段。
  • 业务层:使用目标域名和路径的正向允许列表,避免“任意 URL”设计。
  • 解析层:规范化 URI,校验所有解析地址,并处理 IPv4、IPv6 和多地址返回。
  • 客户端层:关闭不必要的重定向、代理继承和危险协议,设置连接与读取超时。
  • 网络层:让业务进程使用最小化的出口策略,禁止访问不需要的管理网段。
  • 权限层:限制运行账户、容器能力和云身份权限,避免网络访问问题升级为凭证风险。
  • 监控层:记录目标域名、解析结果类别、状态码、耗时和阻断原因,但不要记录敏感响应正文。

对于远程文件抓取,还应增加内容类型、文件大小、压缩层数和解析器限制。下载成功不等于文件可信,后续的图片、XML、办公文档或压缩包解析可能引入另一条攻击链。

八、回归测试清单

修复完成后,建议把安全边界写成自动化测试,而不是只做一次人工验证:

  • 合法允许列表中的 HTTPS 地址可以正常访问。
  • HTTP、FTP、file 等非业务协议被拒绝。
  • 回环、私有网段、链路本地和保留地址被拒绝。
  • 域名解析返回多个地址时,只要存在不符合策略的地址就按策略阻断。
  • 重定向到不允许域名或不允许协议时被拒绝。
  • 连接超时、读取超时和超大响应能够受控结束。
  • 异常响应不会把内部路径、凭证、主机名和完整堆栈返回给用户。
  • 日志中能够区分输入校验失败、DNS 校验失败、网络超时和下游返回错误。

如果业务依赖第三方域名,还要考虑其 DNS 变更、CDN、代理和多区域部署带来的变化。允许列表需要有明确的变更流程,否则开发团队为了临时恢复功能,容易退回到“允许任意 URL”的不安全实现。

九、审计报告如何写得可执行

一份有价值的 SSRF 报告,不应只写“存在 SSRF,请修复”。建议至少包含以下信息:

  • 受影响接口、参数和调用链。
  • 输入如何到达网络请求函数,以及是否存在响应回显。
  • 在隔离环境中的最小复现步骤和预期结果。
  • 请求进程的网络权限、运行身份和部署位置。
  • 实际验证到的影响边界,明确区分已证实与推测风险。
  • 推荐的代码、网络和权限修复方案。
  • 回归测试用例与上线后的监控指标。

复现内容应尽量使用测试域名和本地服务,不要在报告中探测或记录真实生产内网。对于尚未验证的云环境影响,可以说明风险条件和需要由运维团队确认的配置项,而不是把理论结果写成既成事实。

总结

SSRF 的核心不是某个特殊字符串,而是“用户输入影响了服务端的网络访问目标”。有效的防护需要同时处理 URL 规范化、DNS 解析、重定向、协议限制、响应大小、网络出口和运行权限。

从审计角度,最值得坚持的习惯有三点:先沿数据流确认真实调用链;只在隔离环境验证最小影响;修复时优先采用正向允许列表和网络层限制,并用自动化测试固化边界。这样做不仅能解决一个接口的问题,也能为图片抓取、Webhook、远程导入等同类功能建立统一的安全基线。

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

0篇意见

推荐意见

没有意见。

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

SiteMap HACKHAT © 2026 All rights reserved.