diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 6c5618464e..9b7d243141 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -29,7 +29,7 @@ from impacket.dcerpc.v5.samr import SID_NAME_USE from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED from impacket.krb5.ccache import CCache -from impacket.krb5.kerberosv5 import SessionKeyDecryptionError, getKerberosTGT +from impacket.krb5.kerberosv5 import SessionKeyDecryptionError, getKerberosTGT, getKerberosTGS from impacket.krb5.types import KerberosException, Principal from impacket.krb5 import constants from impacket.dcerpc.v5.dtypes import NULL @@ -372,18 +372,18 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", if self.args.delegate: kerb_pass = "" self.username = self.args.delegate - serverName = Principal(self.args.delegate_spn if self.args.delegate_spn else f"cifs/{self.remoteName}", type=constants.PrincipalNameType.NT_SRV_INST.value) + serverName = Principal(self.args.spn if self.args.spn else f"cifs/{self.remoteName}", type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, sk = kerberos_login_with_S4U(domain, self.hostname, username, password, nthash, lmhash, aesKey, kdcHost, self.args.delegate, serverName, useCache, no_s4u2proxy=self.args.no_s4u2proxy) self.logger.debug(f"TGS obtained for {self.args.delegate} for {serverName}") + if self.args.generate_st: + self.save_st(tgs, sk) + spn = f"cifs/{self.remoteName}" - if self.args.delegate_spn: + if self.args.spn: self.logger.debug(f"Swapping SPN to {spn} for TGS") tgs = kerberos_altservice(tgs, spn) - if self.args.generate_st: - self.save_st(tgs, sk, spn if self.args.delegate_spn else None) - self.conn.kerberosLogin(self.username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache, TGS=tgs) if "Unix" not in self.server_os: self.check_if_admin() @@ -397,8 +397,8 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", if self.args.delegate: used_ccache = f" through S4U with {username}" - if self.args.delegate_spn: - used_ccache = f" through S4U with {username} (w/ SPN {self.args.delegate_spn})" + if self.args.spn: + used_ccache = f" through S4U with {username} (w/ SPN {self.args.spn})" out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}" self.logger.success(out) @@ -730,6 +730,65 @@ def generate_tgt(self): except Exception as e: self.logger.fail(f"Failed to get TGT: {e}") + def generate_st(self): + # When --delegate is used, the S4U Service Ticket is already obtained and saved during kerberos_login + if self.args.delegate: + return + + if not self.args.spn: + self.logger.fail("--spn is required with --generate-st when --delegate is not used") + return + + self.logger.info(f"Attempting to get ST for SPN {self.args.spn} as {self.username}@{self.domain}") + + try: + tgt = cipher = tgt_session_key = None + + if self.use_kcache: + try: + _, _, cached_tgt, _ = CCache.parseFile(self.domain, self.username) + if cached_tgt is not None: + tgt = cached_tgt["KDC_REP"] + cipher = cached_tgt["cipher"] + tgt_session_key = cached_tgt["sessionKey"] + self.logger.debug("Using TGT from ccache") + except Exception as e: + self.logger.debug(f"Could not load TGT from ccache: {e}") + + if tgt is None: + user_name = Principal(self.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) + tgt, cipher, _, tgt_session_key = getKerberosTGT( + clientName=user_name, + password=self.password, + domain=self.domain.upper(), + lmhash=binascii.unhexlify(self.lmhash) if self.lmhash else "", + nthash=binascii.unhexlify(self.nthash) if self.nthash else "", + aesKey=self.aesKey, + kdcHost=self.kdcHost, + ) + self.logger.debug(f"TGT obtained for {self.username}@{self.domain}") + + server_name = Principal(self.args.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) + tgs, _, tgs_session_key, _ = getKerberosTGS( + server_name, + self.domain.upper(), + self.kdcHost, + tgt, + cipher, + tgt_session_key, + ) + self.logger.debug(f"ST successfully obtained for SPN {self.args.spn}") + + ccache = CCache() + ccache.fromTGS(tgs, tgs_session_key, tgs_session_key) + st_file = f"{self.args.generate_st.removesuffix('.ccache')}.ccache" + ccache.saveFile(st_file) + + self.logger.success(f"ST saved to: {st_file}") + self.logger.success(f"Run the following command to use the ST: export KRB5CCNAME={st_file}") + except Exception as e: + self.logger.fail(f"Failed to get ST: {e}") + def check_dc_ports(self, timeout=1): """Check multiple DC-specific ports in case first check fails""" import socket diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 6765559c24..ef970bc1d8 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -1,4 +1,4 @@ -from argparse import _StoreTrueAction, _StoreAction +from argparse import _StoreTrueAction from nxc.helpers.args import DisplayDefaultsNotNone, DefaultTrackingAction, get_conditional_action @@ -7,8 +7,8 @@ def proto_args(parser, parents): smb_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes") delegate_arg = smb_parser.add_argument("--delegate", action="store", help="Impersonate user with S4U2Self + S4U2Proxy") - delegate_spn_arg = smb_parser.add_argument("--delegate-spn", action=get_conditional_action(_StoreAction), make_required=[], help="SPN to use for S4U2Proxy, if not specified the SPN used will be cifs/", type=str) - generate_st = smb_parser.add_argument("--generate-st", type=str, dest="generate_st", action=get_conditional_action(_StoreAction), make_required=[], help="Store the S4U Service Ticket in the specified file") + smb_parser.add_argument("--spn", action="store", help="SPN to use for the Service Ticket. With --delegate it's the SPN used for S4U2Proxy (defaults to cifs/). With --generate-st (and without --delegate) it's the SPN of the target service to request a ST for.", type=str) + smb_parser.add_argument("--generate-st", type=str, dest="generate_st", help="Store a Service Ticket in the specified file. Use with --delegate to save the S4U ST, or without --delegate (together with --spn) to request a regular ST for the given SPN") self_delegate_arg = smb_parser.add_argument("--self", dest="no_s4u2proxy", action=get_conditional_action(_StoreTrueAction), make_required=[], help="Only do S4U2Self, no S4U2Proxy (use with delegate)") dgroup = smb_parser.add_mutually_exclusive_group() @@ -27,8 +27,6 @@ def proto_args(parser, parents): smb_parser.add_argument("--generate-krb5-file", type=str, help="Generate a krb5 file like from a range of IP") smb_parser.add_argument("--generate-tgt", type=str, help="Generate a tgt ticket") self_delegate_arg.make_required = [delegate_arg] - generate_st.make_required = [delegate_arg] - delegate_spn_arg.make_required = [delegate_arg] cred_gathering_group = smb_parser.add_argument_group("Credential Gathering") cred_gathering_group.add_argument("--sam", choices={"regdump", "secdump"}, nargs="?", const="regdump", help="dump SAM hashes from target systems")