了解 HTTP/3 和 QUIC 指纹识别的原理:QUIC Initial 数据包中的传输参数、h3 ALPN 信号,以及它与 HTTP/2 指纹识别的区别。
HTTP/2 指纹识别读取的是客户端通过 TCP+TLS 发送的 SETTINGS 帧、窗口增量和伪标头顺序。HTTP/3 把这一整套握手过程压缩进单一的基于 UDP 的传输协议——QUIC——在此过程中,一些信号被搬到了新的位置,另一些被并入了 TLS 层,还有一个信号被彻底移除。结果是一种与 TLS 指纹识别重叠更多的指纹,但它和 HTTP/2 指纹识别并不完全相同。
核心要点
- QUIC 将传输层和 TLS 握手合并为一次往返。 QUIC 客户端发送的第一个 UDP 数据包——Initial 数据包——携带一个 TLS 1.3 ClientHello,因此 JA3/JA4 从 TCP ClientHello 中读取的一切,在这里同样存在。
- QUIC 传输参数是一种全新的、协议专属的信号。 它们作为
quic_transport_parametersTLS 扩展(RFC 9001)搭载在 ClientHello 中,暴露了初始流量控制限制、连接 ID 长度等 HTTP/2 没有对应项的实现特定值。 - JA4 已经原生支持 QUIC。 指纹的第一个字符记录了观察到的传输层:
q代表 QUIC,t代表 TCP,d代表 DTLS——参见 JA4 规范。q13d...指纹可以直接与同一客户端通过 TCP 产生的t13d...指纹进行比较。 - HTTP/3 自身的 SETTINGS 帧比 HTTP/2 的更精简,PRIORITY 帧也已消失——取而代之的是基于标头的可扩展优先级方案,而大多数客户端根本不发送它。
h3ALPN 值是最早出现的应用层信号:它出现在与 HTTP/2 的h2相同的 ClientHello 扩展中,只是搭载在 QUIC 而非 TCP 之上。
为何 QUIC 改变了指纹识别的格局
HTTP/2 指纹识别之所以存在,是因为 TCP 和 TLS 是各自独立的协议:先由 TCP 握手建立连接,再由 TLS ClientHello 协商加密,然后——TLS 完成之后——客户端才发送 HTTP/2 自己的启动帧(SETTINGS、WINDOW_UPDATE、PRIORITY)。三个层,三次识别指纹的机会。
QUIC(RFC 9000)是一种基于 UDP 的传输协议,将连接建立和加密合并为一次交换。它的第一个数据包——Initial 数据包——在一个 CRYPTO 帧中携带完整的 TLS 1.3 ClientHello,直接集成到传输层握手中(RFC 9001),而非像以往那样叠加在传输层之上。HTTP/3(RFC 9114)则运行在已经建立好的 QUIC 连接之上,大量复用 QUIC 本身已提供的能力——流复用与流量控制——而不像 HTTP/2 那样必须为 TCP 重新定义它们。
这种重构意味着 QUIC 客户端暴露出:
- 一个 TLS 1.3 ClientHello,携带在 Initial 数据包中——与 JA3/JA4 已经在 TCP 上识别的相同的密码套件、扩展和 ALPN 值。
- QUIC 传输参数,这次握手特有的一个新 TLS 扩展。
- 一个小得多的 HTTP/3 SETTINGS 帧,在连接建立后通过单向控制流发送。
QUIC 传输参数:新出现的信号
每个 QUIC 客户端都必须在其 ClientHello 中发送 quic_transport_parameters 扩展(代码点 0x39),声明诸如 initial_max_data、initial_max_stream_data、max_idle_timeout 和 active_connection_id_limit 等值。这些参数描述了客户端愿意支持的连接限制——与 HTTP/2 的 SETTINGS 值类似,具体实现所选择的默认值在不同 QUIC 库之间存在差异。
Chrome 的 QUIC 协议栈、Firefox 的 neqo、quiche(被 curl 和 Cloudflare 使用)、msquic 以及 ngtcp2 各自采用不同的默认传输参数。由于这个扩展位于 JA3/JA4 已经在哈希计算的同一个 ClientHello 中,做指纹识别的服务器并不需要另建一套采集机制——只需在已经读取的数据包中多解析这一个扩展即可。
JA4 已经能识别 QUIC
TLS 指纹识别并不需要为 QUIC 发明新算法——它只需要多一个字段。JA4 规范将观察到的传输层记录为指纹的第一个字符:q 代表 QUIC,d 代表 DTLS,t 代表普通的 TCP 上的 TLS。该字符之后的一切——TLS 版本、密码哈希和扩展哈希——都以完全相同的方式计算,与传输层无关,因为它读取的仍是相同的 ClientHello 结构。
这带来了一个实际的结果:一个已经在为 TLS 指纹识别检查 JA4 的服务器,不需要任何新逻辑就能对 QUIC 连接做指纹识别。它会自动得到一个 q13d... 风格的指纹,并可以直接与同一浏览器通过普通 TLS 产生的 t13d... 指纹进行比较——如果同一客户端的 TCP 和 QUIC 指纹在哈希部分出现不一致,这本身就是值得调查的信号。
HTTP/3 更精简的 SETTINGS 帧
HTTP/3 自己的 SETTINGS 帧(RFC 9114 §7.2.4)在 HTTP 层只定义了 SETTINGS_MAX_FIELD_SECTION_SIZE 一个参数。另外两个参数——SETTINGS_QPACK_MAX_TABLE_CAPACITY 和 SETTINGS_QPACK_BLOCKED_STREAMS——来自 QPACK,HTTP/3 的字段压缩方案(为支持乱序传递而对 HTTP/2 的 HPACK 做的重新设计),它们在单独的规范中定义。相比 HTTP/2 的六个标准 SETTINGS 参数加上一个 WINDOW_UPDATE 帧,这是一个小得多的暴露面:QUIC 本身已经在传输层通过上述传输参数处理了流量控制和流限制,因此 HTTP/3 不必像 HTTP/2 在普通 TCP 上那样,在应用层重新协商这些内容。
不再有 PRIORITY 帧
HTTP/2 的 PRIORITY 帧——已被 RFC 9113 废弃——在 HTTP/3 的核心帧集中完全没有对应项。流优先级的设定转而移到了可扩展优先级方案,该方案通过 HTTP 的 Priority 标头字段(或等效的帧)来表达紧急程度,而不是在连接建立之初使用专门的二进制帧。许多客户端根本不发送明确的优先级信号,而是依赖服务器的默认设置——这就去掉了早期 HTTP/2 指纹识别工作中曾经最具辨识度的字段之一。
与 HTTP/2 指纹识别的区别
| 信号 | HTTP/2(基于 TCP+TLS) | HTTP/3(基于 QUIC) |
|---|---|---|
| TLS ClientHello | 独立的 TCP + TLS 握手 | 嵌入在 QUIC Initial 数据包中 |
| 传输层参数 | 无(TCP 没有对应项) | quic_transport_parameters 扩展 |
| SETTINGS 帧 | 6 个标准参数 | 1 个 HTTP 参数 + 2 个 QPACK 参数 |
| 流优先级 | PRIORITY 帧(已废弃) | 可扩展优先级标头,常常不设置 |
| ALPN 值 | h2 | h3 |
| JA3/JA4 适用性 | 直接适用(TCP 上的 TLS) | 直接适用,带有 q 传输标记 |
实际结果是:HTTP/3 指纹识别比 HTTP/2 指纹识别更依赖 TLS 层,因为 QUIC 把过去那些独立的传输协商步骤,吸收进了 JA3/JA4 已经在读取的同一个 ClientHello 中。
在机器人和 VPN 检测中的应用
检测系统关注 QUIC 指纹识别有一个具体原因:越来越多访问 Google、由 Cloudflare 提供服务的站点及其他早期 HTTP/3 采用者的浏览器流量,默认就是通过 QUIC 而非 TCP 到达的。一个围绕伪造基于 TCP 的 TLS 握手构建的机器人框架,根本不会产生任何 QUIC 流量——这本身就很有信息量,因为一个真实的 Chrome 实例在访问支持 HTTP/3 的源站时,通常会协商使用 QUIC 而不是退回到 TCP。反过来,那些确实支持 QUIC 的工具(通过 quiche 或 msquic 绑定)仍然携带着该库特有的传输参数默认值和 QPACK 设置,可以像被 SETTINGS 值揭穿的伪造 HTTP/2 客户端一样,与真实浏览器不带 quiche 的协议栈区分开来。
对于 VPN 和代理检测,QUIC 带来了一个新变数:因为它运行在 UDP 之上,一些企业防火墙和较旧的中间设备会直接封锁它,迫使客户端退回到基于 TCP 的 HTTP/2。这种回退模式——一个 User-Agent 声称是支持 QUIC 的 Chrome 版本,却从未协商出 h3——本身就是一个可观察的信号,与指纹内容本身是分开的。
本文所述的 QUIC 和 TLS 传输指纹是在服务器端观察到的,因此不会暴露给页面 JavaScript——但它们与检测系统从你浏览器读取的客户端信号协同工作。你可以用 BrowserInsight 的机器人检测工具在自己身上查看这些信号:它会呈现你的 navigator.webdriver 状态、无头与自动化痕迹以及指纹一致性,每一项都配有通俗易懂的解释,且全部在客户端计算。
常见问题
我需要一个新工具才能看到自己的 QUIC 指纹吗?
从原理上说不需要——任何服务器端的 JA4 实现已经能产生一个,因为 QUIC 的 ClientHello 与 TCP 上的 TLS 一样被解析。改变的是数据包捕获发生的位置:QUIC 通过 UDP 到达,因此仅为捕获 TCP 流而构建的服务器或中间设备,如果不单独抓取 UDP,就看不到它。
机器人能通过拒绝使用 HTTP/3 来规避 QUIC 指纹识别吗?
只能部分做到,而且是拿一个信号换另一个信号。退回到 HTTP/2 可以避开 QUIC 特有的指纹识别,但一个真实的、访问支持 HTTP/3 站点的现代浏览器通常会协商使用 QUIC——因此一个声称是最新版 Chrome 或 Firefox、却始终不用 QUIC 的客户端,本身就是值得标记的异常。
像 ECH 这样加密 QUIC 握手,能隐藏这些信号吗?
Encrypted Client Hello 对网络观察者隐藏了内层 ClientHello 的敏感字段(比如 SNI),但目标服务器——它要解密握手才能提供服务——仍然能看到完整的 ClientHello、传输参数和 HTTP/3 SETTINGS。ECH 改变的是客户端与服务器之间的被动网络观察者能看到什么,并不改变源站服务器本身能识别的指纹内容。
HTTP/3 指纹识别今天有 HTTP/2 指纹识别那么成熟吗?
目前还没那么成熟,主要因为 QUIC 的采用率虽然在少数大型运营商中已相当可观,但仍小于普遍存在的 TCP+TLS。随着越来越多的源站和 CDN 默认终结 HTTP/3 连接,可以预期支持 QUIC 的指纹识别会像 JA3/JA4 和 Akamai 指纹一样,成为机器人检测体系中的标准组成部分。


