JS检测浏览器开发者工具是否打开的方法详解


Posted in Javascript onOctober 02, 2020

在某些情况下我们需要检测当前用户是否打开了浏览器开发者工具,比如前端爬虫检测,如果检测到用户打开了控制台就认为是潜在的爬虫用户,再通过其它策略对其进行处理。本篇文章主要讲述几种前端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>

效果:

 JS检测浏览器开发者工具是否打开的方法详解

当第一次在此页面打开控制台时会触发到检测,但是如果是在一个已经打开了控制台的窗口中粘贴网址访问则不会触发,同理在此页面上已经打开控制台时刷新也不会触发。

这种方式虽然比较取巧,但是并不具有通用性,并且只能捕获到开发者工具处于关闭状态向打开状态转移的过程,具有一定的局限性。

二、debugger

类似于代码里的断点,浏览器在打开开发者工具时(对应于代码调试时的debug模式)检测到debugger标签(相当于是程序中的断点)的时候会暂停程序的执行:

JS检测浏览器开发者工具是否打开的方法详解

此时需要点一下那个蓝色的“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>

效果:

 JS检测浏览器开发者工具是否打开的方法详解

但是上面的代码有一个很严重的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语句时不暂停:

JS检测浏览器开发者工具是否打开的方法详解

如果这个按钮被点亮,再测试上面的网页就会发现很悲剧检测代码失效了,因为debugger标签根本就没有暂停。 

其实debugger标签还有另一种妙用,比如用来反调试,可以设定一个每秒就触发一个debugger,让调试者疲于应付debugger或者耗费他额外的成本去覆盖掉JS,上面给出的几个网站就是这么做的。

下面是一个使用debugger标签反js调试的简单例子:

<html>
<head>
  <title>Anti debug</title>
</head>
<body>
<script>
 
  !function () {
    setInterval(() => {
      debugger;
    }, 1000);
  }();
 
</script>
</body>
</html>

效果:

JS检测浏览器开发者工具是否打开的方法详解

一个实际的例子,这个网站: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视频免费解析的,一旦检测到有人打开开发者工具在调试,就将解析好的视频移除掉,通过弹出一个提示框:

JS检测浏览器开发者工具是否打开的方法详解

而当已经打开开发者工具再粘贴地址进行视频解析的话,将会触发无限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浏览器来说,开发者工具窗口有四个选项:单独窗口、靠左、靠下、靠右。

JS检测浏览器开发者工具是否打开的方法详解

靠左、靠右、靠下都会占用当前窗口的一些空间,这种情况会被检测到,但是独立窗口并不会占用打开网页窗口的空间,所以这种情况是检测不到的,可去此页面进行验证:https://sindresorhus.com/devtools-detect/。

四、总结

本文介绍了几种检测方式,其各有利弊,下面是对其缺点的一个简单的总结:

重写toString():只能捕获到开发者工具从关闭状态向打开状态转移的过程

debugger标签:当勾选了Chrome浏览器的Deactive breakpointJS检测浏览器开发者工具是否打开的方法详解 ,debugger标签不会暂停,将捕获不到

检测窗口大小:当开发者工具是以独立窗口打开的时候不能检测到

Javascript 相关文章推荐
JQuery Tips(2) 关于$()包装集你不知道的
Dec 14 Javascript
javascript修改表格背景色实例代码分享
Dec 10 Javascript
jquery实现邮箱自动填充提示功能
Nov 17 Javascript
使用vue.js制作分页组件
Jun 27 Javascript
Angular 4依赖注入学习教程之ClassProvider的使用(三)
Jun 04 Javascript
jQuery动态移除与增加onclick属性的方法详解
Jun 07 jQuery
微信小程序之几种常见的弹框提示信息实现详解
Jul 11 Javascript
vue框架制作购物车小球动画效果实例代码
Sep 26 Javascript
Vue左滑组件slider使用详解
Aug 21 Javascript
Axios取消重复请求的方法实例详解
Jun 15 Javascript
JavaScript 反射学习技巧
Oct 16 Javascript
一起来看看Vue的核心原理剖析
Mar 24 Vue.js
JavaScript检测是否开启了控制台(F12调试工具)
Oct 02 #Javascript
js屏蔽F12审查元素,禁止修改页面代码等实现代码
Oct 02 #Javascript
js禁止查看源文件屏蔽Ctrl+u/s、F12、右键等兼容IE火狐chrome
Oct 01 #Javascript
JS禁用右键、禁用Ctrl+u、禁用Ctrl+s、禁用F12的实现代码
Dec 01 #Javascript
在Vue里如何把网页的数据导出到Excel的方法
Sep 30 #Javascript
jQuery实现二级导航菜单的示例
Sep 30 #jQuery
javascript canvas封装动态时钟
Sep 30 #Javascript
You might like
PHP4中实现动态代理
2006/10/09 PHP
php实现表单多按钮提交action的处理方法
2015/10/24 PHP
详解WordPress中调用评论模板和循环输出评论的PHP函数
2016/01/05 PHP
php生成txt文件实例代码介绍
2016/04/28 PHP
PHP的new static和new self的区别与使用
2019/11/27 PHP
JavaScript对象、属性、事件手册集合方便查询
2010/07/04 Javascript
jquery 实现返回顶部功能
2014/11/17 Javascript
nodejs教程之制作一个简单的文章发布系统
2014/11/21 NodeJs
日常收藏的jquery技巧
2015/12/02 Javascript
javascript特殊文本输入框网页特效
2016/09/13 Javascript
JS取数字小数点后两位或n位的简单方法
2016/10/24 Javascript
利用node.js写一个爬取知乎妹纸图的小爬虫
2017/05/03 Javascript
JavaScript用二分法查找数据的实例代码
2017/06/17 Javascript
使用Vue完成一个简单的todolist的方法
2017/12/01 Javascript
Vue.js实现数据响应的方法
2018/08/13 Javascript
JS实现深度优先搜索求解两点间最短路径
2019/01/17 Javascript
如何为你的JS项目添加智能提示与类型检查详解
2019/03/12 Javascript
关于JS解构的5种有趣用法
2019/09/05 Javascript
Node.JS用纯JavaScript生成图片或滑块式验证码功能
2019/09/12 Javascript
jQuery 常用特效实例小结【显示与隐藏、淡入淡出、滑动、动画等】
2020/05/19 jQuery
vue 使用原生组件上传图片的实例
2020/09/08 Javascript
Node.js path模块,获取文件后缀名操作
2020/11/07 Javascript
如何在sae中设置django,让sae的工作环境跟本地python环境一致
2017/11/21 Python
django从请求到响应的过程深入讲解
2018/08/01 Python
python 通过 socket 发送文件的实例代码
2018/08/14 Python
水芝澳美国官网:H2O Plus
2016/10/15 全球购物
大码女装:Ulla Popken
2019/08/06 全球购物
linux面试题参考答案(9)
2015/01/07 面试题
怎样声明一个匿名的内部类
2016/06/01 面试题
社团文化节邀请函
2014/01/10 职场文书
总裁助理岗位职责
2014/02/17 职场文书
2014社区三八妇女节活动总结
2014/03/01 职场文书
公路绿化方案
2014/05/12 职场文书
幼师求职自荐信
2014/05/31 职场文书
生日庆典策划方案
2014/06/02 职场文书
2019垃圾分类宣传口号汇总
2019/08/16 职场文书