浏览器指纹识别:它是什么以及如何绕过它

您可能没有听说过浏览器指纹识别,但这是一个流行的过程,可能会在网络抓取时阻止您

如今,大多数网络服务器都会收集有关您设备的信息以形成独特的配置文件或指纹。因此,当您访问网页时,它们可以识别您的身份并相应地提供内容。我们将了解它们如何提取您关心的数据。

什么是浏览器指纹识别?

浏览器指纹识别是通过从设备、HTTP 连接和软件功能收集特定数据点来识别 Web 客户端的过程。

这样想:真实的指纹图像是一个唯一的标识符,看起来像直线和曲线的组合,对吧?现在,在数字世界中,每条直线和曲线都编码来自您的信息,包括:

  • 操作系统类型和语言。
  • 浏览器类型、版本和扩展。
  • 时区。
  • 语言和字体。
  • 电池电量。
  • 键盘布局。
  • 用户代理。
  • 处理器类。
  • 导航器属性。
  • 屏幕分辨率。
  • 其他。

虽然上面的细节是通用的,但两个用户拥有百分百匹配的数据点是极其罕见的。每个人都会生成一个独特的指纹,因为即使是轻微的变化也会导致不同的结果。

例如,数以亿计的用户使用 Chrome 浏览器访问谷歌。但是,有多少人在驱动程序版本为 25.20.99.9221 且配备 16 GB RAM 和 4GB Nvidia 显卡的 HP OMEN PC 上使用具有 1366×768 分辨率的单个扩展程序的 Chrome?不多。

浏览器指纹识别的目的是什么?

尽管本指南重点介绍与网络抓取有关的浏览器指纹识别,但网站出于多种原因识别和跟踪用户。

例如,由于网络攻击呈指数级增长,浏览器指纹保护在网络安全中得到广泛采用。即,系统根据客户指纹中的特定参数开发具有自然用户行为的配置文件,以隔离机器人程序和威胁。此外,试图保护用户帐户的网站会跟踪指纹详细信息,以通知所有者可疑的登录尝试。

另一个常见用途是改善用户体验,因为网站可以通过识别特定于位置的详细信息(例如语言和时区)来提供个性化内容。

广告网络还利用指纹详细信息向目标受众提供产品和服务推荐。例如,如果他们知道您的 PC 规格(例如 GPU 和屏幕分辨率),您可能会收到新 PC 型号的广告。

请注意,浏览器指纹识别不同于 cookie 跟踪。虽然后者是识别和跟踪网络用户的传统解决方案,但隐私法正在慢慢将 cookie 排除在游戏之外。但它们究竟有何不同?

Cookie 是由网络服务器发送到用户浏览器的小数据包,然后浏览器保存并在用户下次向服务器请求同一站点时使用它们以进行自我识别。

最终用户对这种类型的跟踪有一定的控制权,因为网站通常会要求他们接受。因此,如果您删除或拒绝这些 cookie,网站将无法再识别或跟踪您的活动。相反,你在浏览器指纹识别上没有发言权。一旦浏览器向网络服务器发送请求,您的数据就会被收集并分组到一个独特的配置文件中。

让我们更详细地了解网站如何访问生成指纹所需的数据。

浏览器指纹识别如何工作?

当您访问一个页面时,您的 Web 客户端会向该站点的服务器发送一个请求以及建立连接所需的一些数据。这通常包括您的 IP 地址和浏览器属性,但如前所述,这并不是可以发送的所有内容。

一些数据通过连接的第一个数据包到达 Web 服务器。但是,在大多数情况下,网站会根据技术注入 JavaScript 来生成必要的指纹信息。这些脚本通常作为文档被注入,在后台运行并且难以区分。

由于每个 Web 客户端对于这些脚本查询的数据点都有不同的值,因此网站会创建唯一的指纹来识别每个用户。例如,网络抓取工具会生成类似机器人的指纹,从而导致Access denied错误消息。

在我们讨论各种指纹识别技术之前,让我们分析一下现实生活中的例子以更好地理解它们是如何工作的

浏览器指纹识别示例

作为第一个示例,我们将查看来自 Keywee 的 JavaScript 文件,Keywee 是一个使用自然语言处理和机器学习来帮助发布商和营销人员创建、分发和衡量其内容性能的平台。

medium_javascriptfile_807f933fef

您在上面看到的这个脚本被缩小了,因此很难推断出它的作用。但是,我们可以在代码上使用在线美化器来更好地理解它这是结果

从第 1415行开始,我们可以看到收集数据点的函数。

17: [function(b, c, a) {
        (function() {
            var l = b("../lib_managed/lodash"),
                k = b("murmurhash").v3,
                g = b("jstimezonedetect").jstz.determine(),
                e = b("browser-cookie-lite"),
                h = typeof a !== "undefined" ? a : this,
                j = window,
                d = navigator,
                i = screen,
                f = document;
            h.hasSessionStorage = function() {
                try {
                    return !!j.sessionStorage
                } catch (m) {
                    return true
                }
            };
            h.hasLocalStorage = function() {
                try {
                    return !!j.localStorage
                } catch (m) {
                    return true
                }
            };
            h.localStorageAccessible = function() {
                var m = "modernizr";
                if (!h.hasLocalStorage()) {
                    return false
                }
                try {
                    j.localStorage.setItem(m, m);
                    j.localStorage.removeItem(m);
                    return true
                } catch (n) {
                    return false
                }
            };
            h.hasCookies = function(m) {
                var n = m || "testcookie";
                if (l.isUndefined(d.cookieEnabled)) {
                    e.cookie(n, "1");
                    return e.cookie(n) === "1" ? "1" : "0"
                }
                return d.cookieEnabled ? "1" : "0"

第 1461行,代码片段在变量中收集了几个指纹组件p,如下所示。

h.detectSignature = function(r) {
                var p = [d.userAgent, [i.height, i.width, i.colorDepth].join("x"), (new Date()).getTimezoneOffset(), h.hasSessionStorage(), h.hasLocalStorage()];
                var m = [];
                if (d.plugins) {
                    for (var q = 0; q < d.plugins.length; q++) {
                        if (d.plugins[q]) {
                            var n = [];
                            for (var o = 0; o < d.plugins[q].length; o++) {
                                n.push([d.plugins[q][o].type, d.plugins[q][o].suffixes])
                            }
                            m.push([d.plugins[q].name + "::" + d.plugins[q].description, n.join("~")])
                        }
                    }
                }
                return k(p.join("###") + "###" + m.sort().join(";"), r)
            };

在这种情况下,收集的数据包括:

  • 用户代理字符串。
  • 窗口大小。
  • 颜色。
  • 时区。
  • 本地存储。

然后,在第1475行,函数k返回指纹组件的整数哈希码。

像上面的例子一样,大多数网站调用 JavaScript 文件来识别和隔离 Web 客户端。

现在,让我们尝试在法国新闻网站Le Monde定位浏览器指纹识别脚本。

如果我们检查开发人员工具中的性能分析器并导航到调用树,我们可以找到第一个函数调用。

medium_screenshot_a8b153454f

单击上图中突出显示的链接,在新选项卡中打开此脚本。

medium_Screenshot_1_906dcffef9

请注意,浏览器指纹识别可能取决于位置或 IP 地址。换句话说,它只能为一组 IP 地址打开,因此这个脚本可能对您有所不同。

也就是说,让我们美化这个脚本以更好地理解它的作用。您可以在此处找到完整的脚本

第 10381042行,我们看到数据点存储在变量e和中p。他们包括:

  • 用户代理字符串。
  • 浏览器供应商。
  • 窗口宽度和高度。
    f = () => {
             const e = navigator.userAgent || navigator.vendor || window.opera;
             return -1 !== e.indexOf("FBAN") || -1 !== e.indexOf("FBAV")
         },
         p = () => window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
     let m = !1;
     const h = () => {
             const e = sessionStorage.getItem("pageinsession");
             if (!0 === m && e) return e.length;
             if (m = !0, "reload" !== d()) {
                 const t = e && "".concat(e, "i") || "i";
                 return sessionStorage.setItem("pageinsession", t), t.length
             }
             return e ? e.length : 1
         },
    

    遵循相同的原则,其他功能可能会在此脚本中收集额外的指纹数据。

    指纹技术

    站点使用多种方法与 Web 客户端的属性进行交互,以便访问和识别用户数据。让我们来看看其中的一些!

    canva指纹

    HTML5 Canvas 是一种 API,具有用于在画布上绘制文本和图形的内置对象、方法和属性。浏览器可以利用其功能来呈现网站内容。

    2012 年,Mowery 和 Shacham 发现最终用户可见的画布图形直接受多种因素影响,包括:

    • 操作系统。
    • 浏览器版本。
    • 显卡。
    • 安装的字体。
    • 亚像素提示。
    • 抗锯齿。

    这意味着生成的图像和文本会因设备的图形功能而异。因此,浏览器指纹识别网站会请求 Web 客户端处理画布图像。这会生成有价值的信息,网站会收集这些信息并将其存储在浏览器指纹数据库中。

    要了解有关网站如何使用 HTML5 Canvas 功能来阻止网络抓取工具的更多信息,请查看我们关于画布指纹识别的深入文章(即将发布)。

    WebGL

    与 HTML5 Canvas 一样,WebGL 是专门用于渲染 3D 交互式图像的图形 API 。

    是的,你猜对了!这些图形根据设备的图形功能以不同方式呈现。因此,浏览器指纹识别网站可以让浏览器的 WebGL API 生成 3D 图像,从而从结果中提取设备的特征。

    音频上下文

    Web Audio API 是一个用于处理音频的接口。任何设备都可以通过将音频模块连接在一起来生成音频信号并应用特定的压缩级别和过滤器以获得特定结果。

    AudioContext 指纹识别的工作方式与 HTML5 Canvas 和 WebGL 相同。它根据设备音频堆栈(软件和硬件功能)处理存在差异的音频信号。

    这是一种收集指纹数据的相对新颖的方法,因此只有少数网站有实现 Web Audio API 的脚本。

    浏览器扩展

    浏览器扩展是扩展浏览器功能和特性的小型软件模块。一些最受欢迎的是广告拦截器和 VPN。

    也就是说,浏览器指纹识别网站最重要的元素是如何集成扩展。通常,他们通过网络获取一些资源。因此,通过检查特定的 URL,网站可以查询是否存在扩展名。例如,要获取扩展程序的徽标,站点可以使用其对设备的了解并遵循表单的 URLextension://<extensionID>/<pathToFile>来检索它。

    然而,并不是每个扩展都有这样的可访问资源,也不是所有的扩展都可以使用这种技术检测到。

    JavaScript 标准一致性

    网站还可以根据用户的底层 JavaScript 引擎来识别用户。这是可能的,因为这些引擎根据浏览器功能以不同方式执行 JavaScript 代码。甚至后续的浏览器版本也存在差异。因此,网站可以测试 Web 客户端,看它们是否按照 JavaScript 标准编译。然后他们分析支持的特征以提取指纹数据。

    CSS 属性

    Web 浏览器对特定的 CSS 属性有不同的默认值。通过读取这些属性的值,网站可以确定使用哪个浏览器访问该网站。例如,大多数 Firefox CSS 属性都包含-Moz-前缀,而 Safari 和 Chrome 使用-WebKit-前缀。

    字体

    一种不受欢迎但有效的浏览器指纹技术是根据呈现的文本识别用户,因为不同设备上的浏览器呈现相同的字体样式和具有不同边界框的字符。因此,站点可以查询边界框以收集指纹数据。

    传输层安全 (TLS)

    当 Web 客户端向网站发送 HTTPS 请求时,它们是通过传输层安全性进行的。Web 客户端必须创建安全连接以启用通信,这涉及通过 TLS 握手与 Web 服务器交换有价值的数据。网站使用交换中的信息来识别用户,因为这些参数因 Web 客户端而异。

    我们详细介绍了 TLS 指纹识别,所以这次让我们关注如何避免浏览器指纹识别。

    如何绕过浏览器指纹识别?

    理想情况下,您可以通过禁用 JavaScript 来避免浏览器指纹识别。这是可行的,因为网站使用脚本来收集数据来创建您的指纹,但是,虽然一些反浏览器指纹识别解决方案使用这种方法,但它并不可靠。这是因为它会影响可用性和内容可用性,因为当今大多数网站都依赖 JavaScript 来显示内容。

    此外,作为实际用户和网络抓取工具避免浏览器指纹识别之间存在差异

    对于普通冲浪者来说,目标是保护隐私并减少不必要的跟踪。为此,浏览器调整其技术以保护用户免受浏览器指纹识别。例如, Chrome 建议尽量减少在用户代理字符串中共享的标识信息。

    另一方面,网络抓取工具需要访问权限才能提取数据。因此,我们的目标是大规模融入,大多数网络抓取资源都会建议您模仿真实的用户行为。但是如何在不被识别为机器人的情况下实现这一目标呢?

    使用像 Selenium、Playwright 和 Puppeteer 这样的无头浏览器使你的请求看起来比 HTTP 客户端更自然。但他们够了吗?

    JavaScript 使用两个主要概念来识别网络抓取工具。第一个是bot fingerprint leaking,它使用 JavaScript 环境来确定 Web 客户端是实际浏览器还是 bot。第二个是身份指纹识别,其中 JavaScript 环境使用唯一 ID 跟踪客户端。例如,如果您的网络抓取工具使用 ID 45689 进行跟踪,则可以在建立太多不自然的连接后识别它。

    Bot 指纹泄露是网站识别和阻止无头浏览器的最常见方式。虽然它们可以模仿真实的用户行为,但某些默认属性可能会将它们标记为抓取工具。让我们看看如何以及解决方案是什么!

    无头浏览器泄漏

    可悲的是,仅浏览器指纹识别就可以非常强大地检测网络抓取工具,而无头浏览器可以使指纹识别更容易。那是因为它们设置了默认属性,在 JavaScript 执行上下文中将它们标记为机器人。换句话说,JavaScript 可以识别这些属性并通知 Web 服务器机器人控制浏览器。

    因此,让我们的抓取环境不可检测应该是我们绕过浏览器指纹识别的第一步。

    这些告密属性之一是headless: true,表示浏览器在没有 GUI 元素的情况下运行。另一个属性泄漏是navigator.webdriver: true由 Selenium、Playwright 和 Puppeteer 设置的。

    通过检测这些属性值对,网站可以识别并阻止由无头浏览器提供支持的网络抓取工具。那么,我们如何使这些属性无法检测到呢?

    如何堵塞指纹泄漏?

    我们可以通过在属性被访问时强加假值来堵住漏洞。例如,以下脚本会堵住navigator.webdriver漏洞。

    Object.defineProperty(navigator, "webdriver", {
      get: function() {
        return false;
      }
    });
    

它使用Object.defineProperty()方法来创建或修改导航器对象上的属性,而GET函数用于定义navigator.webdriver查询属性时的行为。在这种情况下,该函数始终返回值false

您可以将此代码添加到网络抓取脚本的开头。这样,当网站检查 的值时navigator.webdriver,它总是返回false,并且无法检测到浏览器是自动的。

让我们看看如何为最流行的无头浏览器做到这一点:

Selenium:execute_script()方法将执行脚本。下面是如何在 Python 中执行此操作的示例。

from selenium import webdriver

# create a new instance of the chrome driver
driver = webdriver.Chrome()

# execute the script to hide the fact that the browser is automated
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: function() {return false}})")

编剧:您可以使用evaluate()方法在JavaScript中执行脚本。检查以下示例。

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  // execute the script to hide the fact that the browser is automated
  await page.evaluate(() => {
    Object.defineProperty(navigator, 'webdriver', {get: () => false});
  });
})();

Puppeteer:您也可以使用该evaluate() 方法在 JavaScript 中执行脚本,如下所示。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // execute the script to hide the fact that the browser is automated
  await page.evaluate(() => {
    Object.defineProperty(navigator, 'webdriver', {get: () => false});
  });
})();

但是,每个浏览器都有我们必须单独插入的泄漏向量。不幸的是,我们不能全面使用相同的堵塞技术。相反,我们需要分别处理每一个问题。

Chrome 可以说是最常用的浏览器,因此我们将重点关注它以尽可能多地融入其中。但是,一旦您了解了如何堵住这些漏洞并使您的抓取环境无法检测到,您就可以使用任何浏览器。

Chrome Leaks

有许多分析浏览器泄漏和指纹值的解决方案。但它们大多是开源的,很少能跟上不断发展的浏览器生态系统。然而,我们将创建一个快速脚本,将我们的无头浏览器与实际浏览器进行比较

在这个例子中,我们将使用 Python 脚本来比较 Playwright 和 Selenium浏览器。

import sys
from checkselenium import run as run_selenium
from checkplaywright import run as run_playwright

# run function to execute the script
def run(script:str, url=None):
    # data variable to store the result of the script execution
    data = {}
    # loop through the toolkits, selenium and playwright
    for toolkit, tookit_script in [('selenium', run_selenium), ('playwright', run_playwright)]:
        # loop through the browser, chromium
        for browser in ['chromium']:
            # loop through headless and headful mode
            for head in ['headless', 'headful']:
                # store the result of the script execution in the data variable
                # using the toolkit, head, browser and url as the key
                data[f'{toolkit:<10}:{head:<8}:{browser:<8}:{url or ""}'.strip(':')] = tookit_script(browser, head, script, url)
    # return the data variable
    return data

# main function
if __name__ == "__main__":
    # loop through the result of the run function
    for query, result in run(*sys.argv[1:]).items():
        # print the query and result
        print(f"{query}: {result}")

让我们使用该navigator.webdriver属性测试我们的脚本。

$ python compare.py "navigator.webdriver"
selenium  :headless:chromium: True
selenium  :headful :chromium: True
playwright:headless:chromium: True
playwright:headful :chromium: True

如您所见,我们的脚本true作为 Selenium 和 Playwright 的值返回。

网站识别 Web 客户端的一种常见方式是通过测试功能。此测试广泛使用的数据点是插件和 MIME 类型。网站使用navigator.pluginsnavigator.mimetypes方法访问这些值。

让我们看看 Selenium 和 Playwright 提供的值。

$ python compare.py "[navigator.plugins.length, navigator.mimeTypes.length]"
selenium  :headless:chromium: [0, 0]
selenium  :headful :chromium: [5, 2]
playwright:headless:chromium: [0, 0]
playwright:headful :chromium: [5, 2]

我们的结果是空数组,因为无头浏览器不支持视觉细节。在这种情况下模仿用户行为的范围很广,但是这个 GitHub 存储库解释了如何正确地模仿这些功能。

启动标志

还值得注意的是,自动浏览器会添加额外的启动标志,这些标志可以在 JavaScript 环境中轻松检测到。我们可以通过在无头浏览器中启用调试日志来识别这些标志

对于Selenium,您可以通过添加以下代码行来启用调试日志。

from selenium import webdriver
from selenium.webdriver.remote.remote_connection import LOGGER
 
LOGGER.setLevel(logging.DEBUG)

这将打印在执行 Selenium 脚本期间发送到浏览器的所有浏览器命令,从而产生如下结果。

DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:56426/session {"capabilities": {"firstMatch": [{}]}, "desiredCapabilities": {"browserName": "chromium", "version": "", "platform": "ANY"}}

我们可以通过在Puppeteer的控制台中打印它们来直接查看默认参数。

const puppeteer = require('puppeteer')
console.log(puppeteer.defaultArgs());

那应该让你得到以下结果。

[ '--disable-background-networking',  '--disable-background-timer-throttling',  '--disable-backgrounding-occluded-windows',  '--disable-breakpad',  '--disable-client-side-phishing-detection',  '--disable-component-extensions-with-background-pages',  '--disable-default-apps',  '--disable-dev-shm-usage',  '--disable-extensions',  '--disable-features=TranslateUI,BlinkGenPropertyTrees',  '--disable-hang-monitor',  '--disable-ipc-flooding-protection',  '--disable-popup-blocking',  '--disable-prompt-on-repost',  '--disable-renderer-backgrounding',  '--disable-setuid-sandbox',  '--disable-speech-api',  '--disable-sync',  '--disable-tab-for-desktop-share',  '--disable-translate',  '--disable-web-security',  '--disable-xss-auditor',  '--enable-automation',  '--enable-features=NetworkService,NetworkServiceInProcess',  '--force-color-profile=srgb',  '--metrics-recording-only',  '--no-first-run',  '--no-pings',  '--no-sandbox',  '--no-zygote',  '--remote-debugging-port=0',  '--remote-debugging-address=0.0.0.0',  '--single-process' ]

其中一些标志可以禁用某些功能或安全措施,因此我们需要删除它们以模仿真实的用户行为。

以下是一些可以删除的内容:

  • --disable-web-security此标志禁用同源策略,该策略阻止网页向与服务该网页的域不同的域发出请求。
  • --disable-xss-auditor这会禁用跨站点脚本 (XSS) 审核器,该功能可检测并阻止试图将代码注入网页的恶意脚本。
  • --no-sandbox此标志禁用 Chromium 用于将浏览器的渲染器进程与系统其余部分隔离的安全沙箱。
  • --enable-automation启用浏览器自动化,可用于执行自动化测试等任务。因此,如果网站检测到这一点,它会将您的浏览器标记为机器人。

备注:禁用这些标志会使您的浏览器更容易受到某些攻击,因此您应该只在受控环境中使用它们。以下脚本将禁用上述标志。下面是一个使用 Selenium 的例子:

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--disable-web-security")
options.add_argument("--disable-xss-auditor")
options.add_argument("--no-sandbox")

browser = webdriver.Chrome(chrome_options=options)

您可以使用 Puppeteer 执行相同的操作。

const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
    args: ['--disable-extensions', '--enable-automation=false']
});

隐身模式下的无头浏览器

在使用无头浏览器进行抓取时,您可能需要模仿其他 JavaScript 可检测的功能。幸运的是,这些无头浏览器的隐形版本可以帮助解决其中的一些问题。另外,它们易于使用。您只需导入无头浏览器库的隐形版本并抓取。

这里有些例子:

不幸的是,它们可能只在某些情况下有效。如前所述,开源代码在许多情况下往往会滞后,而浏览器和反机器人检测解决方案会频繁更新其防御措施。

无论如何,让我们看看接下来可以轻松绕过浏览器指纹识别的解决方案。

结论

浏览器指纹识别是一种强大的反机器人检测技术。而且,由于抓取此类网站绝非易事,因此我们提供了不同的选择。

让我们快速回顾一下!我们了解到您可以通过以下方式模仿用户行为

  • 使用无头浏览器。
  • 堵漏。
  • 删除有害的启动标志。

类似文章