JA3指纹介绍

背景介绍

前段时间做了一个看似很简单的需求,即爬取百度搜索内容,例如通过百度搜索“JA3指纹”。在编写程序测试过程中,发现无法正常采集到搜索结果。如下代码通过python requests工具访问百度搜索请求,会被百度安全验证拦截下来。

import requests

res = requests.get("https://www.baidu.com/s?wd=ssl%E6%8C%87%E7%BA%B9", 
    headers = {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"
    }
)
res.encoding = "utf-8"
print(res.text)

但通过以下 curl 语句能够正常访问百度搜索结果。

curl 'https://www.baidu.com/s?wd=ssl%E6%8C%87%E7%BA%B9' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36' \
  --compressed

那么在目标网站、请求头、请求参数都相同的情况下,curl 与 requests 在网络请求过程中还有什么不同呢?由此引出本文介绍的对象“JA3指纹”(也称作SSL指纹、TLS指纹)。

什么是 JA3 指纹

JA3 指纹于2017年6月首次发布在 GitHub 上,是 Salesforce 研究人员 John Althouse、Jeff Atkinson和Josh Atkins 的作品。简单来说,JA3 是一种从 SSL/TLS Client Hello 数据包中提取字段并生成指纹用以识别特定客户端的技术,它于它不会随着客户端更换 IP 或者 User-Agent 而改变。JA3 指纹从一开始就说明客户端应用程序是否存在恶意。

JA3 是怎么生成的

回顾一下 https 建立连接的过程,能更直观的感受到 JA3 指纹是在什么位置生成的。
在这里插入图片描述

JA3 在 SSL/TLS 握手期间解析客户端发送的 Client Hello 数据包,收集以下字段的十进制字节值:TLS Version、Accepted Ciphers、List of Extensions、Elliptic Curves和Elliptic Curve Formats。然后,它将这些值串联起来,使用“,”来分隔各个字段,同时使用“-”来分隔各个字段中的值。最后,计算这些字符串的md5哈希值,即得到易于使用和共享的长度为32字符的指纹。

我们可以通过浏览器请求 https://check.ja3.zone/ 来得到浏览器的 JA3 指纹,其中 hash 字段对应的值就是我们所说的 JA3 指纹,它是 fingerprint 字段的 md5 计算结果。

{
    "hash": "82f7b24ab8c716674e9e78d4b856d388",
    "fingerprint": "771,49195-49199-49196-49200-49171-49172-156-157-47-53-49192-49188-49162-165-163-161-159-107-106-105-104-57-56-55-54-49202-49198-49194-49190-49167-49157-61-49191-49187-49161-164-162-160-158-103-64-63-62-51-50-49-48-49201-49197-49193-49189-49166-49156-60-154-153-152-151-150-10-255,0-11-10-35-13-15-21,23-25-28-27-24-26-22-14-13-11-12-9-10,0-1-2",
    "ciphers": "ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-SHA,ECDHE-RSA-AES256-SHA,AES128-GCM-SHA256,AES256-GCM-SHA384,AES128-SHA,AES256-SHA,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-ECDSA-AES256-SHA,UNKNOWN(00a5),DHE-DSS-AES256-GCM-SHA384,UNKNOWN(00a1),DHE-RSA-AES256-GCM-SHA384,DHE-RSA-AES256-SHA256,DHE-DSS-AES256-SHA256,UNKNOWN(0069),UNKNOWN(0068),DHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,UNKNOWN(0037),UNKNOWN(0036),UNKNOWN(c032),UNKNOWN(c02e),UNKNOWN(c02a),UNKNOWN(c026),UNKNOWN(c00f),UNKNOWN(c005),AES256-SHA256,ECDHE-RSA-AES128-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-ECDSA-AES128-SHA,UNKNOWN(00a4),DHE-DSS-AES128-GCM-SHA256,UNKNOWN(00a0),DHE-RSA-AES128-GCM-SHA256,DHE-RSA-AES128-SHA256,DHE-DSS-AES128-SHA256,UNKNOWN(003f),UNKNOWN(003e),DHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,UNKNOWN(0031),UNKNOWN(0030),UNKNOWN(c031),UNKNOWN(c02d),UNKNOWN(c029),UNKNOWN(c025),UNKNOWN(c00e),UNKNOWN(c004),AES128-SHA256,DHE-RSA-SEED-SHA,DHE-DSS-SEED-SHA,UNKNOWN(0098),UNKNOWN(0097),SEED-SHA,UNKNOWN(000a),TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
    "curves": "0017:0019:001C:001B:0018:001A:0016:000E:000D:000B:000C:0009:000A",
    "protocol": "TLSv1.2",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"
}

为了更近一步描述清楚这些数据的来源,我们将fingerprint与Client Hello数据包中的字段做了映射。
例如fingerprint内容如下:771,49195-49199-49196-49200-49171-49172-156-157-47-53-49192-49188-49162-165-163-161-159-107-106-105-104-57-56-55-54-49202-49198-49194-49190-49167-49157-61-49191-49187-49161-164-162-160-158-103-64-63-62-51-50-49-48-49201-49197-49193-49189-49166-49156-60-154-153-152-151-150-10-255,0-11-10-35-13-15-21,23-25-28-27-24-26-22-14-13-11-12-9-10,0-1-2
在这里插入图片描述
其中第三个字段来自各个扩展项的前2个字节的十进制数值。如下图所示
在这里插入图片描述

这里就能解释,为什么我用 python requests 工具访问百度搜索会被百度的安全验证拦截了,因为 requests 的 JA3 指纹在百度黑名单中。下面是我的 requests 工具的 JA3 指纹,多次请求 JA3 指纹不变。
在这里插入图片描述

JA3 误报率如何

官方解释,存在一定的误报风险,我们可以将 JA3 视为 User-Agent 字符串的 TLS 等价物。 仅仅因为一个软件或恶意软件具有特定的 JA3 指纹并不意味着它对于该软件来说总是唯一的。 其他软件可能拥有一样的 JA3指纹。 但是,没有理由不使用 JA3指纹来增强我们的分析和检测。 就像其他网络元数据一样,JA3 是用于丰富数据的额外信息。 JA3S 与 JA3 结合使用时,可以显着降低误报率。

JA3S 指纹是为 Server Hello 数据包中的以下字段收集字节的十进制值:版本、密码套件和扩展列表。 然后它将这些值按顺序连接在一起,使用“,”来分隔每个字段,使用“-”来分隔每个字段中的每个值。虽然服务器对不同客户端响应的 JA3S不同,但它对同一个客户端响应的 JA3S 总是相同的。

JA3 如何绕过

介绍几种可尝试的方案

本地代理中转请求

在本地启动代理服务器,比如 Charles,requests 发起 https 请求时配置 Charles 本地代理服务,让 Charles 来进行 TLS 握手,算是一种曲线救国的方法。

import requests

res = requests.get("https://www.baidu.com/s?wd=ssl%E6%8C%87%E7%BA%B9", 
    headers = {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"
    },
    proxies = {
        "http": "http://127.0.0.1:8888",
        "https": "http://127.0.0.1:8888",
    }
)
res.encoding = "utf-8"
print(res.text)

这种方案需要找一个不会被拦截的客户端代理才可以。

更换网络客户端

比如 python requests 客户端,可以尝试使用其他客户端比如 scrapy、pycurl、aiohttp 进行网络连接,或者更换客户端的版本。

魔改客户端工具

以 python requests工具为例,requests 其实是对urllib3的一个封装,可以修改 urllib3 代码中的 TLS 握手特征,比如增加或删除部分加密套件。

// vim /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/urllib3/util/ssl_.py

 83 DEFAULT_CIPHERS = ":".join(
 84     [
 85         "ECDHE+AESGCM",
 86         "ECDHE+CHACHA20",
 87         "DHE+AESGCM",
 88         "DHE+CHACHA20",
 89         "ECDH+AESGCM",
 90         "DH+AESGCM",
 91         "ECDH+AES",
 92         "DH+AES",
 93         "RSA+AESGCM",
 94         "RSA+AES",
 95         "!aNULL",
 96         "!eNULL",
 97         "!MD5",
 98         "!DSS",
 99     ]
100 )

如果需要修改scrapy的JA3指纹,则可以在contextfactory.py文件中进行修改。

# vim scrapy/core/downloader/contextfactory.py
def __init__(self, method=SSL.SSLv23_METHOD, tls_verbose_logging=False, tls_ciphers=None, *args, **kwargs):
    super(ScrapyClientContextFactory, self).__init__(*args, **kwargs)
    self._ssl_method = method
    self.tls_verbose_logging = tls_verbose_logging
    if tls_ciphers:
        # 修改示例:打乱ciphers顺序,改变TLS指纹
        ciphers = AcceptableCiphers.fromOpenSSLCipherString(tls_ciphers)._ciphers
        random.shuffle(ciphers)
        self.tls_ciphers = OpenSSLAcceptableCiphers(ciphers)
    else:
        self.tls_ciphers = DEFAULT_CIPHERS
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值