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>

效果:

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

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

二、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 = after.getTime() - before.getTime();
      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有效)

http://app2.sfda.gov.cn/datasearchp/gzcxSearch.do?formRender=cx&optionType=V1

https://www.qimai.cn/

使用此种方案的话可能有个需要注意的点就是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>

效果:

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

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

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

四、总结

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

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

debugger标签:当勾选了Chrome浏览器的Deactive breakpoint ,debugger标签不会暂停,将捕获不到

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

(0)

相关推荐

  • Node.js API详解之 dgram模块用法实例分析

    本文实例讲述了Node.js API详解之 dgram模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 dgram dgram模块提供了 UDP 数据包 socket 的实现. 使用以下方式引用: const dgram = require('dgram'); dgram.createSocket(options[, callback]) 说明: 创建一个 dgram.Socket 对象. 一旦创建了套接字,调用 socket.bind() 会指示套接字开始监听数据报消息

  • Node.js API详解之 V8模块用法实例分析

    本文实例讲述了Node.js API详解之 V8模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 V8 v8 模块暴露了特定于V8版本内置到 Node.js 二进制文件中的API. 通过以下方式使用: const v8 = require('v8'); 注意: APIs 和实现可能在任何时间变动. v8.cachedDataVersionTag() 说明: 返回一个表示从V8版本,命令行标志和已检测到的CPU功能派生的"version tag"的整数. 这对于判

  • 利用浏览器全屏api实现js全屏

    复制代码 代码如下: (function () { var fullScreenApi = {  supportsFullScreen : false,  isFullScreen : function () {   return false;  },  requestFullScreen : function () {},  cancelFullScreen : function () {},  fullScreenEventName : '',  prefix : '' }, browser

  • JS跨浏览器解析XML应用过程详解

    首先介绍简单的理论: 对于XML,我们可以理解它是一种树结构,它包含根,元素,属性,文本等内容.不同浏览器都有自己的解析器,把XML读入内存,并把它转换为可被 JavaScript 访问的 XML DOM 对象. 微软的 XML 解析器与其他浏览器中的解析器是有差异的.微软的解析器支持对 XML 文件和 XML 字符串(文本)的加载,而其他浏览器使用单独的解析器.不过,所有的解析器都含有遍历 XML 树.访问.插入及删除节点的函数. 如果你使用的是一个本地文件或者网络文件且该文件在是在本服务器,

  • 如何基于js判断浏览器版本

    这篇文章主要介绍了如何基于js判断浏览器版本,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 浏览器类型判断 不考虑对 IE9 以下浏览器的判断 function getBrowserType(){ var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串 var browser='unknown'; if (userAgent.indexOf("IE")!=-1) { brow

  • JS浏览器BOM常见操作实例详解

    本文实例讲述了JS浏览器BOM常见操作.分享给大家供大家参考,具体如下: window尺寸 有三种方法能够确定浏览器窗口的尺寸(浏览器的视口,不包括工具栏和滚动条). 对于Internet Explorer.Chrome.Firefox.Opera 以及 Safari: window.innerHeight - 浏览器窗口的内部高度 window.innerWidth - 浏览器窗口的内部宽度 对于 Internet Explorer 8.7.6.5: document.documentElem

  • javascript使用百度地图api和html5特性获取浏览器位置

    复制代码 代码如下: <!DOCTYPE html><html><body><p id="demo">点击这个按钮,获得您的位置:</p><button onclick="getLocation()">试一下</button><script src="http://api.map.baidu.com/api?v=1.4" type="text/ja

  • python 调用API接口 获取和解析 Json数据

    任务背景: 调用API接口数据,抽取我们所需类型的数据,并写入指定mysql数据库. 先从宏观上看这个任务,并对任务进行分解: step1:需要学习python下的通过url读取数据的方式: step2:数据解析,也是核心部分,数据格式从python角度去理解,是字典?列表?还是各种嵌套? step3:连接mysql数据库,将数据写入. 从功能上看,该数据获取程序可以分为3个方法,即step1对应方法request_data(),step2对应方法parse_data(),step3对应data

  • 基于js判断浏览器是否支持webGL

    起因是我之前开发的网页,用到了three.js制作了一个3d的旋转球体效果. 在各种浏览器上运行都没问题,在IE上也做了兼容代码. 但是今天接电话,老板说你这网页在xp上不显示啊.IE上好使.google浏览器不好使. 于是开始解决问题,不禁陷入了沉思.what?网页显示难道还与操作系统有关?google竟然不好使? 于是搭建一个xp虚拟机. 于是下载一个chorme. 惊奇的发现并不能安装最新版本的chrome. 哦?一定是xp上安装的低版本chrome而那时候的chrome还不支持webGL

  • 如何使用 JavaScript 操作浏览器历史记录 API

    History 是 window 对象中的一个 JavaScript 对象,它包含了关于浏览器会话历史的详细信息.你所访问过的 URL 列表将被像堆栈一样存储起来.浏览器上的返回和前进按钮使用的就是 history 的信息. History 对象包含长度属性,它包含了会话历史记录栈中的 URL 数量.例如,如果用户在浏览器中打开一个标签页,历史记录的长度将是 1(新的标签页也是一个网页).然后用户输入一个网址 foo.com 并点击回车,现在历史记录对象的长度将是 2, 用户转到其他页面 bar

  • Vue.js获取手机系统型号、版本、浏览器类型的示例代码

    1.index.html引入 <script src="http://code.jquery.com/jquery-1.11.1.min.js"> </script> <script src="https://cdn.jsdelivr.net/npm/mobile-detect@1.4.4/mobile-detect.min.js"> </script> 2.直接用 <script> //判断数组中是否包含

  • 手机浏览器唤起微信分享(JS)

    最近在做一个手机站,要求点击分享可以直接打开微信分享出去.而不是jiathis,share分享这种的点击出来二维码.在网上看了很多,都说APP能唤起微信,手机网页实现不了.也找了很多都不能直接唤起微信. 总结出来一个可以直接唤起微信的.适应手机qq浏览器和uc浏览器. 下面上代码,把这些直接放到要转发的页面里就可以了: html部分: <script src="mshare.js"></script>//引进mshare.js <button data-m

随机推荐