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 相关文章推荐
javascript常用方法、属性集合及NodeList 和 HTMLCollection 的浏览器差异
Dec 25 Javascript
javascript插入样式实现代码
Feb 22 Javascript
jQuery Migrate 1.1.0 Released 注意事项
Jun 14 Javascript
BootStrap Fileinput的使用教程
Dec 30 Javascript
Vue.js基础知识小结
Jan 13 Javascript
常用的js方法合集
Mar 10 Javascript
AngularJS页面传参的5种方式
Apr 01 Javascript
利用JS对iframe父子(内外)页面进行操作的方法教程
Jun 15 Javascript
Node.Js中实现端口重用原理详解
May 03 Javascript
解决Mac node版本升级失败的问题
May 16 Javascript
vue 微信分享回调iOS和安卓回调出现错误的解决
Sep 07 Javascript
基于javascript实现放大镜特效
Dec 03 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 cron中的批处理
2008/09/16 PHP
一周让你学会PHP 不错的学习资料
2009/02/06 PHP
PHP 递归效率分析
2009/11/24 PHP
由php的call_user_func传reference引发的思考
2010/07/23 PHP
php eval函数用法 PHP中eval()函数小技巧
2012/10/31 PHP
PHP常用函数之根据生日计算年龄功能示例
2019/10/21 PHP
JQuery autocomplete 使用手册
2010/04/01 Javascript
通过复制Table生成word和excel的javascript代码
2014/01/20 Javascript
用js格式化金额可设置保留的小数位数
2014/05/09 Javascript
jQuery $.each遍历对象、数组用法实例
2015/04/16 Javascript
Bootstrap 源代码分析(未完待续)
2016/08/17 Javascript
微信小程序 教程之模块化
2016/10/17 Javascript
javascript编程实现栈的方法详解【经典数据结构】
2017/04/11 Javascript
Angular中实现树形结构视图实例代码
2017/05/05 Javascript
JS 使用 window对象的print方法实现分页打印功能
2018/05/16 Javascript
详解webpack+ES6+Sass搭建多页面应用
2018/11/05 Javascript
微信小程序使用component自定义toast弹窗效果
2018/11/27 Javascript
vue element upload组件 file-list的动态绑定实现
2019/10/11 Javascript
JQuery省市联动效果实现过程详解
2020/05/08 jQuery
[43:26]完美世界DOTA2联赛PWL S2 Forest vs Rebirth 第二场 11.20
2020/11/23 DOTA
python中Apriori算法实现讲解
2017/12/10 Python
Python cookbook(数据结构与算法)同时对数据做转换和换算处理操作示例
2018/03/23 Python
python utc datetime转换为时间戳的方法
2019/01/15 Python
python贪吃蛇游戏代码
2020/04/18 Python
python增加图像对比度的方法
2019/07/12 Python
Django微信小程序后台开发教程的实现
2020/06/03 Python
Python爬虫之Selenium下拉框处理的实现
2020/12/04 Python
HTML中meta标签及Keywords
2020/04/15 HTML / CSS
上海方立数码笔试题
2013/10/18 面试题
数字天堂软件测试面试题
2012/12/23 面试题
CSS实现fullpage.js全屏滚动效果的示例代码
2021/03/24 HTML / CSS
写自荐信有哪些不宜?
2013/10/17 职场文书
校园文化标语
2014/06/18 职场文书
幼儿园2015年度工作总结
2015/04/01 职场文书
大学校园餐饮创业计划书
2019/08/07 职场文书
浅谈Redis在直播场景的实践方案
2021/04/27 Redis