Pwnhub公開賽出了個簡單的PHP代碼審計題目,考點有兩個:php
若是說僅爲了作出題目拿到flag,這個題目太簡單,後臺也有數十名選手提交了答案和writeup。但深刻研究一下這兩個知識點,仍是頗有意思的。html
首先經過簡單的目錄掃描,找到備份文件index.php.bak。下載後發現文件是通過了混淆加密處理的,大部分同窗是直接網上找了付費解密的網站給解的,也有少數幾我的說明了解密方法,我挑幾種方法說一下。git
這種方法我最佩服了,做者甚至給出瞭解密腳本,文章以下: http://sec2hack.com/web/phpjiami-decode.html 。github
我本身在出題目以前也進行過度析,但後面並無耐心寫一個完整的腳本出來,因此我十分佩服這個做者。web
咱們分析phpjiami後的文件,能夠看到他有以下特色:shell
之因此函數名、變量名能夠變成「亂碼」,是由於PHP的函數名、變量名是支持除了特殊符號之外大部分字符的,好比漢字等。利用這一特色,phpjiami就將全部正常的英文變量給轉換了一下形式,其實沒有什麼特別的奧祕。api
那麼,爲了方便分析,咱們能夠想辦法再將其轉換回英文和數字。好比,做者使用的是 http://zhaoyuanma.com/phpcodefix.html 對混淆過的代碼進行美化;而我是使用 https://github.com/nikic/PHP-Parser 對整個代碼進行告終構化的分析,並將全部變量和函數名進行了美化。數組
方法一的好處是我不須要寫任何代碼,就能夠大體進行美化,但顯然,美化後的代碼是有錯誤的,原文中也提到了這一點;方法二,雖然須要本身寫代碼,但美化後的代碼沒有語法錯誤,看起來更加直觀,而且我還能進一步的進行美化,好比將字符串中的亂碼轉換成\x
的形式。安全
我美化後的代碼以下:網絡
後續的操做和上文也差很少,經過源碼的分析,正如上文中所說,phpjiami加密源碼的整個流程是:
加密流程:源碼 -> 加密處理(壓縮,替換,BASE64,轉義)-> 安全處理(驗證文件 MD5 值,限制 IP、限域名、限時間、防破解、防命令行調試)-> 加密程序成品,再簡單的說:源碼 + 加密外殼 == 加密程序 (該段出處)
因此,其實這種方法並無對源碼進行混淆,只是對「解密源碼的殼」進行了混淆。因此你看到的中文變量、中文函數,實際上是一個殼,去掉這層殼,我能夠拿到完整的PHP源碼。
因此呀,後臺提交的writeup裏,有的同窗想固然地認爲修改eval爲echo就能輸出源碼了……實際上根本沒實際試過,改動文件是會致使不能運行的;還有同窗認爲這裏僅是將源碼混淆爲用戶體驗極差的代碼,致使人眼沒法閱讀,並無理解這裏其實混淆的不是源碼。
0x01中說到的方法當然是很美好的,可是假如加密者隨意改動一點加密的邏輯,可能致使咱們須要從新分析加密方法,寫解密腳本。咱們有沒有更通用的方法?
HOOK EVAL應該是被提到過最多的方法,我也看到了Medici.Yan發佈的一篇文章: http://blog.evalbug.com/2017/09/21/phpdecode_01/
我前文說過,phpjiami實際上是隻是混淆了殼,這個殼的做用是執行真正的源碼。那麼,執行源碼必然是會通過eval之類的「函數」(固然也不盡然),那麼,若是咱們可以有辦法將eval給替換掉,不就能夠得到源碼了麼?
遺憾的是,若是咱們僅僅簡單地將eval替換成echo,將致使整個腳本不能運行——由於phpjiami檢測了文件是否被修改。
那麼,咱們能夠尋求更底層的方法。就是不少人之前提到過的,將PHP底層的函數 zend_compile_string
給攔截下來,並輸出值。Medici.Yan的文章中說的很清楚,也給出了參考文檔和源碼,我就再也不贅述了。
我本身簡單寫了一個擴展,並用php5.6編譯: https://drive.google.com/open?id=0B4uxE69uafD5anVTZ1VwNXN0WEU
下載之,在php.ini中添加extension=hookeval.so
,而後直接訪問加密過的php代碼便可(當時參考tool.lu的站長xiaozi的代碼 http://type.so/c/php-dump-eval.html ,因此分隔符裏有關鍵字):
16年kuuki曾分享過一個在線解密的工具: https://xianzhi.aliyun.com/forum/read/64.html ,但測試了一下phpjiami解密不了。緣由是,phpjiami在解密的時候會進行驗證:
php_sapi_name() == 'cli' ? die():'';
因此若是這個源碼是在命令行下運行,在執行這條語句的時候就die了。因此,即便你編譯好了hookeval.so並開啓了這個擴展,也須要在Web環境下運行。
提升篇:有沒有什麼簡單的辦法在命令行下也能模擬web環境呢?方法我先不說,你們能夠本身思考思考。
那麼有的同窗說:php擴展太難了,我不會寫C語言,怎麼辦?
不會寫C語言也不要緊,你只須要會寫PHP便可。這是我鳳凰師傅提到的一個方法,也是我理想中的一個解,很是簡單,兩行代碼搞定,解密用時比你去網上花錢解密還短:
<?php include "index.php"; var_dump(get_defined_vars());
原理其實也很簡單。phpjiami的殼在解密源碼並執行後,遺留下來一些變量,這些變量裏就包含了解密後的源碼。
雖然咱們不能直接修改index.php,將這些變量打印出來,可是咱們能夠動態包含之,並打印下全部變量,其中一定有咱們須要的源碼(var_dump
輸出的不完整,只是用它舉個例子):
固然,這個方法雖然簡單,但有個很嚴重的問題:假如在執行源碼的過程當中exit()
了,咱們就執行不到打印變量的地方了。
因此,這個方法並不必定適用於全部情景,但對於本題來講,已經足夠了。
那麼,若是咱們遇到0x03解決不了的狀況怎麼辦?
這時候就要祭出動態調試武器了。儘管加密後的文件看起來亂七八糟,但其仍然是一個符合php語法的php文件,那麼咱們就能夠直接利用動態調試工具進行單步調試,拿到源碼。
簡單拿xdebug進行調試,不停單步調試後,就能夠發現咱們須要的源碼已經在上下文變量中的:
右鍵「複製值」,便可拿到源碼。這也算一個比較簡單的方法了。
固然,假若有一天phpjiami修改了混淆流程,源碼再也不儲存於變量中,那麼就須要分析一下代碼執行的流程。所謂萬變不離其中,最終斷在eval的那一步,必定有你須要的源碼。
後面的部分反而比較簡單。拿到index.php的源碼後,發現其包含了FileUpload.class.php,因此再次下載這個文件的源碼進行解密。
分析FileUpload類,發現其取後綴有兩種方式:將文件名用.
分割成數組$arr
,一是用$arr[count($arr)-1]
的方式取數組最後一個元素,二是用end($arr)
的方式取數組最後一個元素。
正常來講,字符串用.
分割成的數組,用這兩種方法取到的末元素應該是相同的。但取文件名的時候,若是咱們已經傳入的是數組,則不會再次進行分割:
$filename = $_POST[...]; if(!is_array($filename)) { $filename = explode('.', $filename); }
也就是說我能控制$filename
這個數組。因此,我只須要找到$arr[count($arr)-1]
和end($arr)
的區別,便可繞事後綴檢查。
顯然,前者是取根據數組下標來取的值,後者取的永遠是數組裏最後一個元素。因此,咱們只須要讓下標等於count($arr)-1
的元素不是數組最後一個元素便可。
好比:[1=>'gif', 0=>'php']
或者['0'=>'abc', '2'=>'gif', '100'=>'php']
。
最後想說一句話:不求甚解是阻礙部分人進步的一大阻力。共勉。