现代 SSRF 漏洞利用与防御:DNS 重绑定与内网协议伪协议阻断
一、SSRF 的演进:云原生时代的内网穿透利刃
SSRF(Server-Side Request Forgery,服务端请求伪造)是 Web 安全领域最危险的漏洞类型之一。在单体架构时代,SSRF 主要用于探测内网开放端口或读取敏感接口;而在云原生与微服务架构普及的今天,SSRF 已经演变为攻击者直接窃取云厂商实例元数据(Instance Metadata)、接管 IAM 临时密钥、乃至利用内网未鉴权组件(Redis、Docker API、Kubernetes APIServer)实施 RCE的致命利刃。
许多研发团队认为只要对 URL 做了“私网 IP 过滤”就高枕无忧,但在真实的攻防场景中,简单的正则过滤和常规校验在DNS 重绑定(DNS Rebinding)与URL 解析歧义面前不堪一击。
二、DNS 重绑定攻击原理深度拆解
DNS 重绑定是绕过“先解析域名校验 IP、再发起 HTTP 请求”这种传统防御逻辑的经典攻击手法。其本质是一种典型的TOCTOU(Time-of-Check to Time-of-Use,检查时与使用时)时序竞争漏洞。
sequenceDiagram autonumber actor Attacker as 攻击者 participant Server as 业务受害服务端 participant AttackerDNS as 攻击者控制的 DNS 服务器 participant InternalDB as 内网敏感服务 (127.0.0.1 / 10.x) Attacker->>Server: 提交请求 URL: http://attacker-rebind.com/api Note over Server: 阶段 1: 安全检查 (Check) Server->>AttackerDNS: 解析 attacker-rebind.com AttackerDNS-->>Server: 返回合法公网 IP (1.2.3.4), TTL=0 Server->>Server: 校验 IP: 1.2.3.4 是公网 IP (放行通过) Note over Server: 阶段 2: 实际请求 (Use) Server->>AttackerDNS: 再次发起 DNS 解析 (因为 TTL=0 已过期) AttackerDNS-->>Server: 返回内网目标 IP (127.0.0.1 / 169.254.169.254) Server->>InternalDB: 向内网地址发起 HTTP 请求并获取敏感数据 Server-->>Attacker: 将响应数据回传给攻击者攻击关键要素
- 控制权威 DNS 服务器:攻击者自建 DNS 解析服务,将域名的 TTL(Time To Live)强制设置为
0; - 动态解析策略:DNS 服务器在第 1 次查询时返回合法公网 IP(通过业务安全校验),在极短时间后的第 2 次查询(底层 HTTP 客户端发起连接时)立即返回内网目标 IP(如
169.254.169.254); - 时钟竞争成功:业务服务端使用的标准 HTTP 库重新触发了 DNS 查询,导致最终的 TCP
connect()握手直接打在内网私有地址上。
三、内网高危协议与伪协议利用矩阵
当服务端使用的网络客户端库支持复杂协议解析(如 cURL、JavaURLConnection、PHPcurl)时,SSRF 的危害会被成倍放大:
| 协议 / 伪协议 | 典型利用场景 | 危害级别 |
|---|---|---|
http:///https:// | 访问 AWS/阿里云元数据服务http://169.254.169.254/latest/meta-data/窃取 AK/SK | 严重 |
gopher:// | 构造任意 TCP 原始数据流,攻击内网无认证 Redis、Memcached、FastCGI,写入 Webshell | 致命 (RCE) |
dict:// | 探测内网端口、泄露服务指纹及简单文本交互 | 高危 |
file:// | 绕过网络层直接读取系统底层文件(如file:///etc/shadow) | 严重 |
四、工程级 SSRF 防御体系:自定义传输层实现
防御 SSRF 的根本原则在于:彻底消除 TOCTOU 时钟差,强制在底层 TCP Socket 建立连接(Connect)的那一刻进行 IP 校验,并严格锁定协议白名单。
Python 生产级安全 HTTP 客户端实现
通过重写urllib3的连接池底层逻辑,确保无论 DNS 如何重绑定,实际建立 TCP 连接的 IP 必须通过私网地址拦截:
import ipaddress import socket import urllib3 from urllib3.connection import HTTPConnection, HTTPSConnection from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool from urllib3.poolmanager import PoolManager def is_private_or_reserved_ip(ip_str: str) -> bool: """严格判断是否为私网、回环、链路本地或保留 IP 地址""" try: ip = ipaddress.ip_address(ip_str) return ( ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast or str(ip) == "0.0.0.0" ) except ValueError: return True # 格式异常直接按高危拦截 class SafeHTTPConnection(HTTPConnection): def connect(self): # 1. 显式解析目标主机的所有 IP addr_info = socket.getaddrinfo(self.host, self.port, family=socket.AF_UNSPEC, type=socket.SOCK_STREAM) if not addr_info: raise ConnectionError(f"DNS 解析失败: {self.host}") # 2. 遍历解析出的目标真实 IP 进行校验 target_sockaddr = None for family, socktype, proto, canonname, sockaddr in addr_info: ip_str = sockaddr[0] if is_private_or_reserved_ip(ip_str): raise PermissionError(f"[SSRF-BLOCKED] 拦截对内网或保留 IP 的访问尝试: {ip_str}") target_sockaddr = sockaddr break # 3. 使用校验通过的固定 IP 直接建立底层 Socket 连接 self.sock = socket.create_connection(target_sockaddr, timeout=self.timeout) class SafePoolManager(PoolManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 强制仅允许 HTTP/HTTPS 协议,杜绝 gopher/file/dict 等伪协议 self.pool_classes_by_scheme = { "http": HTTPConnectionPool, "https": HTTPSConnectionPool, } # 使用安全客户端请求外部不可信 URL def safe_fetch_url(url: str, timeout: float = 5.0) -> bytes: http = SafePoolManager() # 禁用自动重定向,防止通过 HTTP 302 跨协议或重定向至内网地址 response = http.request("GET", url, timeout=timeout, retries=False) return response.data五、综合治理与生产加固规范
- 禁用 HTTP 自动重定向(Redirect Handling):
- 必须关闭底层客户端的跟随重定向功能(
max_redirects=0)。若业务必须跳转,每次 301/302 返回的LocationURL 必须重新进入安全校验管线。
- 必须关闭底层客户端的跟随重定向功能(
- 云原生环境元数据服务(IMDSv2)加固:
- 全面启用 AWS IMDSv2 或对应云厂商的 Session-oriented 元数据访问模式,强制要求必须先在请求头中携带 PUT Token 才能获取凭据,直接免疫基础的 GET 型 SSRF。
- 网络隔离与 Egress 访问控制:
- 在生产容器网络策略(NetworkPolicy)中,严禁业务对外发起的 Pod 直接访问内网网段(
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16)及云元数据 IP(169.254.169.254/32)。
- 在生产容器网络策略(NetworkPolicy)中,严禁业务对外发起的 Pod 直接访问内网网段(