現在很多的php軟件廠商為了一些目的都開始將自己的產品用Zend加密起來,當客戶使用的時候根本不知道程序裡面的細節,的確給那些搞安全的人很大的難度,所以一些廠商就開始認為只要是Zend過的產品都是安全的。但是其實不然,即使在不知道原代碼並且不考慮Zend破解的情況下,我們一樣可以利用 Php本身的一些特性來到達獲取程序的一些商業邏輯以及信息以到達探測程序的漏洞等等。. R% a) B0 D3 o
測試程序的安全性方法一般有三種,第一是完全的不知道源代碼,不知道程序內部處理的細節,完全輸入的參數來猜測程序內部的細節以及可能出現的問題,另外一種就是能得到程序的代碼知道代碼的處理流程,通過對代碼的分析來得到程序的問題。還有另外一種方式就是處於中間的灰盒測試,可以知道程序的一部分細節,這裡我們要說的可能就屬於這種方式了,因為我們知道Zend所謂的加密其實是將PHP代碼轉成了一個中間態的代碼,在支持Zend加密的主機上最終都是跟正常的PHP文件一樣被翻譯成底層代碼執行的,不過是源代碼不可見罷了。但是要注意一點就是,這個加密代碼是在我們提供的環境之中運行的,他用的語言環境是我們機器上的PHP,他所選擇的數據庫等存儲系統是我們提供的文件系統或者Mysql等數據庫,這就比單純的黑盒測試多了很多可以利用的地方,因為我們完全可以控制他的運行環境,甚至大部分的處理結果對我們也是可見的,我們就有空子可鑽了。
! H4 f$ j, n. Y4 `; N 然後說說PHP代碼裡的常見漏洞,一般的漏洞包括有Sql注射,文件包含,代碼執行,Xss注射,邏輯性錯誤......等等。在知道代碼的情況下我們可以比較容易檢測出大部分的漏洞,但是由於代碼加密變的不可見,只採用一般的黑盒測試方法只能檢測一些常見的漏洞,但是對於一些變量沒初始化和一些邏輯錯誤無能為力。下面就說說在應用層如何探測程序的一些安全問題。: l3 S; d& A4 |; d1 Q/ @
5 b8 H `5 r3 Q w5 `0 f7 J
首先就是如何「調試」加密的PHP腳本的問題,這裡的調試實際上是指如何讓加密的PHP腳本在我們可以控制的環境下運行,我這裡用的方法就是創建一個 php文件來include包含執行需要調試的加密過的PHP文件,在include之前我們可以設置調試代碼,在include執行完畢後我們還可以利用後面提到的方法查看腳本的一些信息。譬如我們要調試zend.php這個被加密的文件,就可以利用下面的方式: t- C k, ?6 ]0 C, [5 J( X
- \% c$ B1 i) O3 r
eg <?php
, s G& v# L6 A9 P- I9 v0 L //一些運行前的代碼
& v7 L4 `- @+ n- V echo "Just a example";
! L& g0 ^9 f, k# {& |. S include 』zend.php』;# H" S) U: K7 f# _3 L. ]) P$ ?
echo "OK";
/ P/ A: s! L0 y6 A; {+ _% ~. p" c+ C* V //一些運行後的代碼; f, d/ q* |/ r, b$ S; N, I
?>& T- c" ?7 B; z: \! H7 C6 }; c. M
& R9 O# b, ^* v* m
這樣zend.php裡的東西就受我們的影響了。( Z3 Y& V" H8 ~! Z" r1 B; p
# K5 i/ D# r" n# C# e3 s 5 D* W/ T5 ^7 E8 J. Q4 F
然後要做的就是對代碼的刺探了,很幸運的是PHP提供了很多的函數給我們使用,譬如比較有用的有:( W7 @+ L# i, S! [
1 F1 I1 n! E. v
get_defined_vars 返回由所有已定義變量所組成的數組 (PHP 4 >= 4.0.4, PHP 5) 5 V3 k$ F4 i9 H3 P" f
get_declared_classes 返回由已定義類的名字所組成的數組 (PHP 4, PHP 5)
; t z. }1 h5 y X( y/ Q- R0 h& l get_defined_constants 返回由已定義常量的名字所組成的數組
3 J {9 c6 D; u; b% A get_defined_functions 返回由已定義的函數
" F% @( W; S6 ] get_included_files 返回已經包含的文件名
+ o5 e* `1 p/ {, i! d" F( p ....... @ C: ^ Q' p6 ?! p. W: L
' o7 J/ O: c- d' z- U, \/ e; E
大家可以去php手冊裡查下,利用這些函數我們就能做很多事情了,譬如我們知道某個加密的文件zend.php裡面有我們很想要的東西,譬如數據庫帳戶密碼,譬如某個加密的Key,譬如......那麼我們就可以將這個文件利用上面的方法include出來,然後用get_defined_vars函數看信息,例子如下: H) L9 d/ O4 v% f- k
' g8 j/ R7 ~" P2 l# P0 I0 E# A eg <?php. s- ]( ~8 _+ F
include 』zend.php』;( y x2 @7 Z: @% h" M
print_r(get_defined_vars);2 y+ `0 S# q% Y# v
?>
$ W3 x3 o* a' p' x6 L* a* \# G" @
& C7 c" p& u% W 怎麼樣,出來了吧?同樣,一些程序員通常把一些函數放到一個php文件裡,我們就可以通過同樣的方法得到這個php文件裡定義了的函數了。
, a# I& w# g$ }* `# ~
8 H# q2 s3 S( V$ s% r. t 在一些程序破解裡,經常有一個說法叫做Hook,其實我們在進行我們的測試的時候也可以進行簡單的Hook。一個站點的SQL查詢裡經常蘊涵了很多的信息,如果知道了進行某個操作要用的SQL查詢對我們的測試是很有用處的。我們有好幾個方法,譬如更改php的mysql_query函數,在Mysql方面做手腳,這裡我們還是簡單點看看應用層能做什麼吧!一般的PHP代碼都喜歡將Mysql操作封裝起來放到一個文件裡,我們就可以將這個文件替換掉,自己實現他需要實現的函數,在其中將他運行的SQL語句斷下來保存(Thx Saiy),然後我們就可以分析這些SQL查詢了,同樣的道理,我們完全可以做出假的Function來到達猜測某些函數的功能的目的,在假的函數里用如下func_get_args函數就可以截獲傳入的參數。當然,這對於函數內部的細節還是不清楚的,但是很多時候已經足夠了,起碼把黑盒由文件級別降低到函數級別了,呵呵。6 n) G: u# _2 e- T& E+ A: k
而對於程序本身我們能做什麼呢?對於一個程序的安全性我們通常關注的有他處理參數的方式,他如何對待危險函數的,他的變量有不有正確的初始化......等等,通過上面的思想我們就大概能做這些事了:)
! o: @5 u. s- p4 B: i , I* f. g4 s3 @0 w9 }* W( @
1 利用PHP自身的設置猜測程序處理參數的方式 - \* T. E! w z
* h3 p3 Y: u; k* F* R% n
一個PHP程序是要處理參數的,在代碼中程序如何處理我們提交的變量是很重要的。譬如一些程序為了兼容所有的PHP環境,都會在開始釋放變量以實現在 register globals = off的時候程序一樣能運行,並且為了在GPC = off 的時候程序不出問題,一般都會做 addslashes處理,但是也並不是所有的程序都能正確處理好,所以我們通過更改PHP.ini的設置就可以大致推測出程序是如何處理輸入的參數的。譬如,我們將register globals 設置為 off,我們然後include某個文件(譬如index.php),然後在代碼的最後 print_r(GLOBALS)來查看當前符號表裡是不是有我們提交的參數,如果有的話就證明程序是將變量釋放的,然後我們提交一些危險的變量如 php?_POST=aaaaa然後看看的內容就可以知道是不是有變量覆蓋的問題了,# o. `+ p' X8 i; b/ T/ `
當然,之前需要設置register global = off
) ~% b# l2 Q' X9 C7 D. [5 H5 z
% L+ f7 r$ d' `) d q
2 利用PHP自身的出錯機制
: e, g) q8 J) {: \% p! ^" N& ]9 E2 y2 p
! A6 G5 W7 t# k; s6 P 在PHP運行過程中,可以根據自定義的錯誤級別捕捉程序中的一些信息,默認情況下對未初始化這些信息並不做判斷,但是沒有初始化的變量確能給我們有用的信息。一些常見的文件包含漏洞就是都是由變量未初始化造成的。當我們把運行級別錯誤設置為NOTICE時就可以看到這些信息了,但是也不排除有程序人為地在運行過程中修改運行級別,後面我會說說對付這種的方法。找到未初始化變量之後我們就可以試著提交它,來看程序的反映,結合功能以及變量名猜測程序的寫法,通常可以挖掘一些漏洞。
# s" i" \$ u; }$ O/ L" H. c2 O. m3 J
# w, m ?" c" ^8 Q 3 結合php擴展( c/ Q: ?9 k3 ~, Y
! f& F/ v: F2 j: ?- _
6 I# A! c, J8 @* L 這一來就有點向底層了,PHP自己是提供了一些查詢系統狀態的函數如上面提供的get_defined_vars之類,但是這些是遠遠不夠的,但是 PHP本身又不像js那樣覆蓋系統原有的函數就是不允許函數重定義,這就給我們帶來了麻煩,譬如我們想跟蹤程序有不有調用preg_replace函數,並且想知道裡面的參數形式,我們是沒有辦法的,但是利用PHP擴展就變得可能了,我們可以類似於一種Hook的方法來實現這些。譬如想調查 preg_replace的話,我們先將preg_replace這個函數從函數表裡Dump成preg_replace_bak一份,然後 create_function建立我們自己的函數,我們自己的函數目的很簡單,先保存參數然後調用原來的preg_replace_bak函數並返回結果,這樣就可以監視腳本裡這個函數的調用情況了。我這裡有個簡單的從函數表裡刪除函數的代碼:% G" A4 X9 Z ~( `0 b [. U
( w* S, n3 [- |. W* M1 k+ p6 W+ z6 p' M7 r/ I
3 e/ |: T: S$ |; [4 g, o& ?刪除函數的一個php擴展代碼
2 r2 j% s0 U) \/ O4 f* Q6 Q% r0 d* i9 m( L
) y- N1 r! h3 R) w# MPHP_FUNCTION(fuck_fun)
- j( U8 ?# P7 h5 E' f
/ P2 b% ~0 N7 X5 _{
3 I+ E! b* Z5 ?6 r# P7 t: c- r9 Z/ g, O& t7 T4 I
char *function;6 K' ^- {( |5 K( F5 C1 e5 ?- W
int function_len;
8 L' l8 W3 T/ u1 K: y4 D- y zval **findfun;1 v1 k$ l& x3 Y0 a+ w& }8 Q
0 m, r" R. A' p' d3 j4 [7 x6 k
6 z! d: ]+ w7 N; S7 }
if (ZEND_NUM_ARGS() != 1 || zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s", &function, &function_len) == FAILURE) {
7 q1 x& n V+ g6 R* E& t WRONG_PARAM_COUNT;) c4 Q p h& O5 T
}1 i1 j. K2 h/ C5 G! h+ O9 Z' i; D
. t: x" S1 K' B7 L
TSRMLS_FETCH();
# w0 e9 O& Y0 k1 ~
0 x9 q3 T n$ j# }+ Y if (zend_hash_find(EG(function_table), function, strlen(function)+1,(void**)&findfun)==SUCCESS) {
* p0 L, G% V" m) V9 n# z php_printf("Find function %s OK\r\n",function);/ `- L( g9 Q' w& y% J( M
}
! O8 O9 m" F" d: d else {
" a8 {) P& C2 U h& R php_printf("Cann』t find function %s\r\n",function);
" E; T# T7 E6 g- c8 `3 E; J" V }: U$ c0 x i9 S
- Y. A( o. ?- e* y; Z if (zend_hash_del(EG(function_table), function, strlen(function)+1)==SUCCESS) {
I @5 \& t) a7 x; | php_printf("%s Unregister OK\r\n",function);
; j$ z: D& ?. n, k }
9 ~$ R: z0 G& H3 u- Z* W else {6 t) Q' @: |9 U# S# l
php_printf("%s Unregister Faild\r\n",function);
& x+ L, _1 x% y' _# @ }
G& p$ M# t6 q2 G' B* ~9 i RETVAL_LONG(42);- _7 j, x J0 Z: B
return;
8 w; @" P3 J$ D2 j' T# q" W}& a" C' {( @6 m6 p! s9 O
; R3 A* d7 R' _) N4 @6 s
7 F0 w. U) E5 J. c! p
4 再下去呢?再下去就是修改PHP內核了,就有點超出本文的範圍了。文章要說明的是加密不是一切,通過修改程序運行環境以及一些小技巧然後再結合黑盒測試的方法,找出一些加密過的程序的安全漏洞還是可能的,本人就檢測過國內一些加密程序,比較大的漏洞還是有的:) |
|