From 9d6c3fe67ec042b8d593a7af796668ee05419116 Mon Sep 17 00:00:00 2001 From: mpgn Date: Thu, 10 Nov 2022 16:07:37 -0500 Subject: [PATCH] Add kerberos compatibility for laps option --- cme/protocols/ldap/smbldap.py | 79 ++++++++++++++++++++++++++++++++++- cme/protocols/smb.py | 18 ++++++-- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/cme/protocols/ldap/smbldap.py b/cme/protocols/ldap/smbldap.py index 2dd5c824..b9ddbbd5 100644 --- a/cme/protocols/ldap/smbldap.py +++ b/cme/protocols/ldap/smbldap.py @@ -7,10 +7,12 @@ from pyasn1.codec.der import decoder, encoder from pyasn1.type.univ import noValue from impacket.ntlm import compute_lmhash, compute_nthash from impacket.ldap import ldap as ldap_impacket +from impacket.krb5.kerberosv5 import KerberosError from cme.logger import CMEAdapter ldap_error_status = { + "1":"STATUS_NOT_SUPPORTED", "533":"STATUS_ACCOUNT_DISABLED", "701":"STATUS_ACCOUNT_EXPIRED", "531":"STATUS_ACCOUNT_RESTRICTION", @@ -18,7 +20,9 @@ ldap_error_status = { "532":"STATUS_PASSWORD_EXPIRED", "773":"STATUS_PASSWORD_MUST_CHANGE", "775":"USER_ACCOUNT_LOCKED", - "50":"LDAP_INSUFFICIENT_ACCESS" + "50":"LDAP_INSUFFICIENT_ACCESS", + "KDC_ERR_CLIENT_REVOKED":"KDC_ERR_CLIENT_REVOKED", + "KDC_ERR_PREAUTH_FAILED":"KDC_ERR_PREAUTH_FAILED" } @@ -35,6 +39,77 @@ class LDAPConnect: 'hostname': hostname }) + def kerberos_login(self, domain, username, password, ntlm_hash, kdc='', aesKey=''): + lmhash = '' + nthash = '' + + if kdc == None: + kdc = domain + + #This checks to see if we didn't provide the LM Hash + if ntlm_hash and ntlm_hash.find(':') != -1: + lmhash, nthash = ntlm_hash.split(':') + elif ntlm_hash: + nthash = ntlm_hash + + # Create the baseDN + baseDN = '' + domainParts = domain.split('.') + for i in domainParts: + baseDN += 'dc=%s,' % i + # Remove last ',' + baseDN = baseDN[:-1] + + try: + ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % kdc, baseDN) + ldapConnection.kerberosLogin(username, password, domain, lmhash, nthash, aesKey, kdcHost=kdc, useCache=False) + # Connect to LDAP + out = u'{}{}:{}'.format('{}\\'.format(domain), + username, + password if password else ntlm_hash) + self.logger.extra['protocol'] = "LDAP" + self.logger.extra['port'] = "389" + return ldapConnection + except ldap_impacket.LDAPSessionError as e: + if str(e).find('strongerAuthRequired') >= 0: + # We need to try SSL + try: + ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % kdc, baseDN) + ldapConnection.login(username, password, domain, lmhash, nthash, aesKey, kdcHost=kdc, useCache=False) + self.logger.extra['protocol'] = "LDAPS" + self.logger.extra['port'] = "636" + # self.logger.success(out) + return ldapConnection + except ldap_impacket.LDAPSessionError as e: + errorCode = str(e).split()[-2][:-1] + self.logger.error(u'{}\\{}:{} {}'.format(domain, + username, + password if password else ntlm_hash, + ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), + color='magenta' if errorCode in ldap_error_status else 'red') + else: + errorCode = str(e).split()[-2][:-1] + self.logger.error(u'{}\\{}:{} {}'.format(domain, + username, + password if password else ntlm_hash, + 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.debug(u'{}\\{}:{} {}'.format(domain, + username, + password if password else ntlm_hash, + "Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller")) + return False + except KerberosError as e: + self.logger.error(u'{}\\{}:{} {}'.format(domain, + username, + password if password else ntlm_hash, + str(e)), + color='red') + return False + def plaintext_login(self, domain, username, password, ntlm_hash): lmhash = '' @@ -70,6 +145,7 @@ class LDAPConnect: return ldapConnection except ldap_impacket.LDAPSessionError as e: + print(str(e)) if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL try: @@ -87,6 +163,7 @@ class LDAPConnect: ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), color='magenta' if errorCode in ldap_error_status else 'red') else: + print(str(e)) errorCode = str(e).split()[-2][:-1] self.logger.error(u'{}\\{}:{} {}'.format(domain, username, diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index 036612d6..a7c4a2f9 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -298,8 +298,16 @@ class smb(connection): self.domain = self.hostname def laps_search(self, username, password, ntlm_hash, domain): + self.logger.extra['protocol'] = "LDAP" + self.logger.extra['port'] = "389" ldapco = LDAPConnect(self.domain, "389", self.domain) - connection = ldapco.plaintext_login(domain, username[0] if username else '', password[0] if password else '', ntlm_hash[0] if ntlm_hash else '' ) + if self.kerberos: + if self.kdcHost == None: + self.logger.error('Provide --kdcHost parameter') + return False + connection = ldapco.kerberos_login(domain, username[0] if username else '', password[0] if password else '', ntlm_hash[0] if ntlm_hash else '', self.kdcHost, self.aesKey) + else: + connection = ldapco.plaintext_login(domain, username[0] if username else '', password[0] if password else '', ntlm_hash[0] if ntlm_hash else '' ) if connection == False: logging.debug('LAPS connection failed with account {}'.format(username)) return False @@ -323,12 +331,14 @@ class smb(connection): self.username = self.args.laps self.password = msMCSAdmPwd if msMCSAdmPwd == '': - logging.debug('msMCSAdmPwd is empty, account cannot read LAPS property for {}'.format(self.hostname)) + self.logger.error('msMCSAdmPwd is empty or account cannot read LAPS property for {}'.format(self.hostname)) return False if ntlm_hash: hash_ntlm = hashlib.new('md4', msMCSAdmPwd.encode('utf-16le')).digest() self.hash = binascii.hexlify(hash_ntlm).decode() self.domain = self.hostname + self.logger.extra['protocol'] = "SMB" + self.logger.extra['port'] = "445" return True def print_host_info(self): @@ -403,7 +413,7 @@ class smb(connection): self.username, # Show what was used between cleartext, nthash, aesKey and ccache " from ccache" if useCache - else ":%s" % (next(sub for sub in [nthash,password,aesKey] if (sub != '' and sub != None) or sub != None) if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), str(e), '', color='red')) @@ -413,7 +423,7 @@ class smb(connection): self.username, # Show what was used between cleartext, nthash, aesKey and ccache " from ccache" if useCache - else ":%s" % (next(sub for sub in [nthash,password,aesKey] if (sub != '' and sub != None) or sub != None) if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), error, '({})'.format(desc) if self.args.verbose else ''), color='magenta' if error in smb_error_status else 'red')