作者:漂亮鼠
原文鏈接:https://mp.weixin.qq.com/s/KPHU8D_bC_zjD5LmyDWL_g
0x00 前言
去年看了portswigger的top-10-web-hacking-techniques-of-2020-nominations-open文章,里面列舉了2020年比較熱門的技術,非常有意思,地址是(https://portswigger.net/research/top-10-web-hacking-techniques-of-2020-nominations-open)。在一系列前沿技巧中我看到了這篇(https://samy.pl/slipstream/)

NAT我知道,就是動態網絡地址轉換端口映射啥的嘛,防火墻隔絕內外網的基本功能之一。slipstreaming是什么玩意?

好屌啊,nat低壓氣穴,那我這么中二肯定要看看的。第一遍初看,沒看懂,只知道從外網把受nat保護的內網端口給暴露出來了,第二遍第三遍也大概看懂了一個流程,沒有深究,一直拖到現在重新研究。最近實在不知道干嘛了,前幾天又和rr提了一嘴,那rr畢竟牛逼啊,經過rr的指點我可不得直接深入研究,于是有了下文。
0x01 知識背景
由于比較復雜,概念太多我自己也沒有特別搞得懂,我這邊先羅列幾篇背景知識文章供讀者先看看,就不在贅述了。nat slipstream作者的官網:https://samy.pl/slipstream/ 奇安信攻防社區也有發過簡單介紹這塊的文章:https://forum.butian.net/share/88 github上2009年的文章:https://github.com/rtsisyk/linux-iptables-contrack-exploit 主要模塊nf_conntrack的掃盲貼:https://clodfisher.github.io/2018/09/nf_conntrack/
我知道很多人都不會看,所以我大概簡單介紹一下好了。首先,在典型的防火墻iptables里有一個很常見的配置
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTsudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
可以認為是放行input方向標記為ESTABLISHED狀態的tcp鏈接,這種配置甚至在ubuntu官網都能找到(https://help.ubuntu.com/community/IptablesHowTo)

這也說明了這兩條配置的常見性和廣泛性,ESTABLISHED我們應該都能理解,就是tcp鏈接已經建立后的狀態,已經建立完成的鏈接自然是可以從input方向進來的,這種鏈接常見于從內向外發起后的tcp鏈接。那么這里還有一個RELATED狀態是什么呢?這個狀態主要是給ALG類協議使用的,通常ALG類協議會有兩個工作端口(典型如FTP),一個端口負責控制一個端口負責操作其他,而RELATED狀態就是標記ALG類協議的兩條TCP鏈接之間存在關聯性,也就是說如果有一條TCP鏈接被標記為和另外一條相關聯,那么他就可以從外部直接訪問到內部。關于ALG的wiki解釋如下:

下面還有一個比較詳細的表來描述這個東西

關于利用ALG類協議在NAT上任意映射端口使外部發起的鏈接可以直接訪問內部的端口,這一塊的利用可以追溯到2010年之前,可以說是歷史悠久。nat slipstream的作者在最新的文章里利用的是SIP協議,當然在很多年前他也利用過FTP。這里簡單敘述一下SIP的利用思路:
- 找到支持SIP的防火墻環境
- 通過投遞惡意頁面到內網,受害者打開惡意頁面
- 惡意頁面的js對外發送post請求
- 請求通過防火墻時候,由于MTU對包體進行了分片,post體中的一部分被防火墻識別成了SIP
- 被識別成SIP協議后防火墻就會觸發RELATED規則導致外部可以訪問指定內部端口
0x02 FTP ALG
我不像作者那樣利用SIP,因為我覺得不太好找支持SIP的(感覺),所以回到遠古的FTP上來。為了了解iptables對FTP的檢測邏輯,我翻了很多資料,就不展開講了。直接給出答案即可。我們先來了解一下FTP的ALG支持的必須條件:
- 需要有nf_conntrack模塊
- 需要有nf_conntrack_ftp模塊
- 需要配置input方向的related規則
nf_xxx是Linux內核模塊,對鏈接的狀態標記是由內核模塊完成的,所以我們必須先知道系統有沒有默認加載模塊。比如下面是ubuntu20

可以看到已經把nf_conntrack_ftp默認去掉了,所以ftp的ALG默認是不支持的。而老一些Linux系統一般都會默認加載,比如

當然也不見得新的系統就一定不會有,要知道ubuntu20是桌面系統沒有默認加載是正常的,而大部分防火墻不僅僅系統可能較老,而且出于功能性考慮肯定也會大概率加載,所以非常普遍可以認為基本都有。關于FTP的主動被動模式我就不介紹了,一點不了解的可以簡單看看(https://www.cnblogs.com/mawanglin2008/articles/3607767.html)。了解這些前提后,我們來了解一下FTP的一般命令,這里主要看主動模式:
USER adminPASS adminPORT 127,0,0,1,0,22
一目了然不做過多介紹,這里主要看port這個命令,這個命令由客戶端發出,通過防火墻后防火墻會記錄下來,然后進行前面說的映射。這里我直接用奇安信社區的一篇文章的圖來描述這個過程:

port指令前面四位是ip一看便知,后面兩位其實是端口號的高位和低位,舉個例子,比如端口號8848要獲得其高低位,先轉化成十六進制變為0x2290,然后獲得高位0x22和低位0x90,再分別轉成十進制最終得到34,144。這樣如果我們要映射127.0.0.1上的8848就寫成PORT 127,0,0,1,34,144即可。我知道這很麻煩,也不直觀,所以我現在介紹一個和他等效的命令來代替他:
EPRT |1|127.0.0.1|8848|
這個可以認為作為payload是和port等效的即可。一目了然也便于反復修改做實驗。
0x03 初步嘗試
大概概念雛形有了,我們整理一下:
- 尋找一種方法,從內部發送非加密TCP鏈接經過防火墻到外部
- 防火墻匹配到關鍵命令后根據命令的內容生成相關映射
- 從外部發起請求到防火墻上臨時允許的端口上
當然這只是雛形,實際情況要比這個復雜很多,我們一步步來。第一步最容易想到的場景就是SSRF,還有投遞惡意頁面。這里我主要以SSRF作為先決條件,那么假設我有一個SSRF漏洞可以讓我從內部發起請求到外部。接下來的核心問題就是防火墻的匹配邏輯和我的SSRF限制條件之間的場景磨合問題。如果防火墻的匹配規則比較弱智,那么我的SSRF限制條件越多對我來說場景越普遍,因為通常我們獲得的SSRF可能就只有一個限定協議的GET請求。結合nf_conntrack_ftp的匹配邏輯的部分代碼以及實驗,我得出了幾個隱藏的條件:
- 必須保持發起的鏈接狀態保持在ESTABLISHED,也就是鏈接保持住
- 發起的請求必須被拆分成兩次
- 命令必須在TCP PAYLOAD的開頭
- PORT或者EPRT命令必須在非第一次請求里
- 并不需要其他命令,但必須要有第一次請求體
- 請求端口必須是21
什么叫一個鏈接里必須拆分成兩次請求呢?我們都知道有個東西叫做長鏈接,多個http請求可以在一個tcp長鏈接里進行請求和響應,那么這里可以類比成這個,只不過不一定是http。更具體的說如下圖:

兩次包的tcp flag的push位置必須置為1才算一次,兩個push才算兩次。這里也嘗試過前面那個作者的tcp分片,嘗試了對http報文使用tcp分片來達到分包效果,然而似乎防火墻并不吃這一套。


上圖就是tcp分片的實驗截圖,并沒有用,各位可以自己抓包看一下tcp分片的兩個包第一個包并沒有標記push所以兩個分片被視為一個包。由于HTTP分包不能用,如果需要滿足上面列舉的幾個條件在常見的SSRF協議里幾乎很難做到。我分別嘗試了以下幾個方案:
- gopher:不可行,gopher雖然以tcp形式構造任意payload但是不能保持鏈接,更不能一個鏈接里發送兩次push,更多細節就不贅述了太復雜
- 30x條轉:更不可行,無論是http的還是其他協議都嘗試畸形30x跳轉,然而實際情況是30x跳轉是以關閉當前鏈接,創造新的鏈接的方式進行的,就算是滿足了同一個鏈接也控制不了payload使得其中一個以PORT開頭,要知道http的協議開頭都是GET和POST等
- pipeline:沒有場景,正向pipeline是可以的,但是由內而外的pipeline幾乎不可能存在
- http走私:暫時沒想出來可行性,感覺可以但是實際上應該是不行,因為防火墻識別報體的push次數和http的長度、trunk這些不一樣
0x04 柳暗花明
在瞎幾把嘗試的時候,我使用了一個命令來模擬http發包:
curl -X POST -T x.txt http://xxx.xxx.xxx.xxx:21
結果居然成功了,一開始我以為是分片成功了,后來仔細看抓包感覺不太對勁

(這個截圖我沒有用分片所以沒有看到分片的包)注意看劃線的部分,可以看到一個post被拆分成兩個請求報體,我們看一下是不是push位置都是1


確實都是1,太神奇了,這是怎么回事呢?我仔細看了看報文,發現post請求頭里有這個

Expect: 100-continue
這是什么?百度一下(https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/100)

意思就是客戶端先發出頭部體暫時不發送post body,頭部里加入Expect,如果服務器響應說我現在準備好接受post body了,然后客戶端再單獨發送post body。這就直接造成了兩次push在一次tcp鏈接里!完美契合我們的需求!希望有了,可是問題又來了,哪個服務端在實現SSRF的時候會使用
curl -X POST -T x.txt http://xxx.xxx.xxx.xxx:21
這樣的形式來發送呢?怕是腦殘才會這樣寫吧。那這玩意真的就沒有地方用嗎?答案是有的!遠在天邊,近在眼前!我們看一下php的curl的實現,搜一搜找到下面的線索:https://gist.github.com/perusio/1724301

什么?php的curl在發送post體超過1024個字節的時候會使用expect?????我愛php!馬上驗證嘗試,結果是自然的,有一說一,確實是這樣的。那么我們開始構造一個滿足要求的post體:
- body大于1024個字節
- 以命令開頭
- 加入一些小細節能被防火墻正確識別 直接給出php的demo
<?phpfor ($i=0; $i < 1; $i++) {echo "$i";request("http://172.28.64.142:21");}function request($url){ $requestData = "EPRT |1|172.28.64.19|8848|\r\n\r\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $requestData); $data = curl_exec($ch); curl_close($ch);}?>
好了,現在我們能發出被防火墻識別到觸發映射規則的http報文了。
0x05 實驗觀察
這里我建議先準備兩個虛擬機,一臺模擬外網主機,一臺模擬內部主機,然后在內部主機上配上防火墻策略
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTsudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPTsudo iptables -P INPUT DROP
然后我們在內網的主機里執行前面的php文件模擬對外發包,發包到我們的外網服務端上(外部服務端要怎么寫我就不放出來了,有一些小細節留給大家自己寫吧)。在發包之前,我們可以在內網的主機上監聽任意一個端口
nc -l 0.0.0.0:10000
接著在外網的主機上嘗試去直接鏈接
nc -vv xxxxx 10000
會發現怎么都連不上,連不上才是正常的。接著開啟發包,發送一個post請求,接著再次在外部主機上嘗試鏈接,會發現連接成功!

這里有個細節就是一次連接成功后斷開再次嘗試的時候會發現又連不上了,這才是正常的。一個發包只能映射一次。
0x06 拓展攻擊面
前面只是演示了一個可能的場景,但其實還可以延伸出其他可能的場景。比如:
- 惡意頁面這塊也可以再嘗試一下,部分資料顯示有些瀏覽器在特定情況下也會拆分請求,另外用戶在內部瀏覽頁面的話走的pipeline,可能利用這一點也能達成目的
- 服務端的websocket服務,如果會把我的請求放在響應當中,可能可以構造命令激活防火墻,我可以把我的發起端口改成21端口,如果防火墻在判斷的時候不計較發起方向的話?
- 反序列化的時候,類似的發出一個可控ssrf或者是打出一個tcp鏈接到外部惡意服務器比如jdbc?
- 有限getshell的情況下可以自己來觸發映射,比如直接對外請求或者shell里直接打命令通過shell傳輸顯示來觸發映射等等,如果你的內網限制跳板,那么通過觸發花式nat映射,可能可以將外網作為跳板訪問當你本來訪問不到的服務器
- 不僅僅是外網的nat,內網的多個nat,也可以嘗試花式觸發
- 其他ALG協議也有潛在的能力 有人會問,我都有SSRF了我直接打內網不就好了,我映射個啥勁?我只能說,想象力局限了,至于差別在哪里,各位可以自己在深入理解一下。
0x07 如何防御
其實我覺得不太好防御,最暴力的方式就是直接干掉related狀態,一般情況下是用不到的,尤其是在一些比較嚴格的網絡環境下自然是用不到的。用到的地方通常是比較隨意的、便利為主的網絡環境,所以在配置這個狀態規則時還是要分清楚實際場景需要,而不是抄文檔。另外下面是我找到的關于這一塊的安全加固的文章,也可以參考一下 https://home.regit.org/netfilter-en/secure-use-of-helpers/
0x08 寫在最后
由于涉及到的知識太多,我直接壓縮了很多內容的描述,也壓縮了很多細節的推到,我覺得大家真的有興趣最好自己試一下體會一下,多用wireshark抓包觀察一下。這篇文章不是終點,只是一個起點,nat slipstream也只是個起點,貼合實際泛化后,我相信會有更多的潛在場景被挖掘出來。有興趣一起研究一起學習的小伙伴可以加入我的知識星球,我會在知識星球里發更多細節以及其他有趣的文章,公眾號的文章只是冰山一角。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1716/
暫無評論