Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

An example Analysis of arbitrary Code execution vulnerabilities in PHP-FPM under Nginx specific configuration

2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly explains "PHP-FPM in Nginx specific configuration of arbitrary code execution vulnerabilities for example analysis", interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Next let the editor to take you to learn "PHP-FPM in the Nginx specific configuration of arbitrary code execution vulnerabilities example analysis" bar!

Overview of vulnerabilities

An arbitrary code execution vulnerability exists in PHP-FPM in a specific Nginx configuration. The details are:

When a server built with Nginx + PHP-FPM uses nginx.conf similar to the following configuration:

1 location ~ [^ /]\ .php (/ | $) {2 fastcgi_split_path_info ^ (. +?\ .php) (/. *) $; 3 fastcgi_param PATH_INFO $fastcgi_path_info;4 fastcgi_pass php:9000;5.

When fastcgi_split_path_info in Nginx processes path_info with "\ n" (% oA), the PATH_INFO passed to PHP-FPM will be set to null (PATH_INFO= ""), which affects the pointing of key pointers and causes the zeroing operation position of subsequent path_info [0] = 0 to be controllable. By constructing requests for specific length and content, you can overwrite specific location data and insert specific environment variables, resulting in code execution.

Loophole analysis

First of all, the patch is analyzed: in the static void init_request_info (void) function that initializes the request_info structure, the size check of pilen and slen is added to avoid the unexpected backtracking movement of the pointer.

1 / / php-src/sapi/fpm/fpm/fpm_main.c 2... 3 if (pt) {4 while ((ptr = strrchr (pt,'/')) | (ptr = strrchr (pt,'\\')) {5 / / a pair of incoming PATH_INFO for verification. By judging the file status, the real 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 length10 int pilen = env_path_info? Strlen (env_path_info): 0; / / Path info length 011 int tflag = 0 char * path_info;1314 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; / / sets the new env_path_info by offset, but does not check the offset 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} 2425 if (tflag) {26 if (orig_path_info) {27 char old;2829 FCGI_PUTENV (request, "ORIG_PATH_INFO", orig_path_info) 30 old = path_info [0]; 31 path_info [0] = 0 / / zeroing operation 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) / / trigger entry 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...

Among them

1 / / take http://localhost/info.php/test?a=b as an example 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 / / is "/ docroot/info.php/test" 1112 / / after recalculating 13 int ptlen = strlen (pt); / / strlen ("/ docroot/info.php") 14 int pilen = env_path_info? Strlen (env_path_info): 0; / that is, len (PATH_INFO) "/ test" 15 int slen = len-ptlen; / / len ("/ test") 1617 path_info = env_path_info + pilen-slen; / / pilen value may not be 0 or slen, that is, offset to 0 or-N

It can be seen that when PATH_INFO is empty, the path_info direction is offset forward, and the offset length is the length of test. Then path_info [0] = 0; you can set the byte at a specific location to zero. However, zeroing of a normal position does not cause RCE, and further use requires a specific control position zero, and that control bit happens to control the write position. Request- > env- > data- > pos is such a location. Here you need to explain how each variable is stored.

The environment variables passed through the fastcgi protocol are stored in the fcgi_hash structure of _ fcgi_request- > env for subsequent execution. The structure is defined as follows:

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;1112 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;1718 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;2425 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 / / initialize operation 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); / / default allocation (4x8-1) + 409642 h-> data- > pos = h-> data- > data; / / points to environment variable initial write location 43 h-> data- > end = h-> data- > pos + FCGI_HASH_SEG_SIZE Point to the end of / / data_seg 44 h-> data- > next = NULL;45} 46.

We mainly focus on the get/set operation, which is implemented as follows:

1 static char * fcgi_hash_get (fcgi_hash * h, unsigned int hash_value, char * var, unsigned int var_len, unsigned int * val_len) 2 / / Associated 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 / / requires the same hast_ value and the same var_len to take out the value 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} 1920 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 / / Associated FCGI_PUTENV () 22 {23 unsigned int idx = hash_value & FCGI_HASH_TABLE_MASK / / calculate hash_value to determine index24 fcgi_hash_bucket * p = h-> hash_ table [IDX] / / get the corresponding value of 2526 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 in the original hash_table) {3031 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} 3738 if (UNEXPECTED (h-> buckets- > idx > = FCGI_HASH_TABLE_SIZE)) {39 fcgi_hash_buckets* b = (fcgi_hash_buckets*) malloc (sizeof (fcgi_hash_buckets)) 40b-> idx = 0politics 41b-> next = h-> buckets;42 h-> buckets = bscape 43} 4445 p = h > buckets- > data + h-> buckets- > idx;46 h-> buckets- > idx++;47 p-> next = h-> hash_table [idx]; 48h-> hash_ Table[ IDX] = p49scape p-> list_next = h-> list;50 h-> list = p 5152 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} 5960 static inline char* fcgi_hash_strndup (fcgi_hash * h, char* str, unsigned int str_len) 61 / / actual operation request- > env- > data to write data. 62 {63 char * ret;6465 if (UNEXPECTED (h-> data- > pos + str_len + 1 > = h-> data- > end)) {66 / / if the length of the data to be written is greater than the currently pointed fcgi_hash_seg size, insert a new fcgi_hash_seg67 unsigned int seg_size = (str_len + 1 > FCGI_HASH_SEG_SIZE) forward? Str_len + 1: FCGI_HASH_SEG_SIZE;// is a long value that does not span two seg writes. 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 = p73 × 7475 ret = h-> data- > pos 76 memcpy (ret, str, str_len); / write data 77 rett [str _ len] = 0 str_len 78 h-> data- > pos + = str_len + 1 after h-> data- > pos; / / move back h-> data- > pos to the new writable position 79 return ret;80}

From this, we can draw a conclusion: the direction of request- > env- > data- > pos directly affects the writing position of our environment variable Key,Value. As long as we control the direction of char* pos, we may overwrite the existing data. However, there are still the following requirements and restrictions in order to achieve RCE:

The forward movement of the pointer is affected by the current fcgi_hash_seg space structure, too short to set the char* pos to zero, and too long will be allocated to the new fcgi_hash_seg space. (if passing the "shape like" http://127.0.0.1/Somefile_exits/AAAAA.php/" can also cause the pointer to move back,)

Path_info [0] = 0 can only set the single byte to zero, preferably the lowest bit, otherwise it will cause the pointer to deviate too much.

In view of the fact that the lowest bit of the address overwritten in condition 2 should be 0, followed by a conditional overwrite environment variable.

The key of the overwritten location environment variable must be the same as the key expected to be written: var, hash_value, and var_len before it can be read.

When FCGI_PUTENV (request, "ORIG_PATH_INFO", orig_path_info) is executed, ORIG_SCRIPT_NAME and orig_script_name ("ORIG_SCRIPT_NAME/index.php/PHP_VALUE\ nAAAAAA") are written respectively.

Accordingly, we can:

By controlling the length of the query_string, the path_info happens to be in the first place of the data of the new fcgi_hash_seg, and we only need to move 8+8+8+len ("PATH_INFO\ 0") + N = 34 + N to complete the tampering of the char* pos. Meet the requirements of condition 1 and 2.

By customizing the http header, you manipulate the length of the request header to place the environment variables you expect to override to a specific location (0x____00+len ("ORIG_SCRIPT_NAME") + len ("/ index.php/")). Meet the requirements of condition 3 and 5. (in NGINX, the request header in HTTP is passed into PHP-FPM in the form of "HTTP_XXX" and then written to request-env.)

The Exp author provides EBUT as a custom header whose env variable name HTTP_EBUT is equal to PHP_VALUE in length and hash_value, and PHP_VALUE will be attempted to read in subsequent processing (ini = FCGI_GETENV (request, "PHP_VALUE");). Meet the requirements of condition 4.

In addition, since part of the logic of PATH_INFO re-value is mainly to deal with situations where PATH_INFO is different from the real path_info, for the nginx configuration items mentioned at the beginning, there is a case that initiating a url in the shape of http://localhost/index/info.php/test?a=b can construct the following scenarios

1 / / take http://localhost/index/info.php/test?a=b as an example. Index is the existing file 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 / / is "/ docroot/index/info.php/test" 1112 / / after recalculating 13 int ptlen = strlen (pt); / / strlen ("/ docroot/index") 14 int pilen = env_path_info? Strlen (env_path_info): 0; / that is len (PATH_INFO) "/ test" 15 int slen = len-ptlen; / / len ("/ info.php/test") 1617 path_info = env_path_info + pilen-slen; / / pilen

< slen, 即偏移为-N 此时URL中无需存在%0A,亦可完成指针移位,漏洞过程与上述类似,但是因为script_name无效,无法直观显示攻击状态,利用难度较高,不再赘述。 path_info指向了request->

Memory layout after env- > data- > pos

Vulnerability exploitation

The author of Exp uses PHP_VALUE to pass multiple environment variables to PHP to make PHP produce errors, output webshell to / tmp/an in the form of error log, and automatically execute malicious code in / tmp/a through auto_prepend_file to achieve getshell.

1 var chain = [] string {2 "short_open_tag=1", / / enable php short tag 3 "html_errors=0", / / close the HTML tag in the error message. 4 "include_path=/tmp", / / contains path 5 "auto_prepend_file=a", / / specifies the files that are automatically included before the script is executed, which is similar to require (). 6 "log_errors=1", / / enable error log 7 "error_reporting=2", / / specify error level 8 "error_log=/tmp/a", / / error log file 9 "extension_dir=\"\ ", / / specify loaded extension11} scope of influence

Under the configuration mentioned earlier, this vulnerability affects the following versions of PHP:

7.1.x < 7.1.33

7.2.x < 7.2.24

7.3.x < 7.3.11

Vulnerability repair

You can temporarily avoid the impact of vulnerabilities by setting the cgi.fix_pathinfo=0 option through Nginx add configuration try_files% uri = 404php. You can also choose to make a full fix with updates that have been released officially.

At this point, I believe you have a deeper understanding of "PHP-FPM arbitrary code execution vulnerability analysis under Nginx specific configuration". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report