如何绕过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 代码和 逆向工程 策略。

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

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

他们旨在通过旋转住宅代理、随机化用户代理和模拟自然模式来模仿类人行为。这一切都在幕后,因此您不必担心编写长行代码。

与标准代理相比,智能代理提供更高级别的匿名性。因此,他们的流量与自然人流量几乎没有区别。除其他因素外,这使它们成为绕过 PerimeterX 机器人检测系统的绝佳工具。

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

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

# pip install requests
import requests

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


print(response.text)
# ....<title>Luxury fashion & independent designers | SSENSE Canada</title>...

方法#2:使用强化的无头浏览器

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

话虽这么说,最流行的无头浏览器,如 Selenium、Puppeteer 和 Playwright,都带有使您能够强化网络抓取工具的解决方案:

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

但是,即使他们可以,仍然存在缺点。例如,它们消耗大量的 CPU、内存和带宽资源。因此,无头浏览将不可避免地导致抓取成本和性能问题。

尽管您可以采取阻止资源等措施来提高性能,但不加载它们也可能会将您标记为机器人。

方法 #3:使用 API 绕过 PerimeterX

此时,您可能会想,“难道就没有可靠的现有 PerimeterX 绕过方法吗?”。

严酷的现实是,到 2023 年,很难使用公共软件(例如您可以在 GitHub 上找到的库)绕过 PerimeterX 反机器人服务。但是,其中一些值得一试,例如Puppeteer Stealth 。

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

一种选择是编写您自己的 PerimeterX 绕过代码,尽管最简单的方法是使用专为这项工作设计的私有软件。

方法#4:PerimeterX 验证码绕过

PerimeterX 可能会显示验证码作为访问网站内容必须通过的挑战的一部分。有时它们仅在检测到可疑活动(例如请求过多)时才会显示。这为您提供了两种绕过它们的方法:

  • 防止验证码被触发。
  • 当它们出现时解决它们。

第一种是推荐的方法,因为它在规模上更可靠并且便宜得多。强化无头浏览器、智能代理等解决方案可以帮助您低调行事。

另一方面,当出现验证码时,您必须解决它们。这只有通过像 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。

第一步:分析网络日志

首先,打开您选择的网络浏览器的开发人员工具,然后切换到“网络”选项卡。接下来,让开发者工具保持打开状态并导航至SSENSE。当页面加载时,您会注意到许多请求出现在网络日志中。需要注意的重要事项(按时间顺序排列)如下:加载页面时,您会注意到网络日志中出现许多请求。这些是需要注意的重要事项,按时间顺序排列:

GET 对 的 初始 请求https://www.ssense.com/en-ca。查看响应,您会看到 Set-Cookie 的标头 _pxhd。这是一个重要的 cookie:它充当会话指示器,也将在未来的请求中使用。您的 PerimeterX 旁路将需要来自此 cookie 的一些数据来计算将发送给服务器进行验证的正确值。

medium_perimeterx_bypass_get_request_c909b809a2

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

<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); 
        } 
    })(); 
</script>

/<_pxAppId>/init.js 对( <_pxAppId> 的值在 哪里)的 GET 请求 window._pxAppId。这将返回 PerimeterX 用于客户端机器人检测的脚本。它被混淆和缩小了,所以你现在不能理解太多。 单击此处查看整个脚本

medium_perimeterx_bypass_initial_request_6c808689f9

然后,发生 POST 请求 /<_pxAppId>/xhr/api/v2/collector 。请求负载是一个 content-type 的字符串 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>是 cookie 的值 _pxhd 。
  • <rsc> 有价值 1

medium_perimeterx_bypass_collector_1440_cfca278080

响应正文是一个具有单个顶级字段的 JSON 对象:do. 该do字段包含一个字符串数组。格式如下:

{ 
    "do": [ 
        "sid|<sid>", // a string, ex. 4415dfc2-351d-11ed-a66d-7275714f5843 
        "pnf|cu", 
        "cls|<cls>", // an integer, ex. 85062563435994268828 
        "sts|<sts>", // is a UNIX timestamp, ex. 1663263533114 
        "wcs|<wcs>", // a string, ex. cchm6ba3onsi8miotj00 
        "drc|<drc>", // an integer, ex. 4460 
        "cts|<cts>|true", // a string, ex. 4415e33e-351d-11ed-a66d-7275714f584 
        "cs|<cs>", // a SHA2-256 hash, ex. dd2d5dc601445d684b2c4249a4c68f300048446afd4fece93c44ae41f62bdda3 
        "vid|<vid1>|<vid2>|true", // a string and an integer, ex. 43c15b2f-351d-11ed-97ec-797549415148 and 31536000 
        "sff|cc|60|<sff>" // a base64-encoded string, ex. U2FtZVNpdGU9TGF4Ow== 
    ] 
}

和第二个 POST 请求 /<_pxAppId>/xhr/api/v2/collector。有效载荷具有与以前相同的内容类型和类似的格式,并添加了一些字段:

  • <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

如果仔细观察,您会发现 cs、 sid和  字段直接派生自第一个  请求返回的 JSON 对象vid 。 ctsPOST

 此外,相对于第一个请求,seq 和 的值 rsc 增加了 1  。POST此行为也适用于所有后续 POST 请求,因此我们可以确定这些字段充当某种 请求计数器

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

{ 
    "do": ["bake|_px2|330|<jwt>|true|300", "pnf|cu"] 
     // where jwt is a JWT Token, ex. eyJ1IjoiNDQyMGFmZjAtMzUxZC0xMWVkLTk1ZDAtYzEzN2Y0ODk2Y2E5IiwidiI6IjQzYzE1YjJmLTM1MWQtMTFlZC05N2VjLTc5NzU0OTQxNTE0OCIsInQiOjE2NjMyNjM4MzQxMjIsImgiOiIwNzUzZDJhYTU1OWEzZDFhYjM5YjcyOGFmZDA0MDUyYWFlNDQ2MmU1NjMxNjZkNjM4MjM0NjZkNmNjMzIwY2ZlIn0= 
}

您可能已经注意到, 没有一个 POST 请求包含 Set-Cookie 响应标头。通常,一旦浏览器通过了机器人检测检查,反机器人系统就会设置特殊的 cookie 或标头以供将来请求使用。然后,一旦客户端向受保护的端点发出请求,请求中的那些标头/cookie 就会在服务器端得到验证。

那么,这在 PerimeterX 的情况下是如何工作的?如果您向受 PerimeterX 保护的端点发出请求,您将不会看到任何异常标头。但是,您会注意到似乎是一些与 PerimeterX 相关的 cookie。为了获得更清晰的概览,您可以查看网站上的所有 cookie 并按关键字过滤 px

medium_perimeterx_bypass_cookies_ac23273b4b

这些是 PerimeterX 的 清关 cookie。在服务器端检查它们以确定是否应阻止请求或将请求转发到源。Set-Cookie 但请记住,在网络日志中没有记录这些 cookie 是通过标头设置的 。那么,他们来自哪里?

您可能会从请求的响应正文中识别出 cookie 名称和值 POST 。这一定意味着 cookie 是直接通过 JavaScript 设置的,考虑到所有 PerimeterX cookie 都缺少 Http-Only 标志,这是有道理的。

好的,干得好!通过分析请求,我们了解了很多有关 PerimeterX 行为方式的信息。不幸的是,我们仍然缺少很多信息。 我们仍然不知道加密 payload 字段中包含什么数据,其他一些字段是如何生成的,以及客户端机器人检测检查脚本执行的内容。如果你想绕过 PerimeterX,这些知识是至关重要的。

如果我们想回答那些剩下的问题,我们别无选择,只能直接参考 PerimeterX 挑战脚本来弄清楚 它是如何工作的

第 2 步:反混淆 PerimeterX JavaScript 挑战

为了使逆向工程师无法读取脚本,PerimeterX 对他们的 Javascript 挑战进行了混淆处理。以下是一些示例的非详尽列表:

字符串隐藏。此技术用对解码器函数的调用替换了对字符串文字的所有引用。在 PerimeterX 的情况下,字符串要么经过 Base64 编码,要么使用 简单的 XOR 密码进行额外加密。

// String concealing example from the PerimeterX script 
 
// Creates an empty lookup cache for use in the decoding function 
var o = Object.create(null); 
 
/* ... */ 
 
// XOR Decryptor function 
// Returns the decoded string. 
// This function references some external variables and functions. 
// The n() and r() functions are related to recording timestamps, and are irrelevant to the decoding function. 
// The i() function is a polyfill function for atob (base64 decoding) 
// The o variable is defined earlier in the script as a cache. 
 
function c(t) { 
    // n() is irrelevant to the decoding 
    var a = n(), 
        e = o[t]; 
    if (e) u = e; // Try to look up the decoding string in the cache 
    else { 
        // i() is a polyfill function for atob 
        // Base64 decodes the input string 
        var c = i(t); 
 
        var u = ""; 
        // XOR decryption 
        for (var f = 0; f < c.length; ++f) { 
            var A = "dDqXfru".charCodeAt(f % 7); 
            u += String.fromCharCode(A ^ c.charCodeAt(f)); 
        } 
        // Store the result in the cache 
        o[t] = u; 
    } 
    return r(a), u; // r(a) is irrelevant to the decoding. 
} 
 
/* ... */ 
 
// Later on in the script, it's used like this: 
 
c("NBxAaVZGQg"); // => "PX11047"

代理变量/函数。该技术用中间变量替换了对变量/函数标识符的直接引用。

/* Proxy function example */ 
 
// Decoding function from above 
function c(t) { 
    /* ... */ 
} 
 
// Intermediate variable declaration 
var r = c; 
 
// Calling r() instead of c() directly 
r("NBxAaVZERw"); // => "PX11062" 
 
/* Proxy variable example */ 
 
// Intermediate variable for the identifier "window" 
var F = window; 
 
// Referencing "F" instead of "window" directly 
F.performance.now();

一元表达式。这种技术不是直接使用布尔文字或 undefined 关键字,而是利用了 JavaScript 的一元表达式实现的自动类型转换行为。

var o = !0; // equivalent to o = true 
var c = !1; // equivalent to c = false 
void 0 === this.channels; // equivalent to undefined === this.channels

尽管 PerimeterX 挑战脚本的混淆可能不像其他机器人检测供应商那样复杂,但它仍然需要专门的逆向工程技能才能将其转换为可读状态。简单地将它粘贴到通用的 JavaScript 反混淆器中不会产生易于理解的代码。

要对 PerimeterX 脚本进行去混淆处理,您需要创建自定义去混淆器。这一步可能很困难,但 它对于创建 PerimeterX 旁路至关重要

对 PerimeterX 挑战脚本进行去混淆处理后,您可以阅读它以确定 执行了哪些机器人检测检查以及 如何复制挑战解决行为。在下一步中,我们将检查去混淆的脚本并尝试提取有关其内部结构的关键信息。

第 3 步:分析去混淆后的 PerimeterX 脚本

让我们首先弄清楚有效负载是如何加密的!

PerimeterX 的有效载荷加密

为了弄清楚有效负载是如何加密的,这样我们就可以编写我们的自定义 PerimeterX 绕过代码, 我们将向后工作"payload=" 首先,我们通过在去混淆脚本中搜索字符串来找到它的设置位置 :

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", 
];

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

  • n:存储原始负载数据的 JavaScript 对象。
  • B:一个 JavaScript 对象,它存储一些在加密过程中用作密钥的值。

让我们看看 的定义 Wc

var B = { 
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 < 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的payload。这对于分析浏览器发送的有效负载很有用,因为密钥始终 POST 与加密内容一起在请求中发送。

PerimeterX 如何设置 Cookie

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

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

字段名称 , do实际上是非常直白的。对应的值 do 其实是一个指令数组。每个字符串都被拆 | 分成一个数组。对于数组中的第一个字符串 do ,它看起来像这样:

// The first instruction 
var processedInstruction1 = "bake|_px2|330||true|300".split("|"); // => ["bake","_px2","330","","true","300"]

结果数组的第一个元素决定了要执行的函数,而其余元素作为函数的参数。在这种情况下, bake 是要执行的函数的名称。

bake 在去混淆的 PerimeterX 脚本中 搜索 ,我们发现了这个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""<jwt>""true""300"

该 bake 方法调用一个函数,  wt. 让我们也看看它的定义:

/** 
 * @param n = "_px2" 
 * @param r = "330" 
 * @param t = "" 
 * @param a = "true" 
 */ 
 
function wt(n, r, t, a) { 
    /* ...*/ 
 
    try { 
        var i; 
        // Creates the expiry date of the cookie, based on the "r" parameter. 
        if (r !== null) { 
            i = new Date(+new Date() + 1000 * r).toUTCString().replace(/GMT$/, "UTC"); 
        } 
        // Initialize the _px2 cookie string 
        var o = n + "=" + t + "; expires=" + i + "; path=/"; 
        var c = (a === true || a === "true") && bt(); 
        // Append the site domain to the cookie, and add the cookie to document.cookie 
        c && (o = o + "; domain=" + c)((document.cookie = o + "; " + e)); 
        return true; 
    } catch (n) { 
        return false; 
    } 
}

所以,看起来 bake 指令直接设置了 _px2 cookie!这也是一个文字游戏,就像 烤饼干一样。

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

您应该注意到该 cu 对象也包含所有其他可能的 do 指令的处理程序!要创建 PerimeterX 旁路,您需要对每条指令的功能进行逆向工程 do 。

让我们学习如何破解您可能会在这个数组中找到的一些安全检查 do 。

WebGL指纹识别

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

// This function creates, renders, and hashes the image to construct "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);}" 
                ); 
                /* Some more transformations on the canvas image... */ 
                /* ... */ 
                n.drawArrays( 
                    n.TRIANGLE_STRIP, 
                    0, 
                    a.numItems 
                )( 
                    (r.canvasfp = n.canvas === null ? "no_fp" : In(n.canvas.toDataURL())) // In() computes a hash of the generated image 
                )((r.extensions = n.getSupportedExtensions() || ["no_fp"])); 
            } catch (n) { 
                r.errors.push("PX10703"); 
            } 
            /* ... */ 
        }, 1); 
    }); 
}

这对于指纹识别很有用,因为即使指示绘制完全相同的图像,硬件或低级软件(即操作系统)的细微变化也会产生不同的输出(因此,不同的散列)。这使得 WebGL 指纹识别成为对设备进行分类的好方法。

PerimeterX 还收集各种其他 WebGL 属性以更好地对您的设备进行分类。使用机器学习,他们可以使用此数据来检测您是否在欺骗 WebGL 属性/渲染。

computed canvasfp以及其他 WebGL 属性被添加到以下代码片段中的有效负载对象:

// Adding the collected WebGL data to the POST request payload 
 
(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,以确定脚本是否被沙盒化:

var n; 
// The process object only exists in NodeJS. 
try { 
    n = 
        n || 
        ((typeof process == "undefined" ? "undefined" : A(process)) === "object" && 
            String(process) === "[object process]"); 
} catch (n) {} 
 
try { 
    n = n || /node|io.js/.test(process.release.name) === true; 
} catch (n) {}

为确保内置函数未被修改(即 monkey-patched),PerimeterX 调用 typeof 并隐式调用 toString 它们:

// A() acts as a wrapper for "typeof" 
 
function A(n) { 
    A = 
        typeof Symbol == "function" && typeof Symbol.iterator == "symbol" 
            ? function (n) { 
                    return typeof n; 
                } 
            : function (n) { 
                    return n && 
                        typeof Symbol == "function" && 
                        n.constructor === Symbol && 
                        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" && /{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 < 10 && 
            _i.push( 
                +n.movementX.toFixed(2) + "," + +n.movementY.toFixed(2) + "," + wr(r) 
            ); 
 
        if (n && n.movementX && n.movementY) { 
            if (Pi.length < 50) { 
                Pi.push( 
                    (function (n) { 
                        var r = n.touches || n.changedTouches; 
                        var t = r && 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 经常更新其挑战,因此如果您选择反混淆,则必须不断检查您的爬虫以避免被发现。

希望本教程对您的 Web 抓取项目有所帮助。如果您想了解更多绕过机器人防御的技术,您可以查看我们关于Cloudflare 绕过Akamai 绕过的详细指南。

类似文章