Skip to content

Commit

Permalink
Merge branch 'develop' into neff-bh-pc
Browse files Browse the repository at this point in the history
Signed-off-by: Marshall Hallenbeck <Marshall.Hallenbeck@gmail.com>
  • Loading branch information
Marshall-Hallenbeck authored Oct 29, 2023
2 parents e4f1558 + dee5e28 commit b60bef6
Show file tree
Hide file tree
Showing 44 changed files with 556 additions and 374 deletions.
14 changes: 8 additions & 6 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Lint Python code with ruff
# Caching source: https://gist.github.com/gh640/233a6daf68e9e937115371c0ecd39c61?permalink_comment_id=4529233#gistcomment-4529233

on: [push, pull_request]

Expand All @@ -11,15 +12,16 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Install poetry
run: |
pipx install poetry
poetry --version
poetry env info
- name: Install libraries with dev group
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.11
cache: poetry
cache-dependency-path: poetry.lock
- name: Install dependencies with dev group
run: |
poetry install --with dev
- name: Run ruff
Expand Down
1 change: 1 addition & 0 deletions netexec.spec
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ a = Analysis(
'lsassy.parser',
'lsassy.session',
'lsassy.impacketfile',
'bloodhound',
'dns',
'dns.name',
'dns.resolver',
Expand Down
154 changes: 22 additions & 132 deletions nxc/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,12 @@ def gen_cli_args():
{highlight('Version', 'red')} : {highlight(VERSION)}
{highlight('Codename', 'red')}: {highlight(CODENAME)}
""",
formatter_class=RawTextHelpFormatter,
)

parser.add_argument(
"-t",
type=int,
dest="threads",
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",
)
""", formatter_class=RawTextHelpFormatter)

parser.add_argument("-t", type=int, dest="threads", 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("--debug", action="store_true", help="enable debug level information")
parser.add_argument("--version", action="store_true", help="Display nxc version")
Expand All @@ -64,122 +42,34 @@ def gen_cli_args():
module_parser = argparse.ArgumentParser(add_help=False)
mgroup = module_parser.add_mutually_exclusive_group()
mgroup.add_argument("-M", "--module", action="append", metavar="MODULE", help="module to use")
module_parser.add_argument(
"-o",
metavar="MODULE_OPTION",
nargs="+",
default=[],
dest="module_options",
help="module options",
)
module_parser.add_argument("-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(
"--options",
dest="show_module_options",
action="store_true",
help="display module options",
)
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)",
)
module_parser.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
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")

std_parser = argparse.ArgumentParser(add_help=False)
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)",
)
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("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)")
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("-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("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
std_parser.add_argument(
"--use-kcache",
action="store_true",
help="Use Kerberos authentication from ccache file (KRB5CCNAME)",
)
std_parser.add_argument("--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(
"--aesKey",
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",
)
std_parser.add_argument("--aesKey", 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.add_argument(
"--gfail-limit",
metavar="LIMIT",
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",
)
fail_group.add_argument("--gfail-limit", metavar="LIMIT", 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()
protocols = p_loader.get_protocols()
Expand Down
10 changes: 6 additions & 4 deletions nxc/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,20 +393,23 @@ def try_credentials(self, domain, username, owned, secret, cred_type, data=None)
with sem:
if cred_type == "plaintext":
if self.args.kerberos:
self.logger.debug("Trying to authenticate using Kerberos")
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)
elif self.args.protocol == "ssh":
self.logger.debug("Trying to authenticate using plaintext over SSH")
return self.plaintext_login(username, secret, data)
else:
self.logger.debug("Trying to authenticate using plaintext")
return self.plaintext_login(username, secret)
elif cred_type == "hash":
if self.args.kerberos:
return self.kerberos_login(domain, username, "", secret, "", self.kdcHost, False)
return self.hash_login(domain, username, secret)
elif cred_type == "aesKey":
return self.kerberos_login(domain, username, "", "", secret, self.kdcHost, False)
return None

def login(self):
"""Try to login using the credentials specified in the command line or in the database.
Expand Down Expand Up @@ -441,6 +444,7 @@ def login(self):
data.extend(parsed_data)

if self.args.use_kcache:
self.logger.debug("Trying to authenticate using Kerberos cache")
with sem:
username = self.args.username[0] if len(self.args.username) else ""
password = self.args.password[0] if len(self.args.password) else ""
Expand All @@ -455,7 +459,6 @@ def login(self):
owned[user_index] = True
if not self.args.continue_on_success:
return True
return None
else:
if len(username) != len(secret):
self.logger.error("Number provided of usernames and passwords/hashes do not match!")
Expand All @@ -465,7 +468,6 @@ def login(self):
owned[user_index] = True
if not self.args.continue_on_success:
return True
return None

def mark_pwned(self):
return highlight(f"({pwned_label})" if self.admin_privs else "")
1 change: 1 addition & 0 deletions nxc/data/impersonate_module/impersonate.bs64

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions nxc/data/pi_module/pi.bs64

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion nxc/helpers/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@ def get_desktop_uagent(uagent=None):
return desktop_uagents[random.choice(desktop_uagents.keys())]
elif uagent:
return desktop_uagents[uagent]
return None
1 change: 0 additions & 1 deletion nxc/helpers/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ def highlight(text, color="yellow"):
return f"{colored(text, 'yellow', attrs=['bold'])}"
elif color == "red":
return f"{colored(text, 'red', attrs=['bold'])}"
return None
1 change: 0 additions & 1 deletion nxc/helpers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,3 @@ def _access_check(fn, mode):
name = os.path.join(p, thefile)
if _access_check(name, mode):
return name
return None
4 changes: 1 addition & 3 deletions nxc/loaders/moduleloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ def load_module(self, module_path):
except Exception as e:
self.logger.fail(f"Failed loading module at {module_path}: {e}")
self.logger.debug(traceback.format_exc())
return None

def init_module(self, module_path):
"""Initialize a module for execution"""
Expand All @@ -85,7 +84,6 @@ def init_module(self, module_path):
else:
self.logger.fail(f"Module {module.name.upper()} is not supported for protocol {self.args.protocol}")
sys.exit(1)
return None

def get_module_info(self, module_path):
"""Get the path, description, and options from a module"""
Expand All @@ -101,14 +99,14 @@ def get_module_info(self, module_path):
"supported_protocols": module_spec.supported_protocols,
"opsec_safe": module_spec.opsec_safe,
"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):
return module
except Exception as e:
self.logger.fail(f"Failed loading module at {module_path}: {e}")
self.logger.debug(traceback.format_exc())
return None

def list_modules(self):
"""List modules without initializing them"""
Expand Down
2 changes: 0 additions & 2 deletions nxc/modules/find-computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,5 @@ def on_login(self, context, connection):
except socket.gaierror:
context.log.debug("Missing IP")
context.log.highlight(f"{answer[0]} ({answer[1]}) (No IP Found)")
return None
else:
context.log.success(f"Unable to find any computers with the text {self.TEXT}")
return None
4 changes: 1 addition & 3 deletions nxc/modules/get-desc-users.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ def on_login(self, context, connection):
context.log.success("Found following users: ")
for answer in answers:
context.log.highlight(f"User: {answer[0]} description: {answer[1]}")
return None
return None

def filter_answer(self, context, answers):
# No option to filter
Expand All @@ -103,7 +101,7 @@ def filter_answer(self, context, answers):
conditionPasswordPolicy = False
if self.regex.search(description):
conditionPasswordPolicy = True

if (conditionFilter == self.FILTER) and (conditionPasswordPolicy == self.PASSWORDPOLICY):
answersFiltered.append([answer[0], description])
return answersFiltered
2 changes: 0 additions & 2 deletions nxc/modules/group_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ def on_login(self, context, connection):
context.log.success("Found the following members of the " + self.GROUP + " group:")
for answer in self.answers:
context.log.highlight(f"{answer[0]}")
return None
return None


# Carry out an LDAP search for the Group with the supplied Group name
Expand Down
2 changes: 0 additions & 2 deletions nxc/modules/groupmembership.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,3 @@ def on_login(self, context, connection):
group_name = group_parts[0].split("=")[1]

context.log.highlight(f"{group_name}")
return None
return None
1 change: 0 additions & 1 deletion nxc/modules/hash_spider.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ def run_lsassy(self, context, connection, cursor): # copied and pasted from lsa
self.save_credentials(context, connection, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"])
global credentials_data
credentials_data = credentials_output
return None

def spider_pcs(self, context, connection, cursor, dbconnection, driver):
cursor.execute("SELECT * from admin_users WHERE hash is not NULL")
Expand Down
10 changes: 5 additions & 5 deletions nxc/modules/impersonate.py

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions nxc/modules/keepass_discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ def on_admin_login(self, context, connection):
keepass_process_id = row[0]
keepass_process_username = row[1]
keepass_process_name = row[2]
context.log.highlight(
f'Found process "{keepass_process_name}" with PID {keepass_process_id} (user {keepass_process_username})'
)
context.log.highlight(f'Found process "{keepass_process_name}" with PID {keepass_process_id} (user {keepass_process_username})')
if row_number == 0:
context.log.display("No KeePass-related process was found")

Expand Down
Loading

0 comments on commit b60bef6

Please sign in to comment.