如何检测使用 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
- 通过检测 JavaScript 中的 navigator.webdriver 是否 = true
- 通过检测 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 浏览器的僵尸:
- 使用用户代理 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
- 通过检测 JavaScript 中的 navigator.webdriver 是否 = true
- 检测 CDP 的副作用
尽管这些检测技术相当有效–除了 CDP 检测技术会标记打开了开发工具的人之外,它们没有误报–但老练的攻击者已经意识到了这一点,并开始开发反制措施来避免被检测到。特别是,某些框架(如 nodriver)让僵尸开发者可以通过避免使用 Runtime.enable CDP 命令来绕过 CDP 检测。