Merge remote-tracking branch 'upstream/develop' into connection-miss-port
commit
0a2a5df789
|
@ -114,6 +114,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": 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):
|
if self.module_is_sane(module_spec, module_path):
|
||||||
|
|
|
@ -47,6 +47,7 @@ except:
|
||||||
print("Incompatible python version, try with another python version or another binary 3.8 / 3.9 / 3.10 / 3.11 that match your python version (python -V)")
|
print("Incompatible python version, try with another python version or another binary 3.8 / 3.9 / 3.10 / 3.11 that match your python version (python -V)")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def create_db_engine(db_path):
|
def create_db_engine(db_path):
|
||||||
db_engine = sqlalchemy.create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
|
db_engine = sqlalchemy.create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
|
||||||
return db_engine
|
return db_engine
|
||||||
|
@ -179,8 +180,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,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
from nxc.config import process_secret
|
from nxc.config import process_secret
|
||||||
from nxc.connection import *
|
from nxc.connection import *
|
||||||
from nxc.logger import NXCAdapter
|
from nxc.logger import NXCAdapter
|
||||||
|
@ -80,16 +81,42 @@ 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:
|
||||||
files = self.list_directory_full()
|
# If the default directory is specified, then we will list the current directory
|
||||||
self.logger.display(f"Directory Listing")
|
if self.args.ls == ".":
|
||||||
for file in files:
|
files = self.list_directory_full()
|
||||||
self.logger.highlight(file)
|
# 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:
|
if not self.args.continue_on_success:
|
||||||
self.conn.close()
|
self.conn.close()
|
||||||
|
@ -101,9 +128,56 @@ class ftp(connection):
|
||||||
# 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 = []
|
||||||
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
|
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):
|
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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Test file used to test FTP upload and download
|
|
@ -205,6 +205,8 @@ netexec ssh TARGET_HOST -u USERNAME -p '' --key-file data/test_key.priv
|
||||||
##### 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 USERNAME -p PASSWORD
|
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 --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 test_passwords.txt --no-bruteforce
|
netexec ftp TARGET_HOST -u data/test_users.txt -p test_passwords.txt --no-bruteforce
|
||||||
netexec ftp TARGET_HOST -u data/test_users.txt -p test_passwords.txt --no-bruteforce --continue-on-success
|
netexec ftp TARGET_HOST -u data/test_users.txt -p test_passwords.txt --no-bruteforce --continue-on-success
|
||||||
netexec ftp TARGET_HOST -u data/test_users.txt -p test_passwords.txt
|
netexec ftp TARGET_HOST -u data/test_users.txt -p test_passwords.txt
|
Loading…
Reference in New Issue