Pipes


Pipes are a connection-oriented IPC mechanism that can be used to communicate data between two or more processes. There are two types of pipes: anonymous pipes and named pipes. An anonymous pipe is a unidirectional pipe that transfers data locally between two processes. Because anonymous pipes have no names, they can't be referred to by arbitrary processes. Generally, this means only the creating process can make use of an anonymous pipe, unless the pipe handle is duplicated and passed to another process. Usually, anonymous pipes are used for communication between threads in a single process or between a parent and child process. Named pipes, conversely, can be referred to by arbitrary processes and be accessed remotely, depending on the access rights associated with the pipe when it's created. Because anonymous pipes are local and have only a few of the problems associated with named pipes, the following sections focus on named pipes.

Pipe Permissions

All pipes are securable objects, so they have specific access rights associated with their DACL entries. Table 12-2 summarizes the pipe permissions listed in the MSDN.

Table 12-2. Pipe Access Rights

Access Right

Meaning

PIPE_ACCESS_DUPLEX

Allows the caller to read and write to the pipe and gives them SYNCHRONIZE access.

PIPE_ACCESS_INBOUND

Allows the caller to read from the pipe and gives them SYNCHRONIZE access.

PIPE_ACCESS_OUTBOUND

Allows the caller to write to the pipe and gives them SYNCHRONIZE access.


As you can see, access rights for pipes are simpler than most other objects, such as files, so developers are less likely to inadvertently set incorrect permissions on a pipe. Still, vulnerabilities can result when access permissions are applied haphazardly. It might be possible for rogue processes to have read or write access to a pipe when they shouldn't, which could lead to unauthorized interaction with a pipe server. This problem can even occur with anonymous pipes because attackers can enumerate the process handle table and duplicate a handle to a pipe with weak access permissions.

Named Pipes

Named pipes are a multidirectional IPC mechanism for transferring data between unrelated processes on the same machine or different machines across a network. A named pipe can be uni- or bi-directional, depending on how it's created. Pipes work in a client/server architecture; pipe communications are made by having one pipe server and one or more clients. So a number of clients can be connected to a pipe simultaneously, but there can be only one server.

Pipe Creation

Pipes can be created by using CreateFile() or CreateNamedPipe(). You have already examined the semantics for creating and accessing pipes with CreateFile(), so you don't need to review this function again. The prototype for CreateNamedPipe() is shown as follows:

HANDLE CreateNamedPipe(LPCSTR lpName, DWORD dwOpenMode,            DWORD dwPipeMode, DWORD nMaxInstances,            DWORD nOutBufferSize, DWORD nInBufferSize,            DWORD nDefaultTimeout,            LPSECURITY_ATTRIBUTES lpSecurityAttributes)


As you can see, the CreateNamedPipe() function allows more control over certain characteristics of the named pipe than CreateFile() does. In addition to the regular attributes, developers can optionally specify an input and output buffer size for the pipe, although they are only advisory values the system isn't required to honor. The dwOpenMode value specifies which access rights the pipe should be opened with (PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, or PIPE_ACCESS_OUTBOUND). In addition, one or more flags can be specified:

  • FILE_FLAG_FIRST_PIPE_INSTANCE This flag causes the function to fail if the pipe already exists.

  • FILE_FLAG_WRITE_THROUGH On certain types of pipes where the client and server processes are on different machines, this flag causes the client to not return until all data has been written to the pipe successfully.

  • FILE_FLAG_OVERLAPPED Overlapped I/O is enabled; a process doesn't need to wait for operations on the pipe to finish to continue running.

The dwPipeMode value specifies what type of pipe should be created. A pipe can be PIPE_TYPE_BYTE, which causes pipe data to be treated as a single-byte stream, or PIPE_TYPE_MESSAGE, which causes data to be treated as a series of separate messages. The nDefaultTimeout value specifies a timeout value in milliseconds for an operation to be performed on the pipe, and finally, lpSecurityAttributes specifies a security descriptor for the pipe.

Clients that just want to send a single message to a pipe (of type PIPE_TYPE_MESSAGE) don't have to go through the whole process of opening it and closing it. Instead, they can use the CallNamedPipe() function, which has the following prototype:

BOOL CallNamedPipe(LPCSTR lpNamedPipe, LPVOID lpInBuffer,          DWORD nBufferSize, LPVOID lpOutBuffer, DWORD          nOutBufferSize, LPDWORD lpBytesRead, DWORD nTimeOut)


This function opens the pipe specified by lpNamedPipe, writes a single message, reads a single response, and then closes the pipe. It's useful for clients that just need to perform a single pipe transaction.

Impersonation in Pipes

A named pipe server can impersonate the credentials of client servers that connect to it. This impersonation is achieved by using the ImpersonateNamedPipeClient() function, which has the following prototype:

BOOL ImpersonateNamedPipeClient(HANDLE hNamedPipe)


As you can see, this function simply takes a handle to a named pipe and then returns a value of TRUE or FALSE, depending on whether impersonation is successful. If it's successful, the thread impersonates the context associated with the last message read from the pipe. The last message read requirement gets a bit sticky. If the connection is local, impersonation always fails unless data has first been read from and written to the pipe. However, if the client is remote, the impersonation might succeed because messages are transferred in establishing the connection. In either case, it's best to make sure the pipe is read from before impersonation is attempted.

Next, you need to examine the use of impersonation levels. In the context of named pipes, clients can restrict the degree to which a server can impersonate them by specifying an impersonation level in the call to CreateFile(). Specifically, the impersonation level can be indicated in the dwFlagsAndAttributes parameter. Here's the CreateFile() function prototype again:

HANDLE CreateFile(LPCSTR lpFileName, DWORD dwDesiredAccess,            DWORD dwSharedMode,            LPSECURITY_ATTRIBUTES lpSecurityAttributes,            DWORD dwCreationDisposition,            DWORD dwFlagsAndAttributes,            HANDLE hTemplateFile)


By including the SECURITY_SQOS_PRESENT flag in the dwFlagsAndAttributes parameter, you can specify the following impersonation flags:

  • SECURITY_ANONYMOUS This flag enforces the SecurityAnonymous impersonation level for the object being opened.

  • SECURITY_IDENTIFICATION This flag enforces the SecurityIdentification impersonation level for the object being opened.

  • SECURITY_IMPERSONATION This flag enforces the SecurityImpersonation impersonation level for the object being opened.

  • SECURITY_DELEGATION This flag enforces the SecurityDelegation impersonation level for the object being opened.

  • SECURITY_EFFECTIVE_ONLY This flag causes any changes made via AdjustToken*() functions to be ignored.

  • SECURITY_CONTEXT_TRACKING The security tracking mode is dynamic.

Clients can protect their credentials from malicious servers by using these flags, so you should always be on the lookout for instances in which a client is overly permissive in the impersonation it allows. You also need to pay close attention to common oversights when applying these protections. Try to spot the bug in the following code.

BOOL SecureOpenPipe(void) {     HANDLE hPipe;     hPipe = CreateFile("\\\\.\\pipe\\MyPipe", GENERIC_ALL, 0, NULL,         OPEN_EXISTING, SECURITY_IDENTIFICATION, NULL);     if(hPipe == INVALID_HANDLE_VALUE)         Return FALSE;     ... do pipe stuff ... }


Did you see it? The developers are trying to protect the client from connecting to a malicious server by enforcing the SECURITY_IDENTIFICATION impersonation level. It's a great idea, but poor execution. They forgot to use the SECURITY_SQOS_PRESENT flag, so the SECURITY_IDENTIFICATION flag is completely ignored! A correct implementation would look like this:

BOOL SecureOpenPipe(void) {     HANDLE hPipe;     hPipe = CreateFile("\\\\.\\pipe\\MyPipe", GENERIC_ALL, 0, NULL,         OPEN_EXISTING,         SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION, NULL);     if(hPipe == INVALID_HANDLE_VALUE)         Return FALSE;     ... do pipe stuff ... }


It is also important to audit how servers might use impersonation. In "Impersonation Issues" (MSDN Code Secure, March 2003; http://msdn.microsoft.com/library/en-us/dncode/html/secure03132003.asp), Michael Howard points out the dangers of not checking return values of an impersonation function. Say a server accepts a connection from a client and then wants to access an object on the client's behalf. To do this, it impersonates the user and then proceeds to access the object, as shown in this example:

BOOL ProcessRequest(HANDLE hPipe) {     BOOL rc;     DWORD bytes;     unsigned char buffer[BUFSIZ], fname[BUFSIZ];     for(;;)     {         rc = ReadFile(hPipe, buffer, BUFSIZ, &bytes, NULL);         if(rc == FALSE)             break;         if(bytes <= 0)             break;         switch(buffer[0])         {             case REQUEST_FILE:                 extract_filename(buffer, bytes, fname);                 ImpersonateNamedPipeClient(hPipe);                 write_file_to_pipe(hPipe, fname);                 RevertToSelf();                 break;             ... other request types ...         }     }     ... more stuff here ... }


This code is from a named pipe server that can receive a number of requests, one of which is for reading certain files. The code fails to check the return value of the ImpersonateNamedPipeClient() function, however. If this function fails, the application's privileges and access rights are unchanged from its original state. Therefore, a file is accessed with the original permissions of the server process instead of the connecting client's.

You might be wondering "But why would impersonation functions fail? Can a malicious client prompt that?" Yes, it can. You just learned that when auditing clients, you want to look for the presence or absence of enforcing impersonation levels on the server. A malicious client could also use these levels to prohibit the server from impersonating the client. Even something as simple as failing to read from the pipe first may cause the impersonation call to fail. This failure could result in the object being accessed at a higher privilege than intended.

Pipe Squatting

As with many other types of objects, named pipes existing in the object namespace introduces the possibility for name-squatting vulnerabilities. Developers must be careful in deciding how applications create and access named pipes. When auditing an application, you need to look at this issue from both sides of the fence: the implications for servers that are vulnerable to name squatting and the implications for clients that are vulnerable to name squatting.

Servers

A server can be vulnerable to name squatting if it uses a predictable pipe name and fails to check whether the pipe has already been created. A server can also be vulnerable to name squatting if it creates a pool of pipes and uses ConnectNamedPipe() to service multiple connections. A pool of pipes is established by creating and connecting multiple instances of the same pipe and specifying the same value for nMaxInstances on each call to CreateNamedPipe(). Depending on the timing of pipe creation and connection, attackers might be able to squat on a pipe and impersonate the server.

When creating a single-instance pipe using CreateFile(), a squatting vulnerability can occur in much the same way it does with files: The server neglects to use the CREATE_NEW flag in its dwCreationDisposition parameter. When CreateNamedPipe() is used for a single instance, the problem happens when the dwOpenMode parameter doesn't contain FILE_FLAG_FIRST_PIPE_INSTANCE (available only in Windows 2000 SP2 and later). Here's an example of a vulnerable call:

BOOL HandlePipe(SECURITY_DESCRIPTOR *psd) {     HANDLE hPipe;     hPipe = CreateNamedPipe("\\\\.\\pipe\\MyPipe",         PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE,         PIPE_UNLIMITED_INSTANCES, 1024, 1024,         NMPWAIT_USE_DEFAULT_WAIT, psd);     if(hPipe == INVALID_HANDLE_VALUE        || ConnectNamedPipe(hPipe, NULL)) {         CloseHandle(hPipe);         return FALSE;     }     ... do stuff with the pipe ...     DisconnectNamedPipe(); }


This server fails to specify FILE_FLAG_FIRST_PIPE_INSTANCE or limit the number of connections. Therefore, attackers can create and connect to a pipe named "MyPipe" before this application. Because attackers start listening on the pipe first, the client connects to them first. Depending on timing and the number of instances allowed, the real server might receive an error or have a valid pipe handle that's last in the connection queue. If the server creates a pipe successfully and is the last thread in the connection, it can just continue along happily. It might even perform sensitive operations based on the assumption that the pipe is valid.

Clients

Clients are actually more susceptible to name squatting with named pipes because they might unintentionally connect to a malicious pipe server. Guardent Technologies disclosed this type of vulnerability in August 2000 (www.securityfocus.com/advisories/2472). The Windows 2000 Service Control Manager (SCM) uses a predictable named pipe for communication with services. However, the SCM didn't check for preexisting pipes when starting a service. This meant attackers could simply create the pipe and start any service that could be started by a normal user (the ClipBook service, for example). The target service would then connect to the attacker-controlled pipe and the attacker would escalate privilege by impersonating the service account.

Fortunately, the introduction of the SeImpersonatePrivilege has gone a long way toward eliminating this type of impersonation vulnerability. However, it's still a viable attack for older systems and for breaking the isolation of restricted service accounts. Even without impersonation, this attack is still a successful denial of service. It also provides a trusted channel into a privileged process, which could expose sensitive data or other potential vulnerabilities.




The Art of Software Security Assessment. Identifying and Preventing Software Vulnerabilities
The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities
ISBN: 0321444426
EAN: 2147483647
Year: 2004
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net