Discovering and Exploiting Named Pipe Security Flaws for Fun and Profit

Blake Watts
Research & Development
April 2002

1 Abstract

Several named pipe security flaws have been discovered in recent years. However the technicalities involved in their discovery are widely unknown and undocumented. In fact, there isn't even a common terminology to define these security flaws. This paper will present a common terminology and define the intricacies of discovering and exploiting these security flaws. Past named pipe related vulnerabilities will be explored as well as areas in which vulnerabilities could be discovered in the future.

All references to Microsoft(r) Windows(r) software is in reference to the operating systems built on NT technology. This includes Windows NT(r), Windows 2000(r), and most recently, Windows XP(r).

The use of the C programming language will be used to illustrate code algorithms. Some code samples are pseudo code and will not compile.

Security Internals acknowledges Matthew Conover for technical editing of this paper.

Copyright (c) 2002, Security Internals, Inc. All rights not explicitly granted are reserved. This paper may be redistributed freely as long as it remains fully intact without modification. This paper is provided "AS IS" and with no warranties of any kind, whether expressed or implied.

2 Named Pipe Primer

A named pipe is a mechanism that enables interprocess communication for applications to communicate locally or remotely. The application that creates the pipe is known as the pipe server, and the application that connects to the pipe is known as the pipe client. Similar to sockets, after the server creates the named pipe, pipe clients may connect to the server. The function CreateNamedPipe provides the means to create the pipe. Clients use the CreateFile function to connect to the server-end of the named pipe. After the client and server have established a connection, they use the ReadFile and WriteFile functions to communicate.

The way in which named pipes are created and connected to is conceptually identical to how files are created on a file system--that's no coincidence. Internally, named pipes are implemented as a file system (more on this later). The "file location" of creating and connecting to a pipe follows a standard naming convention. This effectively ensures that the creation and connection requests of pipes are internally routed correctly. That convention is as follows: \\.\pipe\pipename. The \\.\ segment is used to route the creation and connection of the pipe to the appropriate file system. The period in the \\.\ segment may be replaced with a remote system name. This enables the associated function to be invoked remotely. Please note, however, that only pipe connection requests may do that. Pipe servers can only be created locally.

A typical algorithm implemented by the server and client is illustrated as follows:

The Server:
   hPipe = CreateNamedPipe("\\\\.\\pipe\\mypipe", ...);
   while (ConnectNamedPipe(hPipe, 0))
   {
      while (ReadFile(hPipe, buf, ...))
      {
         ProcessData(hPipe);
      }
      DisconnectNamedPipe(hPipe);
   }
   CloseHandle(hPipe);

The Client:
   hPipe = CreateFile("\\\\.\\pipe\\mypipe", ...);

   // Request some operation
   WriteFile(hPipe, buf, sizeof(buf));
   ReadFile(hPipe, readBuf, sizeof(readBuf));

   CloseHandle(hPipe);

There are several interesting named pipe related functions. The most interesting to most readers is the ImpersonateNamedPipeClient function. This function allows the server-end of a pipe to assume the security context of its clients. This will be discussed in-depth later. For a complete knowledge of named pipe related functions, use of the MSDN is essential.

2.1 Named Pipe Impersonation

Impersonation is the process of a pipe server temporarily assuming the security context of the client of its pipe. Pipe servers impersonate their client's security context by calling the function ImpersonateNamedPipeClient. The requirements of impersonation are that a client connects to the pipe server and writes some data to it. After the pipe server reads the data from the client, its call to ImpesonateNamedPipeClient will succeed, and it is granted an 'impersonation' token of the client. The requirement of reading data from the pipe can be bypassed, and is discussed in the 'File System Vulnerabilities' section.

Some readers may be wondering as to why in the world would impersonation exist in an interprocess communication mechanism. Impersonation and IPC, implemented properly, can actually lead to better-designed software architecture. In its conception, it was created so that privileged subsystems and services could temporally lower their own security context while processing the requests of its clients. However, if implemented improperly malicious applications can attain full control of the system.

Impersonation is the foundation that the vulnerability classifications discussed in this paper are based. All references to 'usurping the client's security context' is in reference to the server-end of the pipe impersonating the client's security context.

2.2 Named Pipe Client Security

When a client connects to the server-end of a named pipe it may unknowingly be giving the server-end of the pipe the ability to perform any task on their behalf--by impersonating their security context. Clients can control their impersonation "level" by specifying specific flags in their CreateFile call (which is used for connecting the client to the server pipe), however is seldom performed. This process is formally known as setting the security quality of service. So the question on most readers mind is, "What if the server isn't started, or has crashed?" Well, the answer is very simple. Their security context is up for grabs. Any security context (interactive user, service, etc.) may create the pipe that the client is looking for and assume their security context. This vulnerability classification is known as Superfluous Pipe Connectivity. The security quality of service (QoS) allows for several different types of impersonation: SecurityAnonymous, SecurityIdentification, SecurityImpersonation, and SecurityDelegation.

SecurityAnonymous is an interesting security QoS because of the way in which it makes the impersonation APIs behave. If this is specified, the server's call to ImpersonateNamedPipeClient may fail. However, if the server doesn't check the return value of ImpersonateNamedPipeClient, the code that would normally have been executed under the security context of the client will be executed under the security context of the named pipe server. Typically, servers impersonate the security context of the client to lower their own security context so that access checks are performed on the client's behalf. This type of vulnerability obviously has great consequences.

SecurityIdentification allows for the server to identify the client.

SecurityImpersonation is the default for all local and remote interprocess communication. If the security QoS field is omitted, this impersonation level is used by default.

SecurityDelegation is a new security QoS type that was created with the release of Windows 2000. This allows for remote servers to impersonate their client's security context on remote systems. For example, if a connection to a pipe on a remote server is established with the SecurityDelegation QoS specified, that remote server can communicate with other remote servers using the client's security context.

2.3 Named Pipe File System

Named pipes are a service provided to user-mode applications by the NT kernel. More specifically, the named pipe file system driver (NPFS) implements this functionality. The kernel does, however, provide executive support for NPFS. For example, the native NtCreateNamedPipeFile function is exposed via the kernel's system service dispatch table. All other pipe related functions are typically internally invoked via the native NtFsControlFile function.

As stated supra, named pipes are implemented as a file system, just like NTFS or FAT. That is why the CreateFile, ReadFile, and WriteFile functions are used for connecting, reading, and writing data, respectively. NPFS does not support directories, and the security settings (security descriptors) of pipes are not persisted between reboots. All pipes in NPFS are deleted when their reference count reaches zero.

All named pipe users are created equal. That is, they all use the same named pipe namespace for creating and connecting to named pipes. Thus, the SYSTEM user can connect to a pipe created by the GUEST user, using the same pipe name. The reverse is also true: the GUEST user can connect to pipes the SYSTEM user created. Having the same pipe namespace for all security contexts is problematic and subject to race conditions that can lead to privilege escalation. This is conceptually similar to the Unix "/tmp" directory symlink vulnerabilities.

2.4 Named Pipe Server Instances

When a client connects to the server-end of a named pipe, the server-end of the pipe is now dedicated to the client. No more connections will be allowed to that server pipe instance until the client has disconnected. This is where the concept of pipe instances comes into play. A multithreaded pipe server will typically create a number of threads that are dedicated to solely communicating with one pipe client. Usually each thread will create one instance of the specified server pipe name. Therefore, for example, if a server creates ten threads and each thread creates one instance of the server pipe, the server can concurrently handle a total of ten client connections.

One interesting aspect about this is that prior to Windows 2000 SP2, pipe servers could not determine which instance is created when the pipe is created. Unknown to most, this situation creates a security hole. If a pipe server cannot determine what pipe instance it is creating, how does it know whether or not it has obtained the first instance of the pipe? The problem is that another security context on the machine (Terminal Server, Fast User Switching, NT services, etc.) may have created that pipe first with the intention of usurping the security context of the clients of the pipe. This situation is known as a Named Pipe Instance Creation Race Condition vulnerability. This topic will be discussed in a later section.

2.5 Named Pipe Server Security

Like all securable objects, named pipes have security attributes associated to them. This association is established when a named pipe is created with the CreateNamedPipe function. If the security attributes parameter is null when the CreateNamedPipe function is invoked, a default security descriptor is associated to the pipe. This security descriptor essentially dictates that that the creator of the pipe and the SYSTEM trustee has full access to the pipe, and the EVERYONE trustee has read access to the pipe.

Named pipes support the majority of the same access rights that can be assigned to a file (Remember, named pipes are a file system). The most interesting access right assignable to a named pipe is FILE_CREATE_PIPE_INSTANCE. This right dictates what trustees in the DACL can create an instance of the named pipe in question. Thus, it allows for multiple security contexts to create an instance of the pipe in question. The use of this access right is rarely a good idea, however a typical list of pipes on a Windows PC yields several that do just that.

3 Named Pipe Vulnerabilities

This section will focus on defining the criteria that named pipe related vulnerabilities consist of. Specifically, the vulnerability classifications of Superfluous Pipe Connectivity and Named Pipe Instance Creation Race Condition will be explored. Additionally, discovering named pipe file system related vulnerabilities will be explored, as well as new discoveries in manipulating the \DosDevices object directory that also lead to privilege escalation.

3.1 Named Pipe Instance Creation Race Condition

Named pipe instance creation race conditions occur when an application creates an instance of a pipe without any regard to whether or not that instance existed previously. The problem with this ignorance is that a process running in an arbitrary security context may have already created the named pipe prior to the authentic application created its instance of the pipe. Because internally instances are in a FIFO stack, the application that creates the first pipe instance gets the first pipe client. Thus, the first instance owner is given the chance to impersonate the pipe client and perform actions on their behalf. To more fully understand this vulnerability classification, a classic (discovered in 2000 and affects Windows 2000 Gold-SP1) named pipe instance creation race condition vulnerability will be used as a case study: The Service Control Manager Named Pipe Impersonation Vulnerability.

3.1.1 Service Control Manager Vulnerability

The Service Control Manager (SCM) is the central arbiter of all system services for Windows NT based operating systems. When the SCM is instructed to start a service, a named pipe is created that will be used for communication between the SCM and the system service. The pipe that is created is subject to a named pipe instance creation race condition vulnerability; it did not protect its first instance. In fact, prior to this vulnerability, it wasn't even possible to protect its first instance.

The pipe that the SCM creates has the following naming scheme: \\.\pipe\net\NtControlPipeX, where X is an integer that is monotonically incremented for each started service. Obviously, this naming scheme is very predictable. Next, the SCM starts the service's executable that is destined to connect to the pipe it had created. Therefore, under normal circumstances, the service would have connected to its named pipe, and the service would work, as one would expect it to.

However, if an arbitrary process created the pipe prior to the SCM, the SCM would obtain the second instance to the named pipe. Oblivious to this fact, it creates the service that is destined to connect to the first instance of the pipe--the malicious application. The malicious application now has the ability to impersonate the security context of the client, a system service.

3.1.2 Identifying Criteria

Knowing what to look for is essential to identifying named pipe instance creation race condition vulnerabilities. First, the activity that is being performed must generate a named pipe to be created. In the case of the SCM vulnerability, it was instructing the SCM to start a service. Second, the creation of the named pipe must not protect its first instance. In code, this is done by setting the flag 'FILE_FLAG_FIRST_PIPE_INSTANCE' in the dwOpenMode parameter of the CreateNamedPipe function. At runtime, this can be detected by using a specially designed software tool, such as Security Internals' Pipe Security Explorer. Third, a client running in a different security context must at some point in time connect to the pipe that was created.

3.1.3 Conclusion

Another vulnerability that is noticeably similar to the service control manager vulnerability is known as the "Predictable Name Pipes In Telnet" vulnerability. Both vulnerabilities impersonate the SYSTEM security context. The similarities between these two vulnerabilities are anything but surprising. Many named pipes follow similar implementation trends; performing a directory list of the named pipe file system will typically yield this. With the continuing trends of interdependency between system services, it is unlikely that this vulnerability classification will be going away anytime soon.

3.2 Superfluous Pipe Connectivity

Superfluous pipe connectivity vulnerabilities occur when a client application attempts to connect to a server-end of a named pipe, oblivious to whether or not the server process has actually created the named pipe in question. If the server-end of the named pipe is not created, its software hasn't been installed, or the server software has crashed, the client's security context may be usurped by a malicious application that has created the pipe that the client attempts to connect to. What's worse is that some applications effectively poll their connection requests until it succeeds. This effectively makes Windows Terminal Server very insecure if applications that implement such an algorithm are used.

3.2.1 Identifying Criteria

Coaxing an application or service into connecting to a pipe is usually trivial in most situations. For instance, when starting a service, the service will typically connect to another service's named pipe. A monitoring tool, such as Security Internals' Pipe Security Explorer, will yield all the named pipes that an application or service connects to. It is probable that some pipes will not exist. If a client application attempts to connect to a server pipe without verifying that the server is running and has created the corresponding named pipe, it's possible that at some point in time the client's security context may be usurped by a malicious application.

The identifying criterion for this type of vulnerability is that the server pipe must be nonexistent, and a process in a different security context attempts to connect to the nonexistent named pipe. An interesting nuance to this criterion is that if the server pipe is created at anytime other than boot time, it may be subject to a named pipe instance creation race condition, in which this requirement may be bypassed. In any event, if a malicious application creates the pipe and obtains the first instance of the pipe, the clients of the pipe will connect to the malicious server and their security context may be usurped.

3.3 File System Vulnerabilities

Here we will explore getting services and other software to do things that they weren't designed to do, with file system related named pipe vulnerabilities. But first, an introduction to the concept of file system related vulnerabilities is needed.

The following function opens a file and reads some data from it.

   void main(int argc, char *argv[])
   {
      HANDLE hFile = CreateFile(argv[1], 
         GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
   
      if (hFile != INVALID_HANDLE_VALUE)
      {
         UCHAR buf[100];
         DWORD cb;

         // Do Something
         ReadFile(hFile, buf, sizeof(buf), &cb, 0);

         CloseHandle(hFile);
      }
   }

The preceding function takes an argument that defines the file to open. Subsequently, it reads 100 bytes from the file. What's the vulnerability, you ask? That is the exact same code that would be used by a pipe client to connect to a pipe server! There is no distinction in that regard. If a pipe name was passed in argv[1], the code would have connected to a pipe server and attempt to read 100 bytes--and the pipe server would have the ability to usurp the security context of the client of the pipe.

The more technically inclined readers may be now accurately thinking, "But in order to impersonate a client, the server must read data from the client. That code does not write data to the server to read." That is correct. However, there is a most devious way around this requirement. As the MSDN documentation states:

   When the named pipe, RPC, or DDE connection is remote, the 
   flags passed to CreateFile to set the impersonation level are 
   ignored. 

Essentially, the impersonation flags are ignored if a UNC path is specified. But what isn't mentioned is that the requirement to read data from the pipe, for the ability to impersonate the client's security context, is ignored as well. Thus, if a full UNC path is passed as the argv[1] parameter to the preceding function, it will connect to a pipe server, and the pipe server will have the ability to impersonate the client's security context.

To give an example of real world scenarios in which these bugs will exist, an example will be used. This example is not subject to this vulnerability in particular due to specific mitigating factors, however, it illustrates the process of discovering these issues.

The RunAs service was introduced with the release of Windows 2000. It allows for interactive users to launch a process in a specific security context. The command line utility for the RunAs service takes several parameters; we're interested solely in the file path parameter.

The following session with the RunAs utility presents how one would normally use it:

   C:\>runas /user:bwatts cmd.exe
   Enter password for bwatts:
   Attempting to start "cmd.exe" as user "bwatts"...

As you can see, it takes the principal's name (security context) to start the process in, the executable to launch, and the password for the principal. Now, if we wish to manipulate this maliciously, we'd do the following:


   1) Create the server-end of a named pipe for which we want the RunAs service to connect to.
   2) Instruct the RunAs service to connect to our pipe.

Instructing the RunAs service to connect to our pipe is a rather creative process--and that is exactly what it required to find these vulnerabilities. Here is a malicious scenario with the RunAs utility.

   C:\>runas /user:bwatts \\bwatts\pipe\xyz
   Enter password for bwatts:
   Attempting to start "\\bwatts\pipe\xyz" as user "bwatts"...
   RUNAS ERROR:  Unable to run - \\bwatts\pipe\xyz
   193:

The RunAs service connects to our malicious pipe and we have the capability to impersonate the RunAs service. However, because the RunAs service impersonated the client--us--to begin with, it was our own security context that connected to our malicious pipe and thus it is not subject to this type of security flaw.

Vulnerabilities of this nature have been discovered and will be released shortly after the publication of this paper. Discovering these vulnerabilities typically consists of monitoring the data sources of services. If a data source presents a file system path, replace it with a UNC path to a named pipe server instead. Some common data sources include APIs that communicate with the service in question, and the system registry.

3.4 DosDevices Object Directory Vulnerabilities

The \DosDevices (\?? on Windows NT4 / 2000) object directory is accessible in the Windows NT executive object namespace. This namespace can be enumerated by using a tool such as Sysinternals' WinObj. This section will focus on escalating privileges by means of manipulating this directory. This directory is, by default, secured against arbitrary manipulation because of the directory's DACL settings. However, the CSRSS (Win32 client/server runtime) subsystem provides the means of manipulating this directory to interactive users via the DefineDosDevice function.

The DefineDosDevice function does, however, have its limitations. Administrators of the PC have the ability to use the DefineDosDevice function to perform virtually any changes they wish. Interactive users can use this function to create symbolic links that have yet to be created. The MSDN states the security restrictions in the documentation for DefineDosDevice as follows:

   Drive letters and device names defined at system boot time are 
   protected from redefinition and deletion unless the user is an administrator.

The \DosDevices object directory is where all well-known device names are symbolically linked to their corresponding device driver. Whenever a path with the prefix \\.\ is encountered by the operating system, the path is internally looked up and translated in this directory. For example, the symbolic link of \\.\PIPE is present in this directory. The \\.\PIPE symbolic link is targeted at the device \Device\NamedPipe. Another very familiar symbolic link is drive letters. Drive letters are also present in this directory. For example, the C drive is present with a symbolic link object of C:, which typically targets the device \Device\HarddiskVolume1.

The vulnerabilities discussed in this section apply solely to non-terminal services enables PCs. Terminal services gives all sessions their own private \DosDevices object directory. Windows XP is built with terminal services technology.

3.4.1 Drive Letter Redirection

As stated supra, drive letters are simply symbolic links to their corresponding device drivers. This is why drive letters can be mapped to remote systems; the symbolic link for the drive letter targets the network file system redirector. This particular vulnerability classification takes advantage of the fact that the DefineDosDevice function allows for a user to create a new symbolic link and set its target to a named pipe server, instead of a file system. Additionally, it also takes advantage of the knowledge learned in the File System Vulnerabilities section that the requirement of reading data from a pipe in order to impersonate their security context can be bypassed by using a full UNC pipe path, instead of a local pipe path.

If a symbolic link is created in the \DosDevices directory for an arbitrary drive letter and targets the device \Device\LanmanRedirector\COMPUTERNAME\pipe\PIPENAME, then all references to newly mapped drive letter will result in the specified pipe server having the ability to impersonate the client's security context.

Because the \DosDevices directory is global, in non-terminal services enabled operating systems, any system service that accesses the corresponding drive letter will give the pipe server the ability to impersonate their security context. Vulnerabilities of this nature have been discovered and will be released shortly after the publication of this paper.

3.4.2 Device Connection Redirection

Drivers that are started at any time other than boot time are subject to a race condition to the creation of the device's symbolic link object in the \DosDevices object directory.

When a typical driver is started, it creates a symbolic link object in the \DosDevices object directory that targets the driver's device object in the \Device object directory. The problem with this is that any drivers that are not started at boot time are subject to a race condition to the creation of their symbolic link in the \DosDevices object directory. Now, take for example a system service that depends on a specific driver in order to function. In order for the service to detect whether or not the driver has been started, it will typically open a handle to the device object by means of accessing the symbolic link the driver creates in the \DosDevices directory when it is started. If the symbolic link object is nonexistent, the service will typically start the driver and attempt to open a handle again.

There is an obvious race condition vulnerability here. If the symbolic link is created by an arbitrary user and targets a pipe server, then when the service is started and attempts to communicate with the device driver it depends on, it will inadvertently be giving the target of the device's symbolic link--a pipe server--the ability to impersonate its security context.

In order to create a malicious symbolic link in the \DosDevices directory the name of the symbolic link must be the name of the device in question. The target of the symbolic link object must also be the full UNC path of the named pipe server. An example full UNC pipe path could be: \Device\LanmanRedirector\COMPUTERNAME\pipe\PIPENAME, where COMPUTERNAME is the name of the local computer and PIPENAME is the name of the pipe server. See Security Internals Research for a tool that creates such symbolic links.

4 Named Pipe Exploitation

This section will focus on the knowledge one must have in order to exploit a named pipe related vulnerability. After the client has connected to the pipe server and the server has called ImpersonateNamedPipeClient, the calling thread is granted an 'impersonation' token of the pipe client. This impersonation token gives the thread the ability to do virtually anything it desires to do so, as long as it doesn't violate NT security. However, there are some restrictions that are placed on an impersonation token. For example, an impersonation token cannot be used in the CreateProcessAsUser function. As the function's name implies, it is responsible for creating a process with a specified 'primary' access token. Thus, before this function may be used, the impersonation token must be converted into a primary token. This can be done using the DuplicateTokenEx function. After the primary token has been acquired, simply call CreateProcessAsUser, as one would normally do. If the call is successful, the specified process will be started. The following code illustrates how to spawn a process based on the client of a pipe's security context.

Note, however, that there are some limitations with the following code that will not be covered here. The code does not enable any privileges for itself or the process it is creating. Additionally, at times the privilege to assign a primary token to a new process will be disabled. This more advanced exploitation will be covered in a future paper.

   BOOL 
   CreateProcessFromThreadImpersonationToken(
      LPCSTR lpApplicationName, 
      LPSTR lpCommandLine
      )
   {
      HANDLE hPrimaryToken, hImpersonationToken;
      BOOL bSuccess = FALSE; // Assume failure
      STARTUPINFO si;
      PROCESS_INFORMATION pi;

      // Get a handle to the current thread's impersonation token
      bSuccess = OpenThreadToken(
         GetCurrentThread(),  // Thread handle
         TOKEN_ALL_ACCESS,    // Desired access
         FALSE,               // Open As Process
         &hImpersonationToken // Impersonation Handle
         );

      if (bSuccess)
      {
         // Create a primary token from the impersonation token
         bSuccess = DuplicateTokenEx(
            hImpersonationToken,    // Imperonation Token
            0,                      // Desired Access
            0,                      // No new security attributes
            SecurityImpersonation,  // Impersonation QoS level
            TokenPrimary,           // Create a primary token
            &hPrimaryToken          // Output Handle
            );

         if (bSuccess)
         {
            ZeroMemory(&si, sizeof(si));
            ZeroMemory(&pi, sizeof(pi));

            si.cb = sizeof(si);

            bSuccess = CreateProcessAsUser(
               hPrimaryToken,       // Created primary token
               lpApplicationName,   // Target executable
               lpCommandLine,       // Parameters to the executable
               0,                   // Process security attributes
               0,                   // thread security attributes
               FALSE,               // Inherit handles
               CREATE_NEW_CONSOLE | // Creation flags
               CREATE_NEW_PROCESS_GROUP, 
               0,                   // Environment
               0,                   // Current directory
               &si,                 // Startup information
               &pi                  // Process information
               ); 
         
            if (bSuccess)
            {
               CloseHandle(pi.hThread);
               CloseHandle(pi.hProcess);
            }

            CloseHandle(hPrimaryToken);
         }

         CloseHandle(hImpersonationToken);
      }

      return bSuccess;
   }

After the pipe server has successfully impersonated the client's security context, via ImpersonateNamedPipeClient, it would call the preceding CreateProcessFromThreadImpersonationToken function with the arguments of the target executable and the parameters to it.