爬蟲筆記之JS檢測瀏覽器開發者工具是否打開


在某些情況下我們需要檢測當前用戶是否打開了瀏覽器開發者工具,比如前端爬蟲檢測,如果檢測到用戶打開了控制台就認為是潛在的爬蟲用戶,再通過其它策略對其進行處理。本篇文章主要講述幾種前端JS檢測開發者工具是否打開的方法。

 image 

 

一、重寫toString()

對於一些瀏覽器,比如Chrome、FireFox,如果控制台輸出的是對象,則保留對象的引用,每次打開開發者工具的時候都會重新調用一下對象的toString()方法將返回結果打印到控制台(console tab)上。

所以只需要創建一個對象,重寫它的toString()方法,然后在頁面初始化的時候就將其打印在控制台上(這里假設控制台還沒有打開),當用戶打開控制台時會再去調用一下這個對象的toString()方法,用戶打開控制台的行為就會被捕獲到。

下面是一個小小的例子,當Chrome用戶的開發者工具狀態從關閉向打開轉移時,這個動作會被捕獲到並交由回調函數處理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<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標簽(相當於是程序中的斷點)的時候會暫停程序的執行:

image

此時需要點一下那個藍色的“Resume script execution”程序才會繼續執行,這中間會有一定的時間差,通過判斷這個時間差大於一定的值就認為是打開了開發者工具。這個方法並不會誤傷,當沒有打開開發者工具時遇到debugger標簽不會暫停,所以這種方法還是蠻好的,而且通用性比較廣。

下面是一個使用debugger標簽檢測開發者工具是否打開的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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>

效果:

 foo_001.gif

但是上面的代碼有一個很嚴重的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語句時不暫停:

image

如果這個按鈕被點亮,再測試上面的網頁就會發現很悲劇檢測代碼失效了,因為debugger標簽根本就沒有暫停。

 

其實debugger標簽還有另一種妙用,比如用來反調試,可以設定一個每秒就觸發一個debugger,讓調試者疲於應付debugger或者耗費他額外的成本去覆蓋掉JS,上面給出的幾個網站就是這么做的。

下面是一個使用debugger標簽反js調試的簡單例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
     <title>Anti debug</title>
</head>
<body>
<script>
 
     ! function () {
         setInterval(() => {
             debugger;
         }, 1000);
     }();
 
</script>
</body>
</html>

效果:

foo_001.gif

一個實際的例子,這個網站:http://jxw.uou0.com/的js檢測腳本,而針對不同的情況它又會有不同的反調試策略。

注意要想復現需要粘貼視頻地址解析之后才會加載檢測腳本,比如可以嘗試解析這個視頻:http://film.qq.com/film/p/topic/thwjlxby/index.html

當未打開開發者工具進行解析,然后打開開發者工具,則會使用這種檢測方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
! 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視頻免費解析的,一旦檢測到有人打開開發者工具在調試,就將解析好的視頻移除掉,通過彈出一個提示框:

image

而當已經打開開發者工具再粘貼地址進行視頻解析的話,將會觸發無限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都有說明,這里只對其核心代碼做個分析,此處貼出鄙人對此庫核心代碼的分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/* eslint-disable spaced-comment */
/*!
     devtools-detect
     Detect if DevTools is open
     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瀏覽器來說,開發者工具窗口有四個選項:單獨窗口、靠左、靠下、靠右。

image

靠左、靠右、靠下都會占用當前窗口的一些空間,這種情況會被檢測到,但是獨立窗口並不會占用打開網頁窗口的空間,所以這種情況是檢測不到的,可去此頁面進行驗證:https://sindresorhus.com/devtools-detect/

 

四、總結

本文介紹了幾種檢測方式,其各有利弊,下面是對其缺點的一個簡單的總結:

重寫toString():只能捕獲到開發者工具從關閉狀態向打開狀態轉移的過程

debugger標簽:當勾選了Chrome瀏覽器的Deactive breakpointimage ,debugger標簽不會暫停,將捕獲不到

檢測窗口大小:當開發者工具是以獨立窗口打開的時候不能檢測到


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM