What & Why
DevTools 指的是浏览器的开发者工具,网站开发者经常用它来进行开发过程中的调试和线上问题排查。
它方便了开发者的同时,也给一些灰产行为开了方便之门。例如,需要爬取第三方网站数据,最方便的方式就是用这个工具抓接口。有些网站会给接口加上加密逻辑,但既然加密逻辑是跑在用户浏览器上的,就不存在绝对安全;技术人员可以通过 DevTools 一步一步调试代码,找到关键的加密逻辑,自己加密自己用,也就使得加密形同虚设了。
所以,有些网站便想办法要禁用 DevTools,防止自己网站被破解。
常见检测手段
目前,根据 DevTools 特性,主流有几种检测手段:
DevTools 自动读取 ID
DevTools 会自动读取元素的 ID 属性,因此,不难通过一些特定元素 “发现” 该行为。例如
const img = new Image();
Object.defineProperty(img, 'id', {
get: function () {
// 触发 (sylingd)
}
});
当打开 DevTools 时,就会自动触发读取 ID 的事件
console 会自动调用 toString
在使用 console 打印某些特定类型对象时,DevTools 会自动调用对象的 toString 方法;但在没有打开 DevTools 时,打印行为不会有实际影响。因此可以这么检测:
let count = 0;
const date = new Date();
date.toString = () => {
count++;
if (count >= 2) {
// 设置一个阈值,避免误伤 (sylingd)
}
};
console.log(date);
上面使用 Date 举了个例子,类似的还有 Function、RegExp 等。
需要注意的是,不同浏览器可能有不一样的行为。例如使用 RegExp 在 Firefox 浏览器中有效,但不一定在 Chrome 中有效。例如在 QQ 浏览器中,即使不打开控制台,也会触发 RegExp 的 toString 方法。但在打开时,会连续触发两次。
console 序列化行为出现的时间差
console “家族” 有很多个函数。但不论是哪个,其基本流程都需要序列化数据。
在众多函数中,log、warn 等基本接近,但有一个特殊的 table 函数。log 等函数本质上是懒加载的,如果你使用 log 打印一个大对象,它不会立刻读取完整的数据结构,而是在你点击展开时才会读取下一层。但如果是 table,则会一次性读取所有行、列。
因此,在打印大型数据时,两个函数会出现明显的运行时间差值。
function calculateTime(cb) {
const startTime = Date.now();
cb();
return Date.now() - startTime;
}
const largeObject = { /* 一个很大的对象 */};
const tablePrintTime = calculateTime(() => console.table(largeObject));
const logPrintTime = calculateTime(() => console.log(largeObject));
if (tablePrintTime === 0 || logPrintTime === 0) {
// not open
}
if (tablePrintTime > logPrintTime * 10) {
// trigger (sylingd)
}
检查函数篡改
其实上面 console 很好破解,只要你把 console.table “破坏” 掉,比如替换成一个空函数,就无法检测到了。相应的,当然可以检查该函数是否被篡改。
if (!console.table.toString().includes('[native code]')) {
// trigger (sylingd)
}
利用 iframe 获取原始函数
在主窗口 hook 掉这些函数,是不是就万事大吉了?当然不是。脚本还可以利用 iframe 来获取未经篡改的原始函数。例如获取原始的 console 对象:
const el = document.createElement("iframe");
el.style.display = "none",
document.body.appendChild(el);
return el.contentWindow?.console;
反检测
本文目的是进行技术分享和交流,避免被直接被黑灰产利用,故不提供具体实现,仅提供思路。
对于每个方式,思路如下:
-
禁止脚本定义 id 的 getter
-
禁止脚本定义特定对象的 toString 方法
-
console.table 异步化
-
重写被检测对象的 toString,改回
[native code] -
对 iframe 也应用上述手段,或者干脆屏蔽掉 iframe
小结
逛某招聘网站遇到的情况,顺手写了个油猴脚本……