跳转到帖子

服务端请求伪造(SSRF)并不是简单的“把 URL 交给 HTTP 客户端”问题。真正难审计的地方在于:请求入口可能隐藏在图片抓取、Webhook、文档解析、URL 预览、云资源探测等业务中;而防护也不能只靠拦截几个内网 IP。本文以代码审计和安全验证为主线,整理一套适合企业项目的 SSRF 排查、复现与加固方法。

一、问题背景与典型场景

当应用允许用户提交一个 URL,并由服务端代为访问时,服务端就成为了请求发起者。如果访问目标、协议、跳转策略或网络出口缺乏限制,攻击者可能诱导应用访问本不应暴露的管理接口、内部服务或云环境元数据地址。

常见功能包括:

  • 远程图片下载、头像抓取和图片转存;
  • Webhook、回调地址或第三方通知配置;
  • URL 预览、网页截图、网页转 PDF;
  • RSS、Markdown、Office 文档中的远程资源解析;
  • 在线导入、镜像同步和健康检查功能。

这里需要强调,SSRF 的风险不一定表现为“读取敏感内容”。在实际事件中,更常见的是利用服务端网络位置访问内部管理面板、调用缺少认证的内部接口,或者借助出网能力进行内网资产探测。因此,审计时应同时关注数据读取、内部访问和网络出口三个维度。

二、代码审计中的数据流追踪

审计不要从关键词搜索结束,而要建立完整的数据流:外部输入从哪里进入,经过了哪些规范化和校验,最终传给了哪个网络请求组件,以及请求发生时使用了什么身份和网络权限。

第一步是寻找请求发起点。常见危险调用包括 Java 中的 URLConnection、HttpClient、OkHttp,Python 中的 requests、urllib,Node.js 中的 axios、fetch,以及各类图片、文档和爬虫库的封装接口。

String target = request.getParameter("url");
URL url = new URL(target);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(5000);
InputStream input = conn.getInputStream();

第二步是向上追踪输入来源。以下来源都应视为不可信输入:

  • HTTP 参数、JSON 字段、请求头和 Cookie;
  • 数据库中由用户或租户配置的回调地址;
  • 消息队列、任务系统和导入文件中的 URL;
  • 由模板、Markdown 或文档解析得到的外部资源地址。

第三步是向下确认网络行为。需要记录是否允许 HTTP、HTTPS 之外的协议,是否自动跟随重定向,是否解析响应体,是否支持代理,以及请求是否携带当前用户身份、服务账号凭据或内部请求头。

三、几个容易被忽略的绕过点

1. 只校验字符串中的域名

使用字符串包含判断,例如判断 URL 中是否包含某个允许域名,容易受到用户信息、端口、路径和解析差异影响。安全判断应基于解析后的 scheme、host、port 和最终连接地址,而不是原始字符串。

2. 只拦截少量内网地址

仅拦截常见的 127.0.0.1、10.0.0.0/8 等网段是不够的。还需要考虑 IPv6 回环地址、链路本地地址、环回地址、保留地址、容器网段、云平台内部网段,以及 DNS 解析后得到的地址。

3. 忽略 DNS 解析时序

如果校验阶段解析了一次域名,真正建立连接时又由底层库重新解析,校验结果和连接目标可能不一致。这个问题通常被称为 DNS rebinding 或解析时序问题。生产实现应尽量复用经过校验的解析结果,或者在网络出口层再次执行目标限制。

4. 忽略重定向

即使第一跳目标属于允许域名,也可能通过 30x 跳转到未授权地址。安全策略应明确是否允许重定向;如果确实需要,应对每一跳重新执行协议、域名、端口和解析地址校验,并限制最大跳转次数。

5. 忽略非 HTTP 协议

部分客户端支持 file、ftp、jar、gopher 等协议。业务通常只需要 HTTP 或 HTTPS 时,应在客户端配置层明确禁用其他协议,不要依赖业务层的字符串判断。

四、在隔离环境中的安全验证方法

验证 SSRF 不需要访问真实内网资产,也不应把测试指向未经授权的第三方系统。可以在本地或测试网络中准备两个服务:一个作为被测应用,另一个作为可观察的测试回显服务。回显服务只记录请求方法、路径、时间和测试标识,不提供敏感数据。

验证重点包括:

  • 应用是否真正发起了服务端请求,而不是仅在前端处理;
  • 请求是否携带了不应转发的认证头和内部头部;
  • HTTP 与 HTTPS 的协议限制是否生效;
  • 重定向到禁止目标时是否被阻断;
  • 连接超时、读取超时和响应体大小限制是否生效;
  • 不同 DNS 解析结果下,最终连接地址是否仍受策略控制。

可以用本地回显服务进行基础验证。例如在授权的测试环境中启动一个仅监听本机的服务:

python3 -m http.server 18080 --bind 127.0.0.1

然后通过测试应用的正常业务流程提交一个本地测试地址,并在应用日志或回显服务日志中确认请求是否发生。测试结束后应清理临时服务,避免把验证接口暴露到共享网络。

五、推荐的防护设计

1. 优先使用允许列表

如果业务只需要访问固定的第三方服务,最可靠的方式是配置允许的域名、端口和协议,而不是维护不断变化的黑名单。允许列表应按业务场景隔离,例如图片抓取服务和 Webhook 服务不要共用同一套宽泛规则。

allowedSchemes = ["https"]
allowedHosts = ["static.example-cdn.com", "api.partner.example"]
allowedPorts = [443]

域名匹配时应区分精确匹配和子域名匹配。允许 example.com 不应自动意味着允许 example.com.evil.test;允许子域名时也应明确是否允许任意层级,而不是简单使用字符串后缀判断。

2. 在解析后校验 IP 地址

域名校验完成后,还应解析目标主机并检查所有解析结果是否属于禁止范围。判断时覆盖 IPv4 和 IPv6,并拒绝无法稳定解析的目标。不要只检查第一个解析结果,也不要把解析失败当作允许。

for address in resolveAll(host):
    if isLoopback(address) or isPrivate(address):
        reject()
    if isLinkLocal(address) or isReserved(address):
        reject()

这段伪代码表达的是策略,不应直接当作完整生产实现。不同语言和运行环境对 IPv4 映射 IPv6、特殊地址范围以及 DNS 缓存的处理方式不同,最好使用经过维护的网络地址库,并补充单元测试。

3. 控制重定向和连接目标

默认关闭自动重定向通常更安全。如果业务必须支持重定向,应限制跳转次数,并在每一次跳转前重新解析和校验目标。更稳妥的做法是让应用使用受控的出站代理,由代理统一执行域名、IP、端口、协议和审计策略。

4. 设计独立的出站网络权限

应用层校验存在实现遗漏的可能,因此网络层控制不能缺席。建议将需要访问外部资源的服务部署在独立网络区域,默认拒绝访问云元数据地址、内部管理网段和容器控制平面,仅放行必要的外部目的地。

如果企业使用云平台,还应同时检查实例角色、任务角色和工作负载身份权限,避免网络层出现缺口时,服务端请求进一步暴露高权限凭据。应用不应把云身份凭据放在可被业务请求读取的位置。

5. 限制请求本身的资源消耗

SSRF 经常与资源消耗问题一起出现。建议设置连接超时、读取超时、最大响应体大小、最大响应头数量和最大重定向次数,并限制并发任务数。对于图片和文档下载,还要在解析前校验 Content-Type、文件大小和压缩展开后的大小。

六、日志、监控与事件复盘

日志应记录足够的审计信息,但不要直接记录完整的敏感响应内容。推荐记录请求来源、业务功能、目标域名、解析后的地址、目标端口、最终跳转链、阻断原因、响应状态和耗时。

以下行为值得建立告警:

  • 同一用户短时间内请求大量不同域名或端口;
  • 访问被禁止的私网、环回、链路本地或保留地址;
  • 频繁出现 DNS 解析失败、连接超时和多次重定向;
  • 请求目标与业务允许列表长期不一致;
  • 服务端请求携带了用户会话、内部认证头或云身份相关头部。

发生事件后,复盘不应只修改一个 URL 校验函数。应同时检查同类功能、公共网络请求封装、出站防火墙规则、云权限、日志留存和测试用例,避免漏洞从“图片抓取”迁移到“Webhook”或“文档预览”等其他入口。

七、代码审计与修复的落地清单

  • 枚举所有服务端发起网络请求的代码和第三方库;
  • 标记所有来自用户、租户配置、文件和消息队列的 URL 数据流;
  • 确认协议、域名、端口、解析地址和重定向策略是否逐层校验;
  • 检查是否存在请求头透传、凭据透传和代理配置继承;
  • 为禁止地址、IPv6、异常端口、重定向和解析失败补充测试;
  • 在网络层实施默认拒绝的出站访问控制;
  • 对高风险服务建立请求审计、异常告警和定期复核机制。

总结

SSRF 防护的核心不是增加几个黑名单规则,而是把“目标解析、连接建立、重定向、请求凭据和网络出口”作为一个整体来治理。代码审计阶段要追踪完整数据流,验证阶段要使用隔离环境和可观察的测试服务,修复阶段则应采用允许列表、解析后校验、严格超时、独立出站代理和网络分区等多层措施。

对于安全团队而言,最有价值的结果不是证明某个接口能够访问某个地址,而是建立一套可复用的识别规则、测试用例和出站访问基线。这样即使业务不断增加新的远程资源功能,也能在设计评审和代码提交阶段尽早发现同类问题。

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

0篇意见

推荐意见

没有意见。

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