免责声明:如果你是来寻找机器人检测的终极解决方案,这可能不是你要找的,除非你的用户体验策略涉及弹出窗口,而你的营销策略涉及阻止Google爬虫。
我们最近在Chromium的漏洞追踪器上发现了一个漏洞,一个简短的JavaScript片段可以让无头Chromium浏览器(如Puppeteer和Playwright所使用的)崩溃。听起来像是检测机器人的理想信号,对吧?检测机器人,让他们的浏览器崩溃,这一切都可以在客户端JS中实现,无需服务器。如果你足够幸运,甚至可能在他们的服务器上引发内存泄漏!
也许吧。也许不。在这篇文章中,我们将分解这个漏洞,探讨它如何被用于检测,最后解释为什么这可能不是在生产环境中使用它的一个好主意。
分析漏洞报告
漏洞追踪器不仅仅是让工程师感到沮丧的工具——它们是机器人猎人的宝库。每个无头浏览器的怪癖或自动化漏洞都可能是检测信号。如果在 Puppeteer 中出现问题但在 Chrome 中正常,那值得进一步调查。
这个漏洞简单而精妙。在 iframe 上调用 contentWindow.open
并传入特定参数,浏览器就会崩溃。在 Puppeteer 和 Playwright 中均可完全复现:
const iframe = document.createElement("iframe");
iframe.src = "data:text/html,<body></body>";
document.body.appendChild(iframe);
iframe.contentWindow.open("", "", "top=9999");
为了演示,这里是一个Playwright机器人访问Hacker News、截取屏幕截图,然后触发崩溃的示例:
import { chromium } from "playwright";
(async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://news.ycombinator.com');
await page.waitForTimeout(1000);
await page.screenshot({ path: 'screenshot.png' });
try {
await page.evaluate(() => {
const iframe = document.createElement("iframe");
iframe.src = "data:text/html,<body></body>";
document.body.appendChild(iframe);
iframe.contentWindow.open("", "", "top=9999");
});
} catch (error) {
console.log(error);
}
await browser.close();
})();
需要注意的是,try/catch块没有任何作用。没有异常被抛出。对page.evaluate
的调用只是挂起,浏览器会静默崩溃。browser.close()
从未被调用,这可能会导致内存泄漏。
创建终极机器人检测信号?
注意问号。别太兴奋。
以下是 botCheckMate
的代码,我们的不完美检测器:
function botCheckMate() {
const iframe = document.createElement("iframe");
iframe.src = "data:text/html,<body></body>";
document.body.appendChild(iframe);
iframe.contentWindow.open("", "", "top=9999");
// After this point, if the code didn't crash, then you're human
return false;
}
let isBot = botCheckMate();
如果你是人类,这会返回 false
。如果你是基于 Chromium 的机器人,你会崩溃,而我们保存了返回值!#效率至上
你可以在浏览器开发工具中运行此代码进行验证,它将返回 false。如果你使用 Puppeteer 或 Playwright(基于 Chrome)运行,你的浏览器将崩溃。
为什么这是一个在生产环境中糟糕的主意
尽管本文的语气明显是开玩笑的,但有一个严肃的启示:并非所有检测信号都适合在生产环境中使用。这个检测信号尤其存在诸多缺点,其弊端远大于其新颖性。
首先,触发弹出窗口给人类用户绝非明智之举。大多数人不会预期(或希望)在浏览过程中突然弹出未经请求的窗口。这会破坏用户预期、中断其操作流程,并几乎肯定会降低用户体验。坦白说,你的CMO可能也不会对此感到高兴。
其次是副作用问题。构建机器人检测系统时,尤其是我们Castle团队的做法,核心原则是尽量减少影响。我们倾向于使用安静且不易察觉的信号,避免记录噪音事件、占用过多CPU资源或触发控制台警告。而这种检测方法?它就像在数字世界里大声喊叫。
另一个重大担忧是检测与响应之间的紧密耦合。虽然将两者合并看似诱人,尤其是当响应效果如此令人满意时,但这通常并非正确做法。良好的机器人检测意味着将检测与后续行动分离。你可能希望阻止用户、对其进行影子禁令、标记其账户以供审核,或完全不采取行动。但一旦你让用户的浏览器崩溃,选择就已经做出。
此外,由于整个策略在客户端执行,你将失去大部分可用于决策的有用元数据。你无法在服务器端存储机器人签名、管理Googlebot或自有质量保证工具的允许列表,也无法根据威胁级别定制响应。
最后,机器人会进化。一旦机器人作者弄清楚导致崩溃的原因,他们就会覆盖open()方法或对参数进行 sanitization。游戏结束。您又回到了检测军备竞赛中。想深入检测覆盖操作?我们已通过如《画布随机化》文章中所述的技术为您提供支持。但这意味着您将陷入一场彻头彻尾的猫鼠游戏,伴随着所有随之而来的维护工作。
因此,是的,这个信号有效。但并非没有限制,更不用说没有成本了。
结论
从理论上讲,这种检测方法看起来非常吸引人。只需几行 JavaScript 代码,就能轻松识别并屏蔽机器人浏览器。这种方法简洁、有效,甚至让人感到一种奇妙的满足感。然而,在实际应用中,情况要复杂得多。
最佳的检测信号不仅要有效,还要悄无声息地工作。它们不会降低性能或损害用户信任。它们允许你根据上下文做出决策,而不仅仅是在条件满足时立即触发不可逆的操作。最重要的是,它们能够抵御适应性变化。
这种信号虽然在演示中令人捧腹且强大,但它不符合上述任何标准。它过于显眼。它侵入性强。它脆弱易碎。
所以,享受这个漏洞吧。把它留在你的工具箱里。在测试环境中让机器人崩溃时笑一笑。但也许不要在生产环境中部署它。尤其是当Googlebot能看到它的时候。
除非你已经不在Google的搜索索引中。那么,当然,尽情发挥吧。
> 调用 page.evaluate 时会卡住,浏览器会静默关闭。browser.close() 方法从未被调用,这可能导致内存泄漏。
不仅仅是内存泄漏。自几个月前起,若在 macOS 上通过 Playwright 等工具使用 Chrome,系统会将 Chrome 的副本(超过 1GB)存储至 /private/var/folders/kd/<…>/X/com.google.Chrome.code_sign_clone/ 目录中。若未通过 browser.close() 正常退出,该 Chrome 副本将持续存在。我发现其在两天内占用了约 50GB 空间。我不清楚这个代码签名克隆功能的用途,但不得不为所有调用添加 –disable-features=MacAppCodeSignClone 参数来阻止它,这非常烦人。
目前这是一个已知问题,但值得庆幸的是,这些克隆文件是 APFS 格式,因此不会实际占用磁盘空间。
有趣的是,依稀记得我删除所有克隆时确实释放了不少磁盘空间,但当时也删除了大量其他文件,所以可能记错了。由于 du(1) 无法识别 APFS 克隆,因此难以判断。
查看https://issues.chromium.org/issues/340836884,我有点惊讶地发现该报告距今不到一年,且完全未被关注(除了四个月后的一条附和评论),尽管该问题以P1优先级提交,据我所知这意味着“力争在30天内修复”。如果该问题继续无人问津,我好奇它是否会在五天后自动提升优先级,因为他们对P2和P3级别的漏洞会采取类似措施,将状态调整为“可用”或其他状态,具体细节我记不太清了。
我之所以说“轻微”,是因为我对Chromium漏洞的体验(无论是我自己提交的,还是遇到他人提交的)从未很好。我发现Firefox在修复漏洞方面要好得多。
我猜这取决于漏洞的类型,这个漏洞花了25年才修复 https://news.ycombinator.com/item?id=40431444
公平地说,那个漏洞只是P3级别。
我认为“不让谷歌爬虫看到这个”有点好笑,毕竟谷歌搜索结果往往更糟糕。验证码/反爬虫措施已经糟糕到我不得不切换到Kagi来屏蔽某些域名,因为浏览现代网页有时几乎不可能。为什么谷歌不降低这种体验的排名?
此前在HN上的讨论:检测画布指纹中的噪声 https://news.ycombinator.com/item?id=43170079
当时的反响并不积极,原因显而易见。
在谷歌浏览器中,至少我尝试过通过无限循环修改document.title,结果导致其他标签页的页面也冻结。现在我不在电脑旁,无法再次尝试。
我个人觉得“无头浏览器”的存在本身就很滑稽。JavaScript解释器用于渲染网页,这不过是又一个有趣的巧合。“无版本HTML”哈哈哈
它存在是因为广告技术提供商和 CDN 惩罚那些不在其平台上执行未经信任代码的合法用户。
无头浏览器存在是因为广告技术提供商和 CDN 惩罚那些不在其平台上执行未经信任代码的合法用户?
如果我们问无头Chrome或Selenium的创建者为何创建它们,他们会说“因为广告技术提供商和CDN会惩罚那些不在其平台上执行未经信任代码的合法用户”吗?
无论是否属实,人们决定做某事的原因与他们声称做某事的原因不必一致。
另一个用途是测试网站。
[已删除]
目的是让机器人的浏览器崩溃,而不是用户的浏览器
请指给我一个100%准确的机器人检测系统,且零误报。
你明白意图与现实的区别吧?
文章甚至警告过这个副作用。
[已删除]
如果你在我的robots.txt中抓取禁止的数据,我不在乎。我将随意干扰你的机器人,并且愿意不惜一切代价教你尊重我的robots.txt文件。
那么我将教你一课,关于试图将公共数据私有化。住宅代理和头部浏览器会发出嘶嘶声。
[删除]
恶意软件安装与导致运行抓取进程的.exe文件发生段错误是完全不同的两件事。
如果非法抓取行为是机器的预期行为,那么机器所做的事情已经受到《计算机欺诈法》的规制。
以下几点需要注意:
不确定《计算机欺诈法》适用的司法管辖区是否已认定存在“非法抓取”这一行为。
《计算机欺诈法》是否涵盖导致.exe文件段错误的行为?我不知道,因为我并不生活在该法律适用的国家。
如果《计算机欺诈法》规定允许导致.exe文件段错误(我对此表示怀疑),那么实施这种段错误操作的组织是否在作为防范所谓“非法抓取”的措施时,会核实被段错误的机器是否均位于受《计算机欺诈法》管辖的司法管辖区内?
如果他们在这些管辖范围外引发段错误,而当地有其他适用法律,会发生什么?我猜他们可能就完蛋了。早该想到这一点,这么聪明的人。
嘿,我明白,我就是那种可能会决定对那些通过爬取我的网站并無視我的robots.txt文件而让我损失大量金钱的人引发段错误的人。我就是这么报复心强。但我会接受,我所做的事情可能在某些地方是违法的,可惜了,我肯定不会到处辩解说这是完全合法的,我也会接受这样一个可能性:我跳入的这场斗争可能会造成一些附带损害——他们倒霉了。
这里其他人似乎都觉得自己有理由摧毁别人的东西作为报复,而那些电脑被摧毁的人可能甚至不知道他们和你有什么恩怨。
编辑:显然,一旦事情闹到法庭或媒体,我会辩称这是完全合法、合乎道德且正确的做法,以防止这些人继续用他们的“非法抓取”行为攻击其他网站。因为如果我因获胜而受到惩罚,我就没有赢得这场战斗。我只是在谈论在赢得战斗的过程中,要清楚自己到底在做什么。
这不是我的问题。问题将由恶意软件创建者承担。两次。
如果你从robots.txt中禁止的目录崩溃某个浏览器,这不是你的错。
[已删除]
> 如果你不熟悉这个,去了解一下,原因可能相当发人深省
这些原因与无头网页浏览器相关吗?
这里有一个可能相关的例子
https://news.ycombinator.com/item?id=43947910
有些情况肯定不相关,有些情况则可能相关。
因为人们可能会受到伤害。
哪些人可能会因为运行机器的崩溃而受到伤害?
当这些人决定抢劫你的家时,他们就失去了不受伤害的权利,我认为。当然,还要考虑比例原则等因素。
如果情况如此,我们该如何处理那些在网站或应用中禁用返回按钮(手机的直接返回按钮)或右键功能(桌面浏览器)的情况?而这种功能禁用并未在服务条款中提及,甚至在访问网站或使用应用时未向用户展示?
那么也许我们需要法律来规范每分钟不停地爬取服务器163,000次,無視robots.txt的行为?在此之前,对机器人没有同情。
如果你的软件因正常使用而崩溃,那只能怪你自己
没错。由于AI公司不断轰炸我的服务器导致Nginx内存不足,这是我的错。
没错。修改你的配置,确保它不会尝试分配超过你实际拥有的内存。你可以对它们轰炸你的网站感到不满,但如果你的服务器软件因此崩溃,那是一个配置错误,你应该无论如何都去修复它。
请告诉我,我应该如何配置 NGINX,以便它能够正常为所有希望访问我网站的真实人类提供服务,而不会因那些愚蠢的机器人而崩溃?
试着重新阅读上面的内容,而不是编造自己的幻想。
在运行机器人农场吗?
当然不是,为什么你一上来就跳到指责?如果我是,我只会本地修复漏洞并感谢楼主指出他们是如何做的。
这显然是违法的,我不想让任何人陷入法律麻烦
[已删除]
你如何应对常见的CF、Akamai和其他指纹识别及封锁措施?还是说这是客户需要自行解决的问题?
感谢你的提问!这取决于你运营的规模。
1. 对于个人使用(或公司使用但每个用户使用自己的设备),通常流量会被常规用户活动淹没,因为我们使用相同的浏览器,无需特殊措施,它就是这样工作的。我们为高级用户提供选项。
2. 对于大规模使用,我们根据遇到的反机器人措施提供定制解决方案。其中一部分是模拟#1。
3. 我们不处理“黑帽机器人”,因此不提供绕过合法反机器人措施(如社交媒体垃圾机器人等)的支持。
如果你不付出足够的努力,来自云端IP范围的任何无头浏览器都将被互联网的大部分区域封禁。这不仅仅是关于垃圾邮件机器人,在许多情况下,你甚至无法阅读新闻文章。你将面临来自住宅代理和其他定制自动化解决方案的竞争,这些解决方案会为他们的客户处理所有这些问题。
谢谢,这确实如此!我们在过去构建Monitoro[0]和大型数据抓取管道时,就是通过这种方式学到的,因此我们有机会积累了必要的经验。
需要注意的是,网站存在不同“层级”,每个层级都需要不同的应对措施。并非所有人都追求高竞争网站,而且最重要的是,我们从多个案例中了解到,抓取行为在某些情况下是完全获得用户同意或在用户权利范围内的。例如:
* 许多用户抓取自己的网站以向Discord社区发送通知。这是一种无需编码即可创建警报的超级简单方式。
* 有时用户被锁定在自己的服务提供商中,例如一些公司在其招聘管理系统(ATS)中积累了多年的职位发布信息,却无法提取。我们提供帮助解决此类问题。
* 公共数据网站因数据难以获取而被低效利用。我们帮助将这些数据转化为可操作的信息。例如,一名水手通过设置浮标警报在高水位时确保安全。一个随机示例[1]
0: https://monitoro.co
1: https://wavenet.cefas.co.uk/details/312/EXT
我们在 metalsecurity.io 也有类似的解决方案 🙂 用于处理企业级用例的大规模自动化,绕过反机器人措施
这太棒了,感谢分享!不过这是基于Playwright的吧?你能确认你们采用的方法是否也受TFA漏洞影响吗?
我最初的观点并非仅仅关于绕过反机器人保护,而是希望提供一种独立于现有解决方案(如 Puppeteer、Selenium 等)的浏览器自动化分支。我们认为这些工具并非为此目的设计,且如 TFA 所提到的,存在诸多限制,需要大量工作绕过,正如你的解决方案所示。
我们修复自动化框架的漏洞和问题,因此不存在此类问题。像您这样使用用户浏览器的方法,会因规模扩大而烧毁用户的指纹。
感谢分享您的经验!我对这个话题有强烈看法,请系好安全带 😀
我们避免了分支与补丁的路线,因为这既耗时耗力且回报有限,还是一场追赶游戏。更新分支框架本身已具挑战性,更不用说将现有客户负载迁移到新版本,这会让你事实上被锁定在旧版本。我在前一家公司曾维护过一个与Browserless[0]规模相似的自定义分支,我可以告诉你那真是个噩梦。
开发自己的框架(除了满足明显的 NIH 症候群)让你能够精确控制暴露范围(减少攻击面)从安全角度来看,并保护客户免受上游决策的影响,如弃用或重大更改,这些可能与客户需求不一致。我在这方面也有足够的经验,知道我们需要实现什么以及我们希望启用的功能。没有冗余(目前)
> 根据规模,你会烧毁用户的指纹
这取决于你的活动。参见我关于规模和用例的另一条评论,对于个人设备使用,这在实践中不是问题,用户可以使用他们的个人代理自动化多个网站[1]而无需担心这一点。对于更复杂的场景,我们有适当的策略来避免这个问题。
> 我们修复自动化框架的泄露和漏洞
听起来很有趣!我很乐意阅读相关文档,或查看您在上游项目中提交的PR。
0: https://www.browserless.io/
1: https://herd.garden/trails
听起来不错。想必您也深谙此道,我在这领域也有丰富经验 🙂 但公平地说,每个人对实现和维护的难易程度以及相关优缺点都有自己的看法。我们针对的是需要规模和速度的非常具体的用例,因此我们选择的路线最有意义。我显然不能分享我们的实现细节,因为这会暴露我们的规避措施。而这就是开源替代方案如 camoufox 和现已废弃的 puppeteer-stealth 的问题所在。
看来我们得想办法让这些机器人也崩溃。 😀