HTTP/2 指紋與 Akamai Fingerprint

HTTP/2 在送出任何請求之前就交換的連線層參數如何形成第二份獨立指紋,以及三方交叉驗證為何是最強的偵測手段。

📚 系列導覽:本篇是「Cloudflare 反爬蟲機制」系列第 3 篇,對應總覽篇六層偵測中的第 3 層(HTTP/2 連線層)。

# 筆記 主題
01 反爬蟲偵測總覽 六層偵測全景、請求時序地圖
02 TLS 指紋:JA3 與 JA4 ClientHello 為何洩漏身分、兩代演算法
03 HTTP/2 指紋與 Akamai Fingerprint(本篇) 連線層參數指紋、三方交叉驗證
04 Managed Challenge 與 Bot Score 主動挑戰機制、PAT、Cookie 綁定

目錄


什麼是 HTTP/2 指紋?

HTTP/2 指紋是把客戶端在 HTTP/2 連線建立階段送出的協定參數,濃縮成一個可比對的字串,用來識別客戶端使用的是哪一套 HTTP/2 實作。

一句話理解:TLS 指紋看「你怎麼開始加密」,HTTP/2 指紋看「你怎麼開始一個 HTTP/2 連線」。

這兩者是完全獨立的證據,由不同的軟體元件產生。這個獨立性正是它在偵測上的價值所在。

為什麼需要第二份指紋?

只有 TLS 指紋的問題:

  • 可自訂 ClientHello 的 TLS 函式庫已經存在,TLS 指紋不再是無法跨越的門檻
  • 單一訊號無法交叉驗證,一旦被模仿就完全失效
  • TLS 層看不到任何 HTTP 語意(走 h2 還是 h1、請求怎麼組織)

加入 HTTP/2 指紋之後:

  • 多一份需要獨立修補的證據——TLS 函式庫與 HTTP/2 函式庫通常是不同的元件
  • 兩份指紋必須「互相吻合」才合理(Chrome 的 TLS 指紋應該配 Chrome 的 h2 指紋)
  • 加上 User-Agent 的宣稱,形成三方比對

核心特點

HTTP/2 指紋 = SETTINGS + WINDOW_UPDATE + PRIORITY + Pseudo-header 順序

基礎補課:HTTP/1.1 與 HTTP/2 的差異

要理解 HTTP/2 指紋,必須先知道 HTTP/2 多了哪些東西。如果你已熟悉 HTTP/2,可以跳到為什麼更會洩漏身分。

HTTP/1.1:純文字,一次一個

HTTP/1.1 的請求就是可讀的文字:

GET /page HTTP/1.1
Host: example.com
User-Agent: ...
Accept: text/html

它的限制:

  • 一個連線同時只能處理一個請求。雖然 keep-alive 讓連線可以重複使用,但請求必須排隊,前一個回應沒回來、後一個就不能開始
  • 佇列頭部阻塞(head-of-line blocking):排在前面的慢請求會卡住後面所有請求
  • 標頭每次完整重傳:同一個連線上的每個請求都重複送一遍 User-Agent、Cookie,非常浪費
  • 瀏覽器的因應對策是同時開 6 個 TCP 連線到同一網域,硬撐平行度

HTTP/2:二進位框架,單連線多工

HTTP/2 把通訊改成二進位的框架(frame):

一個 TCP 連線
  ├─ Stream 1  ─── HEADERS frame ─── DATA frame ───▶  請求 A
  ├─ Stream 3  ─── HEADERS frame ─── DATA frame ───▶  請求 B
  ├─ Stream 5  ─── HEADERS frame ─── DATA frame ───▶  請求 C
  └─ 連線層     ─── SETTINGS ─── WINDOW_UPDATE ───▶  連線參數

主要改變:

特性 HTTP/1.1 HTTP/2
編碼 純文字 二進位框架
並行 需開多個 TCP 連線 單一連線多工(stream)
標頭 每次完整重傳 HPACK 壓縮
請求行 GET /page HTTP/1.1 拆成四個 pseudo-header
流量控制 依賴 TCP 協定層自己有一套
優先級 無 有(原機制已廢棄,見後文)
標頭大小寫 不強制 強制小寫

主要的 frame 類型

Frame 用途 指紋價值
SETTINGS 宣告連線層參數 🔴 極高
WINDOW_UPDATE 調整流量控制視窗 🔴 高
PRIORITY 宣告 stream 優先級與依賴 🔴 高
HEADERS 送出請求或回應的標頭 🟡 中(pseudo-header 順序)
DATA 送出主體資料 低
RST_STREAM 中止單一 stream 低
GOAWAY 關閉連線 低
PING 連線保活與延遲測量 低

連線的開場

HTTP/2 連線建立後,客戶端要先送一段固定的連線前言(connection preface),是一個magic 字串,接著立刻送出 SETTINGS frame。

TLS 握手完成(ALPN 協商為 h2)
  ↓
客戶端送出連線前言(固定字串,無指紋價值)
  ↓
客戶端送出 SETTINGS frame          ← 指紋開始
  ↓
客戶端可能送出 WINDOW_UPDATE       ← 指紋
  ↓
客戶端可能送出 PRIORITY frames     ← 指紋
  ↓
客戶端送出第一個 HEADERS frame     ← 這時才是「HTTP 請求」
     └─ pseudo-header 順序          ← 指紋
     └─ User-Agent 在這裡           ← 宣稱

注意這個順序:SETTINGS 在第一個 HTTP 請求之前就送出去了。這是 HTTP/2 指紋的關鍵——它不需要等你發請求。


為什麼 HTTP/2 比 HTTP/1.1 更會洩漏身分

HTTP/1.1 能洩漏的東西很有限

可觀察項 辨識力 能否輕易偽造
標頭名稱與順序 🟡 中 ✅ 純字串,照抄即可
標頭大小寫寫法 🟢 低 ✅ 可控制
Connection 標頭寫法 🟢 低 ✅ 可控制

HTTP/1.1 是純文字協定,所有可觀察的東西都在應用層,都是字串。只要願意,程式碼可以逐字模仿真瀏覽器的請求。

HTTP/2 多出了一整個「連線層」

HTTP/2 引入了應用程式通常不會碰到的協定層設定:

可觀察項 辨識力 能否輕易偽造
SETTINGS 參數的組合、值、順序 🔴 極高 ❌ 多數函式庫不開放設定
WINDOW_UPDATE 增量 🔴 高 ❌ 通常寫死
PRIORITY frame 的存在與樹狀結構 🔴 高 ❌ 需深度改造
pseudo-header 順序 🔴 高 ❌ 由 h2 函式庫產生
HPACK 索引策略 🟡 中 ❌ 由 h2 函式庫決定

兩個關鍵差異

差異 1:時序更早

SETTINGS 在任何 HTTP 請求之前送出。也就是說,伺服器在還不知道你要請求哪個路徑、也還沒看到你的 User-Agent 時,就已經知道你用的是哪一類 HTTP/2 實作了。

差異 2:控制權在函式庫

這是更本質的差異。以一段典型的程式碼為例,應用程式能設定的通常只有「網址、方法、標頭、主體」。至於 SETTINGS_INITIAL_WINDOW_SIZE 要送多少、要不要送 WINDOW_UPDATE、pseudo-header 用什麼順序排列——這些連 API 都沒有暴露出來,它們寫在 HTTP/2 函式庫的原始碼裡。

應用程式碼可控                函式庫寫死(不可控)
─────────────────            ──────────────────
網址                         SETTINGS 送哪些、值多少、順序
HTTP 方法                    是否送 WINDOW_UPDATE、增量多少
標頭名稱與值                  是否送 PRIORITY、樹的形狀
請求主體                      pseudo-header 排列順序
                             HPACK 的索引決策

這個分野與 TLS 指紋的情況完全平行——可控的部分不具決定性,具決定性的部分不可控。


指紋來源 1:SETTINGS frame

SETTINGS 是 HTTP/2 指紋最主要的來源。

有哪些參數

RFC 定義了六個標準參數:

編號 名稱 用途 協定預設值
0x1 SETTINGS_HEADER_TABLE_SIZE HPACK 動態表大小 4096
0x2 SETTINGS_ENABLE_PUSH 是否接受伺服器推送 1(接受)
0x3 SETTINGS_MAX_CONCURRENT_STREAMS 最多同時幾個 stream 無限制
0x4 SETTINGS_INITIAL_WINDOW_SIZE 每個 stream 的初始流量視窗 65535
0x5 SETTINGS_MAX_FRAME_SIZE 單一 frame 最大位元組 16384
0x6 SETTINGS_MAX_HEADER_LIST_SIZE 標頭清單最大位元組 無限制

三重指紋訊號

同一個 SETTINGS frame 提供三層獨立的辨識訊號:

1. 送出哪些參數(集合)
   有的實作只送 2 個,有的送 5 個
   「完全不送 SETTINGS」本身也是一種特徵

2. 每個參數的值(數值)
   視窗大小、表格大小的具體數字差異很大
   這些數字是各實作經過效能調校後寫死的

3. 參數的排列順序(序列)
   RFC 未規定順序,各實作固定送出自己的順序

代表性差異(示意)

瀏覽器類型 A(示意)
  HEADER_TABLE_SIZE   = 65536      ← 遠大於預設的 4096
  ENABLE_PUSH         = 0          ← 明確拒絕推送
  INITIAL_WINDOW_SIZE = 6291456    ← 6 MB,為了大檔案吞吐調校過
  MAX_HEADER_LIST_SIZE= 262144

瀏覽器類型 B(示意)
  HEADER_TABLE_SIZE   = 65536
  INITIAL_WINDOW_SIZE = 131072     ← 128 KB,明顯不同的調校取向
  MAX_FRAME_SIZE      = 16384

某通用 HTTP 函式庫(示意)
  INITIAL_WINDOW_SIZE = 65535      ← 直接沿用 RFC 預設值
  (只送一個參數)

⚠️ 上述數值為示意,實際值依軟體版本而異,且會隨改版變動。這裡要看的是模式差異,不是背下具體數字。

這裡最值得注意的模式

「沿用協定預設值」本身就是強烈訊號。

真實瀏覽器經過長年的效能調校,幾乎不會使用 RFC 的預設值——它們的視窗大小、表格大小都是為了實際網頁載入情境特別調整過的。相對地,通用 HTTP 函式庫的作者通常沒有這種調校動機,直接用預設值或只調一兩項。

所以看到「只送一兩個參數、且值等於 RFC 預設」時,基本可以判定不是瀏覽器。而這一點不需要任何指紋資料庫就能判斷——這與 JA4 的 a 段可讀性是同一種思路。


指紋來源 2:WINDOW_UPDATE

流量控制在做什麼

HTTP/2 有自己的流量控制機制,獨立於 TCP。接收方宣告「我還能接收多少位元組」,發送方不能超過這個量。

SETTINGS_INITIAL_WINDOW_SIZE 設定的是每個 stream 的初始視窗。但還有一個連線層級的視窗,它的初始值固定為 65535(RFC 規定,不能用 SETTINGS 改),要調大只能靠送出 WINDOW_UPDATE frame 追加增量。

連線層視窗初始值:65535(協定規定,不可改)
  ↓
客戶端送 WINDOW_UPDATE,增量 = 15663105(示意)
  ↓
連線層視窗變成:65535 + 15663105 ≈ 15 MB

指紋價值

這個增量是一個單一的、寫死的數字,辨識力相當高:

觀察 含義
送出一個特定的大增量 對應某個特定的瀏覽器實作
完全不送 WINDOW_UPDATE 沿用 65535 的預設視窗——多數通用 HTTP 函式庫的行為
送出不同的增量值 對應另一種實作

為什麼瀏覽器要調大?因為 64 KB 的連線層視窗在現代網頁(動輒數 MB 的資源)下會頻繁觸發流量控制,嚴重拖慢載入。瀏覽器有強烈動機調大它;而一個只抓 HTML 的爬蟲函式庫沒有這個動機。

同樣的邏輯再次出現:效能調校的痕跡本身就是「這是真瀏覽器」的證據。


指紋來源 3:PRIORITY 與依賴樹

這是四個來源中辨識力最強、也最難模仿的一個。

瀏覽器為什麼需要優先級

一個網頁包含 HTML、CSS、JavaScript、圖片、字型。它們對「首次可見」的重要性差異巨大:

關鍵路徑:HTML → CSS → 阻塞渲染的 JS   ← 沒有這些,畫面一片空白
次要:    字型、可見區域的圖片            ← 影響體驗但不阻塞
最低:    可見區域外的圖片、預先載入      ← 可以最後再說

在 HTTP/1.1 時代,瀏覽器靠「開幾個連線、先發哪個請求」來間接控制。HTTP/2 把所有請求塞進單一連線後,就必須有明確的機制告訴伺服器「這些 stream 誰先誰後」。

原始機制:依賴樹

RFC 7540 定義的機制是一棵依賴樹:每個 stream 可以宣告「我依賴哪個 stream」以及「權重」(1–256)。

             Stream 0(連線根節點)
                    │
        ┌───────────┼───────────┐
     Stream 3     Stream 5    Stream 7
     權重 256     權重 220    權重 147
     (CSS)      (JS)      (字型)
                    │
                Stream 9
                權重 110
                (圖片)

關鍵行為:某些瀏覽器在連線一開始、還沒發出任何實際請求時,就先送出一組 PRIORITY frame 把這棵樹的骨架建好,之後的請求再掛到對應節點上。

為什麼辨識力極強

真瀏覽器
  ✅ 連線初期送出一組 PRIORITY frame
  ✅ 樹的形狀、stream ID、權重值高度固定
  ✅ 不同瀏覽器的樹形狀明顯不同(各自的資源排程策略)

通用 HTTP 函式庫
  ❌ 完全不送 PRIORITY
  理由很直接:它不是在載入網頁,沒有多種資源要排程

「有沒有送 PRIORITY」幾乎是一道二元的判別線。而且要偽造不只是「送一個 frame」——必須複製正確的樹狀結構、正確的 stream ID 分配、正確的權重值,並且在後續請求中維持一致的掛載行為。多數 HTTP 函式庫連 API 都沒有。

協定演進的注意事項

RFC 7540 的優先級機制在實務上被認為過於複雜、實作差異大、效果不佳。後續發展:

  • RFC 9113(2022,HTTP/2 的修訂版)將原優先級機制標記為已廢棄
  • RFC 9218 定義新的「可擴充優先級」(Extensible Priorities),改用 priority 標頭欄位與 PRIORITY_UPDATE frame

現代瀏覽器已陸續改用新機制。但這不代表這個指紋來源消失了——換成新機制之後,「送不送 priority 標頭、值怎麼設、什麼時候送 PRIORITY_UPDATE」同樣是實作特徵。指紋的載體變了,訊號依然存在。

這是指紋技術的一般規律:只要協定留下實作自由度,就會留下指紋。


指紋來源 4:Pseudo-header 順序

這是最容易理解、卻出乎意料有效的一個。

什麼是 pseudo-header

HTTP/1.1 的請求行是一整行文字:

GET /page HTTP/1.1
Host: example.com

HTTP/2 取消了請求行,把資訊拆成四個特殊標頭,名稱以冒號開頭,稱為 pseudo-header:

Pseudo-header 對應 HTTP/1.1 的什麼
:method 請求行的方法(GET)
:path 請求行的路徑(/page)
:scheme 協定(https)
:authority Host 標頭

RFC 沒有規定順序

RFC 要求 pseudo-header 必須排在所有普通標頭之前,但四者之間的相對順序沒有規定。於是每個實作各自選了一個順序並寫死。

用四個字母的首字表示(m = method、a = authority、s = scheme、p = path):

順序(示意) 縮寫
:method :authority :scheme :path m,a,s,p
:method :path :authority :scheme m,p,a,s
:method :scheme :path :authority m,s,p,a
:method :path :scheme :authority m,p,s,a

⚠️ 具體哪個順序對應哪個軟體會隨版本變動,這裡的重點是「不同實作用不同順序,且各自固定」。

為什麼這麼有效

  • 只有 4 個元素,24 種排列——資訊量看似很小
  • 但它完全不需要額外資料:每個 HTTP/2 請求都必然帶著這四個 pseudo-header
  • 而且它把主流瀏覽器與主流 HTTP 函式庫分到不同的桶子裡
  • 對應用程式碼而言完全不可控:你在程式裡設定標頭的順序,不會影響函式庫排列 pseudo-header 的順序

四個字母,零成本採集,且能把「瀏覽器」與「函式庫」有效分群——這是投報率極高的一個訊號。

附帶訊號:HPACK 與普通標頭順序

HTTP/2 用 HPACK 壓縮標頭。HPACK 有一個 61 項的靜態表(預先定義的常見標頭),加上一個連線期間動態增長的動態表。

雖然標頭被壓縮了,但解壓後的順序完整保留且可觀察。此外還有更細緻的訊號:

  • 哪些標頭被放進動態表建立索引、哪些以字面值傳送——這是實作的決策
  • 動態表大小的使用策略

真瀏覽器的標頭順序高度固定且有明確規律;手寫的客戶端常常是字典序,或是程式碼裡的插入順序。


Akamai Fingerprint 格式組成

Akamai 在 2017 年的一份 HTTP/2 被動指紋研究中提出了一個標準化的表示格式,後來被廣泛採用,通稱 Akamai fingerprint 或 HTTP/2 fingerprint。

格式

四段,用 | 分隔:

SETTINGS | WINDOW_UPDATE | PRIORITY | PSEUDO_HEADER_ORDER
段 內容 缺失時
第 1 段 SETTINGS 的 編號:值,多組用 ; 分隔,保持送出順序 留空
第 2 段 連線層 WINDOW_UPDATE 的增量 00
第 3 段 PRIORITY frame,格式 streamId:獨占:依賴的streamId:權重,多組用 , 分隔 0
第 4 段 pseudo-header 的首字母,用 , 分隔 —

範例對照(示意)

瀏覽器類型(示意)
1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
└────────────┬───────────────┘ └───┬──┘ │ └──┬──┘
   送 4 個 SETTINGS,值皆調校過   調大視窗 │  瀏覽器順序
                                        └─ 此例未送 PRIORITY


某通用 HTTP 函式庫(示意)
4:65535|0|0|m,p,s,a
└──┬──┘ │ │ └──┬──┘
只送 1 個 │ │  函式庫順序
值為 RFC  │ └─ 不送 PRIORITY
預設值    └─ 不送 WINDOW_UPDATE

怎麼讀這個字串

不必背任何具體值,看四個特徵就能判斷:

1. SETTINGS 段有幾組?
   4 組以上且值明顯經過調校 → 像瀏覽器
   1-2 組且值等於 RFC 預設   → 像通用函式庫

2. WINDOW_UPDATE 是 00 嗎?
   00 → 沿用 64KB 預設,不在意大檔案吞吐 → 像函式庫
   大數值 → 為網頁載入調校過 → 像瀏覽器

3. PRIORITY 段是 0 嗎?
   0 → 沒有資源排程需求 → 像函式庫
   有樹狀結構 → 在載入複雜網頁 → 像瀏覽器

4. 第 4 段的順序,對應哪一類實作?

這四個問題全都不需要查表,這正是這個格式設計得好的地方——它跟 JA4 的 a 段一樣,把可讀性當成一等公民。


三方交叉驗證:最強的偵測手段

這一節是本篇、也是整個系列的結論。

三份獨立證據

到目前為止,我們有三份在不同階段、由不同軟體元件產生的證據:

階段 3:TLS 握手
  JA4 指紋 ← 由 TLS 函式庫產生(OpenSSL / BoringSSL / NSS / crypto-tls)

階段 4:HTTP/2 連線初始化
  Akamai 指紋 ← 由 HTTP/2 函式庫產生(nghttp2 / h2 / net-http2 / 瀏覽器內建)

階段 5:HTTP 請求
  User-Agent ← 由應用程式碼填寫

三者由不同元件負責,彼此之間沒有任何機制保證一致。

矛盾偵測

案例:一個聲稱是現代瀏覽器的請求

User-Agent  說:「我是 Chrome 140 / macOS」
JA4         說:無 ALPN、擴充數量偏少、加密套件雜湊對應 OpenSSL 系
Akamai 指紋 說:只送 1 個 SETTINGS(值為 RFC 預設)、不送 WINDOW_UPDATE、
                不送 PRIORITY、pseudo-header 順序對應通用函式庫

     ↓

三份證據指向三個不同的軟體堆疊 → 高度可信的偽裝判定

為什麼矛盾比單一訊號更有力:

情況 判斷 為什麼
誠實的 API 客戶端(UA 寫 python-requests) 🟢 低風險 三份證據一致:它確實是 Python
真實瀏覽器 🟢 低風險 三份證據一致
UA 寫 Chrome,但底層是 Python 🔴 高風險 三份證據矛盾,且矛盾方向顯示刻意偽裝

注意第一列:誠實反而更安全。這常常違反直覺,但符合防禦邏輯——偵測系統要找的不是「機器人」(機器人多得是且大部分合法),而是「假裝不是機器人的機器人」。

為什麼難以修補

要讓三方一致,必須讓整條網路堆疊都對應同一個目標軟體:

需要一致的層                     修補難度
──────────────────              ──────────
TLS 實作(ClientHello)          需可自訂 ClientHello 的函式庫
HTTP/2 實作(連線層參數)         需可自訂 SETTINGS/WINDOW_UPDATE/PRIORITY
HTTP 標頭                        照抄即可
JavaScript 執行環境(系列 04)    需真實瀏覽器

這解釋了一個實務上常見的挫折:專門處理 TLS 指紋的工具只解決了三分之一。它讓 JA4 對上了,但如果 HTTP/2 連線層仍由原本的函式庫負責,第二份證據依然矛盾——而且此時矛盾模式更奇特(TLS 像瀏覽器、h2 像函式庫),在統計上反而更顯眼。

真瀏覽器的天然優勢

真實瀏覽器
  TLS 層、HTTP/2 層、JS 引擎 → 都是同一個軟體的組成部分
  → 一致性是免費的,不需要任何努力

模仿方
  需要讓多個獨立元件同時對應同一個目標
  → 每一層都要維護,且目標軟體每次改版都要跟上

這個不對稱是整套偵測體系的基礎。它不是靠某個聰明的演算法,而是靠架構上的成本差異。


常見問題

問題 1:把 HTTP/2 關掉、改用 HTTP/1.1 會不會比較不容易被偵測?

回答:不會,反而更可疑。

表面邏輯是對的:HTTP/1.1 可觀察的東西少得多。但這忽略了 ALPN。

現代瀏覽器在 TLS 握手時,一律透過 ALPN 擴充宣告支援 h2。而這件事會被記錄在 JA4 的 a 段第 9-10 碼(見系列第 02 篇)。

一個聲稱是 Chrome 的請求,卻走 HTTP/1.1
  → JA4 的 a 段顯示 ALPN 不是 h2
  → 「現代瀏覽器不協商 HTTP/2」本身就是矛盾
  → 你少了 HTTP/2 指紋這份證據,但同時製造了一個新矛盾

淨效果是負的:減少一份證據,換來一個明確的異常訊號。


問題 2:HTTP/1.1 就完全沒有指紋嗎?

回答:有,但弱得多,且都是可偽造的字串層面。

可觀察項 說明
標頭順序 真瀏覽器順序固定;手寫客戶端常是字典序或插入序
標頭大小寫 HTTP/1.1 不強制小寫,各實作寫法有差異(User-Agent vs user-agent)
標頭之間的空白處理 冒號後的空格數等細節
Connection 標頭 送不送、值怎麼寫

這些全都在應用層,理論上都能逐字模仿。這正是 HTTP/2 指紋更有價值的原因——它把觀察點推到了應用程式碼碰不到的層次。


問題 3:HTTP/3(QUIC)有指紋嗎?

回答:有,而且結構上是類似的。

HTTP/3 跑在 QUIC 上,QUIC 把傳輸與加密整合在一起。可指紋化的部分包括:

  • QUIC transport parameters:類似 HTTP/2 的 SETTINGS,在連線建立時交換,同樣由實作寫死
  • QUIC Initial packet 的結構特徵
  • HTTP/3 自己的 SETTINGS frame
  • TLS 1.3 的 ClientHello(QUIC 內嵌 TLS 1.3 握手)——JA4 的第 1 碼 q 就是為此設計的

一般規律再次適用:只要協定給實作留下自由度,就會留下指紋。換協定不會消除指紋,只會換一組指紋。


問題 4:常見誤解整理

誤解 1:HTTP/2 指紋只是 TLS 指紋的補充,重要性不高。

正確理解:它是一份獨立的證據,由不同的函式庫產生。獨立性正是它的價值——這使它必須被獨立修補,也使交叉驗證成為可能。

誤解 2:只要把標頭順序調成跟瀏覽器一樣,HTTP/2 指紋就對上了。

正確理解:標頭順序只是附帶訊號。四個主要來源(SETTINGS、WINDOW_UPDATE、PRIORITY、pseudo-header 順序)全部在應用程式碼碰不到的層次,需要函式庫本身支援自訂才能改變。

誤解 3:PRIORITY 機制已被 RFC 廢棄,所以這個指紋來源沒用了。

正確理解:原機制被廢棄,但取代它的可擴充優先級(RFC 9218)同樣有實作自由度。「送不送 priority 標頭、值怎麼設」是新的指紋載體。載體變了,訊號還在。

誤解 4:Akamai fingerprint 是 Akamai 專有的技術,只有 Akamai 用。

正確理解:它是一種表示格式(如何把觀察到的參數寫成字串),由 Akamai 的研究提出並公開,任何人都能計算與使用。它跟具體廠商無關。


總結

核心要點

HTTP/2 指紋 = 四個連線層來源的組合

  SETTINGS       → 送哪些、值多少、什麼順序   🔴 極高辨識力
  WINDOW_UPDATE  → 增量多少、送不送           🔴 高
  PRIORITY       → 送不送、樹的形狀           🔴 高(近二元判別)
  Pseudo-header  → 四個標頭的排列順序         🔴 高(零成本採集)

Akamai 格式:SETTINGS|WINDOW_UPDATE|PRIORITY|PSEUDO_ORDER

三方交叉驗證(本系列主線):
  JA4(TLS 函式庫)
  Akamai 指紋(HTTP/2 函式庫)      → 三者必須一致才合理
  User-Agent(應用程式碼)

理解重點

  1. 時序比 TLS 稍晚,但仍在請求之前:SETTINGS 在第一個 HTTP 請求之前送出。伺服器在還不知道你要什麼路徑時,就知道你用哪類實作了。

  2. 效能調校的痕跡就是身分證明:瀏覽器把視窗調到 6 MB、把 HPACK 表調到 64 KB,是因為要載入複雜網頁。一個只抓 HTML 的函式庫沒有這種動機,於是沿用 RFC 預設值——而「沿用預設值」本身就是特徵。

  3. 獨立性是核心價值:TLS 指紋與 HTTP/2 指紋由不同函式庫產生,沒有任何機制保證兩者一致。這使交叉驗證成為可能,也使模仿必須逐層進行。

  4. 矛盾比單一訊號更有力:誠實回報自己是 Python 的客戶端風險低;TLS 像 Python 卻聲稱是 Chrome 的客戶端風險高。偵測系統要找的是偽裝,不是機器人。

  5. 關掉 HTTP/2 不是解法:那會讓 JA4 的 ALPN 欄位露出矛盾,用一份證據換一個異常訊號,淨效果為負。

  6. 協定演進不會消除指紋:PRIORITY 被廢棄了,新機制照樣有實作自由度。只要有自由度,就有指紋。

快速記憶

方面 說明
是什麼 HTTP/2 連線建立階段的協定參數組合
什麼時候採集 第一個 HTTP 請求之前
為什麼難偽造 四個來源全在 HTTP/2 函式庫內部,應用程式碼無 API 可控
辨識力最強的單項 PRIORITY 送不送(近乎二元判別)
投報率最高的單項 pseudo-header 順序(4 個字母,零成本)
最強用法 與 JA4、User-Agent 三方交叉驗證
最大的誤解 以為改標頭順序就能修好

學習路徑

1. 建立六層全景                   ✓ 系列 01
2. 理解 TLS 層的第一份證據         ✓ 系列 02
3. 理解 HTTP/2 層的第二份獨立證據  ✓ 本篇
4. 理解主動挑戰在檢驗什麼          → 系列 04

延伸閱讀


建立日期:2026-09-07 最後更新:2026-09-07

🔗相關文章