如何检测使用 Puppeteer 的(无头)Chrome 浏览器(2024 年版)

在本文中,我们将介绍 3 种有效技术,用于检测使用无头和非无头 Chrome 浏览器的 Puppeteer 僵尸程序。这些技术已在 2024 年 6 月进行了测试。

简要说明:

如果你只想了解检测技术的代码,可以看看下面的代码片段。本文的其余部分将详细介绍这些技术,并解释攻击者如何绕过其中一些技术。这 3 种技术的工作原理如下:

使用用户代理 HTTP 标头或 JS 中的 navigator.userAgent 来检测链接到无头 Chrome 浏览器的用户代理:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/125.0.0.0 Safari/537.36

  1. 通过检测 JavaScript 中的 navigator.webdriver 是否 = true
  2. 通过检测 CDP 的副作用

使用 JavaScript 代码检测使用 Puppeteer 工具的(无头)Chrome 浏览器:

let isBot = false;
if (navigator.userAgent.includes("HeadlessChrome")) {
    isBot = true;
}

if (navigator.webdriver) {
    isBot = true;
}

var cdpDetected = false;
var e = new Error();
Object.defineProperty(e, 'stack', {
   get() {
    cdpDetected = true;
   }
});

// This is part of the detection, the console.log shouldn't be removed!
console.log(e);

if (cdpDetected) {
    isBot = true;
}

if (isBot) {
		console.log("Your bot has been detected!")
}

技术 1:如何使用 Puppeteer 自动检测未修改的无头 Chrome 浏览器

为了说明第一种检测技术,我们创建了一个基于无头 Chrome 浏览器和 Puppeteer 的简单机器人。机器人访问 https://deviceandbrowserinfo.com/http_headers,我们对页面进行截图,观察机器人发送的 HTTP 头信息:

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://deviceandbrowserinfo.com/http_headers');

    const headersTable = await page.$('#headers')
    await headersTable.screenshot({ path: './vanila-headless-chrome.png' })
    await browser.close()
})();

我们获得以下 HTTP 标头:

因此,我们注意到,使用 Puppeteer 检测未修改的无头 Chrome 浏览器发送的用户代理表明存在无头 Chrome 浏览器。请注意,用户代理也可以从客户端获取,例如,如果你想从谷歌分析中排除无头 Chrome 浏览器的流量。你可以使用 navigator.userAgent 访问它。就我们的机器人而言,它返回 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/125.0.0.0 Safari/537.36 .

免责声明:此检测技术仅适用于无头 Chrome 浏览器。它不适用于使用 Puppeteer 的普通 Chrome 浏览器,因为用户代理不会包含 HeadlessChrome 子字符串。

技术 2:如何检测使用 Puppeteer 工具修改过的(无头)Chrome 浏览器

第二种技术适用于无头和非无头 Chrome 浏览器,也可用于检测更改了用户代理的僵尸。

要伪造用户代理,我们只需在 page.setUserAgent 中加入想要伪造的用户代理。我们需要在访问页面之前,例如在页面创建之后,使用 page.setUserAgent。

const page = await browser.newPage();
await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36');

一旦我们更改了用户,它就不再包含 HeadlessChrome 子字符串,即使在使用 Headless Chrome 时也是如此。

不过,你仍然可以通过 JavaScript 来检测伪造用户代理的机器人,方法是验证 navigator.webdriver 属性是否等于 true。

技巧 3:如何检测使用 Puppeteer 工具删除了 navigator.webdriver = true 的修改过的(无头)Chrome 浏览器

在创建 puppeteer 浏览器实例时使用 –disable-blink-features=AutomationControlle 参数,就能轻松移除上一节介绍的 navigator.webdriver = true 属性:

const browser = await puppeteer.launch({args: ['--disable-blink-features=AutomationControlled']});

以这种方式创建浏览器后,navigator.webdriver 将返回 false,无法再用于检测。

因此,要利用攻击者通过伪造用户代理和摆脱 navigator.webdriver 来主动谎报自己的性质,我们需要找到另一种检测技术。我们可以使用 CDP 检测,这是我在最近的 DataDome 博文中介绍的一种技术。

在引擎盖下,Puppeteer 利用 Chrome DevTools 协议 (CDP) 来检测(无头)Chrome 浏览器。通过使用下图所示的特制挑战,我们可以检测到 CDP 的使用,进而检测到浏览器是否是自动化的:

var detected = false;
var e = new Error();
Object.defineProperty(e, 'stack', {
   get() {
       detected = true;
   }
});
console.log(e);

如果 detected 的值等于 true,则表示浏览器是自动运行的。这种检测技术的一个副作用是,它会将打开开发工具的人类用户标记为僵尸。请注意,console.log(e) 是挑战的一部分,因为它触发了 CDP 中的序列化。您可以在我的 DataDome 博文中找到有关这项挑战的更多细节。

当前检测技术的局限性

在本文中,我们介绍了 3 种不同的检测技术,这些技术可用于检测基于使用 Puppeteer 仪器的(无头)Chrome 浏览器的僵尸:

  1. 使用用户代理 HTTP 标头或 JS 中的 navigator.userAgent 来检测链接到无头 Chrome 浏览器的用户代理:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/125.0.0.0 Safari/537.36
  2. 通过检测 JavaScript 中的 navigator.webdriver 是否 = true
  3. 检测 CDP 的副作用

尽管这些检测技术相当有效–除了 CDP 检测技术会标记打开了开发工具的人之外,它们没有误报–但老练的攻击者已经意识到了这一点,并开始开发反制措施来避免被检测到。特别是,某些框架(如 nodriver)让僵尸开发者可以通过避免使用 Runtime.enable CDP 命令来绕过 CDP 检测。

阅读余下内容
 

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注


京ICP备12002735号