模擬用戶端
當使用者應用程式透過 WMI 提供者向系統上的物件要求資料時,模擬表示提供者會呈現代表用戶端安全性層級的認證,而不是提供者的 。 模擬可防止用戶端取得系統上資訊的未經授權存取權。
本主題將討論下列各節:
WMI 通常會使用 LocalServer 安全性內容,在高安全性層級以系統管理服務的形式執行。 使用系統管理服務可讓 WMI 存取特殊許可權資訊。 呼叫提供者以取得資訊時,WMI 會將其安全性識別碼 (SID) 傳遞至提供者,讓提供者能夠在相同的高安全性層級存取訊號。
在 WMI 應用程式啟動程式期間,Windows 作業系統會為 WMI 應用程式提供開始程式之使用者的安全性內容。 使用者的安全性內容通常低於 LocalServer 的安全性層級,因此使用者可能沒有許可權存取 WMI 可用的所有資訊。 當使用者應用程式要求動態資訊時,WMI 會將使用者的 SID 傳遞給對應的提供者。 如果適當地寫入,提供者會嘗試使用使用者 SID 來存取訊號,而不是提供者 SID。
若要讓提供者成功模擬用戶端應用程式,用戶端應用程式和提供者必須符合下列準則:
- 用戶端應用程式必須使用 COM 連線安全性層級 的 RPC_C_IMP_LEVEL_IMPERSONATE 或 RPC_C_IMP_LEVEL_DELEGATE來呼叫 WMI。 如需詳細資訊,請參閱 維護 WMI 安全性。
- 提供者必須向 WMI 註冊為模擬提供者。 如需詳細資訊,請參閱 註冊模擬的提供者。
- 提供者必須先切換至用戶端應用程式的安全性層級,才能存取特殊許可權資訊。 如需詳細資訊,請參閱 設定提供者內的模擬層級。
- 如果拒絕存取這項資訊,提供者必須正確處理錯誤狀況。 如需詳細資訊,請參閱 處理提供者中的拒絕存取訊息。
註冊模擬的提供者
WMI 只會將用戶端應用程式的 SID 傳遞給已註冊為模擬提供者的提供者。 若要讓提供者執行模擬,您必須修改提供者註冊程式。
下列程式說明如何註冊模擬的提供者。 此程式假設您已瞭解註冊程式。 如需註冊程式的詳細資訊,請參閱 註冊提供者。
註冊模擬的提供者
將代表提供者之 __Win32Provider類別的ImpersonationLevel屬性設定為 1。
ImpersonationLevel屬性會記載提供者是否支援模擬。 將 ImpersonationLevel 設定為 0 表示提供者不會模擬用戶端,並在與 WMI 相同的使用者內容中執行所有要求的作業。 將 ImpersonationLevel 設定為 1 表示提供者使用模擬呼叫來檢查代表用戶端執行的作業。
將相同__Win32Provider類別的PerUserInitialization屬性設定為TRUE。
注意
如果您向設定為TRUE的 __Win32Provider屬性InitializeAsAdminFirst註冊提供者,則提供者只會在初始化階段使用系統管理層級執行緒安全性權杖。 雖然 對 CoImpersonateClient 的呼叫不會失敗,但提供者會使用 WMI 的安全性內容,而不是用戶端的安全性內容。
下列程式碼範例示範如何註冊模擬的提供者。
instance of __Win32Provider
{
CLSID = "{FD4F53E0-65DC-11d1-AB64-00C04FD9159E}";
ImpersonationLevel = 1;
Name = "MS_NT_EVENTLOG_PROVIDER";
PerUserInitialization = TRUE;
};
在提供者內設定模擬層級
如果您向設定為 1 的 __Win32Provider 類別屬性 ImpersonationLevel 註冊提供者,則 WMI 會呼叫您的提供者來模擬各種用戶端。 若要處理這些呼叫,請在IWbemServices介面的實作中使用CoImpersonateClient和CoRevertToSelf COM 函式。
CoImpersonateClient函式可讓伺服器模擬進行呼叫的用戶端。 藉由對 CoImpersonateClient 的呼叫放入 IWbemServices的實作中,可讓您的提供者設定提供者的執行緒權杖以符合用戶端的執行緒權杖,進而模擬用戶端。 如果您未呼叫 CoImpersonateClient,您的提供者會在系統管理員層級執行程式碼,藉此建立潛在的安全性弱點。 如果您的提供者暫時需要以系統管理員身分或手動執行存取檢查,請呼叫 CoRevertToSelf。
相較于 CoImpersonateClient, CoRevertToSelf 是處理執行緒模擬層級的 COM 函式。 在此情況下, CoRevertToSelf 會將模擬層級變更回原始模擬設定。 一般而言,提供者一開始是系統管理員,而 CoImpersonateClient 與 CoRevertToSelf 之間的替代專案,取決於它是否要進行代表呼叫端或其本身呼叫的呼叫。 提供者必須負責正確放置這些呼叫,以免對終端使用者公開安全性漏洞。 例如,提供者應該只呼叫模擬程式碼序列內的原生 Windows 函式。
注意
CoImpersonateClient和CoRevertToSelf的目的是要設定提供者的安全性。 如果您判斷模擬失敗,您應該透過 IWbemObjectSink::SetStatus將適當的完成程式碼傳回 WMI。 如需詳細資訊,請參閱 處理提供者中的拒絕存取訊息。
維護提供者的安全性層級
提供者無法在IWbemServices的實作中呼叫CoImpersonateClient一次,並假設模擬認證在提供者持續期間保留。 相反地,在實作過程中多次呼叫 CoImpersonateClient ,讓 WMI 無法變更認證。
為提供者設定模擬的主要考慮是重新進入。 在此內容中,重新進入是當提供者呼叫 WMI 以取得資訊,並等候 WMI 回呼提供者。 基本上,執行執行緒會離開提供者程式碼,而只會在稍後重新輸入程式碼。 重新進入是 COM 設計的一部分,通常不是問題。 不過,當執行的執行緒進入 WMI 時,執行緒會採用 WMI 的模擬層級。 當執行緒返回提供者時,您必須使用 對 CoImpersonateClient的另一個呼叫重設模擬層級。
若要保護自己免于提供者中的安全性漏洞,您應該只在模擬用戶端時重新進入 WMI 呼叫。 也就是說,呼叫 CoImpersonateClient 之後,以及呼叫 CoRevertToSelf之前,應該呼叫 WMI。 因為 CoRevertToSelf 會使模擬設定為使用者層級 WMI 正在執行,一般而言,LocalSystem,在呼叫 CoRevertToSelf 之後重新進入 WMI 的呼叫可能會為使用者提供和呼叫的任何提供者,比他們應該擁有的更多功能。
注意
如果您呼叫系統函式或其他介面方法,則不保證會維護呼叫內容。
處理提供者中拒絕存取的訊息
當用戶端要求沒有存取權的類別或資訊時,會出現大部分拒絕存取的錯誤訊息。 如果提供者將「拒絕存取」錯誤訊息傳回給 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);
相關主題