Skip to content

Commit

Permalink
Merge branch 'develop' into neff-neo4j
Browse files Browse the repository at this point in the history
  • Loading branch information
NeffIsBack committed Nov 3, 2023
2 parents e20ed5c + 9fc67da commit 3b466a3
Show file tree
Hide file tree
Showing 21 changed files with 398 additions and 74 deletions.
37 changes: 22 additions & 15 deletions nxc/connection.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -22,15 +20,22 @@


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):
Expand Down Expand Up @@ -78,6 +83,7 @@ def __init__(self, args, db, host):
self.args = args
self.db = db
self.hostname = host
self.port = self.args.port
self.conn = None
self.admin_privs = False
self.password = ""
Expand All @@ -91,10 +97,10 @@ def __init__(self, args, db, host):
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
Expand Down Expand Up @@ -389,7 +395,8 @@ def try_credentials(self, domain, username, owned, secret, cred_type, data=None)
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:
Expand Down
16 changes: 8 additions & 8 deletions nxc/modules/daclread.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -208,11 +208,11 @@ def __init__(self, context=None, module_options=None):

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)
Expand Down Expand Up @@ -271,8 +271,8 @@ def options(self, context, module_options):
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

Expand All @@ -292,7 +292,7 @@ def on_login(self, context, connection):
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
Expand Down Expand Up @@ -383,7 +383,7 @@ def search_target_principal_security_descriptor(self, context, connection):
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):
Expand Down
4 changes: 2 additions & 2 deletions nxc/modules/example_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ def on_login(self, context, connection):
# 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.
Expand Down
2 changes: 1 addition & 1 deletion nxc/modules/keepass_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def options(self, context, module_options):
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
Expand Down
4 changes: 2 additions & 2 deletions nxc/modules/ldap-checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ async def run_ldaps_noEPA(target, credential):

# 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()
Expand Down
2 changes: 1 addition & 1 deletion nxc/modules/scan-network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions nxc/modules/schtask_as.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def on_admin_login(self, context, connection):
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}")
Expand All @@ -66,7 +66,7 @@ def on_admin_login(self, context, connection):
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)
Expand Down
2 changes: 1 addition & 1 deletion nxc/modules/teams_localdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def parse_file(context, name):
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:
Expand Down
8 changes: 4 additions & 4 deletions nxc/modules/user_desc.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ def __init__(self, context=None, multiple_options=None):
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)
"""
Expand Down
2 changes: 1 addition & 1 deletion nxc/netexec.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
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))
Expand Down
8 changes: 4 additions & 4 deletions nxc/protocols/ftp.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def proto_logger(self):
extra={
"protocol": "FTP",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname,
}
)
Expand All @@ -41,7 +41,7 @@ def print_host_info(self):
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
Expand All @@ -61,8 +61,8 @@ def plaintext_login(self, username, password):

# 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)

Expand Down
18 changes: 9 additions & 9 deletions nxc/protocols/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ def proto_logger(self):
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:
Expand Down Expand Up @@ -349,7 +349,7 @@ def kerberos_login(

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)
Expand All @@ -374,7 +374,7 @@ def kerberos_login(


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:
Expand Down Expand Up @@ -472,7 +472,7 @@ def plaintext_login(self, domain, username, password):

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)
Expand All @@ -483,7 +483,7 @@ def plaintext_login(self, domain, username, password):
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:
Expand Down Expand Up @@ -563,7 +563,7 @@ def hash_login(self, domain, username, ntlm_hash):

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)
Expand All @@ -573,7 +573,7 @@ def hash_login(self, domain, username, ntlm_hash):
# 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:
Expand Down Expand Up @@ -1319,7 +1319,7 @@ def bloodhound(self):
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(
Expand Down
4 changes: 2 additions & 2 deletions nxc/protocols/mssql.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def proto_logger(self):
extra={
"protocol": "MSSQL",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": "None",
}
)
Expand Down Expand Up @@ -112,7 +112,7 @@ def print_host_info(self):

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}")
Expand Down
6 changes: 3 additions & 3 deletions nxc/protocols/rdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def proto_logger(self):
extra={
"protocol": "RDP",
"host": self.host,
"port": self.args.port,
"port": self.port,
"hostname": self.hostname,
}
)
Expand All @@ -105,7 +105,7 @@ def print_host_info(self):
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()
Expand Down Expand Up @@ -147,7 +147,7 @@ def create_conn_obj(self):
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,
Expand Down
Loading

0 comments on commit 3b466a3

Please sign in to comment.