Странное поведение Windows Impersonation

Моему приложению Windows могут потребоваться права администратора для некоторых его разделов. В таких случаях я хотел бы запросить у пользователя учетные данные администратора и использовать следующий код для выдачи себя за администратора:

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()
}

где была взята функция IsUserAdmin() из MSDN и выглядит следующим образом:

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

Сценарий 1.
Запуск приложения из учетной записи администратора.

  1. вызов IsUserAdmin () => возвращает TRUE (хорошо)

  2. вызов impersonate ("пользователь без прав администратора", "домен", "пароль") => возвращает FALSE (хорошо!)

Сценарий 2:
Запуск приложения из учетной записи без прав администратора с использованием runas.exe из учетной записи администратора.

  1. вызов IsUserAdmin () => возвращает FALSE (хорошо)

  2. вызов impersonate ("администратор", "домен", "пароль") => возвращает FALSE (не очень хорошо!)

Сценарий 3:
Запуск приложения напрямую из учетной записи без прав администратора.

  1. вызов IsUserAdmin () => возвращает FALSE (хорошо)

  2. вызов impersonate ("администратор", "домен", "пароль") => возвращает FALSE (не очень хорошо!)

Все сценарии печатают как step 1, так и step 2.

Насколько я могу судить, приведенное выше должно гарантировать выдачу себя за другое лицо при наличии законных учетных данных. что мне здесь не хватает?


person Hans Pirson    schedule 09.12.2013    source источник
comment
Вы говорите, что impersonate() возвращает ЛОЖЬ, но вы не указали, где именно происходит сбой. impersonate() имеет 4 точки отказа, которые могут привести к возврату FALSE - если LogonUser() не удается, если ImpersonateLoggedOnUser() не удается, если AllocateAndInitializeSid() не удается или CheckTokenMembership(). Вам нужно отладить свой код и точно выяснить, что на самом деле дает сбой.   -  person Remy Lebeau    schedule 10.12.2013


Ответы (2)


Вам действительно не следует программно проверять права администратора перед выполнением задачи, требующей прав администратора. Просто попытайтесь выполнить задачу без каких-либо условий, и пусть API сообщит вам, не удалось ли выполнить задачу из-за недостаточных прав, и если да, то вы можете решить, просто завершить задачу с ошибкой или запросить учетные данные у пользователя и повторить попытку.

Если вы пытаетесь хорошо поиграть с UAC, то, что вы должны сделать, это реализовать свой код администратора как отдельный процесс или COM-объект, тогда вы можете запустить этот процесс / COM в повышенном состоянии, когда это необходимо, без необходимости повышать ваш основной процесс. . Позвольте ОС запрашивать у пользователя учетные данные администратора (и решать, как должно выглядеть это приглашение), когда ОС это необходимо, не делайте этого вручную самостоятельно.

person Remy Lebeau    schedule 09.12.2013

Ответ Реми точен: то, что вы пытаетесь сделать, не является правильным решением для вашего сценария. Однако, согласно документации, ваш код должен работать, как задумано. Похоже, что этого не происходит по двум причинам:

  1. Похоже, что вопреки документации, вы не можете олицетворять токен, полученный через LogonUser на более высоком уровне олицетворения, чем SecurityIdentification, не удерживая SeImpersonatePrivilege. [Проверено на Windows 7 SP1 x64.]

  2. Функция CheckTokenMembership не работает должным образом, если вы передаете NULL в качестве токена, а уровень олицетворения потока равен SecurityIdentification. [то же самое.]

Вы можете достаточно легко обойти проблему 2, явно извлекая токен олицетворения, используя OpenThreadToken, а не передавая NULL в качестве токена, как показано ниже. В качестве альтернативы вы можете продублировать первичный токен с помощью DuplicateToken и передать дублированный токен CheckTokenMembership. Если все, что вам нужно, это проверить содержимое токена, это было бы более эффективным решением.

Мне неизвестно какое-либо решение проблемы 1, кроме запуска подпроцесса в новом контексте для выполнения работы от вашего имени. Конечно, как заметил Реми, это то, что вы должны делать в любом случае.

Вот код, который я использовал при тестировании, для справки:

#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;
}
person Harry Johnston    schedule 10.12.2013