網路安全監控與分析 · 封包分析單元

QUIC 協定深度解析

QUIC 是 HTTP/3 底下的傳輸協定,跑在 UDP 上,把 TCP 的可靠傳輸、TLS 1.3 的加密、以及多串流整合成一層。這份講義先講「TCP 到底卡在哪裡」,再用逐步動畫拆解交握、遺失重傳與連線遷移,最後落地到 Wireshark 怎麼抓、怎麼看、怎麼解密。

01

為什麼需要 QUIC?

TCP 設計於 1970 年代,HTTPS 則是在 TCP 上再疊 TLS。這個組合跑了幾十年,但在行動網路與大量小檔案的現代網頁上,暴露出四個結構性問題。

問題 1 · 交握太慢

開始傳資料前要等 2~3 個 RTT

TCP 三向交握 1 RTT,TLS 1.2 再 2 RTT(TLS 1.3 為 1 RTT)。在 RTT 100 ms 的行動網路上,光「還沒開始傳」就要 200~300 ms。

QUIC 的解法

傳輸與加密交握合併

QUIC 把 TLS 1.3 交握訊息直接放進自己的封包,首次連線 1 RTT 就能送出請求;曾連過的伺服器還能 0-RTT,第一個封包就帶資料。

問題 2 · 隊頭阻塞(HOL Blocking)

掉一個封包,全部串流一起卡

HTTP/2 在一條 TCP 上多工傳 CSS、JS、圖片,但 TCP 只認得「一條位元組流」。中間掉一段,後面已到的資料全部卡在核心緩衝區,等重傳補齊才交給應用程式。

QUIC 的解法

串流是傳輸層原生概念

每個 STREAM frame 帶有 Stream ID 與 offset,遺失只影響該串流;其他串流的資料一到就能交付。

問題 3 · 連線綁死 IP

換網路 = 斷線重連

TCP 連線由四元組(來源 IP、來源埠、目的 IP、目的埠)識別。手機從 Wi‑Fi 切到 4G/5G、或 NAT 對應改變,連線就斷了,得重新交握。

QUIC 的解法

用 Connection ID 識別連線

連線身分是 Connection ID,不是 IP。位址改變後,經過一次路徑驗證(PATH_CHALLENGE / PATH_RESPONSE)就能繼續傳。

問題 4 · 協定僵化(Ossification)

TCP 很難再改

TCP 實作在作業系統核心,標頭又是明文,路上大量防火牆、NAT、中介設備會檢查甚至改寫它。新功能(如 TCP Fast Open)常被中介設備擋掉,部署困難。

QUIC 的解法

跑在使用者空間,幾乎全加密

QUIC 建在 UDP 上、多半在應用程式裡實作(瀏覽器、函式庫),更新快;除了少數欄位外連傳輸層標頭都加密,中介設備沒辦法依賴它的內容,協定才能持續演進。

一句話記住:QUIC ≈ TCP(可靠、壅塞控制)+ TLS 1.3(加密)+ HTTP/2 的多工(但下放到傳輸層),整包搬到 UDP 上。標準為 RFC 9000(傳輸)、RFC 9001(TLS)、RFC 9002(遺失偵測與壅塞控制),HTTP/3 是 RFC 9114。
02

協定堆疊與交握延遲

注意 QUIC 不是「UDP 上的 TCP」而已——TLS 被吸收進 QUIC 內部,HTTP/2 的串流管理也往下搬了。

HTTP/2(傳統 HTTPS)
HTTP/2串流多工、HPACK
TLS 1.2 / 1.3加密
TCP可靠、順序、壅塞控制
IP網路層
HTTP/3
HTTP/3QPACK 標頭壓縮
QUIC使用者空間實作
串流多工TLS 1.3 交握可靠傳輸壅塞控制連線遷移
UDP通常是 UDP/443
IP網路層

首次請求前要等多久?

以 RTT = 50 ms 為例,從用戶端開始連線到「伺服器收到第一個 HTTP 請求」之前,雙方必須來回的次數:

01 RTT2 RTT3 RTT
TCP + TLS 1.2
TCP
TLS 1.2 ×2
150 ms
TCP + TLS 1.3
TCP
TLS 1.3
100 ms
QUIC 首次連線
QUIC+TLS
50 ms
QUIC 0-RTT 恢復
請求跟著第一個封包出去
0 ms
瀏覽器怎麼知道可以用 HTTP/3?第一次通常仍走 TCP,伺服器在回應標頭帶 Alt-Svc: h3=":443",下次才改用 QUIC;或是 DNS 的 HTTPS 記錄(type 65)事先宣告支援 h3。若 UDP/443 被擋,瀏覽器會自動退回 TCP——這點在資安管控上很重要(見第 09 節)。
03

互動動畫:一個封包一個封包看

點「播放」看動畫,或用上一步/下一步逐格講解。右側面板會顯示每個 UDP 資料包裡裝了哪些 QUIC 封包、帶什麼 frame,以及對應的 Wireshark 過濾條件。建議先看 01 的 TCP 對照組,再看 02。

步驟 0 / 0

用戶端 CLIENT
—
QUIC
伺服器 SERVER
—
Initial Handshake 0-RTT 1-RTT(Short Header) Retry TCP TLS over TCP 遺失

本情境封包清單

#方向內容重點
05

加密層級與封包編號空間

QUIC 有四種加密層級、三個獨立的封包編號空間。每個空間的 Packet Number 都從 0 開始、只增不減,ACK 也各自分開。

封包類型標頭封包編號空間金鑰從哪來誰能解密內容
InitialLong (0x0)Initial由用戶端選的 DCID + 公開的版本 salt 推導任何人(包括 Wireshark、防火牆)ClientHello / ServerHello、ACK
0-RTTLong (0x1)Application Data上次連線的 PSK(session ticket)兩端提前送出的應用資料
HandshakeLong (0x2)HandshakeTLS 1.3 (EC)DHE 交換後的 handshake 金鑰兩端Certificate、Finished 等
1-RTTShortApplication DataTLS 1.3 應用流量金鑰兩端HTTP/3 資料、控制 frame
RetryLong (0x3)沒有封包編號只有完整性標籤(固定金鑰)任何人位址驗證 token
重要:Initial 只是「混淆」,不是保密。Initial 金鑰的推導材料(DCID 與 RFC 公開的 salt)都在封包裡,所以任何看得到封包的人都能解開 ClientHello,讀到 SNI(要連的網域)和 ALPN。這也是 Wireshark 不用 key log 就能顯示 SNI 的原因。要隱藏 SNI 得靠 ECH(Encrypted Client Hello)。
06

常見 Frame 類型

QUIC 封包的酬載由一個或多個 frame 組成。封包只是「信封」,真正的語意都在 frame。重傳時重送的是 frame 的內容,而不是原封包。

類型值名稱用途
0x00PADDING填充。用戶端 Initial 必須墊到 UDP 酬載至少 1200 bytes
0x01PING保活、引發對方回 ACK
0x02–0x03ACK確認收到的封包編號範圍(可描述多個缺口,0x03 另帶 ECN 計數)
0x04RESET_STREAM傳送端放棄某個串流
0x05STOP_SENDING接收端要求對方停止傳某個串流
0x06CRYPTO承載 TLS 交握訊息(ClientHello、Certificate…)
0x07NEW_TOKEN給用戶端下次連線用的位址驗證 token
0x08–0x0fSTREAM串流資料,帶 Stream ID、offset、FIN
0x10–0x11MAX_DATA / MAX_STREAM_DATA流量控制:整條連線/單一串流可接收上限
0x12–0x13MAX_STREAMS允許對方開幾個雙向/單向串流
0x14–0x17*_BLOCKED告知「我被流量控制卡住了」
0x18–0x19NEW / RETIRE_CONNECTION_ID發放或退役 Connection ID(連線遷移用)
0x1a–0x1bPATH_CHALLENGE / PATH_RESPONSE路徑驗證:8 bytes 隨機值,對方原樣回傳
0x1c–0x1dCONNECTION_CLOSE關閉連線(傳輸層錯誤/應用層錯誤)
0x1eHANDSHAKE_DONE伺服器告知交握已確認(只能由伺服器送)

Stream ID 的最低 2 位元

… 00 (0, 4, 8…)

用戶端開的雙向串流

HTTP/3 的每個請求/回應

… 01 (1, 5, 9…)

伺服器開的雙向串流

HTTP/3 中一般不使用

… 10 (2, 6, 10…)

用戶端開的單向串流

HTTP/3 控制串流、QPACK 編碼器串流

… 11 (3, 7, 11…)

伺服器開的單向串流

HTTP/3 控制串流(SETTINGS)等

07

TCP vs QUIC 對照

面向TCP (+TLS)QUIC
底層IP 協定號 6UDP(通常 443 埠)
實作位置作業系統核心多在使用者空間(瀏覽器、函式庫)
連線識別四元組Connection ID
建立連線1 RTT + TLS 1~2 RTT1 RTT(恢復時 0-RTT)
加密選用;TCP 標頭永遠明文強制;連傳輸層控制資訊都加密
序號位元組序號,重傳沿用同序號(有重傳歧義)封包編號只增不減,重傳用新編號(無歧義)
多工無;一條連線一個位元組流原生多串流,彼此獨立
隊頭阻塞有(傳輸層)只影響單一串流
換 IP斷線連線遷移
ACK累積 ACK + SACK(最多約 3~4 區段)ACK frame 可描述大量範圍,且回報 ACK 延遲
關閉FIN / RST(明文,可被偽造)CONNECTION_CLOSE(加密);Stateless Reset 需知道 token
08

Wireshark 實作

建議用 Wireshark 3.6 以上(較新版本對 QUIC v1/v2、HTTP/3 的解析更完整)。

步驟一:抓封包

開 Chrome 或 Edge 連到支援 HTTP/3 的網站(例如大型 CDN 服務的網站),在 DevTools → Network 加開「Protocol」欄,看到 h3 就表示走 QUIC。第一次載入可能是 h2,重新整理後才會切換(Alt-Svc 機制)。

步驟二:常用顯示過濾條件

過濾條件用途
quic所有 QUIC 封包
udp.port == 443 && !quicUDP/443 但沒被辨識為 QUIC 的流量(可疑或非標準)
quic.header_form == 1Long Header(交握)
quic.long.packet_type == 0Initial 封包
quic.long.packet_type == 10-RTT 封包
quic.long.packet_type == 2Handshake 封包
quic.long.packet_type == 3Retry 封包
quic.version == 0Version Negotiation
tls.handshake.extensions_server_name從解開的 Initial 讀 SNI(不需金鑰)
quic.frame_type == 0x1a || quic.frame_type == 0x1b路徑驗證(連線遷移)——需解密 1-RTT
quic.frame_type == 0x1c || quic.frame_type == 0x1dCONNECTION_CLOSE——需解密
http3HTTP/3 內容——需解密
dns.qry.type == 65DNS HTTPS 記錄查詢(探索 h3 支援)

註:欄位名稱以近年 Wireshark 版本為準,若舊版不支援某欄位,可在封包詳細窗格對該欄位按右鍵 →「套用為過濾器」確認實際名稱。

步驟三:解密 1-RTT(看到 STREAM、HTTP/3)

QUIC 跟 TLS 一樣支援 SSLKEYLOGFILE。設定環境變數後啟動瀏覽器,瀏覽器會把每條連線的 TLS 祕密寫進檔案,Wireshark 讀進來就能解開 Handshake 與 1-RTT 封包。

Windows PowerShell(關閉所有 Chrome 視窗後執行)$env:SSLKEYLOGFILE="$env:USERPROFILE\Desktop\sslkeys.log"; Start-Process chrome.exe

接著 Wireshark → 編輯 → 偏好設定 → Protocols → TLS → (Pre)-Master-Secret log filename 指向該檔。QUIC 會共用 TLS 的設定。

課堂提醒:key log 檔等同所有連線的鑰匙,實作後請刪除,也不要在正式環境的瀏覽器長期開著這個變數。
09

資安觀點

監控盲點

傳統依賴 TLS 解密(SSL inspection)或 TCP 狀態追蹤的防火牆、Proxy、IDS,多半看不懂 QUIC 的 1-RTT 內容。很多組織的做法是封鎖 UDP/443,讓瀏覽器自動退回 TCP+TLS,以維持既有檢測能力。

仍看得到什麼

SNI(解開 Initial 即可,除非使用 ECH)、QUIC 版本、Connection ID、封包大小與時間序列、Spin Bit(若有啟用)。這些已足夠做網域層級的政策與部分流量指紋分析。

反放大設計

UDP 容易偽造來源 IP。QUIC 規定伺服器在驗證用戶端位址前,回傳量不能超過收到量的 3 倍;加上用戶端 Initial 必須墊到 1200 bytes,攻擊者很難把 QUIC 伺服器當反射放大器。伺服器還能用 Retry 先驗證位址。

0-RTT 重放風險

0-RTT 資料不具前向保密且可被重放:攻擊者把第一個封包錄下來再送一次,伺服器可能重複執行。因此只應允許冪等請求(如 GET),伺服器端需實作防重放或乾脆拒絕 0-RTT。

攻擊者也會用

部分 C2 框架與惡意程式已支援 HTTP/3 / QUIC 作為通道,利用防禦方對 UDP/443 監控較弱的特性。在 NSM 中,對非瀏覽器程序發起的大量 QUIC 連線、或連往罕見網域的 QUIC 流量值得留意。

DoS 面向

Initial 洪水會消耗伺服器的解密與狀態資源,可用 Retry(無狀態)緩解;偽造的 CONNECTION_CLOSE 因為需要金鑰而無效,Stateless Reset 也需事先知道 token,比 TCP RST 注入難得多。

10

自我檢測

Q1. 為什麼 QUIC 首次連線只要 1 RTT,而 TCP + TLS 1.3 要 2 RTT?

TCP 必須先完成三向交握(1 RTT)才能開始傳 TLS ClientHello;QUIC 的第一個 Initial 封包就直接帶著 ClientHello,傳輸層與加密層的交握同時進行。

Q2. 不提供任何金鑰,Wireshark 能看到 QUIC 連線要連的網域嗎?為什麼?

可以(未使用 ECH 時)。Initial 封包的金鑰由明文 DCID 與 RFC 公開的 salt 推導,任何人都能解開,裡面的 ClientHello 帶有 SNI。

Q3. 1-RTT 封包遺失後重傳,封包編號會沿用嗎?這有什麼好處?

不會。QUIC 用新的封包編號重送遺失 frame 的內容,所以收到 ACK 時能明確知道是確認原封包還是重傳封包,RTT 量測不會有 TCP 的「重傳歧義」。

Q4. 手機從 Wi‑Fi 切到 4G,QUIC 連線怎麼不斷線?伺服器為什麼要送 PATH_CHALLENGE?

連線靠 Connection ID 識別,不靠 IP。伺服器送 PATH_CHALLENGE 是為了確認新位址真的屬於用戶端(能收到並回傳 8 bytes 隨機值),避免攻擊者偽造來源位址把流量導向受害者。

Q5. 用戶端的 Initial 封包為什麼至少要 1200 bytes?

配合 3 倍反放大限制:用戶端送得越大,伺服器在驗證前能回的量就越多,足以塞下憑證等交握資料;同時讓偽造小封包換大回應的放大攻擊無利可圖。1200 也確保路徑 MTU 能承載 QUIC 封包。

Q6. 組織若想維持 TLS 解密檢測,最常見的對 QUIC 處置是什麼?副作用?

封鎖 UDP/443(或特定 QUIC 流量),讓瀏覽器退回 HTTP/2 over TCP。副作用是失去 QUIC 的延遲與連線遷移優勢,行動使用者體驗可能變差。

延伸閱讀