0x00:寫在前面
膜拜了國內譯者分析翻譯了BlackHat的一篇議題(HTTP Desync Attacks: Smashing into the Cell Next Door)
收獲滿滿,正好結合一道CTF題目將此知識點進行鞏固
[RoarCTF 2019]Easy Calc
復現地址:https://buuoj.cn/challenges
0x01:HTTP請求走私漏洞
在當前的網絡環境中,不同的服務器以不同的姿勢去實現RFC標准(包含了關於Internet的幾乎所有重要的文字資料)
總體來說在網絡中數據包的傳輸格式都是嚴格遵守着協議來規划的。但正是因為細微的差異性,對同一個HTTP請求,不同的服務器可能會產生不同的處理結果,安全風險就在此處產生了。
首先學習一下數據包傳輸遵守的協議-HTTP 1.1協議
在遠古時期(HTTP1.0)之前的協議中,客戶端每進行一次HTTP請求,就需要和服務器進行一次TCP交互(三次握手),基於當時的網絡環境並不像如今web頁面由多種網絡資源組成,在當時仍然夠用。但如今肯定是太消耗服務器資源,不適用當前的環境。所以HTTP1.0/1出現了。
這里介紹兩個重要協議特性Keep-Alive&Pipeline
keep-alive肯定不陌生,在數據包中經常可以看到connection:close/keep-allive
所謂 Keep-Alive
,就是在HTTP請求中增加一個特殊的請求頭 Connection: Keep-Alive
,告訴服務器,接收完這次HTTP請求后,不要關閉TCP鏈接,后面對相同目標服務器的HTTP請求,重用這一個TCP鏈接,這樣只需要進行一次TCP握手的過程,可以減少服務器的開銷,節約資源,還能加快訪問速度。當然,這個特性在 HTTP1.1
中是默認開啟的。
Pipeline
,在這里呢,客戶端可以像流水線一樣發送自己的HTTP請求,而不需要等待服務器的響應,服務器那邊接收到請求后,需要遵循先入先出機制,將請求和響應嚴格對應起來,再將響應發送給客戶端。
HTTP Pipelining(管線化)字段,它是將多個http請求批量提交,而不用等收到響應再提交的異步技術。如下圖就是使用Pipelining和非Pipelining
這意味着前端與后端必須短時間內對每個數據包的邊界大小達成一致,否則,攻擊者就可以構造發送一個特殊的數據包,在前端看來它是一個請求,但在后端卻被解釋為了兩個不同的HTTP請求。
為了提高用戶體驗。很多站主選擇CDN來進行加速,CDN原理類似nginx的反向代理,在源站前面加上一層反向代理服務器,當用戶請求一些靜態資源時,直接由代理服務器進行分配相關的資源服務器
因為Keep-alive的特性,為了緩解服務器壓力,一般來說,反向代理服務器與后端的源站服務器之間,會重用TCP鏈接。這也很容易理解,用戶的分布范圍是十分廣泛,建立連接的時間也是不確定的,這樣TCP鏈接就很難重用,而代理服務器與后端的源站服務器的IP地址是相對固定。
當我們向代理服務器發送一個精心構造的HTTP請求時,由於兩者服務器的實現方式不同,可能代理服務器認為這是一個HTTP請求,然后將其轉發給了后端的源站服務器,但源站服務器經過解析處理后,只認為其中的一部分為正常請求,剩下的那一部分,就算是走私的請求,當該部分對正常用戶的請求造成了影響之后,就實現了HTTP走私攻擊。
0x02:HTTP走私攻擊的四種攻擊方式
1:CL不為0(Content-length不為0)
所有不攜帶請求體的HTTP
請求都有可能受此影響。這里用GET
請求舉例。
前端代理服務器允許GET
請求攜帶請求體;后端服務器不允許GET請求攜帶請求體,它會直接忽略掉GET
請求中的Content-Length
頭,不進行處理。這就有可能導致請求走私。
構造請求示例:
GET / HTTP/1.1\r\n Host: test.com\r\n Content-Length: 44\r\n GET / secret HTTP/1.1\r\n Host: test.com\r\n \r\n
\r\n是換行的意思,windows的換行是\r\n,unix的是\n,mac的是\r
漏洞觸發
前端服務器收到該請求,讀取Content-Length
,判斷這是一個完整的請求。
然后轉發給后端服務器,后端服務器收到后,因為它不對Content-Length
進行處理,由於Pipeline的存在,后端服務器就認為這是收到了兩個請求,分別是:
第一個:
GET / HTTP/1.1\r\n Host: test.com\r\n
第二個
GET / secret HTTP/1.1\r\n Host: test.com\r\n
隨即第二個請求造成了HTTP請求走私,成功被利用。
2:CL-CL
有些服務器不會嚴格的實現該規范,假設中間的代理服務器和后端的源站服務器在收到類似的請求時,都不會返回400
錯誤。
但是中間代理服務器按照第一個Content-Length
的值對請求進行處理,而后端源站服務器按照第二個Content-Length
的值進行處理。
POST / HTTP/1.1\r\n Host: test.com\r\n Content-Length: 8\r\n Content-Length: 7\r\n 12345\r\n a
觸發過程
中間代理服務器獲取到的數據包的長度為8,將上述整個數據包原封不動的轉發給后端的源站服務器。
而后端服務器獲取到的數據包長度為7。當讀取完前7個字符后,后端服務器認為已經讀取完畢,然后生成對應的響應,發送出去。而此時的緩沖區去還剩余一個字母a,對於后端服務器來說,這個a是下一個請求的一部分,但是還沒有傳輸完畢。
如果此時有一個其他的正常用戶對服務器進行了請求:
GET /index.html HTTP/1.1\r\n Host: test.com\r\n
因為代理服務器與源站服務器之間一般會重用TCP
連接。所以正常用戶的請求就拼接到了字母a的后面,當后端服務器接收完畢后,它實際處理的請求其實是:
aGET /index.html HTTP/1.1\r\n Host: test.com\r\n
這時,恰巧被拼接上的數據包即會返回aGET request method not found
報錯。
3:CL-TE
CL-TE
,就是當收到存在兩個請求頭的請求包時,前端代理服務器只處理Content-Length
請求頭,而后端服務器會遵守RFC2616
的規定,忽略掉Content-Length
,處理Transfer-Encoding
請求頭。
POST / HTTP/1.1\r\n Host: test.com\r\n ...... Connection: keep-alive\r\n Content-Length: 6\r\n Transfer-Encoding: chunked\r\n \r\n 0\r\n \r\n a
連續發送幾次請求就可以獲得響應。
觸發過程
由於前端服務器處理Content-Length
,所以這個請求對於它來說是一個完整的請求,請求體的長度為6,也就是
0\r\n \r\n a
當請求包經過代理服務器轉發給后端服務器時,后端服務器處理Transfer-Encoding
,當它讀取到
0\r\n \r\n
認為已經讀取到結尾了。
但剩下的字母a就被留在了緩沖區中,等待下一次請求。當我們重復發送請求后,發送的請求在后端服務器拼接成了類似下面這種請求:
aPOST / HTTP/1.1\r\n Host: test.com\r\n ... ...
服務器在解析時就會產生報錯了,從而造成HTTP
請求走私。
4:TE-CL
TE-CL
,就是當收到存在兩個請求頭的請求包時,前端代理服務器處理Transfer-Encoding
請求頭,后端服務器處理Content-Length
請求頭。
POST / HTTP/1.1\r\n Host: test.com\r\n ...... Content-Length: 4\r\n Transfer-Encoding: chunked\r\n \r\n 12\r\n aPOST / HTTP/1.1\r\n \r\n 0\r\n \r\n
觸發過程
前端服務器處理Transfer-Encoding
,當其讀取到
0\r\n \r\n
認為是讀取完畢了。
此時這個請求對代理服務器來說是一個完整的請求,然后轉發給后端服務器,后端服務器處理Content-Length
請求頭,因為請求體的長度為4
.也就是當它讀取完
12\r\n
就認為這個請求已經結束了。后面的數據就認為是另一個請求:
aPOST / HTTP/1.1\r\n \r\n 0\r\n \r\n
成功報錯,造成HTTP
請求走私。
0x03:CTF題目考點
在CTF中目前遇到的題目考查點是利用HTTP走私繞waf
題目是一個計算的框,查看源代碼得知了calc.php頁面,訪問得知
通過測試得知,上圖只是一個簡單的過濾,后台仍然存在waf過濾了字母
那么此時就可以用到HTTP走私來進行bypass
構造前
構造后
如上圖所示,添加兩個Content-Length即可進行HTTP走私
效果類似第一個Content-Length相當於欺騙waf 我是一個POST表單,順利通過后傳到后台服務器進行處理,成功解析Phpinfo頁面。
0x04:總結
雖然HTTP走私漏洞實際挖掘漏洞過程中很少涉及
觸發條件苛刻
但不失為一種好的突破方式,日常測試中可以加兩個Content-Length觀察返回包
作為開發和安全運維人員來說,要嚴格遵守RFC協議所制定的標准方可避免走私漏洞的存在。