Dev.to Security 🔐 Cybersecurity 👁 0 📖 1 min read

HTTP/2 是如何做反爬的?

HTTP/2 是如何做反爬的? HTTP/2 反爬的原理是:客户端建立连接时,会按自己的实现发出一组底层参数和帧,这组特征相当稳定,能当作指纹识别客户端类型。服务器把这个指纹和请求头里声称的 User-Agent 做交叉验证。 一、HTTP/2 连接建立过程与指纹来源 客户端 → 服务器: 连接前缀 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n SETTINGS 帧 ← 指纹

HTTP/2 是如何做反爬的?

HTTP/2 反爬的原理是:客户端建立连接时,会按自己的实现发出一组底层参数和帧,这组特征相当稳定,能当作指纹识别客户端类型。服务器把这个指纹和请求头里声称的 User-Agent 做交叉验证。

一、HTTP/2 连接建立过程与指纹来源

客户端 → 服务器:

  1. 连接前缀 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
  2. SETTINGS 帧 ← 指纹来源 ①
  3. WINDOW_UPDATE 帧 ← 指纹来源 ②
  4. PRIORITY 帧(部分浏览器会发)← 指纹来源 ③
  5. HEADERS 帧(第一个请求)← 指纹来源 ④ 伪头部顺序、⑤ 普通头部顺序

RFC 只规定了这些帧"可以怎么发",具体发哪些参数、取什么值、按什么顺序,由各家实现自己决定,所以每种客户端都有自己的特征。

① SETTINGS 帧:参数、取值和顺序

ID 参数名 含义
1 HEADER_TABLE_SIZE HPACK 压缩表大小
2 ENABLE_PUSH 是否允许服务器推送
3 MAX_CONCURRENT_STREAMS 最大并发流数
4 INITIAL_WINDOW_SIZE 每个流的初始流控窗口
5 MAX_FRAME_SIZE 最大帧大小
6 MAX_HEADER_LIST_SIZE 最大头部列表大小

不同客户端的差别举例(具体数值随版本变化,下面只是示意):

  • Chrome:发 1、2、4、6 四项,ENABLE_PUSH=0,INITIAL_WINDOW_SIZE 约 6MB
  • Firefox:发 1、4、5,窗口约 128KB
  • Go net/http、Python h2/httpx、curl:参数集合和取值各不相同

带哪些参数、参数顺序、具体取值,这三点组合起来已经能分辨大多数客户端。

② WINDOW_UPDATE 增量

连接级流控窗口一开始是 65535,客户端通常马上发一个 WINDOW_UPDATE 把它调大。增量值各家不同:

  • Chrome 常见的是 15663105(加上初始值约 15MB)
  • Firefox 是另一个值
  • 很多库则根本不发

③ PRIORITY 帧和流优先级

  • 旧版 Firefox 一连接就发若干 PRIORITY 帧,建一棵"空闲流依赖树",Chrome 不发。
  • 请求 HEADERS 帧里的优先级字段(依赖哪个流、权重、是否独占)也各有习惯。
  • 新版浏览器开始改用 RFC 9218 的 priority 请求头(例如 u=0, i),这个头有没有、值是多少,也是特征。

④ 伪头部顺序

HTTP/2 用 4 个伪头部表示请求行,RFC 没有规定顺序:

客户端 顺序 缩写
Chrome :method :authority :scheme :path m, a, s, p
Firefox :method :path :authority :scheme m, p, a, s
Safari :method :scheme :path :authority m, s, p, a

这是区分度最高、最难伪造的特征之一。

⑤ 普通头部顺序和 HPACK 行为

  • sec-ch-ua、user-agent、accept、sec-fetch-* 等头的排列顺序是固定的。
  • 浏览器会把头名转成小写,并按固定策略决定哪些头放进 HPACK 动态表,哪些标记为"永不索引"(比如 Cookie)。这些编码细节也会被采集。

二、指纹怎么算:Akamai 格式

业界最常用的是 Akamai 2017 年公开的格式(BlackHat 论文 Passive Fingerprinting of HTTP/2 Clients):

SETTINGS | WINDOW_UPDATE | PRIORITY | 伪头部顺序

Chrome(示意):

1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p

把这个字符串做哈希,就得到一个短 ID,和 TLS 的 JA3/JA4 指纹放在一起使用。Cloudflare、Akamai、阿里云、腾讯云等的 WAF 都会提取类似特征。

三、服务器怎么用这个指纹判断

单看一个指纹没有意义,关键是交叉验证是否一致。

  1. 和 UA 对比
    UA 写的是 Chrome/131,HTTP/2 指纹却是 Python h2 的,或者是 Firefox 的 m,p,a,s,就说明在伪装。

  2. 和 TLS 指纹对比
    TLS 的 ClientHello 像 Chrome(JA4 吻合),HTTP/2 层却不像,说明用了只伪造 TLS 的工具,没伪造 HTTP/2。

  3. 协议降级
    现代 Chrome 访问支持 h2 的站点几乎一定走 HTTP/2。如果 UA 是 Chrome,ALPN 却只协商 http/1.1,或者根本没提供 h2,本身就是很强的异常信号。

  4. 同一会话里前后不一致(这条和我们项目直接相关)
    同一个 Cookie 或 token 会话里,页面 HTML、JS、图片都走 h2 并带 Chrome 指纹,只有 joblist.json 这一个接口突然走 HTTP/1.1、带 Node.js 的 TLS 指纹,后台很容易发现"这个 token 被转交给另一个程序在用"。

  5. 行为层面
    流 ID 递增方式、是否响应 PING、是否主动发 RST_STREAM、并发流的数量,也能用来辅助判断。

判定结果一般不会直接封禁,而是计入风险分,再结合 IP 信誉、请求频率、JS 环境检测等综合处理。轻的弹验证,重的返回错误码或假数据。BOSS 返回的 code=37 您的环境存在异常 就是风控拦截的一种表现,但目前没证据说明它依据的是哪个层面。

四、常见的应对方式

方式 原理 局限
直接用真实浏览器发请求 TLS、HTTP/2、JS 环境全部真实 资源占用大,速度慢
curl_cffi / curl-impersonate 改过的 curl,按浏览器参数配置 TLS 和 HTTP/2 帧 要跟着浏览器版本更新,JS 生成的 token 还得另外解决
tls-client(Go,基于 utls 加改造的 http2) 同上,可选的浏览器模板多 同上
普通 requests / Node https 只有 HTTP/1.1,TLS 是 OpenSSL 默认指纹 很容易被识别

五、回到我们项目

之前查过源码,route.fetch() 底层用的是 Node 的 http/https 模块(server/fetch.js:36-37),没有 http2。所以在 BOSS 的服务器看来:

  • 页面请求:TLS 和 HTTP/2 都是 Chrome 的特征
  • joblist.json:Node.js 的 TLS 指纹,HTTP/1.1,没有 HTTP/2 指纹,但带着同一个 __zp_stoken__ 和 Chrome 的 UA

这正好是第三节第 4 条说的"同一会话前后不一致"。目前本地能通过,说明 BOSS 对这个接口暂时没有严格校验这一层,或者对家庭宽带 IP 比较宽松。这不能保证在云服务器 IP 上也能通过,也不能保证以后一直有效。

如果容器里出现之前列的那些失败症状,我建议改成上次说的方案 A:用 CDP 的 Fetch 接口在响应阶段暂停请求再读数据。这样请求始终由 Chrome 发出,TLS 和 HTTP/2 指纹都是真实的。

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.