1、何为流量劫持
前不久小米等六家互联网公司发表联合声明,呼吁运营商打击流量劫持。流量劫持最直观的表现,就是网页上被插入了一些乱七八糟的广告/弹窗之类的内容或者网址被无辜跳转,多了推广尾巴。比如下面这种:
页面的右下角被插入了广告。
流量劫持总体来说属于中间人攻击(Man-in-the-Middle Attack,MITM)的一种,本质上攻击者在通信两端之间对通信内容进行嗅探和篡改,以达到插入数据和获取关键信息的目的。目前互联网上发生的流量劫持基本是两种手段来实现的:
域名劫持:通过劫持掉域名的DNS解析结果,将HTTP请求劫持到特定IP上,使得客户端和攻击者的服务器建立TCP连接,而非和目标服务器直接连接,这样攻击者就可以对内容进行窃取或篡改。在极端的情况下甚至攻击者可能伪造目标网站页面进行钓鱼攻击。一般而言,用户上网的DNS服务器都是运营商分配的,所以,在这个节点上,运营商可以为所欲为。例如,访问http://jiankang.qq.com/index.html,正常DNS应该返回腾讯的ip,而DNS劫持后,会返回一个运营商的中间服务器ip。访问该服务器会一致性的返回302,让用户浏览器跳转到预处理好的带广告的网页,在该网页中再通过iframe打开用户原来访问的地址。
HTTP劫持/直接流量修改:在数据通路上对页面进行固定的内容插入,比如广告弹窗等。在这种情况下,虽然客户端和服务器是直接建立的连接,但是数据内容依然可能遭到野蛮破坏。例如在运营商的路由器节点上,设置协议检测,一旦发现是HTTP请求,而且是html类型请求,则拦截处理。后续做法往往分为2种,1种是类似DNS劫持返回302让用户浏览器跳转到另外的地址,还有1种是在服务器返回的HTML数据中插入js或dom节点(广告)。
能够实施流量劫持的根本原因,是HTTP协议没有办法对通信对方的身份进行校验以及对数据完整性进行校验。如果能解决这个问题,则流量劫持将无法轻易发生。
关于流量劫持的更多危害案例,可以参考:如何看待小米等联合声明:呼吁运营商严格打击流量劫持?
2、如何防止劫持:主动防御型
2.1 HTTPS防止劫持
HTTPS,是HTTP over SSL的意思,提到HTTPS就不得不先简单描述一下SSL/TLS协议。SSL协议是Netscape在1995年首次提出的用于解决传输层安全问题的网络协议,其核心是基于公钥密码学理论实现了对服务器身份认证、数据的私密性保护以及对数据完整性的校验等功能。1999年IETF将SSL 3.0标准化,是为TLS 1.0版本,目前TLS协议的最新版本是1.2版本,TLS 1.3标准正在制定中。为了方便,下文将SSL/TLS协议都简称为SSL协议。
SSL协议在HTTP请求开始之前增加了握手的阶段,其粗略流程如下图所示:
在SSL握手阶段,客户端浏览器会认证服务器的身份,这是通过“证书”来实现的,证书由证书权威(CA)为某个域名签发,可以理解为网站的身份证件,客户端需要对这个证件进行认证,需要确定该证书是否属于目标网站并确认证书本身是否有效。最后在握手阶段,通信的双方还会协商出一个用于加密和解密的会话密钥。
SSL握手阶段结束之后,服务器和客户端使用协商出的会话密钥对交互的数据进行加密/解密操作,对于HTTP协议来说,就是将HTTP请求和应答经过加密之后再发送到网络上。
由此可见,因为SSL协议提供了对服务器的身份认证,所以DNS劫持导致连接错误服务器的情况将会被发现进而终止连接,最终导致DNS挟持攻击无法实现。此外SSL协议还提供数据的加密和完整性校验,这就解决了关键信息被嗅探以及数据内容被修改的可能。
但是https也不是如此完美,虽然https解决了诸多安全问题,但是对性能也有着比较大的影响。一是用户要从http跳转到https,并且要多几次TLS的握手,这会消耗一定的时间;二是服务器的压力也会增加。除此之外如果使用全站的https,所有页面里面的嵌入资源都要改成https,APP的程序也要进行相应的修改,CDN的所有节点也必须都支持https并且导入证书。所以全站https并不是一件容易的事情,国外的Google、Facebook、Twitter早已支持全站https,但目前国内大多数公司都没有采用全站https的方式,微信算是一个,前段时间百度表示已经支持全站的https了,前段时间我试了一下,百度主站在pc端和移动端都已经可以自动跳转到https了,但是近期发现移动端又恢复成http了,可能是考虑访问体验问题。由此可见全站https应该是未来互联网的趋势。
2.2 绕过运营商localDNS,使用公共的DNS
让用户不使用运营商的local DNS改用114DNS,114DNS是国内最大的中立缓存DNS。推动用户自己手工去改DNS显然是不可行的,用户10个有9个都不知道DNS是啥。那如何去让用户不使用运营商的local DNS呢?po主在腾讯的一个工程师的博客里面看到有这么一段话:“如何在用户侧构造域名请求:对于PC端的客户端来说,构造一个标准的DNS请求包并不算什么难事。但在移动端要向一个指定的LocalDNS上发送标准的DNS请求包,而且要兼容各种iOS和android的版本的话,技术上是可行的,只是兼容的成本会很高。”po主认为这种方式暂且不谈技术上是否可行,最大的问题应该是无法确保公共DNS的稳定性。一旦公共的DNS遭到攻击,将会导致全国性的故障,所以一定需要一个冗余的方案,就是当公共DNS挂了后,可以去向运营商的local dns去请求。
2.3 抛弃域名访问方式,直接进行通过IP访问
这种解决思路,po主了解的有两种方式,第一种是httpdns,这种应该是目前DNS防劫持的主流方式,但是网上相关的介绍非常少,从腾讯员工的博客以及阿里朋友给的内部资料上看,现在腾讯和阿里都在用这种方式。另一种是网宿的MAA解决方案。两者的原理大同小异,都是为移动APP而量身定制的。
httpdns的原理大致就是在移动客户端中加入一个域名解析的模块,客户端通过http的方式向企业的流量调度服务器请求ip,此时流量调度服务器会回根据用户所在位置给用户一个最优的ip。客户端在获取ip后直接用此ip来访问所需站点资源。
这种方式看着确实很完美,但是对于一般的企业来说想要实现是一件极具挑战的事情。
1、客户端需要进行一定的开发来满足客户端的httpdns的请求;
2、针对这个流量调度服务器需要自己开发一套所有节点的IP地址库以及测速系统,才可以保证将用户引导的访问最快的IDC节点上,并且对于使用了CDN加速的域名来说,这个IP地址库是不可控的。
3、如何保证高可用性以及不同运营商的用户访问到同一个HttpDNS的服务IP,用户的访问延迟?腾讯的做法是:HttpDNS通过接入了腾讯公网交换平台的BGP Anycast网络,与全国多个主流运营商建立了BGP互联,保证了这些运营商的用户能够快速地访问到HttpDNS服务;另外HttpDNS在多个数据中心进行了部署,任意一个节点发生故障时均能无缝切换到备份节点,保证用户解析正常。
所以说httpdns是一个好的解决方案,但是用起来并不容易,而其投入产出比也只有在一些巨型的互联网公司身上才能体现出来。
再来说说网宿的MAA的解决方案,po主觉得和httpdns比较类似,前提是这个域名要使用网宿的CDN加速。网宿的MAA方案其实就是帮一般企业来解决上面httpdns的三点难点。
1、客户端插入网宿研发的SDK,向其鉴权服务器请求ip;
2、网宿的鉴权服务器类似于httpdns中的流量调度服务器,因为CDN厂商本来就要负责调度CDN节点的资源,所以这点对他们来说很容易;
3、如何保证高可用性以及不同运营商的用户访问到鉴权的服务器,用户的访问延迟?这点网宿的资料里面没有提到,po主个人猜测问题应该不大,因为对于一个CDN加速的域名来说,用户本来就是要去网宿的鉴权的服务器的,而对于一个提供CDN服务的厂商来说,鉴权的服务器的高可用肯定是要做好的。
(以上绝对非软文,po主觉得是个很好的解决思路,表示要向网宿征收广告费啊!!!)
但是网宿的这个解决方案同样存在问题:
1、插入SDK的方式很多大企业可能无法接受
2、只能用于网宿加速的域名,其他厂商的CDN节点的ip信息网宿不可控,这样可能会被一个厂商绑架。
总的来说主动防御型的解决方案总体上看虽然可以从根本上解决劫持问题,但是成本高,研发周期长,适合超大的互联网公司,但未必适合所有企业。
3、如何防止劫持:被动监测型
被动监测型主要是通过采集用户访问站点的网络参数的方式来判断是否遭到劫持,比如通过采集用户访问站点的ip地址来和我们真实站点以及CDN节点的ip地址进行比对,判断是否遭到DNS劫持,或者通过采集用户访问的URL和真实站点的URL进行比对,判断是否遭到链路劫持。在PC时代对于被动监测型一般就是真机模拟这一种方式,但是在随着移动互联网的快速发展,针对于移动APP出现了一种插入SDK来监测的方式,这两种方式采用的技术手段完全不同,也各有利弊。这种小规模的检测还行,实际应用中,基本还是以方案 2 为主。
例如这里先对外网做检测,上报被劫持的情况:
window.addEventListener('DOMNodeInserted', checkDivHijack); function checkDivHijack(e) { var html = e ? (e.srcElement.outerHTML || e.srcElement.wholeText) : $('html').html(); var reg = /http:\/\/([^\/]+)\//g; var urlList = html.match(reg); if (!urlList || urlList.length == 0) { return; } reg = /^http:\/\/(.*\.qq\.com|.*\.gtimg\.cn|.*\.qlogo\.cn|.*\.qpic\.cn|.*\.wanggou\.com)\/$/; var hijack = false; for (var i = 0; i < urlList.length; i++) { if (!reg.test(urlList[i])) { hijack = true; break; } }}
(注:事后发现这个url检查不够严谨,虽然劫持的情况都能发现,但也把产品原有的一些正常插入做劫持误报了。例如<a href="http://jiankang.qq.com" data-id="1">,不过这个是小细节,把正则表达式完善一下就ok了)
比如还可以针对注入dom节点的情况,初始化时做检查,而且后续dom注入也做检查。可以检查dom中是否含有白名单以外的http链接,如果有,就可以判定为http劫持。
4、防劫持的实际案例与小技巧
4.1 重写 document.write
一般来说,工程师做低成本的防御需要先找到运营商设置的劫持规律。比如这个 case 中查到是运营商采用 document.write 改写了页面内容或者 dom 节点,那么我们可以简单粗暴的将document.write重写为空函数:
var oldDocwrite = document.write,newDocwrite = function(str){};if(oldDocwrite.apply){ hao360.docWrite = function(str){ oldDocwrite.apply(document,arguments); }}else{ hao360.docWrite = oldDocwrite;}document.write = newDocwrite;
显然这种方案有些太不优雅,但却也很有效。
第二个方案是豆瓣 http://www.douban.com/js/do.js 尾部用到的白名单过滤方案:
// @TODO 临时应对劫持 by dexteryyvar _write = _doc.write,_white_list = { 'douban.com': 1, 'douban.fm': 1, 'google.com': 1, 'google-analytics.com': 1, 'googleadservices.com': 1},// 统计劫持情况_hijack_stat = function(reason, env){ var img = new Image(); img.onload = function(){}; img.src = "http://www.douban.com/j/except_report?kind=ra022&reason=" + encodeURIComponent(reason) + "&environment=" + encodeURIComponent(env);},_RE_SCRIPTS = /<script.*?src\=["']?([^"'\s>]+)/ig,_RE_DOMAIN = /(.+?)\.([^\/]+).+/;_doc.write = function(str){ try { var s, safes = [], unkowns = []; while (s = _RE_SCRIPTS.exec(str)) { if (_white_list[(_RE_DOMAIN.exec(s) || [])[2]]) { safes.push(s); } else { unkowns.push(s); } } if (unkowns.length > 0) { _hijack_stat([unkowns[0], safes[0] || ""].join("~_~"), location.href); } try { _write.call(this, str); } catch (ex) { _write(str); } } catch (ex) { _write(str); _hijack_stat(ex.name + ":" + ex.message, location.href); }};
上两个方案可视项目情况而定,各有利弊。
但是如果运营商换个方案,不采取document.write,那真就要很难斗了。当然如果无耻的DNS劫持始终跟着流量入口页的对策升级方案,那咱们还是洗洗睡吧。
4.2 更过分的整站劫持
另外说一句一些地方的小运营商,如四川南充某运营商所管辖的这部分地域,直接将hao.360.cn指向自己的导航页面了,虽然样子还是定期缓存360导航的页面,但其中内容却已被改动多半。对于这些的确的用户反馈,我们只能表示无奈,且也尝试付诸于法律手段。
4.3 结语
所以结论就是运营商是用户的“最后一公里”,我们只能尽力而为也就是了。