这是今年BlackHat上的一个议题:When TLS Hacks You,作者是latacora的Joshua Maddux
议题提出了一个新的ssrf攻击思路,利用DNS重绑定技术和https的优化算法(TLS Session resumption)的缺陷,将受限的SSRF变为通用的SSRF。攻击方法与之前的SNI injection类似,但是这次利用的是TLS的一种特性,而不是在特定实现中的bug。
本文是对When TLS Hacks You
这一议题的一些简单总结和分析,如有错误或不当之处请大佬们多多包涵。
其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。
file:///etc/passwd
协议名称 | 简介 |
---|---|
Gopher协议 | 攻击内部应用的主力军 |
Dict协议 | 端口探测,版本信息收集 |
ftp协议 | 探测是否存在ftp |
http协议 | 探测是否存在ssrf |
file协议 | 读取本地文件 |
注:jdk1.7后java不再支持gopher
在请求资源前先访问DNS服务器判断是否为内网IP
针对上图防御手段的一种绕过
TTL是英语Time-To-Live的简称,意思为一条域名解析记录在DNS服务器中的存留时间。当各地的DNS服务器接受到解析请求时,就会向域名指定的NS服务器发出解析请求从而获得解析记录;在获得这个记录之后,记录会在DNS服务器中保存一段时间,这段时间内如果再接到这个域名的解析请求,DNS服务器将不再向NS服务器发出请求,而是直接返回刚才获得的记录;而这个记录在DNS服务器上保留的时间,就是TTL值。
它表示DNS记录在DNS服务器上缓存的时间,数值越小,修改记录各地生效的时间越快。
当我们发起域名解析请求的时候,第一次访问会返回一个ip地址A,但是当我们发起第二次域名解析请求的时候,却会返回一个不同于A的ip地址B。
攻击思路是基于这种SSRF防御思路的基础上的,检查逻辑是第一次DNS查询请求确定host是不是内网IP,第二次请求的时候存在一个小间隔,导致了解析的差异性。
如下图设置好域名的A记录及NS记录,指定DNS服务器为自己搭建的
在vps上运行代码启动DNS服务(嫖的大佬的代码实现)
# -*- coding:utf-8 -*-
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只要是外网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, # 这里设置DNS 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())
可以看到代码用twisted实现了dns服务,并且设置了TTL为0
效果如下
一个完整的 TLS 握手需要两次
优化方案 | 原理 |
---|---|
session id | 服务端记住会话状态,客户端发带session id的client hello,服务端返回之前存储的SSL会话 |
session ticket | 客户端记住会话状态,服务端记住用于加密返回给客户端的ticket的密钥 |
PSK | 客户端和服务器第一次建立会话时,会生成一个PSK(pre-shared key)。服务器会用ticket key去加密 PSK,作为Session Ticket返回 |
session id
的相关数据是存放在 server 端,session ticket
是存放在 client 端, 在这个攻击手法中 session id
只能存放 32 byte 的 payload,session ticket
则能放更多字节的 payload。
在curl的实现代码中只检查了域名、端口和协议。而没有检查IP,因此可以用DNS重绑定的手段。
将上述三者结合,利用DNS缓存和TLS协议将受限SSRF变为通用SSRF。
此种攻击手法需要三个条件,大部分受限的SSRF,带外通信的TLS session,本地端口上运行的应用。
作者给了一个Demo。假设受害者是一个使用django.core.cache的项目的开发者,并且使用了memcached。受害者在Chrome等易受影响的浏览器中浏览电子邮件。受害者有一定安全意识,不会下载邮件中的附件。
攻击者会制作一封钓鱼邮件
邮件中的图片标签指向攻击者准备好的网站
然后弹计算器
整个攻击流程如图所示
ssltest.jmaddux.com:11211
(攻击者准备的网站)发起请求ssltest.jmaddux.com
的NS记录指定的DNS服务器请求DNS解析,DNS服务由攻击者搭建的DNS Server提供session id
/session ticket
/psk
字段中)ssltest.jmaddux.com:11211
127.0.0.1
127.0.0.1:11211
作者在讲述未来工作时提出了要建设更好的测试基础设施
github项目:https://github.com/jmdx/TLS-poison
作者提出了三种防御手段
改变缓存的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: disabled by default
Java, Nodejs, Chrome, others: no option
WEB APP
其实从其他方面也能防御,比如杜绝DNS重绑定,第一次解析后直接使用解析返回的ip替换域名访问url
这个议题技术覆盖比较全面,攻击手法也很新颖,作者在介绍的过程中也提出了对安全与性能之间平衡的思考:https本来是防止中间人攻击提出的安全协议,却因为对此的优化算法使其变得易受攻击。安全开销与时间开销如何取舍?
无论是议题本身还是作者挖掘漏洞的方法,思考问题的全面性,都有很多值得我们学习的地方。
作者最后也提出了一些总结:
https://github.com/jmdx/TLS-poison
https://portswigger.net/daily-swig/when-tls-hacks-you-security-friend-becomes-a-foe
https://securityboulevard.com/2020/08/def-con-safe-mode-joshua-madduxs-when-tls-hacks-you/
https://defcon.org/html/defcon-safemode/dc-safemode-speakers.html#Maddux
https://xz.aliyun.com/t/7495
https://wiki.jikexueyuan.com/project/openresty/ssl/session_resumption.html
https://mp.weixin.qq.com/s/GT3Wlu_2-Ycf_nhWz_z9Vw
https://mp.weixin.qq.com/s/-NABL-xz1Allxr6SKGiYcQ