首发于

Crispr的杂说

Windows协议学习--Kerberos协议(上)

Crispr

blog:crisprx.top

Windows协议学习--Kerberos协议(上)

前言

初入Windows学习,以拜读daiker师傅的gitbook学习为主,记录Kerberos协议的情况,部分图和引用摘自daiker师傅

kerberos 协议概述

Kerberos是一种由MIT(麻省理工大学)提出的一种网络身份验证协议。它旨在通过使用密钥加密技术为客户端/服务器应用程序提供强身份验证。 在Kerberos协议中主要是有三个角色的存在:

  • 1.访问服务的Client(以下表述为Client 或者用户)
  • 2.提供服务的Server(以下表述为服务)
  • 3.KDC(Key Distribution Center)密钥分发中心 其中KDC服务默认会安装在一个域的域控中,而ClientServer为域内的用户或者是服务,如HTTP服务,SQL服务。在Kerberos中Client是否有权限访问Server端的服务由KDC发放的票据来决定。 kerberos的简化认证认证过程如下图:

kerberos协议中,总共会有如下周期,内容和事件列举如下:

  • 1.AS_REQ: Client向KDC发起AS_REQ,请求凭据是Client hash加密的时间戳
  • 2.AS_REP: KDC使用Client hash进行解密,如果结果正确就返回用krbtgt hash加密的TGT票据,TGT里面包含PAC,PAC包含Client的sid,Client所在的组。
  • 3.TGS_REQ: Client凭借TGT票据向KDC发起针对特定服务的TGS_REQ请求
  • 4.TGS_REP: KDC使用krbtgt hash进行解密,如果结果正确,就返回用服务hash 加密的TGS票据(这一步不管用户有没有访问服务的权限,只要TGT正确,就返回TGS票据)
  • 5.AP_REQ: Client拿着TGS票据去请求服务
  • 6.AP_REP: 服务使用自己的hash解密TGS票据。如果解密正确,就拿着PAC去KDC那边问Client有没有访问权限,域控解密PAC。获取Client的sid,以及所在的组,再根据该服务的ACL,判断Client是否有访问服务的权限。

我们重点前四个部分,并且在这里通过对协议底层和字段的了解,希望阐释清楚一些根据Kerberos协议造成的漏洞和利用点

AS_REQ

当用户想KDC发起AS_REQ时,请求凭据使用用户自身的hash值加密时间戳。该请求凭据会放置在PA_DATA段中 借个图看下:

对着数据包来学习下各字段的意义: (1) pvno pvno表示的是Kerberos协议的版本,这里为5表示的是使用Kerbeos V5 (2) msg-type 类型,第一个步骤对应AS_REQ,因此AS_REQ对应的就是KRB_AS_REQ(0x0a),十进制也就是10 (3) PA_DATA 主要是一些认证信息。一个列表,包含若干个认证消息用于认证,我们也可以Authenticator。每个认证消息有type和value。

贴出在PA_DATA字段中对用的type的值和意思:

            NONE = 0,
​            TGS_REQ = 1,
​            AP_REQ = 1,
​            ENC_TIMESTAMP = 2,
​            PW_SALT = 3,
​            ENC_UNIX_TIME = 5,
​            SANDIA_SECUREID = 6,
​            SESAME = 7,
​            OSF_DCE = 8,
​            CYBERSAFE_SECUREID = 9,
​            AFS3_SALT = 10,
​            ETYPE_INFO = 11,
​            SAM_CHALLENGE = 12,
​            SAM_RESPONSE = 13,
​            PK_AS_REQ_19 = 14,
​            PK_AS_REP_19 = 15,
​            PK_AS_REQ_WIN = 15,
​            PK_AS_REQ = 16,
​            PK_AS_REP = 17,
​            PA_PK_OCSP_RESPONSE = 18,
​            ETYPE_INFO2 = 19,
​            USE_SPECIFIED_KVNO = 20,
​            SVR_REFERRAL_INFO = 20,
​            SAM_REDIRECT = 21,
​            GET_FROM_TYPED_DATA = 22,
​            SAM_ETYPE_INFO = 23,
​            SERVER_REFERRAL = 25,
​            TD_KRB_PRINCIPAL = 102,
​            PK_TD_TRUSTED_CERTIFIERS = 104,
​            PK_TD_CERTIFICATE_INDEX = 105,
​            TD_APP_DEFINED_ERROR = 106,
​            TD_REQ_NONCE = 107,
​            TD_REQ_SEQ = 108,
​            PA_PAC_REQUEST = 128,
​            S4U2SELF = 129,
​            PA_PAC_OPTIONS = 167,
​            PK_AS_09_BINDING = 132,
​            CLIENT_CANONICALIZED = 133

可以看到,在我们的AS_REQ阶段使用到了ENC_TIMESTAMP,实际上在AS_REQ阶段主要用到的有两个: 1.ENC_TIMESTAMP 这个是预认证,就是用用户hash加密时间戳,作为value发送给KAS(Kerberos Authentication Service)服务器。然后KAS服务器使用数据库中对应用户的hash进行解密,获得时间戳,如果能解密,且时间戳在一定的范围内,则证明认证通过。对应十进制值是2

2.PA_PAC_REQUEST 这个是启用PAC支持的扩展。PAC(Privilege Attribute Certificate)并不在原生的kerberos里面,是微软引进的扩展。PAC会包含在AS_REQ的响应中即AS_REP。这里的value对应的是include=true或者include=false(KDC根据include的值来判断返回的票据中是否携带PAC)。 对应的十进制值是128

MS14-068也就是因为PAC的原因而实现的伪造域管用户,原理稍后分析

当在域用户中设置选项”Do not require Kerberos preauthentication”,此时Kerberos协议不会进行预认证,这时向域控制器的88端口发送AS-REQ请求,域控就会返回相应的AS_REP,具体利用稍微分析

REQ_BODY

顺着AS_REQ阶段我们不妨来看下请求字段有哪些?

  • kdc-options 应该就是一些标志位,每个标志位对应不同的意思,在这里贴下daiker师傅找到的标志位的字段和值:

  • cname cname比较容易理解,主要包含请求的用户和所在域的名称,其中realm对应的域名为CENTOSO,用户则为test用户,属于NT-PRINCIPAL,也就是通过认证,value值为1(daiker师傅在文章里提到了该用户存在/不存在两种情况返回的数据包不同,可以用来枚举域用户),在其它师傅博客中找到了用户类型有以下几种(最前面的数字为value值):

    0: NT-UNKNOWN Name type not known
    1: NT-PRINCIPAL Just the name of the principal as in DCE, or for users
    2: NT-SRV-INST Service and other unique instance (krbtgt)
    3: NT-SRV-HST Service with host name as instance (telnet, rcommands)
    4: NT-SRV-XHST Service with host as remaining components
    5: NT-UID Unique ID
    

在这里爆破域用户名是通过AS_REP中的字段的响应值,而并非通过请求字段能够判断,具体来说是通过判断标志:error-code

在这里便可以知道我们使用的kerbrute来爆破域用户名和进行密码爆破过程中也就是利用error-code标志进行判断: 如果用户存在,返回的数据包内容如下图: 判断标志:error-code: eRR-PREAUTH-REQUIRED (25)提示需要进行预认证,或者也可能PREAUTH-FAILED

两种情况分别是在daiker师傅和3gstudent师傅博客中的对比

而在如果用户不存在,返回的数据包内容如图: 判断标志:error-code: eRR-C-PRINCIPAL-UNKNOWN (6)

  • sname sname包含服务端的身份,已经域名,till为到期时间,nonce为随机生成数。在AS_REQ里面sname是krbtgt,类型是KRB_NT_SRV_INST。个人理解是面向KDC的通信,因此服务端用户肯定是krbtgt

till到期时间,rubeus和kekeo都是20370913024805Z,这个可以作为特征来检测工具,又是一个小细节

etype是加密类型,字段比较多,列举如下: 需要注意的是,KDC是通过AS_REQ中的etype加密类型来选择对应用户的Hash进行解密的,在认证的时候也是会通过明文密码的Hash值进行加密时间戳,因此这里就会存在PTH和PTK,在这里埋个伏笔

AS_REP

当客户端进行完AS_REQ,将用户的Hash加密时间戳给KDC后,KDC使用对应客户的Hash进行解密,如果结果正确则会返回用krbtgt用户的Hash值加密的TGT凭证,TGT里面包含PAC,PAC包含用户的sid,用户所在的组。

AS_REP中主要存在两个关键字段,ticketenc_part

ticket

ticket用于TGS_REQ的认证,使用krbtgt用户的hash进行加密,AS-REP的整个过程我理解为就是当认证通过后返回TGT票据的过程,因此当我们如果拿到krbtgt的账户hash和SID的话我们就可以伪造一张TGT,也就是所说的黄金票据。 可以看到在ticket中同样包含几个字段:

  1. tkt-vno 票据格式的版本号

  2. realm 派发该票据域的名称

  3. sname 该票据所属的服务端的身份

  4. enc-part 用KDC的密钥(即krbtgt的密钥)加密的票据部分

enc_part

注意区分在ticket下的enc_part,在AS_REP中的enc_part是能够解密的,这里的加密使用的是用户的密钥,也就是使用用户hash加密的Logon Session Key通过用户密码的Hash能够将enc_part解密后得到Encryptionkey,Encryptionkey里面最重要的字段是session key,作为下阶段的认证密钥。

而在ticket中enc_part是KDC中krbtgt用户的Hash进行加密的票据部分

票据

凭据里面最核心的东西是session-key作为TGS_REQ的认证密钥和加密的ticket。 正常我们用工具生成的凭据是.ccache和.kirbi后缀的,用mimikatz,kekeo,rubeus生成的凭据是以.kirbi后缀的。impacket生成的凭据的后缀是.ccache。两种票据主要包含的都是session-key和加密的ticket,因此可以相互转化。

这里直接引用daiker师傅的说明: 以kirbi为例介绍下该结构体。

 KRB-CRED::= [APPLICATION 22] SEQUENCE {
   pvno[0] INTEGER(5),
   msg-type[1] INTEGER(22),
   tickets[2] SEQUENCE OF Ticket,
   enc-part[3] EncryptedData -- EncKrbCredPart
}

其中ticket来自于KRB_AS_REP部分的ticket

EncKrbCredPart  ::= [APPLICATION 29] SEQUENCE {
   ticket-info     [0] SEQUENCE OF KrbCredInfo,  //这里就只用到这个
   nonce           [1] UInt32 OPTIONAL,
   timestamp       [2] KerberosTime OPTIONAL,
   usec            [3] Microseconds OPTIONAL,
   s-address       [4] HostAddress OPTIONAL,
   r-address       [5] HostAddress OPTIONAL
}

而在这ticket-info的主要内容就是session-key,是通过用户的Hash解密AS_REP中的enc_part字段得到

AS_REQ和AS_REP阶段可能出现的问题

pass the hash & pass the key

前文说到,在AS_REQ的时候客户端使用用户的Hash加密时间戳,即使在使用密码进行登录的情况下,也是先把密码加密成hash,再进行认证。因此在只有用户hash,没有明文密码的情况下也是可以进行认证的。其中当ntml的Hash加密方式是rc4,则算是pass the hash,如果是hash是aes key加密则算是pass the key

用户名枚举和密码爆破

前文已经说过在AS_REP返回字段中error_code会根据用户名的不同而返回不同的值,通过这个比较就可以通过改变cname的值进行用户名枚举。在域内没有域账号的情况下进行用户名枚举

注意:如果有域内机器的system权限,则该机器也是个域账户,账户名是机器名$.

在已知域用户名情况下,同样可以进行密码的爆破,在AS_REQ阶段,当密码错误时AS_REP的返回为KDC_ERR_PREAUTH_FAILED,因此可以进行密码爆破。在实践中,通常都会使用一种被称为“密码喷洒(Password Spraying)”的技术来进行测试和攻击。对密码进行喷洒式的攻击,原因是为了避免帐户被锁定,因为针对同一个用户的连续密码猜测会导致帐户被锁定。所以只有对所有用户同时执行特定的密码登录尝试,才能增加破解的概率,消除帐户被锁定的概率。

kerbrute工具便是在这一点的原理和基础上进行开发使用的

Usage

探测域用户
kerbrute.exe usernum --dc 域控IP -d 域名  username.txt

Usage
密码喷洒攻击
kerbrute.exe passwordspray  --dc 域控IP -d 域名 domain_users.txt password

AS-REP Roasting攻击攻击

该攻击方式利用比较局限,因为其需要用户账号设置 "Do not require Kerberos preauthentication(不需要kerberos预身份验证) " 。而该属性默认是没有勾选上的

这种攻击手段主要是攻击的第二个阶段,也就是AS_REP,对于域用户,如果设置了选项Do not require Kerberos preauthentication,向域控制器的88端口发送AS_REQ请求,对收到的AS_REP内容(enc-part底下的ciper,因为这部分是使用用户hash加密session-key,我们通过进行离线爆破就可以获得用户hash)重新组合

这里可以使用Rubeus工具 在Rubeus中的As-repoasting这个功能会通过LDAP查询域内用户设置了选项Do not require Kerberos preauthentication,然后发AS_REQ的包,直接生成hash或者john可破解的格式

DomainPasswordSpray

DomainPasswordSpray是用PowerShell编写的工具,用于对域用户执行密码喷洒攻击。默认情况下,它将利用LDAP从域中导出用户列表,然后扣掉被锁定的用户,再用固定密码进行密码喷洒。

Invoke-DomainPasswordSpray -Password Spring2017

Invoke-DomainPasswordSpray -UserList users.txt -Domain domain-name -PasswordList passlist.txt -OutFile sprayed-creds.txt


关于TGS_REQ等后几个阶段的Kerberos协议学习会在后续进行补充

发布于2021-08-31 15:24:05
+119赞
0条评论
收藏
内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66