跳转到帖子

SSRF(Server-Side Request Forgery,服务器端请求伪造)并不是一个只靠“禁止访问内网 IP”就能彻底解决的问题。实际审计中,漏洞往往出现在图片抓取、Webhook 回调、文档转换、远程资源导入、健康检查等功能里。开发者通常只关注了参数是否为合法 URL,却忽略了 URL 解析差异、DNS 解析变化、重定向链以及非 HTTP 协议等边界情况。

本文以代码审计和修复验证为主线,整理一套适用于 Web 应用的 SSRF 分析方法。示例均以本地测试服务或受控地址为前提,不涉及访问真实内网资产、云平台元数据或第三方系统。

一、典型业务场景与风险边界

常见的 SSRF 入口包括以下几类:

  • 根据用户提交的 URL 下载头像、附件或远程图片。
  • 服务端生成 PDF、截图或预览页面时访问指定地址。
  • Webhook、回调地址、消息推送等功能由用户配置目标 URL。
  • 导入 RSS、OpenAPI 文档或远程配置文件。
  • 后台健康检查功能允许管理员输入待检测地址。

这类功能本身并不一定存在漏洞,关键在于服务端是否会按照用户可控的地址发起网络请求,以及请求是否能够到达不应暴露的网络区域。风险通常包括内部管理面板暴露、服务端口探测、云环境敏感接口访问、内网服务未授权调用以及敏感响应回显。

审计时应区分“服务端可以发起请求”和“攻击者可以利用该请求访问敏感资源”两个层次。前者是行为特征,后者才是需要重点确认的安全影响。

二、代码审计时优先关注的危险模式

以 Python 为例,下面的代码虽然简短,但将用户输入直接交给网络库,风险边界完全由输入决定:

import requests

def fetch_remote(url):
    response = requests.get(url, timeout=10)
    return response.content

审计时不要只搜索 requests.get。还应关注以下调用链:

  • HTTP 客户端:requests、urllib、httpx、Java 的 URL、HttpClient、Go 的 http.Get 等。
  • 间接访问:图片处理器、HTML 转 PDF、截图组件、文档解析器和第三方 SDK。
  • 协议处理:file、ftp、gopher 等非预期协议是否被底层库支持。
  • 重定向逻辑:请求库是否默认跟随 30x,重定向后的地址是否重新校验。
  • 响应处理:是否将响应内容原样返回、写入本地文件,或交给后续解析器。

建议从路由入口开始,沿着“参数接收—URL 解析—DNS 解析—建立连接—跟随重定向—处理响应”的链路逐段确认,而不是只在某个危险函数附近做字符串匹配。

三、URL 校验中最容易出现的误区

1. 只判断字符串前缀

类似 url.startswith(' 的校验并不可靠。合法的 URL 语法允许用户名、端口、路径和编码字符参与解析,单纯的前缀判断无法准确表示目标主机。

2. 只检查一次 DNS 结果

如果应用先解析域名判断是否为公网地址,之后网络库又重新解析一次,就可能出现判断结果与实际连接目标不一致的情况。DNS 记录变化、解析器差异和缓存策略都可能造成这种问题。

3. 只禁止几个常见内网地址

需要同时考虑 IPv4、IPv6、回环地址、链路本地地址、私有地址、保留地址以及 IPv4 映射的 IPv6 地址。仅过滤 127.0.0.1、10.0.0.0/8 等少数网段,容易留下遗漏。

4. 校验原始地址,却信任重定向

即使初始地址是允许的公网域名,目标服务器也可能返回一个指向受限地址的重定向。安全策略应在每一次跳转前重新解析并校验目标,并限制最大跳转次数。

四、一个更稳妥的校验思路

生产环境不建议自行拼装复杂的 URL 黑名单。更可靠的方式是采用“协议白名单、主机白名单、解析后地址校验、连接过程限制”组合策略。

下面是一个用于说明思路的 Python 示例。它不负责下载内容,只展示如何在请求前对目标进行约束。实际项目中还需要结合网络出口策略和统一 HTTP 客户端实现。

from ipaddress import ip_address, ip_network
from urllib.parse import urlparse
import socket

ALLOWED_SCHEMES = {'https'}
ALLOWED_HOSTS = {'static.example.test'}
BLOCKED_NETS = [
    ip_network('10.0.0.0/8'),
    ip_network('172.16.0.0/12'),
    ip_network('192.168.0.0/16'),
    ip_network('127.0.0.0/8'),
    ip_network('169.254.0.0/16'),
    ip_network('::1/128'),
    ip_network('fc00::/7'),
    ip_network('fe80::/10'),
]

def is_blocked_ip(value):
    ip = ip_address(value)
    return any(ip in network for network in BLOCKED_NETS) or ip.is_private or ip.is_loopback or ip.is_link_local

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

    if parsed.scheme not in ALLOWED_SCHEMES:
        raise ValueError('unsupported scheme')
    if parsed.username or parsed.password:
        raise ValueError('userinfo is not allowed')
    if not parsed.hostname or parsed.port not in (None, 443):
        raise ValueError('invalid host or port')
    if parsed.hostname.lower() not in ALLOWED_HOSTS:
        raise ValueError('host is not allowlisted')

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

    return parsed

这个示例仍然不是完整的通用组件,原因在于 DNS 校验和实际建连之间可能存在时间窗口。因此更推荐以下方案:

  • 优先使用固定域名白名单,而不是允许任意公网域名。
  • 对允许访问的域名配置固定解析结果,或通过受控代理统一转发。
  • 禁止用户指定端口,必要时仅允许 443 或明确的业务端口。
  • 禁用自动重定向,或者对每次重定向重新执行完整校验。
  • 设置连接超时、读取超时、响应大小上限和最大响应头长度。
  • 将发起请求的服务放置在隔离网络中,出口防火墙默认拒绝访问管理网段。

五、修复验证的安全流程

验证 SSRF 修复时,应使用自己控制的测试环境,不要直接探测生产内网或第三方地址。可以准备一个简单的本地 HTTP 服务,用于记录请求方法、路径、请求头和来源地址:

python3 -m http.server 18080 --bind 127.0.0.1

然后在测试应用中使用以下几类受控输入验证策略是否生效:

  • 合法的白名单 HTTPS 域名,确认正常业务不受影响。
  • HTTP、FTP 或其他非白名单协议,确认被拒绝。
  • 带用户名密码、非标准端口或异常编码的 URL,确认解析结果符合预期。
  • 解析到回环、私有、链路本地和 IPv6 保留地址的测试域名,确认被拒绝。
  • 返回重定向的测试服务,确认跳转目标会重新校验。
  • 超大响应、慢响应和连接失败场景,确认不会造成资源耗尽。

验证重点不是“某个 Payload 能否成功访问”,而是确认所有不符合业务策略的目标都会在请求发出前被拦截,并且错误信息不会向外暴露内部解析结果、网络拓扑或服务版本。

六、日志与监控同样重要

SSRF 防护不能只依赖代码。建议对服务端主动请求建立结构化日志,但避免记录完整的敏感查询参数。日志至少可以包含请求功能、目标域名、解析地址类别、是否命中策略、失败原因和耗时。

在网络层,可以监控应用服务器是否突然访问管理网段、云平台保留地址或大量非常规端口。对于图片抓取、Webhook 等高频功能,还应设置单用户和单租户的请求速率限制,防止攻击者利用合法功能进行内部端口探测或资源消耗。

七、常见修复失败案例

  • 在控制器中做了校验,但另一个异步任务入口没有复用同一套策略。
  • 只校验页面展示的 URL,后台下载器仍然直接使用原始参数。
  • 过滤了 IPv4 私网段,却遗漏 IPv6 和 IPv4 映射地址。
  • 限制了初始 URL,却让 HTTP 客户端自动跟随重定向。
  • 使用黑名单维护所有危险地址,随着网络环境变化逐渐失效。
  • 应用层校验通过后,容器或主机网络仍可访问不必要的管理服务。

修复时应将策略封装成统一组件,禁止业务代码自行调用底层网络库。对于确实需要访问任意公网资源的场景,可以考虑部署独立的出站代理,在代理层执行域名解析、地址分类、协议限制和审计记录。

总结

SSRF 的核心问题不是某个字符串绕过技巧,而是“用户输入是否能够影响服务端的网络边界”。高质量审计需要同时观察代码调用链、URL 解析行为、DNS 与连接过程、重定向处理、响应使用方式以及底层网络隔离。

实践中,最稳妥的防护组合通常是:默认拒绝、明确的协议和域名白名单、解析后地址校验、禁止或严格控制重定向、限制端口与响应资源,并通过出口防火墙提供最后一道边界。完成修复后,还要使用受控测试环境覆盖 IPv4、IPv6、重定向、超时和异常解析等场景,避免只修复一个入口,却把同样的风险留在其他业务链路中。

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

0篇意见

推荐意见

没有意见。

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

SiteMap HACKHAT © 2026 All rights reserved.