4

My windows application may require administrative privileges for some of it's sections. For those cases, I'd like to ask the user for an administrator credentials, and use the following code to impersonate the administrator:

BOOL impersonate(LPTSTR lpszUsername, LPTSTR lpszDomain, LPTSTR lpszPassword) {

    BOOL ret = LogonUser(lpszUsername,
                         lpszDomain,
                         lpszPassword,
                         LOGON32_LOGON_INTERACTIVE,
                         LOGON32_PROVIDER_DEFAULT,
                         &hToken);
    if (ret != TRUE) return FALSE;

    OutputDebugString (L"step 1");

    ret = ImpersonateLoggedOnUser(hToken);
    if (ret != TRUE) return FALSE;

    OutputDebugString(L"step 2");

    return IsUserAdmin()
}

where the function IsUserAdmin() has been taken from MSDN , and goes as follow:

BOOL IsUserAdmin(VOID) {
    BOOL b;
    SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
    PSID AdministratorsGroup; 
    b = AllocateAndInitializeSid( &NtAuthority,
                                  2,
                                  SECURITY_BUILTIN_DOMAIN_RID,
                                  DOMAIN_ALIAS_RID_ADMINS,
                                  0, 0, 0, 0, 0, 0,
                                  &AdministratorsGroup); 
    if (b)  {
        if (!CheckTokenMembership(NULL, AdministratorsGroup, &b)) {
            b = FALSE;
        }
        FreeSid(AdministratorsGroup); 
    }

    return(b);
}

Scenario 1:
Running the application from an administrator account.

  1. calling IsUserAdmin() => returns TRUE (good)

  2. calling impersonate ("non-admin-user" , "domain", "password" ) => returns FALSE (good!)

Scenario 2:
Running the application from a non-administrator account, using runas.exe from an administrator account.

  1. calling IsUserAdmin() => returns FALSE (good)

  2. calling impersonate ("administrator" , "domain", "password" ) => returns FALSE (not good!)

Scenario 3:
Running the application directly from a non-administrator account.

  1. calling IsUserAdmin() => returns FALSE (good)

  2. calling impersonate ("administrator" , "domain", "password" ) => returns FALSE (not good!)

All scenarios print both step 1 and step 2.

As far as I can tell, the above should have assure impersonation, given the a legitimate credentials. what am I missing here?

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 1
    You say `impersonate()` returns FALSE, but you did not indicate where exactly it is failing. `impersonate()` has 4 failure points that can cause it to return FALSE - if `LogonUser()` fails, if `ImpersonateLoggedOnUser()` fails, if `AllocateAndInitializeSid()` fails, or if `CheckTokenMembership()` fails. You need to debug your code and find out exactly what is actually failing. – Remy Lebeau Dec 09 '13 at 23:37

2 Answers2

3

You really should not be programmably checking for admin rights before doing a task that requires admin rights. Just attempt the task unconditionally and let the API tell you if the task failed due to insufficient rights, and if so then you can decide whether to just fail the task with an error or prompt the user for credentials and try the task again.

If you are trying to play nice with UAC, what you are supposed to do is implement your admin code as a separate process or COM object, then you can run that process/COM in an elevated state when needed without having to elevate your main process. Let the OS prompt the user for admin credentials (and decide how that prompt should look) when the OS needs it, don't do it manually yourself.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2

Remy's answer is spot on, what you're trying to do is not the correct solution for your scenario. However, according to the documentation your code should work as intended. There appears to be two reasons why it doesn't:

  1. It seems that, contrary to the documentation, you cannot impersonate a token obtained via LogonUser at a higher impersonation level than SecurityIdentification without holding SeImpersonatePrivilege. [Tested on Windows 7 SP1 x64.]

  2. The CheckTokenMembership function does not work properly if you pass NULL for the token and the thread's impersonation level is SecurityIdentification. [ditto.]

You can work around problem 2 easily enough by explicitly extracting the impersonation token using OpenThreadToken rather than passing NULL as the token, as shown below. Alternately, you could duplicate the primary token using DuplicateToken and pass the duplicated token to CheckTokenMembership. If all you want is to check the contents of the token, that would be a more efficient solution.

I am not aware of any workaround to problem 1, other than launching a subprocess in the new context to do the work on your behalf. Of course, as Remy pointed out, this is what you should be doing anyway.

Here is some code I used in testing, for reference:

#include <Windows.h>

#include <stdio.h>
#include <conio.h>

void fail(wchar_t * err)
{
    DWORD dw = GetLastError();
    printf("%ws: %u\n", err, dw);
    ExitProcess(1);
}

void impersonate(LPTSTR lpszUsername, LPTSTR lpszDomain, LPTSTR lpszPassword) 
{
    HANDLE hToken, hImpToken2;
    BOOL b;
    SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
    PSID AdministratorsGroup; 
    DWORD dwLen;
    SECURITY_IMPERSONATION_LEVEL imp_level;

    if (!LogonUser(lpszUsername,
        lpszDomain,
        lpszPassword,
        LOGON32_LOGON_INTERACTIVE,
        LOGON32_PROVIDER_DEFAULT,
        &hToken)) fail(L"LogonUser");

    if (!ImpersonateLoggedOnUser(hToken)) 
        fail(L"ImpersonateLoggedOnUser");

    if (!OpenThreadToken(GetCurrentThread(), MAXIMUM_ALLOWED, TRUE, 
        &hImpToken2)) 
        fail(L"OpenThreadToken");

    if (!GetTokenInformation(hImpToken2, TokenImpersonationLevel, 
        &imp_level, sizeof(imp_level), &dwLen)) 
        fail(L"GetTokenInformation");

    printf("Impersonation level: %u\n", imp_level);

    if (!AllocateAndInitializeSid( &NtAuthority,
                                  2,
                                  SECURITY_BUILTIN_DOMAIN_RID,
                                  DOMAIN_ALIAS_RID_ADMINS,
                                  0, 0, 0, 0, 0, 0,
                                  &AdministratorsGroup)) 
        fail(L"AllocateAndInitializeSid");

    if (!CheckTokenMembership(hImpToken2, AdministratorsGroup, &b)) 
        fail(L"CheckTokenMembership");

    if (!b) fail(L"membership");
}

int main(int argc, char ** argv)
{
    wchar_t password[1024];
    wchar_t * ptr;

    for (ptr = password;; ptr++)
    {
        *ptr = _getwch();
        if (*ptr == 13) break;
    }

    *ptr = '\0';

    impersonate(L"Administrator", NULL, password);

    return 0;
}
Harry Johnston
  • 35,639
  • 6
  • 68
  • 158