Sunday, 5 November 2017

Named Pipe Secure Prefixes

When writing named pipe servers on Windows it’s imperative is do so securely. One common problem you’ll encounter is named pipe squatting, where a low privileged application creates a named pipe server either before the real server does or as a new instance of an existing server. This could lead to information disclosure if the server is being used to aggregate private data from a number of clients as well as elevation of privilege if the conditions are right.

There’s some programming strategies to try and eliminate named pipe squatting including passing the FILE_FLAG_FIRST_PIPE_INSTANCE flag to CreatedNamedPipe which will cause an error if the pipe already exists as well as appropriate configuration of the pipe’s security descriptor. A recent addition to Windows is named pipe secure prefixes, which make it easier to develop a named pipe server which isn’t vulnerable to squatting. Unfortunately I can’t find any official documentation on these prefixes or how you use them. Prefixes seem to be mentioned briefly in Windows Internals, but it doesn’t go into any detail about what they are or why they work. So this blog is an effort to remedy that lack of documentation.

First let’s start with how named pipes are named. Named pipes are exposed by the Named Pipe File System (NPFS) driver, which creates the \Device\NamedPipe device object. When you create a new named pipe instance you call CreateNamedPipe (really NtCreateNamedPipeFile under the hood) with the full path to the pipe to create. It’s typically to see this in the form \\.\pipe\PipeName where PipeName is the name you want to assign. At the native API level this path is converted to \??\pipe\PipeName; \??\pipe is a symbolic link which ultimately resolves this path to \Device\NamedPipe\PipeName.

Even though \Device\NamedPipe is a file system, NPFS doesn’t support directories other than the root. If you list the contents of the named pipe root directory you’ll notice that some of the names pipes have backslashes in the name, however NPFS just treats them as names as shown below.

named_pipes.PNG

So we’d assume we can’t have directories, but if we look at the function which handles IRP_MJ_CREATE dispatch in the NPFS driver we find something interesting:

NTSTATUS NpFsdCreate(PDEVICE_OBJECT device, PIRP irp) {
 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
 BOOLEAN directoryfile = stack->Parameters.Create.Options
                                & FILE_DIRECTORY_FILE;
 DWORD disposition = stack->Parameters.Create.Options >> 24;
 NTSTATUS status;
 // Other stuff...

 if (directoryfile) { ← Check for creating a directory
   if (disposition == FILE_OPEN) {
     status = NpOpenNamedPipePrefix(...);
   } else if (disposition == FILE_CREATE
           || disposition == FILE_OPEN_IF) {
     status = NpCreateNamedPipePrefix(...);
   }
 }

 // Even more stuff...
}

In the code we can see that the flag for creating a directory file is checked. If a directory file is requested then the driver tries to create or open a named pipe prefix. Let’s try and create one of these prefixes:

create_prefix.PNG

Well darn, it say it requires a privilege. Let’s dig into NpCreateNamedPipePrefix to see which privilege we’re missing.

NTSTATUS NpCreateNamedPipePrefix(...) {  
 // Blah blah...
 if (SeSinglePrivilegeCheck(SeExports->SeTcbPrivilege, UserMode)) {
   // Continue...
 } else {
   return STATUS_PRIVILEGE_NOT_HELD;
 }
}

So that’s awkward, TCB privilege is only granted to SYSTEM users, not even to administrators. While as an administrator it’s not that hard to get a SYSTEM token the same can’t be said of LocalService or NetworkService accounts. At least let’s check if impersonating SYSTEM with TCB privilege will work to create a prefix:

create_prefix_working.PNG

We now have a new prefix called Badgers, so let’s try and create a new named pipe as a normal user under that prefix and see if it does anything interesting.

create_pipe_access_denied.PNG

We get STATUS_ACCESS_DENIED returned. This it turns out is because the driver will find the largest prefix that’s been registered and check if the caller has access to the prefix’s security descriptor. What’s the security descriptor for the Badgers prefix?
prefix_sd.PNG

Seems it’s just SYSTEM and Administrators group with access, which makes sense based on the original caller being SYSTEM. Therefore, if we supply a more permissive security descriptor then it should allow a normal user to create the pipe.

create_with_dacl.PNG

Another question, how can we get rid of an existing prefix? You just need to close all handles to the prefix and it will go away automatically.

As said it’s a pain that you need TCB privilege to create new secure prefixes especially for non-administrator service accounts. Has the system created any prefixes already? There’s nothing obvious in NPFS. After a bit of investigation the Session Manager process (SMSS) creates a number of known prefixes in the function SmpCreateProtectedPrefixes. For example SMSS creates the following prefixes:

\ProtectedPrefix\Administrators
\ProtectedPrefix\LocalService
\ProtectedPrefix\NetWorkService

Each of these prefixes have a DACL based on their name, e.g. LocalService has a DACL which only allows the LocalService user to create named pipes under that prefix.
prefix_sd-2.PNG

It’s worth noting that the owner for the prefixes is the Administrators group which means an administrator could open the prefixes and rewrite the DACL, if you really wanted to screw with the OS :-)

Anyway, if you’re writing a new named pipe server and you want to make it more difficult for named pipe squatting then adding an appropriate secure prefix will prevent other users, especially low privileged users from creating a new pipe with the same name. If someone knows where this is documented please let me know as I think it’s a useful security feature which few know about.