現在很多的php軟體廠商為了一些目的都開始將自己的產品用Zend加密起來,當客戶使用的時候根本不知道程式裡面的細節,的確給那些搞安全的人很大的難度,所以一些廠商就開始認為只要是Zend過的產品都是安全的。但是其實不然,即使在不知道原代碼並且不考慮Zend破解的情況下,我們一樣可以利用 Php本身的一些特性來到達擷取程式的一些商業邏輯以及資訊以到達探測程式的漏洞等等。
測試程式的安全性方法一般有三種,第一是完全的不知道原始碼,不知道程式內部處理的細節,完全靠輸入的參數來猜測程式內部的細節以及可能出現的問題,另外一種就是能得到程式的代碼知道代碼的處理流程,通過對代碼的分析來得到程式的問題。還有另外一種方式就是處於中間的灰盒測試,可以知道程式的一部分細節,這裡我們要說的可能就屬於這種方式了,因為我們知道Zend所謂的加密其實是將PHP代碼轉成了一個中間態的代碼,在支援Zend加密的主機上最終都是跟正常的PHP檔案一樣被翻譯成底層代碼執行的,不過是原始碼不可見罷了。但是要注意一點就是,這個加密代碼是在我們提供的環境之中啟動並執行,他用的語言環境是我們機器上的PHP,他所選擇的資料庫等儲存系統是我們提供的檔案系統或者Mysql等資料庫,這就比單純的黑箱測試多了很多可以利用的地方,因為我們完全可以控制他的運行環境,甚至大部分的處理結果對我們也是可見的,我們就有空子可鑽了。
然後說說PHP代碼裡的常見漏洞,一般的漏洞包括有Sql注射,檔案包含,代碼執行,Xss注射,邏輯性錯誤......等等。在知道代碼的情況下我們可以比較容易檢測出大部分的漏洞,但是由於代碼加密變的不可見,只採用一般的黑箱測試方法只能檢測一些常見的漏洞,但是對於一些變數沒初始化和一些邏輯錯誤無能為力。下面就說說在應用程式層如何探測程式的一些安全問題。
首先就是如何“調試”加密的PHP指令碼的問題,這裡的調試實際上是指如何讓加密的PHP指令碼在我們可以控制的環境下運行,我這裡用的方法就是建立一個 php檔案來include包含執行需要調試的加密過的PHP檔案,在include之前我們可以設定調試代碼,在include執行完畢後我們還可以利用後面提到的方法查看指令碼的一些資訊。譬如我們要調試zend.php這個被加密的檔案,就可以利用下面的方式:
eg <?php
//一些運行前的代碼
echo "Just a example";
include 'zend.php';
echo "OK";
//一些運行後的代碼
?>
這樣zend.php裡的東西就受我們的影響了。
然後要做的就是對代碼的刺探了,很幸運的是PHP提供了很多的函數給我們使用,譬如比較有用的有:
get_defined_vars 返回由所有已定義變數所組成的數組 (PHP 4 >= 4.0.4, PHP 5)
get_declared_classes 返回由已定義類的名字所組成的數組 (PHP 4, PHP 5)
get_defined_constants 返回由已定義常量的名字所組成的數組
get_defined_functions 返回由已定義的函數
get_included_files 返回已經包含的檔案名稱
......
大家可以去php手冊裡查下,利用這些函數我們就能做很多事情了,譬如我們知道某個加密的檔案zend.php裡面有我們很想要的東西,譬如資料庫帳戶密碼,譬如某個加密的Key,譬如......那麼我們就可以將這個檔案利用上面的方法include出來,然後用 get_defined_vars函數看資訊,例子如下:
eg <?php
include 'zend.php';
print_r(get_defined_vars);
?>
怎麼樣,出來了吧?同樣,一些程式員通常把一些函數放到一個php檔案裡,我們就可以通過同樣的方法得到這個php檔案裡定義了的函數了。
在一些程式破解裡,經常有一個說法叫做Hook,其實我們在進行我們的測試的時候也可以進行簡單的Hook。一個網站的SQL查詢裡經常蘊涵了很多的資訊,如果知道了進行某個操作要用的SQL查詢對我們的測試是很有用處的。我們有好幾個方法,譬如更改php的mysql_query函數,在 Mysql方面做手腳,這裡我們還是簡單點看看應用程式層能做什麼吧!一般的PHP代碼都喜歡將Mysql操作封裝起來放到一個檔案裡,我們就可以將這個檔案替換掉,自己實現他需要實現的函數,在其中將他啟動並執行SQL語句斷下來儲存(Thx Saiy),然後我們就可以分析這些SQL查詢了,同樣的道理,我們完全可以做出假的Function來到達猜測某些函數的功能的目的,在假的函數裡用如下func_get_args函數就可以截獲傳入的參數。當然,這對於函數內部的細節還是不清楚的,但是很多時候已經足夠了,起碼把黑盒由檔案層級降低到函數層級了,呵呵。
而對於程式本身我們能做什麼呢?對於一個程式的安全性我們通常關注的有他處理參數的方式,他如何對待危險函數的,他的變數有不有正確的初始化......等等,通過上面的思想我們就大概能做這些事了:)
1 利用PHP自身的設定猜測程式處理參數的方式
一個PHP程式是要處理參數的,在代碼中程式如何處理我們提交的變數是很重要的。譬如一些程式為了相容所有的PHP環境,都會在開始釋放變數以實現在 register globals = off的時候程式一樣能運行,並且為了在GPC = off 的時候程式不出問題,一般都會做addslashes處理,但是也並不是所有的程式都能正確處理好,所以我們通過更改PHP.ini的設定就可以大致推測出程式是如何處理輸入的參數的。譬如,我們將register globals 設定為 off,我們然後include某個檔案(譬如index.php),然後在代碼的最後print_r($GLOBALS)來查看當前符號表裡是不是有我們提交的參數,如果有的話就證明程式是將變數釋放的,然後我們提交一些危險的變數如php?_POST=aaaaa然後看看$_POST的內容就可以知道是不是有變數覆蓋的問題了,代碼流程如下:
eg <?php
include 'index.php';
print_r($GLOBALS);
?>
當然,之前需要設定register global = off
2 利用PHP自身的出錯機制
在PHP運行過程中,可以根據自訂的錯誤層級捕捉程式中的一些資訊,預設情況下對未初始化這些資訊並不做判斷,但是沒有初始化的變數確能給我們有用的資訊。一些常見的檔案包含漏洞就是都是由變數未初始化造成的。當我們把運行層級錯誤設定為NOTICE時就可以看到這些資訊了,但是也不排除有程式人為地在運行過程中修改運行層級,後面我會說說對付這種的方法。找到未初始設定變數之後我們就可以試著提交它,來看程式的反映,結合功能以及變數名猜測程式的寫法,通常可以挖掘一些漏洞。
3 結合php擴充
這一來就有點向底層靠了,PHP自己是提供了一些查詢系統狀態的函數如上面提供的get_defined_vars之類,但是這些是遠遠不夠的,但是 PHP本身又不像js那樣覆蓋系統原有的函數就是不允許函數重定義,這就給我們帶來了麻煩,譬如我們想跟蹤程式有不有調用preg_replace函數,並且想知道裡面的參數形式,我們是沒有辦法的,但是利用PHP擴充就變得可能了,我們可以類似於一種Hook的方法來實現這些。譬如想調查 preg_replace的話,我們先將preg_replace這個函數從函數表裡Dump成preg_replace_bak一份,然後 create_function建立我們自己的函數,我們自己的函數目的很簡單,先儲存參數然後調用原來的preg_replace_bak函數並返回結果,這樣就可以監視指令碼裡這個函數的調用情況了。我這裡有個簡單的從函數表裡刪除函數的代碼:
刪除函數的一個php擴充代碼
PHP_FUNCTION(fuck_fun)
{
char *function;
int function_len;
zval **findfun;
if (ZEND_NUM_ARGS() != 1 || zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s", &function, &function_len) == FAILURE) {
WRONG_PARAM_COUNT;
}
TSRMLS_FETCH();
if (zend_hash_find(EG(function_table), function, strlen(function)+1,(void**)&findfun)==SUCCESS) {
php_printf("Find function %s OK\r\n",function);
}
else {
php_printf("Cann't find function %s\r\n",function);
}
if (zend_hash_del(EG(function_table), function, strlen(function)+1)==SUCCESS) {
php_printf("%s Unregister OK\r\n",function);
}
else {
php_printf("%s Unregister Faild\r\n",function);
}
RETVAL_LONG(42);
return;
}
4 再下去呢?再下去就是修改PHP核心了,就有點超出本文的範圍了。文章要說明的是加密不是一切,通過修改程式運行環境以及一些小技巧然後再結合黑箱測試的方法,找出一些加密過的程式的安全性漏洞還是可能的,本人就檢測過國內一些加密程式,比較大的漏洞還是有的:)