Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions nxc/protocols/smb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions nxc/protocols/smb/proto_args.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from argparse import _StoreTrueAction, _StoreAction
from argparse import _StoreTrueAction
from nxc.helpers.args import DisplayDefaultsNotNone, DefaultTrackingAction, get_conditional_action


Expand All @@ -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>). With --generate-st (and without --delegate) it's the SPN of the target service to request a ST for.", type=str)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the last sentence (regarding --generate-st) is self explanatory, so imo we should remove it

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()
Expand All @@ -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")
Expand Down
Loading