Update: There has been new PsExec versions released in 2021 (v2.30 and v2.32), we confirmed them to also be vulnerable to this Local Privilege Escalation with minor PoC adjustments.
So…this one’s been here for a while: a local privilege escalation vulnerability in PsExec. This local privilege escalation allows a non-admin process to escalate to SYSTEM if PsExec is executed locally or remotely on the target machine. I was able to confirm this works from Windows 10 all the way back to XP and from my investigation, it affects PsExec v2.2 (latest as of this writing) all the way back to v1.72 (2006). We gave Microsoft 90 days to patch the issue, however it has not been patched yet.
If you’re unfamiliar with PsExec, it is a system administrator tool that’s part of the SysInternals suite that allows for remote execution of applications on client machines. Below is my very short summary of how PsExec works:
PsExec contains an embedded resource called “PSEXESVC,” which is the executable service level component that is extracted, copied to and executed on a remote machine as SYSTEM whenever a PsExec client executes PsExec targeting a remote machine. Communication between the PsExec client and the remote PSEXESVC service takes place over named pipes. Specifically, the pipe named “\PSEXESVC,” is responsible for parsing and executing the PsExec client’s commands, such as “which application to execute,” “relevant command line data,” etc.
Of course, for security reasons, the PSEXESVC service’s “\PSEXESVC” pipe is protected and only allows Administrators read/write access, thus preventing local low-privileged users from read/writing to the service’s pipe.
Through pipe squatting however (a method in which you create the pipe first), it is possible for a low-privilege application to get access to this pipe. If a local low-privileged application creates the “\PSEXESVC” named pipe before PSEXESVC is executed, then PSEXESVC will obtain a handle to the existing instance rather than creating the named pipe itself, which will have some unexpected consequences as we will later see. Below I show disassembly of how PSEXESVC creates the “\PSEXESVC” pipe
Here we see from it’s nMaxInstances argument, that it allows for unlimited “\PSEXESVC” pipe instances to exist. We also see it doesn’t ensure it is the first application to create the “\PSEXESVC” pipe, which normally is done by using the FILE_FLAG_FIRST_PIPE_INSTANCE flag. In this case, it will try to create the named pipe, and if it already exists, just obtain a handle to the existing “\PSEXESVC” pipe after the call. This will end up inheriting the existing pipe’s ACL rather than applying its own “Administrators only” ACL to the pipe, regardless of the fact the Security Attributes say otherwise in its “CreateNamedPipe” call (above).
Here I made a simple “PipeHijack.exe” program that creates this “\PSEXESVC” pipe with read/write access available for: “David Wells,” a non-elevated user.
With this running, if PsExec is ever executed locally or remotely on this machine in the future, the PSEXESVC instance will obtain a handle to my pipe, which I can of course read/write to, thus allowing my low-privileged application to communicate with this PSEXESVC SYSTEM service!
From here it’s pretty much over. All I needed to do is talk to PSEXESVC service. I reverse engineered the PSEXESVC v2.2 protocol and put together a way to communicate over this pipe and execute any desired binary I want as SYSTEM. This is essentially mimicking the PsExec client it thinks it’s getting commands from, so executing any process as SYSTEM is pretty easy to do.
For full PoC, you can find it here: https://github.com/tenable/poc/tree/master/Microsoft/Sysinternals/PsExecEscalate.cpp