如今,许多Web 安全事件都涉及自动化。 网络抓取、密码重复使用和点击欺诈攻击都是由试图模仿真实用户的攻击者发起的,因此会尝试看起来像是来自浏览器。 作为网站所有者,您希望确保为人类提供服务;作为网络服务提供商,您希望通过 API 以编程方式访问您的内容,而不是通过更重且不太稳定的网络界面进行访问。
假设您已经对类似 cURL 的访问者进行了基本检查,那么下一个合理的步骤是确保访问者使用的是真实的、UI 驱动的浏览器 — — 而不是像PhantomJS和SlimerJS这样的无头浏览器。
在本文中,我们将演示一些使用 PhantomJS 识别访问的技术。 我们决定专注于 PhantomJS,因为它是最流行的无头浏览器环境,但我们将介绍的许多概念都适用于 SlimerJS 和其他工具。
笔记: 除非明确说明,本文中介绍的技术适用于 PhantomJS 1.x 和 2.x。 首先:是否有可能在不响应 PhantomJS 的情况下检测到它?
您可能知道,PhantomJS 是基于Qt 框架构建的。 Qt 实现 HTTP 堆栈的方式使其从其他现代浏览器中脱颖而出。
首先,我们来看看 Chrome,它发送了以下标头:
然而,在 PhantomJS 中,相同的 HTTP 请求如下所示:
你会注意到 PhantomJS 标头与 Chrome(以及所有其他现代浏览器)在一些细微的方面有所不同:
检查服务器上的这些 HTTP 标头异常,应该能够识别出 PhantomJS 浏览器。
但是,相信这些价值观是否安全? 如果对手使用代理在无头浏览器前重写标头,他们可以修改这些标头,使其看起来像普通的现代浏览器。
看来纯粹在服务器上解决这个问题并不是灵丹妙药。 那么让我们看看使用 PhantomJS 的 JavaScript 环境可以在客户端做什么。
我们可能无法信任通过 HTTP 传递的 User-Agent 值,但客户端上的情况又如何呢?
不幸的是,在 PhantomJS 中更改 user-agent 标头和 navigator.userAgent 值同样很简单,所以这可能还不够。
navigator.plugins 包含浏览器中存在的插件数组。 典型的插件值包括 Flash、ActiveX、对 Java 小程序的支持以及“默认浏览器助手”,该插件用于指示此浏览器是否是 OS X 中的默认浏览器。在我们的研究中,大多数常用浏览器的新安装都包含至少一个默认插件 — — 即使在移动设备上也是如此。
这与 PhantomJS 不同,它没有实现任何插件,也没有提供添加插件的方法(使用PhantomJS API )。
下面的检查可能会有用:
经过多次测量后,似乎如果警报对话框在 15 毫秒内被抑制,则浏览器可能不受人为控制。 但使用这种方法意味着会打扰真正的用户,他们必须手动关闭警报。
PhantomJS 1.x 在全局对象上公开两个属性:
但是,这些属性是实验性功能的一部分,将来可能会发生变化。
PhantomJS 1.x 和 2.x 目前使用过时的 WebKit 引擎,这意味着较新的浏览器中存在的浏览器功能在 PhantomJS 中不存在。 这扩展到 JavaScript 引擎——其中某些本机属性和方法在 PhantomJS 中不同或不存在。
其中一种方法是 Function.prototype.bind,它在 PhantomJS 1.x 及更早版本中缺失。 以下示例检查 bind 是否存在,以及它在执行环境中是否被欺骗。
请注意,此示例使用自定义 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