通过php extension使disable_function支持通配符
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
??本人學(xué)C語(yǔ)言不久,對(duì)指針內(nèi)存管理等都還沒(méi)入門(mén),php擴(kuò)展的編寫(xiě)更是胡亂在拼湊,以下是我“亂搞”的一點(diǎn)記錄,希望大家指點(diǎn)和輕噴。
? 一天翻php.ini的時(shí)候看到了一堆“同族”的函數(shù)
; 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當(dāng)時(shí)就想要是支持通配符那么直接寫(xiě)成 pcntl_* 這樣就簡(jiǎn)便多了。想法是有了,但是不知道怎么實(shí)現(xiàn)好。偶然的機(jī)會(huì)看到了《淺談從PHP內(nèi)核層面防范PHP WebShell》這文章,當(dāng)中提到 zend_disable_function 這個(gè)函數(shù),于是感覺(jué)先前的通配符想法可以實(shí)現(xiàn)了。
說(shuō)一下簡(jiǎn)單的思路吧:在php.ini讀取配置,遍歷函數(shù)表,正則匹配函數(shù)然后刪除掉,注冊(cè)一個(gè)同名函數(shù)以便給前端提示。
先用C模擬一下實(shí)現(xiàn)吧,代碼如下
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pcre.h>#define OVERCCOUNT 30 #define MAX_REGEX_COUNT 50 //最大支持規(guī)則數(shù)量char *replace_start(char *src) { //替換通配符*號(hào)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 = ", ";//這里支持,號(hào)和空格來(lái)分割規(guī)則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 memoryfor(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; } 因?yàn)橐褂谜齽t,我在這里選擇了pcre庫(kù),于是我們編譯的時(shí)候要帶上 -lpcre。運(yùn)行看看我們的效果。
嗯,好像還不錯(cuò)的樣子。接下來(lái)就是關(guān)鍵了,怎么改編成php擴(kuò)展。
至于怎么快速創(chuàng)建一個(gè)php 擴(kuò)展的就不介紹了,可以參考《快速開(kāi)發(fā)一個(gè)PHP擴(kuò)展》,我在這里新建了一個(gè)叫"solutest"的擴(kuò)展。接著我們把上面的函數(shù)(main函數(shù)對(duì)應(yīng)的改一下名字,我這里改為 static void remove_function())貼到solutest.c(文件名對(duì)應(yīng)你創(chuàng)建時(shí)候輸入的名字)里面,對(duì)應(yīng)的內(nèi)存操作函數(shù)可以換成由php內(nèi)核提供的e*系列函數(shù),malloc->emalloc, free->efree ...還有一點(diǎn)是用e*系列申請(qǐng)的內(nèi)存才用efree來(lái)釋放,要不然不會(huì)有錯(cuò),囧在這里吃過(guò)虧。(詳細(xì)參考《PHP擴(kuò)展開(kāi)發(fā)與內(nèi)核應(yīng)用》- 內(nèi)存管理)。然后在?PHP_MINIT_FUNCTION 里面調(diào)用我們的 remove_function,為什么選擇?PHP_MINIT_FUNCTION ?或者你可以嘗試在?PHP_RINIT_FUNCTION 調(diào)用 (參考《PHP擴(kuò)展開(kāi)發(fā)與內(nèi)核應(yīng)用》- PHP啟動(dòng)與終結(jié))。編譯看看效果,別忘了需要pcre庫(kù)的支持,所以要加上 pcre.h 后,然后編輯 Makefile 在EXTRA_LIBS 加上 -lpcre。
OK,make && sudo make install,接著編輯php.ini加上我們的擴(kuò)展(我測(cè)試環(huán)境是nginx + php-fpm,對(duì)應(yīng)php.ini在 /etc/php5/fpm/php.ini,如果不確定你加載的配置文件路徑可以查看phpinfo的Loaded Configuration File)
sudo /etc/init.d/php5-fpm restart
我們重啟fpm看看效果(如果apache環(huán)境直接重啟apache服務(wù)器即可)
嘛嘛~跑起來(lái)了。
? 怎么獲取系統(tǒng)的函數(shù)呢?我們可以參考一下zend_disable_function的實(shí)現(xiàn)
//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); } /* }}} */ ? ?嗯,從函數(shù)我們可以知道CG(function_table)保持了我們要的函數(shù)表,而且它是一個(gè) HashTable 結(jié)構(gòu),我們可以通過(guò) zend_hash_del 刪除函數(shù)表內(nèi)某個(gè)函數(shù)。跟進(jìn)去 zend_hash_del函數(shù)看看,//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) 是一個(gè)宏,繼續(xù)展開(kāi)深入在 file:"Zend/zend_hash.c" line:486,函數(shù)有點(diǎn)就不貼了,可以看出是對(duì)HashTable的遍歷和一些鏈表刪除的操作,還有得到一個(gè)重要信息是函數(shù)名保存在了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_DEBUGint inconsistent; #endif } HashTable;
詳細(xì)的解釋可以參考《深入理解PHP內(nèi)核》- PHP哈希表實(shí)現(xiàn)
? 好吧,依葫蘆畫(huà)瓢,嘗試遍歷一下function_table。把 remove_function 函數(shù)對(duì)應(yīng)修改為
static void remove_function() { #ifdef ZEND_SIGNALSTSRMLS_FETCH(); #endifchar *ini = "array_*, *str test";char *s, *p;char *delim = ", ";//這里支持,號(hào)和空格來(lái)分割規(guī)則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)進(jìn)行操作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 memoryzend_hash_destroy(&ht_func); //銷(xiāo)毀HashTablepht_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,刷啦啦的一大片,嚇壞了吧,保存下來(lái)看看有多少。應(yīng)該差不多了吧,后面有...省略號(hào)是不是buffer什么的滿(mǎn)了所以還沒(méi)輸出完呢???
? OK,下面是重點(diǎn)了,刪除對(duì)應(yīng)的函數(shù)。其實(shí)我們抄一下zend_disable_function就OK了,有同學(xué)會(huì)問(wèn)為什么不直接調(diào)用zend_disable_function,別急,下面我會(huì)說(shuō)道。再次修改我們的remove_function函數(shù),這次修改便利的循環(huán)體和 char *ini 就好
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);}}} 因?yàn)榘严到y(tǒng)的函數(shù)刪除了,不知請(qǐng)者調(diào)用會(huì)產(chǎn)生一個(gè)php函數(shù)不存在的錯(cuò)誤,腳本也會(huì)停止運(yùn)行,于是需要注冊(cè)一個(gè)同名的函數(shù)回去,而這個(gè)函數(shù)什么也不做,輸出提示就好。那么我們需要在 remove_function 函數(shù)之前定義函數(shù)入口和提示函數(shù)
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 };
估計(jì)有同學(xué)吐槽為什么用***代替了顯示的函數(shù)名,這就是為什么我不調(diào)用zend_disable_function的原因。當(dāng)時(shí)卡在這里很久,一直段錯(cuò)誤,后來(lái)無(wú)意中注釋了?get_active_function_name(TSRMLS_C) 就跑起來(lái)了╯-__-)╯ ╩╩,求告知。。和上面一個(gè)編譯重啟服務(wù)器什么的,然后看效果,因?yàn)槲覀兣渲脤?xiě)的是array_p*,所以一下函數(shù)被禁用了。(測(cè)試完以后記得關(guān)閉輸出)
然后隨便寫(xiě)個(gè)腳本,調(diào)用一下array_pop函數(shù)什么的,然后執(zhí)行之。
It's work!! :)
? 呼,不知不覺(jué)寫(xiě)了這么長(zhǎng)了,也懶得分兩篇了。接下來(lái)把讀取php.ini配置代碼寫(xiě)上就完成了。其實(shí)這部門(mén)工作在擴(kuò)展自動(dòng)生成的代碼已經(jīng)有了,只要稍微加工一下就好。
/* Declare any global variables you may need between the BEGINand 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 函數(shù)里面,去掉注釋 /* Remove comments if you have entries in php.ini */DISPLAY_INI_ENTRIES(); PHP_MINFO_FUNCTION? 函數(shù)里面,去掉注釋然后編輯你的php.ini文件,加入配置
[solutest] extension=solutest.so solutest.disable_functions = array_p*, 編譯重啟服務(wù)器,然后瀏覽phpinfo會(huì)發(fā)現(xiàn)我們的配置已經(jīng)被讀取了。最后把我們的配置利用上,可以通過(guò)SOLUEXT_G(disable_functions)宏來(lái)訪問(wèn),對(duì)應(yīng)修改 remove_function 函數(shù)。去掉 char *ini 因?yàn)橐呀?jīng)不需要了,配置從php.ini 讀取,然后修改 s
s = estrndup(SOLUTEST_G(disable_functions), strlen(SOLUTEST_G(disable_functions))); OK,保存編譯重啟服務(wù)器測(cè)試。
:)預(yù)期的效果達(dá)到了。打完收工。
PS:
? 此擴(kuò)展是本人YY的產(chǎn)物,沒(méi)有經(jīng)過(guò)嚴(yán)格測(cè)試,請(qǐng)勿在生產(chǎn)機(jī)上使用。
代碼下載: http://pan.baidu.com/share/link?shareid=207778&uk=436715329
參考資料:
《淺談從PHP內(nèi)核層面防范PHP WebShell》
《PHP擴(kuò)展開(kāi)發(fā)及內(nèi)核應(yīng)用》
《鳥(niǎo)哥博客》
《快速開(kāi)發(fā)一個(gè)PHP擴(kuò)展》
《深入理解PHP內(nèi)核》
轉(zhuǎn)載于:https://my.oschina.net/s01u/blog/107911
總結(jié)
以上是生活随笔為你收集整理的通过php extension使disable_function支持通配符的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python编程中常用的12种基础知识总
- 下一篇: PHP插件