使用 Credential Guard 保護衍生的網域認證

Credential Guard 是「Windows 10 企業版」中導入的功能,可使用虛擬式安全性來隔離密碼,使得只有具有特殊權限的系統軟體才可以存取它們。對這些密碼的未經授權存取會導致認證竊取攻擊,例如傳遞雜湊 (Pass-the-Hash) 或傳遞票證 (Pass-The-Ticket)。

Credential Guard 提供下列功能和解決方案:

  • 硬體安全性:Credential Guard 可以利用包括安全開機和虛擬化在內的平台安全性功能,提高衍生的網域認證的安全性。

  • 虛擬式安全性:管理衍生的網域認證及其他密碼的 Windows 服務可以在與執行中作業系統隔離的受保護環境中執行。

  • 更佳的進階持續性威脅防護:使用虛擬式安全性環境保護衍生的網域認證的安全可以阻擋許多針對性攻擊中使用的認證竊取攻擊技術和工具。具有系統管理權限,在作業系統中執行的惡意程式碼,無法擷取受虛擬式安全性保護的密碼。雖然 Credential Guard 是一個功能強大的安全防護功能,但是持續性威脅攻擊可能會轉移到新的攻擊技術,因此,您也應該併入 Device Guard 及其他安全性策略和架構。

  • 管理性:您可以使用群組原則、WMI、從命令提示字元及 Windows PowerShell 來管理 Credential Guard。

運作方式

Credential Guard 會使用虛擬式安全性,將舊版 Windows 儲存在「本機安全性授權」(LSA) 中的密碼隔離。在 Windows 10 之前的版本中,LSA 將作業系統使用的密碼儲存在其處理程序記憶體中。作業系統中的 LSA 處理程序會透過 Credential Guard,與稱為隔離的 LSA 程序的新元件溝通,此環境會儲存並保護那些密碼。隔離的 LSA 程序所儲存的資料是使用虛擬式安全性所保護,無法由作業系統的其餘部分存取。LSA 使用遠端程序呼叫,與隔離的 LSA 程序進行通訊。

基於安全性考量,隔離的 LSA 程序不會裝載任何裝置驅動程式。而是只裝載安全性所需的一小組作業系統二進位檔案,除此之外,不會裝載任何其他項目。所有這些二進位檔都是使用虛擬化安全性信任的憑證所簽署,而且這些簽章會先經過驗證,才會啟動受保護環境中的檔案。

使用預設的衍生認證時,Credential Guard 不允許使用較舊形式的 NTLM 和 Kerberos 驗證通訊協定和加密套件,包括 NTLMv1、MS-CHAPv2,以及強度較弱的 Kerberos 加密類型,例如 DES。

以下是有關如何使用虛擬式安全性來隔離 LSA 的整體概觀:

Mt483740.credguard(zh-tw,VS.85).png

新功能和變更的功能

若要查看 Credential Guard 中新增的功能或變更的功能,請參閱 Credential Guard 有哪些新功能?

硬體與軟體需求

電腦必須符合下列硬體與軟體需求,才能使用 Credential Guard:

需求 描述

Windows 10 企業版

電腦必須執行「Windows 10 企業版」。

UEFI 韌體 2.3.1 版或更新版本及「安全開機」

若要確認韌體使用 UEFI 2.3.1 版或更新版本及「安全開機」,您可以依據 System.Fundamentals.Firmware.CS.UEFISecureBoot.ConnectedStandby「Windows 硬體相容性計畫」需求驗證韌體。

虛擬化擴充功能

若要支援虛擬式安全性,必須使用下列虛擬化擴充功能:

  • Intel VT-x 或 AMD-V
  • 第二層位址轉譯

x64 架構

虛擬式安全性在 Windows Hypervisor 中使用的功能只能在 64 位元電腦上執行。

VT-d 或 AMD-Vi IOMMU (輸入/輸出記憶體管理單元)

在 Windows 10 中,IOMMU 增強了系統對記憶體攻擊的復原能力。¹

信賴平台模組 (TPM) 1.2 或 2.0 版

TPM 1.2 和 2.0 可針對儲存在韌體中的加密金鑰提供保護。Windows 10 (組建 10240) 不支援 TPM 1.2;不過,Windows 10 版本 1511 (組建 10586) 與更新版本則支援。

注意  如果您未安裝 TPM,則還是會啟用 Credential Guard,但用來加密 Credential Guard 的金鑰將不會受到 TPM 保護。
 

安全韌體更新程序

若要確認韌體符合安全韌體更新程序,您可以依據 System.Fundamentals.Firmware.UEFISecureBoot「Windows 硬體相容性計畫」需求驗證韌體。

韌體針對安全 MOR 實作進行更新

Credential Guard 需要安全的 MOR 位元,才能防止某些記憶體攻擊。

實體電腦

若是執行 Windows 10 的電腦,您無法在虛擬機器上執行 Credential Guard。

 

¹ 如果您在 [群組原則] 設定中選擇 [安全開機及 DMA 保護] 選項,就需要 IOMMU。[安全開機]**** 群組原則選項可在沒有 IOMMU 的裝置上啟用 Credential Guard。

管理 Credential Guard

Credential Guard 使用虛擬式安全性功能,您必須先在每一部電腦上啟用這些功能,才可以使用它。

使用群組原則來開啟 Credential Guard

您可以使用「群組原則」來啟用 Credential Guard,因為它會為您新增虛擬式安全性功能。

  1. 從「群組原則管理主控台」中,移至 [電腦設定] -> [系統管理範本]**** -> [系統] -> [Device Guard]****。

  2. 按兩下 [開啟虛擬式安全性],然後按一下 [已啟用]**** 選項。

  3. 在 [選取平台安全性層級] 方塊中,選擇 [安全開機]**** 或 [安全開機及 DMA 保護]。

  4. 在 [Credential Guard 設定]**** 方塊中,按一下 [連同 UEFI 鎖定一起啟用],然後按一下 [確定]****。如果您想要能夠從遠端關閉 Credential Guard,選擇 [不連同鎖定一起啟用]。

    Mt483740.CredGuard_GP(zh-tw,VS.85).png

  5. 關閉「群組原則管理主控台」。

將 Credential Guard 新增到映像中

如果您想要將 Credential Guard 新增到映像中,可以透過新增虛擬式安全性功能,然後開啟 Credential Guard,來執行這項操作。

新增虛擬式安全性功能

首先,您必須新增虛擬式安全性功能。您可以使用 [控制台] 或部署映像服務與管理工具 (DISM) 進行。

注意  如果您是透過使用「群組原則」來啟用 Credential Guard,則不需要執行這些步驟。「群組原則」會為您安裝這些功能。

 

Mt483740.wedge(zh-tw,VS.85).gif使用程式和功能來新增虛擬式安全性功能

  1. 開啟 [程式和功能] 控制台。

  2. 按一下 [開啟或關閉 Windows 功能]。

  3. 選取 [隔離的使用者模式]**** 核取方塊。

  4. 移至 [Hyper-V] -> [Hyper-V 平台]****,然後選取 [Hyper-V Hypervisor] 核取方塊。

  5. 按一下 [確定]****。

Mt483740.wedge(zh-tw,VS.85).gif使用 DISM 將虛擬式安全性功能新增到離線映像

  1. 開啟提升權限的命令提示字元。

  2. 執行下列命令來新增 Hyper-V Hypervisor:

    dism /image:<WIM file name> /Enable-Feature /FeatureName:Microsoft-Hyper-V-Hypervisor
    
  3. 執行下列命令來新增「隔離的使用者模式」:

    dism /image:<WIM file name> /Enable-Feature /FeatureName:IsolatedUserMode
    

注意  

您也可以使用 DISM 或 Configuration Manager,將這些功能新增至線上映像中。

 

開啟 Credential Guard

如果您不使用「群組原則」,您可以使用登錄來啟用 Credential Guard。

Mt483740.wedge(zh-tw,VS.85).gif使用登錄來開啟 Credential Guard

  1. 開啟「登錄編輯程式」。

  2. 啟用虛擬式安全性:

    • 瀏覽到 [HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\DeviceGuard]。

    • 新增一個名為 EnableVirtualizationBasedSecurity 的新 DWORD 值。若要啟用虛擬式安全性,請將此登錄設定的值設定為 1,若要將它停用,請將值設定為 0。

    • 新增一個名為 RequirePlatformSecurityFeatures 的新 DWORD 值。若只要使用 [安全開機],請將此登錄設定的值設定為 1,若要使用 [安全開機及 DMA 保護]****,請將值設定為 2。

  3. 啟用 Credential Guard:

    • 瀏覽到 [HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LSA]。

    • 新增一個名為 LsaCfgFlags 的新 DWORD 值。將此登錄設定的值設為 1 可連同 UEFI 鎖定一起啟用 Credential Guard;將其設為 2 可不連同鎖定一起啟用 Credential Guard;將其設為 0 可停用 Credential Guard。

  4. 結束登錄編輯程式。

注意  

您也可以透過設定 FirstLogonCommands 自動安裝設定中的登錄項目來開啟 Credential Guard。

 

移除 Credential Guard

如果您必須移除電腦上的 Credential Guard,您需要執行下列動作:

  1. 如果您使用「群組原則」,請停用您用來啟用 Credential Guard 的「群組原則」設定 ([電腦設定] -> [系統管理範本]**** -> [系統] -> [Device Guard]**** -> [開啟虛擬式安全性])。

  2. 刪除下列登錄設定:[HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\DeviceGuard\LsaCfgFlags]

  3. 使用 bcdedit 來刪除 Credential Guard EFI 變數。

Mt483740.wedge(zh-tw,VS.85).gif刪除 Credential Guard EFI 變數

  1. 從提升權限的命令提示字元中,輸入下列命令:

    mountvol X: /s
    copy %WINDIR%\System32\SecConfig.efi X:\EFI\Microsoft\Boot\SecConfig.efi /Y
    bcdedit /create {0cb3b571-2f2e-4343-a879-d86a476d7215} /d "DebugTool" /application osloader
    bcdedit /set {0cb3b571-2f2e-4343-a879-d86a476d7215} path "\EFI\Microsoft\Boot\SecConfig.efi"
    bcdedit /set {bootmgr} bootsequence {0cb3b571-2f2e-4343-a879-d86a476d7215}
    bcdedit /set {0cb3b571-2f2e-4343-a879-d86a476d7215} loadoptions DISABLE-LSA-ISO
    bcdedit /set {0cb3b571-2f2e-4343-a879-d86a476d7215} device partition=X:
    mountvol X: /d
    
  2. 重新啟動電腦。

  3. 接受停用 Credential Guard 的提示。

  4. 或者,您也可以停用虛擬式安全性功能來關閉 Credential Guard。

注意  

電腦必須要能夠存取網域控制站一次來解密內容 (例如以 EFS 加密的檔案)。

如果您想要同時關閉 Credential Guard 和虛擬式安全性,請在關閉所有虛擬式安全性「群組原則」和登錄設定之後,執行下列 bcdedit 命令:

bcdedit /set {0cb3b571-2f2e-4343-a879-d86a476d7215} loadoptions DISABLE-LSA-ISO,DISABLE-VBS

如需虛擬式安全性和 Device Guard 的詳細資訊,請參閱 Device Guard 部署指南

 

檢查 Credential Guard 是否正在執行

您可以使用「系統資訊」來確保 Credential Guard 正在電腦上執行。

  1. 按一下 [開始],輸入 msinfo32.exe,然後按一下 [系統資訊]。

  2. 按一下 [系統摘要]****。

  3. 確認 [Credential Guard] 顯示在 [Device Guard 安全性服務執行中]**** 旁邊。

    這裡提供一個範例:

    Mt483740.CredGuard_MSInfo32(zh-tw,VS.85).png

使用 Credential Guard 時的考量

  • 如果是在裝置加入網域後才啟用 Credential Guard,則使用者和裝置密碼有可能已經外洩。我們建議您在電腦加入網域之前先啟用 Credential Guard。

  • 您應該定期檢閱已啟用 Credential Guard 的電腦。透過安全性稽核原則或 WMI 查詢,即可完成這項操作。以下是要尋找的 WinInit 事件識別碼的清單:

    • 事件識別碼 13:Credential Guard (LsaIso.exe) 已啟動,且將會保護 LSA 認證。

    • 事件識別碼 14Credential Guard (LsaIso.exe) 設定:0x1, 0

      • 第一個變數:0x1 表示已將 Credential Guard 設定為執行。0x0 表示未將它設定為執行。
      • 第二個變數:0 表示已將它設定為以保護模式執行。1 表示已將它設定為以測試模式執行。這個變數應一律為 0。
    • 事件識別碼 15:已設定 Credential Guard (LsaIso.exe),但安全核心並未執行;正在未使用 Credential Guard 的情況下繼續。

    • 事件識別碼 16:Credential Guard (LsaIso.exe) 無法啟動:[錯誤碼]

    • 事件識別碼 17:讀取 Credential Guard (LsaIso.exe) UEFI 設定時發生錯誤:[錯誤碼]

    您也可以在 [Microsoft] -> [Windows]**** -> [Kernel-Boot] 事件來源中檢查下列事件,驗證 TPM 已使用於金鑰保護。如果您正在執行 TPM,TPM PCR 遮罩值將會是 0 以外的值。

    • 事件識別碼 51:佈建 VSM 主要加密金鑰。使用快取複本狀態:0x0。解除密封快取複本狀態:0x1。新的金鑰產生狀態:0x1。密封狀態:0x1。TPM PCR 遮罩:0x0。
  • 密碼仍是弱式密碼,因此我們建議您的組織部署 Credential Guard,不要使用密碼而使用其他驗證方法,例如實體智慧卡、虛擬智慧卡,Microsoft Passport 或 Microsoft Passport for Work。

  • 有些協力廠商「安全性支援提供者」(SSP 和 AP) 可能與 Credential Guard 不相容。Credential Guard 不允許協力廠商 SSP 向 LSA 要求密碼雜湊。不過,當使用者登入及/或變更其密碼時,SSP 和 AP 仍然會取得密碼通知。不支援在自訂的 SSP 與 AP 內使用任何未記載的 API。我們建議將自訂的 SSP/AP 實作對照 Credential Guard 進行測試,以確保 SSP 與 AP 不會依存於任何未記載或不支援的行為。例如,不支援使用 KerbQuerySupplementalCredentialsMessage API。您不應該使用自訂的 SSP 與 AP 來取代 NTLM 或 Kerberos SSP。如需詳細資訊,請參閱 MSDN 上的登錄及安裝安全性套件的相關限制

  • 隨著 Credential Guard 所提供之保護的深度和廣度增加,後續搭配執行 Credential Guard 的 Windows 10 版本可能會影響過去可行的案例。例如,Credential Guard 可能會封鎖特定類型之認證或特定元件的使用,以防止惡意程式碼利用漏洞。 因此,我們建議在將有執行 Credential Guard 的裝置升級之前,先針對組織中操作所需的案例進行測試。

  • 如果您使用以 MS-CHAPv2 為基礎的 Wi-Fi 和 VPN 端點,則它們會容易遭受到與 NTLMv1 類似的攻擊。我們建議組織針對 Wi-Fi 和 VPN 連線,使用以憑證為基礎的驗證。

  • 自 Windows 10 版本 1511 起,使用「認證管理員」儲存的網域認證會受 Credential Guard 保護。「認證管理員」可讓您儲存認證,例如您用來登入網站或網路上其他電腦的使用者名稱和密碼。下列考量適用於「認證管理員」的 Credential Guard 保護:

    • 如果沒有提供密碼,則遠端桌面服務所儲存的認證無法用來從遠端連線到另一部電腦。

    • 從「認證管理員」擷取衍生的網域認證的應用程式將無法再使用那些認證。

    • 如果認證是從已經開啟 Credential Guard 的電腦上備份,您將無法使用「認證管理員」控制面板還原認證。如果需要備份您的認證,您必須在啟用 Credential Guard 之前進行備份。否則,您將無法還原這些認證。

不受 Credential Guard 保護的案例

有些儲存認證的方式並不受 Credential Guard 保護,包括:

  • 管理 Windows 功能保護以外認證的軟體

  • 本機帳戶與 Microsoft 帳戶

  • 執行特定伺服器角色 (例如網域控制站或「遠端桌面閘道」) 的 Windows Server 2016 Technical Preview 伺服器。如果您使用 Windows Server 2016 Technical Preview 伺服器做為用戶端電腦,則它會獲得與它執行「Windows 10 企業版」時相同的保護。

  • 鍵盤記錄木馬程式

  • 實體攻擊

  • 無法防止在電腦上有惡意程式碼的攻擊者使用與任何認證關聯的權限。我們建議針對高價值的帳戶 (例如 IT 專業人員和可存取您組織中高價值資產的使用者),使用專用的電腦。

其他安全防護功能

Credential Guard 可以提供安全防護功能來抵禦對衍生的認證的攻擊,並防止遭竊的認證被用在其他地方。不過,即使衍生的認證受到 Credential Guard 保護,電腦仍然會 容易遭受某些攻擊。這些攻擊可能包含濫用權限並使用直接來自受連累裝置的衍生的認證、重複使用先前在 Device Guard 之前遭竊的認證,以及不當使用管理工具和弱式應用程式設定。因此,也必須部署其他安全防護功能,讓網域環境變得更健全。

認證竊取攻擊可讓攻擊者從一部裝置竊取密碼,然後從另一部裝置使用這些密碼。藉由在 Windows Server 2012 R2 或更新版本的網域中部署驗證原則搭配複合驗證,將可限制使用者只能從已加入網域的特定裝置進行登入。不過,由於裝置也使用共用密碼來進行驗證,因此攻擊者也可以竊取這些密碼。藉由部署裝置憑證搭配 Credential Guard,驗證原則將可要求裝置使用其私密金鑰進行驗證。這可防止使用遭竊裝置上的共用密碼搭配遭竊的使用者密碼或 Kerberos 祕密金鑰,以使用者身分進行登入。

裝置憑證驗證有下列需求:

  • 裝置網域是 Windows Server 2012 或更新的版本,並且所有網域控制站都具有符合嚴格 KDC 驗證 (KDC EKU 存在且 DNS 網域名稱符合 SubjectAltName (SAN) 延伸的 DNSName 欄位) 的憑證。

  • Windows 10 裝置具有在企業存放區中簽發網域控制站憑證的 CA。

  • 建立一個處理程序,以類似於您建立使用者的身分識別和可信度的方式,確保裝置的身分識別和可信度,然後再為它們簽發智慧卡。

其他群組原則設定

有幾個您可以啟用來提供更多認證攻擊防護的「群組原則」設定:

  • 在網域控制站上,使用「群組原則」來設定 KDC 對宣告、複合驗證及 Kerberos 保護系統的支援。將 [KDC 支援宣告、複合驗證以及 Kerberos 保護]「群組原則」設定設定為 [支援]**** 或 [永遠提供宣告]。

  • 在執行 Windows 10 的裝置上,您也可以使用「群組原則」來開啟它。若要這麼做,請啟用 [電腦設定]**** -> [系統管理範本] -> [系統]**** -> [Kerberos] 底下的 [Kerberos 用戶端支援宣告、複合驗證以及 Kerberos 保護]**** 和 [一律先傳送複合驗證]「群組原則」設定。

複合驗證

複合驗證會在向網域和資源進行驗證的期間,將裝置識別身分新增到使用者的密碼中。如果沒有複合驗證,則只會驗證使用者的密碼。使用複合驗證時,Kerberos 用戶端必須同時具有使用者和裝置的密碼。

啟用複合驗證也會啟用 Kerberos 保護,這提供兩個額外的好處:

  • 已加入網域之裝置上的使用者驗證將受到保護。這表示網路擷取將會包含已加密的 Kerberos 初始驗證。在沒有適當裝置金鑰的情況下,可保護 Kerberos AS-REQ 不受離線字典攻擊危害。

  • KDC 錯誤會經過簽署,這可提供不受錯誤詐騙攻擊的保護。

部署電腦憑證

如果您組織中的網域控制站執行 Windows Server 2016 Technical Preview,則在已啟用 Credential Guard 且電腦已加入網域的情況下,執行 Windows 10 的裝置會自動註冊電腦憑證。

如果網域控制站執行 Windows Server 2012 R2,則必須手動在每個裝置上佈建電腦憑證。您可以在網域控制站或憑證授權單位上建立憑證範本,然後將電腦憑證部署到每個裝置,來執行這項操作。

將智慧卡簽發給使用者所使用的相同安全性程序應該也適用於電腦憑證。

  1. 從「憑證管理員」主控台,在 [憑證範本]**** 上按一下滑鼠右鍵,然後按一下 [管理]。

  2. 在 [工作站驗證]**** 上按一下滑鼠右鍵,然後按一下 [複製範本]。

  3. 在新範本上按一下滑鼠右鍵,然後按一下 [內容]****。

  4. 在 [延伸] 索引標籤上,按一下 [應用程式原則]****,然後按一下 [編輯]。

  5. 按一下 [用戶端驗證]****,然後按一下 [移除]。

  6. 新增 ID-PKInit-KPClientAuth EKU。按一下 [新增]****,按一下 [全新的],然後指定下列值:

    • 名稱:Kerberos 用戶端驗證

    • 物件識別碼:1.3.6.1.5.2.3.4

  7. 在 [延伸]**** 索引標籤上,按一下 [發行原則],然後按一下 [編輯]****。

  8. 在 [發行原則] 底下,按一下 [高度保證]****。

  9. 在 [主體名稱] 索引標籤上,取消選取 [DNS 名稱]**** 核取方塊,然後選取 [使用者主體名稱 (UPN)] 核取方塊。

在執行 Credential Guard 的裝置上,執行下列命令來註冊使用電腦驗證憑證的裝置:

CertReq -EnrollCredGuardCert MachineAuthentication

注意  

在註冊電腦驗證憑證之後,您必須重新啟動裝置。

 

將發行原則連結到群組

藉由使用驗證原則,您可以確保使用者只會登入執行 Credential Guard 的裝置。不過,在部署驗證原則之前,您必須先執行幾個指令碼來設定您的環境。

  • get-IssuancePolicy.ps1 會顯示憑證授權單位上所有可用的發行原則。

    請從 Windows PowerShell 命令提示字元,執行下列命令:

    .\get-IssuancePolicy.ps1 –LinkedToGroup:All
    
  • set-IssuancePolicyToGroupLink.ps1 會建立萬用安全性群組、建立組織單位,然後將發行原則連結到該萬用安全性群組。

    請從 Windows PowerShell 命令提示字元,執行下列命令:

    .\set-IssuancePolicyToGroupLink.ps1 –IssuancePolicyName:”<name of issuance policy>” –groupOU:”<Name of OU to create>” –groupName:”<name of Universal security group to create>”
    

部署驗證原則

在設定驗證原則之前,您應該記錄 KDC 上任何套用驗證原則失敗的嘗試。若要這麼做,請在「事件檢視器」中,瀏覽到 [應用程式及服務記錄檔\Microsoft\Windows\驗證],在 [AuthenticationPolicyFailures-DomainController] 上按一下滑鼠右鍵,然後按一下 [啟用記錄]****。

現在您即可設定一個驗證原則來使用 Credential Guard。

Mt483740.wedge(zh-tw,VS.85).gif為 Credential Guard 新增驗證原則

  1. 確保您的網域控制站至少執行 Windows Server 2012 R2 網域功能等級。

  2. 建立一個安全性群組,此群組將用來識別要套用此驗證原則的電腦。

  3. 將電腦帳戶新增到此安全性群組。

  4. 開啟「Active Directory 管理中心」。

  5. 按一下 [驗證]、按一下 [新增]****,然後按一下 [驗證原則]。

  6. 在 [顯示名稱]**** 方塊中,輸入此驗證原則的名稱。

  7. 在 [帳戶] 標題底下,按一下 [新增]****。

  8. 在 [選取使用者、電腦或服務帳戶] 對話方塊中,輸入使用者帳戶的名稱,然後按一下 [確定]****。

  9. 在 [使用者] 標題底下,按一下適用於使用者帳戶的 [編輯]**** 按鈕。

  10. 按一下 [新增條件]。

  11. 在 [編輯存取控制項條件]**** 方塊中,確定其讀起來是 [使用者] > [群組]**** > [成員隸屬每個] > [值]****,然後按一下 [新增項目]。

  12. 在 [選取使用者、電腦或服務帳戶]**** 對話方塊中,輸入使用 set-IssuancePolicyToGroupLink 指令碼來建立之安全性群組的名稱,然後按一下 [確定]。

  13. 按一下 [確定]**** 以關閉 [編輯存取控制項條件] 方塊。

  14. 按一下 [確定]**** 以建立驗證原則。

  15. 關閉「Active Directory 管理中心」。

注意  

將強制執行模式下的驗證原則與 Credential Guard 一起部署時,使用者將無法使用未佈建電腦驗證憑證的裝置來進行登入。這同時適用於本機和遠端登入案例。

 

附錄:指令碼

此處提供本主題中提及的指令碼清單。

取得憑證授權單位上可用的發行原則

將此指令碼檔案儲存為 get-IssuancePolicy.ps1。

#######################################
##     Parameters to be defined      ##
##     by the user                   ##
#######################################

Param (
$Identity,
$LinkedToGroup
)

#######################################
##     Strings definitions           ##
#######################################
Data getIP_strings {
# culture=\"en-US\"
ConvertFrom-StringData -stringdata @'
help1 = This command can be used to retrieve all available Issuance Policies in a forest. The forest of the currently logged on user is targetted.
help2 = Usage:
help3 = The following parameter is mandatory:
help4 = -LinkedToGroup:<yes|no|all>
help5 = "yes" will return only Issuance Policies that are linked to groups. Checks that the linked Issuance Policies are linked to valid groups.
help6 = "no" will return only Issuance Policies that are not currently linked to any group. 
help7 = "all" will return all Issuance Policies defined in the forest. Checks that the linked Issuance policies are linked to valid groups.
help8 = The following parameter is optional:
help9 = -Identity:<Name, Distinguished Name or Display Name of the Issuance Policy that you want to retrieve>. If you specify an identity, the option specified in the "-LinkedToGroup" parameter is ignored.
help10 = Output: This script returns the Issuance Policy objects meeting the criteria defined by the above parameters.
help11 = Examples:
errorIPNotFound = Error: no Issuance Policy could be found with Identity "{0}"
ErrorNotSecurity = Error: Issuance Policy "{0}" is linked to group "{1}" which is not of type "Security".
ErrorNotUniversal = Error: Issuance Policy "{0}" is linked to group "{1}" whose scope is not "Universal".
ErrorHasMembers = Error: Issuance Policy "{0}" is linked to group "{1}" which has a non-empty membership. The group has the following members:
LinkedIPs = The following Issuance Policies are linked to groups:
displayName = displayName : {0}
Name = Name : {0}
dn = distinguishedName : {0}
        InfoName = Linked Group Name: {0} 
        InfoDN = Linked Group DN: {0}   
NonLinkedIPs = The following Issuance Policies are NOT linked to groups:
'@
}

##Import-LocalizedData getIP_strings


import-module ActiveDirectory


#######################################
##           Help                    ##
#######################################

function Display-Help {

    ""
    $getIP_strings.help1
    ""
$getIP_strings.help2
""
$getIP_strings.help3
"     " + $getIP_strings.help4
"             " + $getIP_strings.help5
    "             " + $getIP_strings.help6
    "             " + $getIP_strings.help7
""
$getIP_strings.help8
    "     " + $getIP_strings.help9
    ""
    $getIP_strings.help10
""
""    
$getIP_strings.help11
    "     " + '$' + "myIPs = .\get-IssuancePolicy.ps1 -LinkedToGroup:All"
    "     " + '$' + "myLinkedIPs = .\get-IssuancePolicy.ps1 -LinkedToGroup:yes"
    "     " + '$' + "myIP = .\get-IssuancePolicy.ps1 -Identity:""Medium Assurance"""
""
}


$root = get-adrootdse
$domain = get-addomain -current loggedonuser
$configNCDN = [String]$root.configurationNamingContext


if ( !($Identity) -and !($LinkedToGroup) ) {
display-Help
break
}

if ($Identity) {
    $OIDs = get-adobject -Filter {(objectclass -eq "msPKI-Enterprise-Oid") -and ((name -eq $Identity) -or (displayname -eq $Identity) -or (distinguishedName -like $Identity)) } -searchBase $configNCDN -properties *

    if ($OIDs -eq $null) {
$errormsg = $getIP_strings.ErrorIPNotFound -f $Identity
write-host $errormsg -ForegroundColor Red
    }
    
    foreach ($OID in $OIDs) {
    
        if ($OID."msDS-OIDToGroupLink") {
            # In case the Issuance Policy is linked to a group, it is good to check whether there is any problem with the mapping.
            $groupDN = $OID."msDS-OIDToGroupLink"
            $group = get-adgroup -Identity $groupDN
    $groupName = $group.Name
            
            # Analyze the group
            if ($group.groupCategory -ne "Security") {
$errormsg = $getIP_strings.ErrorNotSecurity -f $Identity, $groupName
                write-host $errormsg -ForegroundColor Red
            }
            if ($group.groupScope -ne "Universal") {
                $errormsg = $getIP_strings.ErrorNotUniversal -f $Identity, $groupName
write-host $errormsg -ForegroundColor Red
            }
            $members = Get-ADGroupMember -Identity $group
            if ($members) {
                $errormsg = $getIP_strings.ErrorHasMembers -f $Identity, $groupName
write-host $errormsg -ForegroundColor Red
                foreach ($member in $members) {
                    write-host "          "  $member -ForeGroundColor Red
                }
            }
        }
        
    }
    return $OIDs
    break
}

if (($LinkedToGroup -eq "yes") -or ($LinkedToGroup -eq "all")) {
    $LDAPFilter = "(&(objectClass=msPKI-Enterprise-Oid)(msDS-OIDToGroupLink=*)(flags=2))"
    $LinkedOIDs = get-adobject -searchBase $configNCDN -LDAPFilter $LDAPFilter -properties *

    write-host ""    
    write-host "*****************************************************"
    write-host $getIP_strings.LinkedIPs
    write-host "*****************************************************"
    write-host ""
    if ($LinkedOIDs -ne $null){
      foreach ($OID in $LinkedOIDs) {
  
          # Display basic information about the Issuance Policies
          ""
  $getIP_strings.displayName -f $OID.displayName
  $getIP_strings.Name -f $OID.Name
  $getIP_strings.dn -f $OID.distinguishedName
       
          
          # Get the linked group.
          $groupDN = $OID."msDS-OIDToGroupLink"
          $group = get-adgroup -Identity $groupDN
          $getIP_strings.InfoName -f $group.Name
          $getIP_strings.InfoDN -f $groupDN
          
          # Analyze the group
          $OIDName = $OID.displayName
    $groupName = $group.Name
          if ($group.groupCategory -ne "Security") {
          $errormsg = $getIP_strings.ErrorNotSecurity -f $OIDName, $groupName
          write-host $errormsg -ForegroundColor Red
          }
          if ($group.groupScope -ne "Universal") {
          $errormsg = $getIP_strings.ErrorNotUniversal -f $OIDName, $groupName
          write-host $errormsg -ForegroundColor Red
          }
          $members = Get-ADGroupMember -Identity $group
          if ($members) {
          $errormsg = $getIP_strings.ErrorHasMembers -f $OIDName, $groupName
          write-host $errormsg -ForegroundColor Red
              foreach ($member in $members) {
                  write-host "          "  $member -ForeGroundColor Red
              }
          }
          write-host ""
      }
    }else{
write-host "There are no issuance policies that are mapped to a group"
    }
    if ($LinkedToGroup -eq "yes") {
        return $LinkedOIDs
        break
    }
}    

if (($LinkedToGroup -eq "no") -or ($LinkedToGroup -eq "all")) {  
    $LDAPFilter = "(&(objectClass=msPKI-Enterprise-Oid)(!(msDS-OIDToGroupLink=*))(flags=2))"
    $NonLinkedOIDs = get-adobject -searchBase $configNCDN -LDAPFilter $LDAPFilter -properties *

    write-host ""    
    write-host "*********************************************************"
    write-host $getIP_strings.NonLinkedIPs
    write-host "*********************************************************"
    write-host ""
    if ($NonLinkedOIDs -ne $null) {
      foreach ($OID in $NonLinkedOIDs) {

# Display basic information about the Issuance Policies
write-host ""
$getIP_strings.displayName -f $OID.displayName
$getIP_strings.Name -f $OID.Name
$getIP_strings.dn -f $OID.distinguishedName
write-host ""
      }
    }else{
write-host "There are no issuance policies which are not mapped to groups"
    }
    if ($LinkedToGroup -eq "no") {
        return $NonLinkedOIDs
        break
    }
}

注意  

如果您無法執行此指令碼,請嘗試取代 ConvertFrom-StringData 參數之後的單引號。

 

將發行原則連結到群組

將此指令碼檔案儲存為 set-IssuancePolicyToGroupLink.ps1。

#######################################
##     Parameters to be defined      ##
##     by the user                   ##
#######################################

Param (
$IssuancePolicyName,
$groupOU,
$groupName
)

#######################################
##     Strings definitions           ##
#######################################

Data ErrorMsg {
# culture=\"en-US\"
ConvertFrom-StringData -stringdata @'
help1 = This command can be used to set the link between a certificate issuance policy and a universal security group.
help2 = Usage:
help3 = The following parameters are required:
help4 = -IssuancePolicyName:<name or display name of the issuance policy that you want to link to a group>
help5 = -groupName:<name of the group you want to link the issuance policy to>. If no name is specified, any existing link to a group is removed from the Issuance Policy.
help6 = The following parameter is optional:
help7 = -groupOU:<Name of the Organizational Unit dedicated to the groups which are linked to issuance policies>. If this parameter is not specified, the group is looked for or created in the Users container.
help8 = Examples:
help9 = This command will link the issuance policy whose display name is "High Assurance" to the group "HighAssuranceGroup" in the Organizational Unit "OU_FOR_IPol_linked_groups". If the group or the Organizational Unit do not exist, you will be prompted to create them.
help10 = This command will unlink the issuance policy whose name is "402.164959C40F4A5C12C6302E31D5476062" from any group.
MultipleIPs = Error: Multiple Issuance Policies with name or display name "{0}" were found in the subtree of "{1}"
NoIP = Error: no issuance policy with name or display name "{0}" could be found in the subtree of "{1}".
IPFound = An Issuance Policy with name or display name "{0}" was successfully found: {1}
MultipleOUs = Error: more than 1 Organizational Unit with name "{0}" could be found in the subtree of "{1}".
confirmOUcreation = Warning: The Organizational Unit that you specified does not exist. Do you want to create it?
OUCreationSuccess = Organizational Unit "{0}" successfully created.
OUcreationError = Error: Organizational Unit "{0}" could not be created.
OUFoundSuccess = Organizational Unit "{0}" was successfully found.
multipleGroups = Error: More than one group with name "{0}" was found in Organizational Unit "{1}".  
confirmGroupCreation = Warning: The group that you specified does not exist. Do you want to create it?
groupCreationSuccess = Univeral Security group "{0}" successfully created.
groupCreationError = Error: Univeral Security group "{0}" could not be created.
GroupFound = Group "{0}" was successfully found.
confirmLinkDeletion = Warning: The Issuance Policy "{0}" is currently linked to group "{1}". Do you really want to remove the link?
UnlinkSuccess = Certificate issuance policy successfully unlinked from any group.
UnlinkError = Removing the link failed.
UnlinkExit = Exiting without removing the link from the issuance policy to the group.
IPNotLinked = The Certificate issuance policy is not currently linked to any group. If you want to link it to a group, you should specify the -groupName option when starting this script.
ErrorNotSecurity = Error: You cannot link issuance Policy "{0}" to group "{1}" because this group is not of type "Security".
ErrorNotUniversal = Error: You cannot link issuance Policy "{0}" to group "{1}" because the scope of this group is not "Universal".
ErrorHasMembers = Error: You cannot link issuance Policy "{0}" to group "{1}" because it has a non-empty membership. The group has the following members:
ConfirmLinkReplacement = Warning: The Issuance Policy "{0}" is currently linked to group "{1}". Do you really want to update the link to point to group "{2}"?
LinkSuccess = The certificate issuance policy was successfully linked to the specified group.
LinkError = The certificate issuance policy could not be linked to the specified group.
ExitNoLinkReplacement = Exiting without setting the new link.
'@
}

# import-localizeddata ErrorMsg

function Display-Help {
""
write-host $ErrorMsg.help1
""
write-host $ErrorMsg.help2
""
write-host $ErrorMsg.help3
write-host "`t" $ErrorMsg.help4
write-host "`t" $ErrorMsg.help5
""
write-host $ErrorMsg.help6
write-host "`t" $ErrorMsg.help7
""
""
write-host $ErrorMsg.help8
""
write-host $ErrorMsg.help9
".\Set-IssuancePolicyToGroupMapping.ps1 -IssuancePolicyName ""High Assurance"" -groupOU ""OU_FOR_IPol_linked_groups"" -groupName ""HighAssuranceGroup"" "
""
write-host $ErrorMsg.help10
'.\Set-IssuancePolicyToGroupMapping.ps1 -IssuancePolicyName "402.164959C40F4A5C12C6302E31D5476062" -groupName $null '
""
}



# Assumption:  The group to which the Issuance Policy is going 
#              to be linked is (or is going to be created) in 
#              the domain the user running this script is a member of.
import-module ActiveDirectory
$root = get-adrootdse
$domain = get-addomain -current loggedonuser


if ( !($IssuancePolicyName) ) {
display-Help
break
}

#######################################
##     Find the OID object           ##
##     (aka Issuance Policy)         ##
#######################################

$searchBase = [String]$root.configurationnamingcontext
$OID = get-adobject -searchBase $searchBase -Filter { ((displayname -eq $IssuancePolicyName) -or (name -eq $IssuancePolicyName)) -and (objectClass -eq "msPKI-Enterprise-Oid")} -properties *

if ($OID -eq $null) {
$tmp = $ErrorMsg.NoIP -f $IssuancePolicyName, $searchBase  
write-host $tmp -ForeGroundColor Red
break;
}
elseif ($OID.GetType().IsArray) {
$tmp = $ErrorMsg.MultipleIPs -f $IssuancePolicyName, $searchBase  
write-host $tmp -ForeGroundColor Red
break;
}
else {
$tmp = $ErrorMsg.IPFound -f $IssuancePolicyName, $OID.distinguishedName
write-host $tmp -ForeGroundColor Green
}



#######################################
##  Find the container of the group  ##
#######################################

if ($groupOU -eq $null) {
# default to the Users container
$groupContainer = $domain.UsersContainer
} 
else {
$searchBase = [string]$domain.DistinguishedName
$groupContainer = get-adobject -searchBase $searchBase -Filter { (Name -eq $groupOU) -and (objectClass -eq "organizationalUnit")}
if ($groupContainer.count -gt 1) {
$tmp = $ErrorMsg.MultipleOUs -f $groupOU, $searchBase
write-host $tmp -ForegroundColor Red
break;
}
elseif ($groupContainer -eq $null) {
$tmp = $ErrorMsg.confirmOUcreation
write-host $tmp " ( (y)es / (n)o )" -ForegroundColor Yellow -nonewline
$userChoice = read-host
if ( ($userChoice -eq "y") -or ($userChoice -eq "yes") ) {
new-adobject -Name $groupOU -displayName $groupOU -Type "organizationalUnit" -ProtectedFromAccidentalDeletion $true -path $domain.distinguishedName
if ($?){
$tmp = $ErrorMsg.OUCreationSuccess -f $groupOU
write-host $tmp -ForegroundColor Green
}
else{
$tmp = $ErrorMsg.OUCreationError -f $groupOU
write-host $tmp -ForeGroundColor Red 
break;
}
$groupContainer = get-adobject -searchBase $searchBase -Filter { (Name -eq $groupOU) -and (objectClass -eq "organizationalUnit")}
}
else {
break;
}
}
else {
$tmp = $ErrorMsg.OUFoundSuccess -f $groupContainer.name
write-host $tmp -ForegroundColor Green
}
}

#######################################
##  Find the group               ##
#######################################

if (($groupName -ne $null) -and ($groupName -ne "")){
##$searchBase = [String]$groupContainer.DistinguishedName
$searchBase = $groupContainer
$group = get-adgroup -Filter { (Name -eq $groupName) -and (objectClass -eq "group") } -searchBase $searchBase
if ($group -ne $null -and $group.gettype().isarray) {
$tmp = $ErrorMsg.multipleGroups -f $groupName, $searchBase
write-host $tmp -ForeGroundColor Red
break;
}
elseif ($group -eq $null) {
$tmp = $ErrorMsg.confirmGroupCreation
write-host $tmp " ( (y)es / (n)o )" -ForegroundColor Yellow -nonewline
$userChoice = read-host
if ( ($userChoice -eq "y") -or ($userChoice -eq "yes") ) {
new-adgroup -samAccountName $groupName -path $groupContainer.distinguishedName -GroupScope "Universal" -GroupCategory "Security"
if ($?){
$tmp = $ErrorMsg.GroupCreationSuccess -f $groupName
write-host $tmp -ForegroundColor Green
}else{
$tmp = $ErrorMsg.groupCreationError -f $groupName
write-host $tmp -ForeGroundColor Red 
break
}
$group = get-adgroup -Filter { (Name -eq $groupName) -and (objectClass -eq "group") } -searchBase $searchBase
}
else {
break;
}
}
else {
$tmp = $ErrorMsg.GroupFound -f $group.Name
write-host $tmp -ForegroundColor Green
}
} 
else {
## If the group is not specified, we should remove the link if any exists
if ($OID."msDS-OIDToGroupLink" -ne $null) {
$tmp = $ErrorMsg.confirmLinkDeletion -f $IssuancePolicyName, $OID."msDS-OIDToGroupLink"
write-host $tmp " ( (y)es / (n)o )" -ForegroundColor Yellow -nonewline
$userChoice = read-host
if ( ($userChoice -eq "y") -or ($userChoice -eq "yes") ) {
set-adobject -Identity $OID -Clear "msDS-OIDToGroupLink"
if ($?) {
$tmp = $ErrorMsg.UnlinkSuccess
write-host $tmp -ForeGroundColor Green
}else{
$tmp = $ErrorMsg.UnlinkError
write-host $tmp -ForeGroundColor Red
}
} 
else { 
$tmp = $ErrorMsg.UnlinkExit
write-host $tmp
break 
}
}
else {
$tmp = $ErrorMsg.IPNotLinked
write-host $tmp -ForeGroundColor Yellow
}
break;
}


#######################################
##  Verify that the group is         ##
##  Universal, Security, and         ## 
##  has no members                   ##
#######################################

if ($group.GroupScope -ne "Universal") {
$tmp = $ErrorMsg.ErrorNotUniversal -f $IssuancePolicyName, $groupName
write-host $tmp -ForeGroundColor Red
break;
}
if ($group.GroupCategory -ne "Security") {
$tmp = $ErrorMsg.ErrorNotSecurity -f $IssuancePolicyName, $groupName
write-host $tmp -ForeGroundColor Red
break;
}
$members = Get-ADGroupMember -Identity $group
if ($members -ne $null) {
$tmp = $ErrorMsg.ErrorHasMembers -f $IssuancePolicyName, $groupName
write-host $tmp -ForeGroundColor Red
foreach ($member in $members) {write-host "   $member.name" -ForeGroundColor Red}
break;
}


#######################################
##  We have verified everything. We  ##
##  can create the link from the     ## 
##  Issuance Policy to the group.    ##
#######################################

if ($OID."msDS-OIDToGroupLink" -ne $null) {
$tmp = $ErrorMsg.ConfirmLinkReplacement -f $IssuancePolicyName, $OID."msDS-OIDToGroupLink", $group.distinguishedName
write-host $tmp  "( (y)es / (n)o )" -ForegroundColor Yellow -nonewline
$userChoice = read-host
if ( ($userChoice -eq "y") -or ($userChoice -eq "yes") ) {
$tmp = @{'msDS-OIDToGroupLink'= $group.DistinguishedName}
set-adobject -Identity $OID -Replace $tmp
if ($?) {
$tmp = $Errormsg.LinkSuccess
write-host $tmp -Foreground Green
}else{
$tmp = $ErrorMsg.LinkError
write-host $tmp -Foreground Red
}
} else { 
$tmp = $Errormsg.ExitNoLinkReplacement
write-host $tmp
break 
}
}
else {
$tmp = @{'msDS-OIDToGroupLink'= $group.DistinguishedName}
set-adobject -Identity $OID -Add $tmp
if ($?) {
$tmp = $Errormsg.LinkSuccess
write-host $tmp -Foreground Green
}else{
$tmp = $ErrorMsg.LinkError
write-host $tmp -Foreground Red
}
}

注意  

如果您無法執行此指令碼,請嘗試取代 ConvertFrom-StringData 參數之後的單引號。

 

相關主題

Dave Probert 主講:Windows 10 中隔離的使用者模式 (第 9 頻道)

Logan Gabriel 主講:Windows 10 中隔離的使用者模式程序和功能 (第 9 頻道)

Dave Probert 主講:深入探討 Windows 10 隔離的使用者模式中的程序和功能 (第 9 頻道)

使用 Windows 10 隔離的使用者模式來減少認證竊取 (第 9 頻道)

在 Windows Kerberos 中啟用嚴格 KDC 驗證

Windows Server 2012 之 Kerberos 驗證的新功能

Windows Server 2008 R2 中 AD DS 的驗證機制保證逐步指南

信賴平台模組