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 实现的全选和反选
Apr 15 Javascript
jQuery链式操作如何实现以及为什么要用链式操作
Jan 17 Javascript
jQuery处理json数据返回数组和输出的方法
Mar 11 Javascript
JavaScript判断是否是微信浏览器
Jun 13 Javascript
jQuery 限制输入字符串长度
Jun 20 Javascript
JS实现pasteHTML兼容ie,firefox,chrome的方法
Jun 22 Javascript
canvas 绘制圆形时钟
Feb 22 Javascript
ES6新特性二:Iterator(遍历器)和for-of循环详解
Apr 20 Javascript
利用JavaScript对中文(汉字)进行排序实例详解
Jun 18 Javascript
使用VueCli3+TypeScript+Vuex一步步构建todoList的方法
Jul 25 Javascript
Vue 实例事件简单示例
Sep 19 Javascript
JavaScript Tab菜单实现过程解析
May 13 Javascript
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
php中global和$GLOBALS[]的分析之一
2012/02/02 PHP
PHP对MongoDB[NoSQL]数据库的操作
2013/03/01 PHP
PHP采集静态页面并把页面css,img,js保存的方法
2014/12/23 PHP
PHP实现简单的协程任务调度demo示例
2020/02/01 PHP
Alliance vs AM BO3 第二场2.13
2021/03/10 DOTA
JavaScript关于select的相关操作说明
2010/01/13 Javascript
使用js 设置url参数
2013/07/08 Javascript
javascript full screen 全屏显示页面元素的方法
2013/09/27 Javascript
js库Modernizr的介绍和使用
2015/05/07 Javascript
jQuery动态添加与删除tr行实例代码
2016/10/18 Javascript
JavaScript中运算符规则和隐式类型转换示例详解
2017/09/06 Javascript
JavaScript实现的超简单计算器功能示例
2017/12/23 Javascript
jQuery实现左右滑动的toggle方法
2018/03/03 jQuery
js+css实现红包雨效果
2018/07/12 Javascript
jQuery UI实现动画效果代码分享
2018/08/19 jQuery
一秒学会微信小程序制作table表格
2019/02/14 Javascript
python实现猜数字游戏(无重复数字)示例分享
2014/03/29 Python
python实现向ppt文件里插入新幻灯片页面的方法
2015/04/28 Python
Python使用Tkinter实现机器人走迷宫
2018/01/22 Python
Python读写docx文件的方法
2018/05/08 Python
PowerBI和Python关于数据分析的对比
2019/07/11 Python
python在OpenCV里实现投影变换效果
2019/08/30 Python
美国在线宠物用品商店:Entirely Pets
2017/01/01 全球购物
爱尔兰灯和灯具网上商店:Lights.ie
2018/03/26 全球购物
Pedro官网:新加坡时尚品牌
2019/08/27 全球购物
SIMON MILLER官网:洛杉矶的生活方式品牌
2020/10/19 全球购物
编写用C语言实现的求n阶阶乘问题的递归算法
2014/10/21 面试题
初中生学习生活的自我评价
2013/11/20 职场文书
心理健康心得体会
2014/01/02 职场文书
竞选部门副经理的自荐书范文
2014/02/11 职场文书
小学生综合素质评语
2014/04/23 职场文书
学雷锋演讲稿汇总
2014/05/10 职场文书
胡桃夹子观后感
2015/06/11 职场文书
如何书写先进事迹材料?
2019/07/02 职场文书
详解CocosCreator项目结构机制
2021/04/14 Javascript
Python+uiautomator2实现自动刷抖音视频功能
2021/04/29 Python