
现代 SSRF 漏洞利用与防御DNS 重绑定与内网协议伪协议阻断一、SSRF 的演进云原生时代的内网穿透利刃SSRFServer-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 请求”这种传统防御逻辑的经典攻击手法。其本质是一种典型的TOCTOUTime-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), TTL0 Server-Server: 校验 IP: 1.2.3.4 是公网 IP (放行通过) Note over Server: 阶段 2: 实际请求 (Use) Server-AttackerDNS: 再次发起 DNS 解析 (因为 TTL0 已过期) AttackerDNS--Server: 返回内网目标 IP (127.0.0.1 / 169.254.169.254) Server-InternalDB: 向内网地址发起 HTTP 请求并获取敏感数据 Server--Attacker: 将响应数据回传给攻击者攻击关键要素控制权威 DNS 服务器攻击者自建 DNS 解析服务将域名的 TTLTime To Live强制设置为0动态解析策略DNS 服务器在第 1 次查询时返回合法公网 IP通过业务安全校验在极短时间后的第 2 次查询底层 HTTP 客户端发起连接时立即返回内网目标 IP如169.254.169.254时钟竞争成功业务服务端使用的标准 HTTP 库重新触发了 DNS 查询导致最终的 TCPconnect()握手直接打在内网私有地址上。三、内网高危协议与伪协议利用矩阵当服务端使用的网络客户端库支持复杂协议解析如 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, familysocket.AF_UNSPEC, typesocket.SOCK_STREAM) if not addr_info: raise ConnectionError(fDNS 解析失败: {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, timeoutself.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, timeouttimeout, retriesFalse) return response.data五、综合治理与生产加固规范禁用 HTTP 自动重定向Redirect Handling必须关闭底层客户端的跟随重定向功能max_redirects0。若业务必须跳转每次 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及云元数据 IP169.254.169.254/32。