简体   繁体   中英

Odd behavior of windows Impersonation

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?

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'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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM