跳转到帖子

服务端请求伪造(SSRF)是代码审计中经常遇到的一类问题。它通常不是由某个危险函数单独造成,而是由“用户可控地址”“服务端发起请求”“缺少目标限制”三个条件共同形成。实际项目里,SSRF 可能出现在图片抓取、Webhook、远程文件导入、URL 预览、文档转换和代理接口等功能中。

这类问题的难点在于:简单的黑名单很容易被绕过,单纯判断字符串是否包含内网地址也不可靠,而修复时还要兼顾 DNS 解析、重定向、IPv6、代理配置和网络分区。本文以代码审计和防守验证为主线,整理一套适合实际项目的分析方法。

一、先明确风险边界

审计 SSRF 时,不要一开始就把所有“服务器发起的 HTTP 请求”都判定为漏洞。更准确的判断方式是分析请求目标是否受到攻击者控制,以及服务端能访问到什么网络区域。

  • 高风险场景:接口允许用户提交任意 URL,服务端再将响应内容返回给用户,或将内容解析、保存、转发到其他系统。
  • 中风险场景:目标地址可控,但服务端有固定域名白名单;需要进一步确认白名单校验是否可靠。
  • 低风险场景:请求目标完全由服务端配置决定,用户只能选择有限的业务对象,且网络层也有访问控制。
  • 需要重点确认的影响:是否能够访问管理端口、内部 API、云平台实例元数据服务、容器管理接口或仅限本机访问的调试服务。

风险评级不能只看“是否能请求 127.0.0.1”。同一个缺陷在开发环境可能只访问到测试接口,在生产环境则可能触达敏感管理平面,因此必须结合部署拓扑和出网策略判断。

二、代码审计中的入口识别

审计时可以从数据流入手,而不是只搜索某个固定函数名。常见入口包括以下几类:

  • HTTP 参数、JSON 字段、表单字段中的 url、uri、callback、webhook、source。
  • 上传后触发远程下载、图片转码、网页截图、PDF 转换的业务流程。
  • 后台任务、消息队列消费者和定时任务中的远程资源同步逻辑。
  • 支持自定义代理、远程仓库、镜像地址的管理功能。
  • 将用户输入传入 HTTP 客户端、Socket、RPC、DNS 或命令执行组件的代码。

搜索调用点时,除了常见的 HTTP 客户端,还要关注封装层。例如 Java 项目中的 RestTemplate、WebClient、OkHttp,Python 项目中的 requests、urllib、httpx,Node.js 项目中的 axios、fetch,以及项目自定义的 HttpUtil、Downloader、UrlFetcher 等类。

三、建立完整的数据流

一个可复现、可修复的审计结论,至少要说明以下数据流:

  1. 用户输入从哪个参数进入系统。
  2. 输入是否经过 URL 解码、拼接、规范化或重定向。
  3. 最终由哪个组件发起请求。
  4. 请求使用了什么协议、端口、代理和超时配置。
  5. 响应内容是否返回给用户,或被写入文件、数据库、缓存。
  6. 服务端部署网络中有哪些可达目标。

例如,下面这段代码的问题不在于使用了某个特定库,而在于完整 URL 由用户输入直接决定:

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

即使加入超时,也只能降低资源消耗,不能改变请求目标的可控性。若业务只需要抓取固定站点的公开资源,应该让用户提交业务标识或相对路径,由服务端根据配置生成最终地址,而不是直接接收完整 URL。

四、为什么常见黑名单不可靠

实际审计中经常看到以下防护方式:

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

这种判断存在明显缺陷。地址可能使用不同的表示形式,域名还可能解析到受限制的地址,且初次解析和实际连接之间存在时间窗口。除此之外,IPv6、重定向、代理和 URL 解析差异也会造成过滤结果与实际连接目标不一致。

因此,不建议维护“危险地址字符串列表”。更稳妥的做法是采用正向允许策略,并在解析后的 IP 层面进行判断。防护逻辑至少应覆盖:

  • 只允许业务真正需要的协议,通常优先限制为 https,必要时再开放 http。
  • 明确限制端口,避免允许任意端口访问。
  • 解析所有 A、AAAA 记录,对每个结果进行内网、回环、链路本地、保留地址和组播地址检查。
  • 连接前重新确认解析结果,避免 DNS 解析与连接之间的地址变化。
  • 禁止或严格校验重定向,不能只校验第一次请求的目标。
  • 关闭不必要的代理继承,避免环境变量或全局代理改变实际请求路径。
  • 设置连接、读取、响应大小和总耗时限制。

五、推荐的安全请求流程

如果业务确实需要访问用户提供的 URL,可以将请求过程拆成几个明确阶段。下面是伪代码,重点是流程而不是某个语言库的具体实现:

URI uri = parseStrictly(input);
if (!isAllowedScheme(uri.getScheme())) {
    reject();
}
if (uri.getUserInfo() != null) {
    reject();
}
if (uri.getPort() != -1 && !isAllowedPort(uri.getPort())) {
    reject();
}

List<IPAddress> addresses = dnsResolveAll(uri.getHost());
for (IPAddress address : addresses) {
    if (isPrivateOrReserved(address)) {
        reject();
    }
}

HttpClient client = buildClientWithoutProxy();
HttpResponse response = client.request(uri, new RequestLimits(
        connectTimeoutMs = 3000,
        readTimeoutMs = 5000,
        maxBodyBytes = 2 * 1024 * 1024,
        maxRedirects = 0
));

if (response.isRedirect()) {
    rejectOrRevalidateLocation(response.location());
}

其中 isPrivateOrReserved 不应只判断 RFC1918 私有地址,还应覆盖回环地址、链路本地地址、未指定地址、组播地址、文档保留地址以及项目环境中明确不应访问的网段。对于 IPv6,要使用标准地址解析库判断,而不要通过字符串前缀匹配。

六、DNS 与重定向是修复中的重点

1. DNS 解析问题

域名白名单并不等于安全。某个域名在校验时解析到公开地址,实际连接时可能解析到不同地址;或者域名本身由用户控制,能够通过 DNS 配置指向内部网络。修复时应将“允许的主机名”和“解析后的 IP 校验”结合起来,并尽量在受控网络环境中使用固定解析或出站代理。

对于高安全等级场景,可以让应用不直接访问任意外部地址,而是统一调用一个出站代理。代理负责 DNS 解析、地址分类、审计记录和出站策略,应用只提交经过校验的目标。

2. HTTP 重定向问题

即使初始 URL 通过校验,服务器也可能返回 Location,将客户端引导到未授权地址。如果客户端自动跟随重定向,第一次校验就失去意义。

常见的处理方式有两种:

  • 业务不需要重定向时,直接关闭自动跟随,并将重定向视为失败。
  • 业务必须支持重定向时,对每一次新的目标重新执行协议、主机、端口和 IP 校验,同时限制重定向次数。

七、在本地环境中进行安全验证

漏洞验证应优先使用自建测试服务,不要对第三方系统、生产内网或云平台管理接口进行探测。可以准备一个简单的本地测试服务,记录收到的请求,并分别验证以下行为:

  • 正常公开域名是否可以按业务预期访问。
  • 回环地址、私有地址和 IPv6 本地地址是否被拒绝。
  • 域名解析到受限地址时是否被拒绝。
  • 返回重定向到受限地址时,客户端是否停止或重新校验。
  • 超大响应、慢连接和异常状态码是否受到限制。
  • URL 中包含用户名密码、非标准端口或不支持协议时是否被拒绝。

在测试客户端层面,可以通过单元测试固定解析结果,避免依赖真实 DNS。示例测试命令仅用于验证本地服务的 HTTP 行为:

curl --max-time 5 --connect-timeout 3 \
  -H 'Accept: application/json' \
  ' DNS 请求建立告警。
  • 将图片抓取、文档转换等高风险任务放到隔离网络和低权限运行环境。
  • 网络层策略的价值在于降低漏洞影响范围,但它不能替代代码中的输入校验。因为应用可能通过代理、解析器、其他协议或内部服务间接访问受限资源。

    九、代码审计报告应如何落地

    一份有价值的报告不应只写“存在 SSRF”,建议至少包含以下内容:

    项目说明
    入口接口、参数、权限要求及触发条件
    数据流从输入到 URL 解析、DNS、连接和响应处理的过程
    影响基于实际网络拓扑说明可访问的资源范围
    根因缺少正向校验、重定向未校验、代理配置不当等
    修复建议代码、网络、权限和日志层面的具体措施
    验证结果修复前后测试用例和预期行为

    修复验证不能只检查“某个字符串被拦截”。更合理的是覆盖一组行为测试,包括不同地址表示、多个 DNS 结果、重定向、异常端口、超时和超大响应。这样可以避免开发人员修复一个样例,却遗漏同一数据流中的其他路径。

    十、常见误区

    • 误区一:只禁止 localhost。 应检查解析后的地址类别,而不是只匹配一个主机名。
    • 误区二:只校验 URL 字符串。 字符串看起来安全,不代表最终连接地址安全。
    • 误区三:默认跟随重定向。 每次跳转都可能改变实际访问目标。
    • 误区四:只依赖云防火墙。 网络策略可能因环境变化而失效,应用仍需做最小权限控制。
    • 误区五:忽略非 HTTP 组件。 图片处理器、PDF 转换器、DNS 客户端和代理组件也可能形成类似风险。
    • 误区六:没有限制响应大小和耗时。 即使目标地址安全,也可能造成资源耗尽或任务堆积。

    总结

    SSRF 的审计重点不是寻找一个“能否访问内网”的简单结论,而是还原完整的数据流,明确请求目标、解析过程、重定向行为和部署网络边界。修复时应采用正向允许策略,在解析后的 IP 层面校验地址,严格控制协议和端口,并对重定向、DNS 变化、代理继承、超时和响应大小进行统一处理。

    从长期治理角度看,最可靠的方案通常是“代码校验 + 出站网络控制 + 最小权限 + 可审计日志 + 自动化回归测试”。这套方法不仅适用于 SSRF,也适用于文件下载、Webhook、远程导入和服务间代理等多个业务场景。

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

    0篇意见

    推荐意见

    没有意见。

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

    SiteMap HACKHAT © 2026 All rights reserved.