From 6a37fdca8680f779a8dfab5ec0e97eaf58b048b6 Mon Sep 17 00:00:00 2001 From: Bryan De Houwer Date: Thu, 8 Sep 2022 20:07:31 +0200 Subject: [PATCH 1/7] Fix ldap baseDN lookup and kdchost assumptions --- cme/protocols/ldap.py | 100 ++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index 3abe3bcd..d58615e9 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -63,6 +63,7 @@ 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') ldap_parser.add_argument("--no-smb", action='store_true', help='No smb connection') dgroup = ldap_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to") @@ -89,6 +90,28 @@ class ldap(connection): 'hostname': self.hostname }) + def get_baseDN(self, host): + try: + ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % host) + + resp = ldapConnection.search(scope=ldapasn1_impacket.Scope('baseObject'), attributes=['defaultNamingContext'], sizeLimit=0) + for item in resp: + if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + continue + 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 + 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 '' + def get_os_arch(self): try: stringBinding = r'ncacn_ip_tcp:{}[135]'.format(self.host) @@ -177,25 +200,18 @@ class ldap(connection): return True def kerberos_login(self, domain, aesKey, kdcHost): - - if self.kdcHost is not None: - target = self.kdcHost - else: - target = self.domain - self.kdcHost = self.domain - - # Create the baseDN - self.baseDN = '' - domainParts = self.domain.split('.') - for i in domainParts: - self.baseDN += 'dc=%s,' % i - # Remove last ',' - self.baseDN = self.baseDN[:-1] + # 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] try: - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.host, self.baseDN) self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, - self.aesKey, kdcHost=self.kdcHost) + self.aesKey, kdcHost=kdcHost) out = u'{}{}'.format('{}\\'.format(self.domain), self.username) @@ -207,9 +223,9 @@ 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.host, self.baseDN) self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, - self.aesKey, kdcHost=self.kdcHost) + self.aesKey, kdcHost=kdcHost) out = u'{}{}'.format('{}\\'.format(self.domain), self.username) @@ -232,19 +248,13 @@ class ldap(connection): 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] + # 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] if self.password == '' and self.args.asreproast: hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) @@ -255,7 +265,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.host, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() @@ -277,7 +287,7 @@ class ldap(connection): if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL try: - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.host, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.logger.extra['protocol'] = "LDAPS" self.logger.extra['port'] = "636" @@ -323,19 +333,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('.') - for i in domainParts: - self.baseDN += 'dc=%s,' % i - # Remove last ',' - self.baseDN = self.baseDN[:-1] + # 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] if self.hash == '' and self.args.asreproast: hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) @@ -347,7 +351,7 @@ class ldap(connection): # Connect to LDAP try: - self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN, self.kdcHost) + self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.host, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() out = u'{}{}:{} {}'.format('{}\\'.format(domain), @@ -366,7 +370,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.host, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.logger.extra['protocol'] = "LDAPS" self.logger.extra['port'] = "636" From 032945221f51120fb192a277b2f3d3336318ee88 Mon Sep 17 00:00:00 2001 From: Bryan De Houwer Date: Thu, 8 Sep 2022 20:14:50 +0200 Subject: [PATCH 2/7] KerberosLogin resolve username --- cme/protocols/ldap.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index d58615e9..9c54d2ef 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -136,6 +136,20 @@ class ldap(connection): return 0 + def get_ldap_username(self): + extendedRequest = ldapasn1_impacket.ExtendedRequest() + extendedRequest['requestName'] = '1.3.6.1.4.1.4203.1.11.3' # whoami + + response = self.ldapConnection.sendReceive(extendedRequest) + for message in response: + searchResult = message['protocolOp'].getComponent() + if searchResult['resultCode'] == ldapasn1_impacket.ResultCode('success'): + responseValue = searchResult['responseValue'] + if responseValue.hasValue(): + value = responseValue.asOctets().decode(responseValue.encoding)[2:] + return value.split('\\')[1] + return '' + def enum_host_info(self): # smb no open, specify the domain if self.args.no_smb: @@ -213,6 +227,9 @@ class ldap(connection): self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=kdcHost) + if self.username == '': + self.username = self.get_ldap_username() + out = u'{}{}'.format('{}\\'.format(self.domain), self.username) @@ -226,6 +243,9 @@ class ldap(connection): self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.host, self.baseDN) self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=kdcHost) + + if self.username == '': + self.username = self.get_ldap_username() out = u'{}{}'.format('{}\\'.format(self.domain), self.username) From 81d20611025bf35bf02c8c315b568278620b0f76 Mon Sep 17 00:00:00 2001 From: Bryan De Houwer Date: Thu, 8 Sep 2022 20:15:31 +0200 Subject: [PATCH 3/7] Fix identation --- cme/protocols/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index 9c54d2ef..e7faeeb6 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -108,7 +108,7 @@ class ldap(connection): 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.') + self.logger.error(u'Error connecting to the host.') return '' From f391b8a2a6795ed45a79a9d632f4ba216164b2e5 Mon Sep 17 00:00:00 2001 From: Bryan De Houwer Date: Sun, 18 Sep 2022 20:49:03 +0200 Subject: [PATCH 4/7] Bug fix: ensure DN is lowercase --- cme/modules/scan-network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cme/modules/scan-network.py b/cme/modules/scan-network.py index 77f338b6..ce69686e 100644 --- a/cme/modules/scan-network.py +++ b/cme/modules/scan-network.py @@ -39,7 +39,7 @@ def get_dns_resolver(server, context): return dnsresolver def ldap2domain(ldap): - return re.sub(',DC=', '.', ldap[ldap.find('dc='):], flags=re.I)[3:] + return re.sub(',DC=', '.', ldap[ldap.lower().find('dc='):], flags=re.I)[3:] def new_record(rtype, serial): nr = DNS_RECORD() @@ -347,4 +347,4 @@ class DNS_RPC_RECORD_TS(Structure): try: return datetime.datetime(1601,1,1) + datetime.timedelta(microseconds=microseconds) except OverflowError: - return None \ No newline at end of file + return None From f5ada644a960d3cbcdd24e6ba537460e902c872f Mon Sep 17 00:00:00 2001 From: Bryan De Houwer Date: Mon, 19 Sep 2022 01:12:22 +0200 Subject: [PATCH 5/7] Ensure --domain is provided with --no-smb argument --- cme/protocols/ldap.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index e7faeeb6..e8a37036 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -20,6 +20,7 @@ from impacket.krb5.types import KerberosTime, Principal from impacket.ldap import ldap as ldap_impacket from impacket.krb5 import constants from impacket.ldap import ldapasn1 as ldapasn1_impacket +import argparse from io import StringIO from pywerview.cli.helpers import * from pywerview.requester import RPCRequester @@ -35,6 +36,22 @@ ldap_error_status = { "50":"LDAP_INSUFFICIENT_ACCESS" } + +def get_conditional_action(baseAction): + class ConditionalAction(baseAction): + def __init__(self, option_strings, dest, **kwargs): + x = kwargs.pop('make_required', []) + super(ConditionalAction, self).__init__(option_strings, dest, **kwargs) + self.make_required = x + + def __call__(self, parser, namespace, values, option_string=None): + for x in self.make_required: + x.required = True + super(ConditionalAction, self).__call__(parser, namespace, values, option_string) + + return ConditionalAction + + class ldap(connection): def __init__(self, args, db, host): @@ -64,10 +81,12 @@ class ldap(connection): 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') - ldap_parser.add_argument("--no-smb", action='store_true', help='No smb connection') + 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() - dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to") + domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to") dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target') + no_smb_arg.make_required = [domain_arg] egroup = ldap_parser.add_argument_group("Retrevie hash on the remote DC", "Options to get hashes from Kerberos") egroup.add_argument("--asreproast", help="Get AS_REP response ready to crack with hashcat") @@ -897,3 +916,4 @@ class ldap(connection): self.logger.error("No entries found!") return + From 286d8c2aca0c63a9b86c208fd3a611fe42d1afca Mon Sep 17 00:00:00 2001 From: Bryan De Houwer Date: Mon, 19 Sep 2022 12:02:58 +0200 Subject: [PATCH 6/7] Fix inconsistencies between ldap login functions --- cme/protocols/ldap.py | 75 ++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index 954d6375..e654cbb3 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -242,40 +242,55 @@ class ldap(connection): self.baseDN = self.baseDN[:-1] try: + # Connect to LDAP self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.host, self.baseDN) self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=kdcHost) if self.username == '': - self.username = self.get_ldap_username() + self.username = self.get_ldap_username() - out = u'{}{}'.format('{}\\'.format(self.domain), - self.username) + self.check_if_admin() + + # Prepare success credential text + out = u'{}\\{} {}'.format(domain, + self.username, + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.extra['protocol'] = "LDAP" self.logger.extra['port'] = "389" self.logger.success(out) + if not self.args.local_auth: + add_user_bh(self.username, self.domain, self.logger, self.config) except ldap_impacket.LDAPSessionError as e: 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.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=kdcHost) if self.username == '': - self.username = self.get_ldap_username() - - out = u'{}{}'.format('{}\\'.format(self.domain), - self.username) + self.username = self.get_ldap_username() + + self.check_if_admin() + + # Prepare success credential text + out = u'{}\\{} {}'.format(domain, + self.username, + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.extra['protocol'] = "LDAPS" self.logger.extra['port'] = "636" self.logger.success(out) + + if not self.args.local_auth: + add_user_bh(self.username, self.domain, self.logger, self.config) else: errorCode = str(e).split()[-2][:-1] - self.logger.error(u'{}\\{}:{} {}'.format(self.domain, - self.username, + 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') @@ -303,17 +318,17 @@ class ldap(connection): hash_asreproast.write(hash_TGT + '\n') return False - # Prepare success credential text - out = u'{}{}:{} {}'.format('{}\\'.format(domain), - username, - password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, - highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) try: # Connect to LDAP self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.host, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() + # Prepare success credential text + out = u'{}\\{}:{} {}'.format(domain, + self.username, + self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.extra['protocol'] = "LDAP" self.logger.extra['port'] = "389" self.logger.success(out) @@ -327,11 +342,22 @@ class ldap(connection): if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL try: + # Connect to LDAPS self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.host, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.check_if_admin() + + # Prepare success credential text + out = u'{}\\{}:{} {}'.format(domain, + self.username, + self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.extra['protocol'] = "LDAPS" self.logger.extra['port'] = "636" self.logger.success(out) + + if not self.args.local_auth: + add_user_bh(self.username, self.domain, self.logger, self.config) except ldap_impacket.LDAPSessionError as e: errorCode = str(e).split()[-2][:-1] self.logger.error(u'{}\\{}:{} {}'.format(self.domain, @@ -389,14 +415,16 @@ class ldap(connection): hash_asreproast.write(hash_TGT + '\n') return False - # Connect to LDAP try: + # Connect to LDAP self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.host, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() - out = u'{}{}:{} {}'.format('{}\\'.format(domain), - username, - nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, + + # Prepare success credential text + out = u'{}\\{}:{} {}'.format(domain, + self.username, + self.nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.extra['protocol'] = "LDAP" self.logger.extra['port'] = "389" @@ -412,9 +440,19 @@ class ldap(connection): # We need to try SSL self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.host, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.check_if_admin() + + # Prepare success credential text + out = u'{}\\{}:{} {}'.format(domain, + self.username, + self.nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.extra['protocol'] = "LDAPS" self.logger.extra['port'] = "636" self.logger.success(out) + + if not self.args.local_auth: + add_user_bh(self.username, self.domain, self.logger, self.config) except ldap_impacket.LDAPSessionError as e: errorCode = str(e).split()[-2][:-1] self.logger.error(u'{}\\{}:{} {}'.format(self.domain, @@ -918,3 +956,4 @@ class ldap(connection): return + From b11bc433809d2b0a3a9dc631fa8e08fc0b05c0b0 Mon Sep 17 00:00:00 2001 From: Bryan De Houwer Date: Wed, 21 Sep 2022 15:08:31 +0200 Subject: [PATCH 7/7] Fix cross domain kerberos authentication, kerberoasting and asreproasting issues --- cme/protocols/ldap.py | 81 +++++++++++++++++----------------- cme/protocols/ldap/kerberos.py | 5 ++- 2 files changed, 44 insertions(+), 42 deletions(-) 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