在某些狀況下咱們須要檢測當前用戶是否打開了瀏覽器開發者工具,好比前端爬蟲檢測,若是檢測到用戶打開了控制檯就認爲是潛在的爬蟲用戶,再經過其它策略對其進行處理。本篇文章主要講述幾種前端JS檢測開發者工具是否打開的方法。html
對於一些瀏覽器,好比Chrome、FireFox,若是控制檯輸出的是對象,則保留對象的引用,每次打開開發者工具的時候都會從新調用一下對象的toString()方法將返回結果打印到控制檯(console tab)上。前端
因此只須要建立一個對象,重寫它的toString()方法,而後在頁面初始化的時候就將其打印在控制檯上(這裏假設控制檯尚未打開),當用戶打開控制檯時會再去調用一下這個對象的toString()方法,用戶打開控制檯的行爲就會被捕獲到。git
下面是一個小小的例子,當Chrome用戶的開發者工具狀態從關閉向打開轉移時,這個動做會被捕獲到並交由回調函數處理:github
<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>
效果:chrome
當第一次在此頁面打開控制檯時會觸發到檢測,可是若是是在一個已經打開了控制檯的窗口中粘貼網址訪問則不會觸發,同理在此頁面上已經打開控制檯時刷新也不會觸發。瀏覽器
這種方式雖然比較取巧,可是並不具備通用性,而且只能捕獲到開發者工具處於關閉狀態向打開狀態轉移的過程,具備必定的侷限性。app
相似於代碼裏的斷點,瀏覽器在打開開發者工具時(對應於代碼調試時的debug模式)檢測到debugger標籤(至關因而程序中的斷點)的時候會暫停程序的執行:ide
此時須要點一下那個藍色的「Resume script execution」程序纔會繼續執行,這中間會有必定的時間差,經過判斷這個時間差大於必定的值就認爲是打開了開發者工具。這個方法並不會誤傷,當沒有打開開發者工具時遇到debugger標籤不會暫停,因此這種方法仍是蠻好的,並且通用性比較廣。函數
下面是一個使用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標籤先後的邏輯,能夠參考這幾個網站:(當前2018-7-4 23:12:17有效)
http://app2.sfda.gov.cn/datasearchp/gzcxSearch.do?formRender=cx&optionType=V1
使用此種方案的話可能有個須要注意的點就是debugger是有可能不會暫停的,好比Chrome瀏覽器的source面板能夠選擇在debugger語句時不暫停:
若是這個按鈕被點亮,再測試上面的網頁就會發現很悲劇檢測代碼失效了,由於debugger標籤根本就沒有暫停。
其實debugger標籤還有另外一種妙用,好比用來反調試,能夠設定一個每秒就觸發一個debugger,讓調試者疲於應付debugger或者耗費他額外的成本去覆蓋掉JS,上面給出的幾個網站就是這麼作的。
下面是一個使用debugger標籤反js調試的簡單例子:
<html> <head> <title>Anti debug</title> </head> <body> <script> !function () { setInterval(() => { debugger; }, 1000); }(); </script> </body> </html>
效果:
一個實際的例子,這個網站:http://jxw.uou0.com/的js檢測腳本,而針對不一樣的狀況它又會有不一樣的反調試策略。
注意要想復現須要粘貼視頻地址解析以後纔會加載檢測腳本,好比能夠嘗試解析這個視頻:http://film.qq.com/film/p/topic/thwjlxby/index.html。
當未打開開發者工具進行解析,而後打開開發者工具,則會使用這種檢測方式:
!function() { var timelimit = 50; var open = false; setInterval(function() { var starttime = new Date(); debugger ;if (new Date() - starttime > timelimit) { open = true; window.stop(); $("#loading").hide(); $("#a1").remove(); $("#error").show(); $("#error").html("\u7cfb\u7edf\u68c0\u6d4b\u975e\u6cd5\u8c03\u8bd5\u002c\u8bf7\u5237\u65b0\u91cd\u8bd5\u0021") } else { open = false } }, 500) }();
由於這個網站是作vip視頻免費解析的,一旦檢測到有人打開開發者工具在調試,就將解析好的視頻移除掉,經過彈出一個提示框:
而當已經打開開發者工具再粘貼地址進行視頻解析的話,將會觸發無限debugger。
固然,應付上面腳本最簡單的方法是把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
.