深入研究 NTLM 规范
关于版本协商的一些困惑
在开始研究之前,我试图回答“ NTLM 身份验证协议是否支持版本协商?”.当我开始研究 ADFS 中继攻击时,我假设版本协商是协议本身的一部分。因此,当我在规范中读到“ NTLM 身份验证版本不是由协议协商的”时,我感到非常困惑。在验证之前,它必须在客户端和服务器上进行配置。”图1显示了 NTLM 规范的相关部分。
图1: NTLM 规范的一部分,其中声明协议本身不协商 NTLM 身份验证版本。
然而,规范的另一部分使我更加困惑。当它描述 NTLMSSP _ NEGOTIATE _ EXTENDED _ SESSIONSECURITY 标志时,它提到当设置该标志时,该标志“请求使用 NTLM v2会话安全性。NTLMv2会话安全性是一个误称,因为它不是 NTLMv2。使用扩展会话安全性的是 NTLMv1,这也在 NTLMv2中。”(见图2)[1]。
图2: 描述扩展会话安全标志的 NTLM 规范的一部分。
尝试深入
接下来,我花了一些时间研究可以为 LmCompatibilityLevel 设置配置的各种选项,以进一步了解它们。图3中的表列出了此设置的有效选项。这也证实了我对 NTLM 规范的理解,即它们只支持配置一个选项或另一个选项,而不支持任何类型的协商机制。可以将服务器配置为支持 NTLMv1或 NTLMv2,但是客户端没有同时支持两者的选项。
图3: 来自 MSDN 的一个表,列出了 LmCompatibilityLevel 设置的可用选项。
后来,我发现了一篇 MSDN 文章,其中提到“与普通的 NTLMv1或 NTLMv2不同,NTLMv1 w/ESS 实际上是在客户端和服务器之间协商的(NTLMv1和 NTLMv2使用安全密钥 LmCompatibilityLevel 进行配置)。它是通过在谈判标志中设置一点来协商的,称为 P (MS-NLMP,Section 2.2.2.5)。”[2].这个声明使我有些困惑,因为它讨论了对附加安全特性的协商支持(请参见图4)。
图4: MSDN 文章的摘录,其中提供了有关 NTLMv1扩展会话安全性(ESS)的更多细节。
在深入研究了规范之后,我发现了如图5所示的伪代码,它显示了一些用于计算 NTLMv1中的challenge response的示例代码。我惊讶地注意到,这个版本向函数传递了一个 ClientChallenge 值,因为 NTLMv1实际上并不支持 NTLMv2这样的客户端challenge字段。
图5: 来自 NTLM 规范的伪代码,指示 NTLMv1如何计算 NtChallengeResponse 和 LmChallengeResponse。
Client Challenge从哪里发出?
在阅读了当时的规范之后,我不确定客户的质疑从何而来。但是,在图6中,我们可以在 go-ntlm 库源代码中看到客户机的 LmChallengeResponse 字段提示了这个Challenge。我们还可以看到,在 NTLMv2的情况下,这个Challenge是从 NTLMv2 _ CLIENT _ CHALLENGE 结构中读取的,该结构存储在 NTLMv2 _ RESPONSE 字段中,该字段存储在 NtChallengeResponse 字段中。
图6: go-ntlm 库中的源代码,它使用 NTLMv1从 LmChallengeResponse 字段读取客户端询问值。
设想一个场景,我们试图从配置为支持具有扩展会话安全性的 NTLMv1身份验证的客户端捕获 NTLMv1散列。在这种情况下,我们需要客户机对攻击者控制的1122334455667788Challenge进行加密,以便利用 crack.sh 站点提供的彩虹表服务。不幸的是,如果我们用来捕获hash的服务器表明它支持服务器发送给客户机的谈判标志中的扩展会话安全性,那么客户机生成的Challenge将会破坏这一点。在这种情况下,我们希望,例如,在 Responder 中使用-able-ess 标志 ,以防止客户端包含一个客户端Challenge,这将破坏与彩虹表的兼容性。
响应端的源代码
我的下一个问题是,如果 NTLM 协议不协商版本,那么 Responder 如何区分 NTLMv1和 NTLMv2散列?我认为回答这个问题的最好方法是查看 Responder 实用程序的源代码。
图7和8显示了 Responder 源代码的相关部分,其中我们观察到 NthashLen 是消息的有效负载部分中 NtChallengeResponse 字段的长度。我们可以在这里看到,Responder 正在检查该字段的长度,以确定 NTLM 身份验证协议的版本。我们可以看到,字段的长度在图7中为 NTLMv1的24个字节,在图8中为 NTLMv2的大于24个字节。
图7: Responder 用于确定消息是否为 NTLMv1类型的代码。
图8: Responder 用于确定消息是否为 NTLMv2类型的代码。
如果我们随后查看 NTLM 规范的2.2.2.6部分,我们可以看到 NTLM _ RESPONSE 格式概要,以便在使用 NTLMv1时使用。图9显示 NTLM _ RESPONSE 结构包含一个名为 Response 的24字节字段。
图9: 来自 NTLM 规范的一个表,详细描述了 NTLM _ RESPONSE 消息的格式。我们注意到它包含一个24字节的单一固定长度字段。
如何确定版本?
接下来,让我们看一下 NTLMv2响应,以确定为什么在这个场景中 Responder 要检查长度是否大于24字节。检查规范的2.2.2.8部分(如图10所示) ,我们发现 NTLMv2 _ RESPONSE 结构包含一个16字节的固定长度字段和一个名为 NTLMv2 _ CLIENT _ CHALLENGE 的可变长度字段。我们检查了 NTLMv2 _ CLIENT _ CHALLENGE 结构的所需字段(如图11所示) ,并确定该结构至少有28个字节的所需数据。由于计算得到的 NTLMv2响应总是包含至少24字节的所需数据,因此我们可以准确地利用这种机制来确定何时使用 NTLMv2。
图10: NTLMv2 _ RESPONSE 字段包含一个16字节的必需字段和一个嵌入式可变长度 NTLMv2 _ CLIENT _ CHALLENGE 结构。
图11: NTLMv2 _ CLIENT _ CHALLENGE 结构必须至少有28个字节长(不包括可变长度的 AvPair 结构,其中还包括所需的字段)。
了解 NTLMv1 Hash格式
在更多地了解了 Responder 如何区分 NTLMv1和 NTLMv2散列之后,我很好奇 Responder 使用什么格式来存储 NTLMv1hash。在这种情况下,格式非常简单。在图12中,我们看到 NTLMv1hash包含用户名、计算机名、 LmChallengeResponse、 NtChallengeResponse 和 Challenge。本质上,它包含计算的challenge 的输出和计算challenge 响应所需的输入challenge 。希望恢复用户原始明文密码的攻击者只需利用相同的算法并比较计算出的challenge ,以执行离线攻击,试图恢复用户的密码。在图13中,我们显示了 NTLMv1hash中的 LmChallengeResponse 和 NtChallengeResponse 字段如何与捕获的 NTLM _ AUTHENTICATE 消息中的相应字段匹配。
图12: NtChallengeResponse 和 LmChallengeResponse 以及服务器Challenge是生成的 NTLMv1散列的主要组件。
图13: Responder 用于计算 NTLMv1hash输出的 NtChallengeResponse 和 LmChallengeResponse 值。
NTLMv1 DES 加密机制
NTLMv1通过利用 DESL 函数加密服务器请求来计算客户端请求。它通过将用户的 NT hash分解成三个、七个字节的 DES 键来实现这一点。不幸的是,由于它使用的算法也使用三个独立的 DES 密钥加密每个密文值,攻击者可以分别破解每个 DES 密钥,以恢复受害用户的整个 NTLM hash。有关计算 NTLMv1challenge response的 go-ntlm 库代码,请参见图14。
图14: 从 go-ntlm 库生成 NTLMv1 NtChallengeResponse 和 LmChallengeResponse 值的示例代码。
图15显示了代码在图14中使用的 DESL 函数的定义。DESL 函数将用户的 NTLM hash分成三个独立的键,第三个键用零填充。然后,三个独立的 DES 密钥加密并连接服务器Challenge(或扩展会话安全性下的服务器和客户机Challenge的 MD5hash)。不幸的是,由于 DES 算法使用56位密钥,因此攻击者可以利用一个服务(比如 crack.sh)来恢复原始的 NTLM 散列
图15: NTLM 规范的摘录,详细描述了用户的 NT 或 LM hash如何分解成多个 DES 密钥,这些密钥随后对计算出的询问值进行加密。
了解 NTLMv2Challenge Response机制
计算 NTLMv2Challenge Response值的方法与 NTLMv1非常相似,但有一些关键差异。首先,它不使用前面提到的 DES 算法,而是利用 HMAC MD5算法来计算Challenge Response。其次,用于计算Challenge的数据包括额外的信息,例如 NTLMv2客户机挑战结构中的 AV _ PAIR 值。图16显示了计算 NTLMv2Response值的 go-ntlm 库代码。
图16: go-ntlm 库的摘录,显示了如何计算 NTLMv2challenge response消息。
NTLMv2哈希格式与 NTLMv1哈希格式非常相似。在本例中,它包含用户名、域、服务器请求、计算机响应(NTProofStr)和 NTLMv2 _ CLIENT _ CHALLENGE 结构,后者包含时间戳、客户端请求和 AV _ PAIR 字节。图17显示了 NTLMv2结构的各个字段的详细信息。
图17: Responder 捕获的 NTLMv2hash,其标签标识hash数据的相关组件。
基于 NTLMv1的 SMB 到 LDAP 中继攻击
从 SMB 中继到 LDAP 服务在理论上也是可行的,使用 NTLMv1。这将提供另一种可供选择的利用方向,它不需要攻击者利用类似 crack.sh 这样的站点,出于安全原因,这可能是不允许的。NTLMv1不支持计算消息完整性代码(请参见图18) ,该代码允许攻击者修改计算出的 NTLM _ AUTHENTICATE 消息的属性。在图19中,我们看到 LDAP 服务利用 NTLMSSP _ NEGOTIATE _ SIGN 字段来确定是否需要签名。
图18: 使用 NTLMParse 程序检查 NTLMv1 AUTHENTICATE _ MESSAGE,我们观察到消息完整性代码(MIC)不存在,允许修改消息。
图19: 具有 LDAP 服务的 NTLMSSP _ NEGOTIATE _ SIGN 标志解释为签名的一个要求。
在图20中,我们看到 LmCompatibilityLevel 设置,它为客户机和服务器配置 NTLM 身份验证的版本。
图20: 我们将 LmCompatibilityLevel 设置配置为使用“ SendNTLMResponse Only”选项的示例。
从 SMB 到 LDAP 服务的中继非常简单,只需要攻击者在执行中继攻击时指定-remove-mic 标志。最初,添加这个标志是为了支持利用“ Drop the MIC”漏洞。但是,它还具有将 NTLMSSP _ NEGOTIATE _ SIGN 标志设置为 false 的优点。这允许从 SMB 到 LDAP 服务的中继工作,因为 NTLMv1不包括消息完整性代码(MIC)。图21显示了一个示例场景,其中我们使用-move-mic 标志调用 NTLMRelayX,然后从受害者帐户触发对攻击者控制的 SMB 服务器的 SMB 身份验证。我们观察到在这个场景中将 SMB 中继到 LDAP 服务是成功的。
图21: 我们使用-remove-mic 标志调用 NTLMRelayX,并成功地通过 SMB 将 NTLMv1身份验证中继到 LDAP 服务。
利用NTLMv1绕过SMB签名
理论上,当启用 NTLMv1时,总是可以绕过 SMB 签名要求,因为 NTLMv1身份验证机制不包含客户机正在验证的目标的信息。要理解这个问题的原因,我们必须首先了解一下 NTLM 身份验证在域环境中是如何工作的。在高级别上,当客户端在 ActiveDirectory 域环境中利用 NTLM 身份验证向服务器进行身份验证时,服务器通常不拥有用户的 NTLM 散列。
相反,服务器必须将客户端的 NTLM _ AUTHENTICATE 响应发送给给域控制器,该响应将完成对身份验证请求的处理。然后,域控制器返回一个用于 SMB 签名的会话签名密钥。实际上,对于 NTLMv1,客户机不会嵌入任何关于它试图身份验证的目标的信息。这最终意味着攻击者可以自己向域控制器提交 NTLM _ AUTHENTICATE 消息,以验证身份验证并检索 SMB 签名的会话密钥。
NTLMv1 Relay到ADFS
在另一个场景中,我们可能从受害者用户帐户接收 NTLMv1身份验证。由于这是一个user帐户,而不是计算机帐户,因此我们可能希望将此身份验证尝试转发给 ADFS 服务,以便将云服务作为受害用户帐户进行身份验证。
NTLMv1协议不支持通道绑定令牌。从根本上说,这意味着攻击者可以在允许的默认 EPA 设置下将 NTLMv1身份验证中继到 ADFS 服务。
利用 NTLMv2从 SMB 中继 LDAP
在检查 NTLM 规范时,我注意到服务器在 NTLM _ CHALLENGE 消息中提供的 TargetInfo 字段周围有一个特定的部分。本节指出,如果在 TargetInfo 字段中存在 MsvAvTimestamp 字段,那么客户端应该包含消息完整性代码。图22显示了 NTLM 规范的相关部分,其中提到了当 MsvAvTimestamp 字段出现时客户机的预期行为。作为攻击者,我可以修改服务器在执行此攻击时返回的 NTLM _ CHALLENGE 消息以删除时间戳。
图22: NTLM 规范中概述 MsvAvTimestamp 字段出现时的行为。
如果攻击者能从客户端获得一个有效的 NTLM 消息,而不包含 MIC 字段,然后将其传递给服务器。这里的后续假设是,目标服务器可能会利用来自响应的客户机生成的时间戳。
为了验证我的假设,我对 Impacket 做了一些修改,删除了服务器在 NTLM _ CHALLENGE 消息中返回的 MsvAvTimestamp 字段,并向客户机提供修改后的response 。通过测试,我观察到,尽管客户机生成的 NTLM _ AUTHENTICATE 消息不包括消息完整性代码,但是目标服务器拒绝了身份验证尝试。在这种情况下,WindowsNTLM 实现将忽略客户端提供的时间戳。
了解 Samba 源代码
接下来,我花了一些时间研究 Sambda 的源代码。虽然这个实现与相应的 Windows 版本并不完全相同,但我认为它可以提供一些额外的见解,了解 Windows 实现中可能发生的情况。对于 Samba,我很快发现了一条注释,指出系统在执行身份验证时没有利用客户机提供的时间戳。相反,系统利用服务器生成的时间戳。图23显示了 Samba 源代码的这一部分。
图23: 检查 Samba 源代码时,我们看到一条注释,指示服务器不使用“ AUTHENTICATE _ MESSAGE”。NtChallageResponse.TimeStamp 值来执行 NtChallengeResponse 值的验证。
进一步检查代码后,我发现当服务器处理客户机的 NTLM _ AUTHENTICATE 消息时,它将提供的时间戳与 NTLM _ CHALLENGE 消息中的时间戳字段中的时间戳进行比较。当这些字段不匹配时,服务器返回一个错误(参见图24)。
图24: Samba 源代码的一个摘录,比较了客户机提供的和服务器提供的 MsvAvTimestamp 值。如果服务器在比较时发现两者不匹配,则返回错误。
虽然在这种情况下,它可能不存在漏洞,但它仍然是一个有趣的学习经验。
我非常有兴趣看到有人发布了 crack.sh 利用的 NTLMv1彩虹表的一个版本。这将大大减少利用 NTLMv1身份验证所需的工作量,而无需最终用户向外部服务公开目标帐户的 NTLM hash。
话虽如此,但是在使用 crack.sh 的情况下,虽然使用它会暴露帐户的 NTLM hash,但是数据不会包含任何与帐户或域环境相关的关于用户名、域名、域 sid 等的额外信息。此外,在计算机帐户的情况下,公开的 NTLM 散列将在默认设置下30天后自动循环。在某些场景中,这可能使得对于某些用例来说,利用 crack.sh 站点成为一种可接受的风险(尽管从纯理论的角度来看,这是隐晦式安全)。
总结
在本文中,花了一些额外的时间来深入研究 NTLM 规范,以便更好地理解 NTLM 协议的各个方面是如何工作的。我们讨论了一些常见问题的答案,比如:
- NTLM 是否支持身份验证协议版本的协商?
- NTLMv1和 NTLMv2会话安全功能是怎样的?
- 服务器如何识别 NTLMv1和 NTLMv2散列?
- NTLMv1与 NTLMv2散列使用的格式是什么?
然后,我们利用一些关于 NTLM 规范的额外知识来确定利用 NTLMv1身份验证的替代方法。我们特别关注的是确定一个不需要向可能不受信任的第三方服务(比如 crack.sh)提交潜在敏感数据(帐户的有效 NTLM 散列)的环境。这些技术包括绕过 SMB 签名,将 SMB 转发给 LDAP,以及将 NTLMv1身份验证尝试转发给 ADFS 服务。