摘要

報告將 CRLF 驅動的資料流不同步視為邊界完整性失效,而非孤立的標頭注入(header-injection)缺陷。主要研究顯示,百分比解碼後的換行字元可以改變 Reverse Proxy 轉發的 Request ,使得 Proxy 與上游解析器對 Request 數量、訊息長度或回應所有權產生不同認知 [1] 。這條鏈可能導致 Request 分割、回應佇列污染、CL.TE 不同步、快取操作、存取控制繞過、瀏覽器媒介執行,以及 Session 資料暴露。可利用性取決於正規化、Connection reuse、框架、回應大小和瀏覽器行為。因此防禦必須移除不安全的插入點,並保留明確的上游框架。

揭開 Nginx CRLF 注入的真相! CL.TE 與 Desync 如何讓 Proxy 邊界全面失守? | 資訊安全新聞

1. 研究問題與系統模型

低影響的標頭注入操作(primitive)如何變成多使用者資料流不同步狀況?答案在於架構。Reverse Proxy 終止一個串流,並為上游服務建構另一個 Request。如果它對 Path 或變數的正規化方式與上游解析器不同,編碼後的 CRLF 就會變成真正的分隔符。前端可能認為它轉發了一個 Request ,而上游卻解析出兩個 Request 或不同的 Body 邊界:這是一種透過持久串流展現的解析器差異。

主要文章中的 Nginx 範例明確展示了這種轉換。

清單 1. 不安全的 Nginx 轉發設定

  1. # nginx.conf
  2. http {
  3. upstream backend {
  4. server backend.internal.com:8000;
  5. }
  6. server {
  7. location / {
  8. proxy_pass http://backend$uri;
  9. }
  10. }
  11. }
註解. 此設定將正規化後的 $uri 直接放入 proxy_pass 。在主要研究中,Nginx 在正規化期間會對編碼的 CRLF 進行 URL 解碼,因此攻擊者控制的 Path 位元組可以成為上游的換行符。安全邊界不是用戶端可見的 Request ,而是發送給上游解析器的後正規化串流。此設定重現自主要文章,並非建議的部署模式 [1]
sequenceDiagram participant C as Client participant F as Reverse proxy participant B as Upstream parser participant U as Shared user connection C->>F: URL contains encoded CRLF F->>F: Decode and normalize request target F->>B: One forwarded stream with injected delimiter B->>B: Parse wrapper request and smuggled request B-->>F: Response A B-->>F: Response B F-->>C: Maps only Response A F->>U: Queues Response B for next request U-->>C: Later response may belong to another request

圖 1. CRLF 到回應佇列路徑。

2. 從標頭注入到 Request 分割

第一個實驗步驟有可觀測性。主要研究建議注入無效語法,該語法應產生可預測的狀態碼:無效的協定版本可產生 505,而無效的 Transfer-Encoding 值則可產生 501。這些回應是差異預言(Differential oracle),顯示注入的位元組存活下來通過前端轉換,並在上游被解釋。

以下配對的 Request 和回應說明為何必須分別分析表面上的 Path 和轉發的 Request 。

清單 2. 編碼的 CRLF 改變了上游 Request 結構

  1. GET /%20HTTP/1.1%0d%0aContent-Length:%20X%0d%0aX:%20x HTTP/1.1
  2. Host: example.com
  3. GET / HTTP/1.1
  4. Content-Length: X
  5. X: x HTTP/1.1
  6. Host: example.com
  7. HTTP/1.1 400 Bad Request
註解. 第一個區塊是使用者端可見的 Request 。解碼後,注入的位元組引入了新的 Content-Length 欄位,以及在原始 Host 標頭之前的額外類標頭片段。400 回應之所以有用,是因為它是上游解析的確定性證據,而非因為它本身就是影響。一旦分隔符可以建立有效的第二個 Request ,相同的操作(primitive)就可以變成 Request 分割和回應佇列污染 [1]

當第二個 Request 完整時, Request 分割會變得嚴重。上游會產生兩個回應,但預期只有一個回應的前端會將額外的回應保留在佇列中。在重複使用的 TCP 連線上,下一個用戶端 Request 可能會收到佇列中的回應。Academy 證實,Connection reuse、完整的獨立 Request 以及連線持久性是關鍵的先決條件 [4] 。因此,機密性和可用性是相互關聯的:在使用者收到錯誤回應的同時,不相關的回應也可能被收集。

3. 框架差異與 CL.TE 資料流不同步

CRLF 注入也可以添加框架標頭,而不是第二個 Request 邊界。在主要研究中,注入的 Transfer-Encoding: chunked 標頭與用戶端提供的 Content-Length 結合。前端可能使用長度值,而上游則採用 chunked framing,或者相反。Timeout 探測很有價值,因為它可以測試前端是否仍在等待上游已認為完整的位元組。

清單 3. 主要研究中的 CL.TE timeout 探測

  1. POST /<@urlencode_all> HTTP/1.1
  2. Transfer-Encoding: chunked
  3. Foo: bar</@urlencode_all> HTTP/1.1
  4. Host: example.com
  5. Content-Length: 13
  6. d
  7. x=y
  8. 0
  9. -TIMEOUT-
註解. Hackvertor 包裝器表示可見的注入在傳輸前經過 URL 編碼。插入的 Transfer-Encoding 標頭改變了上游解析器解析 Body 的方式。Short chunk sequence 終止了 chunk 解析,而宣告的長度則為前端留下了框架問題。懸停(hang)而非立即回應是兩個解析器對訊息完成狀態認知不同的證據;此探測僅應在授權的測試環境中使用 [1]

僅掃描狀態碼是不夠的。目標可能會拒絕兩個連續 Request 並關閉連線,但卻接受一個注入的標頭並進入 CL.TE 狀態。相反地,堆疊的回應可能是由於過度讀取或普通的流程化(pipelining)所導致。可靠的分析需比較解析器行為、連線生命週期、邊界、回應順序以及受控的 Payload 變化。

4. 瀏覽器傳遞的資料流不同步

主要研究將威脅模型從客製化用戶端擴展到瀏覽器。許多 CRLF 驅動的案例可以透過 navigation 或 fetch 發出,將執行環境移到受害者的連線環境中。補充研究指出,跨來源(cross-origin)的 header 和 Body 受到限制,而 HTTP/1.1 connection reuse 對用戶端案例仍然很重要 [3] 。主要研究表明,當直接的 cross-user reuse 不可行時, CRLF 注入 仍然可以提供此種瀏覽器相容路徑。

清單 4. 主要研究中與瀏覽器相容的 Request 建構

  1. fetch(
  2. "https://example.com/%20HTTP/1.1%0d%0a
  3. Transfer-Encoding:%20chunked%0d%0a
  4. Foo:%20bar",
  5. {
  6. method: "POST",
  7. body: "0\r\n\r\nTRACE / HTTP/1.1\r\nX: x"
  8. }
  9. )
註解. URL 承載了編碼的換行字元,這些字元在上游變成了語法;Body 提供了一個 chunk terminator,後接第二個 Request 。重要的屬性不是 JavaScript 的複雜性,而是傳輸等價性:瀏覽器可以發出一個 Request ,其位元組導致 Proxy/上游邊界產生分歧。如果產生的回應提供 XSS 執行環境,瀏覽器可以重新啟動相同的操作(primitive),並形成一個自我傳播的 Desync 蠕蟲,正如主要研究中所理論化和展示的那樣 [1]

連線鎖定和 IP 鎖定的案例會改變影響路徑,而不是移除缺陷。HEAD 回應大小操作、利用 Range 的長度控制、iframe 重複和視窗排序使時序敏感的執行更加可靠。這些是疊加在原始解析器差異上的傳遞技術,因此評估應區分網路可達性和瀏覽器可達性。

5. 防禦意涵

主要研究建議避免在 Nginx proxy_pass return 指令中使用 $uri $document_uri ,同時確保正規表達式衍生的變數排除空格和控制字元。審查應包含 OpenResty 和 Tengine,因為其衍生版本保留了正規化風險 [1] 。應在正規化之前進行驗證,並拒絕編碼的 CR、LF 和其他與框架相關的重要位元組,而不是在解碼後進行修復。

防禦也必須是系統性的。每個 hop 應使用 one request-length 演算法,移除衝突的框架標頭,並在解析失敗時關閉連線。除非能嚴格證明請求與回應的數量對應關係,否則回應佇列不應跨越不同的安全環境。比較 HTTP/1.1 的研究認為,HTTP/2 在上游消除了文字框架歧義的一大類問題,但轉換與實作上的缺陷仍需進一步驗證 [2] 。目標是 Request 位元組、解析訊息和回應之間的一對一對應。

6. 結論

CRLF 驅動的資料流不同步是一種組合性失效:正規化創造了語法,解析器差異創造了額外訊息,Connection reuse 傳播了錯誤,而瀏覽器操作(primitive)將執行環境重新定位到使用者 Session 中。主要文章將標頭注入重新定義為一個影響機密性、完整性和可用性的串流完整性問題 [1] 。嚴謹的測試應對每個轉換進行建模,確認回應佇列行為,並將時序、狀態碼和連線關閉視為解析器狀態的證據。安全的設定和明確的框架比針對 Payload 的過濾器更為持久。