Windows Server中跨传入信任汇报 TGT 委派

应用对象
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 委派,从而授予对受信任林中的资源的访问权限。 这不适用于 Kerberos 约束委派 (KCD) 。

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 委派, (新的和现有的) 和外部信任。
  • 管理员可以使用 NETDOM 和 AD PowerShell 模块的 5 月或更高版本启用不受约束的 Kerberos 委派。

对于当前需要跨林或外部信任进行不受约束委派的应用程序,这些更新可能会导致兼容性冲突。 对于默认启用隔离标志 (也称为 SID 筛选) 的外部信任尤其如此。 具体而言,当你请求新票证时,对对列出的信任类型使用不受约束委派的服务的身份验证请求将失败。

有关发布日期,请参阅 汇报 时间线

解决方法

若要在具有 Kerberos 完整委派功能的林边界强制实施Windows Server版本上提供数据和帐户安全性,可以在跨传入信任安装 2019 年 3 月更新后阻止 TGT 委派,方法是将 netdom 标志 EnableTGTDelegation 设置为 No,如下所示:


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

在分别安装 2019 年 5 月和 7 月更新后,会在新的和现有的林和外部信任上阻止 TGT 委派。

若要跨信任重新启用委派,并在启用受约束或基于资源的委派之前返回到原始不安全配置,请将 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

用于在 contoso.com 服务器上启用 fabrakam.com 用户的 TGT 委派的 NETDOM 语法如下:


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

         注意

  • 在这种情况下,应在受信任的域 (fabrikam.com 设置 EnableTGTDelegation 标志,) 每个信任域 ((例如 contoso.com) )。 设置标志后,受信任的域将不再允许将 TGT 委托给信任域。
  • EnableTGTDelegation 的安全状态为 “否”。
  • 当 EnableTGTDelegation 手动或以编程方式设置为“是”时,任何依赖于跨林不受约束委派的应用程序或服务都将失败 安装 2019 年 5 月和 2019 年 7 月更新后,在新的和现有信任上,EnableTGTDelegation 默认为 NO 。 有关如何检测此故障的详细信息,请参阅 查找依赖于不受约束委派的服务。 有关影响此解决方法应用方式的更改时间线,请参阅汇报 时间线
  • 有关 NETDOM 的详细信息,请参阅 Netdom.exe 文档
  • 如果必须在信任上启用 TGT 委派,建议通过在客户端计算机上启用 Windows Defender Credential Guard 来缓解该风险。 这可以防止从已启用并运行 Windows Defender Credential Guard 的计算机进行所有不受约束的委派。
  • 如果你有一个林或外部信任,并且其中一个都配置为隔离,则无法启用 TGT 委派,因为这两个标志具有相反的语义。 隔离位可增强参与域之间的安全边界。 启用 TGT 委派会通过授予信任域访问来自受信任域的用户凭据的权限来清除域之间的安全边界。 不能双向操作。
    如果当前已启用隔离标志,请将 quarantine:no 标志添加到 NETDOM 命令行语法。
  • 如果将 EnableTGTDelegation 更改为 “是”,请根据需要删除发起方和中间调用方上的 Kerberos 票证。 要删除的相关票证是客户端跨相关信任的引荐 TGT。 这可能涉及多个设备,具体取决于给定环境中的委派跃点数。

有关此过程的详细信息,请参阅以下 Windows IT 专业人员中心文章:

使用 Windows Defender Credential Guard 保护派生域凭据

汇报 时间线

2019 年 3 月 12 日

针对 Kerberos 完整委派的林边界强制实施将作为更新提供,以便在本文顶部的“应用于”部分中列出的所有受支持的Windows Server版本上启用此功能。 建议在传入林信任上设置该功能。

此更新将向以下系统添加 “Kerberos 完整委派的林边界强制” 功能:

  • Windows Server 2008 R2
  • Windows Server 2008

2019 年 5 月 14 日

发布了一项更新,向新的林和外部信任添加了新的安全默认配置。 如果需要跨信任委派,则应在安装 2019 年 7 月 9 日更新之前将 EnableTGTDelegation 标志设置为 “是 ”。 如果不需要跨信任委派,则不应设置 EnableTGTDelegation 标志。 在安装 2019 年 7 月 9 日更新之前, 将忽略 EnableTGTDelegation 标志,以便管理员有时间在需要时重新启用不受约束的 Kerberos 委派。

作为此更新的一部分,对于任何新创建的信任, 默认情况下,EnableTGTDelegation 标志将设置为 “否 ”。 这与之前的行为相反。 建议管理员重新配置受影响的服务,以使用基于资源的约束委派。

有关如何检测兼容性问题的详细信息,请参阅 查找依赖于不受约束委派的服务

2019 年 7 月 9 日

发布了一个更新,该更新在林和外部信任的入站端强制实施新的默认行为。 对对列出的信任类型使用不受约束委派的服务的身份验证请求将进行身份验证,但不进行委派。 当服务尝试运行委托的操作时,它将失败。

有关缓解措施,请参阅“解决方法”部分。

查找依赖于不受约束的委派的服务

若要扫描具有允许 TGT 委派的传入信任的林,并查找允许不受约束委派的任何安全主体,请在脚本文件中运行以下 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 危险 用户
partner.fabrikam.com labsrv$ 计算机

通过 Windows 事件检测不受约束的委派

颁发 Kerberos 票证时,Active Directory 域控制器会记录以下安全事件。 事件包含有关目标域的信息。 可以使用事件来确定是否在传入信任之间使用不受约束的委派。

注意

检查包含与受信任域名匹配 的 TargetDomainName 值的事件。

事件日志 事件源 事件 ID 详细信息
安全性 Microsoft-Windows-Security-Auditing 4768 颁发了 Kerberos TGT。
安全性 Microsoft-Windows-Security-Auditing 4769 已颁发 Kerberos 服务票证。
安全性 Microsoft-Windows-Security-Auditing 4770 Kerberos 服务票证已续订。

排查身份验证失败问题

禁用不受约束委派后,如果应用程序依赖于不受约束的委派,则应用程序可能会出现这些更改的兼容性问题。 这些应用程序应配置为使用约束委派或基于资源的约束委派。 有关详细信息,请参阅 Kerberos 约束委派概述

使用约束委派不支持依赖跨信任的往返身份验证的应用程序。 例如,如果林 A 中的用户向林 B 中的应用程序进行身份验证,而林 B 中的应用程序正在尝试将票证委托回林 A,则委托会失败。