快睡的時候,打開B站發現有位用戶留言,大意就是讓我幫忙看一道題,正好當時有空,於是就打開了他發的鏈接,代碼如下
很明顯是一道PHP代碼審計的題目,而且只需要繞過第三行的if即可進行任意命令執行。
解決思路
看了代碼之後覺得是道普通的題目,對於/a-zA-Z/這個正則表達式,我們可以利用PHP動態函數的特性,構造出字符串即可。
對於想要的字符串,我們可以通過以下三種方式來構造:
1. 異或
對於PHP中的字符串,兩個字符串異或的結果是將兩個字符串逐位異或,返回一個新字符串。那麼我們便可以使用此特性進行構造。
例如我們需要構造phpinfo,則可以用腳本得到('0302181', '@[@[_^^')這兩個字符串,腳本如下:
其中valid是可用的字符,answer是我們需要構造的字符串。那麼我們得到了這個字符串,又該如何去執行呢。我們可以通過一個變量存儲兩字符串異或後的值,再讓這個變量進行動態函數執行即可,而變量的話因為PHP的變量命名規則和C語言相同,可以使用下劃線進行命名,如下:
2. 取反構造
和第一種類似,都是基於PHP字符串位運算的特點(會逐位進行位運算)。而取反構造某些情況比異或構造要方便,因為異或的情況某些字符是無法直接通過其他字符進行異或構造的,而取反卻可以利用漢字或者其他特殊字符進行構造(不會有題目會限制某個漢字吧)。比如字母s我們可以通過~('和'{2})得到。而這個時候如果要用異或去構造的話,你得找到兩個異或值為s的特殊字符。
取反構造的腳本和異或構造類似,在此不再給出。
3. 自增構造
<code>`++'a' == 'b'`/<code>
這個特點是利用了PHP是弱類型語言的特性,在對變量進行操作的時候,PHP會隱式的轉換其變量類型,很多代碼審計的題目也是利用了這一特性。
遇到障礙
有了上面的思路後,而且也成功執行了phpinfo(),下一步是不是就可以直接構造命令執行函數去進行讀取文件,當時我也是這麼想的,於是我構造了passthru(), system(), shell_exec(), exec()等函數,都受到了阻礙,沒有回顯,通過進一步的調試之後發現是禁用了這些函數(通過在函數後面加一個打印函數觀察是否執行)。
在這裡我停留了很久,試過打印$GLOBALS等都沒有任何有用的信息。最後通過使用glob()函數進行目錄掃描,發現了flag.php文件,以及file_get_contents()進行獲取,最終的payload如下
<code>?mess=$_="`0123"^"?`~``";${$_}[_](${$_}[__]);&_=assert&__=print_r(base64_encode(file_put_contents("flag.php")))/<code>
最後再將其界面就可以得到最終的flag了。在最終的payload中我沒有去一個一個的構造字符串異或,因為那太長了,而是構造了一個$_POST[_]($_POST[__])的動態函數,這樣就可以在其他參數位置直接寫函數了。
深入分析
題目到這裡就結束了,其實並沒有多難,首先是通過特性去構造動態函數,然後發現了命令執行被禁用之後,能夠知道使用其他函數去進行獲取信息就行了。但是做完這道題後,不僅引發了我的思考,PHP中的命令執行就只有這些方式了嗎,肯定不是。於是,我總結了幾種新的命令執行技巧供大家參考。
1. 當打印函數被禁用時
如果沒有了打印函數,意味著你無法看到回顯,這時候即使命令執行成功了你也無法得到信息,這個時候你就得利用其他方式去獲取回顯了。
首先是網上很多blog使用的方式,phpinfo如果發現開啟了curl,或者其他文件傳輸擴展的的話,可以自建一個靶機,將所有訪問信息存入數據庫或是文件,然後將回顯信息發送到你的靶機地址,這樣你去看日誌就可以了,這種方式不是很方便而且有一定的侷限性。這裡我要介紹的是一種新的方式。
如果你使用過Django或者jsp開發web的話,你肯定知道輸出一個變量可以使用{{xxx}}或是的方式,那麼在PHP中是否也有這種方式呢,答案是肯定的,PHP中的=xxx?>就可以將一個變量輸出,那麼如果使用它呢,你只需要構造如下的payload
<code>valid = "1234567890!@$%^*(){}[];\\'\",.<>/?-=_`~ "answer = "phpinfo"tmp1,tmp2 = '',''for c in answer: for i in valid: for j in valid: if (ord(i)^ord(j) == ord(c)): tmp1 += i tmp2 += j break else: continue breakprint(tmp1,tmp2)/<code>
這樣就可以將變量$_裡面的內容打印到屏幕上,而且關鍵的是這個輸出方式默認是開啟的,管理員很容易就忽視這個選項。所以在做題時不妨一試。
2. 其他的命令執行方式
當system,passthru等不能用時,網上會告訴你可以使用popen,proc_open這些管道命令去進行執行命令,當然這沒有問題,而這裡我向你介紹一種新方式,使用反引號,在PHP中,被兩個反引號括起來的內容將會作為shell命令執行,並將輸出信息返回,所以你可以構造下面的payload進行命令執行
<code>$_=`ls`;/<code>
3. 不能使用數字字母的命令執行
當不能使用字母數字時,當然你可以使用上述的方式構造字符串進行執行,但是這裡提供一些新東西,對於linux中的shell是支持正則表達式的,當你忘記某些字符時可以通過? % *來代替,經過測試,這裡的匹配方式也是按照順序進行匹配,所以你可以查看你的linux中/bin目錄下面的順序,來獲取一些可以使用的命令,比如
<code>/???/??? => /bin/cat/<code>
那麼這樣的話,如果要獲取/var/www/html/index.php(你得感謝apache默認目錄如此之深),則可以直接使用
<code>/???/??? /???/???/????/?????.???/<code>
來獲取
總結
這篇文章只是針對這道題而延展出的一些東西,在真正做題時,情況可能更加複雜,例如限制長度,限制參數等等情況,而我們的做法也不可能千篇一律,可能某些時候我們甚至會用到一些CVE漏洞。而本篇文章只是告訴讀者一些可能以前沒有見過的新東西。
那麼本篇文章就到此結束了。
如果想更多系統的學習CTF,可點擊“http://www.hetianlab.com/pages/CTFLaboratory.jsp”,進入CTF實驗室學習,裡面涵蓋了6個題目類型系統的學習路徑和實操環境。
聲明:筆者初衷用於分享與普及網絡知識,若讀者因此作出任何危害網絡安全行為後果自負,與合天智匯及原作者無關!
閱讀更多 合天網安實驗室 的文章