Impersonation Issues

 

Michael Howard
Secure Windows Initiative

March 27, 2003

Summary: Michael Howard discusses security issues related to impersonation, and shows you how to protect your system against this type of attack using the new Microsoft Windows Server 2003 as an example. (5 printed pages)

I assume you all know what impersonation is; it's the ability for a thread in an application running on Microsoft Windows NT® and later to take on the identity of the caller. If you need a little reminder, go to https://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/security_6d9q.asp. Impersonation is a staple capability of Windows NT and later, and is not available in versions of Microsoft Windows® based on the Windows 95 code base.

It's common for services to use impersonation. For example, a process runs as a service account, such as Local System (also called SYSTEM), Network Service, or Local Service, and it accepts a connection from a client, impersonates the client, accesses resources as the impersonated account, and then reverts to the process identity once the resource interaction is complete. When the resource is touched by the service, an access check is performed as the impersonated account.

So here's the issue—what happens if the impersonation function fails? What account is the thread operating under? Imagine a service is running as a privileged account; for example, the all-powerful SYSTEM account. Then a user, Blake, connects to the service using a named pipe, and the service impersonates Blake and attempts to write data to a file that has the following ACL:

  • Administrators (Full Control)
  • Users (Read)

The code that does this looks a little like the code below, which was the Spot the Security Flaw snippet from a previous article.

bool WritePipeDataToFile(HANDLE hPipe) {
   bool fDataWritten = false;

   ImpersonateNamedPipeClient(hPipe);

   HANDLE hFile = CreateFile(...);
   if (hFile != INVALID_HANDLE_VALUE) {
      BYTE buff[1024];
      DWORD cbRead = 0;
      if (ReadFile(hPipe,
             buff,
             sizeof(buff),
             &cbRead,
             NULL)) {

         DWORD cbWritten = 0;
         if (WriteFile(hFile,
                 buff,
                 cbRead,
                 &cbWritten,
                 NULL)) {
            if (cbRead == cbWritten)
               fDataWritten = true;
         }
      }

      if (hFile) CloseHandle(hFile);
   }

   RevertToSelf();

   return fDataWritten;
}

A good number of you spotted the error in this code. What happens if the call to ImpersonateNamedPipeClient fails? The answer is that the thread is still operating as the process identity, SYSTEM, which is a member of the administrators group. This means the service can write to the file even though the ACL only allows administrators to do so. Oops! The fix is very simple. Check the return value of any impersonation function, including SetThreadToken, and if it fails, then return an error. Something as simple as the following code may suffice:

if (ImpersonateNamedPipeClient(hPipe) == 0)
   return GetLastError();

Note we have updated, or are in the process of updating, much of the online and printed Microsoft Visual Studio® documentation to reflect the security ramifications of not checking the return value of impersonation functions.

When Can Impersonation Functions Fail?

You're probably asking yourself, "When can an impersonation function fail?" The answer just got a whole lot more complex with Microsoft Windows Server 2003. But first, let's get a little background. Imagine a scenario where you install a rogue application on a computer that opens a named pipe. Then you convince an administrator to connect to that pipe. When a user connects to the pipe you impersonate the user, in this case the admin, and potentially your rogue code is running as the admin for that computer! Of course this exploit violates the first Immutable Law of Security, which is "If a bad guy can persuade you to run his program on your computer, it's not your computer anymore." More information about these laws can be found at https://www.microsoft.com/technet/treeview/default.asp?url=/technet/columns/security/essays/10imlaws.asp. However, we decided to make an architectural change in Windows Server 2003 by adding a new privilege—the ability to impersonate in Release Candidate 2 and later (https://www.microsoft.com/windows.netserver/preview/default.mspx). Think of it as an in-depth defense measure. That is to say, we added it "just in case."

In short, unless you have this privilege, you cannot impersonate another account. This kind of attack is no longer an issue in Windows Server 2003, as the ability to impersonate is only granted to accounts with the Service SID (Network Service, Local Service, and Local System) and Administrators.

If an account is not granted this privilege and code running as that account attempts to call an impersonation function, the code will not fail, but rather return an identify token.

For impersonation to work, one or more of the following must be true:

  1. The requested impersonation level is less than impersonate (that is anonymous or identify level, which should always succeed).
  2. The process token has the SeImpersonatePrivilege privilege.
  3. A process (or another process in the same logon session) created the token by calling LogonUser with explicit credentials. Let's be honest, if you know an account's password, then you can impersonate them too.
  4. The token is for the expected user. In other words, the code is attempting to impersonate the process identity.
  5. Component Object Model (COM) servers that are started by the COM infrastructure and that are configured to run under a specific account also have the Service group added to their access tokens. As a result, these services get this user when they are started. This does not apply if the COM server is marked as Activate as Activator.

The intention is to back-port this privilege to prior versions of Windows if customers feel it important to do so.

Applications may fail if the code attempts to impersonate an account when none of the conditions above are met. The issue can be rectified by granting the appropriate account the impersonate privilege. But remember, granting the privilege to everyone means the privilege cannot protect against the attack scenario noted earlier.

If your application performs impersonation, then you should test it on Windows Server 2003 RC2 or later, and make sure that all impersonation function return values are checked for failure.

Spot the Security Flaw

Many people got last months security bug. The problem was this line:

char *buff = new char(250);

This line creates a one-byte buffer and sets it to a default value of 250. It should read:

char *buff = new char[250];

This change allocates 250 bytes, as expected.

Now, to this month's bug…

int ConcatString(char *buf1, char *buf2, 
          size_t len1, size_t len2){
    char buf[256];

    if((len1 + len2) > 256) return -1;

    memcpy(buf, buf1, len1); 
    memcpy(buf + len1, buf2, len2);

    // do stuff with buf

    return 0;
}

Michael Howard is a Senior Security Program Manager in the Secure Windows Initiative group at Microsoft and is the coauthor of Writing Secure Code, now in its second edition, and the main author of Designing Secure Web-based Applications for Windows 2000. His main focus in life is making sure people design, build, test, and document nothing short of a secure system. His favorite line is "One person's feature is another's exploit."