RC4 仍被认为有害
作者:James Forshaw,Project Zero
我最近花了很多时间研究Windows身份验证实现,特别是Kerberos。2022年6月,我发现了一个关于RC4加密处理的有趣问题2310,如果你能够拦截Kerberos与KDC之间的网络流量,或者当用户配置为禁用典型预认证要求时,该漏洞允许你以另一个用户的身份进行身份验证。
这篇博客文章将更详细地介绍这个漏洞的工作原理,以及我如何仅需最少的暴力破解就能利用它。注意,我不会花时间完全解释Kerberos身份验证的工作原理,网上有很多资源。例如,在Microsoft工作的Steve Syfuhs的这篇博客文章是一个很好的入门。
背景
Kerberos是一个非常古老的身份验证协议。当前版本(v5)在1993年的RFC1510中描述,尽管在2005年的RFC4120中进行了更新。由于Kerberos的核心安全概念是使用加密来证明对用户凭据的了解,因此其设计允许协商客户端和服务器将使用的加密和校验和算法。
例如,当向密钥分发中心(KDC)发送初始身份验证服务请求(AS-REQ)时,客户端可以指定支持的加密算法列表,作为预定义的整数标识符,如下面RFC4120中ASN.1定义的片段所示。
KDC-REQ-BODY ::= SEQUENCE {
...
etype [8] SEQUENCE OF Int32 -- EncryptionType
-- in preference order --,
...
}
当服务器收到请求时,它会检查其支持的加密类型列表以及用户帐户支持的加密类型(基于用户配置的密钥),然后通常会选择客户端最偏好的算法。选定的算法随后用于任何需要加密的操作,例如生成会话密钥或如下所示的EncryptedData结构:
EncryptedData ::= SEQUENCE {
etype [0] Int32 -- EncryptionType --,
kvno [1] UInt32 OPTIONAL,
cipher [2] OCTET STRING -- ciphertext
}
KDC将返回一个身份验证服务回复(AS-REP)结构,其中包含用户的票据授予票据(TGT)和一个EncryptedData结构,该结构包含使用TGT请求服务票据所需的会话密钥。然后,用户可以使用与请求的加密算法对应的已知密钥来解密会话密钥并完成身份验证过程。

这种选择加密算法的灵活性既是福音也是诅咒。在Kerberos的原始实现中,仅支持DES加密,按照现代标准,DES过于脆弱。由于这种灵活性,开发人员能够通过RFC3962添加对AES的支持,所有现代版本的Windows都支持AES。然后可以在客户端和服务器之间协商使用双方都支持的最佳算法。然而,除非明确禁用弱算法,否则没有什么能阻止恶意客户端或服务器降级正在使用的加密算法,并尝试使用密码学攻击来破坏Kerberos。
现代版本的Windows已经开始禁用DES作为支持的加密算法,转而优先使用AES。然而,Windows还支持另一种默认启用的加密算法,即RC4。Microsoft在Windows 2000中为Kerberos使用了这种算法,尽管其文档在2006年RFC4757发布之前一直处于草案形式。
RC4流密码有许多重大弱点,但在引入时,它仍然被认为是比DES更好的选择,因为DES已被证明对硬件破解(如EFF的"Deep Crack")足够脆弱。使用RC4还有一个优点,即它相对容易在缩减密钥大小的模式下运行,以满足美国对密码系统的出口要求。
如果你阅读了Kerberos中RC4实现的RFC,你会注意到它并没有直接使用流密码。相反,它实施了各种保护措施来防范常见的密码学攻击:
- 加密数据受到带密钥的MD5 HMAC哈希的保护,以防止篡改,这对于像RC4这样的简单流密码来说是微不足道的。哈希数据包括一个随机生成的8字节"混淆器",因此即使对于相同的明文,哈希也是随机的。
- 用于加密的密钥派生自哈希和基础密钥。这与混淆器结合,几乎确保相同的密钥永远不会被重复用于加密。
- 基础密钥不是用户的密钥,而是派生自使用用户密钥对4字节消息类型值进行MD5 HMAC密钥处理的结果。例如,AS-REQ和AS-REP结构的消息类型不同。这可以防止攻击者将Kerberos用作加密预言机,并在协议的不相关部分重用现有的加密数据。
RC4的许多已知弱点与收集使用已知密钥加密的大量密文有关。由于RC4-HMAC算法的设计和Kerberos的一般功能原则,这实际上并不是一个重大问题。然而,Microsoft为Kerberos定义的RC4的最大弱点与其说是算法本身,不如说是从用户密码生成用户密钥的方式。
如前所述,Kerberos在Windows 2000中引入,以取代从NT 3.1开始使用的现有NTLM身份验证过程。然而,将现有用户迁移到新的身份验证协议存在一个问题。通常,KDC不存储用户的密码,而是存储该密码的哈希形式。对于NTLM,该哈希是使用MD4算法的单次传递从Unicode密码生成的。因此,为了提供简单的升级路径,Microsoft指定RC4-HMAC Kerberos密钥就是这个相同的哈希值。
由于MD4输出是16字节大小,暴力破解整个密钥是不切实际的。然而,哈希算法没有针对暴力破解攻击的保护措施,例如没有加盐或多重迭代。如果攻击者可以访问使用RC4-HMAC密钥加密的密文,他们可以尝试通过猜测密码来暴力破解密钥。由于用户倾向于选择弱密码或简单密码,这增加了暴力破解攻击成功恢复密钥的机会。有了密钥,攻击者就可以以该用户的身份向他们喜欢的任何服务进行身份验证。
为了获得适当的密文,攻击者可以向KDC发出请求并指定他们需要的加密类型。最著名的攻击技术称为Kerberoasting。该技术为目标用户请求服务票据,并将RC4-HMAC加密类型指定为首选算法。如果用户配置了RC4密钥,则返回的票据可以使用RC4-HMAC算法加密。由于票据数据的明文中有很大一部分是已知的,攻击者可以尝试从中暴力破解密钥。
这种技术确实要求攻击者在KDC上拥有一个帐户来发出服务票据请求。它还要求用户帐户配置了服务主体名称(SPN),以便可以请求票据。此外,现代版本的Windows Server将尝试通过强制使用从服务用户密码派生的AES密钥来阻止此攻击,即使攻击者只请求了RC4支持。
另一种形式称为AS-REP Roasting。这依赖于初始身份验证请求返回加密数据,而不是请求服务票据。当用户发送AS-REQ结构时,KDC可以查找用户,生成TGT及其关联的会话密钥,然后使用用户的RC4-HMAC密钥加密返回该信息。此时,KDC在返回加密数据之前尚未验证客户端是否知道用户的密钥,这允许攻击者暴力破解密钥,而无需自己在KDC上拥有帐户。
幸运的是,这种攻击更为罕见,因为Windows的Kerberos实现需要预认证。对于基于密码的登录,用户使用其加密密钥加密时间戳值,该值作为AS-REQ的一部分发送给KDC。KDC可以解密时间戳,检查其是否在一个小的时间窗口内,然后才返回用户的TGT和加密的会话密钥。这将阻止攻击者获取用于暴力破解攻击的加密数据。
然而,Windows确实支持一个用户帐户标志:"不需要Kerberos预认证"。如果在用户上启用此标志,则身份验证请求不需要发送加密的时间戳,AS-REP roasting过程可以继续进行。这应该是一种不常见的配置。
暴力破解攻击的成功完全取决于密码复杂性。通常,服务用户帐户具有长的、至少25个字符的随机生成密码,这几乎不可能被暴力破解。普通用户通常密码较弱,但他们不太可能配置SPN,这使他们成为Kerberoasting的目标。系统管理员也可以通过在整个网络中完全禁用RC4来缓解攻击,尽管出于兼容性原因,这通常不会做。一个更有限的替代方案是将敏感用户添加到受保护用户组,这为他们禁用RC4,而不必在整个网络中禁用。
Windows Kerberos加密实现
在研究Windows Defender Credential Guard(CG)时,我想了解Windows实际上如何实现各种Kerberos加密方案。CG至少对于Kerberos的主要目标是保护用户的密钥,特别是那些从密码和TGT的会话密钥派生出来的密钥。如果我能找到一种使用弱加密算法密钥的方法,我希望能够提取原始密钥,从而移除CG的保护。
加密算法都在CRYPTDLL.DLL库中实现,该库与客户端KERBEROS.DLL和服务器KDCSVC.DLL中的核心Kerberos库是分开的。这个接口是未公开的,但很容易弄清楚如何调用导出的函数。例如,要从加密类型整数获取"加密系统",可以使用以下导出函数:
NTSTATUS CDLocateCSystem(int etype, KERB_ECRYPT** engine);
KERB_ECRYPT结构包含引擎的配置信息,例如密钥大小以及将密码转换为密钥、生成新会话密钥以及执行加密或解密的函数指针。该结构还包含一个文本名称,以便您可以快速了解算法应该是什么,这导致了以下支持的系统:
名称 加密类型
RSADSI RC4-HMAC 24
RSADSI RC4-HMAC 23
Kerberos AES256-CTS-HMAC-SHA1-96 18
Kerberos AES128-CTS-HMAC-SHA1-96 17
Kerberos DES-CBC-MD5 3
Kerberos DES-CBC-CRC 1
RSADSI RC4-MD4 -128
Kerberos DES-Plain -132
RSADSI RC4-HMAC -133
RSADSI RC4 -134
RSADSI RC4-HMAC -135
RSADSI RC4-EXP -136
RSADSI RC4 -140
RSADSI RC4-EXP -141
Kerberos AES128-CTS-HMAC-SHA1-96-PLAIN -148
Kerberos AES256-CTS-HMAC-SHA1-96-PLAIN -149
具有正值的加密类型是RFC中定义的众所周知的加密类型,而负值是私有类型。因此,我决定花时间研究这些私有类型。大多数私有类型只是现有已知类型的细微变化,或者是具有旧编号的克隆。
然而,有一个类型与众不同,即"RSADSI RC4-MD4",类型值为-128。之所以不同,是因为其实现极其不安全,具体来说,它具有以下特性:
- 密钥大小为16字节,但加密算法只使用密钥的前8个字节。
- 密钥按原样使用,没有盲化,因此对于相同的用户密钥,密钥流始终相同。
- 忽略消息类型,这意味着在使用相同密钥时,Kerberos协议不同部分的密钥流是相同的。
- 加密数据不包含任何密码学哈希来防止篡改密文,这对于RC4基本上是灾难性的。尽管名称中包含MD4,但这仅用于从密码派生密钥,不用于任何消息完整性。
- 生成的会话密钥大小为16字节,但只包含40位(5字节)的随机性。剩余的11字节填充固定值0xAB。
从密码学的角度来看,说这很糟糕是轻描淡写的。幸运的是,可以安全地假设,虽然这个加密系统在CRYPTDLL中实现,但它不会被Kerberos使用?不幸的是,并非如此——当在AS-REQ中发送给KDC时,它完全被接受为有效的加密类型。那么问题就变成了如何利用这种行为?
在线利用(CVE-2022-33647)
我最初的想法是攻击会话密钥生成。如果我们能让服务器返回带有TGT的RC4-MD4会话密钥的AS-REP,那么该密钥的任何后续使用都可以被捕获并用于暴力破解40位密钥。那时,我们可以获取以明文发送的用户TGT和会话密钥,并以该认证用户的身份发出请求。
强制首选加密类型为RC4-MD4的最明显方法是拦截客户端和KDC之间的连接。AS-REQ的etype字段对于基于密码的身份验证不受保护。因此,代理可以修改该字段,使其仅包含RC4-MD4,然后发送给KDC。完成后,代理还需要捕获服务票据请求以获取加密数据进行暴力破解。
暴力破解40位密钥在技术上是可行的,至少如果你构建了一个巨大的查找表,但我觉得这不切实际。我意识到有一种更简单的方法,当客户端进行身份验证时,它通常会向KDC发送一个没有预认证时间戳的请求。只要预认证未被禁用,KDC就会向客户端返回一个带有KDC_ERR_PREAUTH_REQUIRED错误代码的Kerberos错误。
作为该错误响应的一部分,KDC还在PA-ETYPE-INFO2预认证数据结构中发送可接受的加密类型列表。此列表包含密码到密钥派生的附加信息,例如AES密钥的盐。客户端可以使用此信息正确生成用户的加密密钥。我注意到,如果你只返回一个指示支持RC4-MD4的条目,那么客户端将使用不安全的算法生成预认证时间戳。即使客户端最初没有请求RC4-MD4,这也能工作。
当KDC收到时间戳时,它将使用RC4-MD4算法进行验证,并返回AS-REP,其中TGT的RC4-MD4会话密钥使用与时间戳相同的密钥加密。由于RC4-MD4算法中已经提到的弱点,用于时间戳的密钥流必须与响应中用于加密会话密钥的密钥流相同。因此,我们可以发起已知明文攻击,从时间戳中恢复密钥流,并用其解密部分响应。

时间戳本身具有以下ASN.1结构,该结构使用可分辨编码规则(DER)序列化,然后进行加密。
PA-ENC-TS-ENC ::= SEQUENCE {
patimestamp [0] KerberosTime -- client's time --,
pausec [1] Microseconds OPTIONAL
}
AS-REP加密响应具有以下ASN.1结构:
EncASRepPart ::= SEQUENCE {
key [0] EncryptionKey,
last-req [1] LastReq,
nonce [2] UInt32,
key-expiration [3] KerberosTime OPTIONAL,
flags [4] TicketFlags,
authtime [5] KerberosTime,
starttime [6] KerberosTime OPTIONAL,
endtime [7] KerberosTime,
renew-till [8] KerberosTime OPTIONAL,
srealm [9] Realm,
sname [10] PrincipalName,
caddr [11] HostAddresses OPTIONAL
}
从这两个结构中我们可以看到,幸运的是,AS-REP中的会话密钥位于加密数据的开头。这意味着时间戳的已知部分和密钥之间应该存在重叠,允许我们应用密钥流恢复来解密会话密钥,而无需任何暴力破解。

该图表显示了时间戳和AS-REP开头的ASN.1 DER结构。绿色带有特定十六进制数字的值是我们知道或可以计算的明文,因为它们是ASN.1结构的一部分,例如类型和长度。我们可以看到,时间戳中的4个已知字节与会话密钥的前4个字节存在明显的重叠。由于末尾的填充,我们只需要密钥的前5个字节,但这确实意味着我们需要暴力破解最后一个密钥字节。
我们可以通过两种方式之一进行暴力破解。首先,我们可以向KDC发送带有用户TGT和猜测的会话密钥的服务票据请求,直到一个成功。这最多需要向KDC发送256个请求。或者,我们可以捕获来自客户端的服务票据请求,这可能在身份验证后立即发生。由于服务票据请求将使用会话密钥加密,我们可以在本地执行暴力破解攻击,而无需与KDC通信,这样会更快。无论选择哪种选项,这种方法都比暴力破解整个40位会话密钥快几个数量级。
执行此利用的最简单方法是拦截客户端到服务器的连接并修改流量。然而,由于没有预认证的初始请求只返回错误消息,因此很可能可以通过在KDC处理真实请求时向客户端注入响应来完成利用。这只需要能够监控网络流量并将任意网络流量注入该网络即可完成。不过,我尚未验证这种方法。
无需拦截的利用(CVE-2022-33679)
需要访问客户端到服务器的身份验证流量确实使这个漏洞看起来影响较小。尽管存在许多攻击者可以拦截的场景,例如共享WiFi网络,或者可能用于危害计算机帐户身份验证的物理攻击,这些攻击可能发生在域加入系统启动时。
如果存在一种攻击向量,可以在完全不需要真实Kerberos用户的情况下利用此漏洞,那将很有趣。我意识到,如果用户禁用了预认证,那么我们就拥有了执行攻击所需的一切。重要的是,如果预认证被禁用,我们可以为用户请求TGT,指定RC4-MD4加密,KDC将使用该算法加密返回AS-REP。
利用的关键是反转先前的攻击,不是使用时间戳来解密AS-REP,而是使用AS-REP来加密时间戳。然后,当时间戳值发送给KDC时,我们可以将其用作加密预言机,以暴力破解足够的密钥流字节来解密TGT的会话密钥。例如,如果我们移除时间戳的可选微秒组件,我们得到以下DER编码值:

该图表显示,目前时间戳(由T字节表示)和40位会话密钥之间没有重叠。然而,我们知道或者至少可以计算AS-REP的整个DER编码数据以覆盖整个时间戳缓冲区。我们可以用此来计算用户的RC4-MD4密钥的密钥流,而无需实际知道密钥本身。有了密钥流,我们可以加密一个有效的时间戳并将其发送给KDC。
如果KDC响应一个有效的AS-REP,那么我们知道我们已经正确计算了密钥流。我们如何用此来开始解密会话密钥?用于时间戳的KerberosTime值是一个ASCII字符串,形式为YYYYMMDDHHmmssZ。KDC将此字符串解析为适合服务器处理的格式。解析器将时间作为NUL终止的字符串,因此我们可以在字符串末尾添加一个额外的NUL字符,这不应影响解析。因此,我们可以将时间戳更改为以下内容:

我们现在可以猜测加密的NUL字符的值,并将新的时间戳发送给KDC。如果KDC返回错误,我们知道解析失败,因为它没有解密为NUL字符。然而,如果身份验证成功,我们猜测的值就是密钥流中的下一个字节,我们可以解密会话密钥的第一个字节。
此时我们遇到了一个问题,我们不能只是添加另一个NUL字符,因为解析器会在我们发送的第一个NUL字符处停止。即使该值没有解密为NUL,也无法检测到。这时第二个技巧就派上用场了,我们不是扩展字符串,而是滥用DER中编码值长度的方式。长度可以有两种形式之一:如果长度小于128,则为短形式;否则为长形式。
对于短形式,长度编码在单个字节中。对于长形式,第一个字节的最高位设置为1,低7位以大端格式编码长度值的尾随字节数。例如,在上图中,时间戳的总大小为0x14字节,以短形式存储。我们可以改为以任意大小的长形式编码长度,例如0x81 0x14、0x82 0x00 0x14、0x83 0x00 0x00 0x14等。下面显示的示例移动NUL字符以暴力破解会话密钥的下两个字节:

尽管从技术上讲,DER应该期望使用编码长度所需的最短形式,但Microsoft ASN.1库在解析时并不强制执行这一点,因此我们可以重复这种长度编码技巧来覆盖密钥中剩余的4个未知字节。由于利用一次暴力破解一个字节,我们需要发送给KDC的最大请求数是5 × 28,即1280个请求,而不是240个请求,后者大约是1万亿个。
即使请求数量如此之少,暴力破解密钥仍然可能需要大约30秒,但这仍然使其成为一种实用的攻击。尽管这在网络上会非常嘈杂,并且你会期望任何称职的EDR系统都会注意到,但到那时可能为时已晚。
修复措施
我能找到的唯一修复是在域控制器的KDC服务中。Microsoft添加了一个新标志,默认禁用RC4-MD4算法和加密类型为-133的旧RC4-HMAC变体。可以通过设置KDC配置注册表值AllowOldNt4Crypto来重新启用此行为。对NT4的引用很好地表明了此漏洞存在了多长时间,因为它可能早于Windows 2000中引入Kerberos。客户端可能也有一些更改,但我无法立即找到它们,而且花时间进行逆向工程并不值得。
在发现类似攻击之前降低风险是很好的。禁用RC4绝对是推荐的,但这可能会带来其自身的问题。如果这个特定漏洞在野外被利用,应该很容易检测到。此外,不寻常的Kerberos加密类型以及重复的登录尝试也会立即成为危险信号。
另一个选项是在环境中的所有客户端和KDC上强制执行Kerberos Armoring(FAST)。这将使检查和篡改Kerberos身份验证流量变得更加困难。然而,它并非万灵药,例如,要使FAST工作,域加入的计算机需要首先在没有FAST的情况下进行身份验证,以获得可用于保护通信的密钥。如果该初始身份验证被破坏,整个保护就会失效。