服务主体名称 (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 验证客户端的身份后,将执行以下步骤:
- KDC根据解密后的时间戳检查TGT是否仍然有效;
- 如果 TGT 发出后超过 15 分钟,KDC 重新计算解密后的 PAC,并检查客户端是否在 Active Directory 中没有被禁用;
- KDC 查找发送的服务主体名称解析到的帐户;
- KDC 提取发现账户的 kerberos 密钥;
- KDC构建服务票据,由PAC和服务票据会话密钥组成;服务票证使用服务帐户的 kerberos 密钥进行加密和签名;
- KDC 使用服务票证会话密钥创建一个结构,并使用 TGT 会话密钥对其进行加密和签名。
服务票据和带有服务票据会话密钥的结构都包含在 TGS-REP 数据包中:
TGS-REP 数据包的内容 (#12)
服务票据的加密部分是在 Kerberoasting 攻击中被暴力破解的部分。
探索主体名称的格式
让我们检查之前收集的 AS-REQ 数据包中的主体名称:
Kerberos 流量中主体名称的示例
客户端主体名称在 cname 字段中传递,服务主体名称在 sname 字段中发送。所有主体名称都带有一个称为主体名称类型的整数。
主体名称通常由“/”字符分割成一系列字符串。例如,主体名称的krbtgt / CONTOSO .COM在Kerberos通信由两个字符串:KRBTGT和CONTOSO.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