Олицетворение клиента
Когда приложение пользователя запрашивает данные из объектов в системе через поставщик 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 передает идентификатор безопасности клиентского приложения только поставщикам, зарегистрированным в качестве поставщиков олицетворения. Чтобы поставщик выполнял олицетворение, необходимо изменить процесс регистрации поставщика.
В следующей процедуре описывается, как зарегистрировать поставщик для олицетворения. В процедуре предполагается, что вы уже понимаете процесс регистрации. Дополнительные сведения о процессе регистрации см. в разделе Регистрация поставщика.
Регистрация поставщика для олицетворения
Задайте для свойства ImpersonationLevel класса __Win32Provider , представляющего поставщика значение 1.
Свойство ImpersonationLevel определяет, поддерживает ли поставщик олицетворение. Значение ImpersonationLevel равное 0 означает, что поставщик не олицетворяет клиента и выполняет все запрошенные операции в том же контексте пользователя, что и WMI. Если задать значение ImpersonationLevel значение 1, поставщик использует вызовы олицетворения для проверка операций, выполняемых от имени клиента.
Задайте для свойства 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;
};
Если вы регистрируете поставщик с свойством класса __Win32ProviderImpersonationLevel , равным 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 не изменял учетные данные.
Main задачей при настройке олицетворения для поставщика является повторный вход. В этом контексте повторный вход — это когда поставщик вызывает WMI для получения информации и ожидает, пока WMI не вызовет поставщик обратно. По сути, поток выполнения покидает код поставщика, только чтобы повторно ввести код позже. Reentry является частью проектирования 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 (SQL) с безопасностью на уровне строк, возвращают успешные частичные результаты с помощью уровня безопасности вызывающего объекта для определения результирующих наборов.
Завершите всю операцию с WBEM_E_ACCESS_DENIED и не возвращают экземпляры.
Поставщик может при необходимости включить объект ошибки, описывающий ситуацию для клиента. Обратите внимание, что некоторые поставщики могут получать доступ к источникам данных последовательно и не столкнуться с отказами, пока не будет выполняться перечисление.
Возвращает все экземпляры, к которым можно получить доступ, но также возвращает код состояния nonerror WBEM_S_ACCESS_DENIED.
Поставщик должен отметить отказ во время перечисления и может продолжать предоставлять экземпляры, завершая код состояния nonerror. Поставщик также может завершить перечисление при первом отказе. Обоснование этого варианта заключается в том, что разные поставщики имеют разные парадигмы извлечения. Поставщик, возможно, уже доставлял экземпляры перед обнаружением нарушения доступа. Некоторые поставщики могут продолжить предоставлять другие экземпляры, а другие могут захотеть завершить работу.
Из-за структуры 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);