利用DNS缓存和TLS协议将受限SSRF变为通用SSRF
字数 2764 2025-08-18 11:35:42
利用DNS缓存和TLS协议将受限SSRF变为通用SSRF技术分析
前言
本文基于BlackHat 2020议题《When TLS Hacks You》的技术内容,详细分析如何结合DNS缓存和TLS会话恢复机制,将受限的SSRF漏洞转变为通用SSRF攻击的技术原理、实现方法和防御措施。
背景知识
SSRF概述
原理:服务端提供了从其他服务器获取数据的功能,但未对目标地址做严格过滤与限制,导致攻击者可传入任意地址让后端服务器发起请求。
危害:
- 内网/本地端口扫描,获取开放端口信息
- 主机信息收集,Web应用指纹识别
- 针对特定应用发送payload攻击(如Struts2)
- 攻击内网和本地应用程序及服务
- 穿越防火墙
- 利用file协议读取本地文件
常用协议:
| 协议名称 | 简介 |
|---|---|
| Gopher | 攻击内部应用的主力军 |
| Dict | 端口探测,版本信息收集 |
| ftp | 探测是否存在ftp服务 |
| http | 探测是否存在SSRF |
| file | 读取本地文件 |
注:JDK 1.7后不再支持Gopher协议
防御手段:
- 禁止跳转
- 过滤返回信息
- 禁用不需要的协议
- 设置URL白名单或限制内网IP
- 限制请求端口为HTTP常用端口
- 统一错误信息
- 请求资源前先访问DNS服务器判断是否为内网IP
DNS重绑定技术
针对"请求资源前先访问DNS服务器判断是否为内网IP"这一防御手段的绕过技术。
TTL(Time-To-Live):
- DNS记录在DNS服务器上的缓存时间
- 数值越小,修改记录各地生效时间越快
- 设置为0表示不缓存
DNS解析流程:
- 检查浏览器缓存
- 检查本机系统缓存(如HOSTS文件)
- 向本地域名解析服务系统发起请求
- 递归查询运营商DNS → 根域名服务器 → 顶级域名服务器 → NS域名服务器
- NS返回IP地址给本地服务器,本地服务器缓存解析结果(TTL)
- 解析结果返回用户,建立TCP通信
DNS重绑定:
- 第一次DNS查询返回IP地址A
- 第二次DNS查询返回不同于A的IP地址B
- 利用检查逻辑与请求间隔的差异实现绕过
技术实现
实验设置
-
DNS服务器配置:
- 设置域名的A记录及NS记录
- 指定DNS服务器为攻击者搭建的服务器
-
DNS服务代码:
from twisted.internet import reactor, defer
from twisted.names import client, dns, error, server
record={}
class DynamicResolver(object):
def _doDynamicResponse(self, query):
name = query.name.name
if name not in record or record[name]<1:
ip="106.56.229.29" # 外网IP
else:
ip="127.0.0.1"
if name not in record:
record[name]=0
record[name]+=1
print name+" ===> "+ip
answer = dns.RRHeader(
name=name,
type=dns.A,
cls=dns.IN,
ttl=0, # TTL设置为0
payload=dns.Record_A(address=b'%s'%ip,ttl=0)
)
answers = [answer]
authority = []
additional = []
return answers, authority, additional
def query(self, query, timeout=None):
return defer.succeed(self._doDynamicResponse(query))
def main():
factory = server.DNSServerFactory(
clients=[DynamicResolver(), client.Resolver(resolv='/etc/resolv.conf')]
)
protocol = dns.DNSDatagramProtocol(controller=factory)
reactor.listenUDP(53, protocol)
reactor.run()
if __name__ == '__main__':
raise SystemExit(main())
TLS会话恢复机制
完整TLS握手:
- Client发送ClientHello
- Server回复ServerHello
- Client回复最终确定的Key,Finished
- Server回复Finished
- 握手完毕,Client发送加密HTTP请求
- Server回复加密HTTP响应
花费2RTT(Round-Trip-Time)
优化方案:
-
Session ID:
- 服务端记住会话状态
- 客户端发送带session id的client hello
- 服务端返回之前存储的SSL会话
-
Session Ticket:
- 客户端记住会话状态
- 服务端记住用于加密返回给客户端的ticket的密钥
-
PSK(Pre-Shared Key):
- 首次建立会话时生成PSK
- 服务器用ticket key加密PSK作为Session Ticket返回
注:session id只能存放32字节payload,session ticket能放更多字节
HTTPS协议实现(CURL)
在curl实现中只检查域名、端口和协议,未检查IP,因此可利用DNS重绑定绕过。
攻击方法
攻击条件
- 受限的SSRF漏洞
- 带外通信的TLS session
- 本地端口上运行的应用
实际漏洞案例
- Youtrack - CVE-2019-12852
- Nextcloud - 分享功能造成的SSRF,使用TLS重绑定攻击本地memcached
攻击面
- OIDC discovery
- Webpush
- Webmention
- Apple Pay Web
- Wifi captive portals
- SSDP
- SVG conversion
- URL-based XXE
- Scraping
- Webhooks
- PDF renderers with images enabled
攻击流程(Demo: Phishing→CSRF→RCE)
- 攻击者制作钓鱼邮件,内容标签指向攻击者网站(如ssltest.jmaddux.com:11211)
- 受害者打开邮件,请求域名
- 客户端向攻击者DNS服务器请求解析
- DNS服务器返回正常TLS Server地址,TTL=0
- 客户端发送Client Hello
- 服务端返回Server Hello,在session id/ticket/psk字段设置payload
- 完成TLS握手
- 网站返回301跳转到ssltest.jmaddux.com:11211
- 由于TTL=0,客户端再次询问DNS
- DNS返回127.0.0.1
- 客户端使用带有payload的SSL会话缓存访问127.0.0.1:11211
- payload加载完成实现RCE
防御措施
技术层面
-
改变TLS缓存key值:
- 当前:(hostname, port)
- 建议:(hostname, port, ip_addr)或(hostname, port, addr_type(ip_addr))
-
禁用带外通信时的TLS session resumption:
- libcurl:
CURLOPT_SSL_SESSIONID_CACHE=false - Firefox:
security.ssl.disable_session_identifiers=true - Tor browser: 默认禁用
- Java, Nodejs, Chrome等: 无选项
- libcurl:
-
Web应用防御:
- 关注webhooks, apple pay等应用
- 对出站请求设置代理监控(如smokescreen工具)
- 阻止未经验证的内部TCP内容运行,特别是带换行符的内容
替代方案
- 杜绝DNS重绑定:第一次解析后直接使用解析返回的IP替换域名访问URL
总结与思考
-
技术启示:
- HTTPS本为防止中间人攻击的安全协议,却因优化算法成为攻击媒介
- 安全开销与时间开销的平衡问题
-
关键结论:
- TLS对SSRF攻击有重要利用价值
- 需要持续关注最新技术手段以打破常规
- 必须认真权衡TLS会话重用的利弊
-
研究方向:
- 建设更好的测试基础设施
- 开发Alternating DNS Server、Custom TLS等工具