命令註入新玩法:巧借環境攻擊目標
0x00 酷趣 wargame
由於受賞金廠商保密協議所限,我無法公開原始漏洞詳情,但我更清楚 “talk is cheap, show me the code”,耗時費神,找到一個 wargame,相較賞金漏洞,不但體現了相同精髓,這個 wargame 還多了些限制條件,所以,讓它變得更加有趣、更有挑戰。
我把 wargame 源碼寫入
CMDi_lab/escaping_quotation/index.php,核心如下:
快速過下源碼。首先,用 GET 方法獲取 f1、f2 兩個參數;然後,用相同的正則過濾 f1、f2,包括過濾引號防止利用環境寫 webshell,過濾常見命令分隔符(;)、命令替換符($())防止註入新命令,過濾常見命令(ls、cat)禁止基礎操作;接著,用引號再建一道防禦工事,讓所有輸入均在引號內,讓命令行元字符失效;最後,以 f1、f2 為命令參數執行系統命令 file。
怎麽樣!看上去是很完善的防禦體系。先正常訪問試試:
服務端執行 file 命令,正確識別出目錄和文件的類型。嘗試提交命令分隔符(;)和命令替換符(“):
由於服務端正則表達式匹配上 ; 和 “,導致提交的文件名被置為空,所以,file 提示無法找到相關文件。
經驗主義,我將從三個層面尋找突破口:攻擊正則、攻擊引號、攻擊命令行。那麽,我就準備開動了,各位。
成敗正則
在命令註入場景下審查正則表達式,我習慣關註四個方面:是否使用多行模式修飾符(/foo/m)、是否遺漏匹配對象末尾的換行符(/^\d+$/)、是否允許空白字符(\s)、是否誤寫反斜杠匹配模式(/\/)。
使用多行模式修飾符。把多行模式用於匹配希望允許的字符時,就會存在邏輯問題。比如,如下代碼:
原本希望只允許 xx.xx.xx.xx 格式的 IP 地址,由於使用多行模式,只要輸入中某行滿足條件即可,那麽,我可以用換行符輸入多行,第一行滿足要求 127.0.0.1、第二行任意內容,這樣輕松繞過正則限制:
本 wargame 未使用多行模式,所以不存在這個問題。
遺漏匹配對象末尾的換行符。某些模式在匹配時會忽略字符串末的換行符,而換行符自身又是一個有效的目錄分隔符,將導致註入新命令。如下代碼:
原意是過濾掉輸入中非字母、數字外的其他所有字符,輸入換行符試試:
果然被過濾了。但,若把換行符放至字符串末尾,正則反而無法匹配上:
這可有趣了,又能愉快地註入新命令了:
本 wargame 未能成功過濾掉換行符,但不是因為上面的原因。
允許空白字符。空白字符包括空格、換行符、水平制表符、垂直制表符等四個,命令註入的好朋友換行符也在其中。代碼:
本意只允許字母、數字、空格等字符,但遺漏了換行符,導致命令註入漏洞:
本 wargame 未能成功過濾掉換行符,但不是因為上面的原因。
誤寫反斜杠匹配模式。正則表達式自身是個字符串,並非直接傳遞給正則引擎,而是先由語言對字符串進行處理後再傳遞給正則引擎。我希望匹配上反斜杠(),逆向思考下這個過程,由於反斜杠在正則引擎是個特殊字符,所以 \ 才能讓正則引擎正確識別到反斜杠 ;正則引擎之前,\ 經過語言的的字符串處理,由於反斜杠在字符串中也是特殊字符,所以,一個 就得用 \ 表示、兩個 就得用 \\ 表示。那麽,但凡用正則表達式匹配斜杠,必須得用 \\。這是不具備原生字符串特性(r)的腳本語言的通病,是有一點繞。比如:
訪問看看:
記得 wargame 也過濾了反斜杠,回過頭看看,哇喔,的確誤用了:
OK,在正則部分,由於誤寫匹配模式,我找到了漏網之魚,反斜杠。如何利用?不知道,走一步看一步。
0×02 引號逃逸
接著我來琢磨下 25、26 行。這兩行目的很清晰,用引號包裹輸入字符串,預防可能因正則過濾不嚴傳遞一些個特殊字符到命令行環境,思路是對的,但效果就差強人意了。
載荷一旦進入引號內,都將退化成普通字符串,好無殺傷力,唯一例外,命令替換符(“ 或 $()),遺憾的是,命令替換符被正則嚴防死守,無法到達 25、26 行。所以,下意識地想到,引號逃逸。
引號逃逸,目的是讓輸入跳脫至引號外,恢復特殊字符的身份,而不再被引號所束縛,僅僅是個普通字符。我常用兩種手法,一是閉合、二是轉義。
閉合手法逃逸引號。在輸入中添加一個引號,讓其與左引號結對,自然閉合,接著輸入中就能出現惡意字符,最後輸入中再添加一個引號,與右引號結對,或者,輸入註釋符以忽略右引號。比如:
我的所有輸入都只能留在引號內,導致命令分隔符無法被命令行正確識別:
我在輸入中增加兩個引號(②、③),這樣剛好與代碼中的引號閉合(① 和 ②、③ 和 ④),所以,我的其他輸入字符(;id;)就能出現在引號之外,成功逃逸引號:
轉義手法逃逸引號。引號自身也是個特殊字符,如果有辦法讓它變成普通字符,那麽輸入的其他特殊字符就能讓命令行正確識別。反斜杠可以辦到!如下代碼:
含有惡意字符的輸入被限定在引號內:
假設服務端生成的命令模型為 file “foo” “;date”,這時,我利用反斜杠將 ② 號引號轉義為普通字符,那麽 ① 和 ③ 號引號將自然結合,接著利用註釋符將 ④ 號引號註釋掉,;date 對於命令行直接可見,邏輯上我能用 file “foo” “;id #bar” 註入任意命令,再次逃逸到引號外:
註,註釋符 # 需要 URL 編碼為 %23。
好了,還記得前面我找到正則漏洞無法過濾反斜杠麽,wargame 中的引號已經無法束縛我,雖然當下無法直接利用,但至少又讓我向前邁出一步。
0×03 選項註入
繼續看 29、30 兩行的命令執行代碼。顯然這與命令註入漏洞多少有些關系。命令註入常見三種手法:利用命令分隔符註入命令、利用命令替換符註入命令、利用命令選項註入命令。
命令分隔符註入命令。命令分隔符包括換行符(\n)、分號(;)、邏輯與(&&、&)、邏輯或(||、|),若在 win 批處理腳本中還能用 %1A。比如:
命令替換符註入命令。shell 優先執行命令替換符內的命令,目的是便於運維人員將前個命令的輸出作為後個命令的輸入。命令替換符包括 $(…)、反引號 …
。比如:
命令選項註入命令。命令選項(option)和命令參數(argument)是兩個概念,國內外很多文獻都將他倆混淆。比如:
其中,-d 是命令選項、/tmp/ 是命令參數。很多時候,藍隊過濾掉所有命令分隔符、命令替換符,雖然我無法直接註入命令,但我可以註入其他命令選項,這就給我很大想像空間,某些選項可以讀取文件、有些又能寫入文件、甚至執行其他命令。比如,有個頁面,可以將 web 目錄打包為你指定的歸檔文件,輸入為歸檔文件名 $archive 參數,服務端過濾所有命令註入相關字符,調用 system(“tar -cf” . $archive . “*”) 執行命令:
但我通過註入 tar 命令的 –checkpoint、–checkpoint-action=exec 兩個選項,成功執行命令 id:
wargame 中執行的是 file 命令,查看下它有哪些用得上的選項,比如,是否有選項可以讀文件,man 中搜索 read,找到 -f 選項:
仔細看下,該參數並不能讀取顯示文件內容,只是從該文件中獲取文件列表,沒意思(。?_?。)。等等,報錯信息中有啥提示:
哇噢,歷害啦,通過註入命令選項 -f,讓我可以讀取 wargame 的文件內容。
0×04 全面瓦解
管它金城湯池還是銅墻鐵壁,一顆松滑螺釘,它將全面瓦解。
將多個獨立漏洞組合成漏洞鏈,完成目標攻擊,絕對是我的 G 點。回顧前面的成果,由於誤寫正則表達式,導致無法過濾反斜杠;通過反斜杠,可以逃逸引號;通過引號逃逸,創造出命令選項註入的條件;通過註入 -f 選項,實現 flag 文件讀取。過濾反斜杠的正則,就是那顆松滑的螺釘。
現在,攻擊目標前,還剩一個問題,我並不清楚 flag 文件路徑及文件名。首先想到的是暴破。土!的確很土氣,用常見的 flag、FLAG、f14g 等等常見 flag 名暴了一遍,毫無收獲。換個手法,通配符模式匹配。這下洋氣了吧。
通配符模式匹配(globbing patterns),也叫路徑名擴展(pathname expansion),簡單來說,在表示文件名/目錄名或路徑時,你可以用 ? 代表任一可見字符、用 * 代表零或多個可見字符、用 [a-z] 代表字符範圍,唯一例外,以 . 開頭的文件或目錄、以 / 分隔的路徑必須顯式寫明,否則無法被模式匹配。
比如,我並不還知道 /tmp/ 目錄下有個名為 FindMe 的文件,但,借助通配符多次測試,不但刺探出該文件的存在,還成功查看到文件內容:
好,現在一切就緒,攻擊 CMDi_lab/escaping_quotation。有了前面的分析,我構造了載荷 f1=foo&f2=-f ? bar #,將 file “foo” “bar” 轉換為 file “foo” “-f ? bar #”,猜解文件名只有一個字符的文件:
顯然,沒找到這樣的文件,相同思路,借助 burp 自動查找文件名長度在 [1, 16] 的所有文件:
跑完還是沒有找到任何文件。這就奇怪了,前面說過,通配符無法匹配 .,莫非是隱藏文件,調整下載荷, f1=foo&f2=-f .? bar #,再次暴破:
找到名為 .f1a9_ 的目錄,繼續調整載荷 f1=foo&f2=-f .f1a9_/.? bar #,暴破:
找到名為 .f1a9_/.flag_15_here.txt 的文件,帶上準確路徑訪問:
WTF!不應該啊,邏輯上說不通。別急,捋一捋,莫非載荷中新增部分有被過濾的字符?回到前面的正則源碼處,的確過濾了 flag 關鍵字,我用通配符替換,載荷變成 f1=foo&f2=-f .f1a9_/.fl?g_15_here.txt bar #,另外,命令選項 -f 前應該得加個空格,最終載荷為 f1=foo&f2= -f .f1a9_/.fl?g_15_here.txt bar #,來一發:
多麽愉悅的攻擊體驗!
0×05 非預期解法
絲滑般的思緒,真實而自然!思緒自然?!正則未正確過濾反斜杠、利用反斜杠逃逸引號、通配符模式猜解路徑、註入命令選項讀取文件,做作、別扭!以上是我為了湊字數、增篇幅寫的,真實的攻擊手法並非如此。
仔細審計正則過濾的代碼。用 \ 而非 \\ 表述反斜杠,不僅無法正確過濾反斜杠,還會引發連鎖反應。你看,緊隨 \ 其後的是 |\n:
前面提過,\ 結果字符串轉義後到達正則引擎變成 ,它與 |\n 結合變成 |\n,正則引擎誤解成匹配豎線與換行符的組合。當我輸入豎線與換行符的組合,確認被過濾:
換言之,服務端只過濾 |\n 而放行 \n。有換行符,我可以直接註入新命令,比如,執行命令 id:
既然能註入命令了,查看 flag 易如反掌!命令 grep -r . . 可以查看當前目錄下所有文件內容,服務端過濾了 grep,我用內部空變量輕松繞過(g$1rep -r . .),或者,無效轉義繞過(g\rep -r . .),或者,通配符繞過(/bin/gr?p -r . . 或 /bin/gr[d-f]p -r . .),我有 1024 種方式吊打目標。
OK,清晰,爭取一次搞定,構造載荷 ?f1=foo&f2=%0a/bin/gr[d-f]p+-r+.+.+%23,頁面顯示:
0×06 故事尾聲
最後聊聊你關心的賞金漏洞。大致業務場景,服務端執行打包命令壓縮幾個固定目錄,允許用戶輸入歸檔文件名,多次刺探確認使用的 zip 命令,類似:
其中,歸檔文件名 archive.tar 可控。服務端正則過濾所有命令分隔符、命令替換符、其他元字符,同時,禁止出口流量,顯然無法直接註入命令。
一番嘗試,發現允許橫線(-),這就告訴我可以註入命令選項。我開始分析環境 zip 自身有哪些選項可以為我所用。先查找關鍵字 execute,一無所獲;接著搜索關鍵字 command,找選項 –unzip-command(簡寫 -TT) 和 –test(簡寫 -T),允許用戶指定第三方程序來校驗歸檔文件的完整性:
換言之,選項 -T 和 –unzip-command 可以註入新命令 id:
成功拿到賞金。
命令註入攻擊,除了常規的命令分隔符、命令替換符之外,利用環境自身也能實現。
註一,wargame 的原型來自 Kaibro 所寫的 wargame,見 http://final.kaibro.tw:10002/;
註二,escaping_quotation 源碼,以及更多命令註入相關 wargame 見 https://github.com/yangyangwithgnu/CMDi_lab
命令註入新玩法:巧借環境攻擊目標