一、从爬虫与反爬说起
你是不是也做过这些事:
- 写个 Python 脚本,带上
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... - 再挂个代理 IP,觉得自己神不知鬼不觉?
结果网站还是弹了验证码,或者直接拦掉,数据没拿到,IP 还被封了。你郁闷:我明明装成了 Chrome,为什么还是被识破?
因为你只在“衣服”上做了伪装,而你的“骨架和神态”根本就没变。
这就是本文要聊的——协议层指纹识别。
二、你和网站握手时,其实递了一张“明信片”
每次你访问 HTTPS 网站,浏览器与服务器之间会进行一次加密握手(TLS 握手)。在正式开始传加密数据之前,客户端要先发一个叫 Client Hello 的报文。
你可以把这想成你去住酒店,前台需要先登记。你递上身份证,上面写了:
- 你支持的加密方式有哪些(密码套件)
- 你能说哪些“方言”(TLS 扩展)
- 你想住到几楼(TLS 版本)
- 你是不是某个特定域名的常客(SNI)
最关键的是,这张“身份证”是明文的,谁都能看到。 就算后面全程加密,仅凭这张明信片,就足以识别你是什么来头。
不同客户端递出的身份证长得完全不一样。比如:
- Chrome 用 Google 自家的 BoringSSL 库,身份证排版工整,项目齐全;
- Python 的 requests 库底层是 OpenSSL,身份证里总会夹带一些自己特有的标志(如
TLS_EMPTY_RENEGOTIATION_INFO_SCSV); - 某个老旧木马的自定义网络库,可能连基本的扩展都缺斤短两。
于是,安全人员就想:我们干脆把这张“身份证”的内容提取出来,做成一个固定指纹,以后看到同样的就标记它是谁。
你可以通过这个网站查看你浏览器的TLS指纹:https://browserleaks.com/tls

三、JA3:当年的指纹“快照机”
2017年,有人提出了 JA3 算法。它干了件很简单的事:
- 从 Client Hello 里挑出五个关键字段:TLS 版本、密码套件列表、扩展列表、椭圆曲线、曲线格式。
- 把这些字段的数值按出现的原始顺序连成一个长字符串。
- 用 MD5 算出一个 32 位的哈希值。
比如:769,47-53-5-10-… → 哈希得到 b20b44b18b853ef29ab773e921b03422
这个哈希就是 JA3 指纹。以后只要看到相同的 JA3,就认为是同一个客户端或同一类工具发出的。服务器也可以回一个自己的指纹(JA3S),两者配对,等于把犯罪嫌疑人和受害者同时定了位。
这种办法一度极好用,很多 WAF、反爬系统都靠它拦住了大量脚本。
四、但好景不长:有人学会了“洗指纹”
问题来了:既然身份证是明文,那我不就可以故意改一改上面的项目吗?
第一招:打乱顺序
Google Chrome 从某个版本开始,每次发 Client Hello 时会把 TLS 扩展的排列顺序随机打乱(这是一种防止协议僵化的机制,叫 GREASE 的延伸)。
这下惨了,同一个浏览器每次发出的 JA3 都不同,一个浏览器能产生上百个 JA3。
原本指望只要匹配黑名单就能拦住坏人的方法,基本废了。
第二招:故意塞假选项
一些编程语言的网络库也开始随机化密码套件的顺序,或者插入一些无意义的扩展。
同样一个 Go 程序,今天和明天的指纹可能就不一样。
第三招:MD5 的“黑箱”问题
安全分析师看到一个 JA3 指纹变化后,看着 e7f0d… 一脸懵逼——到底是因为用户升级了系统补丁,还是真的来了攻击流量?完全没法从哈希本身知道原因。毫无可读性。
五、JA4+:新一代指纹——不但看得准,还能看得懂
于是,那伙做 JA3 的团队痛定思痛,搞出了 JA4+ 套件。JA4 + 是 2023 年 9 月发布的模块化网络指纹识别套件,由 FoxIO 公司推出,是 JA3/JA4 的全面升级。它不再局限于单一 TLS 指纹,而是通过多协议、多维度的指纹组合,实现更精准的客户端 / 服务器身份识别,特别适合网络安全、反爬与威胁狩猎场景。
JA4 的指纹不再是一串无意义的乱码,而是像车牌号一样,分成了三段,每段都能读懂一部分。
比如一个真实的 Chrome 指纹:t13d1516h2_8daaf6152771_e5627efa2ab1
-
第一部分
t13d1516h2:基础信息t:走的是 TCP13:TLS 1.3d:请求里带了域名(d=domain)15:有 15 种密码套件16:有 16 个扩展h2:优先使用 HTTP/2
这部分完全是人能直接读的,像查户口一样。
-
第二部分
8daaf6152771:密码套件的签名不管原来顺序怎么打乱,JA4 先给所有套件按十六进制排个队,再算 SHA-256 取前 12 位。彻底免疫顺序干扰。 -
第三部分
e5627efa2ab1:扩展和签名算法的签名
同样的,不管 Chrome 怎么随机排列扩展,它都先把能代表浏览器特性的非 SNI、非 ALPN 扩展排序后再哈希。所以依旧稳定。
更大的杀器是——模糊匹配。
如果某木马为了逃避检测,每次都随机修改一个不重要的密码套件,它的 JA4 第二部分会不断变化。但安全团队可以只看 JA4_ac(第一段+第三段),忽略第二部分。这样所有变种流量就会被自动聚合成同类,一抓一个准。
JA4+ 还扩展到了 TCP 层面(JA4T)和 HTTP 层面(JA4H)。比如 JA4T 会抓你 TCP 三次握手 SYN 包里的选项、MSS 大小等。这些参数写在操作系统内核里,你写个 Python 脚本根本改不了。如果 TLS 指纹把自己伪装成 Windows 的 Chrome,而 TCP 指纹却露出了 Linux 内核的特征,这就是典型的 “跨层矛盾” ,直接实锤伪装。
六、就算过了 TLS 这关,还有 HTTP/2 的“方言”等着你
即使你的 TLS 指纹模仿得惟妙惟肖,还有一个更深的坑:HTTP/2 的底层行为。
HTTP/2 不再用纯文本,而是走二进制帧,并且一上来就要互相交换一堆“设置”(SETTINGS 帧),规定窗口大小、最大并发流、头部压缩表尺寸等等。这些值在不同客户端里极其不同:
- Python 的某个库,默认设置少,可能直接禁用服务器推送;
- Chrome 的窗口初始值往往是
6291456; - Edge 的窗口更新增量又是一种固定常数;
- Firefox 甚至会在连接刚开始时就发送一堆 PRIORITY 帧,预先定义“资源下载的辈分关系”。
这些就像不同地方的方言,就算你穿中山装(TLS 指纹像 Chrome),一张口却是广东话(HTTP/2 的参数像 Python),经验丰富的反爬系统立刻警觉。
更细节的是 伪头部发送顺序。HTTP/2 把请求行拆成了四个冒号开头的头::method、:authority、:scheme、:path。规范只说“它们必须在普通头之前”,没说彼此之间怎么排。
Chrome 往往是 :method, :authority, :scheme, :path(简写 m-a-s-p),Firefox 可能是 :method, :path, :authority, :scheme。而 Python 的字典默认是字母序,出来就是 :authority, :method, :path, :scheme。这个顺序鬼得很,除非你刻意改,否则隐藏不掉。
于是,单纯改个 User-Agent 就想骗过全世界的时代,彻底结束了。
七、快速判断:网站是否开启 JA3/JA4+ 严格TLS指纹校验
普通浏览器能正常访问,原生脚本(requests/axios/fetch)直接403/503/拦截/空白,基本就是开了TLS指纹风控。 纯证书问题只会报SSL错误,不会正常返回403。
6.1、3步最简自测(零工具、最快)
测试1:浏览器访问
Chrome/Edge/Firefox 直接打开网址,完全正常加载。
测试2:Python 原生 requests 直连
import requests
# 原生默认TLS栈,JA3/JA4 是特征指纹
res = requests.get("目标URL")
print(res.status_code)
结果判定
- 返回 403 / 503 / 拒绝连接 / 拦截页 👉 高概率:开启JA3/JA4+ TLS指纹校验
- 正常200 👉 无TLS指纹拦截,只做UA/IP/Header检测
测试3:关闭证书校验再测(排除证书干扰)
requests.get("目标URL", verify=False)
- 依然403:实锤TLS指纹/多层风控
- 正常200:只是证书问题,无指纹校验
6.2、网站开启强制 TLS 指纹校验后脚本该怎么访问
原生 requests /axios/fetch 全部废掉,必须用「能模拟浏览器 TLS 指纹」的库。
🛠️ 主流工具横评
| 语言 | 推荐方案 | 原理与特性 | 优点 | 潜在缺点 |
|---|---|---|---|---|
| Python | curl_cffi | 绑定 curl-impersonate,能模拟 Chrome/Firefox/Safari 等多种浏览器的 JA3/JA4 指纹和 HTTP/2 特征。 | API与requests库高度相似,迁移成本低;支持同步/异步;性能强。 | 非纯Python,需要安装额外的 C 依赖(虽然官方提供预编译包)。 |
| Python | ViperTLS | 纯 Python 实现,不依赖 curl 或外部二进制包。支持 HTTP/2 帧排序、SETTINGS 帧模拟等高级特性。 | 纯 Python,安装简单 (pip install vipertls);遇到 JS 挑战时可自动调用 Playwright 等真实浏览器求解。 | 相对较新,社区生态可能不如 curl_cffi 成熟。 |
| Node.js | wreq-js | 基于 Rust 的原生模块,性能极高。内置了 Chrome/Safari/OkHttp 等多种库的指纹配置。 | 提供与 fetch API 高度一致的现代化接口,支持 TypeScript,开发体验好。 | 需要 Rust 编译环境,上手门槛略高。 |
| Node.js | node-libcurl-ja3 | 绑定 curl-impersonate,核心能力与 Python 的 curl_cffi 对等。 | 底层依赖被广泛验证的 curl,指纹模拟准确度高。 | API 风格偏底层 C++,使用起来不如 wreq-js 的 fetch 风格方便。 |
八、道高一尺魔高一丈:curl-impersonate 的“画皮”戏法
攻防双方永远在赛跑。既然网站开始用这些底层指纹,攻击方就开发出了更强悍的伪装工具——curl-impersonate。
传统的 curl 底层是 OpenSSL,一言一行都是脚本的样子。curl-impersonate 直接来了个“换核手术”:
- 要模仿 Chrome?就把加密库替换成 Google 的 BoringSSL 编译;
- 要模仿 Firefox?就换成 Mozilla 的 NSS 库。
不光换库,它还加入了精细控制:可以故意打乱扩展顺序、模仿证书压缩、甚至精确设置 HTTP/2 的 SETTINGS 窗口大小和伪头部顺序。
最终,你用一个命令 curl_chrome116 https://目标,它就能发出和真实 Chrome 116 一模一样的 TLS 和 HTTP/2 流量,连最狡猾的检测系统都不容易分辨。
它甚至支持 LD_PRELOAD 注入,老的程序不用改一行代码,就能瞬间“换皮”成浏览器。
九、但 CDN 厂商开始用“上帝视角”了
家被偷了,防御方也升级了战术。
Cloudflare、Akamai 这些全球 CDN 不再只凝神看一个请求,而是把每个请求放在整个互联网的时空背景里看。
他们给每个 JA4 指纹维持了一套“群体画像”,比如:
ips_quantile_1h:过去一小时内,使用这个指纹的独立 IP 数量排在全网百分之多少。如果一个指纹本身长得特像 Chrome,但一看背后挂了 5 万个 IP 地址在疯狂轮换,那妥妥是代理池在搞事。- 异常响应率:这个指纹产生的 5xx 错误、缓存穿透是不是高得离谱。
- 行为连续性:真实用户总会加载图片、CSS,停留一段时间;而脚本可能直接请求 API 返回数据就走。
这些指标会送入机器学习模型,在毫秒级给出一个风险评分。这样,即使你用了 curl-impersonate 把指纹模仿到极致,只要你的行为模式是“机器人式”的,还是会被揪出来。
此外,移动端 App 的防护更依赖这种无感校验。
银行 App 在发请求时,其 SDK 除了验证设备令牌,还会比对请求的 JA4 指纹是否与该 App 编译时的 TLS 特征一致。如果有个请求带着合法令牌,却用的是 Python 的 TLS 指纹,立刻判定为凭证被窃取的冒充请求——这时候根本不需要弹验证码打扰用户。
十、未来的挑战:加密的彻底化与“盲区”降临
有一个叫 ECH(Encrypted Client Hello) 的东西正在部署。它把 TLS 握手里原本明文的 SNI(你要访问哪个域名)也加密了,甚至把 Client Hello 拆成内外两层,内层用服务器公钥锁起来,外部只放一张通用的假牌。
对个人隐私来说,这是天大的好事,运营商再也无法轻易偷看你到底去了哪个网站。
但对安全检测来说,堪称灾难。原来依赖明文 SNI 做流量过滤的企业防火墙,会瞬间变瞎;那些靠解析扩展列表做指纹的中间设备,会看到千篇一律的伪造外壳。也就是说,JA4 这种提取也会面临熵值大幅降低的困境,因为外层暴露的信息已经毫无个性。
那时,防守方只能更激进地转向看不见内容时的“流分析”——分析数据包的到达间隔、大小抖动、时序特征,结合图神经网络去推测谁在跟谁通信。
换句话说,这场猫鼠游戏,将从“查身份证”全面进化到“看步态、听呼吸”。

2859

被折叠的 条评论
为什么被折叠?



