Поделиться через


Олицетворение клиента

Когда пользовательское приложение запрашивает данные из объектов в системе через поставщика WMI, олицетворение означает, что поставщик предоставляет учетные данные, представляющие уровень безопасности клиента, а не уровень безопасности самого поставщика. Олицетворение запрещает клиенту получать несанкционированный доступ к информации в системе.

В этом разделе рассматриваются следующие разделы:

WMI обычно выполняется в качестве административной службы на высоком уровне безопасности с помощью контекста безопасности LocalServer. Использование административной службы предоставляет WMI средства для доступа к привилегированным сведениям. При вызове поставщика сведений WMI передает поставщику идентификатор безопасности (SID), что позволяет поставщику получать доступ к информации на том же высоком уровне безопасности.

В процессе запуска приложения WMI операционная система Windows предоставляет приложению WMI контекст безопасности пользователя, который начал процесс. Контекст безопасности пользователя обычно является более низким уровнем безопасности, чем LocalServer, поэтому пользователь может не иметь разрешения на доступ ко всем сведениям, доступным для WMI. Когда пользовательское приложение запрашивает динамическую информацию, WMI передает идентификатор безопасности пользователя соответствующему поставщику. Если написано соответствующим образом, поставщик пытается осуществить доступ к информации с помощью идентификатора безопасности пользователя вместо идентификатора безопасности поставщика.

Чтобы поставщик успешно олицетворял клиентское приложение, клиентское приложение и поставщик должны соответствовать следующим критериям:

  • Клиентское приложение должно вызывать WMI с уровнем безопасности COM-подключения RPC_C_IMP_LEVEL_IMPERSONATE или RPC_C_IMP_LEVEL_DELEGATE. Для получения дополнительной информации см. Поддержание безопасности WMI.
  • Поставщик должен зарегистрироваться в WMI в качестве поставщика имперсонации. Дополнительную информацию см. в разделе Регистрация поставщика для выдачи за другого.
  • Поставщик должен переключиться на уровень безопасности клиентского приложения перед доступом к привилегированным данным. Дополнительные сведения см. в разделе «Настройка уровней имперсонации у поставщика».
  • Поставщик должен правильно обрабатывать ошибочные ситуации, если доступ к этой информации запрещён. Дополнительные сведения см. в разделеОбработка сообщений об отказе в доступе у поставщика.

Регистрация поставщика для олицетворения

WMI передает идентификатор безопасности клиентского приложения провайдерам, зарегистрированным в качестве провайдеров имперсонации. Для разрешения поставщику выполнения олицетворения требуется изменить процесс его регистрации.

В следующей процедуре описывается регистрация поставщика для олицетворения. В процедуре предполагается, что вы уже понимаете процесс регистрации. Дополнительные сведения о процессе регистрации см. в Регистрация поставщика.

Регистрация поставщика для олицетворения

  1. Установите свойство ImpersonationLevel класса __Win32Provider, который представляет вашего поставщика, на значение 1.

    ImpersonationLevel свойство документирует, поддерживает ли поставщик олицетворение или нет. Параметр ImpersonationLevel значение 0 указывает на то, что поставщик не олицетворяет клиента и выполняет все требуемые операции в том же пользовательском контексте, что и WMI. Установка параметра ImpersonationLevel в значение 1 указывает, что поставщик использует вызовы олицетворения для проверки операций, выполняемых от имени клиента.

  2. Задайте для свойства PerUserInitialization того же класса __Win32Provider значение TRUE.

Заметка

Если вы регистрируете поставщика с помощью свойства __Win32ProviderInitializeAsAdminFirst значение TRUE, то поставщик использует маркер безопасности потока уровня администрирования только во время этапа инициализации. Хотя вызов CoImpersonateClient не завершается ошибкой, поставщик использует контекст безопасности WMI вместо контекста клиента.

 

В следующем примере кода показано, как зарегистрировать поставщика для имперсонификации.

instance of __Win32Provider
{
    CLSID = "{FD4F53E0-65DC-11d1-AB64-00C04FD9159E}";
    ImpersonationLevel = 1;
    Name = "MS_NT_EVENTLOG_PROVIDER";
    PerUserInitialization = TRUE;
};

Настройка уровней олицетворения в поставщике

Если вы регистрируете поставщика с помощью свойства класса __Win32Provider и устанавливаете ImpersonationLevel на 1, то WMI вызывает поставщика для олицетворения различных клиентов. Для обработки этих вызовов используйте COM-функции CoImpersonateClient и CoRevertToSelf в реализации интерфейса IWbemServices.

Функция coImpersonateClientпозволяет серверу имитировать клиента, который сделал вызов. Поместив вызов CoImpersonateClient в вашу реализацию IWbemServices, вы позволяете вашему поставщику установить токен потока провайдера так, чтобы он совпадал с токеном потока клиента, и таким образом, имитировать клиента. Если вы не вызываете CoImpersonateClient, поставщик выполняет код на уровне безопасности администратора, тем самым создавая потенциальную уязвимость безопасности. Если поставщик временно должен действовать в качестве администратора или выполнять проверку доступа вручную, вызовите CoRevertToSelf.

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

Заметка

Назначение CoImpersonateClient и CoRevertToSelf заключается в установлении безопасности для провайдера. Если определить, что олицетворение завершилось ошибкой, необходимо вернуть соответствующий код завершения в WMI через IWbemObjectSink::SetStatus. Дополнительные сведения см. в разделеОбработка сообщений об отказе в доступе у поставщика.

 

Поддержание уровней безопасности у провайдера

Поставщики не могут вызывать CoImpersonateClient один раз в процессе реализации IWbemServices и предполагать, что учетные данные олицетворения сохраняются в течение всего времени работы поставщика. Вместо этого вызовите CoImpersonateClient несколько раз во время реализации, чтобы оставить WMI от изменения учетных данных.

Основной проблемой настройки олицетворения для поставщика является реентерабельность. В этом контексте реентерабельность означает, что поставщик выполняет вызов к WMI для получения информации и ждет, пока WMI не вызовет его обратно. В сущности, поток выполнения выходит из кода поставщика, чтобы затем вернуться в него позже. Повторный вход является частью архитектуры COM и, как правило, не представляет проблемы. Однако, когда поток выполнения входит в WMI, поток принимает уровни олицетворения WMI. Когда поток возвращается к поставщику, необходимо снова сбросить уровни имперсонации другим вызовом CoImpersonateClient.

Чтобы защитить себя от ошибок безопасности в поставщике, необходимо повторно вызывать WMI только при олицетворении клиента. То есть вызовы WMI следует выполнять после вызова CoImpersonateClient и перед вызовом CoRevertToSelf. Поскольку вызов CoRevertToSelf устанавливает имперсонацию на уровне пользователя, под которым запускается WMI, обычно это LocalSystem, повторные вызовы к WMI после вызова CoRevertToSelf могут предоставить пользователю и любому вызываемому поставщику гораздо больше прав и возможностей, чем следует.

Заметка

При вызове системной функции или другого метода интерфейса контекст вызова не гарантируется.

 

Обработка сообщений об отказе в доступе в поставщике

Большинство сообщений об ошибках с отказом в доступе отображаются, когда клиент запрашивает класс или сведения, к которым у них нет доступа. Если поставщик возвращает сообщение об ошибке "Отказано в доступе" в WMI, и WMI передает его клиенту, клиент может сделать вывод, что такая информация существует. В некоторых ситуациях это может быть нарушением безопасности. Поэтому поставщик не должен распространять сообщение клиенту. Вместо этого набор классов, который должен был бы предоставить поставщик, не должен быть открыт. Аналогичным образом поставщик динамического экземпляра должен вызывать базовый источник данных, чтобы определить, как работать с сообщениями с отказом в доступе. Поставщик несет ответственность за репликацию этой философии в среду WMI. Дополнительные сведения см. в разделе отчетов о частичных экземплярах и отчетов о частичных перечислениях.

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

Отчеты о частичных случаях

Одним из распространенных случаев сообщения с отказом в доступе является то, что WMI не может предоставить всю информацию для заполнения экземпляра. Например, клиент может иметь право просматривать объект жесткого диска, но может не иметь права узнать, сколько места доступно на самом жестком диске. Поставщик должен определить, как обрабатывать ситуацию, если он не может полностью снабдить экземпляр свойствами из-за нарушения прав доступа.

WMI не требует единственного ответа для клиентов, имеющих частичный доступ к экземплярам. Вместо этого WMI версии 1.x позволяет поставщику использовать один из следующих вариантов:

  • В случае сбоя всей операции с WBEM_E_ACCESS_DENIED экземпляры не возвращаются.

    Верните объект ошибки вместе с WBEM_E_ACCESS_DENIED, чтобы описать причину отказа.

  • Вернуть все доступные свойства и заполнить недоступные свойства значением NULL.

Заметка

Убедитесь, что возврат WBEM_E_ACCESS_DENIED не создает уязвимость в безопасности в вашей организации.

 

Составление отчетов о частичных перечислениях

Еще одним распространенным случаем нарушения доступа является то, что WMI не может вернуть все перечисление. Например, клиент может иметь доступ к просмотру всех объектов локального сетевого компьютера, но может не иметь доступа к просмотру объектов компьютеров за пределами своего домена. Поставщик должен определить, как обрабатывать любую ситуацию, когда перечисление не может быть завершено из-за нарушения доступа.

Как и в случае с поставщиком экземпляров, WMI не требует единого ответа на частичное перечисление. Вместо этого WMI версии 1.x позволяет поставщику использовать один из следующих вариантов:

  • Возвращайте WBEM_S_NO_ERROR для всех экземпляров, к которым может получить доступ поставщик.

    Если этот параметр используется, пользователь не знает, что некоторые экземпляры недоступны. Ряд поставщиков, таких как те, которые используют язык структурированных запросов (SQL) с безопасностью на уровне строк, возвращает успешные частичные результаты с помощью уровня безопасности вызывающего абонента для определения результирующего набора.

  • В случае сбоя всей операции с WBEM_E_ACCESS_DENIED экземпляры не возвращаются.

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

  • Возвращает все экземпляры, к которым можно получить доступ, но также возвращает код состояния nonerror WBEM_S_ACCESS_DENIED.

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

Из-за структуры COM при возникновении ошибки вы не можете передавать никакую информацию, кроме объекта ошибки. Таким образом, нельзя возвращать как сведения, так и код ошибки. Если вы решили вернуть информацию, вместо этого необходимо использовать код состояния nonerror.

Отладка кода с ошибкой "доступ запрещён"

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

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

Код нуждается в следующих ссылках и инструкциях #include для правильной компиляции.

#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <wbemidl.h>

В следующем примере кода показано, как определить, сымитировал ли провайдер клиентское приложение успешно.

DWORD dwImp = 0;
HANDLE hThreadTok;
DWORD dwBytesReturned;
BOOL bRes;

// You must call this before trying to open a thread token!
CoImpersonateClient();

bRes = OpenThreadToken(
    GetCurrentThread(),
    TOKEN_QUERY,
    TRUE,
    &hThreadTok
);

if (bRes == FALSE)
{
    printf("Unable to read thread token (%d)\n", GetLastError());
    return 0;
}

bRes = GetTokenInformation(
    hThreadTok,
    TokenImpersonationLevel, 
    &dwImp,
    sizeof(DWORD),
    &dwBytesReturned
);

if (!bRes)
{
    printf("Unable to read impersonation level\n");
    CloseHandle(hThreadTok);
    return 0;
}

switch (dwImp)
{
case SecurityAnonymous:
    printf("SecurityAnonymous\n");
    break;

case SecurityIdentification:
    printf("SecurityIdentification\n");
    break;

case SecurityImpersonation:
    printf("SecurityImpersonation\n");
    break;

case SecurityDelegation:
    printf("SecurityDelegation\n");
    break;

default:
    printf("Error. Unable to determine impersonation level\n");
    break;
}

CloseHandle(hThreadTok);

Разработка поставщика WMI

Установка дескрипторов безопасности пространства имен

Обеспечение безопасности вашего поставщика