0x00 开始
最近看到一篇文章叫做《Pretty-Bad-Proxy:An Overlooked Adversary in Browsers’ HTTPS Deployments》,虽然是好几年前的文章,有些漏洞也早已修复,不过现在看来,很多想法思路都非常棒,毕竟是国际顶级信息安全会议出品,值得与大家分享,我大概对其总结了一下,挑出了其中的关键部分。
我们都知道,HTTPS是为了加密通信传输而专门设计的一个协议,防的就是中间人嗅探和攻击,目的是保证两边接收与发送的数据都是没有经过篡改和伪造的,协议设计的初衷是好的,但浏览器在处理的时候留下了太多的缺陷,下面的5种方式就能看出浏览器是怎么拖HTTPS的后腿。
以下默认情况都是存在代理,或者说用户已经被中间人攻击,我们可以随意拦截修改用户的数据包,只是因为加密传输,无法获知内容而已,就像下面这样。
第一种方法:利用浏览器对错误信息页面(4XX/5XX)中脚本的信任执行(已被修复)
我们都知道服务器错误信息都是自定义的,不同服务器的信息都不一样,类似于下面这样:
问题就出在这里,如果里面有恶意脚本那怎么办?
像下面的情景:
(1) 用户访问https://secret.com/
(2) 中间人拦截请求,并发送回一个状态码为502的响应,响应内容包括以下:
handle = ifr.document//do anything
1
2
1
2
(3) 浏览器渲染页面,重新发送对https://secret.com/的请求,此时中间人不再拦截。
(4) 页面正常加载,脚本执行,加密传输的页面被嗅探和篡改。
注意脚本执行,因为返回的502状态码,地址栏依旧是https://secret.com/
让我们看看实际的结果,用版本为54.0的chrome做下实验,并用fiddler在响应回浏览器前抓包修改内容和状态码:
结果如下:
iframe 成功加载,地址栏也是https://www.baidu.com/,但是脚本被净化了。
第二种方法,利用HTTPS站点引用第三方脚本,伪造重定向到恶意脚本站点(已复现)
很多的HTTPS站点也会引用一些第三方的js脚本文件,为了安全,他们一般也会确保这个第三方脚本所在网站是HTTPS,但是,这样就安全了吗?
试想,当你的页面在请求第三方脚本时,请求被拦截,然后中间人给你返回一个302重定向,浏览器能分辨响应的真伪吗?答案是肯定不能的。
大概的流程如下:
(1) 用户请求https://secret.com/,中间人不拦截
(2) 页面请求第三方脚本https://js.secret.com/example.js,中间人拦截
(3) 中间人返回302数据包,location的值为第三方恶意脚本网站https://evil.com/evil.js/
(4) 页面重新请求脚本,地址为https://evil.com/evil.js/,中间人放行,然后恶意脚本被插入HTTPS的页面中。
我们实际测试一下,拦截某HTTPS网站的脚本请求,修改响应数据包:
这里我们返回的地址是fiddler的测试地址,然后,浏览器成功的发起了对https://www.fiddler2.com/sandbox/FormAndCookie.asp的请求:
这里之所以显示301的请求结果是因为fiddler的那个测试页面不存在导致的,至此,我们发现,这种方法可以完美突破HTTPS。
0x04 第三种方法,利用HPIHSL(HTTP-Intended-but-HTTPS-Loadable) (已复现)
为了提高访问速度或者是降低成本的着想,一个站点往往并不是所有的页面都是HTTPS的,对于那些不那么重要的页面,我们常常会用HTTP,因为反正不包含敏感信息,泄露了也没关系。但恰恰就是这种页面有一个特性,那就是大多数这样的页面通常可以用HTTPS来连接,称这样的页面为HPIHSL。
单纯这样一个页面肯定是无害的,但如果这个页面引用的是HTTP的资源(js,img,css等),那当它采用HTTPS的方式发起连接,所引用的资源却是HTTP的,虽然地址栏左边会失去带锁的图标。
试想当我们替换掉HTTP资源内容为恶意代码,那我们便成功攻入了HTTPS的页面,打破了SOP。或许会有疑问,那也只是个不包含敏感信息的页面,根本没有价值。
但配合上点击劫持呢?(其实只要注入了恶意JS代码,那就可以做任何事了,没有SOP的限制,一切都是那么轻松)
当我们把一个包含恶意代码的js脚本混入HTTPS页面时,新建一个指向正常HTTPS页面的透明iframe覆盖在某个按钮上(像结算,登陆等需要跳转的地方),并且绑定一个点击事件,点击后在新标签页打开HTTPS页面,那么,我们是不是成功拿到了对真正HTTPS页面的控制权?
实际测试中发现,这个问题依旧存在,但是这种方式要求比较多:
(1)HTTP和HTTPS共同存在的站点
(2)域名相同(为了之后获得对真正HTTPS页面的控制权)
(3)HTTP站点有第三方脚本的请求
我们再回顾一下整个攻击过程:
(1)用户访问HTTP页面,中间人在响应中加入iframe,该iframe指向一个HPIHSL页面。
(2)之后该HPIHSL请求第三方HTTP资源,中间人拦截后返回恶意js代码。
(3)该恶意代码添加一些点击事件,点击以后会打开一个正常HTTPS页面。
(4)用户随意点击页面,在新标签中打开正常HTTPS页面,因为此时符合SOP,HTTPS页面被获取和控制。
这种方法适合有针对性的攻击,也可以配合鱼叉式钓鱼,可以说很难发现破绽。
0x05 第四种方法,利用 certificate cache,伪造HTTPS页面
前面提到利用浏览器对错误信息页面(4XX/5XX)中脚本的信任执行可以攻破HTTPS,但是这个缺陷现在已经被修复了,所以这里有另外一种方法可以在不使用任何脚本的情况下依旧攻破HTTPS。
讲这个之前不得不提一下HTTPS的原理,我们都知道HTTPS发送的内容是经过加密的,但具体是怎么做的呢?
或许我们最熟悉的就是RSA公钥加密算法了,虽然高长度密钥的对称加密算法也非常安全,但是对Web应用这种多用户访问一个服务器的方式,服务器需要保存数量巨大的对称密钥,简直原地爆炸。公钥加密就很适合目前的场景,所有用户共用一个公钥,服务器保存私钥,安全简便,但是RSA慢啊!!!对称加密快啊!!怎么办?所以就有了公钥加密和对称加密混合使用的方式,也就是如今SSL(TSL)协议的实现方式。
简单点说,就是在建立起一个安全的传输层之前,需要进行两次SSL握手,期间商定好加密的方法和各种信息(如验证服务器的证书,生成一些随机数等),用公钥加密算法加密一个随机数发送给服务器 ,之后两边再用这些随机数和商定的加密方法生成对称加密密钥,也就是之后的“会话密钥”。
很麻烦?我也觉得,这么说吧,意思就是客户端与服务器两次握手建立安全通道,之后的通信不必再进行公钥加密和身份认证,就像正常的HTTP会话一样,只不过内容被对称加密了,密钥我们无法获知。
我们可以干嘛?不去破坏身份认证环节,在会话中返回一个伪造页面。
具体流程如下:
(1)用户访问https://secret.com/, 中间人拦截请求返回502页面,包含一个meta标签和一个img标签。
(2)meta标签将在一秒后重定向到https://secret.com/,img标签请求https://secret.com/上的一张图片(可以不存在,我们的目的是完成SSL的两次握手)
(3)浏览器为了能够成功请求图片,与服务器成功完成SSL握手建立连接,生成了会话密钥,之后的会话不需要身份验证,浏览器已经信任这个链接。
(4)一秒之后,浏览器重定向到https://secret.com/,中间人拦截请求,返回502响应,其中为纯HTML的一个伪造页面(如登陆页面,只不过表单中的action地址是我们的服务器)
(5)浏览器渲染页面,此时地址栏依旧为绿绿的HTTPS!!!
我们依旧来实际操作一下:
首先拦截前往https://www.baidu.com/的请求:
然后等待它完成SSL握手之后,再次拦截前往https://www.baidu.com/的请求,返回一个502状态码,内容为:
看一下效果:
成功了,问题就是途中的重定向之前会被发现,你可以把时间调短一点,也可以把返回的页面做一些社会工程,比如加一段话“正在前往中,请稍后。”之类的。
0x06 第五种方法,利用Cookie的同源策略不区分HTTP和HTTPS来盗取Cookies
前面介绍的两种方法都要求了较高的社会工程技术和门槛,或许不容易成功,但现在这个方法却相当容易施展。
因为Cookie同源策略的原因,如果不对其设置secure标志,那么Cookie将被发送到HTTP页面,就算一个站点没有HTTP页面,但我们可以通过篡改任意HTTP响应添加一个iframe来要求浏览器发送对HTTPS站点的HTTP请求,而该请求会附带明文Cookie,之后就可以利用这个Cookie登陆HTTPS站点了。
我们看一下具体流程:
(1)用户完成一个HTTPS站点(如https://secret.com/)的登陆操作,获得会话,会话信息存储在Cookie中。
(2)用户此时又请求另一个不相干的HTTP站点,我们发现之后在响应中添加一个指向http://secret.com/的HTTP请求。
(3)浏览器渲染页面,发起对http://secret.com/请求,并附带HTTPS页面的Cookie,我们拦截并获取到了Cookie。
(4)利用该Cookie成功获得会话。
这个应该不用我演示,肯定会成功的。
如果真的存心想嗅探你的数据,方法总比困难多。
最后再次致敬微软研究院的大神前辈们。