理论基础
Fetch API
关于Fetch API有两个比较重要的点:
1.Fetch API作为Cache,Service Workers等API的基础,可以获取任何资源,包括需要认证的跨域资源。
2.fetch()返回的是一个Promise对象,一旦Response对象接收到了第一个字节的数据,Promise对象就开始resolve,并且已经可以访问Response对象,这时候Response对象仍然会有数据流入。
Performance API
浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。performance.getEntries方法以数组形式,返回这些请求的时间统计信息。
攻击过程
首先从TCP层看一下一个典型的HTTP请求,在三次握手之后,客户端发出一个包含请求的TCP包,通常只有几百字节,到达服务器之后,服务器生成一个response并发回给客户端。如果response的尺寸大于MSS(最大传输单元除去TCP+IP头,对于以太网来说是1460字节),服务器会将response拆成多个分组,这些分组会根据TCP慢启动算法来发送。
慢启动的算法如下(cwnd全称Congestion Window,拥塞窗口):
连接建好的开始先初始化cwnd = initcwnd,表明可以传initcwnd个MSS大小的数据。
每当收到一个ACK,cwnd++; 呈线性上升
每当过了一个RTT,cwnd = cwnd*2; 呈指数让升
还有一个ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法”(这里不涉及这个算法)
Linux 3.0之后把cwnd初始化成了10个MSS。
通过fetch(),我们可以知道第一次TCP数据返回的时间,如果我们再知道数据完全返回的时间,我们就能知道数据是一次TCP返回的,还是多次返回的。
这时候就要另一个Performance API来配合,通过资源的responseEnd来得到资源完全下载需要的时间。
我们把发起请求的时间记为T0,第一次TCP返回时间记为T1,完全接收时间记为T2。如果是一次返回的,那么T2-T1将是一个很小的值,通常在1ms内。如果是两次及以上的,时间会明显增加很多。
这时候看起来还是没什么卵用。然而,下一步就是利用这一点得到response的确切大小(这个大小是经过gzip,以及加密过的)。
首先来看看一次返回的情况,很多时候一个请求参数在请求的结果里会有返回,然后就可以利用这一点。我们把response分为两部分,一部分是我们想得到的实际大小,一部分是攻击者控制的请求参数,暂且称之为反射参数吧。通过重复调整反射参数,我们可以得到第一次TCP返回的最大可能尺寸(对每个服务器来说一般是个固定值)。之后,只要减去HTTP和SSL/TLS的header的尺寸就可以了,而这两个都是可以预计的。
举例来说,当把反射参数调到708字节长时,正好可以一次TCP请求返回,而709就需要两次了,拿10*MSS(14600字节)- 528字节的http头 – 26字节的SSL/TLS头 – 708,得到response的实际大小为13337字节。论文里介绍了两种对该算法的优化方法,这里暂时跳过。
除了反射参数这种情况,还可以对目标网站发布大量不同尺寸的内容,通过调整正常的query参数来查看返回内容的大小达到同样的目的。
对于多次TCP返回的情况,会受到慢启动算法的影响,攻击者会向一个已知尺寸的资源发起一个请求,然后再向目标资源发起请求,服务器会将拥塞窗口提高。通过调整第一个请求资源的尺寸多次分析也可以得到结果。
接下来,只要配合BREACH/CRIME等攻击,就可以轻松获取E-mail地址,社保号等信息了,而不像BREACH攻击一样还要借助中间人攻击去得到资源的大小。
另外,在HTTP2下,利用一些新特性,这种攻击的情况还会更加糟糕。
推荐阅读: