爬蟲筆記之JS檢測瀏覽器開發者工具是否打開
在某些情況下我們需要檢測當前用戶是否打開了瀏覽器開發者工具,比如前端爬蟲檢測,如果檢測到用戶打開了控制臺就認為是潛在的爬蟲用戶,再通過其它策略對其進行處理。本篇文章講述了幾種前端JS檢測開發者工具是否打開的方法。
一、重寫toString()
對於一些瀏覽器,比如Chrome、FireFox,如果控制臺輸出的是對象,則保留對象的引用,每次打開開發者工具的時候都會重新調用一下對象的toString()方法將返回結果打印到控制臺(console tab)上。
所以只需要創建一個對象,重寫它的toString()方法,然後在頁面初始化的時候就將其打印在控制臺上(這裏假設控制臺還沒有打開),當用戶打開控制臺時會再去調用一下這個對象的toString()方法,用戶打開控制臺的行為就會被捕獲到。
下面是一個小小的例子,當Chrome用戶的開發者工具狀態從關閉向打開轉移時,這個動作會被捕獲到並交由回調函數處理:
<html> <head> <title>console detect test</title> </head> <body> <script> /** * 控制臺打開的時候回調方法 */ function consoleOpenCallback(){ alert("CONSOLE OPEN"); return ""; } /** * 立即運行函數,用來檢測控制臺是否打開 */ !function () { // 創建一個對象 let foo = /./; // 將其打印到控制臺上,實際上是一個指針 console.log(foo); // 要在第一次打印完之後再重寫toString方法 foo.toString = consoleOpenCallback; }() </script> </body> </html>
效果:
當第一次在此頁面打開控制臺時會觸發到檢測,但是如果是在一個已經打開了控制臺的窗口中粘貼網址訪問則不會觸發,同理在此頁面上已經打開控制臺時刷新也不會觸發。
這種方式雖然比較取巧,但是並不具有通用性,並且只能捕獲到開發者工具處於關閉狀態向打開狀態轉移的過程,具有一定的局限性。
二、debugger
類似於代碼裏的斷點,瀏覽器在打開開發者工具時(對應於代碼調試時的debug模式)檢測到debugger標簽(相當於是程序中的斷點)的時候會暫停程序的執行:
此時需要點一下那個藍色的“Resume script execution”程序才會繼續執行,這中間會有一定的時間差,通過判斷這個時間差大於一定的值就認為是打開了開發者工具
下面是一個使用debugger標簽檢測開發者工具是否打開的例子:
<html> <head></head> <body> <script> function consoleOpenCallback() { alert("CONSOLE OPEN"); } !function () { const handler = setInterval(() => { const before = new Date(); debugger; const after = new Date(); const cost = after.getTime() - before.getTime(); if (cost > 100) { consoleOpenCallback(); clearInterval(handler) } }, 1000) }(); </script> </body> </html>
效果:
但是上面的代碼有一個很嚴重的bug,就是在執行到debugger那一行的時候如果用戶發現了貓膩沒有點按resume script execution按鈕,而是直接退出頁面的話,那麽將不能檢測到本次的打開開發者工具行為,實際結果與預期不符,我認為這是嚴重bug,就像電影裏演的不小心踩到地雷及時察覺不擡腳就還有活命機會,到了debugger標簽我察覺到這是檢測控制臺是否打開的代碼我退出然後使用其它手段繞過它,那我可能做了一個假功能。
有一個需要註意的地方就是使用此方法的時候當卡在debugger標簽的時候,用戶是能夠看到debugger標簽附近的代碼的,如果是有經驗的用戶一眼就能看出裏面的道道,所以要想辦法隱藏一下真實目的,比如將debugger標簽隱藏,並且對代碼進行混淆盡量增加閱讀難度,關於如何隱藏debugger標簽前後的邏輯,可以參考這個網站:http://app2.sfda.gov.cn/datasearchp/gzcxSearch.do?formRender=cx&optionType=V1(當前2018-7-4 23:12:17有效)
使用此種方案的話可能有個需要註意的點就是debugger是有可能不會暫停的,比如Chrome瀏覽器的source面板可以選擇在debugger語句時不暫停:
如果這個按鈕被點亮,再測試上面的網頁就會發現很悲劇檢測代碼失效了,因為debugger標簽根本就沒有暫停。
其實debugger標簽還有另一種妙用,比如用來反調試,可以設定一個每秒就觸發一個debugger,讓調試者疲於應付debugger或者耗費他額外的成本去覆蓋掉JS,下面是一個簡單的例子:
<html> <head> <title>Anti debug</title> </head> <body> <script> !function () { setInterval(() => { debugger; }, 1000); }(); </script> </body> </html>
效果:
當然,應付上面腳本最簡單的方法是把Chrome瀏覽器設定為Deactive breakpoint,上面的腳本就歇菜了,不過這樣的話自己也沒辦法調試了,用來反調試確實能夠惡心一下對面的家夥,比較好的方法是使用Fiddler修改網頁返回內容過濾掉debugger標簽可以完美破解此套路。
三、檢測窗口大小
檢測窗口大小比較簡單,首先要明確兩個概念,窗口的outer大小和inner大小:
window.innerWidth / window.innerHeight :可視區域的寬高,window.innerWidth包含了縱向滾動條的寬度,window.innerHeight包含了水平(橫向)滾動條的寬度。
window.outerWidth / window.outerHeight:會在innerWidth和innerHeight的基礎上加上工具條的寬度。
關於檢測窗口大小,不再自己寫例子,有人專門針對此寫了個庫:https://github.com/sindresorhus/devtools-detect,畢竟幾百個star,比我這個渣渣寫的好多了,代碼比較簡單,使用部分其github都有說明,這裏只對其核心代碼做個分析,此處貼出鄙人對此庫核心代碼的分析:
/* eslint-disable spaced-comment */ /*! devtools-detect Detect if DevTools is open https://github.com/sindresorhus/devtools-detect by Sindre Sorhus MIT License comment by CC11001100 */ (function () { ‘use strict‘; var devtools = { open: false, orientation: null }; // inner大小和outer大小超過threshold被認為是打開了開發者工具 var threshold = 160; // 當檢測到開發者工具後發出一個事件,外部監聽此事件即可,設計得真好,很好的實現了解耦 var emitEvent = function (state, orientation) { window.dispatchEvent(new CustomEvent(‘devtoolschange‘, { detail: { open: state, orientation: orientation } })); }; // 每500毫秒檢測一次開發者工具的狀態,當狀態改變時觸發事件 setInterval(function () { var widthThreshold = window.outerWidth - window.innerWidth > threshold; var heightThreshold = window.outerHeight - window.innerHeight > threshold; var orientation = widthThreshold ? ‘vertical‘ : ‘horizontal‘; // 第一個條件判斷沒看明白,heightThreshold和widthThreshold不太可能同時為true,不論是其中任意一個false還是兩個都false取反之後都會為true,此表達式恒為true if (!(heightThreshold && widthThreshold) && // 針對Firebug插件做檢查 ((window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized) || widthThreshold || heightThreshold)) { // 開發者工具打開,如果之前開發者工具沒有打開,或者已經打開但是靠邊的方向變了才會發送事件 if (!devtools.open || devtools.orientation !== orientation) { emitEvent(true, orientation); } devtools.open = true; devtools.orientation = orientation; } else { // 開發者工具沒有打開,如果之前處於打開狀態則觸發事件報告狀態 if (devtools.open) { emitEvent(false, null); } // 將標誌位恢復到未打開 devtools.open = false; devtools.orientation = null; } }, 500); if (typeof module !== ‘undefined‘ && module.exports) { module.exports = devtools; } else { window.devtools = devtools; } })();
缺點:
1. 使用window屬性檢查大小可能會有瀏覽器兼容性問題,因為不是專業前端只測試了Chrome和ff是沒有問題的。
2. 此方案還是有漏洞的,就拿Chrome瀏覽器來說,開發者工具窗口有四個選項:單獨窗口、靠左、靠下、靠右。
靠左、靠右、靠下都會占用當前窗口的一些空間,這種情況會被檢測到,但是獨立窗口並不會占用打開網頁窗口的空間,所以這種情況是檢測不到的,可去此頁面進行驗證:https://sindresorhus.com/devtools-detect/。
四、總結
本文介紹了幾種檢測方式,其各有利弊,下面是對其缺點的一個簡單的總結:
重寫toString():只能捕獲到開發者工具從關閉狀態向打開狀態轉移的過程
debugger標簽:當勾選了Chrome瀏覽器的Deactive breakpoint ,debugger標簽不會暫停,將捕獲不到
檢測窗口大小:當開發者工具是以獨立窗口打開的時候不能檢測到
相關資料:
1. Find out whether Chrome console is open
.
爬蟲筆記之JS檢測瀏覽器開發者工具是否打開