HSTS是让浏览器强制使用HTTPS访问网站的一项安全策略。HSTS的设计初衷是缓解中间人攻击带来的风险。本文主要介绍HSTS及其他Web功能带来的一些隐私问题,比如如何利用它们来探测浏览器的用户历史纪录。

一、背景:什么是HSTS

HSTS的英文全称是HTTP Strict Transport Security,中文译作HTTP严格传输安全。2012年11月IETF发布RFC 6797,在这篇文档中正式定义了HSTS。HSTS的开启方式是在HTTP响应头中加入Strict-Transport-Security字段。如:Strict-Transport-Security: max-age=31536000 。这意味着在接下来的31536000秒内(1年),当浏览器需要访问同一个域名时,必须使用HTTPS,并且用户不可以忽略证书错误警告。使用HSTS避免了一系列的中间人攻击问题,比如HTTPS剥离攻击 [1]、HTTPS Cookie注入攻击 [2]等。

设置HTTP响应头的方法虽然可以规避大量的中间人攻击,但是用户的第一次访问仍然是不受HSTS保护的。于是诞生了浏览器预置HSTS列表。网站站长可以主动向Chrome团队提交自己的域名。批准后,各主流浏览器厂商(不只是Chrome)会在编译新版浏览器时将你的域名硬编码进内置HSTS列表中。

现在已经有越来越多的网站开启了HSTS,比如Google、百度、支付宝等。根据trustworthyinternet.org 发布的SSL Pulse报告显示,截至2017年5月,有11.8%的网站支持HSTS [3]。最新版的主流浏览器也都支持HSTS,比如Chrome、Edge、IE 11、Firefox、Opera、Safari等。

二、漏洞一:利用端口号和<img>标签探测历史纪录

上一节所述的都是HSTS好的一方面,下面来说HSTS导致的问题。第一个漏洞是我和Vlad Tsyrklevich在2014年独立发现的 [4][5]。简单来说,如果www.example.com开启了HSTS,如果用户没有访问过它,那么http://www.example.com:443/favicon.ico一定会访问失败。如果访问过,那么HSTS会使浏览器请求https://www.example.com:443/favicon.ico,这样就会成功(如果不存在favicon.ico这个图片的话,就任选一个这个域名下其他图片地址)。所以我们用<img src="http://www.example.com:443/favicon.ico" onerror="not_visited()" onload="visited()">,如果onerror被调用就说明没有访问过www.example.com,如果onload被调用就说明访问过。

这个方法有一定的限制,比如被测试的域名必须要使用HSTS,并且不能在HSTS预置列表中。而且只能判断一个域名是否访问过,而无法测试整个URL是否被访问过。

这个漏洞我报给了Chromium团队,报告和完整PoC可参见 [4]。我的建议是禁止http协议使用443端口。但是由于这样会给WebSocket造成兼容性问题,并且这个漏洞影响小,所以他们最终决定不修复这个漏洞。

网站可以把自己的域名提交到HSTS预置列表来规避这个漏洞。用户可以通过清空历史纪录避免这个漏洞,因为清空历史记录会同时清空动态设定的HSTS记录。

三、漏洞二:Sniffly — 利用HSTS和CSP探测历史纪录

这个漏洞是由雅虎的安全工程师Yan Zhu于2015年发现的。她在Toorcon 2015会议上讲述了这个漏洞(演讲视频参见[6],幻灯片参见[7]),并把这个漏洞命名为Sniffly。Freebuf之前也有一篇文章《Sniffly: 利用HSTS和CSP嗅探浏览器历史记录》[8],就是写这个漏洞的。

这个漏洞利用CSP(内容安全策略)来阻止https协议的图片,而同时允许http协议。这个CSP是这样设置的:Content-Security-Policy: img-src http://*。这样如果有一个http到https的重定向,那么这个CSP将在这个重定向发生之后,阻止https请求,并调用onerror handler。攻击者可以使用JavaScript来测从http请求发出到https被阻止之间的时间间隔,这个时间间隔就是重定向所需时间。如果这个时间很短(小于10毫秒),那么我们可以认为浏览器没有向服务器发送任何请求,也就是说这个重定向来源于HSTS或者是缓存的301重定向。这样我们就知道用户曾经访问过这个域名。

这个漏洞很快地在Chrome中修复了,漏洞编号是CVE-2016-1617。修复方法是:如果CSP中指定了http://*,则它同时允许http和https协议。这样就没法用这个方法屏蔽http到https的重定向。Yan Zhu给Chrome提交的漏洞报告和PoC可参见 [9]。

四、漏洞三:利用HSTS、CSP和端口号探测历史记录

这个漏洞是我在2016年,看完漏洞二的细节后想出来的绕过方法。首先我们看Google对漏洞二的修复代码 [10]:

补丁在WebKit/Source/core/frame/csp/CSPSource.cpp文件中的CSPSource::schemeMatches函数中加入了下面4行代码:

if (equalIgnoringCase(m_scheme, "http")) return equalIgnoringCase(url.protocol(), "http") || equalIgnoringCase(url.protocol(), "https"); if (equalIgnoringCase(m_scheme, "ws")) return equalIgnoringCase(url.protocol(), "ws") || equalIgnoringCase(url.protocol(), "wss"); 

这个代码的意思就是当CSP中的协议是http时,url的协议是http或https都能成功匹配。ws是WebSocket协议,同样CSP中指定的ws协议可以同时匹配ws和wss。

很显然这个修复只考虑了URL中的协议部分,所以我想到利用漏洞一中的技巧,我们在CSP中显式指定端口号,就绕过了修复。

比如,我们设置这个CSP策略:img-src http://example.com:80。漏洞二修复之后,这个CSP会允许http://example.com:80https://example.com:80,但是后一个URL并没有意义,因为https不用80端口,而真正的https://example.com依然被阻止,因为https的端口号不匹配”:80”。有了这个思路之后,剩下的利用方法就和漏洞二一样了,也是测http到https的重定向时间。

这个漏洞同时存在于Chrome、Firefox、WebKit。但Edge、IE不存在这个漏洞。Edge是在https请求返回之后才调用onerror,所以Edge中无法计算重定向时间。

给Chrome的报告和PoC在[11],给Mozilla的报告在[12],给WebKit的报告在[13]。他们都早已修复完毕。漏洞编号是CVE-2016-5137(Chrome)和CVE-2016-9017(Firefox)。Google还给了我1000美元奖金。

五、总结

这篇文章主要介绍了什么是HSTS以及和HSTS相关的三个漏洞。这三个漏洞影响都不大,但是我写出来主要为了分享,如何灵活运用端口号这个技巧来绕过相关限制。HSTS其实还能当Cookie用,也是HSTS带来的隐私问题,鉴于和本文关系不大,就不涉及了,想了解的话可参见[14]。

六、参考文献

*本文原创作者:tocttou