(360 A-TEAM 长期招收高级安全研究人员,APT 攻防人员,请联系 wufangdong@360.net)
国外有文章在去年讲到了 Kerberos 账号枚举的技巧,同时提供了 Java 编写的一个枚举工具,nmap 也带有一个 NSE 脚本来实现了同样的功能。国内也有安全媒体翻译过。
https://www.attackdebris.com/?p=311
我在这里简单介绍一下一些与 Kerberos 账号枚举相关联的知识,以及我自己用 Python 实现的 Kerberos 账号枚举工具。编写这个工具本身并没有什么难度,只是为了简单练习一下自己对 Kerberos 协议的理解。
背景知识简述
Kerberos 的三个子协议:
AS-REQ 与 AS-REP
TGS-REQ 与 TGS-REP
AP-REQ 与 AP-REP
Kerberos 账号枚举利用到的是 AS-REQ 与 AS-REP。
AS-REQ 与 AS-REP 简述
Kerberos 是基于票据的协议,访问任何使用 Kerberos 进行身份验证的服务,都需要你提供一张能够证明你身份的票据。所有的票据都是由 KDC 发布的,而你应该获取的第一张票据是一张被称为 TGT 的票,这个票代表着你的身份。
我们可以通过发送 AS-REQ 请求,来向 KDC 申请一张 TGT。KDC 对我们的 AS-REQ 请求进行的响应被称为 AS-REP,而 TGT 也包含在这个 AS-REP 响应里面。
Pre-Authentication 与 Authenticator 简述
在我们利用 AS-REQ 向 KDC 申请代表我们身份的 TGT 的时候,我们需要:
AS-REQ 的 cname 字段处写上我们的用户名(比如说 n1nty)
一般情况下,我们还需要在 AS-REQ 的 padata 字段处填上一段数据,这段数据被称为 authenticator,用于向 KDC 证明,我们知道 n1nty 这个账号的密码。 如果我们填写了 padata,则说明我们希望进行 pre authentication,如果没有填,说明我们不希望进行 pre authentication。
在 Kerberos 的设计中,padata 这个字段的值是可选的。如果我们不填写 padata,那么意思就是我们希望 KDC 能够将 n1nty 这个账号的 TGT 返回给我们,即使我们没有向 KDC 证明我们知道这个账号的密码。
看到这里是不是感觉有问题,我们不需要向 KDC 证明我们知道目标账号的密码就可以随意获取目标账号的 TGT?其实是没有问题的,因为:
这是可配置的。在微软对 Kerberos 的实现中,所有的账号默认都是强制要求我们进行 Pre authentication 的。也就是默认情况下所有账号都要求我们在提供 authenticator 后,才能给我们返回 TGT。当然你可以针对某些账号来进行单独的设置,允许在不提供 authenticator 的情况下从 KDC 申请这些账号的 TGT,如下图:
就算你在不提供 Authenticator 的情况下拿到了那些不要求进行 Pre authentication 的账号的 TGT,在不知道目标账号的密码的情况下,你是无法从 AS-REP 响应里面解密与 TGT 相关联的 session key的,导致你最终依然无法使用这张 TGT。
但是这也确实造成了另一个问题,也就是 TGT 离线爆破。
另一个与 TGT 的离线爆破很相似的一种攻击方式被称为 Kerberoast,Kerberoast 是对 TGS-REP 响应的离线爆破,用于爆破服务启动账号的密码。而 TGT 离线爆破是针对 AS-REP 响应进行的的离线爆破。
账号枚举的原理
原理其实很简单,我们可以向 KDC 发送 AS-REQ ,AS-REQ 里面包含我们想要判断的账号比如 n1nty,同时我们不进行 pre authentication,也就是我们不在 padata 字段里面提供 authenticator。此时,根据 KDC 的响应,我们能做出以下判断:
如果 KDC 给出的是 KRB_ERROR 响应,且 error-code 为 UNKNOWN_PRINCIPLE,则说明 n1nty 账号不存在
如果 KDC 给出的是 KRB_ERROR 响应,且 error-code 为 PREAUTH-REQUIRED,则说明目标账号是存在的,但是因为我们没有提供 authenticator,所以导致出现了错误
如果 KDC 给出的是 KRB_ERROR 响应,且 error-code 为 CLIENT_REVOKED,则说明账号存在,但是账号被禁用了
如果 KDC 给出的是 AS-REP 响应,则说明账号存在,并且账号关闭了 pre authentication,此时我们应该可以直接从 AS-REP 响应中获取到代表着 n1nty 这个账号的 TGT
KerberosUserEnum
我用 Python 编写了一个小工具来实现 Kerberos 账号的枚举。