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 相关文章推荐
(currentStyle)javascript为何有时用style得不到已设定的CSS的属性
Aug 15 Javascript
JQUERY1.6 使用方法四 检测浏览器
Nov 23 Javascript
checkbox使用示例
Aug 23 Javascript
javascript中的window.location.search方法简介
Sep 02 Javascript
javascript如何创建表格(javascript绘制表格的二种方法)
Dec 10 Javascript
javascript中typeof的使用示例
Dec 19 Javascript
javascript实时显示当天日期的方法
May 20 Javascript
JavaScript事件处理的方式(三种)
Apr 26 Javascript
jQuery密码强度验证控件使用详解
Jan 05 Javascript
vue调试工具vue-devtools安装及使用方法
Nov 07 Javascript
layui使用表格渲染获取行数据的例子
Sep 13 Javascript
vue3获取当前路由地址
Feb 18 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
PHP-MySQL教程归纳总结
2008/06/07 PHP
PHP的Yii框架中YiiBase入口类的扩展写法示例
2016/03/17 PHP
php提交过来的数据生成为txt文件
2016/04/28 PHP
php自定义函数实现二维数组排序功能
2016/07/20 PHP
php源码之将图片转化为data/base64数据流实例详解
2016/11/27 PHP
php中final关键字用法分析
2016/12/07 PHP
PHP实现图片的等比缩放和Logo水印功能示例
2017/05/04 PHP
php获取微信基础接口凭证Access_token
2018/08/23 PHP
从重置input file标签中看jQuery的 .val() 和 .attr(“value”) 区别
2016/06/12 Javascript
Vue响应式添加、修改数组和对象的值
2017/03/20 Javascript
基于JS代码实现简单易用的倒计时 x 天 x 时 x 分 x 秒效果
2017/07/13 Javascript
微信、QQ、微博、Safari中使用js唤起App
2018/01/24 Javascript
如何在js代码中消灭for循环实例详解
2018/07/29 Javascript
ES6 Symbol数据类型的应用实例分析
2019/06/26 Javascript
Node使用koa2实现一个简单JWT鉴权的方法
2021/01/26 Javascript
Python实现列表转换成字典数据结构的方法
2016/03/11 Python
Python开发之快速搭建自动回复微信公众号功能
2016/04/22 Python
利用Python暴力破解zip文件口令的方法详解
2017/12/21 Python
利用python实现简单的邮件发送客户端示例
2017/12/23 Python
基于python的多进程共享变量正确打开方式
2018/04/28 Python
对Python正则匹配IP、Url、Mail的方法详解
2018/12/25 Python
在django中,关于session的通用设置方法
2019/08/06 Python
Python 分布式缓存之Reids数据类型操作详解
2020/06/24 Python
Python接口测试环境搭建过程详解
2020/06/29 Python
详解Anaconda 的安装教程
2020/09/23 Python
可以随进度显示不同颜色的css3进度条分享
2014/04/11 HTML / CSS
英国森林假期:Forest Holidays
2021/01/01 全球购物
艺术系大学生毕业个人自我评价
2013/09/19 职场文书
软件毕业生个人鉴定
2014/03/03 职场文书
环保志愿者活动总结
2014/06/27 职场文书
党支部创先争优活动总结
2014/08/28 职场文书
财务总监岗位职责
2015/02/03 职场文书
入党自传范文2015
2015/06/26 职场文书
小学语文教师研修日志
2015/11/13 职场文书
React Hook用法示例详解(6个常见hook)
2021/04/28 Javascript
JavaWeb Servlet开发注册页面实例
2022/04/11 Java/Android