Https
HTTPS(Hypertext Transfer Protocol Secure)是 HTTP 協議的加密版本,它使用 SSL/TLS 協議來加密客戶端和服務器之間的通信。具體來說:
? 加密通信:在用戶請求訪問一個 HTTPS 網站時,客戶端(如瀏覽器)和服務器通過 SSL/TLS 握手 來建立一條加密的通道。這個過程包括證書驗證、密鑰交換等步驟,最終生成一個用于加密的會話密鑰。
? 數據加密:一旦加密通道建立,瀏覽器和服務器之間的所有通信數據都會使用對稱加密技術(如 AES)加密。這意味著即使中間人(例如攻擊者)截獲了通信數據,他們也無法輕易解密這些數據,因為沒有會話密鑰。
為什么抓包工具能解密流量?
抓包工具(如 Charles, Fiddler, Wireshark 等)能夠解密 HTTPS 流量,通常是因為它們 充當了一個代理服務器,而這個代理需要安裝在用戶的設備上并且信任其證書。
具體來說:
? 代理模式:這些抓包工具通過設置為用戶的代理(HTTP 或 HTTPS 代理),讓所有的流量首先經過這些工具,然后由工具轉發到目標服務器。
? 證書安裝和信任:抓包工具會在本地生成自己的 中間人證書,并要求用戶安裝該證書到瀏覽器或操作系統的受信任根證書列表中。這樣,抓包工具就可以解密和重新加密數據。
? 解密流量:
-
用戶請求 HTTPS 網站時,抓包工具充當一個中間人(man-in-the-middle)。
-
抓包工具與目標服務器建立 HTTPS 連接(此時它會驗證服務器證書并加密通信)。
-
抓包工具與用戶的瀏覽器之間再建立一個 HTTPS 連接(并使用抓包工具自己的證書加密通信)。
-
由于用戶信任抓包工具的證書,瀏覽器認為它與真正的服務器建立了加密通道,因此不會警告或報錯。
谷歌瀏覽器的開發者工具(F12)
瀏覽器的開發者工具(如谷歌 Chrome 的 F12)也能抓取 HTTPS 請求,但這并不是因為它解密了流量,而是因為瀏覽器本身 能夠查看請求和響應的元數據。
? 瀏覽器在發送 HTTPS 請求時,會將數據加密并發送到目標服務器,但服務器的響應會被加密并發送回瀏覽器。此時,瀏覽器解密響應并顯示在開發者工具中。
? 所以,當你在 開發者工具的 Network 面板 查看請求時,你看到的是 瀏覽器已解密后的數據,而不是中間人攻擊的結果。這里的流量是瀏覽器自己解密的,目的是供開發者查看。
請求
發送了一個簡單的 HTTP POST 請求,但在背后會涉及到一些額外的網絡層級的操作,包括 TCP 三次握手 和 四次揮手,還有 SSL/TLS 握手 的過程。這些操作會稍微增加請求的延遲。
TCP 三次握手(Three-Way Handshake)
當你發出一個 HTTP POST 請求時,它首先會建立一個 TCP 連接。TCP 是一個面向連接的協議,所以在客戶端與服務器之間建立連接時,會經過三次握手:
-
客戶端 -> 服務器:客戶端發送一個 SYN(同步)包來請求建立連接。
-
服務器 -> 客戶端:服務器響應一個 SYN-ACK(同步-確認)包,表示它準備好與客戶端通信。
-
客戶端 -> 服務器:客戶端再發送一個 ACK(確認)包,表示連接建立成功。
這三次握手完成后,客戶端和服務器之間就可以開始數據傳輸了。
SSL/TLS 握手(建立加密通道)
在 HTTPS(而不是 HTTP)中,所有的 HTTP 請求都會先通過 SSL/TLS 握手 來加密通信。這個過程也需要一定的時間,但它是為了確保通信的安全性。具體過程如下:
- 客戶端 -> 服務器:客戶端發送一個包含自己支持的加密算法和其他配置信息的 ClientHello 消息。
客戶端(瀏覽器、curl 或其他工具)會發送一個 ClientHello 消息給服務器,包含:
? 支持的加密算法(如 AES、RSA、ECDHE 等)。
? 支持的 SSL/TLS 版本。
? 隨機數,用于加密密鑰的生成。
? 支持的擴展(例如 SNI,服務器名稱指示)。
加密套件的選擇:不同客戶端會支持不同的加密算法。
? TLS 版本:例如,Chrome 可能會首先嘗試使用最新的 TLS 版本(如 TLS 1.3),而 curl 可能會允許更多的 TLS 版本(如 TLS 1.2 或 1.3)。
? SNI(服務器名稱指示):瀏覽器和工具通常會帶上 SNI 信息來告訴服務器想訪問的虛擬主機名。(例如 Chrome),它會通過多次優化過的策略來減少加密握手的時間和帶寬消耗。例如,Chrome 可能會在一定條件下使用 Session Resumption 或 TLS 0-RTT 來加速后續請求,避免每次都重新進行握手。
- 服務器 -> 客戶端:服務器根據客戶端的請求選擇一種加密算法并發送 ServerHello 消息,并發送自己的證書(包含公鑰)來讓客戶端驗證服務器身份。
回復一個 ServerHello 消息,包含:
? 選擇的加密算法和協議。
? 服務器證書(公鑰)用于加密。
? 隨機數。
- 客戶端 -> 服務器:客戶端生成一個 Pre-Master Secret,用服務器的公鑰加密后發送給服務器。然后,客戶端和服務器通過這個 Pre-Master Secret 計算出加密會話密鑰。
? 如果是對稱加密(例如使用 RSA 或 ECDHE),客戶端會使用服務器的公鑰加密一個共享密鑰(pre-master secret)。
? 服務器解密并生成最終的對稱加密密鑰。
- 服務器 -> 客戶端:服務器解密這個 Pre-Master Secret,并且確認雙方可以使用相同的會話密鑰進行加密通信。
一旦 SSL/TLS 握手 完成后,數據的加密和解密可以開始,通信將使用 對稱加密(如 AES)進行。
TCP 四次揮手(Four-Way Handshake)
當通信結束時,連接需要關閉。這個過程需要 四次揮手 來完成:
-
客戶端 -> 服務器:客戶端發送一個 FIN(結束)包,表示它要關閉連接。
-
服務器 -> 客戶端:服務器確認收到 FIN 包,并發送一個 ACK(確認)包,表示連接半關閉。
-
服務器 -> 客戶端:服務器發送一個 FIN 包,表示服務器也準備關閉連接。
-
客戶端 -> 服務器:客戶端確認收到服務器的 FIN 包,并發送一個 ACK 包,表示連接完全關閉。
每個新的連接都會進行一次握手:如果瀏覽器或工具每次都建立新的連接(沒有復用連接),每次請求都會重新進行 SSL/TLS 握手。
? 連接復用:在 HTTP/2 或 HTTP/1.1 keep-alive 下,多個請求可以復用同一個連接,減少握手的頻率。
http1.1 與http 2
HTTP(Hypertext Transfer Protocol,超文本傳輸協議)是 Web 通信的基礎,目前主要使用的版本有 HTTP/1.1 和 HTTP/2
如果你沒有配置 keepalive_timeout,Nginx 使用默認值:
? keepalive_timeout 默認 75 秒
? keepalive_requests 默認 100 【nginx -T | grep keepalive】
server {listen 443 ssl http2;server_name model.abc.com;ssl_certificate /home/ubuntu/crt_ssl/model.abc.com.crt;ssl_certificate_key /home/ubuntu/crt_ssl/model.abc.com.key;ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305-SHA256';ssl_protocols TLSv1.2 TLSv1.3;ssl_prefer_server_ciphers on;# 配置 `keep-alive`keepalive_timeout 65; # 65 秒內可以復用 TCP 連接keepalive_requests 100; # 允許 100 次請求復用同一個連接location / {proxy_pass http://localhost:3000; # Django 后端容器proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}server {listen 443 ssl http2;server_name abc.com;ssl_certificate /home/ubuntu/crt_ssl/abc.com.crt;ssl_certificate_key /home/ubuntu/crt_ssl/abc.com.key;ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305-SHA256';ssl_protocols TLSv1.2 TLSv1.3;ssl_prefer_server_ciphers on;location / {proxy_pass http://localhost:8004; # Django 后端容器proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}
HTTP/1.1
HTTP/1.1 是 HTTP 協議的一個改進版本,解決了 HTTP/1.0 存在的一些問題:
? 支持持久連接(Keep-Alive):HTTP/1.0 每次請求都需要建立新的 TCP 連接,而 HTTP/1.1 通過 Connection: keep-alive 允許一個 TCP 連接復用多個 HTTP 請求。【在請求頭有這個keep-alive】
? 支持分塊傳輸(Chunked Transfer Encoding):允許服務器分塊傳輸數據,提高大文件的傳輸效率。
? 增加 Host 頭:HTTP/1.1 允許同一 IP 托管多個網站,因此 Host 頭變為必填項。
FastAPI 默認是基于 Uvicorn 運行的,而 Uvicorn 默認使用的是 HTTP/1.1,但是:
? 如果你用 HTTPS(TLS 加密),并且啟用了 HTTP/2,Uvicorn 也可以支持 HTTP/2。
? 但是如果你是在 Nginx 代理 FastAPI,那么 HTTP/2 主要由 Nginx 負責,FastAPI 依然是 HTTP/1.1。
HTTP/1.1 默認是長連接,即:
? 服務器返回 Connection: keep-alive[ Keep-Alive: timeout=5, max=100,表示 5 秒后關閉連接。]
? 只要客戶端愿意,TCP 連接可以復用,不用每次請求都新建一個連接
? 但是 HTTP/1.1 仍然有隊頭阻塞問題,所以瀏覽器一般會開多個 TCP 連接(每個域名 6-8 個)
HTTP/2
HTTP/2 主要目標是提升 Web 性能,它在 HTTP/1.1 的基礎上做了大量優化,最重要的特性包括:
- 多路復用(Multiplexing)
? HTTP/2 通過 “幀”(Frame) 的概念,將 HTTP 請求拆分成多個流(Stream),在一個 TCP 連接中可以同時發送多個請求。 HTTP/2 解決了 HTTP/1.1 在應用層的隊頭阻塞,但仍然依賴 TCP 連接【流式】。如果 TCP 層丟包,會導致整個 HTTP/2 連接受影響。所以 HTTP/3 進一步采用 QUIC 協議,徹底規避了這個問題。
? 這樣就解決了 HTTP/1.1 的隊頭阻塞問題,一個請求的慢速不會影響其他請求。
- 頭部壓縮(HPACK)
? HTTP/2 采用 HPACK 算法,對頭部進行哈夫曼編碼(Huffman Coding),避免重復傳輸相同的 Header 信息,大幅減少帶寬消耗。
- 服務器推送(Server Push)
? 服務器可以主動將客戶端可能需要的資源推送到瀏覽器,而不需要客戶端先請求,減少延遲。例如,在請求 index.html 時,服務器可以提前推送 CSS、JS 文件。
- 流優先級控制
? HTTP/2 允許客戶端為請求分配不同的優先級,保證關鍵資源(如 CSS、JavaScript)優先加載,提高頁面渲染速度。
? 依賴 TLS(HTTPS):雖然 HTTP/2 協議本身并不強制加密,但主流瀏覽器要求 HTTP/2 必須 使用 TLS(HTTPS)。
因為 HTTP/2 本身就是長連接!
? HTTP/2 默認復用 TCP 連接,不需要 Connection: keep-alive 這個標識。
? 瀏覽器只要發現服務器支持 HTTP/2,它就會一直復用這個連接,直到服務器主動關閉。
? 因此,在 HTTP/2 響應頭中,你不會看到 Connection: keep-alive
瀏覽器訪問 API 時:如果 HTTP/2 可用,TCP 連接是可以復用的。
? 非瀏覽器環境(比如 Python requests、Postman、curl):
? 默認不會復用,因為每次請求都會重新創建連接。
? 但是如果你使用 HTTP/1.1 且加了 keep-alive,則有可能復用(但這取決于 HTTP 客戶端是否支持)。
? 使用 httpx、requests 這些庫時,你需要手動啟用連接池來復用連接。
連接復用
TCP 連接復用 本質上就是 在一定時間內,瀏覽器或客戶端不會每次都重新建立 TCP 連接(或者 SSL/TLS 握手),而是復用現有的連接進行 HTTP 請求。
在 HTTP/1.1 下,TCP 連接默認是持久連接,也就是:
? 服務器默認不會立即關閉連接
? 瀏覽器會在一段時間內復用這個連接
如果客戶端在 5 秒內發送了新請求,它會直接復用現有 TCP 連接,而不是重新握手。
HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=100( ? timeout=5:表示服務器會等待 5 秒,如果客戶端沒有新的請求,就關閉連接。? max=100:最多允許復用 100 次請求。)
? 瀏覽器的 TCP 連接復用 是 基于 (源 IP, 源端口, 目標 IP, 目標端口) 的四元組。
? 只要 源 IP + 源端口 + 目標 IP + 目標端口 不變,連接就能復用。
? 瀏覽器不會復用 已經關閉的 TCP 連接,但會盡可能在 keep-alive 時間內重用它。
? NAT 設備(如家庭路由器) 會將你的內網 IP(如 192.168.1.100:54321)映射到 公網 IP(如 203.0.113.10:34567)。
? 服務器只知道 公網 IP + 端口(203.0.113.10:34567),而不知道你的本地 IP。
? NAT 設備會在內存里維護連接表,確保返回的數據包能夠正確映射到你的設備。
題目
1、HTTP/2 多路復用(Multiplexing)是如何避免 HTTP/1.1 的隊頭阻塞(HOL Blocking)的?但為什么仍然可能存在 HOL Blocking?
在 HTTP/1.1 中,一個 TCP 連接只能串行處理請求:
? 瀏覽器默認最多 6-8 個 TCP 連接,但是每個連接里請求是 串行執行,導致 隊頭阻塞(HOL Blocking)。
? 如果前一個請求耗時較長,后續請求 必須等待,即使它們本身處理很快。
HTTP/2 解決方案:
? 引入多路復用(Multiplexing),允許一個 TCP 連接同時處理多個請求和響應,每個請求都分配一個流 ID。
? 請求之間不再是嚴格的順序執行,即使前一個請求慢,后面的請求仍然可以并行傳輸。
但 HTTP/2 仍然可能存在 HOL Blocking:
? TCP 層的隊頭阻塞:HTTP/2 仍然是基于 TCP 傳輸的,如果 TCP 層丟包,整個 TCP 連接需要等待丟失的數據包重傳,導致所有 HTTP/2 請求阻塞。
? HTTP/2 不能解決 TCP 層的 HOL Blocking,而 HTTP/3 采用 QUIC(基于 UDP),完全消除了這一問題。
2、 服務器是如何驗證復用的連接是否真的來自同一個客戶端?
服務器會使用 TLS 會話恢復(TLS Session Resumption) 機制:
? Session Ticket(TLS 1.2):服務器在第一次握手時生成一個加密票據(Session Ticket),存儲在客戶端,下次請求時客戶端帶上這個票據,服務器就知道它是同一個會話。
? Session ID(TLS 1.2):服務器給每個 TLS 會話分配一個 ID,客戶端后續請求時可以用相同 ID 進行恢復。
? TLS 1.3 采用 PSK(Pre-Shared Key):客戶端和服務器可以在 0-RTT(Zero Round Trip Time)模式下恢復會話。
這意味著:
? 即使 IP 變了,服務器仍然可以識別客戶端是否是同一個會話(只要 Session Ticket 沒有過期)。
? 攻擊者即使偽造 IP 和端口,也無法繞過 TLS 認證。
3、連接復用時,為什么還需要 SYN-ACK?
同一個 TCP 連接在 keep-alive 時間內的請求可以共享,但一旦連接關閉,新的連接仍然需要重新握手(SYN -> SYN-ACK -> ACK)。
- 客戶端(瀏覽器)建立 TCP 連接
? Client → SYN → Server
? Server → SYN-ACK → Client
? Client → ACK → Server
? TCP 連接建立后,客戶端發送 HTTP 請求:
Client → GET /index.html HTTP/1.1 → Server
如果 keep-alive 生效,連接不會立即關閉
? 服務器返回 Connection: keep-alive,表示 TCP 連接仍然可用。
? 后續請求不會重新握手,直接復用 TCP 連接:
Client → GET /api/data HTTP/1.1 → Server
攻擊者不可能劫持現有的 TCP 連接
? 連接復用是客戶端和服務器維護的,攻擊者無法直接插入現有的 TCP 連接,因為 TCP 連接在 NAT 內部,攻擊者無法獲取 NAT 的端口映射狀態。
? NAT 設備有 狀態表,它只允許 真正發起連接的設備 接收服務器的數據。
? 攻擊者無法強行加入 NAT 設備已經維護的連接,除非 NAT 設備本身被攻破。
- 攻擊者只能嘗試偽造新的 TCP 連接
? 偽造 IP + 端口并不會讓攻擊者進入原連接,而是必須發起新的 SYN。
? 但服務器發送的 SYN-ACK 不會送到攻擊者手里,而是回到 NAT 設備,導致握手無法完成。
? 這就是TCP 偽造攻擊失敗的核心原因。
- 即使 TCP 偽造成功,TLS 仍然會保護數據
? 在 HTTPS(TLS)連接里,TCP 連接的建立并不代表攻擊成功,因為攻擊者還需要完成 TLS 握手。
? TLS 握手涉及服務器證書、公私鑰加密,攻擊者無法偽造
? 即使攻擊者能劫持 TCP 連接,他仍然無法解密數據。