Skip to content

Commit 49f6f9c

Browse files
Merge branch 'main' into neff-e2e_tests
Signed-off-by: Marshall Hallenbeck <Marshall.Hallenbeck@gmail.com>
2 parents 4afc674 + 630d325 commit 49f6f9c

19 files changed

Lines changed: 334 additions & 224 deletions

File tree

nxc/cli.py

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from nxc.paths import NXC_PATH
1010
from nxc.loaders.protocolloader import ProtocolLoader
1111
from nxc.helpers.logger import highlight
12+
from nxc.helpers.args import DisplayDefaultsNotNone
1213
from nxc.logger import nxc_logger, setup_debug_logging
1314
import importlib.metadata
1415

@@ -23,8 +24,29 @@ def gen_cli_args():
2324
COMMIT = ""
2425
CODENAME = "nxc4u"
2526
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")
26-
27-
parser = argparse.ArgumentParser(description=rf"""
27+
28+
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
29+
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
30+
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
31+
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
32+
generic_group.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
33+
34+
output_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
35+
output_group = output_parser.add_argument_group("Output", "Options to set verbosity levels and control output")
36+
output_group.add_argument("--verbose", action="store_true", help="enable verbose output")
37+
output_group.add_argument("--debug", action="store_true", help="enable debug level information")
38+
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
39+
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
40+
41+
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
42+
dns_group = dns_parser.add_argument_group("DNS")
43+
dns_group.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
44+
dns_group.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
45+
dns_group.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
46+
dns_group.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds")
47+
48+
parser = argparse.ArgumentParser(
49+
description=rf"""
2850
. .
2951
.| |. _ _ _ _____
3052
|| || | \ | | ___ | |_ | ____| __ __ ___ ___
@@ -42,62 +64,55 @@ def gen_cli_args():
4264
{highlight('Version', 'red')} : {highlight(VERSION)}
4365
{highlight('Codename', 'red')}: {highlight(CODENAME)}
4466
{highlight('Commit', 'red')} : {highlight(COMMIT)}
45-
""", formatter_class=RawTextHelpFormatter)
46-
47-
parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)")
48-
parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
49-
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication (default: None)")
50-
parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
51-
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
52-
parser.add_argument("--debug", action="store_true", help="enable debug level information")
53-
parser.add_argument("--version", action="store_true", help="Display nxc version")
67+
""",
68+
formatter_class=RawTextHelpFormatter,
69+
parents=[generic_parser, output_parser, dns_parser]
70+
)
5471

55-
dns_parser = parser.add_argument_group("DNS")
56-
dns_parser.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
57-
dns_parser.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
58-
dns_parser.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
59-
dns_parser.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds (default: %(default)s)")
72+
parser.add_argument("--version", action="store_true", help="Display nxc version")
6073

6174
# we do module arg parsing here so we can reference the module_list attribute below
62-
module_parser = argparse.ArgumentParser(add_help=False)
63-
mgroup = module_parser.add_mutually_exclusive_group()
75+
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
76+
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
6477
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
65-
module_parser.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
66-
module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
67-
module_parser.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
68-
module_parser.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server (default: https)")
69-
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)")
70-
module_parser.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
71-
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)")
78+
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
79+
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
80+
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
7281

73-
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
82+
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
7483

75-
std_parser = argparse.ArgumentParser(add_help=False)
84+
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
7685
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)")
77-
std_parser.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
78-
std_parser.add_argument("-u", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
79-
std_parser.add_argument("-p", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
80-
std_parser.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
81-
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
82-
std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2")
83-
std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
84-
std_parser.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
85-
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
86-
std_parser.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
87-
std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
88-
89-
fail_group = std_parser.add_mutually_exclusive_group()
90-
fail_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
91-
fail_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
92-
fail_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
86+
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
87+
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
88+
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
89+
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
90+
credential_group.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
91+
credential_group.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2)")
92+
credential_group.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
93+
credential_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
94+
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
95+
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
96+
97+
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
98+
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
99+
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
100+
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
101+
kerberos_group.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")
102+
103+
server_group = std_parser.add_argument_group("Servers", "Options for nxc servers")
104+
server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server")
105+
server_group.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to")
106+
server_group.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
107+
server_group.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to")
93108

94109
p_loader = ProtocolLoader()
95110
protocols = p_loader.get_protocols()
96111

97112
try:
98113
for protocol in protocols:
99114
protocol_object = p_loader.load_protocol(protocols[protocol]["argspath"])
100-
subparsers = protocol_object.proto_args(subparsers, std_parser, module_parser)
115+
subparsers = protocol_object.proto_args(subparsers, [std_parser, module_parser])
101116
except Exception as e:
102117
nxc_logger.exception(f"Error loading proto_args from proto_args.py file in protocol folder: {protocol} - {e}")
103118

@@ -112,6 +127,10 @@ def gen_cli_args():
112127
print(f"{VERSION} - {CODENAME} - {COMMIT}")
113128
sys.exit(1)
114129

130+
# Multiply output_tries by 10 to enable more fine granural control, see exec methods
131+
if hasattr(args, "get_output_tries"):
132+
args.get_output_tries = args.get_output_tries * 10
133+
115134
return args
116135

117136

nxc/helpers/args.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE
2+
3+
class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter):
4+
def _get_help_string(self, action):
5+
help_string = action.help
6+
if "%(default)" not in action.help and action.default is not SUPPRESS:
7+
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
8+
if (action.option_strings or action.nargs in defaulting_nargs) and action.default: # Only add default info if it's not None
9+
help_string += " (default: %(default)s)" # NORUFF
10+
return help_string

nxc/modules/wcc.py

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,6 @@
2828
REG_VALUE_TYPE_UNICODE_STRING_SEQUENCE = 7
2929
REG_VALUE_TYPE_64BIT_LE = 11
3030

31-
# Setup file logger
32-
if "wcc_logger" not in globals():
33-
wcc_logger = logging.getLogger("WCC")
34-
wcc_logger.propagate = False
35-
log_filename = nxc_logger.init_log_file()
36-
log_filename = log_filename.replace("log_", "wcc_")
37-
wcc_logger.setLevel(logging.INFO)
38-
wcc_file_handler = logging.FileHandler(log_filename)
39-
wcc_file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
40-
wcc_logger.addHandler(wcc_file_handler)
41-
4231

4332
class ConfigCheck:
4433
"""Class for performing the checks and holding the results"""
@@ -75,7 +64,7 @@ def run(self):
7564
def log(self, context):
7665
result = "passed" if self.ok else "did not pass"
7766
reasons = ", ".join(self.reasons)
78-
wcc_logger.info(f'{self.connection.host}: Check "{self.name}" {result} because: {reasons}')
67+
self.module.wcc_logger.info(f'{self.connection.host}: Check "{self.name}" {result} because: {reasons}')
7968
if self.module.quiet:
8069
return
8170

@@ -99,6 +88,19 @@ class NXCModule:
9988
supported_protocols = ["smb"]
10089
opsec_safe = True
10190
multiple_hosts = True
91+
92+
def __init__(self):
93+
self.context = None
94+
self.module_options = None
95+
96+
self.wcc_logger = logging.getLogger("WCC")
97+
self.wcc_logger.propagate = False
98+
log_filename = nxc_logger.init_log_file()
99+
log_filename = log_filename.replace("log_", "wcc_")
100+
self.wcc_logger.setLevel(logging.INFO)
101+
wcc_file_handler = logging.FileHandler(log_filename)
102+
wcc_file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
103+
self.wcc_logger.addHandler(wcc_file_handler)
102104

103105
def options(self, context, module_options):
104106
"""
@@ -156,15 +158,9 @@ def __init__(self, context, connection):
156158
self.dce = remoteOps._RemoteOperations__rrp
157159

158160
def run(self):
159-
# Prepare checks
160161
self.init_checks()
161-
162-
# Perform checks
163162
self.check_config()
164-
165-
# Check methods #
166-
#################
167-
163+
168164
def init_checks(self):
169165
# Declare the checks to do and how to do them
170166
self.checks = [
@@ -483,9 +479,6 @@ def check_applocker(self):
483479

484480
return success, reasons
485481

486-
# Methods for getting values from the remote registry #
487-
#######################################################
488-
489482
def _open_root_key(self, dce, connection, root_key):
490483
ans = None
491484
retries = 1
@@ -595,9 +588,6 @@ def get_value(subkey_handle, dwIndex=0):
595588
return data
596589
return DCERPCSessionError(error_code=ERROR_OBJECT_NOT_FOUND)
597590

598-
# Methods for getting values from SAMR and SCM #
599-
################################################
600-
601591
def get_service(self, service_name, connection):
602592
"""Get the service status and configuration for specified service"""
603593
remoteOps = RemoteOperations(smbConnection=connection.conn, doKerberos=False)
@@ -645,23 +635,15 @@ def ls(self, smb, path="\\", share="C$"):
645635
self.context.log.error(f"ls(): C:\\{path} {e}\n")
646636
return file_listing
647637

648-
649-
# Comparison operators #
650-
########################
651-
652-
653638
def le(reg_sz_string, number):
654639
return int(reg_sz_string[:-1]) <= number
655640

656-
657641
def in_(obj, seq):
658642
return obj in seq
659643

660-
661644
def startswith(string, start):
662645
return string.startswith(start)
663646

664-
665647
def not_(boolean_operator):
666648
def wrapper(*args, **kwargs):
667649
return not boolean_operator(*args, **kwargs)

nxc/protocols/ftp/proto_args.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
def proto_args(parser, std_parser, module_parser):
2-
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=[std_parser, module_parser])
3-
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port (default: 21)")
1+
from nxc.helpers.args import DisplayDefaultsNotNone
42

5-
cgroup = ftp_parser.add_argument_group("FTP Access", "Options for enumerating your access")
6-
cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory, ex: --ls or --ls Directory")
7-
cgroup.add_argument("--get", metavar="FILE", help="Download a file, ex: --get fileName.txt")
8-
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file, ex: --put inputFileName.txt outputFileName.txt")
3+
4+
def proto_args(parser, parents):
5+
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=parents, formatter_class=DisplayDefaultsNotNone)
6+
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port")
7+
8+
cgroup = ftp_parser.add_argument_group("File Operations", "Options for enumerating and interacting with files on the target")
9+
cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory")
10+
cgroup.add_argument("--get", metavar="FILE", help="Download a file")
11+
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file")
912
return parser

0 commit comments

Comments
 (0)