博客

检测基于 PhantomJS 的访客

F5 缩略图
F5
2015 年 1 月 22 日发布


如今,许多Web 安全事件都涉及自动化。 网络抓取、密码重复使用和点击欺诈攻击都是由试图模仿真实用户的攻击者发起的,因此会尝试看起来像是来自浏览器。 作为网站所有者,您希望确保为人类提供服务;作为网络服务提供商,您希望通过 API 以编程方式访问您的内容,而不是通过更重且不太稳定的网络界面进行访问。

假设您已经对类似 cURL 的访问者进行了基本检查,那么下一个合理的步骤是确保访问者使用的是真实的、UI 驱动的浏览器 — — 而不是像PhantomJSSlimerJS这样的无头浏览器。

在本文中,我们将演示一些使用 PhantomJS 识别访问的技术。 我们决定专注于 PhantomJS,因为它是最流行的无头浏览器环境,但我们将介绍的许多概念都适用于 SlimerJS 和其他工具。

笔记: 除非明确说明,本文中介绍的技术适用于 PhantomJS 1.x 和 2.x。 首先:是否有可能在不响应 PhantomJS 的情况下检测到它?

HTTP 堆栈

您可能知道,PhantomJS 是基于Qt 框架构建的。 Qt 实现 HTTP 堆栈的方式使其从其他现代浏览器中脱颖而出。

首先,我们来看看 Chrome,它发送了以下标头:

幻影JS

然而,在 PhantomJS 中,相同的 HTTP 请求如下所示:

 
幻影JS

你会注意到 PhantomJS 标头与 Chrome(以及所有其他现代浏览器)在一些细微的方面有所不同:

  • Host 标头最后出现
  • 连接标头值是大小写混合的
  • 唯一的 Accept-Encoding 值是 gzip
  • User-Agent 包含“PhantomJS”

检查服务器上的这些 HTTP 标头异常,应该能够识别出 PhantomJS 浏览器。

但是,相信这些价值观是否安全? 如果对手使用代理在无头浏览器前重写标头,他们可以修改这些标头,使其看起来像普通的现代浏览器。

看来纯粹在服务器上解决这个问题并不是灵丹妙药。 那么让我们看看使用 PhantomJS 的 JavaScript 环境可以在客户端做什么。

客户端用户代理检查

我们可能无法信任通过 HTTP 传递的 User-Agent 值,但客户端上的情况又如何呢?

幻影JS

不幸的是,在 PhantomJS 中更改 user-agent 标头和 navigator.userAgent 值同样很简单,所以这可能还不够。

插件

navigator.plugins 包含浏览器中存在的插件数组。 典型的插件值包括 Flash、ActiveX、对 Java 小程序的支持以及“默认浏览器助手”,该插件用于指示此浏览器是否是 OS X 中的默认浏览器。在我们的研究中,大多数常用浏览器的新安装都包含至少一个默认插件 — — 即使在移动设备上也是如此。

这与 PhantomJS 不同,它没有实现任何插件,也没有提供添加插件的方法(使用PhantomJS API )。

下面的检查可能会有用:

幻影JS

另一方面,通过在页面加载之前修改 PhantomJS JavaScript 环境来欺骗此插件数组是相当简单的。

不难想象,使用真实实现的插件来构建 PhantomJS 的自定义版本。 这比听起来容易,因为 PhantomJS 所基于的 Qt 框架提供了用于实现插件的本机 API

定时

另一个有趣的点是 PhantomJS 如何抑制 JavaScript 对话框:

幻影JS

经过多次测量后,似乎如果警报对话框在 15 毫秒内被抑制,则浏览器可能不受人为控制。 但使用这种方法意味着会打扰真正的用户,他们必须手动关闭警报。

全局属性

PhantomJS 1.x 在全局对象上公开两个属性:

幻影JS

但是,这些属性是实验性功能的一部分,将来可能会发生变化。

缺乏 JavaScript 引擎功能

PhantomJS 1.x 和 2.x 目前使用过时的 WebKit 引擎,这意味着较新的浏览器中存在的浏览器功能在 PhantomJS 中不存在。 这扩展到 JavaScript 引擎——其中某些本机属性和方法在 PhantomJS 中不同或不存在。

其中一种方法是 Function.prototype.bind,它在 PhantomJS 1.x 及更早版本中缺失。 以下示例检查 bind 是否存在,以及它在执行环境中是否被欺骗。

幻影JS

这段代码有点太复杂,无法在此详细解释,但您可以从我们的演示中了解更多信息。

堆栈跟踪

PhantomJS 通过评估命令评估的 JavaScript 代码抛出的错误包含唯一可识别的堆栈跟踪,我们可以从中识别无头浏览器。

例如,假设 PhantomJS 在以下代码上调用评估:

幻影JS

请注意,此示例使用自定义 indexOfString() 函数,留给读者练习,因为本机 String.prototype.indexOf 可以被 PhantomJS 欺骗以始终返回负面结果。

现在,如何获取 PhantomJS 脚本来评估此代码? 一种技术是覆盖一些可能被调用的常用 DOM API 函数。 例如,以下代码覆盖 document.querySelectorAll 来检查浏览器的堆栈跟踪:

概括

在本文中,我们研究了 7 种不同的识别 PhantomJS 的技术,包括在服务器上和通过在 PhantomJS 的客户端 JavaScript 环境中执行代码来识别。 通过将检测结果与强大的反馈机制相结合(例如,使动态页面无效,或使当前会话 cookie 无效),您可以为 PhantomJS 访问者引入坚实的障碍。 然而,请始终牢记,这些技术并非万无一失,老练的对手最终会成功。

要了解更多信息,建议您观看我们在 2014 年 AppSec USA 大会上的演讲录音(幻灯片)。 我们还整理了一个GitHub 存储库,其中包含这里介绍的技术的示例实现和可能的规避方法。

感谢您的阅读,祝您狩猎愉快。

贡献者

谢尔盖·谢基扬 @sshekyan
本·维尼加(Ben Vinegar) @bentlegen
张北 @ikarienator