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
Pipe PermissionsAll 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
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
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
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
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
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
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
,
Impersonation in PipesA 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:
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
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
BOOL SecureOpenPipe(void)
{
HANDLE hPipe;
hPipe = CreateFile("\\.\pipe\MyPipe", GENERIC_ALL, 0, NULL,
OPEN_EXISTING,
SECURITY_SQOS_PRESENTSECURITY_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
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
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
ServersA 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
Fortunately, the introduction of the SeImpersonatePrivilege has gone a long way toward eliminating this type of impersonation vulnerability. However, it's still a
|