httphijack1.0.0.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. /**
  2. * @author Coco
  3. * @QQ:308695699
  4. * @name httphijack 1.0.0
  5. * @update : 2016-08-10
  6. * @description 使用Javascript实现前端防御http劫持及防御XSS攻击,并且对可疑攻击进行上报
  7. * -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  8. *
  9. 1、使用方法:调用 httphijack.init()
  10. 2、建立自己的黑白名单、上报系统及接收后端
  11. 3、防范范围:
  12. 1)所有内联事件执行的代码
  13. 2)href 属性 javascript: 内嵌的代码
  14. 3)静态脚本文件内容
  15. 4)动态添加的脚本文件内容
  16. 5)document-write添加的内容
  17. 6)iframe嵌套
  18. *
  19. */
  20. (function(window, undifined) {
  21. var httphijack = function() {},
  22. // 记录内联事件是否被扫描过的 hash map
  23. mCheckMap = {},
  24. // 记录内联事件是否被扫描过的id
  25. mCheckID = 0;
  26. // 建立白名单
  27. var whiteList = [
  28. 'test.iamberry.com',
  29. 'w.iamberry.com',
  30. 'res.wx.qq.com',
  31. 'static.iamberry.com',
  32. 's.iamberry.com',
  33. 'h5.iamberry.com'
  34. ];
  35. // 建立黑名单
  36. var blackList = [
  37. '192.168.1.0'
  38. ];
  39. // 建立关键词黑名单
  40. var keywordBlackList = [
  41. 'xss',
  42. 'embed_v3',
  43. '_embed_v3',
  44. '_embed_v3_dc',
  45. '_embed_v3_hd_l',
  46. '_embed_v3_hd_c',
  47. '_embed_v3_hd_r',
  48. '_embed_v3_frmc',
  49. '_embed_v3_main',
  50. '_embed_v3_ft',
  51. 'BAIDU_SSP__wrapper',
  52. 'BAIDU_DSPUI_FLOWBAR',
  53. '_embed_v3_hd'
  54. ];
  55. // 触发内联事件拦截
  56. function triggerIIE() {
  57. var i = 0,
  58. obj = null;
  59. for (obj in document) {
  60. if (/^on./.test(obj)) {
  61. interceptionInlineEvent(obj, i++);
  62. }
  63. }
  64. }
  65. /**
  66. * 内联事件拦截
  67. * @param {[String]} eventName [内联事件名]
  68. * @param {[Number]} eventID [内联事件id]
  69. * @return {[type]} [description]
  70. */
  71. function interceptionInlineEvent(eventName, eventID) {
  72. var isClick = (eventName == 'onclick');
  73. document.addEventListener(eventName.substr(2), function(e) {
  74. scanElement(e.target, isClick, eventName, eventID);
  75. }, true);
  76. }
  77. /**
  78. * 扫描元素是否存在内联事件
  79. * @param {[DOM]} elem [DOM元素]
  80. * @param {[Boolean]} isClick [是否是内联点击事件]
  81. * @param {[String]} eventName [内联 on* 事件名]
  82. * @param {[Number]} eventID [给每个内联 on* 事件一个id]
  83. */
  84. function scanElement(elem, isClick, eventName, eventID) {
  85. var
  86. flag = elem['isScan'],
  87. // 扫描内联代码
  88. code = "",
  89. hash = 0;
  90. // 跳过已扫描的事件
  91. if (!flag) {
  92. flag = elem['isScan'] = ++mCheckID;
  93. }
  94. hash = (flag << 8) | eventID;
  95. if (hash in mCheckMap) {
  96. return;
  97. }
  98. mCheckMap[hash] = true;
  99. // 非元素节点
  100. if (elem.nodeType != Node.ELEMENT_NODE) {
  101. return;
  102. }
  103. if (elem[eventName]) {
  104. code = elem.getAttribute(eventName);
  105. if (code && blackListMatch(keywordBlackList, code)) {
  106. // 注销事件
  107. elem[eventName] = null;
  108. console.log('拦截可疑内联事件:' + code);
  109. hijackReport('拦截可疑内联事件', code);
  110. }
  111. }
  112. // 扫描 <a href="javascript:"> 的脚本
  113. if (isClick && elem.tagName == 'A' && elem.protocol == 'javascript:') {
  114. var code = elem.href.substr(11);
  115. if (blackListMatch(keywordBlackList, string)) {
  116. // 注销代码
  117. elem.href = 'javascript:void(0)';
  118. console.log('拦截可疑事件:' + code);
  119. hijackReport('拦截可疑javascript:代码', code);
  120. }
  121. }
  122. // 递归扫描上级元素
  123. scanElement(elem.parentNode);
  124. }
  125. // 主动防御 MutationEvent
  126. /**
  127. * 使用 MutationObserver 进行静态脚本拦截
  128. * @return {[type]} [description]
  129. */
  130. function interceptionStaticScript() {
  131. // MutationObserver 的不同兼容性写法
  132. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  133. // 该构造函数用来实例化一个新的 Mutation 观察者对象
  134. // Mutation 观察者对象能监听在某个范围内的 DOM 树变化
  135. var observer = new MutationObserver(function(mutations) {
  136. mutations.forEach(function(mutation) {
  137. // 返回被添加的节点,或者为null.
  138. var nodes = mutation.addedNodes;
  139. // 逐个遍历
  140. for (var i = 0; i < nodes.length; i++) {
  141. var node = nodes[i];
  142. // 扫描 script 与 iframe
  143. if (node.tagName === 'SCRIPT' || node.tagName === 'IFRAME') {
  144. // 拦截到可疑iframe
  145. if (node.tagName === 'IFRAME' && node.srcdoc) {
  146. node.parentNode.removeChild(node);
  147. console.log('拦截到可疑iframe', node.srcdoc);
  148. hijackReport('拦截可疑静态脚本', node.srcdoc);
  149. } else if (node.src) {
  150. // 只放行白名单
  151. if (!whileListMatch(whiteList, node.src)) {
  152. node.parentNode.removeChild(node);
  153. // 上报
  154. console.log('拦截可疑静态脚本:', node.src);
  155. hijackReport('拦截可疑静态脚本', node.src);
  156. }
  157. }
  158. }
  159. }
  160. });
  161. });
  162. // 传入目标节点和观察选项
  163. // 如果 target 为 document 或者 document.documentElement
  164. // 则当前文档中所有的节点添加与删除操作都会被观察到d
  165. observer.observe(document, {
  166. subtree: true,
  167. childList: true
  168. });
  169. }
  170. /**
  171. * 使用 DOMNodeInserted 进行动态脚本拦截监
  172. * 此处无法拦截,只能监测
  173. * @return {[type]} [description]
  174. */
  175. function interceptionDynamicScript() {
  176. // DOMNodeInserted 的执行时机早于 MutationObserver
  177. document.addEventListener('DOMNodeInserted', function(e) {
  178. var node = e.target;
  179. if (/xss/i.test(node.src) || /xss/i.test(node.innerHTML)) {
  180. node.parentNode.removeChild(node);
  181. console.log('拦截可疑动态脚本:', node);
  182. hijackReport('拦截可疑动态脚本', node.src);
  183. }
  184. }, true);
  185. }
  186. // 重写 createElement
  187. function resetCreateElement() {}
  188. /**
  189. * 重写单个 window 窗口的 document.write 属性
  190. * @param {[BOM]} window [浏览器window对象]
  191. * @return {[type]} [description]
  192. */
  193. function resetDocumentWrite(window) {
  194. var old_write = window.document.write;
  195. window.document.write = function(string) {
  196. if (blackListMatch(keywordBlackList, string)) {
  197. console.log('拦截可疑模块:', string);
  198. hijackReport('拦截可疑document-write', string);
  199. return;
  200. }
  201. // 调用原始接口
  202. old_write.apply(document, arguments);
  203. }
  204. }
  205. /**
  206. * 重写单个 window 窗口的 setAttribute 属性
  207. * @param {[BOM]} window [浏览器window对象]
  208. * @return {[type]} [description]
  209. */
  210. function resetSetAttribute(window) {
  211. // 保存原有接口
  212. var old_setAttribute = window.Element.prototype.setAttribute;
  213. // 重写 setAttribute 接口
  214. window.Element.prototype.setAttribute = function(name, value) {
  215. // 额外细节实现
  216. if (this.tagName == 'SCRIPT' && /^src$/i.test(name)) {
  217. if (!whileListMatch(whiteList, value)) {
  218. console.log('拦截可疑模块:', value);
  219. hijackReport('拦截可疑setAttribute', value);
  220. return;
  221. }
  222. }
  223. // 调用原始接口
  224. old_setAttribute.apply(this, arguments);
  225. };
  226. }
  227. /**
  228. * 使用 MutationObserver 对生成的 iframe 页面进行监控,
  229. * 防止调用内部原生 setAttribute 及 document.write
  230. * @return {[type]} [description]
  231. */
  232. function defenseIframe() {
  233. // 先保护当前页面
  234. installHook(window);
  235. }
  236. /**
  237. * 实现单个 window 窗口的 setAttribute保护
  238. * @param {[BOM]} window [浏览器window对象]
  239. * @return {[type]} [description]
  240. */
  241. function installHook(window) {
  242. // 重写单个 window 窗口的 setAttribute 属性
  243. resetSetAttribute(window);
  244. // 重写单个 window 窗口的 document.Write 属性
  245. resetDocumentWrite(window);
  246. // MutationObserver 的不同兼容性写法
  247. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  248. // 该构造函数用来实例化一个新的 Mutation 观察者对象
  249. // Mutation 观察者对象能监听在某个范围内的 DOM 树变化
  250. var observer = new MutationObserver(function(mutations) {
  251. mutations.forEach(function(mutation) {
  252. // 返回被添加的节点,或者为null.
  253. var nodes = mutation.addedNodes;
  254. // 逐个遍历
  255. for (var i = 0; i < nodes.length; i++) {
  256. var node = nodes[i];
  257. // 给生成的 iframe 里环境也装上重写的钩子
  258. if (node.tagName == 'IFRAME') {
  259. installHook(node.contentWindow);
  260. }
  261. }
  262. });
  263. });
  264. observer.observe(document, {
  265. subtree: true,
  266. childList: true
  267. });
  268. }
  269. /**
  270. * 使用 Object.defineProperty,锁住call和apply,使之无法被重写
  271. * @return {[type]} [description]
  272. */
  273. function lockCallAndApply() {
  274. // 锁住 call
  275. Object.defineProperty(Function.prototype, 'call', {
  276. value: Function.prototype.call,
  277. // 当且仅当仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变
  278. writable: false,
  279. // 当且仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除
  280. configurable: false,
  281. enumerable: true
  282. });
  283. // 锁住 apply
  284. Object.defineProperty(Function.prototype, 'apply', {
  285. value: Function.prototype.apply,
  286. writable: false,
  287. configurable: false,
  288. enumerable: true
  289. });
  290. }
  291. /**
  292. * 重定向iframe hijack(页面被iframe包裹)
  293. */
  294. function redirectionIframeHijack() {
  295. var flag = 'iframe_hijack_redirected';
  296. // 当前页面存在于一个 iframe 中
  297. // 此处需要建立一个白名单匹配规则,白名单默认放行
  298. if (self != top) {
  299. var
  300. // 使用 document.referrer 可以拿到跨域 iframe 父页面的 URL
  301. parentUrl = document.referrer,
  302. length = whiteList.length,
  303. i = 0;
  304. for (; i < length; i++) {
  305. // 建立白名单正则
  306. var reg = new RegExp(whiteList[i], 'i');
  307. // 存在白名单中,放行
  308. if (reg.test(parentUrl)) {
  309. return;
  310. }
  311. }
  312. var url = location.href;
  313. var parts = url.split('#');
  314. if (location.search) {
  315. parts[0] += '&' + flag + '=1';
  316. } else {
  317. parts[0] += '?' + flag + '=1';
  318. }
  319. try {
  320. console.log('页面被嵌入iframe中:', parentUrl);
  321. hijackReport('页面被嵌入iframe中', parentUrl);
  322. top.location.href = parts.join('#');
  323. } catch (e) {}
  324. }
  325. }
  326. /**
  327. * 自定义上报 -- 替换页面中的 console.log()
  328. * @param {[String]} name [拦截类型]
  329. * @param {[String]} value [拦截值]
  330. * @return {[type]} [description]
  331. */
  332. function hijackReport(name, value) {
  333. var img = document.createElement('img'),
  334. hijackName = name,
  335. hijackValue = value.toString(),
  336. curDate = new Date().getTime();
  337. // 上报
  338. img.src = 'http://www.reportServer.com/report/?msg=' + hijackName + '&value=' + hijackValue + '&time=' + curDate;
  339. }
  340. /**
  341. * [白名单匹配]
  342. * @param {[Array]} whileList [白名单]
  343. * @param {[String]} value [需要验证的字符串]
  344. * @return {[Boolean]} [false -- 验证不通过,true -- 验证通过]
  345. */
  346. function whileListMatch(whileList, value) {
  347. var length = whileList.length,
  348. i = 0;
  349. for (; i < length; i++) {
  350. // 建立白名单正则
  351. var reg = new RegExp(whiteList[i], 'i');
  352. // 存在白名单中,放行
  353. if (reg.test(value)) {
  354. return true;
  355. }
  356. }
  357. return false;
  358. }
  359. /**
  360. * [黑名单匹配]
  361. * @param {[Array]} blackList [黑名单]
  362. * @param {[String]} value [需要验证的字符串]
  363. * @return {[Boolean]} [false -- 验证不通过,true -- 验证通过]
  364. */
  365. function blackListMatch(blackList, value) {
  366. var length = blackList.length,
  367. i = 0;
  368. for (; i < length; i++) {
  369. // 建立黑名单正则
  370. var reg = new RegExp(blackList[i], 'i');
  371. // 存在黑名单中,拦截
  372. if (reg.test(value)) {
  373. return true;
  374. }
  375. }
  376. return false;
  377. }
  378. // 待完成:
  379. // 建立黑白名单列表
  380. // 对正则匹配精细化
  381. // 初始化方法
  382. httphijack.init = function() {
  383. // 触发内联事件拦截
  384. triggerIIE();
  385. // 进行静态脚本拦截
  386. interceptionStaticScript();
  387. // 进行动态脚本拦截
  388. // interceptionDynamicScript();
  389. // 锁住 apply 和 call
  390. lockCallAndApply();
  391. // 对当前窗口及多重内嵌 iframe 进行 setAttribute | document.write 重写
  392. defenseIframe();
  393. // 对iframe劫持进行重定向
  394. redirectionIframeHijack();
  395. }
  396. window.httphijack = httphijack;
  397. })(window);