diff --git a/netexec.spec b/netexec.spec index 184be698..28bb2485 100644 --- a/netexec.spec +++ b/netexec.spec @@ -48,6 +48,7 @@ a = Analysis( 'lsassy.parser', 'lsassy.session', 'lsassy.impacketfile', + 'bloodhound', 'dns', 'dns.name', 'dns.resolver', diff --git a/nxc/loaders/moduleloader.py b/nxc/loaders/moduleloader.py index 01c1008f..6f5806fb 100755 --- a/nxc/loaders/moduleloader.py +++ b/nxc/loaders/moduleloader.py @@ -101,6 +101,7 @@ class ModuleLoader: "supported_protocols": module_spec.supported_protocols, "opsec_safe": module_spec.opsec_safe, "multiple_hosts": module_spec.multiple_hosts, + "requires_admin": True if hasattr(module_spec, 'on_admin_login') and callable(module_spec.on_admin_login) else False, } } if self.module_is_sane(module_spec, module_path): diff --git a/nxc/netexec.py b/nxc/netexec.py index fff3afb1..66b1a935 100755 --- a/nxc/netexec.py +++ b/nxc/netexec.py @@ -37,6 +37,7 @@ if platform != "win32": resource.setrlimit(resource.RLIMIT_NOFILE, file_limit) + def create_db_engine(db_path): return sqlalchemy.create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True) @@ -165,8 +166,13 @@ def main(): modules = loader.list_modules() if args.list_modules: + nxc_logger.highlight("LOW PRIVILEGE MODULES") 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']}") exit(0) elif args.module and args.show_module_options: diff --git a/nxc/protocols/ftp.py b/nxc/protocols/ftp.py index 8d374cee..c54d19fd 100644 --- a/nxc/protocols/ftp.py +++ b/nxc/protocols/ftp.py @@ -69,16 +69,42 @@ class ftp(connection): host_id = self.db.get_hosts(self.host)[0].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!')}") else: self.logger.success(f"{username}:{process_secret(password)}") if self.args.ls: - files = self.list_directory_full() - self.logger.display("Directory Listing") - for file in files: - self.logger.highlight(file) + # If the default directory is specified, then we will list the current directory + if self.args.ls == ".": + 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(f"Directory Listing") + for file in files: + 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: self.conn.close() @@ -90,9 +116,56 @@ class ftp(connection): # 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 files = [] - self.conn.retrlines("LIST", callback=files.append) + try: + 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 + 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) + 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(f"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")) + 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): 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] diff --git a/nxc/protocols/ftp/proto_args.py b/nxc/protocols/ftp/proto_args.py index 0e9e94d4..adf1e69a 100644 --- a/nxc/protocols/ftp/proto_args.py +++ b/nxc/protocols/ftp/proto_args.py @@ -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)") 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 diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 91bc9536..3b94d6eb 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -1,12 +1,16 @@ import logging from io import StringIO - import paramiko +import re +import uuid +import logging +import time +import socket +from io import StringIO from nxc.config import process_secret -from nxc.connection import connection -from nxc.helpers.logger import highlight +from nxc.connection import connection, highlight from nxc.logger import NXCAdapter from paramiko.ssh_exception import ( AuthenticationException, @@ -18,11 +22,30 @@ from paramiko.ssh_exception import ( class ssh(connection): def __init__(self, args, db, host): self.protocol = "SSH" - self.remote_version = None - self.server_os = None + self.remote_version = "Unknown SSH Version" + self.server_os_platform = "Linux" + self.user_principal = "root" 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): + logging.getLogger("paramiko").disabled = True + logging.getLogger("paramiko.transport").disabled = True self.logger = NXCAdapter( extra={ "protocol": "SSH", @@ -31,28 +54,22 @@ class ssh(connection): "hostname": self.hostname, } ) - logging.getLogger("paramiko").setLevel(logging.WARNING) 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 def enum_host_info(self): - self.remote_version = self.conn._transport.remote_version + if self.conn._transport.remote_version: + self.remote_version = self.conn._transport.remote_version self.logger.debug(f"Remote version: {self.remote_version}") - self.server_os = "" - 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) + self.db.add_host(self.host, self.args.port, self.remote_version) def create_conn_obj(self): self.conn = paramiko.SSHClient() self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - 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: return True except SSHException: @@ -62,123 +79,222 @@ class ssh(connection): except OSError: return False - def client_close(self): - self.conn.close() - 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 # 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") - if stdout.read().decode("utf-8").find("uid=0(root)") != -1: - self.logger.info("Determined user is root via `id` command") - self.admin_privs = True - return True - stdin, stdout, stderr = self.conn.exec_command("sudo -ln | grep 'NOPASSWD: ALL'") - if stdout.read().decode("utf-8").find("NOPASSWD: ALL") != -1: - self.logger.info("Determined user is root via `sudo -ln` command") - self.admin_privs = True - return True - return None + self.logger.info("Determined user is root via `id; sudo -ln` command") + _, stdout, _ = self.conn.exec_command("id; sudo -ln 2>&1") + 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.keys(): + 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 + break + self.logger.info(f"Remove up temporary files {shadow_backup}") + self.conn.exec_command(f"echo {self.password} | sudo -S rm -rf {shadow_backup}") + 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 + break + 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): + self.username = username + self.password = password + private_key = "" + stdout = None try: 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.conn.connect( - self.host, - port=self.args.port, - username=username, - passphrase=password if password != "" else None, - pkey=pkey, - look_for_keys=False, - allow_agent=False, + + if self.args.key_file: + with open(self.args.key_file, "r") as f: + private_key = f.read() + + pkey = paramiko.RSAKey.from_private_key(StringIO(private_key), password) + + self.conn._transport.auth_publickey(username, pkey) + + cred_id = self.db.add_credential( + "key", + username, + password if password != "" else "", + key=private_key, ) - if private_key: - cred_id = self.db.add_credential( - "key", - username, - password if password != "" else "", - key=private_key, - ) - else: - with open(self.args.key_file) as f: - key_data = f.read() - 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, - ) + self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}") + self.conn._transport.auth_password(username, password, fallback=True) 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 host_id = self.db.get_hosts(self.host)[0].id - if self.check_if_admin(): - shell_access = True - self.logger.debug(f"User {username} logged in successfully and is root!") - if self.args.key_file: - self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id) + 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: - self.db.add_admin_user( - "plaintext", - username, - password, - host_id=host_id, - cred_id=cred_id, - ) + # 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: - 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 + 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!") + if self.args.key_file: + self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id) + else: + self.db.add_admin_user( + "plaintext", + username, + password, + host_id=host_id, + cred_id=cred_id, + ) + 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 - 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: - command = payload if payload is not None else self.args.execute - stdin, stdout, stderr = self.conn.exec_command(command) - except AttributeError: - return "" - if output: + _, stdout, _ = self.conn.exec_command(f"{payload} 2>&1") + stdout = stdout.read().decode(self.args.codec, errors="ignore") + except Exception as e: + self.logger.fail(f"Execute command failed, error: {str(e)}") + return False + else: self.logger.success("Executed command") - for line in stdout: - self.logger.highlight(line.strip()) + if get_output: + for line in stdout.split("\n"): + self.logger.highlight(line.strip("\n")) return stdout - return None diff --git a/nxc/protocols/ssh/proto_args.py b/nxc/protocols/ssh/proto_args.py index 7d5473d1..51b6c88b 100644 --- a/nxc/protocols/ssh/proto_args.py +++ b/nxc/protocols/ssh/proto_args.py @@ -1,11 +1,37 @@ +from argparse import _StoreAction + 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.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("--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.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("-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 + +def get_conditional_action(baseAction): + class ConditionalAction(baseAction): + def __init__(self, option_strings, dest, **kwargs): + x = kwargs.pop('make_required', []) + super(ConditionalAction, self).__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(ConditionalAction, self).__call__(parser, namespace, values, option_string) + + return ConditionalAction \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 2c10a1e5..331dd112 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2218,13 +2218,13 @@ widechars = ["wcwidth"] [[package]] name = "termcolor" -version = "2.3.0" +version = "2.0.1" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.7" files = [ - {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, - {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, + {file = "termcolor-2.0.1-py3-none-any.whl", hash = "sha256:7e597f9de8e001a3208c4132938597413b9da45382b6f1d150cff8d062b7aaa3"}, + {file = "termcolor-2.0.1.tar.gz", hash = "sha256:6b2cf769e93364a2676e1de56a7c0cff2cf5bd07f37e9cc80b0dd6320ebfe388"}, ] [package.extras] diff --git a/pyproject.toml b/pyproject.toml index f53a4460..40219647 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ python = "^3.7.0" requests = ">=2.27.1" beautifulsoup4 = ">=4.11,<5" lsassy = ">=3.1.8" -termcolor = "^2.3.0" +termcolor = "2.0.1" msgpack = "^1.0.0" neo4j = "^4.1.1" # do not upgrade this until performance regression issues in 5 are fixed (as of 9/23) pylnk3 = "^0.4.2" @@ -63,6 +63,7 @@ rich = "^13.3.5" python-libnmap = "^0.7.3" 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) +pyreadline = "^2.1" # for the build - impacket imports its hidden from the builder so an error occurs ruff = "=0.0.292" [tool.poetry.group.dev.dependencies] diff --git a/tests/data/test_file.txt b/tests/data/test_file.txt new file mode 100644 index 00000000..62053472 --- /dev/null +++ b/tests/data/test_file.txt @@ -0,0 +1 @@ +Test file used to test FTP upload and download \ No newline at end of file diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 1b56a713..f3951d91 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -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 --nla-screenshot ##### 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 --continue-on-success 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 LOGIN_USERNAME -p '' --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 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 -netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD -netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --ls +netexec ftp TARGET_HOST -u USERNAME -p PASSWORD +netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --ls +netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --put data/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 --continue-on-success -netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt \ No newline at end of file +netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt