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⽅法
}()
</script>
</body>
</html>
效果:
当第⼀次在此页⾯打开控制台时会触发到检测,但是如果是在⼀个已经打开了控制台的窗⼝中粘贴⽹址访问则不会触发,同理在此页⾯上已经打开控制台时刷新也不会触发。
这种⽅式虽然⽐较取巧,但是并不具有通⽤性,并且只能捕获到开发者⼯具处于关闭状态向打开状态转移的过程,具有⼀定的局限性。
js控制滚动条⼆、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 = Time() - Time();
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有效)
使⽤此种⽅案的话可能有个需要注意的点就是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>
效果:
当未打开开发者⼯具进⾏解析,然后打开开发者⼯具,则会使⽤这种检测⽅式:
!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的基础上加上⼯具条的宽度。
/* eslint-disable spaced-comment */
/*!
devtools-detect
Detect if DevTools is open
github/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 || ientation !== orientation) {
emitEvent(true, orientation);
}
devtools.open = true;
} else {
// 开发者⼯具没有打开,如果之前处于打开状态则触发事件报告状态
if (devtools.open) {
emitEvent(false, null);
}
/
/ 将标志位恢复到未打开
devtools.open = false;
}
}, 500);
if (typeof module !== 'undefined' && ports) {
} else {
window.devtools = devtools;
}
})();
缺点:
1. 使⽤window属性检查⼤⼩可能会有浏览器兼容性问题,因为不是专业前端只测试了Chrome和ff是没有问题的。
2. 此⽅案还是有漏洞的,就拿Chrome浏览器来说,开发者⼯具窗⼝有四个选项:单独窗⼝、靠左、靠下、靠右。
四、总结
本⽂介绍了⼏种检测⽅式,其各有利弊,下⾯是对其缺点的⼀个简单的总结:
重写toString():只能捕获到开发者⼯具从关闭状态向打开状态转移的过程
debugger标签:当勾选了Chrome浏览器的Deactive breakpoint,debugger标签不会暂停,将捕获不到
检测窗⼝⼤⼩:当开发者⼯具是以独⽴窗⼝打开的时候不能检测到
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论