Merge branch 'develop' into neff-neo4j

Signed-off-by: Marshall Hallenbeck <Marshall.Hallenbeck@gmail.com>
main
Marshall Hallenbeck 2023-10-29 01:11:25 -04:00 committed by GitHub
commit 4b2535d8b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 555 additions and 373 deletions

View File

@ -1,4 +1,5 @@
name: Lint Python code with ruff name: Lint Python code with ruff
# Caching source: https://gist.github.com/gh640/233a6daf68e9e937115371c0ecd39c61?permalink_comment_id=4529233#gistcomment-4529233
on: [push, pull_request] on: [push, pull_request]
@ -11,15 +12,16 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Install poetry - name: Install poetry
run: | run: |
pipx install poetry pipx install poetry
poetry --version - name: Set up Python
poetry env info uses: actions/setup-python@v4
- name: Install libraries with dev group with:
python-version: 3.11
cache: poetry
cache-dependency-path: poetry.lock
- name: Install dependencies with dev group
run: | run: |
poetry install --with dev poetry install --with dev
- name: Run ruff - name: Run ruff

View File

@ -48,6 +48,7 @@ a = Analysis(
'lsassy.parser', 'lsassy.parser',
'lsassy.session', 'lsassy.session',
'lsassy.impacketfile', 'lsassy.impacketfile',
'bloodhound',
'dns', 'dns',
'dns.name', 'dns.name',
'dns.resolver', 'dns.resolver',

View File

@ -28,34 +28,12 @@ def gen_cli_args():
{highlight('Version', 'red')} : {highlight(VERSION)} {highlight('Version', 'red')} : {highlight(VERSION)}
{highlight('Codename', 'red')}: {highlight(CODENAME)} {highlight('Codename', 'red')}: {highlight(CODENAME)}
""", """, formatter_class=RawTextHelpFormatter)
formatter_class=RawTextHelpFormatter,
)
parser.add_argument( parser.add_argument("-t", type=int, dest="threads", default=100, help="set how many concurrent threads to use (default: 100)")
"-t", parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
type=int, parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each connection (default: None)")
dest="threads", parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
default=100,
help="set how many concurrent threads to use (default: 100)",
)
parser.add_argument(
"--timeout",
default=None,
type=int,
help="max timeout in seconds of each thread (default: None)",
)
parser.add_argument(
"--jitter",
metavar="INTERVAL",
type=str,
help="sets a random delay between each connection (default: None)",
)
parser.add_argument(
"--no-progress",
action="store_true",
help="Not displaying progress bar during scan",
)
parser.add_argument("--verbose", action="store_true", help="enable verbose output") parser.add_argument("--verbose", action="store_true", help="enable verbose output")
parser.add_argument("--debug", action="store_true", help="enable debug level information") parser.add_argument("--debug", action="store_true", help="enable debug level information")
parser.add_argument("--version", action="store_true", help="Display nxc version") parser.add_argument("--version", action="store_true", help="Display nxc version")
@ -64,122 +42,34 @@ def gen_cli_args():
module_parser = argparse.ArgumentParser(add_help=False) module_parser = argparse.ArgumentParser(add_help=False)
mgroup = module_parser.add_mutually_exclusive_group() mgroup = module_parser.add_mutually_exclusive_group()
mgroup.add_argument("-M", "--module", action="append", metavar="MODULE", help="module to use") mgroup.add_argument("-M", "--module", action="append", metavar="MODULE", help="module to use")
module_parser.add_argument( module_parser.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
"-o",
metavar="MODULE_OPTION",
nargs="+",
default=[],
dest="module_options",
help="module options",
)
module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules") module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
module_parser.add_argument( module_parser.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
"--options", module_parser.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server (default: https)")
dest="show_module_options", module_parser.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to (default: 0.0.0.0)")
action="store_true", module_parser.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
help="display module options", module_parser.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to (default: same as server-host)")
)
module_parser.add_argument(
"--server",
choices={"http", "https"},
default="https",
help="use the selected server (default: https)",
)
module_parser.add_argument(
"--server-host",
type=str,
default="0.0.0.0",
metavar="HOST",
help="IP to bind the server to (default: 0.0.0.0)",
)
module_parser.add_argument(
"--server-port",
metavar="PORT",
type=int,
help="start the server on the specified port",
)
module_parser.add_argument(
"--connectback-host",
type=str,
metavar="CHOST",
help="IP for the remote system to connect back to (default: same as server-host)",
)
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols") subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
std_parser = argparse.ArgumentParser(add_help=False) std_parser = argparse.ArgumentParser(add_help=False)
std_parser.add_argument( std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules or module_parser.parse_known_args()[0].show_module_options) else "*", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
"target", std_parser.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
nargs="+" if not (module_parser.parse_known_args()[0].list_modules or module_parser.parse_known_args()[0].show_module_options) else "*", std_parser.add_argument("-u", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
type=str, std_parser.add_argument("-p", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)",
)
std_parser.add_argument(
"-id",
metavar="CRED_ID",
nargs="+",
default=[],
type=str,
dest="cred_id",
help="database credential ID(s) to use for authentication",
)
std_parser.add_argument(
"-u",
metavar="USERNAME",
dest="username",
nargs="+",
default=[],
help="username(s) or file(s) containing usernames",
)
std_parser.add_argument(
"-p",
metavar="PASSWORD",
dest="password",
nargs="+",
default=[],
help="password(s) or file(s) containing passwords",
)
std_parser.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file") std_parser.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication") std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2") std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2")
std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes") std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
std_parser.add_argument( std_parser.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
"--use-kcache",
action="store_true",
help="Use Kerberos authentication from ccache file (KRB5CCNAME)",
)
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file") std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
std_parser.add_argument( std_parser.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
"--aesKey", std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
metavar="AESKEY",
nargs="+",
help="AES key to use for Kerberos Authentication (128 or 256 bits)",
)
std_parser.add_argument(
"--kdcHost",
metavar="KDCHOST",
help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter",
)
fail_group = std_parser.add_mutually_exclusive_group() fail_group = std_parser.add_mutually_exclusive_group()
fail_group.add_argument( fail_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
"--gfail-limit", fail_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
metavar="LIMIT", fail_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
type=int,
help="max number of global failed login attempts",
)
fail_group.add_argument(
"--ufail-limit",
metavar="LIMIT",
type=int,
help="max number of failed login attempts per username",
)
fail_group.add_argument(
"--fail-limit",
metavar="LIMIT",
type=int,
help="max number of failed login attempts per host",
)
p_loader = ProtocolLoader() p_loader = ProtocolLoader()
protocols = p_loader.get_protocols() protocols = p_loader.get_protocols()

View File

@ -393,12 +393,16 @@ class connection:
with sem: with sem:
if cred_type == "plaintext": if cred_type == "plaintext":
if self.args.kerberos: if self.args.kerberos:
self.logger.debug("Trying to authenticate using Kerberos")
return self.kerberos_login(domain, username, secret, "", "", self.kdcHost, False) return self.kerberos_login(domain, username, secret, "", "", self.kdcHost, False)
elif hasattr(self.args, "domain"): # Some protocolls don't use domain for login elif hasattr(self.args, "domain"): # Some protocols don't use domain for login
self.logger.debug("Trying to authenticate using plaintext with domain")
return self.plaintext_login(domain, username, secret) return self.plaintext_login(domain, username, secret)
elif self.args.protocol == "ssh": elif self.args.protocol == "ssh":
self.logger.debug("Trying to authenticate using plaintext over SSH")
return self.plaintext_login(username, secret, data) return self.plaintext_login(username, secret, data)
else: else:
self.logger.debug("Trying to authenticate using plaintext")
return self.plaintext_login(username, secret) return self.plaintext_login(username, secret)
elif cred_type == "hash": elif cred_type == "hash":
if self.args.kerberos: if self.args.kerberos:
@ -406,7 +410,6 @@ class connection:
return self.hash_login(domain, username, secret) return self.hash_login(domain, username, secret)
elif cred_type == "aesKey": elif cred_type == "aesKey":
return self.kerberos_login(domain, username, "", "", secret, self.kdcHost, False) return self.kerberos_login(domain, username, "", "", secret, self.kdcHost, False)
return None
def login(self): def login(self):
"""Try to login using the credentials specified in the command line or in the database. """Try to login using the credentials specified in the command line or in the database.
@ -441,6 +444,7 @@ class connection:
data.extend(parsed_data) data.extend(parsed_data)
if self.args.use_kcache: if self.args.use_kcache:
self.logger.debug("Trying to authenticate using Kerberos cache")
with sem: with sem:
username = self.args.username[0] if len(self.args.username) else "" username = self.args.username[0] if len(self.args.username) else ""
password = self.args.password[0] if len(self.args.password) else "" password = self.args.password[0] if len(self.args.password) else ""
@ -455,7 +459,6 @@ class connection:
owned[user_index] = True owned[user_index] = True
if not self.args.continue_on_success: if not self.args.continue_on_success:
return True return True
return None
else: else:
if len(username) != len(secret): if len(username) != len(secret):
self.logger.error("Number provided of usernames and passwords/hashes do not match!") self.logger.error("Number provided of usernames and passwords/hashes do not match!")
@ -465,7 +468,6 @@ class connection:
owned[user_index] = True owned[user_index] = True
if not self.args.continue_on_success: if not self.args.continue_on_success:
return True return True
return None
def mark_pwned(self): def mark_pwned(self):
return highlight(f"({pwned_label})" if self.admin_privs else "") return highlight(f"({pwned_label})" if self.admin_privs else "")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,4 +19,3 @@ def get_desktop_uagent(uagent=None):
return desktop_uagents[random.choice(desktop_uagents.keys())] return desktop_uagents[random.choice(desktop_uagents.keys())]
elif uagent: elif uagent:
return desktop_uagents[uagent] return desktop_uagents[uagent]
return None

View File

@ -13,4 +13,3 @@ def highlight(text, color="yellow"):
return f"{colored(text, 'yellow', attrs=['bold'])}" return f"{colored(text, 'yellow', attrs=['bold'])}"
elif color == "red": elif color == "red":
return f"{colored(text, 'red', attrs=['bold'])}" return f"{colored(text, 'red', attrs=['bold'])}"
return None

View File

@ -77,4 +77,3 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
name = os.path.join(p, thefile) name = os.path.join(p, thefile)
if _access_check(name, mode): if _access_check(name, mode):
return name return name
return None

View File

@ -58,7 +58,6 @@ class ModuleLoader:
except Exception as e: except Exception as e:
self.logger.fail(f"Failed loading module at {module_path}: {e}") self.logger.fail(f"Failed loading module at {module_path}: {e}")
self.logger.debug(traceback.format_exc()) self.logger.debug(traceback.format_exc())
return None
def init_module(self, module_path): def init_module(self, module_path):
"""Initialize a module for execution""" """Initialize a module for execution"""
@ -85,7 +84,6 @@ class ModuleLoader:
else: else:
self.logger.fail(f"Module {module.name.upper()} is not supported for protocol {self.args.protocol}") self.logger.fail(f"Module {module.name.upper()} is not supported for protocol {self.args.protocol}")
sys.exit(1) sys.exit(1)
return None
def get_module_info(self, module_path): def get_module_info(self, module_path):
"""Get the path, description, and options from a module""" """Get the path, description, and options from a module"""
@ -101,6 +99,7 @@ class ModuleLoader:
"supported_protocols": module_spec.supported_protocols, "supported_protocols": module_spec.supported_protocols,
"opsec_safe": module_spec.opsec_safe, "opsec_safe": module_spec.opsec_safe,
"multiple_hosts": module_spec.multiple_hosts, "multiple_hosts": module_spec.multiple_hosts,
"requires_admin": bool(hasattr(module_spec, "on_admin_login") and callable(module_spec.on_admin_login)),
} }
} }
if self.module_is_sane(module_spec, module_path): if self.module_is_sane(module_spec, module_path):
@ -108,7 +107,6 @@ class ModuleLoader:
except Exception as e: except Exception as e:
self.logger.fail(f"Failed loading module at {module_path}: {e}") self.logger.fail(f"Failed loading module at {module_path}: {e}")
self.logger.debug(traceback.format_exc()) self.logger.debug(traceback.format_exc())
return None
def list_modules(self): def list_modules(self):
"""List modules without initializing them""" """List modules without initializing them"""

View File

@ -76,7 +76,5 @@ class NXCModule:
except socket.gaierror: except socket.gaierror:
context.log.debug("Missing IP") context.log.debug("Missing IP")
context.log.highlight(f"{answer[0]} ({answer[1]}) (No IP Found)") context.log.highlight(f"{answer[0]} ({answer[1]}) (No IP Found)")
return None
else: else:
context.log.success(f"Unable to find any computers with the text {self.TEXT}") context.log.success(f"Unable to find any computers with the text {self.TEXT}")
return None

View File

@ -78,8 +78,6 @@ class NXCModule:
context.log.success("Found following users: ") context.log.success("Found following users: ")
for answer in answers: for answer in answers:
context.log.highlight(f"User: {answer[0]} description: {answer[1]}") context.log.highlight(f"User: {answer[0]} description: {answer[1]}")
return None
return None
def filter_answer(self, context, answers): def filter_answer(self, context, answers):
# No option to filter # No option to filter

View File

@ -62,8 +62,6 @@ class NXCModule:
context.log.success("Found the following members of the " + self.GROUP + " group:") context.log.success("Found the following members of the " + self.GROUP + " group:")
for answer in self.answers: for answer in self.answers:
context.log.highlight(f"{answer[0]}") context.log.highlight(f"{answer[0]}")
return None
return None
# Carry out an LDAP search for the Group with the supplied Group name # Carry out an LDAP search for the Group with the supplied Group name

View File

@ -84,5 +84,3 @@ class NXCModule:
group_name = group_parts[0].split("=")[1] group_name = group_parts[0].split("=")[1]
context.log.highlight(f"{group_name}") context.log.highlight(f"{group_name}")
return None
return None

View File

@ -231,7 +231,6 @@ class NXCModule:
self.save_credentials(context, connection, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"]) self.save_credentials(context, connection, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"])
global credentials_data global credentials_data
credentials_data = credentials_output credentials_data = credentials_output
return None
def spider_pcs(self, context, connection, cursor, dbconnection, driver): def spider_pcs(self, context, connection, cursor, dbconnection, driver):
cursor.execute("SELECT * from admin_users WHERE hash is not NULL") cursor.execute("SELECT * from admin_users WHERE hash is not NULL")

File diff suppressed because one or more lines are too long

View File

@ -48,9 +48,7 @@ class NXCModule:
keepass_process_id = row[0] keepass_process_id = row[0]
keepass_process_username = row[1] keepass_process_username = row[1]
keepass_process_name = row[2] keepass_process_name = row[2]
context.log.highlight( context.log.highlight(f'Found process "{keepass_process_name}" with PID {keepass_process_id} (user {keepass_process_username})')
f'Found process "{keepass_process_name}" with PID {keepass_process_id} (user {keepass_process_username})'
)
if row_number == 0: if row_number == 0:
context.log.display("No KeePass-related process was found") context.log.display("No KeePass-related process was found")

View File

@ -51,7 +51,6 @@ class NXCModule:
# LDAPS bind successful # LDAPS bind successful
# because channel binding is not enforced # because channel binding is not enforced
return False return False
return None
# Conduct a bind to LDAPS with channel binding supported # Conduct a bind to LDAPS with channel binding supported
# but intentionally miscalculated. In the case that and # but intentionally miscalculated. In the case that and
@ -74,10 +73,8 @@ class NXCModule:
return False return False
elif err is not None: elif err is not None:
context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err)) context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err))
return None
elif err is None: elif err is None:
return False return False
return None
# Domain Controllers do not have a certificate setup for # Domain Controllers do not have a certificate setup for
# LDAPS on port 636 by default. If this has not been setup, # LDAPS on port 636 by default. If this has not been setup,
@ -128,10 +125,8 @@ class NXCModule:
sys.exit() sys.exit()
elif err is None: elif err is None:
return False return False
return None
else: else:
context.log.fail(str(err)) context.log.fail(str(err))
return None
# Run trough all our code blocks to determine LDAP signing and channel binding settings. # Run trough all our code blocks to determine LDAP signing and channel binding settings.
stype = asyauthSecret.PASS if not connection.nthash else asyauthSecret.NT stype = asyauthSecret.PASS if not connection.nthash else asyauthSecret.NT

View File

@ -117,7 +117,6 @@ class NXCModule:
context.log.debug("Calling process_credentials") context.log.debug("Calling process_credentials")
self.process_credentials(context, connection, credentials_output) self.process_credentials(context, connection, credentials_output)
return None
def process_credentials(self, context, connection, credentials): def process_credentials(self, context, connection, credentials):
if len(credentials) == 0: if len(credentials) == 0:

View File

@ -139,7 +139,6 @@ class NXCModule:
else: else:
self.context.log.display(f"{user.username} can impersonate: {grantor.username}") self.context.log.display(f"{user.username} can impersonate: {grantor.username}")
return self.browse_path(context, initial_user, grantor) return self.browse_path(context, initial_user, grantor)
return None
def query_and_get_output(self, query): def query_and_get_output(self, query):
return self.mssql_conn.sql_query(query) return self.mssql_conn.sql_query(query)
@ -354,8 +353,6 @@ class NXCModule:
if db in trusted_databases: if db in trusted_databases:
return db return db
return None
def do_dbowner_privesc(self, database, exec_as=""): def do_dbowner_privesc(self, database, exec_as=""):
""" """
Executes a series of SQL queries to perform a database owner privilege escalation. Executes a series of SQL queries to perform a database owner privilege escalation.

File diff suppressed because one or more lines are too long

View File

@ -88,8 +88,5 @@ class NXCModule:
value = self.convert_time_field(field, pso[field]) value = self.convert_time_field(field, pso[field])
context.log.highlight(f"{field}: {value}") context.log.highlight(f"{field}: {value}")
context.log.highlight("-----") context.log.highlight("-----")
return None
else: else:
context.log.info("No Password Settings Objects (PSO) found.") context.log.info("No Password Settings Objects (PSO) found.")
return None

View File

@ -137,8 +137,6 @@ class SMBSpiderPlus:
if self.reconnect(): if self.reconnect():
return self.get_remote_file(share, path) return self.get_remote_file(share, path)
return None
def read_chunk(self, remote_file, chunk_size=CHUNK_SIZE): def read_chunk(self, remote_file, chunk_size=CHUNK_SIZE):
"""Reads the next chunk of data from the provided remote file using the specified chunk size. """Reads the next chunk of data from the provided remote file using the specified chunk size.
If a `SessionError` is encountered, it retries up to 3 times by reconnecting the SMB connection. If a `SessionError` is encountered, it retries up to 3 times by reconnecting the SMB connection.

File diff suppressed because one or more lines are too long

View File

@ -37,6 +37,7 @@ if platform != "win32":
resource.setrlimit(resource.RLIMIT_NOFILE, file_limit) resource.setrlimit(resource.RLIMIT_NOFILE, file_limit)
def create_db_engine(db_path): def create_db_engine(db_path):
return sqlalchemy.create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True) return sqlalchemy.create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
@ -165,8 +166,13 @@ def main():
modules = loader.list_modules() modules = loader.list_modules()
if args.list_modules: if args.list_modules:
nxc_logger.highlight("LOW PRIVILEGE MODULES")
for name, props in sorted(modules.items()): for name, props in sorted(modules.items()):
if args.protocol in props["supported_protocols"]: if args.protocol in props["supported_protocols"] and not props["requires_admin"]:
nxc_logger.display(f"{name:<25} {props['description']}")
nxc_logger.highlight("\nHIGH PRIVILEGE MODULES (requires admin privs)")
for name, props in sorted(modules.items()):
if args.protocol in props["supported_protocols"] and props["requires_admin"]:
nxc_logger.display(f"{name:<25} {props['description']}") nxc_logger.display(f"{name:<25} {props['description']}")
exit(0) exit(0)
elif args.module and args.show_module_options: elif args.module and args.show_module_options:

View File

@ -1,9 +1,9 @@
import os
from nxc.config import process_secret from nxc.config import process_secret
from nxc.connection import connection from nxc.connection import connection
from nxc.helpers.logger import highlight from nxc.helpers.logger import highlight
from nxc.logger import NXCAdapter from nxc.logger import NXCAdapter
from ftplib import FTP from ftplib import FTP, error_perm
class ftp(connection): class ftp(connection):
def __init__(self, args, db, host): def __init__(self, args, db, host):
@ -69,30 +69,102 @@ class ftp(connection):
host_id = self.db.get_hosts(self.host)[0].id host_id = self.db.get_hosts(self.host)[0].id
self.db.add_loggedin_relation(cred_id, host_id) self.db.add_loggedin_relation(cred_id, host_id)
if username in ["anonymous", ""] and password in ["", "-"]: if username in ["anonymous", ""]:
self.logger.success(f"{username}:{process_secret(password)} {highlight('- Anonymous Login!')}") self.logger.success(f"{username}:{process_secret(password)} {highlight('- Anonymous Login!')}")
else: else:
self.logger.success(f"{username}:{process_secret(password)}") self.logger.success(f"{username}:{process_secret(password)}")
if self.args.ls: if self.args.ls:
# If the default directory is specified, then we will list the current directory
if self.args.ls == ".":
files = self.list_directory_full() files = self.list_directory_full()
# If files is false, then we encountered an exception
if not files:
return False
# If there are files, then we can list the files
self.logger.display("Directory Listing") self.logger.display("Directory Listing")
for file in files: for file in files:
self.logger.highlight(file) self.logger.highlight(file)
else:
# If the default directory is not specified, then we will list the specified directory
self.logger.display(f"Directory Listing for {self.args.ls}")
# Change to the specified directory
try:
self.conn.cwd(self.args.ls)
except error_perm as error_message:
self.logger.fail(f"Failed to change directory. Response: ({error_message})")
self.conn.close()
return False
# List the files in the specified directory
files = self.list_directory_full()
for file in files:
self.logger.highlight(file)
if self.args.get:
self.get_file(f"{self.args.get}")
if self.args.put:
self.put_file(self.args.put[0], self.args.put[1])
if not self.args.continue_on_success: if not self.args.continue_on_success:
self.conn.close() self.conn.close()
return True return True
self.conn.close() self.conn.close()
return None
def list_directory_full(self): def list_directory_full(self):
# in the future we can use mlsd/nlst if we want, but this gives a full output like `ls -la` # in the future we can use mlsd/nlst if we want, but this gives a full output like `ls -la`
# ftplib's "dir" prints directly to stdout, and "nlst" only returns the folder name, not full details # ftplib's "dir" prints directly to stdout, and "nlst" only returns the folder name, not full details
files = [] files = []
try:
self.conn.retrlines("LIST", callback=files.append) self.conn.retrlines("LIST", callback=files.append)
except error_perm as error_message:
self.logger.fail(f"Failed to list directory. Response: ({error_message})")
self.conn.close()
return False
return files return files
def get_file(self, filename):
# Extract the filename from the path
downloaded_file = filename.split("/")[-1]
try:
# Check if the current connection is ASCII (ASCII does not support .size())
if self.conn.encoding == "utf-8":
# Switch the connection to binary
self.conn.sendcmd("TYPE I")
# Check if the file exists
self.conn.size(filename)
# Attempt to download the file
self.conn.retrbinary(f"RETR {filename}", open(downloaded_file, "wb").write) # noqa: SIM115
except error_perm as error_message:
self.logger.fail(f"Failed to download the file. Response: ({error_message})")
self.conn.close()
return False
except FileNotFoundError:
self.logger.fail("Failed to download the file. Response: (No such file or directory.)")
self.conn.close()
return False
# Check if the file was downloaded
if os.path.isfile(downloaded_file):
self.logger.success(f"Downloaded: {filename}")
else:
self.logger.fail(f"Failed to download: {filename}")
def put_file(self, local_file, remote_file):
try:
# Attempt to upload the file
self.conn.storbinary(f"STOR {remote_file}", open(local_file, "rb")) # noqa: SIM115
except error_perm as error_message:
self.logger.fail(f"Failed to upload file. Response: ({error_message})")
return False
except FileNotFoundError:
self.logger.fail(f"Failed to upload file. {local_file} does not exist locally.")
return False
# Check if the file was uploaded
if self.conn.size(remote_file) > 0:
self.logger.success(f"Uploaded: {local_file} to {remote_file}")
else:
self.logger.fail(f"Failed to upload: {local_file} to {remote_file}")
def supported_commands(self): def supported_commands(self):
raw_supported_commands = self.conn.sendcmd("HELP") raw_supported_commands = self.conn.sendcmd("HELP")
supported_commands = [item for sublist in (x.split() for x in raw_supported_commands.split("\n")[1:-1]) for item in sublist] supported_commands = [item for sublist in (x.split() for x in raw_supported_commands.split("\n")[1:-1]) for item in sublist]

View File

@ -138,7 +138,6 @@ class database:
if updated_ids: if updated_ids:
nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}") nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}")
return updated_ids return updated_ids
return None
def add_credential(self, username, password): def add_credential(self, username, password):
"""Check if this credential has already been added to the database, if not add it in.""" """Check if this credential has already been added to the database, if not add it in."""
@ -205,9 +204,7 @@ class database:
self.CredentialsTable.c.password == password, self.CredentialsTable.c.password == password,
) )
results = self.sess.execute(q).first() results = self.sess.execute(q).first()
if results is None: if results is not None:
return None
else:
return results.id return results.id
def get_credentials(self, filter_term=None): def get_credentials(self, filter_term=None):
@ -291,7 +288,6 @@ class database:
return inserted_id_results[0].id return inserted_id_results[0].id
except Exception as e: except Exception as e:
nxc_logger.debug(f"Error inserting LoggedinRelation: {e}") nxc_logger.debug(f"Error inserting LoggedinRelation: {e}")
return None
def get_loggedin_relations(self, cred_id=None, host_id=None): def get_loggedin_relations(self, cred_id=None, host_id=None):
q = select(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id) q = select(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id)

View File

@ -3,5 +3,7 @@ def proto_args(parser, std_parser, module_parser):
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port (default: 21)") ftp_parser.add_argument("--port", type=int, default=21, help="FTP port (default: 21)")
cgroup = ftp_parser.add_argument_group("FTP Access", "Options for enumerating your access") cgroup = ftp_parser.add_argument_group("FTP Access", "Options for enumerating your access")
cgroup.add_argument("--ls", action="store_true", help="List files in the directory") cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory, ex: --ls or --ls Directory")
cgroup.add_argument("--get", metavar="FILE", help="Download a file, ex: --get fileName.txt")
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file, ex: --put inputFileName.txt outputFileName.txt")
return parser return parser

View File

@ -834,7 +834,6 @@ class ldap(connection):
resp = self.search(search_filter, attributes, 0) resp = self.search(search_filter, attributes, 0)
if resp == []: if resp == []:
self.logger.highlight("No entries found!") self.logger.highlight("No entries found!")
return None
elif resp: elif resp:
answers = [] answers = []
self.logger.display(f"Total of records returned {len(resp):d}") self.logger.display(f"Total of records returned {len(resp):d}")
@ -884,10 +883,8 @@ class ldap(connection):
return True return True
else: else:
self.logger.highlight("No entries found!") self.logger.highlight("No entries found!")
return None
else: else:
self.logger.fail("Error with the LDAP account used") self.logger.fail("Error with the LDAP account used")
return None
def kerberoasting(self): def kerberoasting(self):
# Building the search filter # Building the search filter
@ -984,9 +981,7 @@ class ldap(connection):
return True return True
else: else:
self.logger.highlight("No entries found!") self.logger.highlight("No entries found!")
return None
self.logger.fail("Error with the LDAP account used") self.logger.fail("Error with the LDAP account used")
return None
def trusted_for_delegation(self): def trusted_for_delegation(self):
# Building the search filter # Building the search filter
@ -1117,7 +1112,6 @@ class ldap(connection):
self.logger.highlight(f"User: {value[0]} Status: {value[5]}") self.logger.highlight(f"User: {value[0]} Status: {value[5]}")
else: else:
self.logger.fail("No entries found!") self.logger.fail("No entries found!")
return None
def admin_count(self): def admin_count(self):
# Building the search filter # Building the search filter

View File

@ -243,4 +243,9 @@ class KerberosAttacks:
return None return None
# Let's output the TGT enc-part/cipher in Hashcat format, in case somebody wants to use it. # Let's output the TGT enc-part/cipher in Hashcat format, in case somebody wants to use it.
return "$krb5asrep$%d$%s@%s:%s$%s" % (as_rep["enc-part"]["etype"], client_name, domain, hexlify(as_rep["enc-part"]["cipher"].asOctets()[:12]).decode(), hexlify(as_rep["enc-part"]["cipher"].asOctets()[12:]).decode()) if as_rep["enc-part"]["etype"] == 17 or as_rep["enc-part"]["etype"] == 18 else "$krb5asrep$%d$%s@%s:%s$%s" % (as_rep["enc-part"]["etype"], client_name, domain, hexlify(as_rep["enc-part"]["cipher"].asOctets()[:16]).decode(), hexlify(as_rep["enc-part"]["cipher"].asOctets()[16:]).decode()) hash_tgt = f"$krb5asrep${as_rep['enc-part']['etype']}${client_name}@{domain}:"
if as_rep["enc-part"]["etype"] in (17, 18):
hash_tgt += f"{hexlify(as_rep['enc-part']['cipher'].asOctets()[:12]).decode()}${hexlify(as_rep['enc-part']['cipher'].asOctets()[12:]).decode()}"
else:
hash_tgt += f"{hexlify(as_rep['enc-part']['cipher'].asOctets()[:16]).decode()}${hexlify(as_rep['enc-part']['cipher'].asOctets()[16:]).decode()}"
return hash_tgt

View File

@ -647,7 +647,22 @@ class smb(connection):
current_method = method current_method = method
if method == "wmiexec": if method == "wmiexec":
try: try:
exec_method = WMIEXEC(self.host if not self.kerberos else self.hostname + "." + self.domain, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.args.share, logger=self.logger, timeout=self.args.dcom_timeout, tries=self.args.get_output_tries) exec_method = WMIEXEC(
self.host if not self.kerberos else self.hostname + "." + self.domain,
self.smb_share_name,
self.username,
self.password,
self.domain,
self.conn,
self.kerberos,
self.aesKey,
self.kdcHost,
self.hash,
self.args.share,
logger=self.logger,
timeout=self.args.dcom_timeout,
tries=self.args.get_output_tries
)
self.logger.info("Executed command via wmiexec") self.logger.info("Executed command via wmiexec")
break break
except Exception: except Exception:
@ -656,7 +671,19 @@ class smb(connection):
continue continue
elif method == "mmcexec": elif method == "mmcexec":
try: try:
exec_method = MMCEXEC(self.host if not self.kerberos else self.hostname + "." + self.domain, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.args.share, self.hash, self.logger, self.args.get_output_tries, self.args.dcom_timeout) exec_method = MMCEXEC(
self.host if not self.kerberos else self.hostname + "." + self.domain,
self.smb_share_name,
self.username,
self.password,
self.domain,
self.conn,
self.args.share,
self.hash,
self.logger,
self.args.get_output_tries,
self.args.dcom_timeout
)
self.logger.info("Executed command via mmcexec") self.logger.info("Executed command via mmcexec")
break break
except Exception: except Exception:
@ -665,7 +692,20 @@ class smb(connection):
continue continue
elif method == "atexec": elif method == "atexec":
try: try:
exec_method = TSCH_EXEC(self.host if not self.kerberos else self.hostname + "." + self.domain, self.smb_share_name, self.username, self.password, self.domain, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.logger, self.args.get_output_tries, self.args.share) exec_method = TSCH_EXEC(
self.host if not self.kerberos else self.hostname + "." + self.domain,
self.smb_share_name,
self.username,
self.password,
self.domain,
self.kerberos,
self.aesKey,
self.kdcHost,
self.hash,
self.logger,
self.args.get_output_tries,
self.args.share
)
self.logger.info("Executed command via atexec") self.logger.info("Executed command via atexec")
break break
except Exception: except Exception:
@ -674,7 +714,23 @@ class smb(connection):
continue continue
elif method == "smbexec": elif method == "smbexec":
try: try:
exec_method = SMBEXEC(self.host if not self.kerberos else self.hostname + "." + self.domain, self.smb_share_name, self.conn, self.args.port, self.username, self.password, self.domain, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.args.share, self.args.port, self.logger, self.args.get_output_tries) exec_method = SMBEXEC(
self.host if not self.kerberos else self.hostname + "." + self.domain,
self.smb_share_name,
self.conn,
self.args.port,
self.username,
self.password,
self.domain,
self.kerberos,
self.aesKey,
self.kdcHost,
self.hash,
self.args.share,
self.args.port,
self.logger,
self.args.get_output_tries
)
self.logger.info("Executed command via smbexec") self.logger.info("Executed command via smbexec")
break break
except Exception: except Exception:
@ -1556,7 +1612,6 @@ class smb(connection):
credential.password, credential.password,
credential.url, credential.url,
) )
return None
@requires_admin @requires_admin
def lsa(self): def lsa(self):

View File

@ -289,7 +289,6 @@ class database:
if updated_ids: if updated_ids:
nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}") nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}")
return updated_ids return updated_ids
return None
def add_credential(self, credtype, domain, username, password, group_id=None, pillaged_from=None): def add_credential(self, credtype, domain, username, password, group_id=None, pillaged_from=None):
"""Check if this credential has already been added to the database, if not add it in.""" """Check if this credential has already been added to the database, if not add it in."""
@ -452,9 +451,7 @@ class database:
if user_domain: if user_domain:
q = select(self.HostsTable).filter(func.lower(self.HostsTable.c.id) == func.lower(user_domain)) q = select(self.HostsTable).filter(func.lower(self.HostsTable.c.id) == func.lower(user_domain))
results = self.conn.execute(q).all() results = self.conn.execute(q).all()
return len(results) > 0 return len(results) > 0
return None
def is_host_valid(self, host_id): def is_host_valid(self, host_id):
"""Check if this host ID is valid.""" """Check if this host ID is valid."""
@ -826,7 +823,6 @@ class database:
return inserted_id_results[0].id return inserted_id_results[0].id
except Exception as e: except Exception as e:
nxc_logger.debug(f"Error inserting LoggedinRelation: {e}") nxc_logger.debug(f"Error inserting LoggedinRelation: {e}")
return None
def get_loggedin_relations(self, user_id=None, host_id=None): def get_loggedin_relations(self, user_id=None, host_id=None):
q = select(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id) q = select(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id)
@ -897,7 +893,6 @@ class database:
if updated_ids: if updated_ids:
nxc_logger.debug(f"add_check() - Checks IDs Updated: {updated_ids}") nxc_logger.debug(f"add_check() - Checks IDs Updated: {updated_ids}")
return updated_ids return updated_ids
return None
def add_check_result(self, host_id, check_id, secure, reasons): def add_check_result(self, host_id, check_id, secure, reasons):
"""Check if this check result has already been added to the database, if not, add it in.""" """Check if this check result has already been added to the database, if not, add it in."""
@ -910,4 +905,3 @@ class database:
if updated_ids: if updated_ids:
nxc_logger.debug(f"add_check_result() - Check Results IDs Updated: {updated_ids}") nxc_logger.debug(f"add_check_result() - Check Results IDs Updated: {updated_ids}")
return updated_ids return updated_ids
return None

View File

@ -150,7 +150,6 @@ class FirefoxTriage:
fh.close() fh.close()
return b"" return b""
fh.close() fh.close()
return None
def is_master_password_correct(self, key_data, master_password=b""): def is_master_password_correct(self, key_data, master_password=b""):
try: try:
@ -236,9 +235,4 @@ class FirefoxTriage:
# 04 is OCTETSTRING, 0x0e is length == 14 # 04 is OCTETSTRING, 0x0e is length == 14
encrypted_value = decoded_item[0][1].asOctets() encrypted_value = decoded_item[0][1].asOctets()
cipher = AES.new(key, AES.MODE_CBC, iv) cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(encrypted_value) return cipher.decrypt(encrypted_value)
if decrypted is not None:
return decrypted
else:
return None
return None

View File

@ -155,7 +155,6 @@ class SAMRQuery:
return resp["ServerHandle"] return resp["ServerHandle"]
else: else:
nxc_logger.debug("Error creating Samr handle") nxc_logger.debug("Error creating Samr handle")
return None
def get_domains(self): def get_domains(self):
"""Calls the hSamrEnumerateDomainsInSamServer() method directly with list comprehension and extracts the "Name" value from each element in the "Buffer" list.""" """Calls the hSamrEnumerateDomainsInSamServer() method directly with list comprehension and extracts the "Name" value from each element in the "Buffer" list."""

View File

@ -132,7 +132,6 @@ class SMBEXEC:
pass pass
self.get_output_remote() self.get_output_remote()
return None
def get_output_remote(self): def get_output_remote(self):
if self.__retOutput is False: if self.__retOutput is False:

View File

@ -1,12 +1,12 @@
import paramiko
import re
import uuid
import logging import logging
import time
from io import StringIO from io import StringIO
import paramiko
from nxc.config import process_secret from nxc.config import process_secret
from nxc.connection import connection from nxc.connection import connection, highlight
from nxc.helpers.logger import highlight
from nxc.logger import NXCAdapter from nxc.logger import NXCAdapter
from paramiko.ssh_exception import ( from paramiko.ssh_exception import (
AuthenticationException, AuthenticationException,
@ -18,11 +18,30 @@ from paramiko.ssh_exception import (
class ssh(connection): class ssh(connection):
def __init__(self, args, db, host): def __init__(self, args, db, host):
self.protocol = "SSH" self.protocol = "SSH"
self.remote_version = None self.remote_version = "Unknown SSH Version"
self.server_os = None self.server_os_platform = "Linux"
self.user_principal = "root"
super().__init__(args, db, host) super().__init__(args, db, host)
def proto_flow(self):
self.logger.debug("Kicking off proto_flow")
self.proto_logger()
if self.create_conn_obj():
self.enum_host_info()
self.print_host_info()
if self.remote_version == "Unknown SSH Version":
self.conn.close()
return
if self.login():
if hasattr(self.args, "module") and self.args.module:
self.call_modules()
else:
self.call_cmd_args()
self.conn.close()
def proto_logger(self): def proto_logger(self):
logging.getLogger("paramiko").disabled = True
logging.getLogger("paramiko.transport").disabled = True
self.logger = NXCAdapter( self.logger = NXCAdapter(
extra={ extra={
"protocol": "SSH", "protocol": "SSH",
@ -31,28 +50,22 @@ class ssh(connection):
"hostname": self.hostname, "hostname": self.hostname,
} }
) )
logging.getLogger("paramiko").setLevel(logging.WARNING)
def print_host_info(self): def print_host_info(self):
self.logger.display(self.remote_version) self.logger.display(self.remote_version if self.remote_version != "Unknown SSH Version" else f"{self.remote_version}, skipping...")
return True return True
def enum_host_info(self): def enum_host_info(self):
if self.conn._transport.remote_version:
self.remote_version = self.conn._transport.remote_version self.remote_version = self.conn._transport.remote_version
self.logger.debug(f"Remote version: {self.remote_version}") self.logger.debug(f"Remote version: {self.remote_version}")
self.server_os = "" self.db.add_host(self.host, self.args.port, self.remote_version)
if self.args.remote_enum:
stdin, stdout, stderr = self.conn.exec_command("uname -r")
self.server_os = stdout.read().decode("utf-8")
self.logger.debug(f"OS retrieved: {self.server_os}")
self.db.add_host(self.host, self.args.port, self.remote_version, os=self.server_os)
def create_conn_obj(self): def create_conn_obj(self):
self.conn = paramiko.SSHClient() self.conn = paramiko.SSHClient()
self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try: try:
self.conn.connect(self.host, port=self.args.port) self.conn.connect(self.host, port=self.args.port, timeout=self.args.ssh_timeout, look_for_keys=False)
except AuthenticationException: except AuthenticationException:
return True return True
except SSHException: except SSHException:
@ -62,72 +75,184 @@ class ssh(connection):
except OSError: except OSError:
return False return False
def client_close(self):
self.conn.close()
def check_if_admin(self): def check_if_admin(self):
self.admin_privs = False
if self.args.sudo_check:
self.check_if_admin_sudo()
return
# we could add in another method to check by piping in the password to sudo # we could add in another method to check by piping in the password to sudo
# but that might be too much of an opsec concern - maybe add in a flag to do more checks? # but that might be too much of an opsec concern - maybe add in a flag to do more checks?
stdin, stdout, stderr = self.conn.exec_command("id") self.logger.info("Determined user is root via `id; sudo -ln` command")
if stdout.read().decode("utf-8").find("uid=0(root)") != -1: _, stdout, _ = self.conn.exec_command("id; sudo -ln 2>&1")
self.logger.info("Determined user is root via `id` command") stdout = stdout.read().decode(self.args.codec, errors="ignore")
admin_flag = {
"(root)": [True, None],
"NOPASSWD: ALL": [True, None],
"(ALL : ALL) ALL": [True, None],
"(sudo)": [False, f"Current user: '{self.username}' was in 'sudo' group, please try '--sudo-check' to check if user can run sudo shell"]
}
for keyword in admin_flag:
match = re.findall(re.escape(keyword), stdout)
if match:
self.logger.info(f"User: '{self.username}' matched keyword: {match[0]}")
self.admin_privs = admin_flag[match[0]][0]
if not self.admin_privs:
tips = admin_flag[match[0]][1]
else:
break
if not self.admin_privs and "tips" in locals():
self.logger.display(tips)
return
def check_if_admin_sudo(self):
if not self.password:
self.logger.error("Check admin with sudo does not support using a private key")
return
if self.args.sudo_check_method:
method = self.args.sudo_check_method
self.logger.info(f"Doing sudo check with method: {method}")
if method == "sudo-stdin":
_, stdout, _ = self.conn.exec_command("sudo --help")
stdout = stdout.read().decode(self.args.codec, errors="ignore")
# Read sudo help docs and find "stdin"
if "stdin" in stdout:
shadow_backup = f"/tmp/{uuid.uuid4()}"
# sudo support stdin password
self.conn.exec_command(f"echo {self.password} | sudo -S cp /etc/shadow {shadow_backup} >/dev/null 2>&1 &")
self.conn.exec_command(f"echo {self.password} | sudo -S chmod 777 {shadow_backup} >/dev/null 2>&1 &")
tries = 1
while True:
self.logger.info(f"Checking {shadow_backup} if it existed")
_, _, stderr = self.conn.exec_command(f"ls {shadow_backup}")
if tries >= self.args.get_output_tries:
self.logger.info(f"The file {shadow_backup} does not exist, the pipe may be hanging. Increase the number of tries with the option '--get-output-tries' or change other method with '--sudo-check-method'. If it's still failing, maybe sudo shell does not work with the current user")
break
if stderr.read().decode("utf-8"):
time.sleep(2)
tries += 1
else:
self.logger.info(f"{shadow_backup} existed")
self.admin_privs = True self.admin_privs = True
return True break
stdin, stdout, stderr = self.conn.exec_command("sudo -ln | grep 'NOPASSWD: ALL'") self.logger.info(f"Remove up temporary files {shadow_backup}")
if stdout.read().decode("utf-8").find("NOPASSWD: ALL") != -1: self.conn.exec_command(f"echo {self.password} | sudo -S rm -rf {shadow_backup}")
self.logger.info("Determined user is root via `sudo -ln` command") else:
self.logger.error("Command: 'sudo' not support stdin mode, running command with 'sudo' failed")
return
else:
_, stdout, _ = self.conn.exec_command("mkfifo --help")
stdout = stdout.read().decode(self.args.codec, errors="ignore")
# check if user can execute mkfifo
if "Create named pipes" in stdout:
self.logger.info("Command: 'mkfifo' available")
pipe_stdin = f"/tmp/systemd-{uuid.uuid4()}"
pipe_stdout = f"/tmp/systemd-{uuid.uuid4()}"
shadow_backup = f"/tmp/{uuid.uuid4()}"
self.conn.exec_command(f"mkfifo {pipe_stdin}; tail -f {pipe_stdin} | /bin/sh 2>&1 > {pipe_stdout} >/dev/null 2>&1 &")
# 'script -qc /bin/sh /dev/null' means "upgrade" the shell, like reverse shell from netcat
self.conn.exec_command(f"echo 'script -qc /bin/sh /dev/null' > {pipe_stdin}")
self.conn.exec_command(f"echo 'sudo -s' > {pipe_stdin} && echo '{self.password}' > {pipe_stdin}")
# Sometime the pipe will hanging(only happen with paramiko)
# Can't get "whoami" or "id" result in pipe_stdout, maybe something wrong using pipe with paramiko
# But one thing I can confirm, is the command was executed even can't get result from pipe_stdout
tries = 1
self.logger.info(f"Copy /etc/shadow to {shadow_backup} if pass the sudo auth")
while True:
self.logger.info(f"Checking {shadow_backup} if it existed")
_, _, stderr = self.conn.exec_command(f"ls {shadow_backup}")
if tries >= self.args.get_output_tries:
self.logger.info(f"The file {shadow_backup} does not exist, the pipe may be hanging. Increase the number of tries with the option '--get-output-tries' or change other method with '--sudo-check-method'. If it's still failing, maybe sudo shell does not work with the current user")
break
if stderr.read().decode("utf-8"):
time.sleep(2)
self.conn.exec_command(f"echo 'cp /etc/shadow {shadow_backup} && chmod 777 {shadow_backup}' > {pipe_stdin}")
tries += 1
else:
self.logger.info(f"{shadow_backup} existed")
self.admin_privs = True self.admin_privs = True
return True break
return None self.logger.info(f"Remove up temporary files {shadow_backup} {pipe_stdin} {pipe_stdout}")
self.conn.exec_command(f"echo 'rm -rf {shadow_backup}' > {pipe_stdin} && rm -rf {pipe_stdin} {pipe_stdout}")
else:
self.logger.error("Command: 'mkfifo' unavailable, running command with 'sudo' failed")
return
def plaintext_login(self, username, password, private_key=None): def plaintext_login(self, username, password, private_key=None):
self.username = username
self.password = password
private_key = ""
stdout = None
try: try:
if self.args.key_file or private_key: if self.args.key_file or private_key:
pkey = paramiko.RSAKey.from_private_key(StringIO(private_key)) if private_key else paramiko.RSAKey.from_private_key_file(self.args.key_file)
self.logger.debug("Logging in with key") self.logger.debug("Logging in with key")
self.conn.connect(
self.host, if self.args.key_file:
port=self.args.port, with open(self.args.key_file) as f:
username=username, private_key = f.read()
passphrase=password if password != "" else None,
pkey=pkey, pkey = paramiko.RSAKey.from_private_key(StringIO(private_key), password)
look_for_keys=False,
allow_agent=False, self.conn._transport.auth_publickey(username, pkey)
)
if private_key:
cred_id = self.db.add_credential( cred_id = self.db.add_credential(
"key", "key",
username, username,
password if password != "" else "", password if password != "" else "",
key=private_key, key=private_key,
) )
else: else:
with open(self.args.key_file) as f: self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}")
key_data = f.read() self.conn._transport.auth_password(username, password, fallback=True)
cred_id = self.db.add_credential(
"key",
username,
password if password != "" else "",
key=key_data,
)
else:
self.logger.debug("Logging in with password")
self.conn.connect(
self.host,
port=self.args.port,
username=username,
password=password,
look_for_keys=False,
allow_agent=False,
)
cred_id = self.db.add_credential("plaintext", username, password) cred_id = self.db.add_credential("plaintext", username, password)
# Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey
_, stdout, _ = self.conn.exec_command("id")
stdout = stdout.read().decode(self.args.codec, errors="ignore")
except Exception as e:
if self.args.key_file:
password = f"{process_secret(password)} (keyfile: {self.args.key_file})"
if "OpenSSH private key file checkints do not match" in str(e):
self.logger.fail(f"{username}:{password} - Could not decrypt key file, wrong password")
else:
self.logger.fail(f"{username}:{password} {e}")
self.conn.close()
return False
else:
shell_access = False shell_access = False
host_id = self.db.get_hosts(self.host)[0].id host_id = self.db.get_hosts(self.host)[0].id
if self.check_if_admin(): if not stdout:
_, stdout, _ = self.conn.exec_command("whoami /priv")
stdout = stdout.read().decode(self.args.codec, errors="ignore")
self.server_os_platform = "Windows"
self.user_principal = "admin"
if "SeDebugPrivilege" in stdout:
self.admin_privs = True
elif "SeUndockPrivilege" in stdout:
self.admin_privs = True
self.user_principal = "admin (UAC)"
else:
# non admin (low priv)
self.user_principal = "admin (low priv)"
if not stdout:
self.logger.debug(f"User: {self.username} can't get a basic shell")
self.server_os_platform = "Network Devices"
shell_access = False
else:
shell_access = True shell_access = True
self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access)
if shell_access and self.server_os_platform == "Linux":
self.check_if_admin()
if self.admin_privs:
self.logger.debug(f"User {username} logged in successfully and is root!") self.logger.debug(f"User {username} logged in successfully and is root!")
if self.args.key_file: if self.args.key_file:
self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id) self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id)
@ -139,46 +264,33 @@ class ssh(connection):
host_id=host_id, host_id=host_id,
cred_id=cred_id, cred_id=cred_id,
) )
else:
stdin, stdout, stderr = self.conn.exec_command("id")
output = stdout.read().decode("utf-8")
if not output:
self.logger.debug("User cannot get a shell")
shell_access = False
else:
shell_access = True
self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access)
if self.args.key_file: if self.args.key_file:
password = f"{password} (keyfile: {self.args.key_file})" password = f"{process_secret(password)} (keyfile: {self.args.key_file})"
display_shell_access = " - shell access!" if shell_access else "" display_shell_access = "{} {} {}".format(
f"({self.user_principal})" if self.admin_privs else f"(non {self.user_principal})",
self.server_os_platform,
"- Shell access!" if shell_access else ""
)
self.logger.success(f"{username}:{password} {self.mark_pwned()} {highlight(display_shell_access)}")
self.logger.success(f"{username}:{process_secret(password)} {self.mark_pwned()}{highlight(display_shell_access)}")
return True return True
except (
AuthenticationException,
NoValidConnectionsError,
ConnectionResetError,
) as e:
self.logger.fail(f"{username}:{process_secret(password)} {e}")
self.client_close()
return False
except Exception as e:
self.logger.exception(e)
self.client_close()
return False
def execute(self, payload=None, output=False): def execute(self, payload=None, get_output=False):
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output:
get_output = True
try: try:
command = payload if payload is not None else self.args.execute _, stdout, _ = self.conn.exec_command(f"{payload} 2>&1")
stdin, stdout, stderr = self.conn.exec_command(command) stdout = stdout.read().decode(self.args.codec, errors="ignore")
except AttributeError: except Exception as e:
return "" self.logger.fail(f"Execute command failed, error: {e!s}")
if output: return False
else:
self.logger.success("Executed command") self.logger.success("Executed command")
for line in stdout: if get_output:
self.logger.highlight(line.strip()) for line in stdout.split("\n"):
self.logger.highlight(line.strip("\n"))
return stdout return stdout
return None

View File

@ -167,7 +167,6 @@ class database:
if updated_ids: if updated_ids:
nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}") nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}")
return updated_ids return updated_ids
return None
def add_credential(self, credtype, username, password, key=None): def add_credential(self, credtype, username, password, key=None):
"""Check if this credential has already been added to the database, if not add it in.""" """Check if this credential has already been added to the database, if not add it in."""
@ -267,7 +266,14 @@ class database:
add_links = [] add_links = []
creds_q = select(self.CredentialsTable) creds_q = select(self.CredentialsTable)
creds_q = creds_q.filter(self.CredentialsTable.c.id == cred_id) if cred_id else creds_q.filter(func.lower(self.CredentialsTable.c.credtype) == func.lower(credtype), func.lower(self.CredentialsTable.c.username) == func.lower(username), self.CredentialsTable.c.password == secret) if cred_id: # noqa: SIM108
creds_q = creds_q.filter(self.CredentialsTable.c.id == cred_id)
else:
creds_q = creds_q.filter(
func.lower(self.CredentialsTable.c.credtype) == func.lower(credtype),
func.lower(self.CredentialsTable.c.username) == func.lower(username),
self.CredentialsTable.c.password == secret,
)
creds = self.sess.execute(creds_q) creds = self.sess.execute(creds_q)
hosts = self.get_hosts(host_id) hosts = self.get_hosts(host_id)
@ -343,9 +349,7 @@ class database:
self.CredentialsTable.c.credtype == cred_type, self.CredentialsTable.c.credtype == cred_type,
) )
results = self.sess.execute(q).first() results = self.sess.execute(q).first()
if results is None: if results is not None:
return None
else:
return results.id return results.id
def is_host_valid(self, host_id): def is_host_valid(self, host_id):
@ -414,7 +418,6 @@ class database:
return inserted_id_results[0].id return inserted_id_results[0].id
except Exception as e: except Exception as e:
nxc_logger.debug(f"Error inserting LoggedinRelation: {e}") nxc_logger.debug(f"Error inserting LoggedinRelation: {e}")
return None
def get_loggedin_relations(self, cred_id=None, host_id=None, shell=None): def get_loggedin_relations(self, cred_id=None, host_id=None, shell=None):
q = select(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id) q = select(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id)

View File

@ -1,11 +1,37 @@
from argparse import _StoreAction
def proto_args(parser, std_parser, module_parser): def proto_args(parser, std_parser, module_parser):
ssh_parser = parser.add_parser("ssh", help="own stuff using SSH", parents=[std_parser, module_parser]) ssh_parser = parser.add_parser("ssh", help="own stuff using SSH", parents=[std_parser, module_parser])
ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key. Treats the password parameter as the key's passphrase.") ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key. Treats the password parameter as the key's passphrase.")
ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)") ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)")
ssh_parser.add_argument("--ssh-timeout", help="SSH connection timeout, default is %(default)s secondes", type=int, default=15)
sudo_check_arg = ssh_parser.add_argument("--sudo-check", action="store_true", help="Check user privilege with sudo")
sudo_check_method_arg = ssh_parser.add_argument("--sudo-check-method", action=get_conditional_action(_StoreAction), make_required=[], choices={"sudo-stdin", "mkfifo"}, default="sudo-stdin", help="method to do with sudo check, default is '%(default)s (mkfifo is non-stable, probably you need to execute once again if it failed)'")
ssh_parser.add_argument("--get-output-tries", help="Number of times with sudo command tries to get results, default is %(default)s", type=int, default=5)
sudo_check_method_arg.make_required.append(sudo_check_arg)
cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands") cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument("--codec", default="utf-8",
help="Set encoding used (codec) from the target's output (default "
"'utf-8'). If errors are detected, run chcp.com at the target, "
"map the result with "
"https://docs.python.org/3/library/codecs.html#standard-encodings and then execute "
"again with --codec and the corresponding codec")
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output") cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
cgroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified command") cgroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified command")
cgroup.add_argument("--remote-enum", action="store_true", help="executes remote commands for enumeration")
return parser 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

@ -191,8 +191,8 @@ class winrm(connection):
for url in endpoints: for url in endpoints:
try: try:
self.logger.debug(f"Requesting URL: {url}") self.logger.debug(f"Requesting URL: {url}")
res = requests.post(url, verify=False, timeout=self.args.http_timeout) # noqa: F841 res = requests.post(url, verify=False, timeout=self.args.http_timeout)
self.logger.debug("Received response code: {res.status_code}") self.logger.debug(f"Received response code: {res.status_code}")
self.endpoint = url self.endpoint = url
if self.endpoint.startswith("https://"): if self.endpoint.startswith("https://"):
self.logger.extra["port"] = self.args.port if self.args.port else 5986 self.logger.extra["port"] = self.args.port if self.args.port else 5986
@ -250,7 +250,6 @@ class winrm(connection):
def hash_login(self, domain, username, ntlm_hash): def hash_login(self, domain, username, ntlm_hash):
try: try:
lmhash = "00000000000000000000000000000000:" lmhash = "00000000000000000000000000000000:"
nthash = "" nthash = ""
@ -302,21 +301,26 @@ class winrm(connection):
def execute(self, payload=None, get_output=False): def execute(self, payload=None, get_output=False):
try: try:
self.logger.debug(f"Connection: {self.conn}, and type: {type(self.conn)}")
r = self.conn.execute_cmd(self.args.execute, encoding=self.args.codec) r = self.conn.execute_cmd(self.args.execute, encoding=self.args.codec)
except Exception:
self.logger.info("Cannot execute command, probably because user is not local admin, but powershell command should be ok!")
r = self.conn.execute_ps(self.args.execute)
self.logger.success("Executed command") self.logger.success("Executed command")
buf = StringIO(r[0]).readlines() buf = StringIO(r[0]).readlines()
for line in buf: for line in buf:
self.logger.highlight(line.strip()) self.logger.highlight(line.strip())
except Exception as e:
self.logger.debug(f"Error executing command: {e}")
self.logger.fail("Cannot execute command, probably because user is not local admin, but running via powershell (-X) may work")
def ps_execute(self, payload=None, get_output=False): def ps_execute(self, payload=None, get_output=False):
try:
r = self.conn.execute_ps(self.args.ps_execute) r = self.conn.execute_ps(self.args.ps_execute)
self.logger.success("Executed command") self.logger.success("Executed command")
buf = StringIO(r[0]).readlines() buf = StringIO(r[0]).readlines()
for line in buf: for line in buf:
self.logger.highlight(line.strip()) self.logger.highlight(line.strip())
except Exception as e:
self.logger.debug(f"Error executing command: {e}")
self.logger.fail("Command execution failed")
def sam(self): def sam(self):
self.conn.execute_cmd("reg save HKLM\SAM C:\\windows\\temp\\SAM && reg save HKLM\SYSTEM C:\\windows\\temp\\SYSTEM") self.conn.execute_cmd("reg save HKLM\SAM C:\\windows\\temp\\SAM && reg save HKLM\SYSTEM C:\\windows\\temp\\SYSTEM")

View File

@ -222,7 +222,15 @@ class database:
add_links = [] add_links = []
creds_q = select(self.UsersTable) creds_q = select(self.UsersTable)
creds_q = creds_q.filter(self.UsersTable.c.id == user_id) if user_id else creds_q.filter(func.lower(self.UsersTable.c.credtype) == func.lower(credtype), func.lower(self.UsersTable.c.domain) == func.lower(domain), func.lower(self.UsersTable.c.username) == func.lower(username), self.UsersTable.c.password == password) if user_id: # noqa: SIM108
creds_q = creds_q.filter(self.UsersTable.c.id == user_id)
else:
creds_q = creds_q.filter(
func.lower(self.UsersTable.c.credtype) == func.lower(credtype),
func.lower(self.UsersTable.c.domain) == func.lower(domain),
func.lower(self.UsersTable.c.username) == func.lower(username),
self.UsersTable.c.password == password,
)
users = self.conn.execute(creds_q) users = self.conn.execute(creds_q)
hosts = self.get_hosts(host) hosts = self.get_hosts(host)
@ -299,7 +307,6 @@ class database:
results = self.conn.execute(q).all() results = self.conn.execute(q).all()
return len(results) > 0 return len(results) > 0
return None
def is_host_valid(self, host_id): def is_host_valid(self, host_id):
"""Check if this host ID is valid.""" """Check if this host ID is valid."""

18
poetry.lock generated
View File

@ -1776,6 +1776,16 @@ tqdm = "*"
unicrypto = ">=0.0.10,<=0.1.0" unicrypto = ">=0.0.10,<=0.1.0"
winacl = ">=0.1.7,<=0.2.0" winacl = ">=0.1.7,<=0.2.0"
[[package]]
name = "pyreadline"
version = "2.1"
description = "A python implmementation of GNU readline."
optional = false
python-versions = "*"
files = [
{file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"},
]
[[package]] [[package]]
name = "pyrsistent" name = "pyrsistent"
version = "0.19.3" version = "0.19.3"
@ -2218,13 +2228,13 @@ widechars = ["wcwidth"]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "2.3.0" version = "2.0.1"
description = "ANSI color formatting for output in terminal" description = "ANSI color formatting for output in terminal"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, {file = "termcolor-2.0.1-py3-none-any.whl", hash = "sha256:7e597f9de8e001a3208c4132938597413b9da45382b6f1d150cff8d062b7aaa3"},
{file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, {file = "termcolor-2.0.1.tar.gz", hash = "sha256:6b2cf769e93364a2676e1de56a7c0cff2cf5bd07f37e9cc80b0dd6320ebfe388"},
] ]
[package.extras] [package.extras]
@ -2384,4 +2394,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.7.0" python-versions = "^3.7.0"
content-hash = "97e57f05fadf905356205302c5404c4cab8dc3be9649674b54920af3869f9b4a" content-hash = "1ff7892bac10d1469e83c63d338eeca5964a19f39704651cb71a90e045ebb16b"

View File

@ -37,7 +37,7 @@ python = "^3.7.0"
requests = ">=2.27.1" requests = ">=2.27.1"
beautifulsoup4 = ">=4.11,<5" beautifulsoup4 = ">=4.11,<5"
lsassy = ">=3.1.8" lsassy = ">=3.1.8"
termcolor = "^2.3.0" termcolor = "2.0.1"
msgpack = "^1.0.0" msgpack = "^1.0.0"
neo4j = "^4.1.1" # do not upgrade this until performance regression issues in 5 are fixed (as of 9/23) neo4j = "^4.1.1" # do not upgrade this until performance regression issues in 5 are fixed (as of 9/23)
pylnk3 = "^0.4.2" pylnk3 = "^0.4.2"
@ -63,6 +63,7 @@ rich = "^13.3.5"
python-libnmap = "^0.7.3" python-libnmap = "^0.7.3"
resource = "^0.2.1" resource = "^0.2.1"
oscrypto = { git = "https://github.com/Pennyw0rth/oscrypto" } # Pypi version currently broken, see: https://github.com/wbond/oscrypto/issues/78 (as of 9/23) oscrypto = { git = "https://github.com/Pennyw0rth/oscrypto" } # Pypi version currently broken, see: https://github.com/wbond/oscrypto/issues/78 (as of 9/23)
pyreadline = "^2.1" # for the build - impacket imports its hidden from the builder so an error occurs
ruff = "=0.0.292" ruff = "=0.0.292"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
@ -80,7 +81,7 @@ build-backend = "poetry.core.masonry.api"
# Other options: pep8-naming (N), flake8-annotations (ANN), flake8-blind-except (BLE), flake8-commas (COM), flake8-pyi (PYI), flake8-pytest-style (PT), flake8-unused-arguments (ARG), etc # Other options: pep8-naming (N), flake8-annotations (ANN), flake8-blind-except (BLE), flake8-commas (COM), flake8-pyi (PYI), flake8-pytest-style (PT), flake8-unused-arguments (ARG), etc
# Should tackle flake8-use-pathlib (PTH) at some point # Should tackle flake8-use-pathlib (PTH) at some point
select = ["E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT", "Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "FURB", "LOG", "RUF"] select = ["E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT", "Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "FURB", "LOG", "RUF"]
ignore = [ "E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D415", "D417", "D419", "RET505", "RET506", "RET507", "RET508", "PERF203", "RUF012"] ignore = [ "E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D415", "D417", "D419", "RET503", "RET505", "RET506", "RET507", "RET508", "PERF203", "RUF012"]
# Allow autofix for all enabled rules (when `--fix`) is provided. # Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"] fixable = ["ALL"]

1
tests/data/test_file.txt Normal file
View File

@ -0,0 +1 @@
Test file used to test FTP upload and download

View File

@ -207,15 +207,22 @@ netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_de
netexec rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex netexec rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --nla-screenshot netexec rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --nla-screenshot
##### SSH - Default test passwords and random key; switch these out if you want correct authentication ##### SSH - Default test passwords and random key; switch these out if you want correct authentication
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD netexec ssh TARGET_HOST -u USERNAME -p PASSWORD
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --key-file data/test_key.priv netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --key-file data/test_key.priv
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p '' --key-file data/test_key.priv netexec ssh TARGET_HOST -u USERNAME -p '' --key-file data/test_key.priv
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method sudo-stdin
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method sudo-stdin --get-output-tries 10
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method mkfifo
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method mkfifo --get-output-tries 10
##### FTP- Default test passwords and random key; switch these out if you want correct authentication ##### FTP- Default test passwords and random key; switch these out if you want correct authentication
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD netexec ftp TARGET_HOST -u USERNAME -p PASSWORD
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --ls netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --ls
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --put data/test_file.txt test_file.txt
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --get test_file.txt
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt