本人學C語言不久,對指針內存管理等都還沒入門,php擴展的編寫更是胡亂在拼湊,如下是我「亂搞」的一點記錄,但願你們指點和輕噴。 php
一天翻php.ini的時候看到了一堆「同族」的函數 html
; This directive allows you to disable certain functions for security reasons. ; It receives a comma-delimited list of function names. This directive is ; *NOT* affected by whether Safe Mode is turned On or Off. ; http://php.net/disable-functions disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited, pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig, pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask, pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority
當時就想要是支持通配符那麼直接寫成 pcntl_* 這樣就簡便多了。想法是有了,可是不知道怎麼實現好。偶然的機會看到了《淺談從PHP內核層面防範PHP WebShell》這文章,當中提到 zend_disable_function 這個函數,因而感受先前的通配符想法能夠實現了。 前端
說一下簡單的思路吧:在php.ini讀取配置,遍歷函數表,正則匹配函數而後刪除掉,註冊一個同名函數以便給前端提示。 nginx
先用C模擬一下實現吧,代碼以下 web
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pcre.h> #define OVERCCOUNT 30 #define MAX_REGEX_COUNT 50 //最大支持規則數量 char *replace_start(char *src) { //替換通配符*號 static char buffer[4096]; char *p, *str; char *orig = "*"; char *rep = "(\\w+)"; str = (char *)malloc(4096); p = strstr(src, orig); if (p == src) { sprintf(str, "%s%s", src, "$"); } else { sprintf(str, "%s%s%s", "^", src, "$"); } if (!(p = strstr(str, orig))) { return str; } strncpy(buffer, str, p-str); // Copy characters from 'str' start to 'orig' st$ buffer[p-str] = '\0'; sprintf(buffer+(p-str), "%s%s", rep, p+strlen(orig)); free(str); return buffer; } int matchpattern(char *src, char *pattern) { pcre *re; const char *error; int erroffset; int ovector[OVERCCOUNT]; int rc; re = pcre_compile(pattern, PCRE_CASELESS|PCRE_DOTALL, &error, &erroffset, NULL); if (re == NULL) return 0; rc = pcre_exec(re, NULL, src, strlen(src), 0, 0, ovector, OVERCCOUNT); free(re); return rc; } int main(int argc, char **argv) { char *function_table[] = \ {"array_diff", "array_pop", "array_shift", "var_dump", "time", \ "date", "str_replace", "strstr", "test", "abc_str"}; char *ini = "array_*, *str test"; char *s, *p; char *delim = ", ";//這裏支持,號和空格來分割規則 char *regex_list[MAX_REGEX_COUNT] = {0}; int i = 0; s = strndup(ini, strlen(ini)); p = strtok(s, delim); if (p) { do { p = replace_start(p); regex_list[i] = strndup(p, strlen(p)); i++; } while ((p = strtok(NULL, delim))); } int match = -1, k; char *func, *regex; for (i = 0; i < 10; i++) { func = function_table[i]; for (k = 0; k < MAX_REGEX_COUNT; k++) { regex = regex_list[k]; if (!regex) break; //printf("regex:%s\n", regex); match = matchpattern(func, regex); if (match >= 0) { printf("function:%s() are disabled!!\n", func); } } } //free memory for(i = 0; i < MAX_REGEX_COUNT; i++) { regex = regex_list[i]; if (regex) { free(regex); regex_list[i] = NULL; } } free(s); s = NULL; return 0; }由於要使用正則,我在這裏選擇了pcre庫,因而咱們編譯的時候要帶上 -lpcre。運行看看咱們的效果。
嗯,好像還不錯的樣子。接下來就是關鍵了,怎麼改編成php擴展。 shell
至於怎麼快速建立一個php 擴展的就不介紹了,能夠參考《快速開發一個PHP擴展》,我在這裏新建了一個叫"solutest"的擴展。接着咱們把上面的函數(main函數對應的改一下名字,我這裏改成 static void remove_function())貼到solutest.c(文件名對應你建立時候輸入的名字)裏面,對應的內存操做函數能夠換成由php內核提供的e*系列函數,malloc->emalloc, free->efree ...還有一點是用e*系列申請的內存才用efree來釋放,要否則不會有錯,囧在這裏吃過虧。(詳細參考《PHP擴展開發與內核應用》- 內存管理)。而後在 PHP_MINIT_FUNCTION 裏面調用咱們的 remove_function,爲何選擇 PHP_MINIT_FUNCTION ?或者你能夠嘗試在 PHP_RINIT_FUNCTION 調用 (參考《PHP擴展開發與內核應用》- PHP啓動與終結)。編譯看看效果,別忘了須要pcre庫的支持,因此要加上 pcre.h 後,而後編輯 Makefile 在EXTRA_LIBS 加上 -lpcre。 apache
OK,make && sudo make install,接着編輯php.ini加上咱們的擴展(我測試環境是nginx + php-fpm,對應php.ini在 /etc/php5/fpm/php.ini,若是不肯定你加載的配置文件路徑能夠查看phpinfo的Loaded Configuration File)
服務器
[solutest] extension=solutest.so
sudo /etc/init.d/php5-fpm restart 函數
咱們重啓fpm看看效果(若是apache環境直接重啓apache服務器便可)
嘛嘛~跑起來了。 php-fpm
怎麼獲取系統的函數呢?咱們能夠參考一下zend_disable_function的實現
//file:"Zend/zend_API.c" line:2524 ZEND_API int zend_disable_function(char *function_name, uint function_name_length TSRMLS_DC) /* {{{ */ { if (zend_hash_del(CG(function_table), function_name, function_name_length+1)==FAILURE) { return FAILURE; } disabled_function[0].fname = function_name; return zend_register_functions(NULL, disabled_function, CG(function_table), MODULE_PERSISTENT TSRMLS_CC); } /* }}} */嗯,從函數咱們能夠知道CG(function_table)保持了咱們要的函數表,並且它是一個 HashTable 結構,咱們能夠經過 zend_hash_del 刪除函數表內某個函數。跟進去 zend_hash_del函數看看,
//file:"Zend/zend_hash.h" line:154 #define zend_hash_del(ht, arKey, nKeyLength) \ zend_hash_del_key_or_index(ht, arKey, nKeyLength, 0, HASH_DEL_KEY)是一個宏,繼續展開深刻在 file:"Zend/zend_hash.c" line:486,函數有點就不貼了,能夠看出是對HashTable的遍歷和一些鏈表刪除的操做,還有獲得一個重要信息是函數名保存在了Bucket的arKey。如下是HashTbale的定義
//file:"Zend/zend_hash.h" line:52 struct _hashtable; typedef struct bucket { ulong h; /* Used for numeric indexing */ uint nKeyLength; void *pData; void *pDataPtr; struct bucket *pListNext; struct bucket *pListLast; struct bucket *pNext; struct bucket *pLast; const char *arKey; } Bucket; typedef struct _hashtable { uint nTableSize; uint nTableMask; uint nNumOfElements; ulong nNextFreeElement; Bucket *pInternalPointer; /* Used for element traversal */ Bucket *pListHead; Bucket *pListTail; Bucket **arBuckets; dtor_func_t pDestructor; zend_bool persistent; unsigned char nApplyCount; zend_bool bApplyProtection; #if ZEND_DEBUG int inconsistent; #endif } HashTable;
詳細的解釋能夠參考《深刻理解PHP內核》- PHP哈希表實現
好吧,依葫蘆畫瓢,嘗試遍歷一下function_table。把 remove_function 函數對應修改成
static void remove_function() { #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif char *ini = "array_*, *str test"; char *s, *p; char *delim = ", ";//這裏支持,號和空格來分割規則 char *regex_list[MAX_REGEX_COUNT] = {0}; int i = 0; s = estrndup(ini, strlen(ini)); p = strtok(s, delim); if (p) { do { //p = replace_str(p, "*", "(\\w+)"); p = replace_start(p); regex_list[i] = estrndup(p, strlen(p)); i++; } while ((p = strtok(NULL, delim))); } int match = -1, k; char *regex; HashTable ht_func, *pht_func; Bucket *pBk; //拷貝一份CG(function_table)進行操做 zend_hash_init(&ht_func, zend_hash_num_elements(CG(function_table)), NULL, NULL, 0); zend_hash_copy(&ht_func, CG(function_table), NULL, NULL, sizeof(zval*)); pht_func = &ht_func; for (pBk = pht_func->pListHead; pBk != NULL; pBk = pBk->pListNext) { printf("%s()\n", pBk->arKey); } //free memory zend_hash_destroy(&ht_func); //銷燬HashTable pht_func = NULL; for(i = 0; i < MAX_REGEX_COUNT; i++) { regex = regex_list[i]; if (regex) { efree(regex); regex_list[i] = NULL; } } efree(s); s = NULL; }保存之後又是一輪的 make && sudo make install。sudo /etc/init.d/php5-fpm restart,刷啦啦的一大片,嚇壞了吧,保存下來看看有多少。
應該差很少了吧,後面有...省略號是否是buffer什麼的滿了因此還沒輸出完呢???
OK,下面是重點了,刪除對應的函數。其實咱們抄一下zend_disable_function就OK了,有同窗會問爲何不直接調用zend_disable_function,別急,下面我會說道。再次修改咱們的remove_function函數,此次修改便利的循環體和 char *ini 就好
char *ini = "array_p*,"; //使用array族函數測試
for (pBk = pht_func->pListHead; pBk != NULL; pBk = pBk->pListNext) { for (k = 0; k < MAX_REGEX_COUNT; k++) { regex = regex_list[k]; if (!regex) break; //regex = "^array_p(\\w+)"; match = matchpattern(pBk->arKey, regex); if (match >= 0) { printf("function:%s are disabled!!\n", pBk->arKey); //zend_disable_function(func, sizeof(func)); if (zend_hash_del(CG(function_table), pBk->arKey, strlen(pBk->arKey)+1) == FAILURE) { printf("disable %s error\n", pBk->arKey); }; disabled_function[0].fname = pBk->arKey; zend_register_functions(NULL, disabled_function, CG(function_table), MODULE_PERSISTENT TSRMLS_CC); } } }由於把系統的函數刪除了,不知請者調用會產生一個php函數不存在的錯誤,腳本也會中止運行,因而須要註冊一個同名的函數回去,而這個函數什麼也不作,輸出提示就好。那麼咱們須要在 remove_function 函數以前定義函數入口和提示函數
PHP_FUNCTION(print_disabed_info) { //I don't know why I can't use get_active_function_name in here // Maybe "EG" zend_error(E_WARNING, "*** function has been disabled! (°Д°≡°д°)エッ!?"); //get_active_function_name(TSRMLS_C) } static zend_function_entry disabled_function[] = { PHP_FALIAS(display_disabled_function, print_disabed_info, NULL) PHP_FE_END };
估計有同窗吐槽爲何用***代替了顯示的函數名,這就是爲何我不調用zend_disable_function的緣由。當時卡在這裏好久,一直段錯誤,後來無心中註釋了 get_active_function_name(TSRMLS_C) 就跑起來了╯-__-)╯ ╩╩,求告知。。和上面一個編譯重啓服務器什麼的,而後看效果,由於咱們配置寫的是array_p*,因此一下函數被禁用了。(測試完之後記得關閉輸出)
而後隨便寫個腳本,調用一下array_pop函數什麼的,而後執行之。
It's work!! :)
呼,不知不覺寫了這麼長了,也懶得分兩篇了。接下來把讀取php.ini配置代碼寫上就完成了。其實這部門工做在擴展自動生成的代碼已經有了,只要稍微加工一下就好。
/* Declare any global variables you may need between the BEGIN and END macros here: */ ZEND_BEGIN_MODULE_GLOBALS(solutest) char *disable_functions; ZEND_END_MODULE_GLOBALS(solutest)php_solutest.h 大概47行左右的樣子,去掉註釋加入咱們的disable_functions變量
/* If you declare any globals in php_solutest.h uncomment this:*/ ZEND_DECLARE_MODULE_GLOBALS(solutest)solutest.c 30行左右,去掉註釋
/* {{{ PHP_INI */ /* Remove comments and fill if you need to have entries in php.ini*/ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("solutest.disable_functions", "", PHP_INI_ALL, OnUpdateString, disable_functions, zend_solutest_globals, solutest_globals) PHP_INI_END() /* }}} */solutest.c 71行左右,去掉註釋,修改成咱們的變量
/* If you have INI entries, uncomment these lines */ REGISTER_INI_ENTRIES();PHP_MINIT_FUNCTION 函數裏面,去掉註釋
/* Remove comments if you have entries in php.ini */ DISPLAY_INI_ENTRIES();PHP_MINFO_FUNCTION 函數裏面,去掉註釋
而後編輯你的php.ini文件,加入配置
[solutest] extension=solutest.so solutest.disable_functions = array_p*,編譯重啓服務器,而後瀏覽phpinfo會發現咱們的配置已經被讀取了。
s = estrndup(SOLUTEST_G(disable_functions), strlen(SOLUTEST_G(disable_functions)));OK,保存編譯重啓服務器測試。
:)預期的效果達到了。打完收工。
PS:
此擴展是本人YY的產物,沒有通過嚴格測試,請勿在生產機上使用。
代碼下載: http://pan.baidu.com/share/link?shareid=207778&uk=436715329
參考資料:
《淺談從PHP內核層面防範PHP WebShell》
《PHP擴展開發及內核應用》
《鳥哥博客》
《快速開發一個PHP擴展》
《深刻理解PHP內核》