Skip to content

Named Pipes

Named pipes are Windows IPC endpoints. A privileged service exposing a pipe with permissive DACLs (Everyone or Authenticated Users granted write) and trusting the client side becomes a privilege escalation vector. Enumerate pipes, check their DACLs, identify writable ones owned by privileged processes, and exploit the trust the service places in its clients.

# Enumerate active pipes
pipelist.exe /accepteula # Sysinternals
gci \\.\pipe\ # PowerShell equivalent
# Check DACL on specific pipe
accesschk.exe -accepteula -w \\.\Pipe\NAME -v
# Find all writable pipes (operator-priority filter)
accesschk.exe -accepteula -w \pipe\ -v | findstr /B "RW"
# Inspect what owns the pipe
Get-Process -Id <PID> # cross-reference pipe owner

Success indicator: a pipe is RW accessible to Everyone or Authenticated Users, owned by a SYSTEM-level process, and the service speaks a protocol that accepts attacker-controlled input.

Named pipes are how Windows processes talk to each other on the same machine (and across machines, though that’s less common). Every named pipe has:

  • A name - \\.\pipe\<name> accessed via the special \\.\ device path. Examples: \\.\pipe\lsass, \\.\pipe\WindscribeService.
  • A server side - The process that created the pipe and reads/writes from it.
  • Zero or more client connections - Each connection creates a new pipe instance sharing the name but with independent data buffers.
  • A DACL - Specifies which security principals can read, write, or modify the pipe.

The privilege escalation angle: a pipe is a programmable communication channel between a high-privileged process and whatever clients its DACL permits. If a SYSTEM-level service creates a pipe writable by Everyone, every standard user can send messages to that service. If the service trusts the messages and acts on them with its SYSTEM token, attackers control SYSTEM behavior.

This is fundamentally a service-design vulnerability, not a Windows vulnerability. The OS provides the IPC mechanism correctly; the service vendor chose the wrong DACL.

Terminal window
C:\> pipelist.exe /accepteula
PipeList v1.02 - Lists open named pipes
Copyright (C) 2005-2016 Mark Russinovich
Sysinternals - www.sysinternals.com
Pipe Name Instances Max Instances
--------- --------- -------------
InitShutdown 3 -1
lsass 4 -1
ntsvcs 3 -1
scerpc 3 -1
Winsock2\CatalogChangeListener-340-0 1 1
epmapper 3 -1
LSM_API_service 3 -1
atsvc 3 -1
eventlog 3 -1
spoolss 3 -1
wkssvc 4 -1
trkwks 3 -1
vmware-usbarbpipe 5 -1
srvsvc 4 -1
ROUTER 3 -1
vmware-authdpipe 1 1

Output columns: pipe name, number of currently-connected client instances, and maximum allowed instances (-1 means unlimited).

The first dozen entries are Windows built-ins (lsass, eventlog, spoolss, srvsvc, etc.) - these have correct DACLs by design; don’t waste time on them. The interesting entries are everything else - vmware-usbarbpipe, vmware-authdpipe, WindscribeService, and any other third-party pipes.

Without Sysinternals, the same listing is available through PowerShell:

Terminal window
PS> gci \\.\pipe\
Directory: \\.\pipe
Mode LastWriteTime Length Name
---- ------------- ------ ----
------ 12/31/1600 4:00 PM 3 InitShutdown
------ 12/31/1600 4:00 PM 4 lsass
------ 12/31/1600 4:00 PM 3 ntsvcs
------ 12/31/1600 4:00 PM 3 scerpc

The Length column shows the number of instances. The LastWriteTime is a Windows artifact (always 1600/12/31 - pipes don’t track this meaningfully).

For just the names without the noise:

Terminal window
PS> [System.IO.Directory]::GetFiles("\\.\pipe\") | ForEach-Object { Split-Path $_ -Leaf }

Filter for non-standard entries - anything not in the standard Windows set:

Terminal window
PS> $standard = @('InitShutdown','lsass','ntsvcs','scerpc','epmapper','LSM_API_service','atsvc','eventlog','spoolss','wkssvc','trkwks','srvsvc','ROUTER','PIPE_EVENTROOT\CIMV2SCM EVENT PROVIDER','Winsock2','MsFteWds','SQLLocal')
PS> [System.IO.Directory]::GetFiles("\\.\pipe\") | ForEach-Object {
$name = Split-Path $_ -Leaf
if ($standard -notcontains $name -and $name -notmatch '^Winsock2|^MsFte|^SQLLocal') {
$name
}
}

This produces a smaller list of pipes worth investigating. The standard-pipe filter list isn’t exhaustive - environments have different baselines - but the principle is to focus enumeration on non-Microsoft pipes first.

A pipe’s DACL determines who can read from it, write to it, and modify its security descriptor. The DACL is what makes a pipe an escalation vector or not.

Terminal window
C:\> accesschk.exe /accepteula \\.\Pipe\lsass -v
Accesschk v6.12 - Reports effective permissions for securable objects
Copyright (C) 2006-2017 Mark Russinovich
Sysinternals - www.sysinternals.com
\\.\Pipe\lsass
Untrusted Mandatory Level [No-Write-Up]
RW Everyone
FILE_READ_ATTRIBUTES
FILE_READ_DATA
FILE_READ_EA
FILE_WRITE_ATTRIBUTES
FILE_WRITE_DATA
FILE_WRITE_EA
SYNCHRONIZE
READ_CONTROL
RW NT AUTHORITY\ANONYMOUS LOGON
FILE_READ_ATTRIBUTES
FILE_READ_DATA
...
RW BUILTIN\Administrators
FILE_ALL_ACCESS

Output format:

  • Pipe path at top
  • Mandatory integrity level - Untrusted Mandatory Level [No-Write-Up] means even unauthenticated callers can connect, but they can’t escalate their integrity through the pipe
  • Each ACE prefixed with RW (read+write), R (read-only), or W (write-only), followed by the principal and granted permissions

For lsass, Everyone has RW but only with specific limited permissions - this is normal for the LSA RPC interface. The check is whether the useful permissions for sending commands (typically FILE_WRITE_DATA and FILE_WRITE_EA) are granted to non-privileged groups.

Bulk DACL review - find all writable pipes

Section titled “Bulk DACL review - find all writable pipes”

The operationally useful command - find every pipe writable by the current user:

Terminal window
C:\> accesschk.exe -accepteula -w \pipe\ -v

Flags:

  • -accepteula - Skip the EULA prompt
  • -w - Show only objects the current user has write access to
  • \pipe\ - Target all named pipes (note: leading backslash, not \\.\pipe\)
  • -v - Verbose, show specific permissions

Output is large; filter for RW lines with non-Administrator principals:

Terminal window
C:\> accesschk.exe -accepteula -w \pipe\ -v | findstr /B "RW" | findstr /V "Administrators SYSTEM TrustedInstaller"

This narrows to pipes writable by less-privileged principals. Each match needs investigation: what process owns the pipe, what protocol does it speak, what does it accept from clients.

A pipe’s name doesn’t always reveal who owns it. Cross-reference using Process Monitor or by inspecting handles:

Terminal window
C:\> handle.exe -accepteula -a \pipe\WindscribeService
Handle v5.0 - Handle viewer
Copyright (C) 1997-2022 Mark Russinovich
Sysinternals - www.sysinternals.com
WindscribeService.exe pid: 2156 type: File 224: \Device\NamedPipe\WindscribeService

handle.exe (Sysinternals) shows that the pipe is owned by WindscribeService.exe (PID 2156). Then check what context that process runs as:

Terminal window
PS> Get-Process -Id 2156 -IncludeUserName
Handles WS(K) CPU(s) Id UserName ProcessName
------- ----- ------ -- -------- -----------
245 8456 1.23 2156 NT AUTHORITY\SYSTEM WindscribeService

NT AUTHORITY\SYSTEM confirms this is a SYSTEM-level process - a writable pipe to it is the escalation vector.

Get-Process -IncludeUserName requires elevation on most systems. From an unprivileged context, use tasklist /v instead:

Terminal window
C:\> tasklist /fi "PID eq 2156" /v

The User Name column will appear in the output.

Once a pipe is identified as writable by a non-privileged principal and owned by a privileged process, the actual exploitation depends on what the service’s protocol does. The pattern:

  1. Identify the service - vendor product, version, name of the binary. Critical for finding documented CVEs and protocol details.
  2. Understand the protocol - reverse-engineer or find published documentation of what messages the service accepts. This is the heavy lifting.
  3. Find an actionable command - does the protocol allow file writes, command execution, arbitrary registry changes, service install? If so, identify the format.
  4. Send the message - write the crafted message to the pipe via standard file I/O (named pipes are accessed like files).
  5. The service acts on the message with its SYSTEM token - and now your attacker-supplied data has run with SYSTEM privileges.

The canonical published example is the Windscribe VPN named pipe vulnerability. Walking through it as a methodology illustration:

1. Enumerate pipes, find Windscribe in the list:

Terminal window
C:\> pipelist.exe /accepteula | findstr -i windscribe
WindscribeService 1 -1

2. Check the DACL:

Terminal window
C:\> accesschk.exe -accepteula -w \pipe\WindscribeService -v
\\.\Pipe\WindscribeService
Medium Mandatory Level (Default) [No-Write-Up]
RW Everyone
FILE_ALL_ACCESS

Everyone has FILE_ALL_ACCESS - full read/write/modify. This is the indicator.

3. Identify ownership:

Handle.exe or Process Explorer shows WindscribeService.exe (PID varies) running as NT AUTHORITY\SYSTEM.

4. Understand the protocol:

The Windscribe service speaks a JSON-formatted protocol over the pipe to its UI client. One supported command is “execute helper” which lets the client run a program with arbitrary arguments. The service trusts the client because - by intent - the pipe is between the GUI and the service. But the DACL allows anyone to be that client.

5. Send a crafted message:

The PoC sends a JSON request asking the service to execute cmd.exe (or any binary) with attacker-chosen arguments. The service executes the command as SYSTEM. Net effect: arbitrary command execution as SYSTEM from a standard user context.

The published PoC is in C# at the Exploit-DB link; running it on a vulnerable Windscribe install yields SYSTEM. The patch (in Windscribe 1.83+) fixes the DACL.

What to look for in any pipe-based service

Section titled “What to look for in any pipe-based service”

The Windscribe pattern generalizes. When you find a writable pipe owned by a privileged process:

  • Is there a public CVE or write-up? Search the service name + “named pipe” + “privilege escalation”.
  • What does the GUI client do? If the product has a UI, the UI talks to the service over the pipe. Reverse engineering the UI’s pipe usage reveals the protocol.
  • What’s the message format? JSON, length-prefixed binary, line-oriented text, .NET BinaryFormatter, MSRPC, custom? Each has different attack patterns.
  • What commands does the service support? Look for command names containing Execute, Run, Launch, Install, Update, Service, Process, Spawn, File, Write, Copy.
  • Are arguments sanitized? Often: not really. Path-confusion attacks, argument injection, and “we trusted the GUI” assumptions are common.

When no CVE exists and you need to figure out the protocol yourself:

  • Procmon + the GUI client - Run the GUI, watch its pipe operations. Process Monitor’s Operation: WriteFile events on the pipe show every message sent.
  • Wireshark on \Device\NPF_NamedPipe - Some Wireshark installations have a named-pipe capture plugin.
  • Inject into the GUI process - Hook WriteFile and ReadFile calls; log all data going through.
  • Static analysis - Decompile the service binary with Ghidra or IDA, find the function reading from the pipe, trace what it does with the data.
  • Strings on the binary - Often command names appear as plaintext strings, giving hints about the protocol vocabulary.
DetectionWhat it sees
Sysmon event 17/18 (Pipe Created/Connected)All pipe creates and connects, with PID and image
Process Monitor filter on Process Name: yourtool.exeDirect view of what your tool is doing
EDR pipe heuristics (CrowdStrike, SentinelOne)Known-bad pipe names; unusual cross-PID pipe traffic
WMI permanent event subscriptions watching Win32_NamedPipeFileCustom defender setup

For evasive operations, pipe-based exploitation is moderately noisy - Sysmon catches the pipe connect, and EDR may flag the data flowing across. For non-evasive engagements, the noise level is acceptable for the payoff.

For completeness, the defender side of named-pipe abuse:

  • Pipe DACLs should be principle-of-least-privilege - the GUI client should authenticate to the service over the pipe (not just connect via DACL trust)
  • Services should not trust pipe clients - every message should be validated, authenticated, and parameter-checked
  • Avoid impersonation traps - SeImpersonatePrivilege interacts with pipe authentication; see SeImpersonate for the related token-theft pattern
TaskPattern
List all pipes (Sysinternals)pipelist.exe /accepteula
List all pipes (PowerShell)gci \\.\pipe\
Pipe names only[System.IO.Directory]::GetFiles("\\.\pipe\") | ForEach-Object { Split-Path $_ -Leaf }
DACL of specific pipeaccesschk.exe -accepteula -w \\.\Pipe\NAME -v
All writable pipesaccesschk.exe -accepteula -w \pipe\ -v
Writable pipes (filtered)accesschk.exe -accepteula -w \pipe\ -v | findstr /B "RW"
Find pipe ownerhandle.exe -accepteula -a \pipe\NAME
Process user contextGet-Process -Id PID -IncludeUserName or tasklist /v /fi "PID eq PID"
Standard pipes to ignorelsass, ntsvcs, scerpc, epmapper, eventlog, spoolss, wkssvc, srvsvc, atsvc, etc.
Indicators of vulnerabilityRW Everyone or RW Authenticated Users on a pipe owned by SYSTEM-context service
Protocol RE toolsProcmon, Wireshark NPF, Ghidra, IDA Pro
Canonical CVE exampleCVE-2018-11479 (Windscribe), CVE-2020-7280 (McAfee), CVE-2020-26878 (Ansible)

For the token-impersonation angle that’s adjacent to pipes (Potato-family attacks abuse named pipes plus SeImpersonatePrivilege), see SeImpersonate. For service-binary abuse where pipes aren’t involved but the service is, see the OS attacks round (weak service ACLs).

Defenses D3-SYSM