共用方式為


冒充客戶

當使用者應用程式透過 WMI 提供者向系統上的物件要求資料時,所謂的模擬是指提供者會使用代表用戶端而非自身的安全性層級的認證。 模擬可防止客戶端在系統上未經授權地存取資訊。

本主題將討論下列各節:

WMI 通常會使用 LocalServer 安全性內容,以高安全性層級的管理服務的形式執行。 使用管理服務可提供 WMI 存取特殊權限資訊的方法。 呼叫提供者以取得資訊時,WMI 會將其安全性識別碼 (SID) 傳遞給提供者,讓提供者存取相同高安全性層級的資訊。

在 WMI 應用程式啟動過程中,Windows 作業系統會為 WMI 應用程式提供開始該過程之使用者的安全性上下文。 使用者的安全性內容通常比 LocalServer 低,因此使用者可能無權存取 WMI 可用的所有資訊。 當使用者應用程式要求動態資訊時,WMI 會將使用者的 SID 傳遞給對應的提供者。 如果適當地撰寫,提供者會嘗試使用使用者 SID 存取資訊,而不是提供者 SID。

若要讓提供者成功模擬用戶端應用程式,用戶端應用程式和提供者必須符合下列準則:

  • 用戶端應用程式必須使用 COM 連線安全性層級的 RPC_C_IMP_LEVEL_IMPERSONATERPC_C_IMP_LEVEL_DELEGATE呼叫 WMI。 如需詳細資訊,請參閱 維護 WMI 安全性
  • 提供者必須向 WMI 註冊為模擬提供者。 如需詳細資訊,請參閱 註冊模擬提供者
  • 提供者必須先切換至用戶端應用程式的安全性層級,才能存取特殊許可權資訊。 如需詳細資訊,請參閱提供者 中的模擬層級設定
  • 如果拒絕存取這項資訊,提供者必須正確處理錯誤狀況。 如需詳細資訊,請參閱 處理提供者中的拒絕存取訊息

註冊提供者以進行身份冒充

WMI 只會將用戶端應用程式的 SID 傳遞給註冊為模擬身份提供者的提供者。 若要讓提供者執行模擬,您必須修改提供者註冊程式。

下列程序說明如何註冊仿真的提供者。 此程序假定您已經了解註冊流程。 如需註冊過程的詳細資訊,請參閱 註冊提供者

註冊模擬提供者

  1. __Win32Provider 類別的 impersonationLevel 屬性設定為 1。

    提供者是否支持模擬將在 ImpersonationLevel 屬性文檔中記錄。 將 ImpersonationLevel 設定為 0,表示提供者不會模擬用戶端,並在與 WMI 相同的用戶內容中執行所有要求的作業。 將 ImpersonationLevel 設定為 1,表示提供者會使用模擬呼叫來檢查代表用戶端執行的作業。

  2. 將相同 __Win32Provider 類別的 PerUserInitialization 屬性設定為 TRUE

注意事項

如果您在 __Win32Provider 中將提供者註冊為屬性 InitializeAsAdminFirst 設定為 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 就會呼叫您的提供者來冒充不同的客戶端。 若要處理這些呼叫,請在 IWbemServices介面的實作中使用 CoImpersonateClientCoRevertToSelf COM 函式。

CoImpersonateClient 函式可讓伺服器模擬發出呼叫的用戶端。 在您實作的 IWbemServices中呼叫 CoImpersonateClient,您即可讓提供者設定提供者的執行緒代幣以符合客戶端的執行緒代幣,從而模擬客戶端。 如果您未呼叫 CoImpersonateClient,您的提供者會在系統管理員層級執行程式碼,藉此建立潛在的安全性弱點。 如果您的提供者暫時需要以系統管理員的角色或手動方式執行存取檢查,請呼叫 CoRevertToSelf

CoImpersonateClient相反,CoRevertToSelf 是處理線程模擬層級的 COM 函式。 在此情況下,CoRevertToSelf 將模擬層級變更回原始模擬設定。 一般而言,提供者一開始是系統管理員,並根據它是在代表呼叫端進行呼叫還是進行自身的呼叫,交替使用 CoImpersonateClientCoRevertToSelf。 提供者必須負責正確放置這些呼叫,以免向使用者公開安全性漏洞。 例如,提供者應該只呼叫模擬程式代碼序列內的原生 Windows 函式。

注意

CoImpersonateClientCoRevertToSelf 的目的是設定提供者的安全性。 如果您判斷身分模擬失敗,您應該透過 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) 搭配數據列層級安全性的提供者,使用呼叫者的安全性層級來定義結果集,傳回成功的部分結果。

  • 使用 WBEM_E_ACCESS_DENIED 使整個操作失敗,且不返回任何實例。

    提供者可能會選擇性地包含描述客戶端情況的錯誤物件。 請注意,某些提供者可能會序列存取數據源,而且在部分完成列舉之前,可能不會遇到拒絕。

  • 傳回所有可以存取的例項,同時傳回非錯誤狀態代碼WBEM_S_ACCESS_DENIED

    提供者應在操作過程中的列舉期間記錄下拒絕情況,並且可以繼續提供實例,最終使用無錯誤狀態代碼結束。 提供者也可以選擇在第一次拒絕時終止列舉。 此選項的理由是不同的提供者有不同的擷取範例。 提供者可能已在發現存取違規之前傳送實例。 某些提供者可能會選擇繼續提供其他實例,而其他提供者可能想要終止。

由於 COM 的結構,除了錯誤物件之外,您無法在發生錯誤時傳送任何資訊。 因此,您無法同時傳回資訊和錯誤碼。 如果您選擇傳回資訊,則必須改用 nonerror 狀態代碼。

對拒絕存取程式碼進行除錯

某些應用程式可能會使用低於 RPC_C_IMP_LEVEL_IMPERSONATE的模擬層級。 在此情況下,多數由供應者對用戶端應用程式進行的模擬呼叫將會失敗。 若要成功設計和實作提供者,您必須記住這個想法。

根據預設,唯一可以存取提供者的其他模擬層級是 RPC_C_IMP_LEVEL_IDENTIFY。 如果用戶端應用程式使用 RPC_C_IMP_LEVEL_IDENTIFYCoImpersonateClient 不會傳回錯誤碼。 相反地,提供者只會模擬用戶端以供識別之用。 因此,提供者呼叫的大部分 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 提供者

設定命名空間安全描述符

保護您的提供者