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;可以让我们查看当前的电脑中有多少数据库
想看哪个数据库,我们就可以使用哪个数据库,比如我们现在想查看mysql这个数据库,我们就可以use mysql;使用这个数据库
想看这个数据库中有哪些表,我们就可以 show tables;查看当前数据库下有多少表
MySQL有很多内置函数提供给使用者,包括字符串函数、数值函数、日期和时间函数等,给开发人员和使用者带来了很多方便。比如 user()这个函数,我们执行 select user();这个语句可以查询当前登陆的用户是谁
又比如,如果我们想查看当前的数据库,我们可以执行 select database();这个语句
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文件就可实现相应的功能
在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++,然后我们在新建一个项目
选择 DLL文件,并点击确定
然后我们在 dllmain.cpp 文件中去编写我们的函数
然后点击编译将其编译,就可以了
在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
上传到目标机器
接下来就是把我们编译好的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文件夹下
创建函数
然后我们需要去创建函数,函数名字必须和源码中一样
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();
一句话概括来说,UDF提权就是MySQL的服务端是由谁运行的,就可以提到谁的权限,UDF提权提的是系统用户的权限,而不是数据库用户的权限
在Linux下5.7版本之后,默认运行时MySQL的是mysql用户,所以在MySQL 5.7版本之后,UDF提权基本上来讲算是失效了,除非管理员手动用root身份去运行MYSQL,5.7之前MySQL运行时默认是以root身份去运行的
查询软件版本
为什么要查询版本呢?因为不同版本的动态链接文件导入的地方不同
select version();
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%';
查询读写权限
为什么