摘要

本報告提供對 HTTP/2 MadeYouReset 漏洞的全面技術分析,該漏洞建立在其前身 Rapid Reset 攻擊的基礎上。兩種漏洞均利用 HTTP/2 協議的固有特性,特別是其 stream 管理和 concurrency 機制,以促進 Denial of Service (DoS) 攻擊。我們深入探討 MadeYouReset 的技術 primitives,檢視如何利用無效的 control frames 和不當的 protocol flow,迫使伺服器發出 RST_STREAM frames,從而繞過現有的防護措施。本報告還討論了後端工作在 stream reset 後仍繼續進行的根本原因,這有助於這些攻擊的有效性。最後,我們探討了保護 HTTP/2 實現免受此類複雜 DoS 攻擊的有效防護策略。

CVE-2025-8671 分析:HTTP/2 DoS 攻擊的下一代演進 MadeYouReset | 資訊安全新聞

1. 簡介

超文字傳輸協議版本 2 (HTTP/2) 相較其前身 HTTP/1.1,在效能和效率方面引入了顯著的進展。諸如 multiplexing、header compression 和 server push 等功能,旨在最佳化網路通訊。然而,這些新機制的複雜性也為新型攻擊向量開闢了道路。MadeYouReset 漏洞 (CVE-2025-8671) 是此類攻擊的一個關鍵範例,建立在早期的 Rapid Reset 攻擊原理之上 [1] 。本報告旨在剖析 MadeYouReset 的技術基礎,提供其攻擊向量和繞過傳統 HTTP/2 concurrency 限制機制的詳細檢視,從而導致 Denial of Service 狀況。

2. HTTP/2 基礎與漏洞環境

HTTP/2 使用 frames 作為其基本通訊單位,封裝了包括 HTTP headers、request/response payloads 和 protocol control information 在內的各種資料類型。這些 frames 通過 streams 傳輸,這些 streams 是虛擬通道,可實現同時的 request-response 週期 [1] 。這種 multiplexing 能力顯著提高了相較於 HTTP/1.1 的效率,在 HTTP/1.1 中,每個請求通常需要單獨的連線。然而,這種 concurrency 也可能導致伺服器過載,如果管理不當。為了解決此問題,HTTP/2 引入了 MAX_CONCURRENT_STREAMS 參數,限制客戶端可維持的活躍 streams 數量。許多 HTTP/2 實現的此參數預設值通常設為 100 [1]

Client                    Server
  |                          |
  |--- HEADERS (Stream 1) ---|
  |--- HEADERS (Stream 3) ---|
  |--- HEADERS (Stream 5) ---|
  |                          |
  |<-- 200 OK (Stream 1)  ---|
  |<-- 200 OK (Stream 3)  ---|
  |<-- 200 OK (Stream 5)  ---|
            

圖 1:HTTP/2 Stream Multiplexing

2.1 Rapid Reset 漏洞

MadeYouReset 漏洞是 Rapid Reset 攻擊 (CVE-2023-44487) 的後繼者,後者在 2023 年 10 月作為零日漏洞受到矚目 [1] 。Rapid Reset 利用 HTTP/2 的 request cancellation 功能,客戶端發送 RST_STREAM frame 以表示不再需要特定回應。根據 RFC 的 stream state machine,接收到 RST_STREAM frame 的 stream 會立即轉換為 closed 狀態,且不再計入 MAX_CONCURRENT_STREAMS 限制 [2]

Rapid Reset 攻擊的核心在於伺服器的 HTTP/2 組件與其後端應用程式之間的差異。當 HTTP/2 組件在接收到 RST_STREAM 時正確關閉 stream,後端通常仍繼續處理請求並計算回應。這種行為有時是為了效率而做出的設計選擇,但卻創造了一個漏洞:攻擊者可快速開啟並取消 streams,導致後端執行大量工作,而這些 streams 不計入 MAX_CONCURRENT_STREAMS 限制。這導致伺服器上無限量的並行工作,有效繞過其內建的 concurrency 控制,導致 Denial of Service [1]

Attacker                  HTTP/2 Module               Backend
   |                           |                         |
   |--- HEADERS (Stream 1) ----|                         |
   |                           |--- Process Request -----|
   |--- RST_STREAM (S1)  ------|                         |
   |                           |                         |
   |--- HEADERS (Stream 3) ----|                         |
   |                           |--- Process Request -----|
   |--- RST_STREAM (S3)  ------|                         |
   |                           |                         |
   |                           |                    [Backend still
   |                           |                    processing
   |                           |                    requests]
            

圖 2:Rapid Reset 攻擊流程

2.2 Rapid Reset 的防護

Rapid Reset 的主要防護措施涉及限制客戶端在特定時間範圍內可發送的 RST_STREAM frames 數量。例如,常見的防護措施是將客戶端的 RST_STREAM frames 數量限制為 100 [1] 。此方法有效防止攻擊,通過重新引入對攻擊者快速重設 streams 和壓倒後端的能力限制。然而,MadeYouReset 通過將發送 RST_STREAM frames 的責任從客戶端轉移到伺服器,繞過了此防護措施。

3. MadeYouReset 漏洞

MadeYouReset 利用 HTTP/2 的一個微妙但關鍵的特性:迫使伺服器本身發出 RST_STREAM frames。這繞過了 Rapid Reset 的防護措施,後者主要聚焦於限制客戶端發起的 RST_STREAM frames。要使 MadeYouReset 有效,攻擊需要先發起一個有效的 HTTP Request,隨後執行特定動作觸發 stream-scoped error,促使伺服器發送 RST_STREAM frame,而後端繼續處理 [1] 。格式錯誤的 requests 對此無效,因為它們通常會被立即拒絕而不進行後端工作 [1]

3.1 MadeYouReset Primitives

MadeYouReset 利用六個不同的 primitives,分為兩類:無效的 control frames 和不當的 protocol flow。在這兩種情況下,RFC 定義其結果為 stream error,導致伺服器在受影響的 stream 上回應 RST_STREAM [2]

Category Primitive Description RFC Violation
Invalid Control Frames WINDOW_UPDATE with increment 0 Flow control frame with zero increment Stream error per RFC
PRIORITY frame wrong length PRIORITY frame not exactly 5 bytes Structural violation
PRIORITY self-dependency Stream dependent on itself Logical error in RFC 7540
Improper Protocol Flow WINDOW_UPDATE overflow Window size exceeds 2^31 - 1 Flow control violation
HEADERS after END_STREAM Additional headers after stream closed State machine violation
DATA after END_STREAM Additional data after stream closed State machine violation

3.1.1 無效的 Control Frames

這些 primitives 涉及發送根據 HTTP/2 RFC 結構上無效的 control frames。伺服器在嘗試解析這些格式錯誤的 frames 時,會遇到錯誤,導致 stream reset。

1. WINDOW_UPDATE frame 增量為 0 WINDOW_UPDATE frame 用於管理 flow control,允許對等端發送更多 DATA frames。增量為 0 在 RFC 中明確定義為 stream error,促使伺服器發送 RST_STREAM [2] 。雖然這看似無害,但它迫使伺服器執行有助於攻擊的動作。

// WINDOW_UPDATE frame 增量為零範例
WINDOW_UPDATE Frame {
    Stream ID: 1
    Window Size Increment: 0  // 無效 - 觸發 RST_STREAM
}

2. PRIORITY frame 長度不正確: PRIORITY frame 具有固定的 5 bytes 長度。發送任何其他長度的 PRIORITY frame 是協議的結構違規。雖然 PRIORITY 方案在較新的 RFC(如 RFC 9113 取代 RFC 7540)中已被棄用,但許多伺服器仍為了相容性而解析這些 frames,長度不正確可能觸發 stream error [2]

// 格式錯誤的 PRIORITY frame 範例
PRIORITY Frame {
    Stream ID: 1
    Length: 3  // 無效 - 應為 5 bytes
    Data: [不完整的 priority 資料]
}

3. PRIORITY frame 自我依賴: PRIORITY frame 允許通過使一個 stream 依賴於另一個來進行 stream prioritization。使 stream 依賴於自身的 PRIORITY frame 是邏輯錯誤。此特定情況在原始 HTTP/2 規範(RFC 7540)中定義為 stream error,儘管已被棄用,但在許多實現中仍常觸發 RST_STREAM [2]

// 自我依賴的 PRIORITY frame 範例
PRIORITY Frame {
    Stream ID: 1
    Exclusive: false
    Stream Dependency: 1  // 無效 - stream 依賴於自身
    Weight: 16
}

3.1.2 不當的 Protocol Flow

這些 primitives 涉及在不適當的時間或不正確的順序發送語法上有效的 frames,違反 HTTP/2 stream state machine。

1. WINDOW_UPDATE frame 導致 window overflow: 每個 stream 的 flow-control window 最大大小為 2^31 - 1。如果發送的 WINDOW_UPDATE frame 使當前 window size 加上廣告的增量超過此限制,則構成 stream error,伺服器必須以 RST_STREAM 重設 stream [2] 。此 primitive 在各種實現中被發現是最一致有效的 [2]

// 導致 overflow 的 WINDOW_UPDATE 範例
Current Window Size: 2147483647  // 2^31 - 1
WINDOW_UPDATE Frame {
    Stream ID: 1
    Window Size Increment: 1  // 導致 overflow
}
// 結果:Window size 將超過最大值,觸發 RST_STREAM

2. HEADERS frame 在 END_STREAM 後發送: HEADERS DATA frame 上的 END_STREAM 標誌表示客戶端在該 stream 上不再發送進一步的 HTTP message frames。在 END_STREAM 設定後發送額外的 HEADERS frame 違反 stream state machine,必須視為 stream error,導致 RST_STREAM [2]

// Protocol flow 違規範例
1. HEADERS Frame {
     Stream ID: 1
     END_STREAM: true  // Stream 現在為 half-closed (local)
   }
2. HEADERS Frame {
     Stream ID: 1      // 無效 - stream 已在本地關閉
     Additional headers...
   }
// 結果:Protocol 違規,觸發 RST_STREAM

3. DATA frame 在 END_STREAM 後發送: HEADERS frame 類似,在 END_STREAM 標誌設定後發送 DATA frame 是 protocol 違規,觸發 stream error 和伺服器的 RST_STREAM [2]

3.2 為何 MadeYouReset 有效:後端斷連

MadeYouReset 的有效性,與 Rapid Reset 類似,依賴於許多 HTTP/2 伺服器實現的一個關鍵架構特性:HTTP/2 protocol handling 組件與處理請求的後端應用程式之間的分離。當 HTTP/2 組件接收到 RST_STREAM frame(無論是客戶端發起還是伺服器強制),它通常從其角度關閉 stream 並停止向客戶端發送回應。然而,在許多情況下,此關閉未有效傳達給後端應用程式。因此,後端繼續處理請求並計算回應,消耗寶貴的伺服器資源,儘管客戶端不再等待它 [2]

Attacker                HTTP/2 Module              Backend
   |                        |                         |
   |--- Valid Request ------|                         |
   |                        |--- Forward Request -----|
   |--- MadeYouReset -------|                         |
   |    Primitive           |                         |
   |                        |--- RST_STREAM  ---------|
   |<-- RST_STREAM ---------|                         |
   |                        |                   [Backend continues
   |                        |                    processing -
   |                        |                    no cancellation
   |                        |                    signal received]
            

圖 3:MadeYouReset 攻擊流程

這種行為通常是為了效率而做出的設計選擇,因為中斷複雜的後端程序可能比允許其完成更耗資源。然而,當與 MAX_CONCURRENT_STREAMS 繞過結合時,這一設計決定無意中創造了漏洞。通過迫使伺服器發出 RST_STREAM frames,MadeYouReset 有效地誘騙伺服器在其後端執行無限量的工作,導致資源耗盡和 Denial of Service [2]

4. 防護策略

減輕 MadeYouReset 漏洞需要解決 HTTP/2 協議層與後端應用程式之間的基本斷連。最有效的策略專注於確保 stream resets 在整個伺服器堆疊中被正確傳播和處理。

1. HTTP/2 組件與後端之間的增強信號傳遞: 實現應建立穩健的信號傳遞機制,以在 stream 重設時通知後端應用程式。這允許後端中止特定請求的進行中處理,防止不必要的資源消耗。這可能涉及使用 inter-process communication、shared memory 或專用的 API calls 來傳播 RST_STREAM 事件 [2]

// Stream 取消信號傳遞的範例實現
class StreamManager {
    private Map<Integer, CancellationToken> activeStreams;

    public void handleRstStream(int streamId) {
        CancellationToken token = activeStreams.get(streamId);
        if (token != null) {
            token.cancel();  // 通知後端中止處理
            activeStreams.remove(streamId);
        }
    }

    public void processRequest(int streamId, Request request) {
        CancellationToken token = new CancellationToken();
        activeStreams.put(streamId, token);

        // 將 cancellation token 傳遞給後端
        backend.processAsync(request, token);
    }
}

2. 後端應用程式修改: 後端應用程式需要設計或修改為主動監聽並回應 stream reset 信號。在接收到此類信號後,後端應優雅地終止相關請求的處理。這可能涉及在應用程式邏輯中實現 cancellation tokens 或類似機制 [2]

// 支援取消的後端實現範例
public class BackendProcessor {
    public void processRequest(Request request, CancellationToken token) {
        try {
            // 在執行昂貴操作前檢查取消
            if (token.isCancelled()) return;

            // 執行資料庫查詢
            DatabaseResult result = database.query(request.getQuery());

            if (token.isCancelled()) return;

            // 處理結果
            ProcessedData data = processData(result);

            if (token.isCancelled()) return;

            // 生成回應
            Response response = generateResponse(data);

        } catch (CancellationException e) {
            // 清理資源並優雅退出
            cleanup();
        }
    }
}

3. 限制伺服器發起的 RST_STREAM 回應: 雖然作為主要防護措施效果較低,但限制伺服器對客戶端錯誤回應的 RST_STREAM frames 速率可提供次要防護層。然而,這無法解決漏洞的根本原因,不應作為唯一防護措施 [2]

5. 結論

MadeYouReset 漏洞代表了基於 HTTP/2 的 Denial of Service 攻擊的複雜演進。通過利用特定協議細微差別和 HTTP/2 處理與後端處理之間的常見架構分離,它繞過了如 Rapid Reset 的先前防護措施。理解六個 primitives 和後端工作持續進行的根本原因是開發穩健 HTTP/2 實現的關鍵。持續的安全分析和主動的防護努力,特別是專注於協議層與後端應用程式之間的穩健通訊,對保護系統免受此類進階威脅並確保網際網路基礎設施的穩定性至關重要。