impacket-getTGT scrm.local/ksimpson:ksimpson
KDC(Key Distribution Center)是Kerberos的主要服务,安装在域控制器(DC)上,负责发行票据。它包含AS认证服务和TGS服务,用于Kerberos认证的第一步和第二步。KDC也被称为密钥分发中心。Kerberos认证的第一步解决的问题是验证用户身份。用户将账户和密码发送至KDC上的AS,AS确认无误后返回一张TGT(Ticket Granting Ticket)票据,相当于身份证,确认了用户的身份。用户可以使用这张票据去申请访问其他服务。TGT翻译为中文后是“票据授予票据”,在第二步的服务申请中起到重要作用。具体来说,使用网络包分析可以更好地理解协议的运作过程。为了生成包,可以使用impacket包中的getTGT.py工具,该工具内置在kali中。
kerbrute userenum -d scrm.local --dc dc1.scrm.local A-ZSurnames.txt
kerbrute passwordspray -d scrm.local --dc dc1.scrm.local users.txt ksimpson
命令的内容是使用Ksimpson作为账户,Ksimpson作为密码,向scrm.local申请TGT票据。scrm.local是靶机的域名,攻击机能够识别域名并连接到靶机是因为在/etc/hosts文件中提前写入了靶机的IP地址。Ksimpson:ksimpson是账户和密码,使用冒号分隔。这对账户和密码的获取实际上利用了Kerberos的一个漏洞,该漏洞将在后续的包分析中详细说明。获取的ksimpson.ccache文件包含了TGT和一个登录会话密钥。现在让我们来看一下生成这些包的过程。在此过程中总共生成了四个包。第一个包是AS-REQ,但返回了错误信息,导致第二次发送AS-REQ才成功返回AS-REP。那么这两个包有什么区别呢?第一个AS-REQ包没有包含时间戳信息,因此AS服务器不予通过认证,并告知攻击机需要满足预认证要求,于是攻击机发送了第二个包AS-REQ,并包含了时间戳信息用于预认证。因此,在AS-REQ阶段,核心信息是加密后的时间戳。只要时间戳信息满足要求,就能通过认证。时间戳的值是如何加密的呢?我们已经能够看到加密类型和密钥,实际上值的生成还需要一个额外的输入:用户密码的NTLMHASH。NTLMHASH算法已经公开,getTGT.py会自动将我们输入的密码转换为NTLM_HASH格式,并使用它和密钥对当前时间戳进行加密。对于AS服务器来说,由于AS服务运行在域控上,而域控保存了所有用户的NTLM-HASH值,因此只要AS服务器能够成功使用该用户的NTLM-HASH值解密,并且解密出的时间戳在合理范围内,就会通过预认证,向该用户发放TGT。AS-REP包的内容如下:返回包包含两个主要信息:ticket和enc-part。ticket包含了TGT,而TGT就是ticket分类下的enc-part。此部分enc-part内容是由域控的NTLM-HASH加密的,只有域控上的KDC能够解密,用于下一步的凭证。TGT中包含了一个关键信息:登录会话密钥。独立出来的另一个enc-part包含用户hash加密后的登录会话密钥。用户解密该部分后可以获得原始的登录会话密钥。为什么需要一个登录会话密钥,且用户和KDC都要知晓呢?一个单纯的TGT票据能否在下一步中验证用户的身份?实际上,登录会话密钥机制就是为了防止中间人窃取了AS-REP包拿到了TGT,从而在下一步中骗取服务器的信任。登录会话密钥将在下一步与TGT一起使用。因为只有用户能够解密并获得登录会话密钥,这样可以防止中间人冒充用户。这个过程有什么漏洞呢?实际上,到目前为止,所介绍的内容没有漏洞。但是,如果我使用错误的用户名和错误的密码,或者使用正确的用户名和错误的密码发送getTGT请求,会发生什么呢?当使用错误的用户名时,返回包会显示UNKNOWN,表示无法识别用户。当使用正确的用户名和错误的密码时,仍然会生成四个数据包,但第一个回包与用户名密码正确时的回包相同,都需要进行预认证,而第二个回包显示预认证失败。
export KRB5CCNAME=ksimpson.ccache
python3 GetUserSPNs.py scrm.local/ksimpson -k -no-pass -dc-host dc1.scrm.local
第二条指令是密码喷射,指定用户密码进行爆破用户名。为什么不能指定用户名进行密码爆破呢?因为Kerberos内置了安全机制,失败次数过多会锁定账户。由于该靶机在其他地方提示了用户名和密码相同,所以可以根据爆破的用户名作为密码进行爆破。
python3 GetUserSPNs.py -request scrm.local/ksimpson -k -no-pass -dc-host dc1.scrm.local
第一条指令先设置环境变量。ksimpson.ccache文件是上一步中执行getTGT命令生成的,包含了登录会话密钥和TGT。前面已经解释过,TGT是由域控NTLM-HASH加密的,包含了登录会话密钥和一些用户和域的信息。第二条指令是使用TGT和登录会话密钥查询SPN。不使用kali内置的impacket-getUserSPNs的原因是,内置版本不支持-dc-host参数,只支持-dc-ip参数。在这个环境中使用-dc-ip会出现问题。而从GitHub上下载的最新版本已经支持该参数,所以我下载了最新的包,并使用其中的Python文件。
至此,我们已经拿到了ST!再来看一下这条命令生成的流量包。它多了两个KRB5包。那么这两个包包含了什么呢?第62号包是TGS-REQ,请求访问scrm.localsqlsvc服务。第67号包是TGS-REP,TGS返回了一个ST和service key!这是令人惊叹的!柳暗花明又一村!这个现象的解释涉及到TGS的一个工作逻辑。在TGS的工作思维中,我只负责验证你的TGT是否有效,以及你想要访问的服务是否存在。只要满足这两点,OK,你就通过了我的验证,我就发放给你ST,允许你与服务进行交互。其中最关键的一点是,TGS不检查用户是否有访问服务的权限!我不关心你有没有权限,只要你证明了你是你,我就发放门票,上面写着你的名字。至于你能否进入,那是你要访问的服务的问题。如果你没有访问服务的权限,即使你拿到了ST,去与对应服务打招呼时,对方也不会让你进入。既然我们即使拿到了ST,也无法登录MSSQL服务,那么这个ST有什么用呢?ST是由服务账户的NTLM-HASH生成的。我们拥有了服务账户的密码,就能自己制作一个ST。只要里面包含用户名sqlsvc,我们就能登录MSSQL服务了。这个ST被称为白银票据。制作白银票据需要的信息包括:SPN、NTLM-HASH、Domain SID、domain、user ID。SPN已知,就是MSSQLSVC/dc1.scrm.local。NTLM-HASH算法是公开的,可以根据原始密码计算。domain已知,就是scrm.local。user ID代表账户权限,500表示管理员。因此,在这里默认填写500。我们只缺少Domain SID。Domain SID的数据格式和各部分的含义可以自行搜索资料。这里提供一个工具,impacket工具包中的secretsdump.py。