Add kerberos compatibility for laps option
parent
25978c0be0
commit
9d6c3fe67e
|
@ -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,
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue