Merge branch 'develop' into marshall_cleanup
Signed-off-by: Marshall Hallenbeck <Marshall.Hallenbeck@gmail.com>main
commit
dc5ef93f7c
|
@ -48,6 +48,7 @@ a = Analysis(
|
|||
'lsassy.parser',
|
||||
'lsassy.session',
|
||||
'lsassy.impacketfile',
|
||||
'bloodhound',
|
||||
'dns',
|
||||
'dns.name',
|
||||
'dns.resolver',
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 --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
|
||||
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt
|
||||
|
|
Loading…
Reference in New Issue