freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

从小白到大神 | 详解MySQL UDF提权
2023-10-07 16:11:37

MySQL UDF提权

MySQL介绍

MySQL是最流行的开放源码SQL数据库管理系统,相对于Oracle,DB2等大型数据库系统,MySQL由于其开源性、易用性、稳定性等特点,受到个人使用者、中小型企业甚至一些大型企业的广泛欢迎,MySQL具有以下特点:

1、MySQL是一种关联数据库管理系统,具有灵活性。

2、MySQL软件是一种开放源码软件。

3、MySQL数据库服务器具有快速、可靠和易于使用的特点。

4、MySQL服务器工作在客户端/服务器模式下,或嵌入式系统中。

5、MySQL有大量可用的共享MySQL软件。

MySQL安装

#安装教程
https://blog.csdn.net/qq_64973687/article/details/133012226?spm=1001.2014.3001.5502
#安装mysql.h文件
sudo yum install mysql-devel -y   
#安装gcc-c++
sudo yum install gcc-c++
#启动
systemctl start mysqld 
#root启动
mysqld --user=root  
setenforce 0

MySQL内置函数

平时我们使用mysql时可以执行show databases;可以让我们查看当前的电脑中有多少数据库

图片.png

想看哪个数据库,我们就可以使用哪个数据库,比如我们现在想查看mysql这个数据库,我们就可以use mysql;使用这个数据库

图片.png

想看这个数据库中有哪些表,我们就可以 show tables;查看当前数据库下有多少表

图片.png

MySQL有很多内置函数提供给使用者,包括字符串函数、数值函数、日期和时间函数等,给开发人员和使用者带来了很多方便。比如 user()这个函数,我们执行 select user();这个语句可以查询当前登陆的用户是谁

图片.png

又比如,如果我们想查看当前的数据库,我们可以执行 select database();这个语句

图片.png

user()也好,database()也罢,他们都是MySQL数据库自带的内置函数,是开发者在一开始时就写好的功能

UDF介绍

MySQL的内置函数虽然丰富,但毕竟不能满足所有人的需要,有时候我们需要对表中的数据进行一些处理而内置函数不能满足需要的时候,就需要对MySQL进行一些扩展,幸运的是,MySQL给使用者提供了添加新函数的机制,这种使用者自行添加的MySQL函数就称为UDF(User Define Function)。UDF机制能够起作用,必须使用C或者C++编写函数,你的系统必须支持动态加载,,mysql采用动态链接库加载自定义函数。

这里举个例子方便大家理解,我们在使用user();这个函数时返回的是当前的登陆用户,那我们现在编写一个函数,暂时命名为qianfu();,其作用是返回 a+b的值,现在我们给其传参 qianfu(1,2),那么返回的结果就是 1+2 == 3

除了数值之外,那还可以返回什么呢?比如我们再编写一个函数去执行系统命令,在这个函数中传入一个系统命令,比如 qianfu("whomai"),那这个函数将返回执行系统命令后的结果,我们要讲解的UDF提权就是这种思路下的一个产物。

UDF编写详解

那么问题来了,我们该如何如开发自己的UDF呢?UDF需要编写成动态链接库,什么是动态链接库呢?一般来讲我们常见的dll文件和os文件就是动态链接库,Windows环境下的是dll文件,Linux环境下则是os文件。平时我们电脑上一个程序会有很多功能,比如我们的主程序有功能1,功能2,功能3,我们可以把功能1做成一个dll文件,功能2做成一个dll文件,功能3做成一个dll文件,在程序运行时,让主程序在运行时去加载这些dll文件就可实现相应的功能

图片.png

在MYSQL中,要实现自定义函数,也就是UDF,举个例子,我们要实现qianfu("whomai")这么一个函数去执行系统命令,首先我们需要将其用C语言写好然后编译成一个qianfu.dll文件,然后将其放置于mysql数据库的指定目录下,然后我们就可以在mysql中去调用我们自定义的函数了

UDF就是为了让我们开发者能够自己写方便自己函数,它有3种返回值类型,这三种类型分别是STRING,INTEGER,REAL

STRING        字符型
INTEGER       整型
REAL          实数型

如下面的代码:

#include <mysql.h>
 extern "C" long long testadd(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
int a = *((long long *)args->args[0]);
int b = *((long long *)args->args[1]);
return a + b;
}
extern "C" my_bool testadd_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
return 0;
}

用户主函数

首先我们假设需要定义的函数名字为为xxx, 则我们的函数需要有参数列表和返回值, 这不能由用户随意指定, 是有固定规则的

返回值是STRING 类型或DECIMAL类型

char *xxx(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error);

返回值是INTEGER类型

long long xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);

返回值是REAL类型

double xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);

系统内置函数

在完成了用户定义的主函数以后, 还需要编写配套的系统内置函数

xxx_init函数

这个函数会在自定义的xxx函数调用前被调用, 进行基本的初始化工作, 其完整定义如下,该函数的主要功能一般是分配空间, 函数参数检查的等. 如果不需要做任何操作, 直接返回0即可.

my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message)

返回值: 1代表出错, 可以在message中给出错误信息并且返回给客户端, 0表示正确执行

xxx_deinit

该函数用于释放申请的空间, 其完整定义如下,该函数的功能主要是释放资源, 如果在xxx_init中申请了内存, 可以在此处释放, 该函数在用户函数xxx执行以后执行

void xxx_deinit(UDF_INIT *initid);

执行流程

调用xxx_init来初始化, 并申请内存空间用于存储结果

调用xxx函数

调用xxx_deinit释放空间

运行自己的UDF

编写C/C++代码

#include <mysql.h>
 extern "C" long long testadd(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
int a = *((long long *)args->args[0]);
int b = *((long long *)args->args[1]);
return a + b;
}
extern "C" my_bool testadd_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
return 0;
}

生成动态链接库

动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 文件中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。windows是dll文件,linux是so文件

windows系统的话可以使用vs创建dll文件

使用vs创建dll文件即可

我们这里使用Dev-C++来编写,首先,我们打开Dev-C++,然后我们在新建一个项目

图片.png

选择 DLL文件,并点击确定

图片.png

然后我们在 dllmain.cpp 文件中去编写我们的函数

图片.png

然后点击编译将其编译,就可以了

图片.png

在Linux环境下,我们只需要去编写一个名为udf.cpp 的文件在执行编译的命令就可以将其编译好了,如下图所示

vim udf.cpp
-------------------------------------------------------------------
#include <mysql.h>
 extern "C" long long testadd(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
int a = *((long long *)args->args[0]);
int b = *((long long *)args->args[1]);
return a + b;
}
extern "C" my_bool testadd_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
return 0;
}

-------------------------------------------------------------------
g++ -shared -fPIC -I /usr/include/mysql -o udf.so udf.cpp

图片.png

图片.png

上传到目标机器

接下来就是把我们编译好的udf.so文件放置到MySQL相应的目录下

1、mysql<5.0,路径随意
3、5.0<=MYsql<5.1, 放置系统目录(C:\windows\system32)
2、mysql>5.1,udf.dll文件必须放置在mysql安装目录的lib\plugin文件夹下,(lib\plugin目录默认不存在,需自行创建)

可以看到我们当前的数据库的版本是5.7.43,那我们就需要将刚刚编译好的udf.so文件放置在mysql安装目录的lib\plugin文件夹下

图片.png

创建函数

然后我们需要去创建函数,函数名字必须和源码中一样

create function 函数名 returns string soname "udf.dll";

查询是否导入成功

select * from mysql.func;

执行函数

select 函数名(参数);

提权准备

连接mysql

连接mysql的方式很多,比如拿到了webshell,或者通过暴力破解的方式,UDF提权的第一步就是先连接mysql

查询运行权限

为什么要查询运行权限呢?因为我们UDF本质上不是提权,是通过当前mysql的运行权限去执行系统命令,如果mysql运行权限太低的话就无法执行。比如我们当前机器是以daoer普通用户身份去运行MySQL的,当我们使用UDF去提权,因为UDF本质上不是提权,是通过daoer用户的运行权限去执行命令的,那么提到的权限自然也就是daoer普通用户的权限,像下面这种位于MYSQL数据库中的root用户的权限显然就是错误的,也就是说UDF提权提到的权限不是数据库中的用户权限,而是系统运行数据库这个服务的用户的权限

#错误的,这只是数据库的root用户权限,而不是系统的权限
select user(); 

图片.png

一句话概括来说,UDF提权就是MySQL的服务端是由谁运行的,就可以提到谁的权限,UDF提权提的是系统用户的权限,而不是数据库用户的权限

在Linux下5.7版本之后,默认运行时MySQL的是mysql用户,所以在MySQL 5.7版本之后,UDF提权基本上来讲算是失效了,除非管理员手动用root身份去运行MYSQL,5.7之前MySQL运行时默认是以root身份去运行的

查询软件版本

为什么要查询版本呢?因为不同版本的动态链接文件导入的地方不同

select version();

图片.png

1、mysql<5.0,路径随意3、5.0<=MYsql<5.1, 放置系统目录(C:\windows\system32)2、mysql>5.1,udf.dll文件必须放置在mysql安装目录的lib\plugin文件夹下,(lib\plugin目录默认不存在,需自行创建)

查询系统位数

为什么要查询系统位数呢?不同的系统和位数有不同的动态连接文件

show variables like '%compile%';

图片.png

查询读写权限

为什么

# 网络安全 # web安全 # 内网渗透 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录