logo

如何在 2024 年绕过 PerimeterX:6 种最佳方法

2024-06-30 14:51
本文详细介绍了6 种绕过 PerimeterX反爬虫技术的方式。

你找到一个你想抓取的网站,编写了爬虫并运行后发现,PerimeterX拦截了你。你并不孤单,这是一种常见的困境!

PerimeterX(现在称为HUMAN)使用复杂的服务器和客户端技术来识别并拦截像你这样的网络爬虫。然而,通过本文介绍的方法,你可以绕过PerimeterX,获取你需要的数据

以下是一些可以帮助你突破的方法:

首先,让我们了解这个防火墙是如何工作的。

PerimeterX的工作原理

PerimeterX是2004年成立的首批提供网站安全服务的公司之一(比Cloudflare早六年)。所以在拦截机器人的方面,他们非常有经验。

PerimeterX使用被动和主动机器识别技术。被动机器识别是指在接收到访问者的请求后,在服务器端进行检查。主动机器识别是指在访问者的代理上运行脚本,收集信息以检测机器人

此外,它的检测系统声称能在最小化用户体验影响的同时保护网站不受机器人侵害。换句话说,除非怀疑请求来自机器人,否则它尽量不让人类用户解决验证码或等待认证。

PerimeterX机器识别技术

下面的列表并不详尽,但涵盖了PerimeterX部署的最激进的防御措施。我们将谈论它们是如何工作的,然后重点介绍如何克服它们。

1. IP过滤

像PerimeterX这样的安全公司通常会有大量已知被机器人使用的IP列表。它们也能识别属于数据中心、代理或VPN提供商的IP组合。Web应用防火墙(WAF)通常会给尝试访问受保护网站的每个IP分配一些分数或信誉。如果你的机器使用的IP信誉不好,你很可能会被拦截。

2. 检查HTTP请求头

许多机器人使用像python-requests或Axios这样的库或其他非浏览器代理。这些代理通常不会发送典型浏览器添加到请求中的某些头部。这是像PerimeterX Bot Defender这样的反机器人系统用来识别和拦截机器人的最简单方法之一。幸运的是,添加HTTP头以绕过这种保护很容易。

3. 行为分析

PerimeterX很自豪地使用机器学习算法进行行为分析,这使它能够基于行为识别机器人。比如,它的系统学会了IP在短时间内发出数百个请求通常是机器人。当它们检测到这种行为时,通常会拦截对受保护网页的访问。

4. 指纹识别和黑名单

我们提到的一些方法,如行为分析或检查HTTP请求头,可以与其他方法结合使用,如TLS指纹识别,以识别即使用不同IP访问的访客。一旦Web应用防火墙(WAF)将访客识别为机器人,就会将其添加到黑名单中,以防止他们在未来的访问中进入。

要了解针对被动机器识别的一些反制技巧,请查看我们的文章如何进行网络抓取而不被封锁

如果在应用了绕过被动机器防御的方法后,PerimeterX仍能检测到你,可能是它的主动机器脚本在检测你的机器。如果你准备创建一个PerimeterX验证码的绕过方案,准备好深入研究一些混淆的JavaScript代码和逆向工程策略。

为你的网络爬虫一次又一次被拦截感到沮丧?

ZenRows API为你处理轮换代理和无头浏览器。

免费试用

方法1:使用智能代理绕过PerimeterX

你可以使用智能代理来处理反机器人挑战,并返回访问所需内容所需的数据或会话cookie。

他们通过轮换住宅代理、随机化用户代理和模拟自然模式来模仿人类行为。所有这些都在幕后进行,所以你无需担心编写一长串代码。

智能代理比标准代理提供更高的匿名性。因此,它们的流量与自然的人类流量几乎无法区分。这些因素使它们成为绕过PerimeterX机器人检测系统的极好工具。

ZenRows是一个智能代理的例子,可以帮助用户绕过PerimeterX和任何机器人检测系统。你可以通过指定目标URL来模仿人类行为并解决反机器人挑战。

ZenRows支持所有编程语言,包括Python、Java、Node.js、Go、Ruby等。以下是一个使用Python的简短示例:

scraper.py

# pip install requests
import requests

url = 'https://www.ssense.com/en-ca'
apikey = '<YOUR_ZENROWS_API_KEY>'
params = {
    'url': url,
    'apikey': apikey,
	'js_render': 'true',
	'premium_proxy': 'true',
}
response = requests.get('https://api.zenrows.com/v1/', params=params)

print(response.text)
# ....<title>奢侈时尚与独立设计师 | SSENSE Canada</title>...

方法2:使用增强型无头浏览器

虽然无头浏览器最初是为测试设计的,但它们已经发展成了重要的网络抓取工具。然而,它们具备的自动化特性使得它们容易被像PerimeterX这样的反机器人系统识别。一个常见的特性是navigator.webdriver属性。

尽管如此,最流行的无头浏览器,如Selenium、Puppeteer和Playwright,均有解决方案可以增强你的网络爬虫:

这些工具多年来都被证明很有用,但它们是开源的。这意味着它们可能无法跟上不断发展的像PerimeterX这样的机器人管理系统。

即使它们能跟上发展,仍然有一些缺点。比如,它们消耗大量的CPU、内存和带宽资源。因此,无头浏览器不可避免地导致抓取成本和性能问题。

尽管你可以采取措施优化性能,如阻止资源加载,但不加载这些资源也可能让你被标记为机器人。

方法3:使用API绕过PerimeterX

此时,你可能在想:“难道没有一个可靠的现成的PerimeterX绕过方案吗?”。

2024年,使用公共软件(如GitHub上找到的库)绕过PerimeterX反机器人服务变得困难。然而,其中一些库(如Puppeteer Stealth)仍值得一试。

此外,基于Chrome、Chromium、Firefox或Selenium的标准无头浏览器需要非常具体的配置才能工作。由于这类软件的源代码是公开的,PerimeterX的开发者可以更新他们的反机器人系统来检测它。

一种选择是编写你自己的PerimeterX绕过方案,尽管最简单的方法是使用专门设计的私有软件。例如,可靠的例子之一是ZenRows。

我们的编程团队投入了无数小时开发一个用于网络抓取的API。它能有效避免反机器人系统,如PerimeterX,你可以立即获取免费的API密钥

方法4:绕过PerimeterX CAPTCHA

作为访问网站内容所需通过的挑战的一部分,PerimeterX可能会显示CAPTCHA。有时它们仅在检测到可疑活动(如过多请求)时呈现。这给你两个绕过它们的方法:

  • 防止CAPTCHA触发。
  • 在出现时解决它们。

第一个方法是推荐的方法,因为它在大规模操作时更可靠且成本更低。增强型无头浏览器、智能代理等解决方案可以帮助你避免被检测。

另一方面,当强制出现CAPTCHA时,你必须解决它们。这只能通过付费的CAPTCHA解决服务,如2Captcha来实现。他们雇佣真人手动解决挑战并通过服务的API端点返回解决方案。

方法5:抓取Google缓存

当Google为索引抓取网站时,它会缓存这些页面。因此,我们可以访问目标网站并直接请求这些缓存页面。然而,这种方法仅在你需要的数据不经常变化时才可行。此外,并非所有网站都允许缓存,这种方法可能无法奏效。

要抓取网站的缓存数据,向其Google缓存URL发送请求。通常格式如下:

https://webcache.googleusercontent.com/search?q=cache:{website_url}

方法6:反向工程PerimeterX以绕过它

一种绕过PerimeterX Bot Defender的方法是反向工程其检查和挑战。步骤如下:

  1. 分析网络日志。
  2. 反混淆PerimeterX的JavaScript挑战脚本。
  3. 分析反混淆的脚本及其后续检查。

如何创建PerimeterX绕过

了解防火墙的内部结构是反向工程的关键。我们主要使用JavaScript,但本教程中的技术也允许你使用Python或任何其他编程语言编写你的PerimeterX绕过方案。

在我们的示例中,我们将分析SSENSE上的反机器人实现。该网站是一个好例子,因为许多电子商务网站使用PerimeterX。

准备好了吗?

步骤1:分析网络日志

首先,打开你的选择的Web浏览器的开发者工具并切换到“网络”标签。

接下来,保持开发者工具打开并导航到SSENSE。页面加载时,你会注意到网络日志中出现了许多请求。按时间顺序记录下来的重要请求如下:

初始GET请求到 https://www.ssense.com/en-ca。查看响应,你会看到一个Set-Cookie头。这是一个重要的cookie:它作为会话指示符,也将在未来的请求中使用。你的PerimeterX绕过方案需要从这个cookie中获取一些数据,以计算将发送到服务器进行验证的正确值。

[GET请求

还要检查响应主体的HTML是否包含一个<script>标签,该标签提取PerimeterX挑战脚本:

输出

&#x3C;script type="text/javascript"> 
	(function () { 
		window._pxAppId = "PX58Asv359"; 
		if (window._pxAppId) { 
			var p = document.getElementsByTagName("script")[0], 
				s = document.createElement("script"); 
			s.async = 1; 
			s.src = "/" + window._pxAppId.substring(2) + "/init.js"; 
			p.parentNode.insertBefore(s, p); 
		} 
	})(); 
&#x3C;/script>

一个到 /<_pxAppId>/init.jsGET请求(其中<_pxAppId>window._pxAppId的值)。这会返回PerimeterX用于客户端机器人检测的脚本。它是混淆和缩小的,所以现在你可能看不懂。

初始请求

接下来是一个向 /<_pxAppId>/xhr/api/v2/collectorPOST请求。请求负载是一个具有application/x-www-form-urlencoded内容类型的字符串,包含以下数据:

  • <payload>是一个加密并Base64编码的字符串。
  • <appId>是之前定义的window._pxAppId的值。
  • <tag>是一个版本标记(每个站点固定),例如v8.0.2
  • <uuid>是一个随机生成的UUID,例如4420aff0-351d-11ed-95d0-c137f4896ca9
  • <ft>是一个整数(每个站点固定),例如278
  • <seq>的值为0
  • <en>的值为NTA
  • <pc>是一个整数,例如3195683956001701
  • <pxhd>_pxhdcookie的值。
  • <rsc>的值为1

[收集器请求

响应主体是一个包含单个顶级字段的JSON对象:do。这个字段包含一个字符串数组。格式如下:

输出

{ 
	"do": [ 
		"sid|&#x3C;sid>", // 一个字符串,例如4415dfc2-351d-11ed-a66d-7275714f5843 
		"pnf|cu", 
		"cls|&#x3C;cls>", // 一个整数,例如85062563435994268828 
		"sts|&#x3C;sts>", // 是一个UNIX时间戳,例如1663263533114 
		"wcs|&#x3C;wcs>", // 一个字符串,例如cchm6ba3onsi8miotj00 
		"drc|&#x3C;drc>", // 一个整数,例如4460 
		"cts|&#x3C;cts>|true", // 一个字符串,例如4415e33e-351d-11ed-a66d-7275714f584 
		"cs|&#x3C;cs>", // 一个SHA2-256哈希,例如dd2d5dc601445d684b2c4249a4c68f300048446afd4fece93c44ae41f62bdda3 
		"vid|&#x3C;vid1>|&#x3C;vid2>|true", // 一个字符串和一个整数,例如43c15b2f-351d-11ed-97ec-797549415148和31536000 
		"sff|cc|60|&#x3C;sff>" // 一个Base64编码的字符串,例如U2FtZVNpdGU9TGF4Ow== 
	] 
}

然后是一个向 /<_pxAppId>/xhr/api/v2/collector 的第二个POST请求。有效负载具有与之前相同的内容类型和类似的格式,但有些字段有所增加:

  • <payload>是一个更长的加密+Base64编码的字符串。
  • <appId><tag><uuid><ft><pxhd>与之前的请求相同。
  • <seq>的值为1
  • <en>的值为NTA
  • <cs>是一个SHA2-256哈希,例如dd2d5dc601445d684b2c4249a4c68f300048446afd4fece93c44ae41f62bdda3
  • <pc>是一个整数,例如1670315818019117
  • <sid>是一个字符串,例如4415dfc2-351d-11ed-a66d-7275714f5843
  • <vid>是一个字符串,例如43c15b2f-351d-11ed-97ec-797549415148
  • <cts>是一个字符串,例如4415e33e-351d-11ed-a66d-7275714f5843
  • <rsc>的值为2

仔细观察,你会发现cssidvidcts字段直接来源于第一次POST请求返回的JSON对象。

此外,seqrsc的值相对于第一次POST请求增加了1。这种行为在所有后续POST请求中保持一致,所以我们可以确定这些字段充当某种请求计数器。

PerimeterX在响应主体中再次发送了一个JSON对象,包含一个字符串数组:

输出

{ 
	"do": ["bake|_px2|330|&#x3C;jwt>|true|300", "pnf|cu"] 
	 // 其中jwt是一个JWT Token,例如eyJ1IjoiNDQyMGFmZjAtMzUxZC0xMWVkLTk1ZDAtYzEzN2Y0ODk2Y2E5IiwidiI6IjQzYzE1YjJmLTM1MWQtMTFlZC05N2VjLTc5NzU0OTQxNTE0OCIsInQiOjE2NjMyNjM4MzQxMjIsImgiOiIwNzUzZDJhYTU1OWEzZDFhYjM5YjcyOGFmZDA0MDUyYWFlNDQ2MmU1NjMxNjZkNjM4MjM0NjZkNmNjMzIwY2ZlIn0= 
}

您可能已经注意到,所有的POST请求中都没有Set-Cookie响应头。通常情况下,一旦浏览器通过了机器人检测检查,反机器人系统会设置特殊的cookie或头信息,用于未来的请求。当客户端向受保护的端点发出请求时,服务器端会验证这些请求中的头信息或cookie。

那么,PerimeterX是如何工作的呢?如果您向PerimeterX保护的端点发送请求,不会看到任何异常的头信息。然而,您会注意到一些与PerimeterX相关的cookie。为了获得更清晰的概览,您可以查看站点上的所有cookie,并使用关键字“:px”进行筛选。

这些是PerimeterX的验证cookie。服务器端会检查这些cookie,以确定请求是否应被阻止或转发至源站。但是,请记住,在网络日志中没有这些cookie被设置的记录。那么,它们是从哪里来的呢?Set-Cookie

您可能会在POST请求的响应正文中识别出这些cookie的名称和值。这意味着这些cookie是通过JavaScript直接设置的,这在所有PerimeterX cookie都缺少Http-Only标志的情况下是合理的。

注意

根据PerimeterX保护网站的安全级别、您的浏览器和设备,挑战脚本及其请求的行为可能会略有不同。在撰写本文时,SSENSE只需要上述两个POST请求。第二个POST请求产生一个_px2 cookie,这是主要的验证cookie,赋予网站无阻访问权限。

安全级别更高的网站可能需要额外的POST请求到/<_pxAppId>/xhr/api/v2/collector,以获取_px3 cookie。对于这些网站,_px3是必需的验证cookie。但是,不要担心,我们讨论的技巧在应对高安全级别的PerimeterX网站时也很有用。

太好了,通过分析请求,我们了解了很多PerimeterX的行为。不幸的是,我们仍然缺少很多信息。我们仍然不知道加密的负载字段中包含哪些数据,某些字段是如何生成的,以及脚本进行的客户端机器人检测检查。如果您想绕过PerimeterX,这些知识是至关重要的。

如果我们想回答这些剩下的问题,只能直接查看PerimeterX挑战脚本,弄清它的具体工作原理。

步骤2:反混淆PerimeterX JavaScript挑战

为了使脚本难以被逆向工程,PerimeterX对其JavaScript挑战进行了混淆。以下是不完全列举的一些示例:

  • 字符串隐藏:此技术将所有字符串字面值的引用替换为对解码函数的调用。在PerimeterX的情况下,字符串要么被Base64编码,要么用简单的XOR密码进一步加密。

示例

// PerimeterX脚本中的字符串隐藏示例

// 创建一个空的查找缓存用于解码函数
var o = Object.create(null);

/* ... */

// XOR解密函数
// 返回解码后的字符串
// 该函数引用了一些外部变量和函数。
// n()和r()函数与记录时间戳有关,与解码函数无关。
// i()函数是atob(base64解码)的polyfill函数
// 变量o在脚本中之前定义为一个缓存。

function c(t) {
    // n()与解码无关
    var a = n(),
        e = o[t];
    
    if (e) u = e; // 尝试在缓存中查找解码字符串
    else {
        // i()是一个atob的polyfill函数
        // Base64解码输入字符串
        var c = i(t);

        var u = "";
        // XOR解密
        for (var f = 0; f &#x3C; c.length; ++f) {
            var A = "dDqXfru".charCodeAt(f % 7);
            u += String.fromCharCode(A ^ c.charCodeAt(f));
        }
        // 将结果存储在缓存中
        o[t] = u;
    }
    return r(a), u; // r(a)与解码无关。
}

/* ... */

// 在脚本中稍后使用:
c("NBxAaVZGQg"); // => "PX11047"
  • 代理变量/函数:此技术通过中间变量替换对变量/函数标识符的直接引用。

示例

// 代理函数示例

// 上面的解码函数
function c(t) {
    /* ... */
}

// 中间变量声明
var r = c;

// 直接调用r()而不是c()
r("NBxAaVZERw"); // => "PX11062"

// 代理变量示例

// "window"标识符的中间变量
var F = window;

// 直接引用"F"而不是"window"
F.performance.now();
  • 单一表达式:而不是直接使用布尔字面值或undefined关键字,此技术利用JavaScript单一表达式实现的自动类型转换行为。

示例

var o = !0; // 等同于 o = true
var c = !1; // 等同于 c = false
void 0 === this.channels; // 等同于 undefined === this.channels

虽然PerimeterX挑战脚本的混淆可能没有其他机器人检测供应商的复杂,但它仍需要专业的逆向工程技巧将其转化为可读状态。仅仅将其粘贴到通用的JavaScript反混淆器中并不能生成易于理解的代码。

要反混淆PerimeterX脚本,您需要创建一个自定义反混淆器。这一步可能很困难,但对于创建绕过PerimeterX的技术是至关重要的!

提示

尝试使用抽象语法树(AST)操作。

一旦您反混淆了PerimeterX挑战脚本,您可以阅读它以确定执行了哪些机器人检测检查以及如何复制挑战解答行为。在下一步中,我们将仔细检查反混淆后的脚本,并尝试提取其内部的重要信息。

步骤3:分析反混淆后的PerimeterX脚本

让我们首先找出负载是如何加密的!

PerimeterX的负载加密

为了弄清楚负载是如何加密的,我们将逆向工程。首先,通过搜索反混淆脚本中的字符串“payload=”,找出其设置位置。

示例

// 负载的最终值存储在变量N中。查看N的定义,可以确定Wc函数负责负载加密。Wc接受两个参数:n和B。

var B = {
    vid: cn,
    tag: ff.Bn,
    appID: ff.J,
    cu: Uo,
    cs: f,
    pc: A,
};
var N = Wc(n, B);
var l = [
    "payload=" + N,
    "appId=" + ff.J,
    "tag=" + ff.Bn,
    "uuid=" + Uo,
    "ft=" + ff.Nn,
    "seq=" + Uu++,
    "en=NTA",
];

为了得到的值,我们可以确定Wc函数负责负载加密。Wc接受两个参数:`payloadNNWcWc

  • n:存储原始负载数据的JavaScript对象。
  • B:存储加密过程中使用的一些值的JavaScript对象。

让我们查找定义:

示例

var Wc = function (n, r) {
    var t;
    var a = n.slice();
    t = nc || "1604064986000";
    var e = zr(Un(t), 10);
    var i = z(a);
    a = Un(zr(i, 50));

    var c = (function (n, r, t) {
        var a, e, i, o, c;
        var u = zr(Un(t), 10);
        var f = [];
        var A = -1;

        for (var B = 0; B &#x3C; n.length; B++) {
            /* ... */
        }

        for (var v = 0; n.length > v; v++) {
            /* ... */
        }

        return f.sort(function (n, r) {
            return n - r;
        });
    })(e, a.length, r[Hc]);

    a = (function (n, r, t) {
        /* ... */
        return (a += r.substring(e));
    })(e, a, c);

    return a;
};

突出这是PerimeterX的加密算法。原始函数非常长,引用了许多外部变量/函数。为了实用,我们进行了截断。

不过,您可以通过查看完全反混淆的PerimeterX脚本,学到一些关于这个加密算法的关键点:

  • 负载使用了两个加密密钥:uuid和sts的值。
  • uuid出现在每个POST请求中,而sts出现在第二个POST请求及之后的请求中。如果在第一个POST请求中缺少sts,将使用默认值“1604064986000”。
  • 这是对称密钥算法。因此,只要您拥有原始的sts和uuid值,您就可以解密任何PerimeterX的加密负载。这对于分析浏览器发送的负载很有用,因为密钥总是与加密内容一起发送在POST请求中。

PerimeterX如何设置Cookie

我们之前得出结论,所有与PerimeterX相关的cookie都是由实际的脚本本身设置的。回想一下,_px2 cookie的原始值首次出现在JSON格式的响应体中(如:)。

输出

{
    "do": ["bake|_px2|330||true|300", "pnf|cu"]
}

字段名称do实际上非常直观。对应的值实际上是一个指令数组。每个字符串都会在“|”处拆分为一个数组。对于do数组中的第一个字符串,结果如下所示:

示例

// 第一个指令
var processedInstruction1 = "bake|_px2|330||true|300".split("|"); // => ["bake","_px2","330","","true","300"]

突出结果数组的第一个元素确定要执行的函数,而剩下的元素作为函数的参数。在这里,bake是要执行的函数名。

搜索反混淆后的PerimeterX脚本中的bake,我们发现了cu对象。这个cu对象包含bake指令的处理函数:

示例

var cu = {
    /**
     * @param n = "_px2"
     * @param r = "330"
     * @param t = ""
     * @param a = "true"
     * @param e = "300"
     */
    bake: function (n, r, t, a, e) {
        if (ff.J === window._pxAppId) {
            wt(n, r, t, a);
        }
        /* ... */
    },
    /* ... */
};

斜体参数n、r、t、a和e分别采用"_px2"、"330"、""、"true"和"300"的值。

bake方法调用了一个函数wt。我们也一起查找它的定义:

示例

/**
 * @param n = "_px2"
 * @param r = "330"
 * @param t = ""
 * @param a = "true"
 */

function wt(n, r, t, a) {
    /* ... */

    try {
        var i;
        // 根据“r”参数创建cookie的过期日期。
        if (r !== null) {
            i = new Date(+new Date() + 1000 * r).toUTCString().replace(/GMT$/, "UTC");
        }
        // 初始化_px2 cookie字符串
        var o = n + "=" + t + "; expires=" + i + "; path=/";
        var c = (a === true || a === "true") &#x26;&#x26; bt();
        // 将站点域名附加到cookie,并将cookie添加到document.cookie
        c &#x26;&#x26; (o = o + "; domain=" + c)((document.cookie = o + "; " + e));
        return true;
    } catch (n) {
        return false;
    }
}

看起来bake指令直接设置了_px2 cookie!同时,这也是玩文字游戏,因为制作cookie的过程也称为“bake”。

祝贺您!您找到了他们的主要反机器人cookie在代码中的设置位置!下一步将是计算对PerimeterX有意义的值,使您的机器人不会被标记为可疑。

您还应该注意到,对象cu包含了其他所有可能的do指令的处理函数!要创建PerimeterX绕过程序,您需要逆向工程每个do指令的功能。

让我们学习如何破解这些do指令数组中的安全检查。

WebGL指纹识别

在下面的代码片段中,PerimeterX使用WebGL API创建并渲染图像。然后,将图像的哈希值存储在canvasfp中:

示例

// 这个函数创建、渲染并哈希图像来构建"canvasfp"。
function A() {
    return new T(function (c) {
        setTimeout(function () {
            try {
                a = n.createBuffer();
                n.bindBuffer(n.ARRAY_BUFFER, a);
                var u = new Float32Array([
                    -0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.732134444, 0,
                ]);
                n.bufferData(n.ARRAY_BUFFER, u, n.STATIC_DRAW)((a.itemSize = 3))(
                    (a.numItems = 3)
                )((e = n.createProgram()))((i = n.createShader(n.VERTEX_SHADER)));
                n.shaderSource(
                    i,
                    "attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}"
                );
                /* 对画布图像进行一些进一步的转换... */
                /* ... */
                n.drawArrays(
                    n.TRIANGLE_STRIP,
                    0,
                    a.numItems
                )(
                    (r.canvasfp = n.canvas === null ? "no_fp" : In(n.canvas.toDataURL())) // In()计算生成图像的哈希值
                )((r.extensions = n.getSupportedExtensions() || ["no_fp"]));
            } catch (n) {
                r.errors.push("PX10703");
            }
            /* ... */
        }, 1);
    });
}

这对于指纹识别很有用,因为即使被要求绘制完全相同的图像,硬件或底层软件(如操作系统)的微小差异也会产生不同的输出(从而产生不同的哈希值)。这使得WebGL指纹识别成为分类设备的一种好方法。

PerimeterX还会收集各种其他WebGL属性,以更好地分类您的设备。利用机器学习,他们可以使用这些数据检测您是否在伪造WebGL属性/渲染。

计算出的canvasfp及其他WebGL属性将追加到下方示例代码中的负载对象中:

示例

// 将收集到的WebGL数据添加到POST请求负载中
(function (t) {
    (a.PX10061 = t.canvasfp)((a.PX11016 = t.webglVendor))((a.PX10529 = t.errors))(
        (a.PX10279 = t.webglRenderer)
    )((a.PX10753 = t.webGLVersion))((a.PX10246 = t.extensions))(
        (a.PX11232 = In(t.extensions))
    )((a.PX10871 = t.webglParameters))((a.PX11231 = In(t.webglParameters)))(
        (a.PX11077 = t.unmaskedVendor)
    )((a.PX10165 = t.unmaskedRenderer))((a.PX10244 = t.shadingLangulageVersion));
    tt("PX11223");
    r(a);
});

突出

自动化浏览器检查

下面,PerimeterX检查自动化浏览器特有属性的存在:

示例

try {
    (n.PX10010 = !!window.emit)((n.PX10225 = !!window.spawn))(
        (n.PX10855 = !!window.fmget_targets)
    )((n.PX11065 = !!window.awesomium))((n.PX10456 = !!window.__nightmare))(
        (n.PX10441 = Xr(window.RunPerfTest))
    )((n.PX10098 = !!window.geb))((n.PX10557 = !!window._Selenium_IDE_Recorder))(
        (n.PX10170 = !!window._phantom || !!window.callPhantom)
    )((n.PX10824 = !!document.__webdriver_script_fn))(
        (n.PX10087 = !!window.domAutomation || !!window.domAutomationController)
    )(
        (n.PX11042 =
            window.hasOwnProperty("webdriver") ||
            !!window["webdriver"] ||
            document.getElementsByTagName("html")[0].getAttribute("webdriver") ===
                "true")
    );
} catch (n) {}

沙箱检查

PerimeterX通过检查NodeJS特有API的存在来判断脚本是否在沙箱中运行:

示例

// 进程对象仅存在于NodeJS中。
try {
    n =
        n ||
        ((typeof process == "undefined" ? "undefined" : A(process)) === "object" &#x26;&#x26;
            String(process) === "[object process]");
} catch (n) {}

try {
    n = n || /node|io\.js/.test(process.release.name) === true;
} catch (n) {}

为了确保内置函数没有被修改(即猴子补丁),PerimeterX调用typeof和一个隐含的toString:

示例

// A() acts as a wrapper for "typeof" 
 
function A(n) { 
	A = 
		typeof Symbol == "function" &#x26;&#x26; typeof Symbol.iterator == "symbol" 
			? function (n) { 
					return typeof n; 
				} 
			: function (n) { 
					return n &#x26;&#x26; 
						typeof Symbol == "function" &#x26;&#x26; 
						n.constructor === Symbol &#x26;&#x26; 
						n !== Symbol.prototype 
						? "symbol" 
						: typeof n; 
				}; 
	return A(n); 
} 
// 
function Xr(n) { 
	// When typeof is called on an unmodified built-in function, it will return "function". 
	// "" + n is an implicit toString() 
	// An unmodified built-in function will always include "[native code]" in the result. 
	return A(n) === "function" &#x26;&#x26; /\{\s*\[native code\]\s*\}/.test("" + n); 
} 
 
/* ... */ 
 
// Later used like this: 
 
n.PX10213 = Xr(window.EventSource); 
n.PX10283 = Xr(Function.prototype.bind); 
n.PX10116 = Xr(window.setInterval); 
 
// If they haven't been modified, all the above calls should return true.

用户输入事件追踪

PerimeterX 收集行为生物数据,如鼠标移动、键盘按键和触控移动。通过机器学习对收集的数据进行分析,可以判断输入是人类行为还是由机器人生成的。

以下示例中,PerimeterX 追踪触控事件的时间和位置:

{ 
	(function (n, r) { 
		_i.length &#x3C; 10 &#x26;&#x26; 
			_i.push( 
				+n.movementX.toFixed(2) + "," + +n.movementY.toFixed(2) + "," + wr(r) 
			); 
 
		if (n &#x26;&#x26; n.movementX &#x26;&#x26; n.movementY) { 
			if (Pi.length &#x3C; 50) { 
				Pi.push( 
					(function (n) { 
						var r = n.touches || n.changedTouches; 
						var t = r &#x26;&#x26; r[0]; 
						var a = +(t ? t.clientX : n.clientX).toFixed(0); 
						var e = +(t ? t.clientY : n.clientY).toFixed(0); 
 
						var i = (function (n) { 
							return +(n.timestamp || n.timeStamp || 0).toFixed(0); 
						})(n); 
 
						return "".concat(a, ",").concat(e, ",").concat(i); 
					})(n) 
				); 
			} 
		} 
	})(n, t); 
}

总结

正如你所见,有多种方法可以绕过 PerimeterX 的机器人检测系统,但它们的可靠性各有不同。虽然逆向工程 PerimeterX 的 JavaScript 挑战是其中一条路径,但根据混淆的程度,这可能会变得相当繁琐。

请记住,PerimeterX 经常更新它的挑战机制,因此如果你选择使用反混淆的方法,必须不断检查你的爬虫代码以避免被检测到。

导航目录