Add kerberos compatibility for laps option

main
mpgn 2022-11-10 16:07:37 -05:00
parent 25978c0be0
commit 9d6c3fe67e
2 changed files with 92 additions and 5 deletions

View File

@ -7,10 +7,12 @@ from pyasn1.codec.der import decoder, encoder
from pyasn1.type.univ import noValue from pyasn1.type.univ import noValue
from impacket.ntlm import compute_lmhash, compute_nthash from impacket.ntlm import compute_lmhash, compute_nthash
from impacket.ldap import ldap as ldap_impacket from impacket.ldap import ldap as ldap_impacket
from impacket.krb5.kerberosv5 import KerberosError
from cme.logger import CMEAdapter from cme.logger import CMEAdapter
ldap_error_status = { ldap_error_status = {
"1":"STATUS_NOT_SUPPORTED",
"533":"STATUS_ACCOUNT_DISABLED", "533":"STATUS_ACCOUNT_DISABLED",
"701":"STATUS_ACCOUNT_EXPIRED", "701":"STATUS_ACCOUNT_EXPIRED",
"531":"STATUS_ACCOUNT_RESTRICTION", "531":"STATUS_ACCOUNT_RESTRICTION",
@ -18,7 +20,9 @@ ldap_error_status = {
"532":"STATUS_PASSWORD_EXPIRED", "532":"STATUS_PASSWORD_EXPIRED",
"773":"STATUS_PASSWORD_MUST_CHANGE", "773":"STATUS_PASSWORD_MUST_CHANGE",
"775":"USER_ACCOUNT_LOCKED", "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 '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): def plaintext_login(self, domain, username, password, ntlm_hash):
lmhash = '' lmhash = ''
@ -70,6 +145,7 @@ class LDAPConnect:
return ldapConnection return ldapConnection
except ldap_impacket.LDAPSessionError as e: except ldap_impacket.LDAPSessionError as e:
print(str(e))
if str(e).find('strongerAuthRequired') >= 0: if str(e).find('strongerAuthRequired') >= 0:
# We need to try SSL # We need to try SSL
try: try:
@ -87,6 +163,7 @@ class LDAPConnect:
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red') color='magenta' if errorCode in ldap_error_status else 'red')
else: else:
print(str(e))
errorCode = str(e).split()[-2][:-1] errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(domain, self.logger.error(u'{}\\{}:{} {}'.format(domain,
username, username,

View File

@ -298,8 +298,16 @@ class smb(connection):
self.domain = self.hostname self.domain = self.hostname
def laps_search(self, username, password, ntlm_hash, domain): 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) 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: if connection == False:
logging.debug('LAPS connection failed with account {}'.format(username)) logging.debug('LAPS connection failed with account {}'.format(username))
return False return False
@ -323,12 +331,14 @@ class smb(connection):
self.username = self.args.laps self.username = self.args.laps
self.password = msMCSAdmPwd self.password = msMCSAdmPwd
if 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 return False
if ntlm_hash: if ntlm_hash:
hash_ntlm = hashlib.new('md4', msMCSAdmPwd.encode('utf-16le')).digest() hash_ntlm = hashlib.new('md4', msMCSAdmPwd.encode('utf-16le')).digest()
self.hash = binascii.hexlify(hash_ntlm).decode() self.hash = binascii.hexlify(hash_ntlm).decode()
self.domain = self.hostname self.domain = self.hostname
self.logger.extra['protocol'] = "SMB"
self.logger.extra['port'] = "445"
return True return True
def print_host_info(self): def print_host_info(self):
@ -403,7 +413,7 @@ class smb(connection):
self.username, self.username,
# Show what was used between cleartext, nthash, aesKey and ccache # Show what was used between cleartext, nthash, aesKey and ccache
" from ccache" if useCache " 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), str(e),
'', '',
color='red')) color='red'))
@ -413,7 +423,7 @@ class smb(connection):
self.username, self.username,
# Show what was used between cleartext, nthash, aesKey and ccache # Show what was used between cleartext, nthash, aesKey and ccache
" from ccache" if useCache " 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, error,
'({})'.format(desc) if self.args.verbose else ''), '({})'.format(desc) if self.args.verbose else ''),
color='magenta' if error in smb_error_status else 'red') color='magenta' if error in smb_error_status else 'red')