了解 HTTP/2 指纹的工作原理:SETTINGS 帧、窗口增量与伪标头顺序,以及 Akamai 指纹如何与 JA3/JA4 配合检测机器人。
每一个 HTTPS 连接都先经历 TCP 握手和 TLS 协商,但 HTTP/2 指纹识别并不在这两个阶段进行。HTTP/2 指纹识别捕获的是客户端在 TLS 建立之后——在 HTTP/2 会话开始的最初几毫秒内,在任何请求发出之前——所发送的帧的特征。客户端发送的 SETTINGS 帧、流量控制增量和伪标头顺序的精确组合,由其 HTTP/2 实现决定,而非由用户决定,并在请求之间保持一致。这种一致性,就是它成为指纹的原因。
核心要点
- HTTP/2 客户端在发出第一个请求之前就已自我暴露。 初始 SETTINGS 帧、WINDOW_UPDATE 增量和 PRIORITY 帧由 HTTP/2 库决定,而非由用户决定,从而在 Chrome、Firefox、Safari 和自动化工具之间产生不同的特征签名。
- Akamai 指纹将四个信号——SETTINGS 值、窗口增量、PRIORITY 帧和伪标头顺序——编码为一个紧凑字符串,用于识别底层 HTTP 客户端。
- 它能捕获通过了 TLS 和 User-Agent 检查的机器人。 使用
curl-impersonate仿造 Chrome TLS 握手的脚本,通常仍然无法通过 HTTP/2 层的检查,除非它还完整模拟了 Chrome 的帧序列和伪标头顺序。 - 与 JA3/JA4 配合使用,两种指纹覆盖互补的层:TLS 识别加密库;HTTP/2 指纹识别 HTTP 实现。两者之间的不一致是强烈的机器人信号。
- PRIORITY 帧——历史上 HTTP/2 指纹中最具辨识度的部分——已在 RFC 9113 中被废弃,因此现代指纹更多地依赖 SETTINGS 值和伪标头顺序。
HTTP/2 是什么,为何会产生指纹?
HTTP/2(RFC 7540,由 RFC 9113 更新)用二进制分帧层取代了 HTTP/1.1 纯文本、一次一个请求的模型。所有通信通过带编号的流在单个 TCP 连接上进行,由二进制帧携带——每种帧都有类型:DATA、HEADERS、SETTINGS、WINDOW_UPDATE、PRIORITY 等。
当客户端打开一个 HTTP/2 连接时,它在能够发出任何请求之前,会遵循规定的启动序列:
- 发送 HTTP/2 连接序言——一个固定的 24 字节魔术字符串。
- 发送 SETTINGS 帧,声明本次连接的客户端参数选择。
- 可选地发送 WINDOW_UPDATE 帧,增大初始流量控制窗口。
- 可选地发送 PRIORITY 帧,建立流依赖树。
- 发送第一个携带实际 HTTP 请求的 HEADERS 帧。
步骤 2–4 发生在服务器响应之前,也在任何用户编写的代码运行之前。它们完全由 HTTP/2 库决定——而且不同实现之间的差异在会话、URL 和 IP 地址间保持稳定。
Akamai 指纹捕获的四个信号
Akamai Technologies 的研究人员观察到这些启动信号对每个客户端来说足够稳定,可以作为指纹使用。Akamai 指纹将四个信号编码为一个以竖线分隔的字符串。一个典型的指纹如下所示:
1:65536;3:1000;4:6291456;6:262144|15663105|0|m,a,s,p
1. SETTINGS 帧值
SETTINGS 帧是一个 参数ID:值 对的列表。HTTP/2 定义了六个标准参数:HEADER_TABLE_SIZE(ID 1)、ENABLE_PUSH(2)、MAX_CONCURRENT_STREAMS(3)、INITIAL_WINDOW_SIZE(4)、MAX_FRAME_SIZE(5)和 MAX_HEADER_LIST_SIZE(6)。客户端只发送它想覆盖协议默认值的参数。
出现哪些参数、它们的值及其顺序的组合由 HTTP/2 库决定。Chrome 基于 BoringSSL 的 HTTP 栈选择的值和顺序,与 Firefox 的 Necko、Safari 的 CFNetwork 或 Python 的 httpx 不同。即使是微小的版本更新,也可能改变包含哪些参数或其携带的值。Akamai 指纹字符串的第一段——上方示例中的 1:65536;3:1000;4:6291456;6:262144——就编码了这些键值对。
2. WINDOW_UPDATE 增量
HTTP/2 的流量控制系统从默认连接级窗口 65,535 字节开始。大多数客户端立即发送一个 WINDOW_UPDATE 帧,将其增加到更大的工作大小,所选择的具体增量是该客户端的特征。缺失 WINDOW_UPDATE 或不寻常的增量值本身就是一个信号——许多简化 HTTP 库完全省略这一帧,而主流浏览器则发送一个大的、库特有的增量值。指纹的第二段编码这一值。
3. PRIORITY 帧序列
HTTP/2 最初允许客户端使用 PRIORITY 帧声明流优先级,建立依赖树,让服务器知道优先发送哪些响应。浏览器发送独特的启动模式:例如 Chrome 的历史模式包含针对流 3、5、7、1 和 11 的特定 PRIORITY 帧序列,各有规定的权重。这些模式极为稳定,与通常完全省略 PRIORITY 帧的非浏览器 HTTP 客户端截然不同。
RFC 9113(2022)废弃了 HTTP/2 流优先级机制,因为实现起来很复杂且在实践中很少改善性能。现代客户端越来越多地省略 PRIORITY 帧。Akamai 指纹的第三段捕获这一模式(或其缺失),仍可区分较旧的浏览器版本与无头或脚本化的 HTTP 客户端。
4. 伪标头顺序
HTTP/2 请求将方法、路径、协议方案和主机编码为伪标头——:method、:path、:scheme 和 :authority——在 HEADERS 帧中必须位于普通标头之前。协议要求这四个伪标头都出现在普通标头之前,但彼此之间的顺序由实现决定。
浏览器始终使用特定的顺序。Chrome 通常发送 :method、:authority、:scheme、:path——在指纹中编码为 m,a,s,p。Go、Python 和 Node.js 中的 HTTP 库通常按不同顺序发出这些伪标头,或者顺序因版本而异。第四段捕获这一顺序,结合 SETTINGS 信号,它是区分真实浏览器客户端与自动化库最稳定的指标之一。
HTTP/2 指纹如何与 TLS 指纹互补
TLS 指纹识别(JA3/JA4)从 ClientHello 消息中提取签名——该消息在 TLS 会话加密之前以明文发送。HTTP/2 指纹识别则从 TLS 建立后立即发送的二进制帧中提取签名。两者在相邻层上运作,互无重叠:
| 层 | 技术 | 捕获内容 |
|---|---|---|
| TLS | JA3 / JA4 | ClientHello 中的密码套件、扩展、椭圆曲线 |
| HTTP/2 | Akamai 指纹 | SETTINGS 值、窗口增量、PRIORITY 序列、伪标头顺序 |
成功使用 curl-impersonate 等工具克隆 Chrome TLS 握手的机器人,仍然会从其底层 HTTP 库发送 HTTP/2 帧——不同的 SETTINGS 集和伪标头顺序,任何具备指纹感知能力的服务器都能将其与真实浏览器区分开来。将两种指纹叠加,比单独使用任一种都更难绕过。
这与机器人检测技术指南中描述的复合层原则相同:可观察协议层之间的不一致,比任何单一异常都更有力地证明存在欺骗行为。
HTTP 标头顺序:一个相关信号
除了二进制分帧信号之外,HEADERS 帧中普通 HTTP 标头的顺序也携带信息。Chrome、Firefox 和 Safari 各自按照由浏览器请求准备逻辑决定的固定顺序发出标头——Accept、Accept-Language、Accept-Encoding、User-Agent、Upgrade-Insecure-Requests——而非由页面或用户决定。自动化库则按不同的默认顺序发出标头,通常是按字母顺序或按代码中添加的顺序。
标头顺序与 Python 的 requests 或 Go 的 net/http 匹配而非 Chrome 标准顺序的请求,很可能不是真实浏览器,无论 User-Agent 字段声称什么。标头顺序指纹在 HTTP/1.1 和 HTTP/2 上都有效,通常与二进制分帧信号结合使用,获得更丰富的综合评分。
实际测试
与 Canvas 或 WebGL 等浏览器指纹识别信号不同——这些信号可以通过页面内的 JavaScript 读取——HTTP/2 指纹识别本质上是服务器端的。SETTINGS 和 WINDOW_UPDATE 帧是二进制分帧内容,永远不会到达页面的 JavaScript 上下文,因此测试需要一个在将连接交给应用层之前读取和解释这些原始帧的服务器。
BrowserLeaks 的 /http2 端点显示你的实时 Akamai 指纹字符串——你的当前客户端正在发送的四段值。BrowserInsight 的机器人检测工具涵盖互补的客户端信号(自动化痕迹、无头浏览器泄露、指纹一致性)。TLS 层则通过网络诊断页面单独呈现。
常见问题
HTTP/3 有类似的指纹吗?
有,但信号有所不同。HTTP/3 运行在 QUIC 而非 TCP+TLS 之上,QUIC 携带自己的连接参数——嵌入在 QUIC Initial 数据包中的传输参数——因实现而异。HTTP/3 的 SETTINGS 帧由 QPACK 控制流携带,提供与 HTTP/2 SETTINGS 类似的信号。随着 HTTP/3 越来越普及,主动研究正在将 HTTP/2 式指纹识别扩展到 HTTP/3。
机器人能通过伪造 User-Agent 来规避 HTTP/2 指纹识别吗?
不能。User-Agent 标头由应用代码在 HEADERS 帧中设置,但 SETTINGS 帧、WINDOW_UPDATE 和 PRIORITY 帧由 HTTP/2 库在任何应用代码运行之前发出。伪造 Chrome User-Agent 同时使用 Go 的 net/http 或 Python 的 httpx 的机器人,仍然发送该库的 SETTINGS 值和伪标头顺序,而非 Chrome 的。
HTTP/2 指纹识别今天在生产中使用吗?
是的。主要 CDN、机器人管理厂商和大型网站将其与 TLS 指纹识别和行为信号结合使用,作为多层机器人评分的一部分。由于它不需要执行 JavaScript,且在服务器发送任何响应之前就捕获了帧,因此在没有精确的库级仿真的情况下特别难以规避。
既然 PRIORITY 对指纹如此有用,为何要废弃它?
PRIORITY 在 RFC 9113 中被移除,原因与指纹识别完全无关——实现者发现它难以正确支持,且在实践中对延迟几乎没有可测量的改善。废弃减少了现代客户端指纹的一个维度,但这也意味着任何仍在发送旧 PRIORITY 模式的客户端,很可能运行的是较旧的浏览器版本,这本身就是一个有用的分类信号。


