一、背景
可能Jefferson之前在互联网广告行业的缘故,所以对广告一向比较敏感,偶尔可以看到CPS渠道商劫持自然搜索流量,或是宽带运营商劫持强插广告的情况。然而最近的一些事情,让我对广告劫持及其应对方法更加感兴趣起来。
先是GA 持续报警页面加载时长过长(Jefferson设置的是当天平均6s以上报警),这个起初也没有在意,因为博客放在海外VPS上,连接时长相对较长,加之服务端也没做什么缓存优化,所以平均6s也不奇怪,毕竟之前也常有嘛。但是最近一两个月报警的频率越来越高,这个就真让我下决心有时间要好好优化下了。
二、发现问题
某天无聊进入自己博客,打算看看加载慢的问题,打开Chrome控制台,看到DOMContentLoaded 10s+, Load 20s+, Finish 5min+,果然是漫长!刷新了下页面想看下哪些请求耗时较长,结果,看见了很多不认识的请求!大致研究了下,原来是运营商劫持,先劫持了jquery.js,替换成了劫持方自己的js。而且, 这次劫持的目的似乎并不是插入广告,而是刷数据!因为加载完后没有广告出现,看请求却加载了某视频网站某视频页面,并且细心地将volume设置为了0(这样普通用户就很难发现了)。
三、解决办法
说来也巧,当我正为应对劫持想不到好办法时,就看到了著名Blogger阮一峰发表了一篇Content-Security-Policy的文章,读完后感觉长姿势了。回到家后立马在自己博客上试了下这种方法,劫持问题基本解决了(不过有后遗症,后边再说),因为劫持方的很多逻辑需要依赖于最初始的那个js,正确设置Content-Security-Policy之后,这个js会加载失败,因而页面加载时长得到大幅提升。
站长篇
设置Content-Security-Policy,便是Jefferson要说的站长这边可用来“减少”劫持的方法。
-
Content-Security-Policy简介
Content-Security-Policy(以下简称为CSP),中文名为内容安全策略,实质就是一种白名单机制,用来指示客户端(如浏览器)可以加载哪些域名下的资源。页面加载时经常会加载js脚本(script)、样式(stylesheets)文件、字体(font)文件等。先看下Jefferson博客现在的http response header里的CSP,然后解释下各项的含义:
Content-Security-Policy:script-src 'self' 'unsafe-inline' 'unsafe-eval' *.google-analytics.com; object-src 'none'; style-src 'self' 'unsafe-inline'; report-uri https://example.com/csp-log
先看report-uri 和Content-Security-Policy:之间的部分,便是CSP资源加载的设置。不同类型的资源的CSP使用分号;分割,每部分资源的CSP都先是资源的类型和资源的规则,具有特殊含义的规则使用单引号,普通的规则可以是完整的url也可以是包含通配符的域名等。
- script-src 'self' 'unsafe-inline' 'unsafe-eval' *.google-analytics.com;
- object-src 'none';
- style-src 'self' 'unsafe-inline';
把上边的几部分拆成上边的1,2,3. 1表示仅允许加载当前域名下的script、内嵌的script、类似eval()的动态的script以及域名能匹配到*.google-analytics.com下的script;2表示不允许加载object;3表示仅允许加载当前域名下的stylesheets以及内嵌的style;
-
Content-Security-Policy设置方法
要设置像上边这样的规则,有两种可选的方法。一是使用meta标签来定义,二是使用http header来配置。meta标签这种方法的好处是简单方便,如果像Jefferson一样是使用Wordpress,那么直接到Wordpress后台找到页面header.php在</head>之前添加meta信息即可。不好的地方在于,meta标签的方式没办法指定report uri(指定了也不起作用,浏览器控制台可以直接看到报错信息)。
report-uri 是用来指定一旦出现了违反CSP的情况,应该上报信息到哪里记录下来。这个配置只能使用修改服务端http header的方法。这两种实现方式各给一个参考样例:
方法一示例:
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' *.google-analytics.com; object-src 'none'; style-src 'self' 'unsafe-inline';">
方法二示例:
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval' *.google-analytics.com; object-src 'none'; style-src 'self' 'unsafe-inline'; report-uri https://example.com/csp-log";
csp-log里可以写一些代码逻辑比如记录下上报的信息并邮件发送到站长邮箱,代码可以参考这里。
四、总结
上边说到这种劫持手法(实际上是302重定向)是劫持了一个js文件并替换为劫持方的js,实际上劫持方的js还有加载原来被劫持的js的代码逻辑。试想下,如果劫持方大量劫持了网站的jquery,但之后又不给加载,有些站点很多前端功能可能都依赖jquery,因此用户在网站上的交互势必会受影响,站长或用户很快就会发现哪里不对。所以实际劫持中,劫持方在后期依然会加载原先被劫持的js。如正确设置了CSP,js文件依旧会被302, 302之后因为CSP的限制,劫持方的js会加载失败,这样原先被劫持的js文件也不会再加载了,这便是开头说的CSP在应对这种劫持时的副作用。要解决这种问题,也有办法,比如写个复杂点的逻辑判断之前的js加载成功没,没加载成功就又试着加载一遍呗。
不过,这终究不是根本的解决之道。根本的解决之道是什么呢,那就是全站使用https!下回Jefferson将介绍下全站升级到的https的一点经验。
五、参考资料
- 阮一峰博客Content Security Policy 入门教程
- CSP peport uri php脚本参考Processing Content Security Policy violation reports
- MDN内容安全策略 (CSP)
Pingback: WordPress站点免费升级到https的方法 – Jeff Show – 数据科学笔记