当前位置: 首页 > 科技观察

SSLStrip终极版——跨海位置作弊

时间:2023-03-20 17:16:19 科技观察

前言之前介绍过HTTPS前端劫持方案。虽然很有趣,但现实并不理想。它唯一也是最大的缺陷是它不能防止脚本跳转。如果没有这个缺陷,那就很好了——当然,这篇文章也没有必要写了。说到底,还是因为location对象无法改写——它是脚本跳转的唯一通道。虽然有传言说有些Hack可以勉强实现,但毕竟不靠谱。其实在最近封存的HTML5标准中,location的状态已经被非常明确的定义了——Unforgeable。这是一个悲伤的消息。但这也是一件好事,让我们彻底打消各种不正常的思想,另谋出路。上次也提到了替换明文URL,可以参考SSLStrip将脚本中的所有HTTPSURL替换为HTTP版本,可以满足一些场合。当然,缺陷也很明显。只要URL不以纯文本形式出现——比如通过字符串拼接,就会完全无法识别,最终跳转到HTTPS页面是不可避免的。这种情况并不少见,所以我们需要更高级的解决方案。替换location虽然我们不能覆盖location,但是很容易复制一些和location功能相同的东西。我们只需要定义几个getters和setters就可以模拟出一个功能完全一样的location2。但是如何映射原始位置呢?这个时候后台的作用就发挥出来了。与替换HTTPSURL类似,这次我们只关注脚本中的location字符并将其更改为location2——因此所有与地址栏相关的读写都将落在我们的代理上。以后能做什么,不用多说,大家都懂的。代理所有设置器:如果重定向到HTTPS则阻止,并回退到HTTP版本。Proxyallgetters:如果你当前处于降级页面,我们会在返回的路径中还原HTTPS字符,这样可以骗过协议判断脚本,让那些自检功能完全失效!与之前的URL替换相比,这个解决方案***太多了——动态创建URL很常见,但location不以纯文本形式出现的情况极为罕见。除非脚本被加密,否则即使使用像Uglify这样的压缩工具也不会混淆全局变量。至于故意逃避,那更是无稽之谈。if(window['loc\ation'].protocol!='https:'){//...}至此,我们的目标已经明确:前端:实现一个位置代理。后端:用代理变量名替换脚本中的位置。处理外链脚本虽然替换页面脚本内容不难,但对于外链脚本并不看好。实际上,许多页面使用HTTPS绝对路径链接脚本。这时候,我们的中间人就无能为力了。为了避免这种情况,我们还是需要更换页面中的HTTPSURL,让中间人控制更多的资源。替换URL并不难,一个简单的正则表达式就可以做到——但是由于我们使用正则表达式,所以我们只能面对字符串。然而实际上,接收到的是最原始的二进制数据,连UTF-8都不是。在上一篇文章中,为了简单起见,我们直接使用了二进制注入。但是现在,这种方法显然是行不通的。使用二进制不仅难以控制,而且非常不精确。我们很难知道它是一个独立的字符还是一个宽字符的部分字节。所以,我们还是要按照传统可靠的方式来处理字符串。处理字符集编码,我们不得不借助一个字符集转换库,比如大名鼎鼎的iconv,来辅助这件事情:先把二进制数据转换成UTF-8字符串,有了标准字符串,我们的正则化就可以顺利执行了替换掉处理后的字符串恢复到以前的编码。虽然折腾了两次,性能也不少,但还是很有必要的。其实这个过程并没有想象中那么顺利。有相当多的服务器没有在返回的Content-Type中指定编码字符集,所以我们只能尝试从页面中获取。但是这个标签兼容很多规范,比如过去的:和现在流行的:虽然通过正则表达式很容易获取,但是使用正则表达式的前提是必须先有一个字符串,所以我们就陷入了僵局。好在标签、属性、字符集名称基本都是纯ASCII字符,所以可以先将二进制转成默认的UTF-8字符串,从中提取字符集信息,再进行转码。得益于丰富的处理数据块的第三方扩展,上述问题并不难解决。然而,前面提到的“前端劫持”的巨大优势之一——无需处理所有数据,只需在第一个chunk中注入代码即可。但现在,这一优势正面临严峻考验。我们要替换页面中的HTTPS资源、位置变量等,它们会出现在页面的各个地方。如果我们对每个chunk单独过滤转发,会不会有什么问题?在现实中,它可能并不总是理想的——总会有一定的几率被替换的关键字恰好跨越两个chunk:此时,不完整的head和tail无法匹配,所以会出现遗漏。关键字越长,出现的可能性就越大。这对于像URL这样的长字符串来说是一个潜在的危险。彻底解决这个问题比较麻烦。但是有一个简单的方法:我们可以在块的末尾保留一些字符,在下一个块之前拼接它们,从而减少遗漏的可能性。当然,如果不考虑用户体验,最好是把所有的数据都收集起来,一次处理,这样最省事。其实还有更好的解决办法:中间人开一个buffer,把接收到的数据临时缓存在里面。当数据积累到一定数量,或者长时间没有数据时,缓存队列会被分批处理。这样就可以避免频繁的chunkcontext处理,同时用户的响应时间也不会被长时间阻塞,自然是两全其美。这有点像TCPnagle吗?前端定位代理说完了后端的细节,我们继续回到前端的话题。位置代理的实现很简单,但是有很多细节值得注意:location不仅存在于window中,还存在于document中。location对象本身也可以赋值,效果等同于location.href。([PutForwards=href,...]已经解释的很好了)同理,location的toString返回的是href属性。如果缓存了location2的脚本,用户可能会在未被劫持的页面报错。所以我们必须留下一条兼容的后路。...位置方面只要考虑充分,实现起来还是比较容易的。前面提到的动态脚本劫持来代替页面的HTTPSURL,保证外部脚本的明文传输。然而,实际上,并非所有脚本都是静态的。在当今脚本繁多的时代,动态加载模块很常见。如果引入了HTTPS脚本,那我们的中间人就无从下手了。值得庆幸的是,模块拦截并不像位置一样不可能。在现实中,拦截动态模块的方法有很多种。在之前的文章《XSS 前端防火墙 —— 可疑模块拦截》中已经详细讨论了各种方法和细节,在这里正好派上用场。其实除了脚本,框架页面也有这个问题。在上一篇文章中,我们使用了CSP来屏蔽HTTPS框架页面。但这只是屏蔽,并不是真正意义上的拦截。只有加上现在的hook系统,才算是一个完整的拦截系统。demo说了这么多,真正的核心无非就是改变脚本中的location变量,其他的只是辅助而已。接下来我们找几个之前没有成功过的网站,试试这个增强版的劫持工具。上篇提到的京东登录是通过脚本重定向的。我们先测试一下:当流量经过中间人代理时,页面和脚本中的location就成了我们的变量名。所以与地址栏相关的一切都在我们的掌控之中:注意地址栏中有一个zh_cn标记,这是URL被下变后的识别码。通过location2获取的所有属性在HTTPS页面上看起来完全一样。即使脚本中有自检功能,它也会被我们的虚拟环境给忽悠了。点击登录,自然就成功了。毕竟,HTTPS和HTTP只是传输上的区别。在应用程序层,页面不知道-除了询问脚本的位置,已被我们劫持。除了京东的脚本跳转,财付通网站都是通过非主流进行的。好在我们在页面中替换了HTTPSURL,所以还是可以跳转到降级后的页面:值得注意的是,如果从QQ图标点进去,那么页面会直接进入HTTPS版本,不会被劫持。但来自第三者是认命的。由于一般开发者的思维,location变量是不可能逃脱的。因此,这个解决方案几乎可以杀死所有的安全站点。当然国外网站也是一样的。只要之前没有被HSTS缓存过,劫持还是很容易的。……所以,只要发挥你无穷无尽的想象力,实现工程化的通用劫持方案还是可行的。注意事项如果你仔细阅读了这篇文章,你应该已经想到了如何处理它。事实上,由于JS令人难以置信的灵活性,几乎不可能从静态源代码推测运行时行为。所以,只要涉及位置相关的操作,进行简单的转义混淆,就可以避免中间人的劫持。毕竟在劫持流量的同时,还需要对脚本进行解析,代价有点大。原文地址:http://www.cnblogs.com/index-html/p/sslstrip-plus.html