Students who have used PHP-related environments should be familiar with fastcgi. So what is fastcgi, and why can nginx connect to PHP through fastcgi?
Fastcgi is actually a communication protocol, like the HTTP protocol, it is a channel for data exchange.
HTTP protocol is a protocol for the browser and server middleware to exchange data. The browser assembles the HTTP header and HTTP body into a data packet using a certain rule, and sends it to the server middleware in TCP mode. The server middleware follows The rule decodes the data packet, and obtains the data required by the user as required, and then packs it back to the server in the rules of the HTTP protocol.
In analogy to the HTTP protocol, the fastcgi protocol is a protocol for data exchange between server middleware and a certain language backend. The Fastcgi protocol is composed of multiple records. The record also has header and body. The server middleware encapsulates the two according to the Fastcgi rules and sends them to the language backend. After the language backend decodes the specific data, it performs the specified operation, and The result is encapsulated according to the protocol and then returned to the server middleware.
Different from the HTTP header, the record header is fixed at 8 bytes, and the body is specified by the contentLength in the header, and its structure is as follows:
typedef struct {/* Header */ unsigned char version; < span class="hljs-comment" style="color: rgb(136, 136, 136);">// Version unsigned char< /span> type; < span class="hljs-comment" style="color: rgb(136, 136, 136);">// The type of this record unsigned char requestIdB1; // The request id corresponding to this record unsigned char requestIdB0; unsigned char contentLengthB1; // Body size unsigned char contentLengthB0; unsigned char paddingLength; // Extra block size unsigned char reserved; /* Body */ unsigned char contentData [contentLength]; unsigned char paddingData[paddingLength]; } FCGI_Record;
The header consists of 8 uchar type variables, each of which is 1 byte. Among them, requestId
occupies two bytes, a unique logo id, to avoid the influence between multiple requests; contentLength
occupies two bytes and represents the size of the body.
After the language side parses the fastcgi header, you get contentLength
, and then read the size in the TCP stream Data equal to contentLength
This is the body.
There is an extra piece of data (Padding) after the body, the length of which is specified by the paddingLength in the header, which is reserved. When the padding is not needed, set its length to 0.
It can be seen that the maximum body size supported by a fastcgi record structure is 2^16
, which is 65536 bytes .
I just introduced the meaning of each structure in a record of fastcgi. The second byte type
I Did not say in detail.
type
is to specify the role of the record. Because the size of a record in fastcgi is limited and its function is single, we need to transmit multiple records in one TCP stream. Use type
to mark the role of each record, use requestId
as the id of the same request.
In other words, there will be multiple records for each request, and their requestId
are the same.
borrow a table from this article and list the most important ones type
:
Looking at this table, it’s clear that the server middleware communicates with the back-end language, and the first packet is type
is a record of 1, and then communicate with each other and send type
Records of 4, 5, 6, 7, and records with type
are sent at the end.
When the back-end language receives a record of type
to 4, it will record the body of this record According to the corresponding structure, it is parsed into key-value pairs, which are environment variables. The structure of environment variables is as follows:
typedef struct {unsigned< /span> char nameLengthB0; /* nameLengthB0 >> 7 == 0 */ unsigned char< /span> valueLengthB0; /* valueLengthB0 >> 7 == 0 */ unsigned char nameData[nameLength]; unsigned char valueData[valueLength];} FCGI_NameValuePair11;typedef struct {unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */ < span class="hljs-keyword" style="color: rgb(51, 51, 51);font-weight: 700;">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< /span> 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< /span>) << 24) + (B2 << 16) + (B1 << 8) + B0];} FCGI_NameValuePair44;
These are actually 4 structures. As for which structure to use, there are the following rules:
-
key and value are both less than 128 bytes, use
FCGI_NameValuePair11
-
Key is greater than 128 bytes, value is less than 128 bytes, use
FCGI_NameValuePair41
-
key small If the value is greater than 128 bytes, use
FCGI_NameValuePair14
- < p>key and value are larger than 128 bytes, use
FCGI_NameValuePair44
Why do I only introduce the record whose type
is 4? Because environment variables play an important role in PHP-FPM later, this structure will also be written when you write code later. type
Other situations, you can look through the document to understand.
PHP-FPM (FastCGI Process Manager)
So, PHP-FPM is what?
FPM is actually a fastcgi protocol parser. Server middleware such as Nginx will package user requests according to fastcgi rules and send them to whom through TCP? In fact, it is passed to FPM.
FPM parses the TCP stream into real data according to the fastcgi protocol.
For example, the user visits http://127.0.0.1/index.php?a=1&b=2 code> 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', < span class="hljs-string" style="color: rgb(136, 0, 0);">'QUERY_STRING': '?a=1&b=2', 'REQUEST_URI' span>: '/index.php?a=1&b=2', 'DOCUMENT_ROOT': '/var/www/html', 'SERVER_SOFTWARE': < span class="hljs-string" style="color: rgb(136, 0, 0);">'php/fcgiclient', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT' span>: '12345', 'SERVER_ADDR': '127.0.0.1'< /span>, 'SERVER_PORT': '80', 'SERVER_NAME': "localhost", 'SERVER_PROTOCOL': 'HTTP/1.1'< /span>)
This array is actually part of the PHP $_SERVER
Environment variables. But the role of environment variables is not only to fill the $_SERVER
array, but also to tell fpm: "Which PHP file do I want to execute".
After PHP-FPM gets the fastcgi data packet, it analyzes and obtains the above-mentioned environment variables. Then, execute the PHP file pointed to by the value of SCRIPT_FILENAME
, which is /var/www/html/index.php
.
Nginx (IIS7) parsing vulnerability
Nginx and IIS7 once appeared a PHP related Parsing vulnerabilities (test environment https://github.com/phith0n/vulhub/tree/master/nginx_parsing_vulnerability
), the The vulnerability phenomenon is that when the user visits http://127.0.0.1/favicon.ico/.php
The file is favicon.ico, but it is parsed according to the .php suffix.
The reason for this vulnerability is that the path_info passed in by the user was parsed incorrectly in the Nginx configuration. The environment variables finally sent to fpm are as follows:
{ ... 'SCRIPT_FILENAME': '/var/www/html/favicon.ico', 'SCRIPT_NAME': '/favicon.ico/.php' , 'REQUEST_URI': '/favicon.ico/.php', 'DOCUMENT_ROOT': '/var/www/html', ...}
SCRIPT_FILENAME
is set to < code style=" padding: 2px 4px;;;;;;;;;; ">/var/www/html/favicon.ico, so favicon.ico is parsed as a php file.
security.limit_extensions
Configuration
Up to this point, the PHP-FPM unauthorized access vulnerability is about to emerge. PHP-FPM listens on port 9000 by default. If this port is exposed on the public network, we can construct the fastcgi protocol by ourselves to communicate with fpm.
At this time, the value of SCRIPT_FILENAME
is extremely important. Because fpm executes the php file based on this value, if the file does not exist, fpm will directly return 404:
Before a certain version of fpm, we can specify the value of SCRIPT_FILENAME
to any suffix file, such as /etc/passwd
; but later, an option was added to the default configuration of fpm security.limit_extensions
:
; Limits the extensions of the main script FPM will allow to parse. This can; prevent configuration mistakes on the web server side. You should only limit; FPM to .php extensions to prevent malicious users to use other extensions to; exectute php code.; Note: set an empty value to allow all extensions.; Default Value: .php;security.limit_extensions = .php .php3 .php4 .php5 .php7
It restricts that only files with certain suffixes are allowed to be executed by fpm. The default is .php
. So, when we pass in again /etc/passwd
will return Access denied.
:
security.limit_extensions is empty by default, and there are no restrictions at this time.
Due to the limitation of this configuration item, if you want to exploit the unauthorized access vulnerability of PHP-FPM, you must first find an existing PHP file.
Fortunately, when php is installed from source, some files with php suffix are attached to the server. We use find / -name "*.php"
to search the default environment globally:
找到了不少。这就给我们提供了一条思路,假设我们爆破不出来目标环境的web目录,我们可以找找默认源安装后可能存在的php文件,比如 /usr/local/lib/php/PEAR.php
。
那么,为什么我们控制fastcgi协议通信的内容,就能执行任意PHP代码呢?
理论上当然是不可以的,即使我们能控制 SCRIPT_FILENAME
,让fpm执行任意文件,也只是执行目标服务器上的文件,并不能执行我们需要其执行的文件。
但PHP是一门强大的语言,PHP.INI中有两个有趣的配置项, auto_prepend_file
和 auto_append_file
。
auto_prepend_file
是告诉PHP,在执行目标文件之前,先包含 auto_prepend_file
中指定的文件; auto_append_file
是告诉PHP,在执行完成目标文件后,包含 auto_append_file
指向的文件。
那么就有趣了,假设我们设置 auto_prepend_file
为 php://input
,那么就等于在执行任何php文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在Body中,他们就能被执行了。 (当然,还需要开启远程文件包含选项 allow_url_include
)
那么,我们怎么设置 auto_prepend_file
的值?
这又涉及到PHP-FPM的两个环境变量, PHP_VALUE
和 PHP_ADMIN_VALUE
。这两个环境变量就是用来设置PHP配置项的, PHP_VALUE
可以设置模式为 PHP_INI_USER
和 PHP_INI_ALL
的选项, PHP_ADMIN_VALUE
可以设置所有选项。 ( disable_functions
除外,这个选项是PHP加载的时候就确定了,在范围内的函数直接不会被加载到PHP上下文中)
所以,我们最后传入如下环境变量:
{ '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'}
设置 auto_prepend_file = php://input
且 allow_url_include = On
,然后将我们需要执行的代码放在Body中,即可执行任意代码。
效果如下:
上图中用到的EXP,就是根据之前介绍的fastcgi协议来编写的,代码如下: https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75 。兼容Python2和Python3,方便在内网用。
之前好些人总是拿着一个GO写的工具在用,又不太好用。实际上理解了fastcgi协议,再看看这个源码,就很简单了。
EXP编写我就不讲了,自己读代码吧。
打赏随意❤知识无价
关注随意❤PHP技术大全
@深圳
搭过php相关环境的同学应该对fastcgi不陌生,那么fastcgi究竟是什么东西,为什么nginx可以通过fastcgi来对接php?
Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。
HTTP协议是浏览器和服务器中间件进行数据交换的协议,浏览器将HTTP头和HTTP体用某个规则组装成数据包,以TCP的方式发送到服务器中间件,服务器中间件按照规则将数据包解码,并按要求拿到用户需要的数据,再以HTTP协议的规则打包返回给服务器。
类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。 Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。
和HTTP头不同,record的头固定8个字节,body是由头中的contentLength指定,其结构如下:
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;
头由8个uchar类型的变量组成,每个变量1字节。其中, requestId
占两个字节,一个唯一的标志id,以避免多个请求之间的影响; contentLength
占两个字节,表示body的大小。
语言端解析了fastcgi头以后,拿到 contentLength
,然后再在TCP流里读取大小等于 contentLength
的数据,这就是body体。
Body后面还有一段额外的数据(Padding),其长度由头中的paddingLength指定,起保留作用。不需要该Padding的时候,将其长度设置为0即可。
可见,一个fastcgi record结构最大支持的body大小是 2^16
,也就是65536字节。
刚才我介绍了fastcgi一个record中各个结构的含义,其中第二个字节 type
我没详说。
type
就是指定该record的作用。因为fastcgi一个record的大小是有限的,作用也是单一的,所以我们需要在一个TCP流里传输多个record。通过 type
来标志每个record的作用,用 requestId
作为同一次请求的id。
也就是说,每次请求,会有多个record,他们的 requestId
是相同的。
借用 该文章 中的一个表格,列出最主要的几种 type
:
看了这个表格就很清楚了,服务器中间件和后端语言通信,第一个数据包就是 type
为1的record,后续互相交流,发送 type
为4、5、6、7的record,结束时发送 type
为2、3的record。
当后端语言接收到一个 type
为4的record后,就会把这个record的body按照对应的结构解析成key-value对,这就是环境变量。环境变量的结构如下:
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;
这其实是4个结构,至于用哪个结构,有如下规则:
-
key、value均小于128字节,用
FCGI_NameValuePair11
-
key大于128字节,value小于128字节,用
FCGI_NameValuePair41
-
key小于128字节,value大于128字节,用
FCGI_NameValuePair14
-
key、value均大于128字节,用
FCGI_NameValuePair44
为什么我只介绍 type
为4的record?因为环境变量在后面PHP-FPM里有重要作用,之后写代码也会写到这个结构。 type
的其他情况,大家可以自己翻文档理解理解。
PHP-FPM(FastCGI进程管理器)
那么,PHP-FPM又是什么东西?
FPM其实是一个fastcgi协议解析器,Nginx等服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给谁?其实就是传给FPM。
FPM按照fastcgi的协议将TCP流解析成真正的数据。
举个例子,用户访问 http://127.0.0.1/index.php?a=1&b=2
,如果web目录是 /var/www/html
,那么Nginx会将这个请求变成如下key-value对:
{ '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中 $_SERVER
数组的一部分,也就是PHP里的环境变量。但环境变量的作用不仅是填充 $_SERVER
数组,也是告诉fpm:“我要执行哪个PHP文件”。
PHP-FPM拿到fastcgi的数据包后,进行解析,得到上述这些环境变量。然后,执行 SCRIPT_FILENAME
的值指向的PHP文件,也就是 /var/www/html/index.php
。
Nginx(IIS7)解析漏洞
Nginx和IIS7曾经出现过一个PHP相关的解析漏洞(测试环境 https://github.com/phith0n/vulhub/tree/master/nginx_parsing_vulnerability
),该漏洞现象是,在用户访问 http://127.0.0.1/favicon.ico/.php
时,访问到的文件是favicon.ico,但却按照.php后缀解析了。
这个漏洞的原因就是由于Nginx配置中将用户传入的path_info解析错了,最后发送给fpm的环境变量如下:
{ ... 'SCRIPT_FILENAME': '/var/www/html/favicon.ico', 'SCRIPT_NAME': '/favicon.ico/.php', 'REQUEST_URI': '/favicon.ico/.php', 'DOCUMENT_ROOT': '/var/www/html', ...}
SCRIPT_FILENAME
被设置成了 /var/www/html/favicon.ico
,所以favicon.ico被当做php文件解析了。
security.limit_extensions
配置
写到这里,PHP-FPM未授权访问漏洞也就呼之欲出了。 PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信。
此时, SCRIPT_FILENAME
的值就格外重要了。因为fpm是根据这个值来执行php文件的,如果这个文件不存在,fpm会直接返回404:
在fpm某个版本之前,我们可以将 SCRIPT_FILENAME
的值指定为任意后缀文件,比如 /etc/passwd
;但后来,fpm的默认配置中增加了一个选项 security.limit_extensions
:
; Limits the extensions of the main script FPM will allow to parse. This can; prevent configuration mistakes on the web server side. You should only limit; FPM to .php extensions to prevent malicious users to use other extensions to; exectute php code.; Note: set an empty value to allow all extensions.; Default Value: .php;security.limit_extensions = .php .php3 .php4 .php5 .php7
其限定了只有某些后缀的文件允许被fpm执行,默认是 .php
。所以,当我们再传入 /etc/passwd
的时候,将会返回 Access denied.
:
ps. 这个配置也会影响Nginx解析漏洞,我觉得应该是因为Nginx当时那个解析漏洞,促成PHP-FPM增加了这个安全选项。另外,也有少部分发行版安装中 security.limit_extensions
默认为空,此时就没有任何限制了。
由于这个配置项的限制,如果想利用PHP-FPM的未授权访问漏洞,首先就得找到一个已存在的PHP文件。
万幸的是,通常使用源安装php的时候,服务器上都会附带一些php后缀的文件,我们使用 find / -name "*.php"
来全局搜索一下默认环境:
找到了不少。这就给我们提供了一条思路,假设我们爆破不出来目标环境的web目录,我们可以找找默认源安装后可能存在的php文件,比如 /usr/local/lib/php/PEAR.php
。
那么,为什么我们控制fastcgi协议通信的内容,就能执行任意PHP代码呢?
理论上当然是不可以的,即使我们能控制 SCRIPT_FILENAME
,让fpm执行任意文件,也只是执行目标服务器上的文件,并不能执行我们需要其执行的文件。
但PHP是一门强大的语言,PHP.INI中有两个有趣的配置项, auto_prepend_file
和 auto_append_file
。
auto_prepend_file
是告诉PHP,在执行目标文件之前,先包含 auto_prepend_file
中指定的文件; auto_append_file
是告诉PHP,在执行完成目标文件后,包含 auto_append_file
指向的文件。
那么就有趣了,假设我们设置 auto_prepend_file
为 php://input
,那么就等于在执行任何php文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在Body中,他们就能被执行了。 (当然,还需要开启远程文件包含选项 allow_url_include
)
那么,我们怎么设置 auto_prepend_file
的值?
这又涉及到PHP-FPM的两个环境变量, PHP_VALUE
和 PHP_ADMIN_VALUE
。这两个环境变量就是用来设置PHP配置项的, PHP_VALUE
可以设置模式为 PHP_INI_USER
和 PHP_INI_ALL
的选项, PHP_ADMIN_VALUE
可以设置所有选项。 ( disable_functions
除外,这个选项是PHP加载的时候就确定了,在范围内的函数直接不会被加载到PHP上下文中)
所以,我们最后传入如下环境变量:
{ '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'}
设置 auto_prepend_file = php://input
且 allow_url_include = On
,然后将我们需要执行的代码放在Body中,即可执行任意代码。
效果如下:
上图中用到的EXP,就是根据之前介绍的fastcgi协议来编写的,代码如下: https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75 。兼容Python2和Python3,方便在内网用。
之前好些人总是拿着一个GO写的工具在用,又不太好用。实际上理解了fastcgi协议,再看看这个源码,就很简单了。
EXP编写我就不讲了,自己读代码吧。
打赏随意❤知识无价
关注随意❤PHP技术大全
@深圳