HTTP Client 與瀏覽器指紋:TLS ClientHello、JA3/JA4、HTTP/2 與 HTTP/3
將瀏覽器中的 API 呼叫搬到 Python 或 Rust 程式時,常見的做法是參考開發者工具中的 Request,複製 URL、HTTP Method、Header 與 Cookie。這些資訊描述了應用程式要送出什麼請求;即使內容一致,伺服器仍可能從連線中觀察到不同的 Client 特徵。
差異來自請求背後的連線過程。瀏覽器除了組合 HTTP 請求,還要透過底層網路元件建立連線、協商加密方式與 HTTP 版本。程式採用的網路元件也有自己的預設參數與處理順序;複製瀏覽器的 Header,不會同步套用瀏覽器底層的連線設定。
伺服器可以將這些由協定實作留下的特徵整理、比對,用來辨識相近的 Client 類型或發現異常組合,這就是 HTTP Client Fingerprinting。要理解這些特徵的來源,需要把應用程式設定的請求內容,和底層實際建立的連線分開觀察。
HTTP Client 指紋的分層模型
以一筆讀取帳號資料的 HTTPS 請求為例,Method、路徑與部分 Header 可以用 HTTP/1.1 文字格式表示:
GET /account HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 ... Chrome/140.0 ...
Cookie: session=example
其中,Host 指定目標主機,User-Agent 描述 Client 宣告的軟體資訊,Cookie 則攜帶應用程式狀態。這些欄位都位於 HTTP 層。若實際使用 HTTP/2 或 HTTP/3,相同請求語意會透過 binary frame、pseudo-header 與 header compression 傳送,不會直接使用上面的文字格式。
HTTP 層的資料還需要由底層連線承載。以新建立的 HTTPS 連線為例,Client 需要取得目標位址,並完成傳輸連線與 TLS 握手所需的協商。HTTP/1.1、HTTP/2 常見的路徑是 TLS over TCP;HTTP/3 則使用 QUIC over UDP,將 TLS 1.3 握手整合進 QUIC。兩條路徑的分層關係如下:
HTTP/1.1 或 HTTP/2
Application behavior
↓
HTTP request、Header、Cookie
↓
HTTP/1.1 message 或 HTTP/2 frame/HPACK
↓
TLS over TCP
↓
IP 與 network path
HTTP/3
Application behavior
↓
HTTP/3 frame/QPACK
↓
TLS 1.3 handshake integrated with QUIC
↓
QUIC over UDP
↓
IP 與 network path
沿著這個分層關係,可以看出各種設定的作用位置:修改 User-Agent 影響的是 HTTP Header,TLS 與 HTTP protocol 的連線參數則由對應元件控制。各層留下的特徵因此可以分開觀察,再放回同一條連線中比對。
伺服器端可觀測資訊
從接收連線的一端觀察,每一層提供的資訊都不同。IP 與 TCP 反映網路路徑及作業系統特徵,TLS 與 HTTP protocol 則更接近 Client 使用的函式庫與設定。這些連線特徵通常稱為 Transport Fingerprint。
網站也可能透過 JavaScript 蒐集 Canvas、WebGL、字型與畫面尺寸,形成瀏覽器執行環境的指紋。這類資料可以和 Transport Fingerprint 一起參與風險評分,但需要另外蒐集,不能單靠 TLS 或 HTTP 封包取得。常見訊號的來源與限制可整理如下:
| 協定層 | 常見觀測值 | 主要影響來源 | 判讀限制 |
|---|---|---|---|
| IP/Network | Source IP、ASN、GeoIP、連線頻率 | ISP、Proxy、VPN、NAT、Hosting Provider | 多個使用者可能共用 IP,同一使用者也可能換 IP |
| TCP | MSS、Window Scale、SACK、Timestamp、初始視窗 | 作業系統 Kernel、網路路徑、中介設備 | 一般 HTTP library 通常不直接產生 TCP SYN |
| TLS | ClientHello、Cipher Suites、Extensions、ALPN | TLS library、Client 設定、Session 狀態 | JA3/JA4 只是部分欄位的摘要 |
| HTTP/2 | SETTINGS、Window Update、pseudo-header order、flow control | HTTP/2 implementation | 單一連線開頭不足以涵蓋後續 stream behavior |
| HTTP/3/QUIC | QUIC version、Transport Parameters、HTTP/3 SETTINGS、QPACK | QUIC 與 HTTP/3 implementation | UDP、Proxy 與 fallback 會改變實際協商結果 |
| HTTP | Header 內容、順序、Cookie、request context | Browser profile、Application code | Header 可直接修改,不能單獨證明 Client 類型 |
| Runtime/Behavior | JavaScript API、DOM、navigation、timing、resource graph | 真實瀏覽器環境與使用方式 | 不屬於 TLS/HTTP impersonation 的控制範圍 |
實際能蒐集到表中哪些資料,仍取決於觀測點。例如,CDN 終止訪客連線後,Origin 通常接收到的是 CDN 另外建立的連線。每筆 Fingerprint 都需要標明資料來自哪一段連線,才能合理比較。
瀏覽器與一般 HTTP Client 的實作差異
同樣遵循 HTTP 與 TLS 規範的 Client,仍會在這些欄位上留下差異。RFC 定義哪些訊息合法、欄位代表什麼,以及雙方如何協商;它通常不要求所有 Client 使用完全相同的參數組合與排列順序。各實作對這些選項的取捨,正是 Fingerprinting 的資料來源。
以 TLS 為例,Chromium network stack 使用 BoringSSL,Firefox 則使用 NSS。Python 或 Rust 程式可能透過 OpenSSL、rustls、平台原生 TLS,或綁定其他 C library 建立連線。各 TLS Stack 支援的功能、預設順序與版本更新時間不同。Chromium Network Stack 與 Mozilla Network Security Services 都有說明各自使用的安全元件。
HTTP Stack 也有相同現象。瀏覽器會根據頁面導覽、Fetch、圖片、字型或背景請求建立不同 Header 組合,並管理 connection reuse、stream multiplexing、priority 與 cache。一般 HTTP library 比較常從「送出一個 Request 並取得 Response」的 API 出發,即使 HTTP 語意相同,底層連線行為仍可能不同。
因此,比較瀏覽器與一般 HTTP Client 時,需要同時考慮 TLS Stack、HTTP Stack 與應用程式設定。Header 宣告的瀏覽器類型,是否和底層元件呈現的特徵相符,也能成為一項觀察結果。
TLS ClientHello 與 JA3/JA4
TLS Stack 的差異會直接反映在握手起點:ClientHello。Client 透過這個訊息提出自己支援的加密套件、版本與擴充能力,Server 再從中選擇可共同使用的設定。ClientHello 因而成為 TLS Fingerprint 最常使用的資料來源。
以 TLS 1.3 使用伺服器憑證驗證身分、未發生 HelloRetryRequest 的握手為例,ClientHello 與後續訊息的關係如下:
Client Server
│── ClientHello ───────────────────────────→│
│←─ ServerHello ────────────────────────────│
│←─ EncryptedExtensions │
│←─ Certificate / CertificateVerify │
│←─ Finished │
│── Finished ──────────────────────────────→│
│ │
│══════ Encrypted application data ════════│
TLS 1.3 將更多協商資訊放進 Extensions。依 RFC 8446,ClientHello 會表達支援版本、Cipher Suites,並依使用功能帶上 supported_groups、key_share、signature_algorithms、server_name、pre_shared_key 等 Extensions。
ClientHello 欄位
Fingerprint 觀察的是 Client 提供了哪些能力,以及它如何組織這份能力清單。常見欄位與判讀方式如下:
| 欄位 | 功能 | Fingerprint 觀察方式 |
|---|---|---|
legacy_version |
保留舊版相容格式 | TLS 1.3 仍使用 0x0303,不能直接當成最終版本 |
| Cipher Suites | Client 可接受的加密套件 | ID 集合、數量與順序 |
| Extensions | SNI、ALPN、版本、群組等擴充能力 | Extension ID 集合、數量、順序及內容 |
supported_versions |
Client 願意協商的 TLS versions | 實際版本能力與偏好順序 |
supported_groups |
可用的 ECDHE/DHE groups | 群組集合與順序 |
key_share |
TLS 1.3 預先提供的 key exchange 資料 | Group 選擇、數量與長度 |
signature_algorithms |
可接受的簽章演算法 | 演算法集合與順序 |
| ALPN | 可用的 application protocol | h2、http/1.1、h3 等協商能力 |
| PSK/Session ticket | Session resumption 與 0-RTT 相關能力 | 初次連線與續連線可能不同 |
| Certificate Compression/ALPS | 憑證壓縮與應用層設定傳遞 | 支援與否、演算法及 Extension 關係 |
TLS 1.3 的版本欄位特別容易被誤讀。ClientHello.legacy_version 必須填入 0x0303,也就是 TLS 1.2 的數值;真正提供的版本位於 supported_versions。因此,解析工具若只顯示最外層 version 771,不能直接下結論說 Client 只支援 TLS 1.2。
Session 狀態也會改變 ClientHello。初次連線、TLS resumption、PSK、0-RTT 與 HelloRetryRequest 可能產生不同的 Extension 組合。建立基準資料時,必須分開記錄 cold connection 與 resumed connection,否則同一套 Client 很可能被誤判為兩個 Profile。
RFC 8879 已定義 TLS Certificate Compression;ALPS 則是部分 Client 實作採用的 TLS Application-Layer Protocol Settings 機制。截至目前,ALPS 的 IETF Internet-Draft 已過期,沒有正式 RFC 地位。它仍可成為實作指紋,但不能因為真實瀏覽器曾採用,就把它描述成所有 TLS Client 都應支援的標準功能。
JA3
完整 ClientHello 適合保留作為原始證據,大量連線的索引與比對則需要較精簡的表示方式。JA3 採用的做法,是選取 ClientHello 中五組欄位,依固定格式串接成字串:
TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
同一組內的 decimal ID 以 - 連接,五組之間使用 ,,移除 GREASE values 後再計算 MD5,得到便於索引的 32 字元 Hash。原始方法與參考實作可見 Salesforce JA3 repository;該 repository 已於 2025 年封存,現有產品仍可使用 JA3,但不應把封存的參考實作視為持續更新的 protocol standard。
JA3 的優點是格式簡單,既有 SIEM、IDS 與 log pipeline 容易保存及比對。它的限制也來自相同設計:
- MD5 Hash 只適合當索引,不是身分簽章,也不具備密碼學身分證明。
- 五組欄位沒有涵蓋
signature_algorithms、ALPN 內容、key_share、supported_versions、PSK 與多項新 Extensions 的細節。 - 原始 Extension 順序一變,Hash 就會改變。
- TLS 1.3 的
legacy_version可能讓 version 欄位看起來仍是 TLS 1.2。 - 不同 Client 可以刻意或自然產生相同 JA3,同一 Client 也會因版本及 Session 狀態產生不同 JA3。
保留 JA3 Hash 卻丟棄原始字串及 ClientHello,日後只能比較摘要值,無法追查哪些原始欄位造成差異。
GREASE 與 Extension Permutation
JA3 的欄位中,有些值與順序會因 Client 的協定相容性設計而變動。GREASE 就是其中一種機制:RFC 8701 保留一組看似未知的 protocol values,讓 Client 定期送出這些值,藉此確認 Server 不會因遇到未知 Cipher、Extension、Group 或版本而失敗。它要避免的是 protocol ossification:生態系錯把目前已知值寫死,導致未來無法擴充。
Fingerprint calculator 通常會移除 GREASE values,再產生可比對的結果。否則同一個 Client 只因本次選到不同 GREASE value,就會得到沒有分析價值的新 Hash。
另一個變因是 Extension Permutation。Chromium 系 Client 已經採用 TLS Extension 順序變動,固定依賴原始順序的 JA3 會因此產生多個結果。順序仍然是封包事實,但它不再必然是穩定識別欄位。分析時應同時保留:
- 原始 ClientHello,呈現這一次連線真正送出的順序。
- 正規化結果,用來比對忽略已知隨機化後的 Client family。
- 版本與執行環境,避免把軟體更新誤當成異常行為。
JA3N
針對 Extension Permutation 帶來的順序變動,常見的處理方式是先將 Extension IDs 排序,再產生摘要。JA3N 通常用來表示這類 normalized JA3,讓比較重點轉向忽略順序後的能力集合;原始 JA3 字串則保留所選欄位在這次握手中的排列。
JA3N 並非 IETF protocol,也不是原始 JA3 定義中的唯一標準格式。Diagnostic Service 若回傳 ja3n_hash,仍需確認它如何處理 GREASE、重複 Extension 與排序,才能和另一套工具的結果比較。
JA4
JA3N 主要處理既有欄位的順序問題,JA4 則重新選取、組織 TLS 特徵,將結果分成 a_b_c 三段。可讀的 a 段表達 TCP 或 QUIC、TLS version、SNI 狀態、Cipher 與 Extension 數量,以及 ALPN 的摘要;b、c 段再分別摘要排序後的 Cipher 與 Extension/Signature Algorithm 特徵。這種分段方式可以只比對部分特徵,也降低 Extension permutation 對整體結果的影響。
FoxIO JA4 repository 提供目前的技術規格與實作。授權也要分開看:TLS Client Fingerprinting 的 JA4 採 BSD 3-Clause;JA4S、JA4H、JA4T 等其他 JA4+ 方法另有 FoxIO License。若要把它整合進商業產品,不能把整套 JA4+ 都視為與 JA4 相同授權。
| 格式 | 核心輸入 | 順序處理 | 適合用途 | 主要限制 |
|---|---|---|---|---|
| JA3 | TLS version、Cipher、Extension、Group、Point Format | 保留 Cipher 與 Extension 順序 | 舊有資料比對、快速索引 | 新 TLS 特徵覆蓋不足,易受 permutation 影響 |
| JA3N | 常見實作以 JA3 為基礎正規化 Extensions | 通常排序 Extension IDs | Client family 聚合 | 各服務的正規化定義可能不同 |
| JA4 | Protocol、TLS、SNI、ALPN、Cipher、Extension、Signature Algorithm 等 | 對部分清單排序並分段摘要 | 跨 TCP/QUIC 分析、局部比對 | 仍是摘要,無法表示完整 handshake 與 runtime behavior |
無論使用哪一種格式,Hash 都應和原始欄位、擷取時間、Client version、觀測位置一起保存。Hash 相同表示這個摘要值相同;考慮到正規化、未納入的欄位與碰撞可能性,仍需回到原始資料確認差異,也不能據此認定兩條連線來自同一個人、裝置或程式。
Encrypted ClientHello 的可見範圍
除了摘要方法,取得哪一份 ClientHello 也會影響結果。傳統 TLS Fingerprinting 經常假設 ClientHello 可由路徑上的觀測者直接讀取;啟用 Encrypted ClientHello(ECH)後,這個前提需要進一步區分。
2026 年 3 月發布的 RFC 9849 已將 ECH 標準化。ECH 將敏感的 ClientHelloInner 加密,網路中介者主要看到的是 ClientHelloOuter;持有 ECH key 並終止連線的伺服器,才能取得 Inner 內容。
ECH 不會讓連線完全失去可觀測特徵。IP、QUIC/TCP 行為、Outer ClientHello、封包長度與時間等訊號仍可能存在,但「ClientHello Fingerprint」必須進一步註明是根據 Outer、Inner,還是未使用 ECH 的傳統 ClientHello 計算。不同觀測點的 Hash 即使都標示 JA4,也未必代表輸入資料相同。
HTTP/2 與 HTTP/3 指紋
ClientHello 描述 Client 如何提出 TLS 協商能力,HTTP protocol 則決定請求在連線上如何傳送。以 HTTP/2 為例,Client 透過 ALPN 協商 h2 後,還要送出 connection preface 與 SETTINGS。即使兩個 Client 的 TLS 特徵相近,這些 HTTP/2 設定與後續 frame 行為仍可能不同。
HTTP/2 連線與 SETTINGS
HTTPS 上的 HTTP/2 建立流程可以簡化為:
TLS ClientHello(ALPN 提供 h2)
↓
TLS ServerHello/EncryptedExtensions(選擇 h2)
↓
HTTP/2 connection preface
↓
SETTINGS
↓
WINDOW_UPDATE、HEADERS、DATA ...
Client connection preface 以固定 24-byte sequence 開頭,後面緊接 SETTINGS frame。RFC 9113 規定雙方在連線開始時都要送出 SETTINGS,但 Client 不必明確送出每一項設定;省略的項目使用規範預設值。
| HTTP/2 Setting | ID | 功能 | 可觀察差異 |
|---|---|---|---|
HEADER_TABLE_SIZE |
0x01 |
HPACK dynamic table 上限 | 是否送出、設定值與排列位置 |
ENABLE_PUSH |
0x02 |
Server Push 控制 | Client 是否明確關閉 |
MAX_CONCURRENT_STREAMS |
0x03 |
同時開啟 stream 上限 | 是否送出及數值 |
INITIAL_WINDOW_SIZE |
0x04 |
每個 stream 初始 flow-control window | 設定值與後續更新行為 |
MAX_FRAME_SIZE |
0x05 |
接收 frame 的最大 payload | 是否使用預設值 |
MAX_HEADER_LIST_SIZE |
0x06 |
可接受 Header List 大小 | Client 宣告值 |
ENABLE_CONNECT_PROTOCOL |
0x08 |
Extended CONNECT 支援 | WebSocket 等功能能力 |
NO_RFC7540_PRIORITIES |
0x09 |
RFC 7540 priority scheme 使用方式 | 新舊 priority model 差異 |
SETTINGS 在 protocol 語意上是設定值,不是 Client ID;同一個 ID 重複出現時,後面的值取代前面的值。不過真實實作通常會以穩定順序及固定預設值建立 frame,伺服器便能把「有哪些設定、值是多少、依什麼順序出現」視為特徵。
SETTINGS 建立了連線初始參數,後續如何使用這條連線也會呈現實作差異,包含:
- Connection-level
WINDOW_UPDATE的增量。 - HEADERS frame 中 pseudo-header 的排列。
- HPACK dynamic table 的使用方式。
- Connection reuse、同時開啟的 streams 與 frame 拆分。
- Flow control window 消耗與補充時機。
- Request priority 與 stream lifecycle。
HTTP/2 規定 pseudo-header 必須出現在一般 Header 之前,但 :method、:scheme、:authority、:path 的相對排列仍可能呈現實作慣例;CONNECT 等 Request 使用的 pseudo-header 組合也不同。這種順序可作為觀察值,不能當成 HTTP 語意上的身分欄位。
常見的 Akamai HTTP/2 Fingerprint String 將觀測值整理成下列四個區塊:
SETTINGS | WINDOW_UPDATE | PRIORITY | PSEUDO_HEADER_ORDER
這個摘要比只看 SETTINGS 完整,但仍未涵蓋 HPACK 狀態、後續 frame sequence、connection reuse 與 request timing。curl_cffi 的 TLS 與 HTTP/2 Fingerprint 文件 也採用 JA3 與 Akamai String 說明兩層 Profile 的差異。
HTTP/2 Priority
上述摘要中的 PRIORITY,需要連同 Client 採用的優先順序機制一起判讀。早期 HTTP/2 Fingerprint 常記錄 PRIORITY frame 與 stream dependency tree;RFC 9113 已將 RFC 7540 定義的 PRIORITY frame 標示為 deprecated,實作可改用 RFC 9218 的 extensible priority,包括 Priority Header 與 PRIORITY_UPDATE frame。
因此,「沒有 PRIORITY frame」不等於 Client 沒有 priority behavior。分析資料必須標明 Client 與 protocol implementation 的年代,並檢查 SETTINGS_NO_RFC7540_PRIORITIES、Priority Header 及 PRIORITY_UPDATE。用舊版 Chrome 的固定 priority sequence 判斷現行 Client,很容易把正常版本演進當成異常。
HTTP/3、QUIC 與 QPACK
當連線使用 HTTP/3,觀察對象也要換成 QUIC 與 HTTP/3 自己的設定及 frame。依 RFC 9114,HTTP/3 建立在 QUIC 上,並在 TLS handshake 透過 ALPN token h3 選擇 protocol。QUIC 自身的 Transport Parameters 在連線建立階段協商;HTTP/3 專屬設定則由雙方在各自 control stream 的第一個 frame 送出 SETTINGS。
HTTP/3 可觀察的實作特徵包括:
- QUIC version、Connection ID 長度與 Initial packet behavior。
- QUIC Transport Parameters 的集合、值與排列。
- TLS 1.3 ClientHello 與 ALPN
h3。 - HTTP/3 control stream 建立順序及 SETTINGS。
- QPACK table capacity、blocked streams 與 Header encoding 行為。
- QUIC flow control、stream limit、Retry、connection migration 與 resumption。
HTTP/2 的 HPACK 依賴有序傳輸,無法直接套用在不同 QUIC streams 上。HTTP/3 因此使用 RFC 9204 定義的 QPACK,透過獨立 encoder/decoder streams 管理 dynamic table 與 blocking。能送出 HTTP/3 Request,只代表 protocol 可用;QPACK 與 control stream 的行為是否符合特定瀏覽器,仍是另一組問題。
實際連線可能因 UDP 被封鎖、Proxy 不支援 QUIC、Server 未提供 HTTP/3,或 Client 自身策略而改用 HTTP/2。Fingerprint log 必須記錄最後協商到的 protocol。程式要求「優先 HTTP/3」與封包實際使用 HTTP/3,是兩件不同的事。
Header、Browser Profile 與跨層一致性
TLS 與 HTTP/2、HTTP/3 提供了底層實作的觀察結果;回到開頭複製的 HTTP 請求,Header 則提供了 Client 宣告與這次請求的用途。把兩者放在一起,才能檢查應用層內容是否和連線特徵相符。
瀏覽器會依平台、隱私設定與請求情境組合 Header。除了 User-Agent,Client Hints、Fetch Metadata、內容協商與優先順序欄位,也各自表達不同資訊。
Header 內容與結構
| 類型 | 常見欄位 | 觀察重點 |
|---|---|---|
| Client 宣告 | User-Agent、sec-ch-ua、sec-ch-ua-platform |
Browser family、版本與平台是否合理 |
| Content negotiation | Accept、Accept-Encoding、Accept-Language |
資源類型、壓縮能力、locale |
| Fetch context | Sec-Fetch-Site、Sec-Fetch-Mode、Sec-Fetch-Dest |
Navigation、same-origin、cross-site 與資源用途 |
| Priority | Priority |
urgency、incremental 與 protocol priority behavior |
| State | Cookie、Authorization、cache validators |
Session 延續與 navigation history |
| Routing | Host 或 :authority |
Request 的 target authority |
Header order 對多數不同名稱的欄位不是 HTTP application semantics,但實作仍常以固定資料結構和程式路徑產生穩定順序。HTTP/1.1 還可能觀察 field name casing;HTTP/2 與 HTTP/3 則要求 field names 使用 lowercase,不能把 H1 的 casing 規則直接套到 H2/H3。HTTP 欄位與訊息語意可參考 RFC 9110。
除了欄位本身的格式,請求用途也會影響 Header 組合。同一個瀏覽器載入網頁、呼叫 API 或下載圖片時,會表達不同的內容需求與資源情境:
| Request context | Header Profile 差異 |
|---|---|
| Top-level navigation | Accept 偏向 HTML,Fetch Metadata 表達 navigation 與 document |
| Fetch/XHR | Accept、Content-Type、CORS 與 Fetch Metadata 依 API 呼叫方式改變 |
| Image/Font/CSS | Accept 與 Sec-Fetch-Dest 應反映資源類型 |
| Form submission | Method、Content-Type、Origin/Referer 與 navigation 狀態互相關聯 |
| Service Worker/Cache | Cache validators、request mode 與回應來源可能改變 |
把真實瀏覽器首頁導覽的 Header 原樣複製到每一個 API Request,不一定更像瀏覽器,反而可能形成不合理的 request context。
Browser Profile 一致性
當 TLS、HTTP protocol 與 Header 的觀察值放在一起,就可以整理出某個 Client 的特徵組合,通常稱為 Profile。比對 Browser Profile 時,除了各層是否接近已知瀏覽器,也需要檢查它們彼此是否合理,以及網路環境是否提供其他解釋:
| 觀察組合 | 技術判讀 | 其他合理解釋 |
|---|---|---|
User-Agent 宣告 Chromium,TLS/HTTP/2 也接近相同世代 Profile |
跨層一致性較高 | 仍可能是模擬 Client |
User-Agent 宣告瀏覽器,TLS 呈現通用 library 特徵 |
Profile 不一致 | TLS intercepting proxy 可能重建 ClientHello |
| 宣告支援 HTTP/3,但實際只建立 HTTP/2 | 不足以單獨判定異常 | UDP、Server、Proxy 或網路政策可能阻擋 QUIC |
| Header 像 top-level navigation,URL 與 Fetch Metadata 卻像 background API | Request context 不一致 | Browser extension、Service Worker 或特殊導覽流程 |
| TLS Hash 相同,但 IP、Cookie 與行為完全不同 | 可能是共用 Client implementation | 同版瀏覽器自然會大量共用 Fingerprint |
Fingerprint 的可靠性來自多項訊號共同支持,而不是把每個差異都當成惡意。企業 TLS inspection、VPN、Accessibility tool、Browser extension、舊設備與隱私功能都可能改變部分特徵。合理的偵測系統需要容許替代解釋,並以風險分數、觀察期與額外驗證處理不確定性。
Transport Fingerprint 的適用範圍
跨層一致性也說明了 Browser Impersonation 的目標:讓 HTTP Client 套用某個瀏覽器的 TLS、HTTP 設定與 Header 組合。不過,這些設定分屬不同元件,可模擬到的範圍取決於函式庫實際控制哪些部分。
HTTP Client 可控制的範圍
一般 HTTP library 與提供 Browser Impersonation 的 library,可以從各層的控制能力比較:
| 訊號 | 一般 HTTP library | 具 Browser Impersonation 能力的 library | 主要邊界 |
|---|---|---|---|
| URL、Method、Header、Body | 可控制 | 可控制 | Application 必須建立正確 request context |
| Cookie/Session | 可管理 | 可管理 | 不會自動產生真實 browsing history |
| TLS ClientHello | 受 TLS backend 預設值限制 | 可套用或模擬特定 Profile | Profile 版本、resumption 與 ECH 仍要一致 |
| HTTP/2 SETTINGS/pseudo-header | 通常只有部分設定 | 可調整較完整的 H2 Profile | Runtime frame behavior 不一定完整相同 |
| HTTP/3/QUIC | 視 library 與 build 而定 | 視 Profile 與 QUIC stack 而定 | UDP、Proxy 與 Server support 會改變結果 |
| TCP SYN | 主要由 Kernel 建立 | 通常仍由 Kernel 建立 | TLS/HTTP Profile 不會自動改變 OS TCP Stack |
| Source IP/ASN | 由網路出口決定 | 由網路出口決定 | 使用 Proxy 會引入新的信任與觀測邊界 |
| JavaScript/DOM/Canvas | 沒有 Browser runtime | 沒有 Browser runtime | 需要真實或可執行 Web API 的環境 |
| Navigation/human behavior | 由程式設計 | 由程式設計 | Transport 模擬不會自動建立合理行為 |
curl_cffi 等工具可以調整 TLS 與 HTTP protocol Profile,但本質上仍不是含 DOM 與 JavaScript engine 的瀏覽器。其官方 Impersonation FAQ 也明確區分 TLS/HTTP impersonation 與完整 Browser environment。
TCP 指紋則是另一個常被忽略的邊界。一般 user-space HTTP library 呼叫作業系統 socket,由 Kernel 建立 TCP SYN;若沒有 raw socket、Kernel tuning 或特殊 network stack,僅修改 TLS Cipher 與 HTTP/2 SETTINGS 不會同步改變 MSS、Window Scale、SACK 與 Timestamp。這也是跨層分析可能看到「TLS 像瀏覽器、TCP 像執行程式的伺服器作業系統」的原因之一。
指紋證據的限制
既然函式庫可以重現部分瀏覽器特徵,觀察到相符的 Profile,就只能支持「這條連線具有相近的實作特徵」。同版瀏覽器也會讓大量使用者共享 Profile;軟體更新、A/B testing、企業 Proxy 與 Session resumption 則可能讓同一使用者產生差異。
Fingerprint 因而適合用於連線聚合、異常組合偵測,以及比較程式升級、TLS backend 或 Proxy 帶來的影響。這些結果可以協助 Bot management、abuse detection、threat hunting 與 incident response,但身分認證、存取授權與交易意圖仍需要應用程式自己的驗證。將 JA3 或 JA4 直接當成唯一封鎖條件,容易同時出現 false positive 與 false negative。
指紋觀察、驗證與判讀
實際比較瀏覽器與程式時,目標是找出差異來自哪個欄位、元件或網路條件。這需要把 Client 設定、實際連線與 Server 觀察結果對照起來:先確定觀測的是同一段連線,再控制實驗變因,最後解讀欄位與摘要的差異。
CDN、Proxy 與 TLS 終止點
選擇擷取位置時,需要先標出 TLS 在哪裡終止。以 CDN 終止訪客 TLS、再連往 Origin 的架構為例,兩段連線分別有自己的特徵:
Visitor/Client
│ 連線 A:Client Fingerprint
▼
CDN/WAF/TLS Proxy
│ 連線 B:CDN 或 Proxy Fingerprint
▼
Origin
CDN 終止 TLS 時,Edge 可觀察連線 A 的 ClientHello 與 HTTP behavior;Origin 直接看到的則是連線 B。除非 CDN 透過受信任欄位、metadata 或產品功能傳遞分析結果,Origin 不能從自己的 TLS log 還原 Client 原始 JA3。
Forward proxy 也要區分工作模式。HTTPS CONNECT tunnel 若不攔截 TLS,目標 Server 仍能看到 Client 建立的 TLS handshake,但 Source IP 通常是 Proxy;若 Proxy 執行 TLS inspection,Server 看到的 ClientHello 便是 Proxy 重新建立的連線。未記錄 Proxy mode 時,跨環境比較 Fingerprint 很容易得到錯誤結論。
加密與觀測位置
這些協定特徵雖然存在於連線中,擷取封包的位置卻未必能直接讀到。HTTPS 的 HTTP/2 SETTINGS、WINDOW_UPDATE、HEADERS 都位於 TLS encrypted application data 內;一般 on-path packet capture 無法只靠看到 TCP payload 就解析它們。HTTP/3 frames 同樣受到 QUIC encryption 保護。
能直接取得這些資料的位置通常是:
- 終止 TLS/QUIC 的 Server、CDN 或 Reverse Proxy。
- 支援 TLS key log 的 Client 搭配 packet capture。
- HTTP library 自身的 debug/trace output。
- 受控 Diagnostic Endpoint 對收到的連線進行 server-side parsing。
把「封包抓到了」直接寫成「HTTP/2 Fingerprint 已驗證」並不充分。若沒有 session secrets 或終端解析結果,capture 可能只證明建立了 TLS-over-TCP 連線,無法讀取加密後的 SETTINGS。
實驗紀錄基準
在確定觀測位置與可取得的資料後,可以依下列順序建立比對實驗:
- 在相同作業系統、網路出口與 Proxy 條件下建立基準連線。
- 一次只改一個變因,例如 Client library、TLS Profile 或 HTTP version。
- 同時保存 raw ClientHello、Server 解析欄位與 Hash。
- 分開測量新連線、connection reuse 與 session resumption。
- 重複多次,確認差異是穩定實作特徵或每次連線的隨機值。
- 對照 ALPN 與實際 HTTP protocol,不以 API 指定值代替 wire result。
為了讓各組結果能互相對照,每一組樣本至少記錄:
| 類型 | 必要資料 |
|---|---|
| 執行環境 | 作業系統、Kernel、CPU architecture、Container/VM |
| Client | 軟體名稱、完整版本、HTTP library、TLS backend 與 build options |
| 連線條件 | URL、測試時間、DNS 結果、Proxy mode、Source IP、IPv4/IPv6 |
| Protocol | 實際協商的 TLS、ALPN、HTTP version、是否 resumption/ECH |
| 原始證據 | pcap/pcapng、Server log、Client trace、原始 ClientHello 欄位 |
| 摘要結果 | JA3 string/Hash、JA3N 定義與 Hash、JA4、HTTP/2 Fingerprint |
Browser Profile 名稱不能取代實際版本。chrome、chrome_latest 或「模擬最新版」會隨套件更新改變;可重現紀錄應固定 Client package version、Profile target 與測試日期。
Diagnostic Endpoint
有了固定的比較條件,可以先透過 Diagnostic Endpoint 查看 Server 解析到的 JA3、JA4、HTTP version 與 Header。它適合建立初步基準,但輸出仍受該服務的計算方式限制。相同名稱的 ja3n_hash 或 HTTP/2 Hash,不保證不同網站採用完全相同的正規化規則。
Packet Capture
Diagnostic Endpoint 提供的是 Server 的解析結果;若要進一步檢查原始 ClientHello 或 frame,則需要取得對應的封包。在 Ubuntu 上可安裝擷取與解析工具,並記錄版本:
sudo apt update
sudo apt install --yes tcpdump tshark
curl --version
tcpdump --version
tshark --version
若要解析同次測試的 HTTP/2 SETTINGS,應在建立測試連線前,依 Client 支援的方式啟用 TLS key logging,並保存對應的 key log。完成抓包後才啟用,無法補回先前握手所需的 session secrets。
在自有或明確授權的測試環境,先啟動下列指令擷取 TCP 443 與 UDP 443,再由 Client 建立測試連線:
sudo tcpdump \
-i any \
-s 0 \
-w client-fingerprint.pcap \
'tcp port 443 or udp port 443'
完成一組連線後按 Ctrl+C,再用 TShark 定位一般 TLS ClientHello:
tshark \
-r client-fingerprint.pcap \
-Y 'tls.handshake.type == 1' \
-V
Wireshark GUI 可使用相同 display filter:
tls.handshake.type == 1
QUIC 與 HTTP/3 可先用下列 filter 定位:
quic || http3
Capture 檔可能包含 hostname、IP、Cookie、Authorization 與其他敏感資料,不應直接提交公開 repository。實驗保存時應限制權限、設定保留期限,公開範例則使用清理後的欄位摘要。
TLS Key Log 與 HTTP/2 解析
解析已擷取的 HTTP/2 流量時,需要對應連線的 session secrets,才能解密 TLS application data。應確認 Client 在測試時確實產生了 key log,再於 Wireshark 的 TLS protocol preferences 指向該檔案。不同 Client 的啟用方式不同,不能只設定環境變數就假設一定成功。
Session secrets 的敏感程度等同於對應測試連線的解密能力。Key log 不應用於正式流量,不應與 pcap 一起公開,也不能包含真實帳號、Token 或個人資料。
解密成功後,才適合用下列 display filter 尋找 HTTP/2 SETTINGS:
http2.type == 4
如果 Wireshark 仍只顯示 TLS Application Data,應先排查:
- Key log 是否對應同一次 process 與同一條 connection。
- Client 是否重用了較早建立的 connection。
- Capture 是否從 handshake 開始前就已啟動。
- 實際 ALPN 是否為
h2,而不是http/1.1。 - 流量是否經過另一個 TLS termination point。
判讀層級
觀察結果改變時,先比較原始欄位差異,再解釋其技術意義。若原始封包的差異只來自 GREASE value 或 Extension permutation,代表的含義和 Cipher Suite、ALPN 或 HTTP/2 flow control 改變並不相同。
整理取得的 Endpoint 輸出、Server log、Client trace 或封包後,結論應對應實際蒐集到的證據。只有 TLS 摘要、已比較兩個協定層,或另外驗證了應用程式狀態,能支持的判斷各不相同:
| 判讀層級 | 可成立的結論 | 不足以成立的結論 |
|---|---|---|
| 單一 JA3/JA4 | 選定欄位與某個已知 Profile 相同或相近 | Client 必定是該瀏覽器 |
| TLS + HTTP/2 | 兩個 protocol layers 具有一致或矛盾特徵 | 已重現完整 Browser behavior |
| TLS + HTTP + Session | Transport 與 application state 的一致性更高 | 已確認自然人身分 |
| 加入 JavaScript/Behavior | 可進一步評估 runtime 與互動合理性 | 可取代 authentication 或 authorization |
| 長期多訊號資料 | 可建立風險模型與異常基準 | 每一筆高風險結果都是惡意行為 |
工程上的安全決策應保留原因碼與可觀測資料,例如 rare_tls_profile 記錄罕見的 TLS 特徵組合、header_context_mismatch 記錄 Header 與請求情境不符、session_velocity 記錄 Session 活動頻率相關的判讀依據。具體原因能協助追查誤判,也方便在瀏覽器版本更新後重新校準規則。
結論
將瀏覽器的請求搬到程式時,URL、Header 與 Cookie 描述了應用層內容,TLS 與 HTTP Stack 則決定連線如何建立與傳送。HTTP Client Fingerprint 把這些實作特徵整理成可比較的資料;JA3、JA3N、JA4 與 Akamai HTTP/2 String 提供了不同範圍的摘要。
有意義的比較,需要在已知版本與網路條件下,將摘要差異追溯到原始欄位,並檢查各層是否一致。Browser Impersonation 能調整其中部分特徵,完整瀏覽器的執行環境與應用程式狀態則仍需分開驗證。這樣得到的結果,才能作為實作分析與風險判讀的依據。
系列文章
主要參考資料
- RFC 8446:The Transport Layer Security (TLS) Protocol Version 1.3
- RFC 8701:Applying GREASE to TLS Extensibility
- RFC 8879:TLS Certificate Compression
- RFC 9849:TLS Encrypted Client Hello
- RFC 7301:Transport Layer Security Application-Layer Protocol Negotiation Extension
- RFC 9110:HTTP Semantics
- RFC 9113:HTTP/2
- RFC 9218:Extensible Prioritization Scheme for HTTP
- RFC 9000:QUIC: A UDP-Based Multiplexed and Secure Transport
- RFC 9001:Using TLS to Secure QUIC
- RFC 9114:HTTP/3
- RFC 9204:QPACK: Field Compression for HTTP/3
- Salesforce JA3
- FoxIO JA4
- curl_cffi:TLS、HTTP/2 與 HTTP/3 Fingerprinting
- curl_cffi Impersonation FAQ