123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- /**
- * @author Coco
- * @QQ:308695699
- * @name httphijack 1.0.0
- * @update : 2016-08-10
- * @description 使用Javascript实现前端防御http劫持及防御XSS攻击,并且对可疑攻击进行上报
- * -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- *
- 1、使用方法:调用 httphijack.init()
- 2、建立自己的黑白名单、上报系统及接收后端
- 3、防范范围:
- 1)所有内联事件执行的代码
- 2)href 属性 javascript: 内嵌的代码
- 3)静态脚本文件内容
- 4)动态添加的脚本文件内容
- 5)document-write添加的内容
- 6)iframe嵌套
- *
- */
- (function(window, undifined) {
- var httphijack = function() {},
- // 记录内联事件是否被扫描过的 hash map
- mCheckMap = {},
- // 记录内联事件是否被扫描过的id
- mCheckID = 0;
- // 建立白名单
- var whiteList = [
- 'test.iamberry.com',
- 'w.iamberry.com',
- 'res.wx.qq.com',
- 'static.iamberry.com',
- 's.iamberry.com',
- 'h5.iamberry.com'
- ];
- // 建立黑名单
- var blackList = [
- '192.168.1.0'
- ];
- // 建立关键词黑名单
- var keywordBlackList = [
- 'xss',
- 'embed_v3',
- '_embed_v3',
- '_embed_v3_dc',
- '_embed_v3_hd_l',
- '_embed_v3_hd_c',
- '_embed_v3_hd_r',
- '_embed_v3_frmc',
- '_embed_v3_main',
- '_embed_v3_ft',
- 'BAIDU_SSP__wrapper',
- 'BAIDU_DSPUI_FLOWBAR',
- '_embed_v3_hd'
- ];
- // 触发内联事件拦截
- function triggerIIE() {
- var i = 0,
- obj = null;
- for (obj in document) {
- if (/^on./.test(obj)) {
- interceptionInlineEvent(obj, i++);
- }
- }
- }
- /**
- * 内联事件拦截
- * @param {[String]} eventName [内联事件名]
- * @param {[Number]} eventID [内联事件id]
- * @return {[type]} [description]
- */
- function interceptionInlineEvent(eventName, eventID) {
- var isClick = (eventName == 'onclick');
- document.addEventListener(eventName.substr(2), function(e) {
- scanElement(e.target, isClick, eventName, eventID);
- }, true);
- }
- /**
- * 扫描元素是否存在内联事件
- * @param {[DOM]} elem [DOM元素]
- * @param {[Boolean]} isClick [是否是内联点击事件]
- * @param {[String]} eventName [内联 on* 事件名]
- * @param {[Number]} eventID [给每个内联 on* 事件一个id]
- */
- function scanElement(elem, isClick, eventName, eventID) {
- var
- flag = elem['isScan'],
- // 扫描内联代码
- code = "",
- hash = 0;
- // 跳过已扫描的事件
- if (!flag) {
- flag = elem['isScan'] = ++mCheckID;
- }
- hash = (flag << 8) | eventID;
- if (hash in mCheckMap) {
- return;
- }
- mCheckMap[hash] = true;
- // 非元素节点
- if (elem.nodeType != Node.ELEMENT_NODE) {
- return;
- }
- if (elem[eventName]) {
- code = elem.getAttribute(eventName);
- if (code && blackListMatch(keywordBlackList, code)) {
- // 注销事件
- elem[eventName] = null;
- console.log('拦截可疑内联事件:' + code);
- hijackReport('拦截可疑内联事件', code);
- }
- }
- // 扫描 <a href="javascript:"> 的脚本
- if (isClick && elem.tagName == 'A' && elem.protocol == 'javascript:') {
- var code = elem.href.substr(11);
- if (blackListMatch(keywordBlackList, string)) {
- // 注销代码
- elem.href = 'javascript:void(0)';
- console.log('拦截可疑事件:' + code);
- hijackReport('拦截可疑javascript:代码', code);
- }
- }
- // 递归扫描上级元素
- scanElement(elem.parentNode);
- }
- // 主动防御 MutationEvent
- /**
- * 使用 MutationObserver 进行静态脚本拦截
- * @return {[type]} [description]
- */
- function interceptionStaticScript() {
- // MutationObserver 的不同兼容性写法
- var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
- // 该构造函数用来实例化一个新的 Mutation 观察者对象
- // Mutation 观察者对象能监听在某个范围内的 DOM 树变化
- var observer = new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- // 返回被添加的节点,或者为null.
- var nodes = mutation.addedNodes;
- // 逐个遍历
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
- // 扫描 script 与 iframe
- if (node.tagName === 'SCRIPT' || node.tagName === 'IFRAME') {
- // 拦截到可疑iframe
- if (node.tagName === 'IFRAME' && node.srcdoc) {
- node.parentNode.removeChild(node);
- console.log('拦截到可疑iframe', node.srcdoc);
- hijackReport('拦截可疑静态脚本', node.srcdoc);
- } else if (node.src) {
- // 只放行白名单
- if (!whileListMatch(whiteList, node.src)) {
- node.parentNode.removeChild(node);
- // 上报
- console.log('拦截可疑静态脚本:', node.src);
- hijackReport('拦截可疑静态脚本', node.src);
- }
- }
- }
- }
- });
- });
- // 传入目标节点和观察选项
- // 如果 target 为 document 或者 document.documentElement
- // 则当前文档中所有的节点添加与删除操作都会被观察到d
- observer.observe(document, {
- subtree: true,
- childList: true
- });
- }
- /**
- * 使用 DOMNodeInserted 进行动态脚本拦截监
- * 此处无法拦截,只能监测
- * @return {[type]} [description]
- */
- function interceptionDynamicScript() {
- // DOMNodeInserted 的执行时机早于 MutationObserver
- document.addEventListener('DOMNodeInserted', function(e) {
- var node = e.target;
- if (/xss/i.test(node.src) || /xss/i.test(node.innerHTML)) {
- node.parentNode.removeChild(node);
- console.log('拦截可疑动态脚本:', node);
- hijackReport('拦截可疑动态脚本', node.src);
- }
- }, true);
- }
- // 重写 createElement
- function resetCreateElement() {}
- /**
- * 重写单个 window 窗口的 document.write 属性
- * @param {[BOM]} window [浏览器window对象]
- * @return {[type]} [description]
- */
- function resetDocumentWrite(window) {
- var old_write = window.document.write;
- window.document.write = function(string) {
- if (blackListMatch(keywordBlackList, string)) {
- console.log('拦截可疑模块:', string);
- hijackReport('拦截可疑document-write', string);
- return;
- }
- // 调用原始接口
- old_write.apply(document, arguments);
- }
- }
- /**
- * 重写单个 window 窗口的 setAttribute 属性
- * @param {[BOM]} window [浏览器window对象]
- * @return {[type]} [description]
- */
- function resetSetAttribute(window) {
- // 保存原有接口
- var old_setAttribute = window.Element.prototype.setAttribute;
- // 重写 setAttribute 接口
- window.Element.prototype.setAttribute = function(name, value) {
- // 额外细节实现
- if (this.tagName == 'SCRIPT' && /^src$/i.test(name)) {
- if (!whileListMatch(whiteList, value)) {
- console.log('拦截可疑模块:', value);
- hijackReport('拦截可疑setAttribute', value);
- return;
- }
- }
- // 调用原始接口
- old_setAttribute.apply(this, arguments);
- };
- }
- /**
- * 使用 MutationObserver 对生成的 iframe 页面进行监控,
- * 防止调用内部原生 setAttribute 及 document.write
- * @return {[type]} [description]
- */
- function defenseIframe() {
- // 先保护当前页面
- installHook(window);
- }
- /**
- * 实现单个 window 窗口的 setAttribute保护
- * @param {[BOM]} window [浏览器window对象]
- * @return {[type]} [description]
- */
- function installHook(window) {
- // 重写单个 window 窗口的 setAttribute 属性
- resetSetAttribute(window);
- // 重写单个 window 窗口的 document.Write 属性
- resetDocumentWrite(window);
- // MutationObserver 的不同兼容性写法
- var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
- // 该构造函数用来实例化一个新的 Mutation 观察者对象
- // Mutation 观察者对象能监听在某个范围内的 DOM 树变化
- var observer = new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- // 返回被添加的节点,或者为null.
- var nodes = mutation.addedNodes;
- // 逐个遍历
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
- // 给生成的 iframe 里环境也装上重写的钩子
- if (node.tagName == 'IFRAME') {
- installHook(node.contentWindow);
- }
- }
- });
- });
- observer.observe(document, {
- subtree: true,
- childList: true
- });
- }
- /**
- * 使用 Object.defineProperty,锁住call和apply,使之无法被重写
- * @return {[type]} [description]
- */
- function lockCallAndApply() {
- // 锁住 call
- Object.defineProperty(Function.prototype, 'call', {
- value: Function.prototype.call,
- // 当且仅当仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变
- writable: false,
- // 当且仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除
- configurable: false,
- enumerable: true
- });
- // 锁住 apply
- Object.defineProperty(Function.prototype, 'apply', {
- value: Function.prototype.apply,
- writable: false,
- configurable: false,
- enumerable: true
- });
- }
- /**
- * 重定向iframe hijack(页面被iframe包裹)
- */
- function redirectionIframeHijack() {
- var flag = 'iframe_hijack_redirected';
- // 当前页面存在于一个 iframe 中
- // 此处需要建立一个白名单匹配规则,白名单默认放行
- if (self != top) {
- var
- // 使用 document.referrer 可以拿到跨域 iframe 父页面的 URL
- parentUrl = document.referrer,
- length = whiteList.length,
- i = 0;
- for (; i < length; i++) {
- // 建立白名单正则
- var reg = new RegExp(whiteList[i], 'i');
- // 存在白名单中,放行
- if (reg.test(parentUrl)) {
- return;
- }
- }
- var url = location.href;
- var parts = url.split('#');
- if (location.search) {
- parts[0] += '&' + flag + '=1';
- } else {
- parts[0] += '?' + flag + '=1';
- }
- try {
- console.log('页面被嵌入iframe中:', parentUrl);
- hijackReport('页面被嵌入iframe中', parentUrl);
- top.location.href = parts.join('#');
- } catch (e) {}
- }
- }
- /**
- * 自定义上报 -- 替换页面中的 console.log()
- * @param {[String]} name [拦截类型]
- * @param {[String]} value [拦截值]
- * @return {[type]} [description]
- */
- function hijackReport(name, value) {
- var img = document.createElement('img'),
- hijackName = name,
- hijackValue = value.toString(),
- curDate = new Date().getTime();
- // 上报
- img.src = 'http://www.reportServer.com/report/?msg=' + hijackName + '&value=' + hijackValue + '&time=' + curDate;
- }
- /**
- * [白名单匹配]
- * @param {[Array]} whileList [白名单]
- * @param {[String]} value [需要验证的字符串]
- * @return {[Boolean]} [false -- 验证不通过,true -- 验证通过]
- */
- function whileListMatch(whileList, value) {
- var length = whileList.length,
- i = 0;
- for (; i < length; i++) {
- // 建立白名单正则
- var reg = new RegExp(whiteList[i], 'i');
- // 存在白名单中,放行
- if (reg.test(value)) {
- return true;
- }
- }
- return false;
- }
- /**
- * [黑名单匹配]
- * @param {[Array]} blackList [黑名单]
- * @param {[String]} value [需要验证的字符串]
- * @return {[Boolean]} [false -- 验证不通过,true -- 验证通过]
- */
- function blackListMatch(blackList, value) {
- var length = blackList.length,
- i = 0;
- for (; i < length; i++) {
- // 建立黑名单正则
- var reg = new RegExp(blackList[i], 'i');
- // 存在黑名单中,拦截
- if (reg.test(value)) {
- return true;
- }
- }
- return false;
- }
- // 待完成:
- // 建立黑白名单列表
- // 对正则匹配精细化
- // 初始化方法
- httphijack.init = function() {
- // 触发内联事件拦截
- triggerIIE();
- // 进行静态脚本拦截
- interceptionStaticScript();
- // 进行动态脚本拦截
- // interceptionDynamicScript();
- // 锁住 apply 和 call
- lockCallAndApply();
- // 对当前窗口及多重内嵌 iframe 进行 setAttribute | document.write 重写
- defenseIframe();
- // 对iframe劫持进行重定向
- redirectionIframeHijack();
- }
- window.httphijack = httphijack;
- })(window);
|