Как проверить, имеет ли пользователь некоторую привилегию?

Автор: Александр Федотов
Опубликовано: 07.03.2002
Версия текста: 1.0

Демонстрационное приложение

Возможность успешного выполнения некоторых операций в Windows NT зависит от того, имеет ли вызывающий пользователь некоторую привилегию или набор привилегий. Например, чтобы завершить работу системы, необходимо иметь привилегию SeShutdownPrivilege. Хотя это обязанность операционной системы - проверять наличие необходимых привилегий, приложение может делать это, чтобы более тонко настроить свой пользовательский интерфейс. Например, оно может запретить команду завершения работы системы, если у пользователя нет соответствующей привилегии, вместо того, чтобы сообщать ему об этом, когда он попытается воспользоваться этой командой.

Список привилегий, предоставленных пользователю, хранится в системной базе данных учетных записей. При входе пользователя в систему, этот список заносится в токен пользователя, откуда его легко получить с помощью функции GetTokenInformation:

BOOL GetTokenInformation(
    HANDLE TokenHandle,                       // описатель токена
    TOKEN_INFORMATION_CLASS InformationClass, // тип информации
    PVOID TokenInformation,                   // выходной буфер
    DWORD TokenInformationLength,             // размер буфера
    PDWORD ReturnLength                       // требуемый размер буфера
    );

Эта функция позволяет получить различную информацию о токене пользователя, параметр InformationClass указывает требуемый вид информации. Нас интересует список привилегий, поэтому мы будем указывать в этом параметре значение TokenPrivileges. Функция IsPrivilege, приведенная в листинге 1, определяет наличие указанной привилегии с использованием GetTokenInformation.

Листинг 1. Использование функции GetTokenInformation
BOOL IsPrivilege(
    IN PCTSTR pszPrivilegeName      // имя привилегии
    )
{
    _ASSERTE(pszPrivilegeName != NULL);

    LUID Luid;
    HANDLE hToken;
    DWORD cbNeeded;
    PTOKEN_PRIVILEGES pPriv;
    
    // получаем идентификатор привилегии
    if (!LookupPrivilegeValue(NULL, pszPrivilegeName, &Luid))
        return FALSE;

    // получаем токен текущего потока
    if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken))
    {
        if (GetLastError() != ERROR_NO_TOKEN)
            return FALSE;

        // получаем токен процесса
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
            return FALSE;
    }

    // определяем размер буфера, необходимый для получения
    // всех привилегий
    if (!GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &cbNeeded))
    {
        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
            DWORD dwError = GetLastError();
            CloseHandle(hToken);
            return SetLastError(dwError), FALSE;
        }
    }

    // выделяем память для выходного буфера
    pPriv = (PTOKEN_PRIVILEGES)_alloca(cbNeeded);
    _ASSERTE(pPriv != NULL);

    // получаем список привилегий
    if (!GetTokenInformation(hToken, TokenPrivileges, pPriv, cbNeeded,
                             &cbNeeded))
    {
        DWORD dwError = GetLastError();
        CloseHandle(hToken);
        return SetLastError(dwError), FALSE;
    }

    CloseHandle(hToken);

    // проходим по списку привилегий и проверяем, есть ли в нем
    // указанная привилегия
    for (UINT i = 0; i < pPriv->PrivilegeCount; i++)
    {
        if (pPriv->Privileges[i].Luid.LowPart == Luid.LowPart &&
            pPriv->Privileges[i].Luid.HighPart == Luid.HighPart)
            return TRUE;
    }

    SetLastError(ERROR_SUCCESS);
    return FALSE;
}

Своим первым действием, функция IsPrivilege получает идентификатор привилегии по ее имени. Хотя привилегии идентифицируются текстовыми строками, при загрузке системы каждой привилегии сопоставляется 64-битный идентификатор, используемый в дальнейшем для сравнения привилегий. Функция LookupPrivilegeValue позволяет получить по имени привилегии ее идентификатор.

Затем функция открывает описатель токена, представляющего текущего пользователя, учитывая при этом возможность имперсонации. Механизм имперсонации в Windows NT позволяет некоторому потоку процесса временно выполнять действия от лица другого пользователя, нежели пользователя изначально создавшего процесс. Поэтому функция сначала пытается открыть токен, связанный с текущим потоком, так как в случае имперсонации этот токен будет представлять имперсонируемого пользователя. Если же поток не имеет ассоциированного с ним токена, функция использует OpenProcessToken, чтобы получить токен, назначенный процессу.

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

Как известно, даже если привилегия присутствует в токене пользователя, она может находится в двух состояниях: включенном (enabled) или выключенном (disabled). Некоторые функции Win32 API требуют, чтобы привилегия была явно включена приложением с помощью AdjustTokenPrivileges, некоторые же самостоятельно включают необходимые привилегии. Если необходимо определить, включена ли некоторая привилегия в токене пользователя, для этого можно воспользоваться функцией PrivilegeCheck, что иллюстрируется функцией IsPrivilegeEnabled, приведенной в листинге 2.

Листинг 2. Использование функции PrivilegeCheck
BOOL IsPrivilegeEnabled(
    IN PCTSTR pszPrivilegeName
    )
{
    _ASSERTE(pszPrivilegeName != NULL);

    HANDLE hToken;
    PRIVILEGE_SET PrivSet;
    BOOL bEnabled = FALSE;

    PrivSet.PrivilegeCount = 1;
    PrivSet.Control = 0;
    PrivSet.Privilege[0].Attributes = 0;

    // получаем идентификатор привилегии
    if (!LookupPrivilegeValue(NULL, pszPrivilegeName, 
                              &PrivSet.Privilege[0].Luid))
        return FALSE;

    // получаем токен текущего потока
    if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken))
    {
        if (GetLastError() != ERROR_NO_TOKEN)
            return FALSE;

        // получаем токен процесса
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
            return FALSE;
    }

    // проверяем статус привилегии
    if (!PrivilegeCheck(hToken, &PrivSet, &bEnabled))
    {
        DWORD dwError = GetLastError();
        CloseHandle(hToken);
        return SetLastError(dwError), FALSE;
    }

    CloseHandle(hToken);

    SetLastError(0);
    return bEnabled;
}

Применение описанных в этой статье методов демонстрирует тестовое приложение PrivChk. Это консольное приложение, которое, будучи запущенным без параметров, выдает список привилегий текущего пользователя. Если же в качестве параметра указано имя конкретной привилегии, программа отображает ее состояние, используя функции IsPrivilege и IsPrivilegeEnabled.

Ссылки

  1. Keith Brown, Security Briefs, Microsoft Systems Journal, August 1999.

Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.