匯報 TGT 在 Windows Server 中跨入信任的委派

套用到
Windows Server 2008 Windows Server 2008 R2 Windows Server 2012 Windows Server 2012 R2 Windows Server 2016 Windows Server 2019, all editions

摘要

森林信任提供一種方式,讓 Active Directory 樹系中的資源能夠信任來自另一個森林的身份。 這種信任可以雙向配置。 受信任森林是使用者身份的來源。 信任森林包含使用者驗證的資源。 受信任森林可以將使用者認證到信任森林,而不允許反向發生。

無限制 Kerberos 委派是一種機制,使用者將憑證傳送給服務,使該服務能代表使用者存取資源。 要啟用不受限制的 Kerberos 委派,必須將服務在 Active Directory 中的帳號標記為可信任委派。 如果使用者和服務屬於不同的森林,這就會造成問題。 服務林負責允許委派。 委派包含使用者森林中使用者的憑證。

允許一個森林做出影響另一個森林帳戶的安全決策,違反了森林間的安全邊界。 擁有信任森林的攻擊者可以請求委託 TGT 以取得可信任森林的身份,從而取得可信任森林中的資源。 這不適用於 KCD) (Kerberos 受限授權。

Windows Server 2012 引入了 Kerberos 全委派的森林邊界強制執行功能。 此功能新增了可信網域的政策,以依信任方式停用無限制委派。 此功能的預設設定允許不受限制的委派,且不安全。

以下版本的 Windows Server 有提供安全強化的匯報:

  • Windows Server 2019
  • Windows Server 2016
  • Windows Server 2012 R2
  • Windows Server 2012

此功能連同安全強化的變更,被回溯至以下版本:

  • Windows Server 2008 R2
  • Windows Server 2008

這些安全更新會做出以下變更:

  • 在安裝 5 月 14 日更新及之後的更新後,無限制 Kerberos 委派預設會被禁用,無論是新森林還是新的外部信任。
  • 在安裝 2019 年 7 月 9 日及之後更新後,森林 (新舊) 及外部信任的 Kerberos 委派功能會被停用。
  • 管理員可透過使用 5 月或更新版本的 NETDOM 與 AD PowerShell 模組,啟用無限制的 Kerberos 委派。

這些更新可能導致目前需要在森林或外部信任間無限制委派的應用程式產生相容性衝突。 這點在外部信任中尤其明顯,因為隔離旗標 (也稱為 SID 過濾) 預設啟用。 具體來說,對於使用無限制委派權限的服務,在你申請新工單時,認證請求會失敗。

關於發行日期,請參見匯報時間軸

因應措施

為了在具備 Enforcement for Forest Boundary for Kerberos Full Delegation 功能的 Windows Server 版本提供資料與帳號安全,您可以在安裝 2019 年 3 月更新後,透過將 Netdom 旗標 EnableTGTDelegation 設為 No,以以下方式封鎖 TGT 委派:


netdom.exe trust fabrikam.com /domain:contoso.com /EnableTGTDelegation:No

TGT 委派會在新舊的森林及外部信託中被封鎖,安裝 2019 年 5 月和 7 月更新後。

若要重新啟用跨信任委派,並回到原本的不安全配置,直到能啟用受限或資源型委派,請將 EnableTGTDelegation 旗標設為 「是」。

啟用 TGT 委派的 NETDOM 指令列如下:


netdom trust <TrustedDomainName > /domain:<TrustingDomainName > /EnableTgtDelegation:Yes

你可以在概念上將啟用 TGT 委派的 NETDOM 語法想像為:


netdom trust <domain that you are administering> /domain:<domain whose trust NETDOM is modifying> /EnableTgtDelegation:Yes

啟用 TGT 委派給 contoso.com 伺服器上的 fabrakam.com 使用者的 NETDOM 語法如下:


netdom.exe trust fabrikam.com /domain:contoso.com /EnableTGTDelegation:Yes

         附註

  • EnableTGTDelegation 旗標應設定在受信任網域 (fabrikam.com 此例中) 每個信任網域 (如 contoso.com) 。 旗標設定完成後,受信任網域將不再允許將 TGT 委派給信任網域。
  • EnableTGTDelegation 的安全狀態為 No.
  • 任何依賴於森林間無限制委派的應用程式或服務,當 EnableTGTDelegation 被手動或程式化設定為 「是」 時,都會失敗。安裝 2019 年 5 月和 2019 年 7 月更新後,EnableTGTDelegation 在新建及現有信託上預設為 NO 。 欲了解更多如何偵測此故障的資訊,請參閱 尋找依賴無限制委派的服務。 請參閱匯報時間軸,了解影響此變通方法應用的變更時間軸。
  • 欲了解更多關於 NETDOM 的資訊,請參閱 Netdom.exe 文件
  • 如果你必須在信任上啟用 TGT 委派,建議你在用戶端電腦上啟用 Windows Defender 憑證保護來降低風險。 這會阻止所有啟用且執行 Windows Defender 憑證保護的電腦進行無限制的委派。
  • 如果你有森林或外部信任,且其中任一設定為隔離,則無法啟用 TGT 委派,因為這兩個旗標語意相反。 隔離位元強化了參與網域間的安全邊界。 啟用 TGT 委派可消除網域間的安全邊界,因為信任網域可存取受信任網域使用者的憑證。 你不能兩頭討好。
    如果隔離標誌目前啟用,請在 NETDOM 命令列語法中加入 quarantine:no 旗標。
  • 如果你把 EnableTGTDelegation 改成 是,請依需求刪除 Kerberos 對起始及中繼來電者的工單。 相關刪除的工單是客戶在相關信託中轉介的 TGT。 這可能涉及多個裝置,視同一環境中委派跳數而定。

欲了解更多此程序資訊,請參閱以下 Windows IT Pro Center 文章:

使用 Windows Defender 憑證守護保護衍生網域憑證

匯報時間軸

2019 年 3 月 12 日

Kerberos 完整委派的森林邊界強制執行功能將以更新形式提供,以啟用本文頂端「套用」區塊中所有支援版本的 Windows Server 版本。 我們建議您將此功能設定在進入森林信託上。

本次更新將為以下系統新增 Kerberos 森林邊界執行全委派 功能:

  • Windows Server 2008 R2
  • Windows Server 2008

2019 年 5 月 14 日

更新釋出,新增了新的安全預設設定,並加入了新的森林與外部信任。 如果你需要跨信託委派, EnableTGTDelegation 旗標應在 2019 年 7 月 9 日更新安裝前設為 「是 」。 如果你不需要跨信託委派,就不應該設定 EnableTGTDelegation 旗標。 EnableTGTDelegation 旗標將被忽略,直到 2019 年 7 月 9 日的更新安裝完成,以便管理員有時間在需要時重新啟用無限制的 Kerberos 委派。

作為本次更新的一部分,對於新建立的信任, EnableTGTDelegation 旗標將預設為 「否 」。 這和之前的行為正好相反。 我們建議管理員改為重新配置受影響的服務,使用基於資源的受限委派。

欲了解更多如何偵測相容性問題的資訊,請參閱 「尋找依賴無限制委派的服務」。

2019 年 7 月 9 日

有一個更新強制執行森林與外部信任入站端的新預設行為。 對於使用無限制委派、涵蓋上述信任類型的服務的認證請求,將被驗證但不委派。 當服務嘗試執行委派操作時會失敗。

關於緩解措施,請參見「變通方法」部分。

尋找依賴不受限制委派的服務

若要掃描允許 TGT 委派的 Forest,並尋找允許無限制委派的安全主體,請在腳本檔中執行以下 PowerShell 腳本,例如 (Get-RiskyServiceAccountsByTrust.ps1 -Collect) :

注意

你也可以透過 -ScanAll 標記來跨不允許 TGT 委派的信託進行搜尋。

PowerShell 腳本

[CmdletBinding()]  
Param  
(  
    [switch]$Collect, 
    [switch]$ScanAll 
) 
 
if ($Debug) {  
    $DebugPreference = 'Continue'  
} 
else { 
    $DebugPreference = 'SilentlyContinue'  
} 

function Get-AdTrustsAtRisk 
{ 
    [CmdletBinding()]  
    Param  
    (  
        [string]$Direction = "Inbound", 
        [switch]$ScanAll 
    ) 
 
    if ($ScanAll) { 
        return get-adtrust -filter {Direction -eq $Direction} 
    } 
    else { 
        return get-adtrust -filter {Direction -eq $Direction -and TGTDelegation -eq $false} 
    } 
} 
 
function Get-ServiceAccountsAtRisk 
{ 
    [CmdletBinding()]  
    Param  
    (  
        [string]$DN = (Get-ADDomain).DistinguishedName, 
        [string]$Server = (Get-ADDomain).Name 
    ) 
 
    Write-Debug "Searching $DN via $Server" 
 
    $SERVER_TRUST_ACCOUNT = 0x2000  
    $TRUSTED_FOR_DELEGATION = 0x80000  
    $TRUSTED_TO_AUTH_FOR_DELEGATION= 0x1000000  
    $PARTIAL_SECRETS_ACCOUNT = 0x4000000    
 
    $bitmask = $TRUSTED_FOR_DELEGATION -bor $TRUSTED_TO_AUTH_FOR_DELEGATION -bor $PARTIAL_SECRETS_ACCOUNT  
  
$filter = @"  
(& 
  (servicePrincipalname=*) 
  (| 
    (msDS-AllowedToActOnBehalfOfOtherIdentity=*) 
    (msDS-AllowedToDelegateTo=*) 
    (UserAccountControl:1.2.840.113556.1.4.804:=$bitmask) 
  ) 
  (| 
    (objectcategory=computer) 
    (objectcategory=person) 
    (objectcategory=msDS-GroupManagedServiceAccount) 
    (objectcategory=msDS-ManagedServiceAccount) 
  ) 
) 
"@ -replace "[\s\n]", ''  
  
    $propertylist = @(  
        "servicePrincipalname",   
        "useraccountcontrol",   
        "samaccountname",   
        "msDS-AllowedToDelegateTo",   
        "msDS-AllowedToActOnBehalfOfOtherIdentity"  
    )  
 
    $riskyAccounts = @() 
 
    try { 
        $accounts = Get-ADObject -LDAPFilter $filter -SearchBase $DN -SearchScope Subtree -Properties $propertylist -Server $Server 
    } 
    catch { 
        Write-Warning "Failed to query $Server. Consider investigating seperately. $($_.Exception.Message)" 
    } 
              
    foreach ($account in $accounts) {  
        $isDC = ($account.useraccountcontrol -band $SERVER_TRUST_ACCOUNT) -ne 0  
        $fullDelegation = ($account.useraccountcontrol -band $TRUSTED_FOR_DELEGATION) -ne 0  
        $constrainedDelegation = ($account.'msDS-AllowedToDelegateTo').count -gt 0  
        $isRODC = ($account.useraccountcontrol -band $PARTIAL_SECRETS_ACCOUNT) -ne 0  
        $resourceDelegation = $account.'msDS-AllowedToActOnBehalfOfOtherIdentity' -ne $null  
      
        $acct = [PSCustomobject] @{  
            domain = $Server 
            sAMAccountName = $account.samaccountname  
            objectClass = $account.objectclass          
            isDC = $isDC  
            isRODC = $isRODC  
            fullDelegation = $fullDelegation  
            constrainedDelegation = $constrainedDelegation  
            resourceDelegation = $resourceDelegation  
        }  
 
        if ($fullDelegation) {  
            $riskyAccounts += $acct    
        } 
    }  
 
    return $riskyAccounts 
} 
 
function Get-RiskyServiceAccountsByTrust  
{ 
    [CmdletBinding()]  
    Param  
    ( 
        [switch]$ScanAll 
    ) 
     
    $riskyAccounts = @() 
 
    $trustTypes = $("Inbound", "Bidirectional") 
 
    foreach ($type in $trustTypes) { 
 
        $riskyTrusts = Get-AdTrustsAtRisk -Direction $type -ScanAll:$ScanAll 
 
        foreach ($trust in $riskyTrusts) { 
            $domain = $null 
     
            try { 
                $domain = Get-AdDomain $trust.Name -ErrorVariable eatError -ErrorAction Ignore 
            } catch { 
                write-debug $_.Exception.Message 
            } 
 
            if($eatError -ne $null) { 
                Write-Warning "Couldn't find domain: $($trust.Name)" 
            } 
 
            if ($domain -ne $null) { 
                $accts = Get-ServiceAccountsAtRisk -DN $domain.DistinguishedName -Server $domain.DNSRoot 
 
                foreach ($acct in $accts) { 
                    Write-Debug "Risky: $($acct.sAMAccountName) in $($acct.domain)" 
                }             
 
                $risky = [PSCustomobject] @{  
                    Domain = $trust.Name 
                    Accounts = $accts 
                } 
 
                $riskyAccounts += $risky 
            } 
        } 
    } 
 
    return $riskyAccounts 
} 
 
if ($Collect) { 
   Get-RiskyServiceAccountsByTrust -ScanAll:$ScanAll | Select-Object -expandProperty Accounts | format-table 
}

PowerShell 腳本的輸出會列出設定為執行域中輸入信任且已設定無限制委派的網域中的 Active Directory 安全主體。 輸出將類似以下範例。

範圍 sAMAccountName objectClass
partner.fabrikam.com 危險 user
partner.fabrikam.com LabsRV$ 電腦

透過 Windows 事件偵測無限制委派

當發出 Kerberos 工單時,Active Directory 網域控制器會記錄以下安全事件。 這些事件包含了關於目標領域的資訊。 你可以利用這些事件判斷是否在新入信託間使用無限制委派。

注意

檢查是否有包含與受信任網域名稱相符的 TargetDomainName 值的事件。

事件記錄檔 事件來源 事件識別碼 詳細資料
安全性 Microsoft-Windows-Security-Auditing 4768 一台Kerberos型TGT被配發。
安全性 Microsoft-Windows-Security-Auditing 4769 開立了 Kerberos 服務票。
安全性 Microsoft-Windows-Security-Auditing 4770 Kerberos 服務券已續期。

故障排除認證失敗

當無約束委派被停用時,若應用程式依賴無約束委派,可能會遇到相容性問題。 這些應用程式應配置為使用受限委派或基於資源的受限委派。 欲了解更多資訊,請參閱 Kerberos 受限授權總覽

依賴跨信任往返驗證的應用程式,則不支援使用受限委派。 例如,如果 Forest A 的使用者向 Forest B 的應用程式進行認證,而 Forest B 的應用程式試圖將工單重新委派回給 Forest A,則委派失敗。