【前端持久化】evercookie
引言:
前端持久化就是要将数据永久的保存在前端,让数据难以删除或者删除后能够重新恢复。存储的数据可以理解为是一种 “僵尸数据”,下面介绍一种前端持久化方法 -- evercookie。
一.evercookie简介:
evercookie是由Samy Kamkar(美国白帽黑客、安全研究员)开发的一组jsApi,它的目的在于持久化cookie,即使用户清除标准cookie、Flash cookie等之后依然能够获取设置过的数据,并且重新恢复清除掉的cookie(比较狭隘,本质上是恢复所有维度,一个重新写的动作)。
二.evercookie原理:
evercookie的原理很简单,就是将数据写入浏览器各个维度,获取的时候再从各个维度中读出来,无论用户怎样清洗,只要其中一个维度有数据就可以得到数据。比较强大的地方在于:1.存储的维度非常多,用户很难清理;2.取数据的时候会将已经清除的数据重新恢复,名副其实的僵尸cookie。
下面介绍下存储的维度以及读取数据的方式和思路:
evercookie 存储数据的维度:
1.标准HTTP Cookie:
evercookie会将数据存在 document.cookie 中,获取的时候直接获取就可以了,没什么可说的,这部分数据是比较容易被清除的,比如浏览器清除cookie、js脚本设置等,分享关于cookie的两个点:
- http请求自带本域以及根域下所有cookie,CSRF的根源就在这里;
- js设置cookie默认在当前域以及当前路径下, cookie一般都会跨路径使用,一定注意设置path字段;
2.Flash Cookie:
evercookie提供了一个flash文件,使用的时候会将数据存储在flash的本地对象中,只有删除对应的flash存储文件才可以清除,把flash文件反编译了一下,得到了AS源码:
shared = sharedobject.getlocal("evercookie"); if (everdata) { var newdata = everdata.split("="); var str = shared.data.cookie; var results = str.split("&"); var i = 0; while (i < results.length) { var elem = results[i].split("="); if (elem[0] != newdata[0]) { everdata = everdata + ("&" + results[i]); } i++; } shared.data.cookie = everdata.replace("\\", "\\\\"); shared.flush(); } flash.external.ExternalInterface.call("_evercookie_flash_var", shared.data.cookie);
存数据的时候调用swfObject中的接口存入即可,可以看下js源码:
this.evercookie_lso = function (name, value) { var div = document.getElementById("swfcontainer"), flashvars = {}, params = {}, attributes = {}; if (div===null || div === undefined || !div.length) { div = document.createElement("div"); div.setAttribute("id", "swfcontainer"); document.body.appendChild(div); } if (value !== undefined) { flashvars.everdata = name + "=" + value; } params.swliveconnect = "true"; attributes.id = "myswf"; attributes.name = "myswf"; //写入flash swfobject.embedSWF(_ec_baseurl + _ec_asseturi + _ec_swf_file_name, "swfcontainer", "1", "1", "9.0.0", false, flashvars, params, attributes); };
而flash加载后会使用 flash.external.ExternalInterface.call("_evercookie_flash_var", shared.data.cookie) 调用window下的javascript方法 _evercookie_flash_var 将数据传给js,对js来说就是读取flash数据。
var _global_lso; function _evercookie_flash_var(cookie) { _global_lso = cookie; // remove the flash object now var swf = document.getElementById("myswf"); if (swf && swf.parentNode) { swf.parentNode.removeChild(swf); } } window._evercookie_flash_var = _evercookie_flash_var;
3.localStorage:localStorage是HTML5的一个新特性,可以将数据永久存储在本地,获取时没有窗口的限制,同域下即可获取,可以调用localStorage的接口来清除,浏览器直接清除缓存数据也能清掉;
4.sessionStorage:同localStorage类似,生存周期是当前对话,浏览器关闭重新打开后消失;
5.globalStorage:同localStorage类似,同样是永久存储在本地,目前只有 Firefox48 以上才支持;
6.openDatabase:HTML5的WebSQL数据库,可以理解为本地存储 Local Storage 和 Session Storage 的一个加强,用来操纵大量结构化数据,由于各个浏览器实现原因,WebSQL规范已经被废弃掉了;
7.IndexedDB:浏览器内置的一种数据库,永久保存数据,IndexDB与WebSQL比较,IndexedDB更像是一个NoSQL数据库,而WebSQL更像是关系型数据库,使用SQL查询数据。
8.图片缓存数据存储:
evercookie利用了图片的缓存进行了存储,简单介绍下:
- 写数据的时候根据key构造一个http请求,将值通过document.cookie传给后台;
- 后台根据cookie中传入的值按照每三位生成一个像素点的方式生成一张200个像素点的png图片(最大为600个字符),并且设置缓存到前端;
- 读数据的时候同样根据key构造相同的http请求,获取缓存的图片并用canvas解析出对应的像素点,恢复出数据。
这里面可以看出两点,一个 evercookiejs 设置的图片存储支持的最大数据为600个字符,二是此种方式必须使用canvas进行解析,有兼容性要求。这种方式显然可以通过浏览器清除缓存直接清掉了。
9.ETag存储:
ETag存储也要依靠后台,利用的原理主要是当浏览器第一次访问一个请求的时候如果服务器响应设置ETag标签,浏览器第二次访问会自动带上一个IF-NONE-MATCH上来(跟ETag设置的值相同),所以只要把数据值存在ETag上,取数据的时候直接去后台查链接上的 IF-NONE-MATCH 字段就可以了, 跟上面png图片缓存类似。
10.web Cache:看evercookie的思路是对 http cookie 的一种加强,相当于通过后台对cookie设置个过期时间,evercookie提供的脚本感觉有问题。
11.silvelright客户端存储:
silvelright也是一种本地存储方式,可以将数据直接存在本地,类似于flash可以跨浏览器获取,需要安装silverlight插件。evercookie提供了相应的编译文件可以通过sliverlight进行存取数据,对sliverlighr不大了解,有兴趣的同学可以研究一下。
12.java应用程序本地存储:通过使用JNLP调用Java Applet的能力将数据存在了本地文件中,代码量比较大不细分析了,反编译了jar包以及class文件,放在文件中有兴趣的可以看下。
13.IE的userData存储:
userData是IE独有的一种存储方式,可以通过XML、HTML标签将数据存储在本地,一般支持IE5以上,官方文档看存储数据大小一般在单个域名640k左右,使用方法很简单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> .userData{ behavior: url(#default#userdata) } </style> </head> <body> <div id="userData" class="userData"></div> </body> <script> var persistDom = document.getElementById("userData"); function set(name, value){ persistDom.setAttribute(name, value); persistDom.save(name); } function get(name){ persistDom.load(name); return persistDom.getAttribute(name) } set("devinn", 1990); window.console && console.log(get("devinn"));//1990 </script> </html>
14.window.name:
window.name是window的一个很特殊的属性,可以设置,有两个特点:
- window.name设置后刷新页面不会消失;
- iframe从一个src跳转到另一个src 获取contentWindow.name 不会发生变化;
evercookie主要是利用了上面的一点,只要页面不刷新,页面随便清理都不会发生变化(奇特的是放在iframe里面清缓存就可以清掉 TT)。
window.name经常用于跨域通信,顺便说下window.name跨域通信原理:
iframe src 从 A.html跳转到 B.html 的时候 window.name 是不变的, 所以如果一个域的页面想跨域获取数据可以设置一个iframe 先将src指向想要获取数据的域页面(此页面将想要传递的数据放在window.name中, ps:此时由于跨域无法获取iframe的contentWindow),之后src指向自己域名下的一个页面(已变成同域)通过iframe的contentWindow即可获取;
跨域获取注意两个关键点:
- 必须放在iframe中;
- 必须使用name属性(console了一下contentWindow,测试了几个其他属性都不行);
15.<a>标签历史访问状态存储:
浏览器中的 <a> 标签有个特性, 同一个浏览器被访问过后状态会变成 "visited" 状态,一般只有清理浏览器浏览记录才会消失,evercookie利用了这点进行存储。
简单说下思路:
- 构造<a>标签并预设visited样式(a:visited)作为访问校验值;
- 构造http请求,请求的地址为设置的键以及值的各个字符(多个http,个数是值的长度);
- 写数据通过构造iframe对上面的http请求进行一次访问;
- 读数据用键和一个字符构造一个链接赋予<a>标签的href,获取<a>标签的样式与预设visited样式进行
- 直接将http请求赋给<a>标签的href,获取如果样式为预设visited的样式说明这个http请求访问过,解出字符;
说明:2中设置的值是个encode后的值,最后一步解出的字符拼装后需要decode后才能获取到原来的值,evercookie里面的实现很有意思,有兴趣的可以看下。
16.HSTS存储:
HSTS一般用来防止中间人攻击, 简单来说,如果一个域名的http响应设置过 Strict-Transport-Security,那么此域名再次发送http请求时浏览器会直接转成https请求(可以设置prelist,第一次请求也可以直接在浏览器端转成https),利用这点可以做数据存储:
- 申请多个域名(例如32个),构造好服务,设置(端口注意设置成443或者80)、清除、查询;
- 将设置的值转为二进制整数,比如 1101, 1的bit发送到对应的第一个域名设置 Strict-Transport-Security,0的清除掉 Strict-Transport-Security: max-age=0;
- 获取时向上面32个域名发送请求进行查询,服务器返回是否是http,对应的bit位设置为0,对应的二进制转成数值就是获取的结果;
直接看实现:
<?php //header('Access-Control-Allow-Origin: *'); $is_ssl = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443; if(isset($_GET['SET'])){ if($is_ssl){ header('Strict-Transport-Security: max-age=31536000'); header('Content-type: image/png'); echo base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAgAAAAJCAIAAACAMfp5AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYSURBVBhXY/z//z8DNsAEpTHAkJJgYAAAo0sDD8axyJQAAAAASUVORK5CYII='); }else{ $redirect = "https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; header("Location: $redirect"); } die(); } if(isset($_GET['DEL'])){ if($is_ssl){ header('Strict-Transport-Security: max-age=0'); }else{ $redirect = "https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; header("Location: $redirect"); } die(); } if($is_ssl){ header('Content-type: image/png'); // some white pixel echo base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAgAAAAJCAIAAACAMfp5AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYSURBVBhXY/z//z8DNsAEpTHAkJJgYAAAo0sDD8axyJQAAAAASUVORK5CYII='); die(); }else{ header('X-PHP-Response-Code: 404', true, 404); } ?>
HSTS存储方式缺点比较大,要申请多个域名,发送多个请求,evercookie默认关闭HSTS存储,chrome和firefox兼容新比较好、IE不支持HSTS设置, 浏览器也可以手动设置关闭HSTS。
evercookie读数据:
evercookie读数据只说一点就可以了,它的思想并不是从任意维度获取到数据就直接返回结果,而是要将所有设置的维度全部取出进行最优解查找,可以防止部分数据被篡改导致的数据异常;也带来一个问题,因为很多都是异步获取,比如数据库、e-tag等,那么获取数据就不是立即获取,会有一部分等待时长。
三.应用:
使用evercookie进行持久化,可以让我们的数据常驻浏览器。利用它不仅可以收集各种浏览器数据,更重要的是,即使用户对浏览器cookie进行了大清洗,这些数据仍然可以起死回生。比如,利用它可以给浏览器建立一个长期有效的身份标识符,利用标识符上报数据对用户的历史信息进行分析进而判断一个操作是善意还是恶意, 对前端风控体系有很大作用。
四.总结:
evercookie简单来讲就是存数据取数据,并没有多少东西,比较闪光的地方在于里面的存取数据的维度和方法,各种奇淫巧技。同时要注意到里面的一些方法,比如HSTS会带来很大开销,获取数据是一个异步过程也会有时间开销,应用的时候尽量根据业务额场景来调整使用。