Friday, November 13, 2009

13.9 Impersonation



< BACK  NEXT >

[oR]


13.9
Impersonation



Sections 11.4 and 11.5 deal with named pipes. In all of the code samples in those sections, a named pipe server runs on one machine, and a named pipe client connects to the server and sends it messages. The client can run on the same machine as the server, or it can run on a different machine and connect over the network. You may recall that all through those sections you were warned that if you run the client on a different machine, you should make sure that the logon ID and password used on the server and client machine are the same. The reason for this admonition stems from the way that the security system handles default security descriptors.



The named pipe code in Chapter 11 creates all of its pipes with the following sort of call:





/* Create a named pipe for receiving messages */
ssnpPipe=CreateNamedPipe("\\\\.\\pipe\\ssnp",
PIPE_ACCESS_INBOUND,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
1, 0, 0, 150, 0);



The last parameter in the list is the security-attributes parameter, and it is set to 0. This tells Windows 2000 to create a default security descriptor for the new pipe. The default security descriptor contains a default DACL. If you go back to Section 13.6 and run the access token dumping program in Listing 13.5, you can see what your default DACL contains.



You will notice that your default DACL is specific to you. It allows only you or the Windows 2000 system itself to access the object. This is fine for normal objects like processes, threads, and semaphores, because you are the only one logged into the Windows 2000 machine. However, when this default DACL is applied to the server end of a named pipe, it restricts the users who can connect to the pipe: Only you can connect. Therefore, when you try to connect from another machine, the account that you logon with must have the exact same name and password as the account on the server machine. If the network has a domain controller, then this is guaranteed as long as you use the same account on both machines.



If you want to create a server that any user can connect to, then you need to create a security descriptor with a NULL DACL. As discussed earlier, a NULL DACL is understood by the system to mean "any user can access this object." You would therefore use the following code to create a pipe that any user can access:





InitializeSecurityDescriptor(&sd,
SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL,
FALSE);
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = &sd;

/* Create a named pipe for receiving messages */
ssnpPipe=CreateNamedPipe("\\\\.\\pipe\\ssnp",
PIPE_ACCESS_INBOUND,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
1, 0, 0, 150, &sa);



Try this in Listing 13.3 to prove that it works correctly and that anyone can connect to the resulting pipe.



Letting anyone connect to a named pipe server creates a potential problem, however. Let's say that the goal of the server is to let the user execute certain commands remotely. Let's say that one of those commands is a file-delete command. Once the user connects, the user has the user ID of the server process on the remote machine. If the server is implemented as a Windows 2000 service (see Chapter 12), then the server has SYSTEM privileges, and therefore so does the remote user. The remote user can therefore delete any file on the system.



What you would like instead is for the server to take on the identity of the client user while the user is connected. That way the client user can access only those files that are appropriate to him or her. Windows 2000 provides a function that does this, calledImpersonateNamed PipeClient, as demonstrated in Listing 13.9.





Listing 13.9 Impersonating another user in a named pipe server


// imprecv.cpp

#include <windows.h>
#include <iostream.h>

int main(void)
{
char toDisptxt[80];
HANDLE ssnpPipe;
DWORD NumBytesRead;
char buffer[80];
DWORD bufferLen;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd;

InitializeSecurityDescriptor(&sd,
SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL,
FALSE);
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = &sd;

/* Create a named pipe for receiving messages */
ssnpPipe=CreateNamedPipe("\\\\.\\pipe\\ssnp",
PIPE_ACCESS_INBOUND,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
1, 0, 0, 150, &sa);

/* Check and see if the named pipe was created */
if (ssnpPipe == INVALID_HANDLE_VALUE)
{
cerr << "ERROR: Unable to create a named pipe."
<< endl;
return (1);
}
bufferLen = 80;
GetUserName(buffer, &bufferLen);
cout << "Right now my user is: "
<< buffer
<< endl;
cout << "Waiting for connection..." << endl;

/* Allow a client to connect to the name pipe,
terminate if unsuccessful */
if(!ConnectNamedPipe(ssnpPipe,
(LPOVERLAPPED) NULL))
{
cerr << "ERROR: Unable to connect a pipe "
<< GetLastError() << endl;
CloseHandle(ssnpPipe);
return (1);
}

cout << "Beginning impersonation...\n";

if (!ImpersonateNamedPipeClient(ssnpPipe))
{
cerr << "ERROR: Cannot impersonate, "
<< GetLastError() << endl;
CloseHandle(ssnpPipe);
return(1);
}

bufferLen = 80;
GetUserName(buffer, &bufferLen);
cout << "Now my user is: "
<< buffer << endl;

/* Repeatedly check for messages until
the program is terminated */
while(1)
{
/* Read the message and check to see if read
was successful */
if (!ReadFile(ssnpPipe, toDisptxt,
sizeof(toDisptxt),
&NumBytesRead, (LPOVERLAPPED) NULL))
{
cerr << "ERROR: Unable to read from pipe"
<< endl;
CloseHandle(ssnpPipe);
break;
}
/* Display the Message */
cout << toDisptxt << endl;

} /* while */

cout << "Reverting back to original user.\n";
RevertToSelf();

bufferLen = 80;
GetUserName(buffer, &bufferLen);
cout << "Now my user is: "
<< buffer
<< endl;
}


To test Listing 13.9, follow the same procedure used to test Listing 13.3. Compile and execute Listing 13.9 on one machine. It will display a message that indicates its current user ID. Logon to another machine with a different ID, and use the code in Listing 11.7 to connect to the server. Listing 13.9 will recognize the connection, and then immediately copy the access token of the connected client into its thread. It will display its new identity. When you kill off the client, the server will revert back to its former identity usingRevertToSelf. You should be able to connect to the server using any logon ID.



The call to ImpersonateNamedPipeClient causes the calling thread to take on the access token of the thread connected to the other end of the named pipe specified.























ImpersonateNamed PipeClient


Causes the calling thread to take on the access token of a connected client thread



BOOL ImpersonateNamedPipeClient(
HANDLE namedPipe)

namedPipe

The server end of a named pipe

Returns TRUE on success



The RevertToSelf function causes the calling thread to revert back to the access token of its parent process.

















RevertToSelf


Causes a thread to revert to the access token of its parent process



BOOL RevertToSelf(VOID)

Returns TRUE on success



In a multiple-client server like the ones shown in Section 8.5, each separate client thread can take on a different identity using the same technique. You can create extremely robust and secure servers using impersonation.





< BACK  NEXT >


No comments:

Post a Comment