没有 SPN 的 Kerberoasting

2022-01-04 11:11:02 浏览数 (1)

服务主体名称 (SPN) 是 Active Directory (AD) 数据库中的记录,显示哪些服务注册到哪些帐户:

具有 SPN 的帐户示例

如果一个帐户有一个 SPN 或多个 SPN,您可以通过 Kerberos 向其中一个 SPN 请求服务票证,并且由于服务票证的一部分将使用从帐户密码派生的密钥进行加密,您将能够破解强制此密码离线。这就是 Kerberoasting 的工作原理。

有一种方法可以在不知道目标服务的 SPN 的情况下执行 Kerberoasting 攻击。我将展示它是如何完成的,它是如何工作的,以及它何时有用。

Kerberos 基础知识

Kerberos 是一种基于 ASN.1 格式的开源二进制协议。Kerberos 的核心是密钥分发中心 (KDC) 服务,它使用 88/tcp 和 88/udp 端口​​。在 Active Directory 环境中,它们安装在每个域控制器上。

让我们运行 Impacket 中的 GetUserSPNs.py 工具来演示 Kerberoasting 的工作原理:

在实验室环境中执行 Kerberoasting 攻击

首先,该工具连接到 LDAP,并查找具有 SPN 且不是计算机帐户的用户。AD 中的每个机器帐户都有一堆 SPN,但它们的服务票证是不可暴力破解的,因为机器帐户有 240 字节长的密码。

然后,该工具连接到 KDC,并为每个发现的帐户使用其 SPN 之一获取服务票证。在我们的示例中,只发现了一个帐户,并且该工具选择了“MSSQLSvc/sp-sql:1433”SPN 来请求票证。

所选服务是否正常运行并不重要;AD 数据库中存在 SPN 就足以进行攻击。

这是此 GetUserSPNs.py 启动的流量转储,因此现在我们可以详细检查所有描述的阶段:

Kerberoasting 攻击的流量转储

客户如何获得 TGT

每个客户端都必须向 KDC 进行身份验证并获得一个票证授予票证 (TGT),这将允许他们继续请求任意数量的服务票证。

这种机制是用来减少需要认证的次数,没有TGT是没有办法绕过它来请求服务票的。

未经身份验证的 AS-REQ / Preauth 请求

AS-REQ 数据包用于请求 TGT。

在 AS-REQ 中,客户端在 sname 字段中指定特殊的“krbtgt/DomainFQDN”SPN,以及在 cname 字段中请求 TGT 的帐户的主体名称:

未经身份验证的 AS-REQ 数据包的内容 (#7)

第一个 AS-REQ 数据包在没有身份验证数据的情况下发送,以保持向后兼容性。只有在目标帐户的 Active Directory 中设置了 DONT_REQ_PREAUTH 标志时,它才会成功。

AS-REQ 的响应应该包含一个结构,该结构使用从客户帐户的密码派生的密钥进行加密和签名,因此如果 AS-REQ 在没有任何身份验证的情况下工作,任何人都可以离线暴力破解其他人的密码。

这称为 ASREPRoasting 攻击,在 Impacket 中它可以由 GetNPUsers.py 脚本执行:

使用来自 Impacket 的 GetNPUsers.py 执行 ASREPRoasting 攻击

ASREPRoasting 的一种应用是 Targeted Kerberoasting。它依赖于有意为您在 AD 中控制的帐户设置 DONT_REQ_PREAUTH 标志,并获取其$krb5asrep$ 哈希值。

由于我们使用的“管理员”帐户没有设置 DONT_REQ_PREAUTH 标志,因此 KDC 向客户端发送了一个 KRB-ERR 数据包,其中包含 KRB_PREAUTH_REQURED 错误。此数据包称为 Preauth 请求。

KRB-ERR 数据包的内容 (#8)

如果“管理员”帐户不存在,我们将收到 KDC_ERR_C_PRINCIPAL_UNKNOWN 错误。这是在 Kerberos 用户枚举攻击中使用的功能。

经认证的 AS-REQ

让我们检查下一个 AS-REQ 数据包:

已认证的 AS-REQ 数据包的内容 (#9)

下一个 AS-REQ 与第一个请求基本相同,但它包含可以授权客户端的数据。这个数据是一个包含当前时间戳的特殊结构,这个结构是用从账户密码派生的密钥加密和签名的。

从帐户密码派生的密钥称为 Kerberos 密钥,它们的计算方式取决于所使用的加密算法:

  • AES-128 和 AES-256:密钥是根据密码的 PBKDF2 哈希计算的
  • RC4:密钥是从密码的 NT 哈希计算出来的(总是与 Pass-The-Hash 攻击一起使用)
  • DES:密钥直接从密码中计算出来

在请求中使用客户端主体名称,KDC 尝试在 AD 数据库中查找客户端的帐户,提取其预先计算的 Kerberos 密钥,并验证客户端的身份。

AS-REP

在 KDC 验证客户端的身份后,它会发送一个 AS-REP 数据包,其中包含客户端可以从中构建 TGT 内存对象的数据:

AS-REP 数据包的内容 (#10)

TGT 本身是用 krbtgt 帐户的 kerberos 密钥加密和签名的,因此它旨在仅在 KDC 端解压缩。它包含会话密钥、元数据和客户端的特权属性证书 (PAC)。PAC 包括客户端的名称、安全标识符 (SID) 和组。

为了让客户端使用 TGT,它需要构造一个 TGT 内存对象,该对象将包含 TGT 本身、其会话密钥和所有元数据。客户端从由其密钥加密的 AS-REP 部分提取会话密钥。

客户如何获得服务票

客户端构造 TGT 内存对象后,它可以使用 TGS-REQ 数据包请求任意数量的服务票证。当这些请求被接受时,KDC 将使用 TGS-REP 数据包进行响应。

TGS请求

TGS-REQ 包含票证请求的服务主体名称、TGT 和使用 TGT 会话密钥加密并包含当前时间戳的结构:

TGS-REQ 数据包的内容 (#11)

当 KDC 收到 TGS-REQ 时,它会解密 TGT,提取会话密钥,并检查客户端的身份。

TGS-REP

TGS-REP 数据包用于将服务票据传输到 KDC 客户端。

在 KDC 验证客户端的身份后,将执行以下步骤:

  1. KDC根据解密后的时间戳检查TGT是否仍然有效;
  2. 如果 TGT 发出后超过 15 分钟,KDC 重新计算解密后的 PAC,并检查客户端是否在 Active Directory 中没有被禁用;
  3. KDC 查找发送的服务主体名称解析到的帐户;
  4. KDC 提取发现账户的 kerberos 密钥;
  5. KDC构建服务票据,由PAC和服务票据会话密钥组成;服务票证使用服务帐户的 kerberos 密钥进行加密和签名;
  6. KDC 使用服务票证会话密钥创建一个结构,并使用 TGT 会话密钥对其进行加密和签名。

服务票据和带有服务票据会话密钥的结构都包含在 TGS-REP 数据包中:

TGS-REP 数据包的内容 (#12)

服务票据的加密部分是在 Kerberoasting 攻击中被暴力破解的部分。

探索主体名称的格式

让我们检查之前收集的 AS-REQ 数据包中的主体名称:

Kerberos 流量中主体名称的示例

客户端主体名称在 cname 字段中传递,服务主体名称在 sname 字段中发送。所有主体名称都带有一个称为主体名称类型的整数。

主体名称通常由“/”字符分割成一系列字符串。例如,主体名称的krbtgt / CONTOSO .COM在Kerberos通信由两个字符串:KRBTGTCONTOSO.COM

根据RFC 4120, cname 和 sname 字段有不同的用途,但这些字段的结构是相同的:

代码语言:javascript复制
KDC-REQ-BODY    ::= SEQUENCE {
 kdc-options  [0] KDCOptions,
 cname        [1] PrincipalName OPTIONAL
 realm        [2] Realm
 sname        [3] PrincipalName OPTIONAL,
 ...
}

PrincipalName   ::= SEQUENCE {
 name-type    [0] Int32,
 name-string  [1] SEQUENCE OF KerberosString
}

KerberosString  ::= GeneralString (IA5String)

cname 和 sname 字段的相同结构引起了我的注意,我决定在 Kerberos 协议中测试它们的不同用法。

Kerberos 的秘密

发现 Windows KDC 服务通过相同的函数集处理 cname 和 sname 字段,并且在任何给定时间选择哪种格式的主体名称都无关紧要。

解析为同一个帐户的所有主体名称都是相同的

如果您在 Kerberos 数据包中有一个 SPN 值,您可以将其替换为该 SPN 所属帐户的 SAM 帐户名称 (SAN) 值,并且不会有任何中断:

带有 SAN 的 TGT-REQ 数据包示例

这样您就可以在不知道目标帐户的任何 SPN 的情况下执行 Kerberoasting 攻击。但是,将继续需要目标帐户至少存在一个 SPN。

奖励:重温 S4U 和 AnySPN 攻击

我检查了 Impacket 源代码,我发现了两个有趣的地方,它们与所发现的技术密切相关,但与 Kerberoasting 无关。

使用 SAM 帐户名称的 S4U2Self 和 S4U2Proxy 请求

让我们尝试使用 getST.py 形式的 Impacket 来滥用基于资源的约束委托:

使用 Impacket 滥用基于资源的约束委托的示例

这里我们有“user01”帐户,该帐户具有“http/test”SPN 和授权访问“SRV02$”帐户的任何 SPN 的权限。

根据规范(S4USelf KRB_TGT_REQ,S4U2Proxy KRB_TGS_REQ),user01 的服务应该在 S4U2Self 和 S4U2Proxy 请求中使用其 SPN。但是,您可以看到 Impacket 在此类请求中使用了 SAN:

Impacket 的 S4U2Self 请求的流量转储

这些请求不符合规范,但会成功,因为 Windows KDC 对给定的主体名称格式不敏感。

AnySPN 攻击

Impacket 实现了一种叫做 AnySPN 攻击的东西。此攻击尝试修改给定服务票据文件中的 SPN,当它与目标服务 SPN 不同时:

使用 Impacket 执行 AnySPN 攻击

Alberto Solino 写了一篇优秀的文章Kerberos 委托、SPN 和更多 解释它是如何工作的。

这是本文的主要部分:

Alberto Solino 的文章片段

简而言之,Benjamin Delpy、Ben Campbell 和 Alberto Solino 注意到主机 A 上服务 A 的服务票可能适用于主机 A 上的服务 B。

实际上,如果我们解密任何服务票证的加密部分,我们将看到它不包含任何 SPN:

使用服务帐户的密码解密服务票的加密部分

打印服务票据加密部分包含的信息

服务票据的加密部分仅包含票据的会话密钥、元数据和验证用户的 PAC。服务票证的 SPN 包含在协议的未加密和未签名部分中,客户端可能根本不考虑它。

服务票证对其服务帐户运行的所有服务均有效

因此,如果您想知道在没有 SPN 的情况下请求服务票证时将服务票证颁发给哪个 SPN,现在您知道服务票证不包含任何内容。

奖励:使用主要名称类型

cname 和 sname 字段的结构包含一个称为Principal Name Type的整数。RFC 4120 规范为其定义了 9 个可能的值:

RFC 4120 的摘录:6.2。校长姓名

我做了一些研究,并创建了一个表,其中包含实际的 Principal Name Types 值及其在 Windows 中的含义:

姓名类型

价值

意义

NT-未知

0

代表 SPN 和 SAN 格式

NT-校长

1

等于 NT-UNKNOWN

NT-SRV-INST

2

等于 NT-UNKNOWN

NT-SRV-HST

3

等于 NT-UNKNOWN

NT-SRV-XHST

4

表示 SPN 格式

NT-UID

5

不支持

NT-X500-校长

6

代表DN格式

NT-SMTP-名称

7

等于 NT-UNKNOWN

NT-企业

10

代表UPN、SAN和多种DomainName SAN格式

NT-MS-校长

-128

代表SAN和多种DomainName SAN格式

NT-MS-PRINCIPAL-AND-ID

-129

等于 NT-MS-PRINCIPAL

NT-ENT-PRINCIPAL-AND-ID

-130

等同于 NT-X500-PRINCIPAL

*

等于 NT-UNKNOWN

我发现 NT-ENTERPRISE 类型比常用的 NT-UNKNOWN 类型更有价值。它支持以下一堆名称格式:

  • 用户主体名称
  • sAMA账户名
  • sAMAccountName@DomainNetBIOSName
  • sAMAccountName@DomainFQDN
  • 域NetBIOSNamesAMAccountName
  • 域FQDNsAMAccountName

请注意,如果您使用SRV01 字符串作为 sAMAccountName,并且SRV01 帐户不存在,而SRV01$ 帐户存在,则此名称将被视为SRV01$ 帐户的主体名称。

其他有趣的主体名称类型是 NT-X500-PRINCIPAL。它支持RFC 1779结构中的 DN 。以下是如何在此结构中编写相同 Active Directory 对象的三个示例:

代码语言:javascript复制
CN=SQL ADMIN,OU=LAB Users,DC=CONTOSO,DC=COM
CN="SQL ADMIN";OU="LAB Users";DC="CONTOSO";DC="COM"
OID.2.5.4.3=SQL ADMIN,OU=LAB Users,DC=CONTOSO,DC=COM

不幸的是,跨林信任不支持 NT-X500-PRINCIPAL 类型。

该技术在Kerberoasting中的应用

我已将 NT-ENTERPRISE 和 NT-MS-PRINCIPAL 类型的用法添加到 Impacket 的 GetUserSPNs.py。让我们看看当这些更改是 Kerberoasting 成功所必需的三种常见情况。

无法访问 LDAP 的 Kerberoasting

您可能会发现自己处于这样一种情况:您可以访问 KDC 服务,您获得了一个帐户列表(例如,通过 RID 循环攻击),但您没有 SPN。

由于您不再需要 SPN,您可以使用新的-userfile选项仅通过用户列表请求服务票证:

使用新的 GetUserSPNs.py 按用户列表执行 Kerberoasting

-userfile选项利用NT企业类型从指定的文件中查找accountd。

使用不正确 SPN 的 Kerberoasting 帐户

KDC 禁止退票的 SPN 有两种类型:

  • 错误的语法 SPN
  • 重复的 SPN,即当相同的 SPN 值分配给多个帐户时

如果 KDC 发现其中之一是这种情况,它会返回 KDC_ERR_S_PRINCIPAL_UNKNOWN 错误,就好像传递的 SPN 不存在一样:

使用不正确的 SPN 对帐户进行 Kerberoasting

新的 GetUserSPNs.py 将帐户列表从 LDAP 包装到 NT-MS-PRINCIPAL 类型,并且不使用 SPN,因此您甚至可以从误解的 SPN 中获取哈希值:

使用新的 GetUserSPNs.py 对 SPN 不正确的帐户进行 Kerberoasting

内部使用“DomainFQDNsAMAccountName”格式,输出中的“”字符改为“/”,以符合Impacket格式的用户名并防止其在其他工具中转义。

通过 Forest Trusts 使用 NetBIOS 名称 SPN 对帐户进行 Kerberoasting

当您从另一个域请求 SPN 的服务票证,并且此 SPN 具有 NetBIOS 名称格式的主机名时,您的 KDC 将无法找到目标服务:

通过林信任对具有 NetBIOS 名称 SPN 的帐户进行 Kerberoast 处理

使用新的 GetUserSPNs.py 文件,您将永远不会获得此类服务的 KDC_ERR_S_PRINCIPAL_UNKNOWN:

使用新的 GetUserSPNs.py 通过森林信任对具有 NetBIOS 名称 SPN 的帐户进行 Kerberoasting

0 人点赞