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標籤不會暫停,所以這種方法還是蠻好的,而且通用性比較廣。
下面是一個使用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
https://www.qimai.cn/
使用此種方案的話可能有個需要注意的點就是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標籤不會暫停,將捕獲不到
檢測視窗大小:當開發者工具是以獨立視窗開啟的時候不能檢測到