Merge pull request #1239 from azoxlpf/feat/add-generate-st

Add standalone --generate-st
This commit is contained in:
Alex authored and GitHub committed 2026-07-11 17:47:43 +02:00
commit 4013347211
3 files changed
+83 -11

No files matched your search

+1
View File
@@ -148,6 +148,7 @@ class connection:
self.args.use_kcache or
self.args.aesKey or
(hasattr(self.args, "delegate") and self.args.delegate) or
(hasattr(self.args, "generate_st") and self.args.generate_st) or
(hasattr(self.args, "no_preauth_targets") and self.args.no_preauth_targets))
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
+79 -6
View File
@@ -31,7 +31,7 @@ from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP
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
@@ -147,6 +147,8 @@ class smb(connection):
self.protocol = "SMB"
self.is_guest = None
self.isdc = None
self.tgt = None
self.tgs = None
connection.__init__(self, args, db, host)
@@ -389,19 +391,28 @@ class smb(connection):
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, u2u=self.args.u2u)
self.logger.debug(f"TGS obtained for {self.args.delegate} for {serverName}")
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.save_st(tgs, sk, spn if self.args.spn else None)
self.conn.kerberosLogin(self.username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache, TGS=tgs)
if self.args.generate_st:
try:
creds = self.conn.getCredentials()
self.tgt, self.tgs = creds[6], creds[7]
except Exception as e:
self.logger.fail(f"Could not retrieve credentials for --generate-st: {e}")
return False
if "Unix" not in self.server_os:
self.check_if_admin()
@@ -416,10 +427,10 @@ class smb(connection):
auth_user = username if username else "ccache"
used_ccache = f" through S4U{u2u_str} with {auth_user}"
if self.args.delegate_spn:
if self.args.spn:
u2u_str = "+U2U" if self.args.u2u else ""
auth_user = username if username else "ccache"
used_ccache = f" through S4U{u2u_str} with {auth_user} (w/ SPN {self.args.delegate_spn})"
used_ccache = f" through S4U{u2u_str} with {auth_user} (w/ SPN {self.args.spn})"
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
self.logger.success(out)
@@ -770,6 +781,68 @@ class smb(connection):
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
spn = f"cifs/{self.remoteName}" if not self.args.spn else self.args.spn
self.logger.info(f"Attempting to get ST for SPN {spn} as {self.username}@{self.domain}")
try:
tgt = cipher = tgt_session_key = None
if self.tgt is not None:
tgt = self.tgt["KDC_REP"]
cipher = self.tgt["cipher"]
tgt_session_key = self.tgt["sessionKey"]
self.logger.debug("Reusing TGT obtained during SMB login")
elif 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(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 {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
+3 -5
View File
@@ -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/<target>", 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/<target>).", 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)")
u2u_arg = smb_parser.add_argument("--u2u", action=get_conditional_action(_StoreTrueAction), make_required=[], help="Use User-to-User (U2U) authentication with S4U2Self")
@@ -28,8 +28,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]
u2u_arg.make_required = [delegate_arg]
cred_gathering_group = smb_parser.add_argument_group("Credential Gathering")