導讀:本文經過一個Netty的一個issue來學習什麼是 "http request smuggling"、它產生的緣由與解決方法,從而對http協議有進一步瞭解。html
前陣子在Netty的issue裏有人提了一個問題 http request smuggling, cause by obfuscating TE header ,描述了一個Netty的http解碼器一直以來都存在的問題:沒有正確地分割http header field名稱,可能致使被駭客利用。java
引發問題的那段code很簡單,它的做用是從一個字符串中分割出header field-name:node
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[空格]
。nginx
乍一看可能讓不少人迷惑,一個Header Field名稱識別錯了爲何會被駭客攻擊呢?大不了就缺了一個Header嘛。但真實世界,卻沒這麼簡單。這事,還得從現代的web服務架構提及。git
一般現代的Web服務器並不是單體存在的,而是由一系列的程序組成(好比nginx -> tomcat),爲了實現均衡負載、請求路由、緩存等等功能。這些系統都會解析HTTP協議來進行一系列處理,處理完後,會將對應的請求分發鏈路中的下一個(每每也是經過HTTP協議)。github
假設如今一個用戶的請求到達邏輯服務器B的過程爲: 用戶 -> A -> B
。一般會有許多個用戶鏈接到A,而爲了減小鏈接創建的消耗,A到B的鏈接數會少不少,A與B之間會保持穩定的長鏈接,這樣可以使性能獲得提高。因此可能屬於不一樣客戶端的多個請求會共用同一個鏈接。(以NGINX舉例,Nginx從1.1.4開始支持到後端的長鏈接池,不過在以前是使用的HTTP/1協議與後端通訊,即建立鏈接處理完以後銷燬。)web
你們的請求都合在一塊兒了,如何區分請求與請求之間的邊界呢?接下來咱們再來看看Http協議中的Transfer-Encoding: chunked
和Content-Length
。windows
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[空格]: chunked
和Content-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識別到了CL爲8,並將這兩個請求合併到一個鏈接中一塊兒傳給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的響應。
(任何上下游識別請求的分界不一致的狀況都會出現這樣的問題)
其中咱們展開了解一下爲何HTTP/2能夠避免這種狀況
HTTP/2是一個二進制協議,致力於避免沒必要要的網絡流量以及提升TCP鏈接的利用率等等。
它對於經常使用的Header使用了一個靜態字典來壓縮。好比Content-Length
使用28來表示;`
Transfer-Encoding`使用57來表示。這樣一來,各類實現就不會有歧義了。更多的定義詳見HTTP/2關於Header靜態表的定義