MS14-068漏洞

先从一个知名的漏洞开始吧:MS14-068

这个漏洞在当时影响了全版本的Windows服务器,攻击者可以籍由这个漏洞实现权限提升,从普通域用户提升至域管理员身份。

那在分析以及复现这个漏洞之前,先让我们来看看什么是Kerberos协议,这是所有这一切的核心所在

Kerberos:地狱三头犬

Kerberos协议是一种基于第三方可信主机的计算机网络协议,它允许两个实体之间在非安全网络环境(可能被窃听、被重放攻击)下以一种安全的方式证明自己的身份。

假设一个场景,即有两者A和B,他们共同享有同样的一个secret,那么A在不安全的网络环境下如何向B证明其身份就成了一个问题

最简单的方法就是A直接发送secret给B,让B来验证其真实性,但是在不安全的网络环境中,这段secret随时可能被恶意攻击者窃取,故而这一方法不可行

那么要不A将secret作为密钥,用secret加密一段数据后,将密文和明文一起发给B,由B再进行校验?这样听起来可行,但是只要时间允许,攻击者是有可能对加密后的密文和明文之间进行破解来得到密钥的,故而也不可取

那么Kerberos就是为了解决这个问题而产生的,它为这个过程带来了一个第三方机构KDC,每次的通讯都会由KDC来向A颁发一个临时的通讯密钥,保证每一次密钥都是不重复的,防止攻击者对其进行破解

具体来说,KDC是由两部分组成的,AS和TGS,AS主要的职责是负责对申请者的认证,而TGS主要是向申请者颁发Service Ticket(后面会详细说)

大概流程可以看下图:

auth

从图中可以看出,一开始A向AS认证的时候利用的是时间戳,这也引出了一点就是,Kerberos协议要求整个域内时间必须同步

详细流程大致如下:

第①步:KRB_AS_REQ:Client-A发送Authenticator向KDC的AS服务认证自己的身份(通过提供自身密码加密的一个时间戳TimeStamp)

第②步:KRB_AS_REP:AS通过KDC数据库中存储的Client-A密码的副本,解密收到的Authenticator,如果解密出的TimeStamp符合要求,则AS服务认为Client-A就是所谓的Client-A。认证成功后,AS服务生成一个短期有效的SessionKeya-kdc,将该Key使用A的密码副本加密成密文1,另外将Key连同时间戳标志(控制该SessionKey的有效时间)通过TGS服务的密码也就是KDC的密码加密为密文2(称为TGT),将这两个密文组合成KRB_AS_REP返回给Client-A

第③步:KRB_TGS_REQ:Client-A在接收到KRB_AS_REP后,首先使用自身密码解密密文1得到SessionKeya-kdc,此时需要注意的是,密文2(TGT)是被KDC的密码加密的,所以Client-A无法解密,这也是Kerberos协议设计的精妙之处,既解决了Server端(TGS相对于Client-A也称之为Server端)无法及时接收SessionKey的问题,又不怕Client-A对该TGT的伪造,因为Client-A不知道Server端的密码

得到SessionKeya-kdc后,Client-A利用其加密时间戳生成Authenticator用于向TGS申请Client-A与Client-B进行认证所需的SessionKeya-b,连同刚才KRB_AS_REP接收的TGT一同组合成KRB_TGS_REQ发送给TGS

第④步:KRB_TGS_REP:TGS在接收到KRB_TGS_REP之后,利用KDC密码解密TGT获得本来就该发送给自己的SessionKeya-kdc,然后用其解密KRB_TGS_REQ中的Authenticator得到Client-A发送过来的时间戳,如果时间戳符合要求,则生成一个短期有效的SessionKeya-b,注意此时利用SessionKeya-kdc将SessionKeya-b加密为密文1,然后利用Server-B的密码将SessionKeya-b加密为密文2(称为ServiceTicket),两个密文一同构成KRB_TGS_REP返回给Client-A

第⑤步:KRB_AP_REQ:Client-A在接收到KRB_TGS_REP之后,首先使用缓存的SessionKeya-kdc将密文1中的SessionKeya-b解密出来,然后利用其加密时间戳生成Authenticator用于向B进行对自身的验证,另外,和刚才TGT一样,密文2也就是ServiceTicket是用Server-B的密码加密的,所以Client-A无法解密,也就无法伪造,这也同样解决了在三方认证中作为Server端的B无法及时接收SessionKey的问题,又不怕Client-A对ServiceTicket的伪造

第⑥步:KRB_AP_REP:Server-B受到KRB_AP_REQ之后,利用自身密码解密ServiceTicket,得到SessionKeya-b,然后用SessionKeya-b解密Authenticator得到时间戳,验证A的身份

一个参考:

详细过程

PAC

那么以上就是Kerberos协议的标准实现过程,但是在实际运行的windows服务器上,微软对标准Kerberos协议进行了扩充,其中最重要的扩充就是增加了权限认证,即PAC(Privilege Attribute Certificate),特权属性证书

PAC的扩充主要旨在帮助B知道A是否有访问自身某些资源的权限,因为原本Kerberos并没有规定权限方面的问题

那么在一个域中,如何才能知道某个域用户所拥有的权限呢?自然是需要提供User的SID和所在组Group的SID。必须了解的一个前提是,KDC、A和B三者中,B只信任KDC所提供的关于A到底是什么权限,所以在一个域初始时,KDC上拥有A和B的权限。现在需要解决的是,KDC必须告诉B关于A的权限,这样B验证A的权限后才能决定让不让A访问自身的网络资源。

为了最终使得Server-B能知道Client-A所具有的权限,微软在KRB_AS_REP中的TGT中增加了Client-A的PAC(特权属性证书),也就是Client-A的权限,包括Client-A的User的SID、Group的SID:

图片如下:

image

可以看到被KDC加密的TGT中,不仅包括了被加密的SessionKeya-kdc,还包括KRB_AS_REQ中申请者(Client-A)的权限属性证书,为了防止该特权证书被篡改(即使被KDC加密,Client-A无法轻易解密,但谁也无法保证绝对的安全),在PAC的尾部增加了两个校验Server Signature和KDC Signature:

这两个校验一个是Server Signature,另一个是KDC Signature,对于Client-A与AS服务来说,Server代表的是TGS服务,KDC代表的是AS服务(AS作为Client-A与TGS的第三方信任机构),而AS服务与TGS服务具有相同的krgtgt账号,所以这两个校验都是krgtgt账号的密码生成的,当然,整个TGT也是用KDC的密码也就是krgtgt账号密码加密的,它们三者不同的是,用的算法和加密内容有所不同。

微软是这样打算的,无论如何也要把PAC从KDC传送到Server-B,为了在Kerberos认证过程中实现,微软选择了如下做法:

将PAC放在TGT中加密后从AS服务经Client-A中转给TGS服务,再放在由TGS服务返回的ServiceTicket中加密后经Client-A中转给Server-B

image

需要注意的是,在KRB_TGS_REQ阶段,携带PAC的TGT被TGS服务接收后,认证A的合法性后(解密Authenticator符合要求)会将PAC解密出来,验证尾部两个签名的合法性,如果合法则认为PAC没有被篡改,于是重新在PAC的尾部更换了另外两个签名,一个是Server Signature,这次是以Server-B的密码副本生成的签名(因为对于Client-A和Server-B,这次的第三方机构是TGS服务),另外一个仍旧是KDC Signature,两者合在一起,最终成为New Signed PAC被拷贝在ServericeTicket中被加密起来。

注意,这里再次签发的PAC尾部的签名一个基于是B的密码hash一个仍然是基于KDC的密码hash(krgtgt用户的密码hash),在B端可以选择是否认证这个PAC的KDC签名,如果选择不认证,就不对PAC进行签名校验(也可能只校验基于B的密码hash的签名,这一点不太确定),并且之后也只是在解密PAC后直接根据用户权限来决定是否允许客户端访问相对应的资源,但是如果选择认证,B将会把PAC中的签名传递给DC来进行检验,只有DC确认签名有效后B才会根据用户权限来决定是否允许客户端访问相对应的资源,如下图所示,图片来自微软官方巨硬

check

当然,有的服务器并不校验KDC签名,校验与否决定了银票是否能够正常被使用(银票下一篇文章会讲)

最终绕过来绕过去,KDC上所拥有的关于Client-A的权限证书PAC终于发给了Server-B,Server-B在对Client-A进行认证的同时,同时也能判断Client-A有没有访问网络资源的权限。

MS14-068

这里我发现的一个大师傅分析的很不错,受教了

详细

可以继续好好消化

实战测试

我自己在本地搭建了一下环境

域控是win2008,域用户端是win7

首先先看一下没有工具之前,我使用net use尝试挂载域控的c盘是无效的,需要输入用户名密码

init

可以看到是无效的

这是这个时候缓存的票据信息:

before

此时,执行exploit

attack

将获得的TGT用mimikatz注入内存

inject

此时的缓存票据信息:

after

可以看见第一个票据被改变了(原来的第一个票据被移到第二个,看清标志),这就是我们的新TGT

接着再尝试挂载域控的c盘:

nah

可以看见无效,这是因为,这时我们注入的是TGT,而我们实际上在登陆的时候就有了一张初始权限的ServiceTicket(可以看第三个票据,cifs/domain.test的那一个),这是我们挂载时用到的ServiceTicket。也就是说其实系统会先检查内存中有没有相关服务的ServiceTicket,如果有就直接使用,如果没有,那么才会使用TGT去换ServiceTicket,故而这里我们虽然注入了高权限的TGT,系统却并没有用它来换ServiceTicket,而是用了之前的,故而还是无效的。

这时我们清空票据缓存

purge

重新注入票据后:

again

可以看见只有一张我们注入的TGT了

重新尝试挂载:

success

可以看见已经不需要用户名密码了

再看看票据缓存:

final

可以发现,系统使用我们注入的高权限TGT重新申请了高权限的ServiceTicket,从而完成了攻击,在票据到期前,攻击者拥有域内最高的权限

结语

虽然是好几年前的漏洞了,但是对于理解Kerberos协议的工作原理还是有着很大的帮助