Compare commits

...
7 changed files with 106 additions and 33 deletions

No files matched your search

+2 -3
View File
@@ -545,9 +545,8 @@ class connection:
if self.args.use_kcache:
self.logger.debug("Trying to authenticate using Kerberos cache")
with sem:
username = self.args.username[0] if len(self.args.username) else ""
password = self.args.password[0] if len(self.args.password) else ""
self.kerberos_login(self.domain, username, password, "", "", self.kdcHost, True)
username = self.args.username[0] # we check this in the main() function when using kcache and set it appropriately already
self.kerberos_login(self.domain, username, "", "", "", self.kdcHost, True)
self.logger.info("Successfully authenticated using Kerberos cache")
return True
+4
View File
@@ -19,6 +19,10 @@ class NXCModule:
"""No options available"""
def on_login(self, context, connection):
if not connection.username:
context.log.debug("Blank username, cannot get a Kerberos Principal object")
return False
user_name = Principal(connection.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
try:
tgt_with_pac, cipher, old_session_key, session_key = getKerberosTGT(
+39 -3
View File
@@ -24,6 +24,9 @@ from os.path import join as path_join
from sys import exit
from rich.progress import Progress
import platform
from impacket.krb5.ccache import CCache
if sys.stdout.encoding == "cp1252":
sys.stdout.reconfigure(encoding="utf-8")
@@ -92,9 +95,42 @@ def main():
nxc_logger.fail("Password is required, even if a key file is used - if no passphrase for key, use `-p ''`")
exit(1)
if args.use_kcache and not os.environ.get("KRB5CCNAME"):
nxc_logger.error("KRB5CCNAME environment variable is not set")
exit(1)
if args.use_kcache:
krb5ccname = os.getenv("KRB5CCNAME")
if not krb5ccname:
nxc_logger.error("KRB5CCNAME environment variable is not set!")
exit(1)
if not os.path.isfile(krb5ccname):
nxc_logger.error(f"Kerberos cache file '{krb5ccname}' does not exist!")
exit(1)
if not os.access(krb5ccname, os.R_OK):
nxc_logger.error(f"Kerberos cache file '{krb5ccname}' is not readable!")
exit(1)
krb_domain, krb_username, krb_tgt, krb_tgs = CCache.parseFile()
nxc_logger.debug(f"Kerberos cache file parsed: {krb_username}, {krb_domain}, {krb_tgt}, {krb_tgs}")
if len(args.username) > 1:
nxc_logger.error(f"Only one username can be used when using Kerberos cache! The domain\\user for this cache file is '{krb_domain}\\{krb_username}'")
exit(1)
if args.username and krb_username != args.username[0]:
nxc_logger.error(f"Kerberos cache file username '{krb_username}' does not match provided username '{args.username[0]}', setting username to '{krb_username}'")
else:
nxc_logger.debug(f"No username provided, using the one from the cache file: {krb_username}")
if args.domain and krb_domain != args.domain:
nxc_logger.error(f"Kerberos cache file domain '{krb_domain}' does not match provided domain '{args.domain}', setting domain to '{krb_domain}'")
else:
nxc_logger.debug(f"No domain provided, using the one from the cache file: {krb_domain}")
args.username = [krb_username]
args.domain = krb_domain
# at this point the user should be a single user, but in a list, because we still parse credentials the same later, and it expects a list
nxc_logger.debug(f"Domain set to {args.domain} and username set to {args.username} for kerberos cache login")
if args.password:
nxc_logger.display("Setting password is irrelevant when using Kerberos cache, ignoring")
targets = []
+6 -6
View File
@@ -27,7 +27,6 @@ from impacket.dcerpc.v5.samr import (
)
from impacket.krb5 import constants
from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError
from impacket.krb5.ccache import CCache
from impacket.krb5.types import Principal, KerberosException
from impacket.ldap import ldap as ldap_impacket
from impacket.ldap import ldaptypes
@@ -313,13 +312,14 @@ class ldap(connection):
else:
self.no_ntlm = True
if self.args.domain:
if self.args.use_kcache: # already parsed kcache domain and username in main()
self.domain = self.args.domain
self.username = self.args.username[0]
elif self.args.domain:
self.domain = self.args.domain
elif self.args.use_kcache: # Fixing domain trust, just pull the auth domain out of the ticket
self.domain = CCache.parseFile()[0]
self.username = CCache.parseFile()[1]
else:
self.domain = self.targetDomain
self.logger.debug(f"Domain set to {self.domain} and username set to {self.username} for LDAP connection object")
self.check_ldap_signing()
if getattr(self.args, "port_explicitly_set", False) and self.port == 389:
@@ -358,7 +358,7 @@ class ldap(connection):
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) ({signing}) ({cbt_status}) {ntlm}")
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
self.username = username if not self.args.use_kcache else self.username # With ccache we get the username from the ticket
self.username = username
self.password = password
self.domain = domain
self.kdcHost = kdcHost
+17 -6
View File
@@ -228,8 +228,6 @@ class smb(connection):
if self.args.domain:
self.domain = self.args.domain
elif self.args.use_kcache: # Fixing domain trust, just pull the auth domain out of the ticket
self.domain = CCache.parseFile()[0]
else:
self.domain = self.targetDomain
@@ -648,8 +646,16 @@ class smb(connection):
relay_list.write(self.host + "\n")
def generate_tgt(self):
if not self.username:
self.logger.error("No username provided, cannot generate TGT")
return False
self.logger.info(f"Attempting to get TGT for {self.username}@{self.domain}")
userName = Principal(self.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
try:
userName = Principal(self.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
except Exception as e:
self.logger.fail(f"Failed to create Principal object: {e}")
return False
try:
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(
@@ -667,13 +673,15 @@ class smb(connection):
ccache = CCache()
ccache.fromTGT(tgt, oldSessionKey, sessionKey)
tgt_file = f"{self.args.generate_tgt}.ccache"
tgt_file = f"{self.args.generate_tgt}.ccache" if not self.args.generate_tgt.endswith(".ccache") else self.args.generate_tgt
ccache.saveFile(tgt_file)
self.logger.success(f"TGT saved to: {tgt_file}")
self.logger.success(f"Run the following command to use the TGT: export KRB5CCNAME={tgt_file}")
return True
except Exception as e:
self.logger.fail(f"Failed to get TGT: {e}")
return False
def check_dc_ports(self, timeout=1):
"""Check multiple DC-specific ports in case first check fails"""
@@ -818,7 +826,7 @@ class smb(connection):
try:
# https://github.com/fortra/impacket/issues/1611
if self.kerberos:
raise Exception("MMCExec current is buggly with kerberos")
raise Exception("MMCExec current is buggy with kerberos")
exec_method = MMCEXEC(
self.remoteName,
self.smb_share_name,
@@ -1614,7 +1622,10 @@ class smb(connection):
def local_groups(self):
self.logger.display("Enumerating with SAMRPC protocol")
try:
groups, members = SamrFunc(self).get_local_groups(self.args.local_groups)
samr_func = SamrFunc(self)
if not samr_func.lsa_query.dce or not samr_func.samr_query.dce:
return
groups, members = samr_func.get_local_groups(self.args.local_groups)
except DCERPCException as e:
self.logger.fail(f"Error enumerating local groups: {e}")
return
+37 -15
View File
@@ -5,6 +5,7 @@
from impacket.dcerpc.v5 import transport, lsat, lsad, samr
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.krb5.kerberosv5 import KerberosError
from impacket.nmb import NetBIOSError
from impacket.smbconnection import SessionError
@@ -18,8 +19,8 @@ class SamrFunc:
self.password = connection.password
self.domain = connection.domain
self.hash = connection.hash
self.lmhash = ""
self.nthash = ""
self.lmhash = connection.lmhash
self.nthash = connection.nthash
self.aesKey = connection.aesKey
self.doKerberos = connection.kerberos
self.kdcHost = connection.kdcHost
@@ -34,8 +35,8 @@ class SamrFunc:
if self.password is None:
self.password = ""
self.samr_query = SAMRQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey, logger=self.logger)
self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kdcHost=self.kdcHost, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger)
self.samr_query = SAMRQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, lmhash=self.lmhash, nthash=self.nthash, kerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey, logger=self.logger)
self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, lmhash=self.lmhash, nthash=self.nthash, kdcHost=self.kdcHost, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger)
def get_builtin_groups(self, group):
domains = self.samr_query.get_domains()
@@ -84,12 +85,12 @@ class SamrFunc:
class SAMRQuery:
def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", kerberos=None, kdcHost="", aesKey="", logger=None,):
def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", lmhash="", nthash="", kerberos=None, kdcHost="", aesKey="", logger=None):
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ""
self.__nthash = ""
self.__lmhash = lmhash
self.__nthash = nthash
self.__aesKey = aesKey
self.__port = port
self.__remote_name = remote_name
@@ -98,7 +99,10 @@ class SAMRQuery:
self.__kdcHost = kdcHost
self.logger = logger
self.dce = self.get_dce()
self.server_handle = self.get_server_handle()
if self.dce:
self.server_handle = self.get_server_handle()
else:
return
def get_transport(self):
string_binding = rf"ncacn_np:{self.__port}[\pipe\samr]"
@@ -126,10 +130,16 @@ class SAMRQuery:
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)
except NetBIOSError as e:
self.logger.error(f"NetBIOSError on Connection: {e}")
self.logger.fail(f"NetBIOSError on Connection: {e}")
return None
except SessionError as e:
self.logger.error(f"SessionError on Connection: {e}")
error_code, error_string = e.getErrorString()
if error_code == "KDC_ERR_S_PRINCIPAL_UNKNOWN":
self.logger.fail(f"Error {error_code} when creating SAMRPC connection, which means the server {self.__remote_host} is not an object in the Kerberos realm (try using an FQDN)")
elif error_code == "KDC_ERR_C_PRINCIPAL_UNKNOWN":
self.logger.fail(f"Error {error_code} when creating SAMRPC connection, which means the user {self.__username} is not an object in the Kerberos realm")
else:
self.logger.fail(f"SessionError on SAMRPC Connection: {e}")
return None
return dce
@@ -177,21 +187,24 @@ class SAMRQuery:
class LSAQuery:
def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", kdcHost="", aesKey="", kerberos=None, logger=None):
def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", lmhash="", nthash="", kdcHost="", aesKey="", kerberos=None, logger=None):
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ""
self.__nthash = ""
self.__lmhash = lmhash
self.__nthash = nthash
self.__aesKey = aesKey
self.__port = port
self.__remote_name = remote_name
self.__remote_host = remote_host
self.__kdcHost = kdcHost
self.__kerberos = kerberos
self.dce = self.get_dce()
self.policy_handle = self.get_policy_handle()
self.logger = logger
self.dce = self.get_dce()
if self.dce:
self.policy_handle = self.get_policy_handle()
else:
return
def get_transport(self):
string_binding = f"ncacn_np:{self.__remote_name}[\\pipe\\lsarpc]"
@@ -223,6 +236,15 @@ class LSAQuery:
except NetBIOSError as e:
self.logger.fail(f"NetBIOSError on Connection: {e}")
return None
except KerberosError as e:
error_code, error_string = e.getErrorString()
if error_code == "KDC_ERR_S_PRINCIPAL_UNKNOWN":
self.logger.fail(f"Error {error_code} when creating LSA connection, which means the server {self.__remote_host} is not an object in the Kerberos realm (try using an FQDN)")
elif error_code == "KDC_ERR_C_PRINCIPAL_UNKNOWN":
self.logger.fail(f"Error {error_code} when creating LSA connection, which means the user {self.__username} is not an object in the Kerberos realm")
else:
self.logger.fail(f"KerberosError on LSA Connection: {e}")
return None
return dce
def get_policy_handle(self):
+1
View File
@@ -3,6 +3,7 @@ netexec -h
##### SMB
netexec smb TARGET_HOST --generate-hosts-file /tmp/hostsfile
netexec smb TARGET_HOST --generate-krb5-file /tmp/krb5conf
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --generate-tgt /tmp/tgt.ccache
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec {DNS} smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares