From 091915b99024c3daaaad2bc0623d6213ab609862 Mon Sep 17 00:00:00 2001 From: mpgn Date: Thu, 24 Jun 2021 14:37:54 -0400 Subject: [PATCH 1/3] Fix and add a lot, check commit message Update LDAP proto: - can fetch a LDAP domain from an account from another domain (trust relation between forest) - fix sizeLimit to unlimited on LDAP queries - fix little mistake in LDAP modules Update SMB proto: - fix users function when DC is vulnerable to NULL SESSION - add SAMRPC function to fetch users on the domain - add option --computers to fetch all computers Update CLI - add function export, but it's not tested --- cme/cli.py | 1 + cme/connection.py | 6 +- cme/modules/MachineAccountQuota.py | 1 + cme/modules/get_description.py | 15 +- cme/protocols/ldap.py | 408 +++++++++++++++-------------- cme/protocols/smb.py | 40 ++- cme/protocols/smb/passpol.py | 2 + cme/protocols/smb/samruser.py | 111 ++++++++ 8 files changed, 366 insertions(+), 218 deletions(-) create mode 100644 cme/protocols/smb/samruser.py diff --git a/cme/cli.py b/cme/cli.py index 99e40489..d12be090 100755 --- a/cme/cli.py +++ b/cme/cli.py @@ -46,6 +46,7 @@ def gen_cli_args(): std_parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="username(s) or file(s) containing usernames") std_parser.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="password(s) or file(s) containing passwords") std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)") + std_parser.add_argument("--export", metavar="EXPORT", nargs='+', help="Export result into a file, probably buggy") std_parser.add_argument("--aesKey", metavar="AESKEY", nargs='+', help="AES key to use for Kerberos Authentication (128 or 256 bits)") std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter") diff --git a/cme/connection.py b/cme/connection.py index a26823d2..2ea1a424 100755 --- a/cme/connection.py +++ b/cme/connection.py @@ -7,6 +7,7 @@ from socket import gethostbyname from functools import wraps from cme.logger import CMEAdapter from cme.context import Context +from cme.helpers.logger import write_log sem = BoundedSemaphore(1) global_failed_logins = 0 @@ -45,6 +46,7 @@ class connection(object): self.kerberos = True if self.args.kerberos else False self.aesKey = None if not self.args.aesKey else self.args.aesKey self.kdcHost = None if not self.args.kdcHost else self.args.kdcHost + self.export = None if not self.args.export else self.args.export self.failed_logins = 0 self.local_ip = None @@ -103,7 +105,9 @@ class connection(object): if hasattr(self, k) and hasattr(getattr(self, k), '__call__'): if v is not False and v is not None: logging.debug('Calling {}()'.format(k)) - getattr(self, k)() + r = getattr(self, k)() + if self.export: + write_log(str(r), self.export[0]) def call_modules(self): module_logger = CMEAdapter(extra={ diff --git a/cme/modules/MachineAccountQuota.py b/cme/modules/MachineAccountQuota.py index 66862502..69372135 100644 --- a/cme/modules/MachineAccountQuota.py +++ b/cme/modules/MachineAccountQuota.py @@ -23,6 +23,7 @@ class CMEModule: multiple_hosts = False def on_login(self, context, connection): + result = [] context.log.info('Getting the MachineAccountQuota') searchFilter = '(objectClass=*)' attributes = ['ms-DS-MachineAccountQuota'] diff --git a/cme/modules/get_description.py b/cme/modules/get_description.py index e2cf3f7b..358f0f04 100644 --- a/cme/modules/get_description.py +++ b/cme/modules/get_description.py @@ -1,4 +1,5 @@ from impacket.ldap import ldapasn1 as ldapasn1_impacket +from impacket.ldap import ldap as ldap_impacket import re class CMEModule: @@ -35,13 +36,13 @@ class CMEModule: searchFilter = "(objectclass=user)" try: - logging.debug('Search Filter=%s' % searchFilter) + context.log.debug('Search Filter=%s' % searchFilter) resp = connection.ldapConnection.search(searchFilter=searchFilter, attributes=['sAMAccountName','description'], - sizeLimit=999) + sizeLimit=0) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: - logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received') + context.log.debug('sizeLimitExceeded exception caught, giving up and processing the data received') # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() @@ -51,7 +52,7 @@ class CMEModule: return False answers = [] - logging.debug('Total of records returned %d' % len(resp)) + context.log.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue @@ -66,14 +67,14 @@ class CMEModule: if sAMAccountName != '' and description != '': answers.append([sAMAccountName,description]) except Exception as e: - logging.debug("Exception:", exc_info=True) - logging.debug('Skipping item, cannot process due to error %s' % str(e)) + context.log.debug("Exception:", exc_info=True) + context.log.debug('Skipping item, cannot process due to error %s' % str(e)) pass answers = self.filter_answer(context, answers) if len(answers) > 0: context.log.success('Found following users: ') for answer in answers: - context.log.highlight('User: {} description: {}'.format(answer[0],answer[1])) + context.log.highlight(u'User: {} description: {}'.format(answer[0],answer[1])) def filter_answer(self, context, answers): # No option to filter diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index 04a9e55d..648b33e9 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -58,7 +58,7 @@ class ldap(connection): vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION") vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD") vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") - vgroup.add_argument("--users", action="store_true", help="Enumerate domain users") + vgroup.add_argument("--users", action="store_true", help="Enumerate enabled domain users") vgroup.add_argument("--groups", action="store_true", help="Enumerate domain groups") return parser @@ -150,10 +150,10 @@ class ldap(connection): # Remove last ',' self.baseDN = self.baseDN[:-1] - if self.kdcHost is not None: - target = self.kdcHost - else: - target = self.domain + # if self.kdcHost is not None: + # target = self.kdcHost + # else: + # target = self.domain try: self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, @@ -161,7 +161,7 @@ class ldap(connection): except ldap_impacket.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.kdcHost, self.baseDN, self.kdcHost) self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=self.kdcHost) @@ -174,16 +174,16 @@ class ldap(connection): self.domain = domain # Create the baseDN self.baseDN = '' - domainParts = self.domain.split('.') + domainParts = self.kdcHost.split('.') for i in domainParts: self.baseDN += 'dc=%s,' % i # Remove last ',' self.baseDN = self.baseDN[:-1] - if self.kdcHost is not None: - target = self.kdcHost - else: - target = domain + # if self.kdcHost is not None: + # target = self.kdcHost + # else: + # target = domain if self.password == '' and self.args.asreproast: hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) @@ -194,7 +194,7 @@ class ldap(connection): return False try: - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.kdcHost, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) #self.check_if_admin() @@ -251,16 +251,16 @@ class ldap(connection): self.domain = domain # Create the baseDN self.baseDN = '' - domainParts = self.domain.split('.') + domainParts = self.kdcHost.split('.') for i in domainParts: self.baseDN += 'dc=%s,' % i # Remove last ',' self.baseDN = self.baseDN[:-1] - if self.kdcHost is not None: - target = self.kdcHost - else: - target = domain + # if self.kdcHost is not None: + # target = self.kdcHost + # else: + # target = domain if self.hash == '' and self.args.asreproast: hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) @@ -275,7 +275,7 @@ class ldap(connection): username, nthash) try: - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.kdcHost, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) #self.check_if_admin() self.logger.success(out) @@ -286,7 +286,7 @@ class ldap(connection): if str(e).find('strongerAuthRequired') >= 0: try: # We need to try SSL - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.kdcHost, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.logger.success(out) except ldap_impacket.LDAPSessionError as e: @@ -344,7 +344,7 @@ class ldap(connection): t /= 10000000 return t - def search(self, searchFilter, attributes, sizeLimit=999): + def search(self, searchFilter, attributes, sizeLimit=0): try: logging.debug('Search Filter=%s' % searchFilter) resp = self.ldapConnection.search(searchFilter=searchFilter, @@ -366,76 +366,74 @@ class ldap(connection): # Building the search filter searchFilter = "(sAMAccountType=805306368)" attributes= [] - resp = self.search(searchFilter, attributes, 999) - answers = [] - logging.debug('Total of records returned %d' % len(resp)) + resp = self.search(searchFilter, attributes, sizeLimit=0) + if resp: + answers = [] + self.logger.info('Total of records returned %d' % len(resp)) - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - mustCommit = False - sAMAccountName = '' - badPasswordTime = '' - badPwdCount = 0 - try: - for attribute in item['attributes']: - if str(attribute['type']) == 'sAMAccountName': - sAMAccountName = str(attribute['vals'][0]) - mustCommit = True - elif str(attribute['type']) == 'badPwdCount': - badPwdCount = "0x%x" % int(attribute['vals'][0]) - elif str(attribute['type']) == 'badPasswordTime': - if str(attribute['vals'][0]) == '0': - badPasswordTime = '' - else: - badPasswordTime = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) - if mustCommit is True: - answers.append([sAMAccountName, badPwdCount, badPasswordTime]) - except Exception as e: - logging.debug("Exception:", exc_info=True) - logging.debug('Skipping item, cannot process due to error %s' % str(e)) - pass - if len(answers)>0: - logging.debug(answers) - for value in answers: - self.logger.highlight('{:<30} badpwdcount: {} pwdLastSet: {}'.format(value[0], int(value[1],16),value[2])) - else: - self.logger.error("No entries found!") - return + for item in resp: + if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + continue + sAMAccountName = '' + badPasswordTime = '' + badPwdCount = 0 + try: + for attribute in item['attributes']: + if str(attribute['type']) == 'sAMAccountName': + sAMAccountName = str(attribute['vals'][0]) + elif str(attribute['type']) == 'badPwdCount': + badPwdCount = str(attribute['vals'][0]) + elif str(attribute['type']) == 'pwdLastSet': + if str(attribute['vals'][0]) == '0': + pwdLastSet = '' + else: + pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) + answers.append([sAMAccountName, badPwdCount, pwdLastSet]) + except Exception as e: + self.logger.debug('Skipping item, cannot process due to error %s' % str(e)) + pass + if len(answers)>0: + logging.debug(answers) + for value in answers: + self.logger.highlight('{:<30} badpwdcount: {} pwdLastSet: {}'.format(value[0], value[1],value[2])) + else: + self.logger.error("No entries found!") + return def groups(self): # Building the search filter searchFilter = "(objectCategory=group)" attributes=[] - resp = self.search(searchFilter, attributes, 999) - answers = [] - logging.debug('Total of records returned %d' % len(resp)) + resp = self.search(searchFilter, attributes, 0) + if resp: + answers = [] + logging.debug('Total of records returned %d' % len(resp)) - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - mustCommit = False - name = '' - try: - for attribute in item['attributes']: - if str(attribute['type']) == 'name': - name = str(attribute['vals'][0]) - mustCommit = True - # if str(attribute['type']) == 'objectSid': - # print(format_sid((attribute['vals'][0]))) - if mustCommit is True: - answers.append([name]) - except Exception as e: - logging.debug("Exception:", exc_info=True) - logging.debug('Skipping item, cannot process due to error %s' % str(e)) - pass - if len(answers)>0: - logging.debug(answers) - for value in answers: - self.logger.highlight('{}'.format(value[0])) - else: - self.logger.error("No entries found!") - return + for item in resp: + if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + continue + mustCommit = False + name = '' + try: + for attribute in item['attributes']: + if str(attribute['type']) == 'name': + name = str(attribute['vals'][0]) + mustCommit = True + # if str(attribute['type']) == 'objectSid': + # print(format_sid((attribute['vals'][0]))) + if mustCommit is True: + answers.append([name]) + except Exception as e: + logging.debug("Exception:", exc_info=True) + logging.debug('Skipping item, cannot process due to error %s' % str(e)) + pass + if len(answers)>0: + logging.debug(answers) + for value in answers: + self.logger.highlight('{}'.format(value[0])) + else: + self.logger.error("No entries found!") + return def asreproast(self): if self.password == '' and self.nthash == '' and self.kerberos == False: @@ -445,139 +443,143 @@ class ldap(connection): "(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % \ (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE) attributes = ['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] - resp = self.search(searchFilter, attributes, 999) + resp = self.search(searchFilter, attributes, 0) + if resp: + answers = [] + logging.debug('Total of records returned %d' % len(resp)) - answers = [] - logging.debug('Total of records returned %d' % len(resp)) - - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - mustCommit = False - sAMAccountName = '' - memberOf = '' - pwdLastSet = '' - userAccountControl = 0 - lastLogon = 'N/A' - try: - for attribute in item['attributes']: - if str(attribute['type']) == 'sAMAccountName': - sAMAccountName = str(attribute['vals'][0]) - mustCommit = True - elif str(attribute['type']) == 'userAccountControl': - userAccountControl = "0x%x" % int(attribute['vals'][0]) - elif str(attribute['type']) == 'memberOf': - memberOf = str(attribute['vals'][0]) - elif str(attribute['type']) == 'pwdLastSet': - if str(attribute['vals'][0]) == '0': - pwdLastSet = '' - else: - pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) - elif str(attribute['type']) == 'lastLogon': - if str(attribute['vals'][0]) == '0': - lastLogon = '' - else: - lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) - if mustCommit is True: - answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl]) - except Exception as e: - logging.debug("Exception:", exc_info=True) - logging.debug('Skipping item, cannot process due to error %s' % str(e)) - pass - if len(answers)>0: - for user in answers: - hash_TGT = KerberosAttacks(self).getTGT_asroast(user[0]) - self.logger.highlight(u'{}'.format(hash_TGT)) - with open(self.args.asreproast, 'a+') as hash_asreproast: - hash_asreproast.write(hash_TGT + '\n') - return True - else: - self.logger.error("No entries found!") + for item in resp: + if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + continue + mustCommit = False + sAMAccountName = '' + memberOf = '' + pwdLastSet = '' + userAccountControl = 0 + lastLogon = 'N/A' + try: + for attribute in item['attributes']: + if str(attribute['type']) == 'sAMAccountName': + sAMAccountName = str(attribute['vals'][0]) + mustCommit = True + elif str(attribute['type']) == 'userAccountControl': + userAccountControl = "0x%x" % int(attribute['vals'][0]) + elif str(attribute['type']) == 'memberOf': + memberOf = str(attribute['vals'][0]) + elif str(attribute['type']) == 'pwdLastSet': + if str(attribute['vals'][0]) == '0': + pwdLastSet = '' + else: + pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) + elif str(attribute['type']) == 'lastLogon': + if str(attribute['vals'][0]) == '0': + lastLogon = '' + else: + lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) + if mustCommit is True: + answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl]) + except Exception as e: + logging.debug("Exception:", exc_info=True) + logging.debug('Skipping item, cannot process due to error %s' % str(e)) + pass + if len(answers)>0: + for user in answers: + hash_TGT = KerberosAttacks(self).getTGT_asroast(user[0]) + self.logger.highlight(u'{}'.format(hash_TGT)) + with open(self.args.asreproast, 'a+') as hash_asreproast: + hash_asreproast.write(hash_TGT + '\n') + return True + else: + self.logger.highlight("No entries found!") + return + self.logger.error("Error with the LDAP account used") def kerberoasting(self): # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))" attributes = ['servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] - resp = self.search(searchFilter, attributes, 999) + resp = self.search(searchFilter, attributes, 0) + if resp: + answers = [] + logging.debug('Total of records returned %d' % len(resp)) - answers = [] - logging.debug('Total of records returned %d' % len(resp)) - - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - mustCommit = False - sAMAccountName = '' - memberOf = '' - SPNs = [] - pwdLastSet = '' - userAccountControl = 0 - lastLogon = 'N/A' - delegation = '' - try: - for attribute in item['attributes']: - if str(attribute['type']) == 'sAMAccountName': - sAMAccountName = str(attribute['vals'][0]) - mustCommit = True - elif str(attribute['type']) == 'userAccountControl': - userAccountControl = str(attribute['vals'][0]) - if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: - delegation = 'unconstrained' - elif int(userAccountControl) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: - delegation = 'constrained' - elif str(attribute['type']) == 'memberOf': - memberOf = str(attribute['vals'][0]) - elif str(attribute['type']) == 'pwdLastSet': - if str(attribute['vals'][0]) == '0': - pwdLastSet = '' - else: - pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) - elif str(attribute['type']) == 'lastLogon': - if str(attribute['vals'][0]) == '0': - lastLogon = '' - else: - lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) - elif str(attribute['type']) == 'servicePrincipalName': - for spn in attribute['vals']: - SPNs.append(str(spn)) - - if mustCommit is True: - if int(userAccountControl) & UF_ACCOUNTDISABLE: - logging.debug('Bypassing disabled account %s ' % sAMAccountName) - else: - for spn in SPNs: - answers.append([spn, sAMAccountName,memberOf, pwdLastSet, lastLogon, delegation]) - except Exception as e: - logging.error('Skipping item, cannot process due to error %s' % str(e)) - pass - - if len(answers)>0: - #users = dict( (vals[1], vals[0]) for vals in answers) - TGT = KerberosAttacks(self).getTGT_kerberoasting() - for SPN, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation in answers: + for item in resp: + if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + continue + mustCommit = False + sAMAccountName = '' + memberOf = '' + SPNs = [] + pwdLastSet = '' + userAccountControl = 0 + lastLogon = 'N/A' + delegation = '' try: - serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) - tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.domain, - self.kdcHost, - TGT['KDC_REP'], TGT['cipher'], - TGT['sessionKey']) - r = KerberosAttacks(self).outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName, SPN) - 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: - hash_kerberoasting.write(r + '\n') + for attribute in item['attributes']: + if str(attribute['type']) == 'sAMAccountName': + sAMAccountName = str(attribute['vals'][0]) + mustCommit = True + elif str(attribute['type']) == 'userAccountControl': + userAccountControl = str(attribute['vals'][0]) + if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: + delegation = 'unconstrained' + elif int(userAccountControl) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: + delegation = 'constrained' + elif str(attribute['type']) == 'memberOf': + memberOf = str(attribute['vals'][0]) + elif str(attribute['type']) == 'pwdLastSet': + if str(attribute['vals'][0]) == '0': + pwdLastSet = '' + else: + pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) + elif str(attribute['type']) == 'lastLogon': + if str(attribute['vals'][0]) == '0': + lastLogon = '' + else: + lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) + elif str(attribute['type']) == 'servicePrincipalName': + for spn in attribute['vals']: + SPNs.append(str(spn)) + + if mustCommit is True: + if int(userAccountControl) & UF_ACCOUNTDISABLE: + logging.debug('Bypassing disabled account %s ' % sAMAccountName) + else: + for spn in SPNs: + answers.append([spn, sAMAccountName,memberOf, pwdLastSet, lastLogon, delegation]) except Exception as e: - logging.debug("Exception:", exc_info=True) - logging.error('SPN: %s - %s' % (SPN,str(e))) - else: - self.logger.error("No entries found!") + logging.error('Skipping item, cannot process due to error %s' % str(e)) + pass + + if len(answers)>0: + #users = dict( (vals[1], vals[0]) for vals in answers) + TGT = KerberosAttacks(self).getTGT_kerberoasting() + for SPN, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation in answers: + try: + serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) + tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.domain, + self.kdcHost, + TGT['KDC_REP'], TGT['cipher'], + TGT['sessionKey']) + r = KerberosAttacks(self).outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName, SPN) + 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: + hash_kerberoasting.write(r + '\n') + except Exception as e: + logging.debug("Exception:", exc_info=True) + logging.error('SPN: %s - %s' % (SPN,str(e))) + else: + self.logger.highlight("No entries found!") + return + self.logger.error("Error with the LDAP account used") def trusted_for_delegation(self): # Building the search filter searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" attributes = ['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] - resp = self.search(searchFilter, attributes, 999) + resp = self.search(searchFilter, attributes, 0) answers = [] logging.debug('Total of records returned %d' % len(resp)) @@ -632,7 +634,7 @@ class ldap(connection): resp = self.ldapConnection.search(searchFilter=searchFilter, attributes=['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'], - sizeLimit=999) + sizeLimit=0) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received') @@ -694,7 +696,7 @@ class ldap(connection): # Building the search filter searchFilter = "(adminCount=1)" attributes=['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] - resp = self.search(searchFilter, attributes, 999) + resp = self.search(searchFilter, attributes, 0) answers = [] logging.debug('Total of records returned %d' % len(resp)) diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index 3b3ba285..2b2b0042 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -25,6 +25,7 @@ from cme.protocols.smb.smbexec import SMBEXEC from cme.protocols.smb.mmcexec import MMCEXEC from cme.protocols.smb.smbspider import SMBSpider from cme.protocols.smb.passpol import PassPolDump +from cme.protocols.smb.samruser import UserSamrDump from cme.helpers.logger import highlight from cme.helpers.misc import * from cme.helpers.powershell import create_ps_command @@ -149,6 +150,7 @@ class smb(connection): egroup.add_argument("--loggedon-users", action='store_true', help='enumerate logged on users') egroup.add_argument('--users', nargs='?', const='', metavar='USER', help='enumerate domain users, if a user is specified than only its information is queried.') egroup.add_argument("--groups", nargs='?', const='', metavar='GROUP', help='enumerate domain groups, if a group is specified than its members are enumerated') + egroup.add_argument('--computers', nargs='?', const='', metavar='COMPUTER', help='enumerate computer users') egroup.add_argument("--local-groups", nargs='?', const='', metavar='GROUP', help='enumerate local groups, if a group is specified then its members are enumerated') egroup.add_argument("--pass-pol", action='store_true', help='dump password policy') egroup.add_argument("--rid-brute", nargs='?', type=int, const=4000, metavar='MAX_RID', help='enumerate users by bruteforcing RID\'s (default: 4000)') @@ -675,12 +677,17 @@ class smb(connection): domain = domain+"."+v return domain + def domainfromdnshostname(self, dns): + dnsparts = dns.split(".") + domain = ".".join(dnsparts[1:]) + return domain, dnsparts[0]+"$" + def groups(self): groups = [] for dc_ip in self.get_dc_ips(): if self.args.groups: try: - groups = get_netgroupmember(dc_ip, '', self.username, password=self.password, + groups = get_netgroupmember(dc_ip, self.domain, self.username, password=self.password, lmhash=self.lmhash, nthash=self.nthash, queried_groupname=self.args.groups, queried_sid=str(), queried_domain=str(), ads_path=str(), recurse=False, use_matching_rule=False, full_data=False, custom_filter=str()) @@ -703,7 +710,7 @@ class smb(connection): self.logger.error('Error enumerating domain group members using dc ip {}: {}'.format(dc_ip, e)) else: try: - groups = get_netgroup(dc_ip, '', self.username, password=self.password, + groups = get_netgroup(dc_ip, self.domain, self.username, password=self.password, lmhash=self.lmhash, nthash=self.nthash, queried_groupname=str(), queried_sid=str(), queried_username=str(), queried_domain=str(), ads_path=str(), admin_count=False, full_data=True, custom_filter=str()) @@ -726,7 +733,7 @@ class smb(connection): users = [] for dc_ip in self.get_dc_ips(): try: - users = get_netuser(dc_ip, '', self.username, password=self.password, lmhash=self.lmhash, + users = get_netuser(dc_ip, self.domain, self.username, password=self.password, lmhash=self.lmhash, nthash=self.nthash, queried_username=self.args.users, queried_domain='', ads_path=str(), admin_count=False, spn=False, unconstrained=False, allow_delegation=False, custom_filter=str()) @@ -735,14 +742,33 @@ class smb(connection): for user in users: domain = self.domainfromdsn(user.distinguishedname) self.logger.highlight('{}\\{:<30} badpwdcount: {} baddpwdtime: {}'.format(domain,user.samaccountname,getattr(user,'badpwdcount',0),getattr(user, 'badpasswordtime',''))) - self.db.add_user(domain, user.samaccountname) - + #self.db.add_user(domain, user.samaccountname) break except Exception as e: - logging.debug('Error enumerating domain users using dc ip {}: {}'.format(dc_ip, e)) - + self.logger.error('Error enumerating domain users using dc ip {}: {}'.format(dc_ip, e)) + self.logger.info('Trying with SAMRPC protocol') + self.logger.success('Enumerated domain user(s)') + users = UserSamrDump(self).dump() + break return users + def computers(self): + computers = [] + for dc_ip in self.get_dc_ips(): + try: + computers = get_netcomputer(dc_ip, self.domain, self.username, password=self.password, lmhash=self.lmhash, + nthash=self.nthash, queried_domain='', ads_path=str(), custom_filter=str()) + + self.logger.success('Enumerated domain computer(s)') + for computer in computers: + domain, computerclean = self.domainfromdnshostname(computer.dnshostname) + self.logger.highlight('{}\\{:<30}'.format(domain, computerclean)) + break + except Exception as e: + self.logger.error('Error enumerating domain computers using dc ip {}: {}'.format(dc_ip, e)) + break + return computers + def loggedon_users(self): loggedon = [] try: diff --git a/cme/protocols/smb/passpol.py b/cme/protocols/smb/passpol.py index cbec8034..2249e71a 100644 --- a/cme/protocols/smb/passpol.py +++ b/cme/protocols/smb/passpol.py @@ -173,6 +173,8 @@ class PassPolDump: 'lock_accnt_dur': self.__lock_accnt_dur, 'accnt_lock_thres': self.__accnt_lock_thres, 'force_logoff_time': self.__force_logoff_time} + dce.disconnect() + def pretty_print(self): PASSCOMPLEX = { diff --git a/cme/protocols/smb/samruser.py b/cme/protocols/smb/samruser.py new file mode 100644 index 00000000..bd9daff2 --- /dev/null +++ b/cme/protocols/smb/samruser.py @@ -0,0 +1,111 @@ +#Stolen from Impacket + +import logging +from impacket.dcerpc.v5.rpcrt import DCERPC_v5 +from impacket.dcerpc.v5 import transport, samr +from impacket.dcerpc.v5.samr import DCERPCSessionError +from impacket.dcerpc.v5.rpcrt import DCERPCException +from impacket.nt_errors import STATUS_MORE_ENTRIES +from impacket import ntlm + +class UserSamrDump: + + KNOWN_PROTOCOLS = { + '139/SMB': (r'ncacn_np:%s[\pipe\samr]', 139), + '445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445), + } + + def __init__(self, connection): + self.logger = connection.logger + self.addr = connection.host + self.protocol = connection.args.port + self.username = connection.username + self.password = connection.password + self.domain = connection.domain + self.hash = connection.hash + self.lmhash = '' + self.nthash = '' + self.aesKey = None + self.doKerberos = False + self.protocols = UserSamrDump.KNOWN_PROTOCOLS.keys() + self.users = [] + + if self.hash is not None: + if self.hash.find(':') != -1: + self.lmhash, self.nthash = self.hash.split(':') + else: + self.nthash = self.hash + + if self.password is None: + self.password = '' + + def dump(self): + + # Try all requested protocols until one works. + for protocol in self.protocols: + try: + protodef = UserSamrDump.KNOWN_PROTOCOLS[protocol] + port = protodef[1] + except KeyError: + logging.debug("Invalid Protocol '{}'".format(protocol)) + logging.debug("Trying protocol {}".format(protocol)) + rpctransport = transport.SMBTransport(self.addr, port, r'\samr', self.username, self.password, self.domain, + self.lmhash, self.nthash, self.aesKey, doKerberos = self.doKerberos) + try: + self.fetchList(rpctransport) + except Exception as e: + logging.debug('Protocol failed: {}'.format(e)) + return self.users + + def fetchList(self, rpctransport): + dce = DCERPC_v5(rpctransport) + dce.connect() + dce.bind(samr.MSRPC_UUID_SAMR) + + # Setup Connection + resp = samr.hSamrConnect2(dce) + if resp['ErrorCode'] != 0: + raise Exception('Connect error') + + resp2 = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle=resp['ServerHandle'], + enumerationContext=0, + preferedMaximumLength=500) + if resp2['ErrorCode'] != 0: + raise Exception('Connect error') + + resp3 = samr.hSamrLookupDomainInSamServer(dce, serverHandle=resp['ServerHandle'], + name=resp2['Buffer']['Buffer'][0]['Name']) + if resp3['ErrorCode'] != 0: + raise Exception('Connect error') + + resp4 = samr.hSamrOpenDomain(dce, serverHandle=resp['ServerHandle'], + desiredAccess=samr.MAXIMUM_ALLOWED, + domainId=resp3['DomainId']) + if resp4['ErrorCode'] != 0: + raise Exception('Connect error') + + self.__domains = resp2['Buffer']['Buffer'] + domainHandle = resp4['DomainHandle'] + # End Setup + + status = STATUS_MORE_ENTRIES + enumerationContext = 0 + while status == STATUS_MORE_ENTRIES: + try: + resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, enumerationContext = enumerationContext) + except DCERPCException as e: + if str(e).find('STATUS_MORE_ENTRIES') < 0: + raise + resp = e.get_packet() + for user in resp['Buffer']['Buffer']: + r = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, user['RelativeId']) + info = samr.hSamrQueryInformationUser2(dce, r['UserHandle'],samr.USER_INFORMATION_CLASS.UserAllInformation) + (username, uid, info_user) = (user['Name'], user['RelativeId'], info['Buffer']['All']) + self.logger.highlight('{}\\{:<30} {}'.format(self.domain, user['Name'], info_user['AdminComment'])) + self.users.append(user['Name']) + samr.hSamrCloseHandle(dce, r['UserHandle']) + + enumerationContext = resp['EnumerationContext'] + status = resp['ErrorCode'] + + dce.disconnect() From d5a005898e4b946f00f2ce9626801a09206b1833 Mon Sep 17 00:00:00 2001 From: mpgn Date: Fri, 2 Jul 2021 04:50:41 -0400 Subject: [PATCH 2/3] Improve LDAP protocol - improve authentification status error - check if user is on a juicy group --- cme/cli.py | 2 +- cme/protocols/ldap.py | 211 ++++++++++++++++++++++++++++++------------ 2 files changed, 154 insertions(+), 59 deletions(-) diff --git a/cme/cli.py b/cme/cli.py index e7f44c43..1ef446c5 100755 --- a/cme/cli.py +++ b/cme/cli.py @@ -48,7 +48,7 @@ def gen_cli_args(): std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)") std_parser.add_argument("--export", metavar="EXPORT", nargs='+', help="Export result into a file, probably buggy") std_parser.add_argument("--aesKey", metavar="AESKEY", nargs='+', help="AES key to use for Kerberos Authentication (128 or 256 bits)") - std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter") + std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter") fail_group = std_parser.add_mutually_exclusive_group() fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='max number of global failed login attempts') diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index 648b33e9..de117c62 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -4,6 +4,7 @@ import requests import logging import configparser +from binascii import b2a_hex, unhexlify, hexlify from cme.connection import * from cme.helpers.logger import highlight from cme.logger import CMEAdapter @@ -18,6 +19,17 @@ from impacket.krb5 import constants from impacket.ldap import ldapasn1 as ldapasn1_impacket from io import StringIO +ldap_error_status = { + "533":"STATUS_ACCOUNT_DISABLED", + "701":"STATUS_ACCOUNT_EXPIRED", + "531":"STATUS_ACCOUNT_RESTRICTION", + "530":"STATUS_INVALID_LOGON_HOURS", + "532":"STATUS_PASSWORD_EXPIRED", + "773":"STATUS_PASSWORD_MUST_CHANGE", + "775":"USER_ACCOUNT_LOCKED", + "50":"LDAP_INSUFFICIENT_ACCESS" +} + class ldap(connection): def __init__(self, args, db, host): @@ -142,36 +154,12 @@ class ldap(connection): self.smbv1)) def kerberos_login(self, aesKey, kdcHost): - # Create the baseDN - domainParts = self.domain.split('.') - self.baseDN = '' - for i in domainParts: - self.baseDN += 'dc=%s,' % i - # Remove last ',' - self.baseDN = self.baseDN[:-1] + if self.kdcHost is not None: + target = self.kdcHost + else: + target = self.domain + self.kdcHost = domain - # if self.kdcHost is not None: - # target = self.kdcHost - # else: - # target = self.domain - - try: - self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, - self.aesKey, kdcHost=self.kdcHost) - except ldap_impacket.LDAPSessionError as e: - if str(e).find('strongerAuthRequired') >= 0: - # We need to try SSL - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.kdcHost, self.baseDN, self.kdcHost) - self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, - self.aesKey, kdcHost=self.kdcHost) - - return True - - - def plaintext_login(self, domain, username, password): - self.username = username - self.password = password - self.domain = domain # Create the baseDN self.baseDN = '' domainParts = self.kdcHost.split('.') @@ -180,10 +168,44 @@ class ldap(connection): # Remove last ',' self.baseDN = self.baseDN[:-1] - # if self.kdcHost is not None: - # target = self.kdcHost - # else: - # target = domain + try: + self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, + self.aesKey, kdcHost=self.kdcHost) + except ldap_impacket.LDAPSessionError as e: + if str(e).find('strongerAuthRequired') >= 0: + # We need to try SSL + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN, self.kdcHost) + self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, + self.aesKey, kdcHost=self.kdcHost) + else: + errorCode = str(e).split()[-2][:-1] + self.logger.error(u'{}\\{}:{} {}'.format(self.domain, + self.username, + self.password, + ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), + color='magenta' if errorCode in ldap_error_status else 'red') + + return True + + + def plaintext_login(self, domain, username, password): + self.username = username + self.password = password + self.domain = domain + + if self.kdcHost is not None: + target = self.kdcHost + else: + target = domain + self.kdcHost = domain + + # Create the baseDN + self.baseDN = '' + domainParts = self.kdcHost.split('.') + for i in domainParts: + self.baseDN += 'dc=%s,' % i + # Remove last ',' + self.baseDN = self.baseDN[:-1] if self.password == '' and self.args.asreproast: hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) @@ -194,9 +216,9 @@ class ldap(connection): return False try: - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.kdcHost, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) - #self.check_if_admin() + self.check_if_admin() # Connect to LDAP out = u'{}{}:{} {}'.format('{}\\'.format(domain), @@ -216,17 +238,23 @@ class ldap(connection): self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.logger.success(out) except ldap_impacket.LDAPSessionError as e: - self.logger.error(u'{}\{}:{}'.format(self.domain, - self.username, - self.password)) + errorCode = str(e).split()[-2][:-1] + self.logger.error(u'{}\\{}:{} {}'.format(self.domain, + self.username, + self.password, + ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), + color='magenta' if errorCode in ldap_error_status else 'red') else: - self.logger.error(u'{}\{}:{}'.format(self.domain, + errorCode = str(e).split()[-2][:-1] + self.logger.error(u'{}\\{}:{} {}'.format(self.domain, self.username, - self.password)) + self.password, + ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), + color='magenta' if errorCode in ldap_error_status else 'red') return False except OSError as e: - self.logger.error(u'{}\{}:{} {}'.format(self.domain, + self.logger.error(u'{}_\{}:{} {}'.format(self.domain, self.username, self.password, "Error connecting to the domain, please add option --kdcHost with the IP of the domain controller")) @@ -249,6 +277,13 @@ class ldap(connection): self.username = username self.domain = domain + + if self.kdcHost is not None: + target = self.kdcHost + else: + target = domain + self.kdcHost = domain + # Create the baseDN self.baseDN = '' domainParts = self.kdcHost.split('.') @@ -257,11 +292,6 @@ class ldap(connection): # Remove last ',' self.baseDN = self.baseDN[:-1] - # if self.kdcHost is not None: - # target = self.kdcHost - # else: - # target = domain - if self.hash == '' and self.args.asreproast: hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) if hash_TGT: @@ -275,9 +305,9 @@ class ldap(connection): username, nthash) try: - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.kdcHost, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) - #self.check_if_admin() + self.check_if_admin() self.logger.success(out) if not self.args.continue_on_success: @@ -286,17 +316,23 @@ class ldap(connection): if str(e).find('strongerAuthRequired') >= 0: try: # We need to try SSL - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.kdcHost, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.logger.success(out) except ldap_impacket.LDAPSessionError as e: - self.logger.error(u'{}\{}:{}'.format(self.domain, + errorCode = str(e).split()[-2][:-1] + self.logger.error(u'{}\\{}:{} {}'.format(self.domain, self.username, - self.nthash)) + self.password, + ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), + color='magenta' if errorCode in ldap_error_status else 'red') else: - self.logger.error(u'{}\{}:{}'.format(self.domain, - self.username, - self.nthash)) + errorCode = str(e).split()[-2][:-1] + self.logger.error(u'{}\\{}:{} {}'.format(self.domain, + self.username, + self.password, + ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), + color='magenta' if errorCode in ldap_error_status else 'red') return False except OSError as e: self.logger.error(u'{}\{}:{} {}'.format(self.domain, @@ -339,6 +375,65 @@ class ldap(connection): return False + def sid_to_str(self, sid): + + try: + # revision + revision = int(sid[0]) + # count of sub authorities + sub_authorities = int(sid[1]) + # big endian + identifier_authority = int.from_bytes(sid[2:8], byteorder='big') + # If true then it is represented in hex + if identifier_authority >= 2 ** 32: + identifier_authority = hex(identifier_authority) + + # loop over the count of small endians + sub_authority = '-' + '-'.join([str(int.from_bytes(sid[8 + (i * 4): 12 + (i * 4)], byteorder='little')) for i in range(sub_authorities)]) + objectSid = 'S-' + str(revision) + '-' + str(identifier_authority) + sub_authority + + return objectSid + except Exception: + pass + + return sid + + def check_if_admin(self): + + # 1. get SID of the domaine + sid_domaine = "" + searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=8192)" + attributes= ["objectSid"] + resp = self.search(searchFilter, attributes, sizeLimit=0) + answers = [] + for attribute in resp[0][1]: + if str(attribute['type']) == 'objectSid': + sid = self.sid_to_str(attribute['vals'][0]) + sid_domaine = '-'.join(sid.split('-')[:-1]) + + # 2. get all group cn name + searchFilter = "(|(objectSid="+sid_domaine+"-512)(objectSid="+sid_domaine+"-544)(objectSid="+sid_domaine+"-519)(objectSid=S-1-5-32-549)(objectSid=S-1-5-32-551))" + attributes= ["distinguishedName"] + resp = self.search(searchFilter, attributes, sizeLimit=0) + answers = [] + for item in resp: + if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + continue + for attribute in item['attributes']: + if str(attribute['type']) == 'distinguishedName': + answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute['vals'][0] + ")")) + + # 3. get memeber of these groups + searchFilter = "(&(objectCategory=user)(sAMAccountName=" + self.username + ")(|" + ''.join(answers) + "))" + attributes= [""] + resp = self.search(searchFilter, attributes, sizeLimit=0) + answers = [] + for item in resp: + if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + continue + if item: + self.admin_privs = True + def getUnixTime(self, t): t -= 116444736000000000 t /= 10000000 @@ -352,13 +447,13 @@ class ldap(connection): sizeLimit=sizeLimit) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: - logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received') + self.logger.error('sizeLimitExceeded exception caught, giving up and processing the data received') # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: - logging.debug(e) + self.logger.error(e) return False return resp @@ -446,7 +541,7 @@ class ldap(connection): resp = self.search(searchFilter, attributes, 0) if resp: answers = [] - logging.debug('Total of records returned %d' % len(resp)) + self.logger.info('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: @@ -502,7 +597,7 @@ class ldap(connection): resp = self.search(searchFilter, attributes, 0) if resp: answers = [] - logging.debug('Total of records returned %d' % len(resp)) + self.logger.info('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: From fdab5c545f889e70d7ce2c569dbe34a6d6fb4490 Mon Sep 17 00:00:00 2001 From: mpgn Date: Sat, 18 Sep 2021 17:02:01 -0400 Subject: [PATCH 3/3] Update ldap protocol message --- cme/protocols/ldap.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index de117c62..edfee191 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -254,10 +254,10 @@ class ldap(connection): return False except OSError as e: - self.logger.error(u'{}_\{}:{} {}'.format(self.domain, + self.logger.error(u'{}\\{}:{} {}'.format(self.domain, self.username, self.password, - "Error connecting to the domain, please add option --kdcHost with the IP of the domain controller")) + "Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller")) return False @@ -335,10 +335,10 @@ class ldap(connection): color='magenta' if errorCode in ldap_error_status else 'red') return False except OSError as e: - self.logger.error(u'{}\{}:{} {}'.format(self.domain, + self.logger.error(u'{}\\{}:{} {}'.format(self.domain, self.username, self.nthash, - "Error connecting to the domain, please add option --kdcHost with the IP of the domain controller")) + "Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller")) return False def create_smbv1_conn(self):