return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, GetLastError());
ReadFile(hfile, pdwOld, sizeof(DWORD), &cb, 0);
CloseHandle(hfile);
// get call context object
// получаем контекстный объект вызова
IServerSecurlty *pss = 0;
HRESULT hr = CoGetCallContext(IID_IServerSecurity, (void**)&pss);
if (FAILED(hr)) return hr;
// set thread token to use caller's credentials
// устанавливаем маркер потока для использования
// полномочий вызывающей программы
hr = pss->ImpersonateClient();
assert(SUCCEEDED(hr));
// execute using client's token to let only users that can
// write to the file change the value
// выполняем с использованием маркера клиента, чтобы
// изменять это значение могли только те пользователи,
// которые имеют право записывать в файл
hfile = CreateFile(«C:\file2.bin»,
GENERIC_READ | GENERIC_WRITE, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hfile == INVALID_HANDLE_VALUE)
hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, GetLastError());
else {
WriteFile(hfile, &dwNew, sizeof(DWORD), &cb, 0);
CloseHandle(hfile);
}
// restore thread to use process-level token
// восстанавливаем режим использования потоком маркера процесса
pss->RevertToSelf();
// release call context
// освобождаем контекст вызова
pss->Release();
return hr;
}
Отметим, что первый вызов CreateFile выполняется с использованием полномочий процесса объекта, в то время как второй вызов – с полномочиями клиента. Если клиент имеет права доступа для чтения/записи в соответствующий файл, то второй вызов метода CreateFile может быть успешным, даже если обычно процесс объекта не имеет доступа к этому файлу.
Важно, что хотя методы IServerSecurity::ImpersonateClient всегда достигают цели, исключая катастрофический сбой, клиент объекта контролирует уровень заимствования прав, допускаемый результирующим маркером. Каждый интерфейсный заместитель имеет свой уровень заимствования прав, который должен быть равным одной из четырех констант (RPC_C_IMP_LEVEL_ANONYMOUS, RPC_C_IMP_LEVEL_IDENTIFY, RPC_C_IMP_LEVEL_IMPERSONATE или RPC_C_IMP_LEVEL_DELEGATE). Во время демаршалинга COM устанавливает этот уровень равным величине, определенной в клиентском вызове CoInitializeSecurity; однако данная установка может быть изменена вручную с помощью IClientSecurity::SetBlanket. Когда объект вызывает IServerSecurity::ImpersonateClient, новый маркер будет ограничен уровнем, заданном в интерфейсном заместителе, который использовался в данном вызове. Это означает, что если клиент задал только уровень RPC_C_IMP_LEVEL_IDENTIFY, то объект не может получить доступ к ресурсам ядра во время выполнения с полномочиями клиента. Объект, однако, может применить API-функции Win32 OpenThreadToken или GetTokenInformation для чтения информации о клиенте (например, ID защиты, групповое членство) из маркера режима анонимного воплощения (impersonation token). Важно отметить, что пока клиент не задал уровень RPC_C_IMP_LEVEL_DELEGATE, объект не может получить доступ ни к одному из удаленных ресурсов защиты, используя полномочия клиента. В их число входят открытие файлов в удаленной файловой системе, а также выполнение аутентифицированных COM- вызовов к удаленным объектам. К сожалению, протокол аутентификации NTLM не поддерживает уровень RPC_C_IMP_LEVEL_DELEGATE, так что под Windows NT 4.0 делегирование невозможно.
Во время предыдущего обсуждения акцент делался на том, что в нормальном режиме методы объекта выполняются с использованием маркера доступа процесса объекта. Однако не обсуждался вопрос о том, как проконтролировать, какой принципал защиты должен использоваться для создания начального маркера серверного процесса. Когда SCM запускает серверный процесс, то он присваивает новому серверному процессу маркер, основанный на конфигурации именованной величины RunAs из AppID. Если же в AppID нет величины RunAs, то считается, что сервер неправильно сконфигурирован для работы в режиме распределенного доступа. Для того чтобы этот тип серверного процесса не внедрял указанные «дыры» в защите в систему, SCM запускает такие процессы с использованием того принципала защиты, который произвел запрос на активацию. Такой тип активации часто называют активацией «как активизатор» («As Activator»), так как серверный процесс выполняет тот же принципал защиты, что и запускающий пользователь. Активация типа «как активизатор» предназначена для поддержки удаленной активации старых серверов и содержит несколько ловушек. Во-первых, чтобы придерживаться семантики типа «как активизатор», COM запустит отдельный серверный процесс для каждой активационной учетной записи пользователя, независимо от того, используется ли REGCLS_MULTIPLEUSE в CoRegisterClassObject. Это вступает в серьезный конфликт с принципом расширяемости и вдобавок делает невозможным сохранение всех экземпляров класса в одном и том же процессе. Во-вторых, каждый серверный процесс запускается с маркером, ограниченным уровнем RPC_C_IMP_LEVEL_IMPERSONATE, из чего следует, что серверные процессы не имеют доступа ни к каким удаленным ресурсам или объектам[1].
В идеале серверные процессы конфигурируются для запуска как отдельные принципалы защиты. Управлять этим можно, помещая именованную величину RunAs в имя учетной записи в AppID:
[HKCRAppID{27EE6A4D-DF65-11d0-8C5F-0080C73925BA}]
RunAs='DomainXUserY'
Если эта именованная величина присутствует, SCM будет использовать указанное имя учетной записи для создания нового регистрационного маркера (login token) и присвоит этот маркер серверному процессу. Для правильной работы этой схемы требуются два условия. Во-первых, соответствующий пароль должен быть записан в определенном месте реестра в качестве ключа локальных средств защиты (LSA – Local Security Authority). Во-вторых, указанная учетная запись пользователя должна иметь полномочия «Вход в систему как пакетное задание» («Logon as a batch job»). При установке значения RunAs утилита DCOMCNFG.EXE обеспечивает выполнение обоих этих условий[2].
Для предотвращения спуфинга (spoofing, получение доступа путем обмана) классов злонамеренными программами CoRegisterClassObject проверяет, зарегистрирован ли AppID данного класса. Если AppID имеет установку RunAs, то COM гарантирует, что принципал вызывающей программы совпадает с именем принципала, записанным в реестре. Если же вызывающая программа не имеет указанной учетной записи RunAs для AppID класса, то вызов метода CoRegisterСlassObject будет отклонен и возвратится известный HRESULT CO_E_WRONG_SERVER_IDENTITY. Поскольку конфигурационные установки COM записаны в защищенной части реестра, только привилегированные пользователи могут изменять список соответствия классов и пользователей.
Важно отметить, что когда в AppID имеется явная учетная запись пользователя RunAs, то SCM всегда будет запускать серверный процесс в его собственной отдельной window-станции (window station)[3]. Это означает, что серверный процесс не может с легкостью ни создавать окна, видимые для интерактивного пользователя на данной машине, ни принимать информацию