為什么一個Http Header中的空格會被駭客利用 - HTTP request smuggling


 

導讀:本文通過一個Netty的一個issue來學習什么是 "http request smuggling"、它產生的原因與解決方法,從而對http協議有進一步了解。

前言

前陣子在Netty的issue里有人提了一個問題 http request smuggling, cause by obfuscating TE header ,描述了一個Netty的http解碼器一直以來都存在的問題:沒有正確地分割http header field名稱,可能導致被駭客利用。

引起問題的那段code很簡單,它的作用是從一個字符串中分割出header field-name: 

 
for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
            char ch = sb.charAt(nameEnd);
            if (ch == ':' || Character.isWhitespace(ch)) {
                break;
            }
 }

 

這段有什么問題呢?它不應該把空格也當成header field-name的終止符,這會導致Transfer-Encoding[空格]: chunked 被解析為Transfer-Encoding 而不是Transfer-Encoding[空格]

乍一看可能讓很多人迷惑,一個Header Field名稱識別錯了為什么會被駭客攻擊呢?大不了就缺了一個Header嘛。但真實世界,卻沒這么簡單。這事,還得從現代的web服務架構說起。

起因

鏈式處理

通常現代的Web服務器並非單體存在的,而是由一系列的程序組成(比如nginx -> tomcat),為了實現均衡負載、請求路由、緩存等等功能。這些系統都會解析HTTP協議來進行一系列處理,處理完后,會將對應的請求分發鏈路中的下一個(往往也是通過HTTP協議)。

假設現在一個用戶的請求到達邏輯服務器B的過程為: 用戶 -> A -> B 。通常會有許多個用戶連接到A,而為了減少連接建立的消耗,A到B的連接數會少很多,A與B之間會保持穩定的長連接,這樣能夠使性能得到提升。所以可能屬於不同客戶端的多個請求會共用同一個連接。(以NGINX舉例,Nginx從1.1.4開始支持到后端的長連接池,不過在之前是使用的HTTP/1協議與后端通信,即創建連接處理完之后銷毀。)

 

 

大家的請求都合在一起了,如何區分請求與請求之間的邊界呢?接下來我們再來看看Http協議中的Transfer-Encoding: chunkedContent-Length

Transfer-Encoding: chunked和Content-Length

Http協議通過Transfer-Encoding: chunked(以下簡稱TE)或Content-Length(以下簡稱CL)來確定entity的長度。

TE表示將數據以一系列分塊的形式進行發送,在每一個分塊的開頭需要添加當前分塊的長度,以十六進制的形式表示,后面緊跟着 '\r\n' ,之后是分塊本身,后面也是'\r\n' 。終止塊是一個常規的分塊,不同之處在於其長度為0。

而CL通過直接指定entity的字節長度來完成同樣的使命。

那么當它們兩個同時出現在Http Header里時會發生什么呢?實際上Http協議規范有明確定義,當這兩種確定長度的Header都存在時,應該優先使用TE,然后忽略CL。

看到這,結合上述那段不規范的header field-name解析代碼,或許你想到了一些東西?假如一個包含了TE和CL的Http請求被處理了兩次會發生什么?接下來,我們來看看具體的情況。

發送攻擊請求

此時我們依舊使用 用戶 -> A -> B 的例子。 這里我們假設B是存在問題的Netty,而A是一個正常的只識別CL的程序。一個包含了Transfer-Encoding[空格]: chunkedContent-Length的Http請求來了

POST / HTTP/1.1

Host: vulnerable-website.com

Content-Length: 8

Transfer-Encoding[空格]: chunked

 

0

 

FOR

然后又來了一個正常的用戶請求

GET /my-info HTTP/1.1

Host: vulnerable-website.com

A

A識別到了CL為8,並將這兩個請求合並到一個連接中一起傳給B

B

B識別到了Transfer-Encoding[空格],按照規則忽略了CL,此時B會怎么分割這兩個請求呢:

請求1

POST / HTTP/1.1

Host: vulnerable-website.com

Content-Length: 8

Transfer-Encoding[空格]: chunked

 

0

請求2

FORGET /my-info HTTP/1.1

Host: vulnerable-website.com

這就糟糕了:服務端報了一個錯,正常用戶獲得了一個400的響應。

 

(任何上下游識別請求的分界不一致的情況都會出現這樣的問題)

如何避免這樣的漏洞

  1. 避免重用連接,這樣的方式會造成性能的下降。
  2. 使用HTTP/2作為系統間協議,此協議避免了http header的混淆。
  3. 使用同一種服務器作為上下游服務器。
  4. 大家都按規范來,不出BUG。

其中我們展開了解一下為什么HTTP/2可以避免這種情況

HTTP/2不會有含糊不清的Header

HTTP/2是一個二進制協議,致力於避免不必要的網絡流量以及提高TCP連接的利用率等等。

它對於常用的Header使用了一個靜態字典來壓縮。比如Content-Length使用28來表示;`

Transfer-Encoding`使用57來表示。這樣一來,各種實現就不會有歧義了。更多的定義詳見HTTP/2關於Header靜態表的定義

 

 

文獻

  1. Hypertext Transfer Protocol (HTTP/1.1)關於Http Header Field的解析定義
  2. Transfer-Encoding - HTTP | MDN
  3. Content-Length - HTTP | MDN
  4. What is HTTP request smuggling? Tutorial & Examples
  5. HTTP/1.x 的連接管理 - HTTP | MDN
  6. HTTP/2關於Header靜態表的定義
  7. HTTP Keepalive Connections and Web Performance


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM