Merge branch 'main' into main

main
Marshall Hallenbeck 2024-02-26 10:22:34 -05:00 committed by GitHub
commit 56338d28f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 174 additions and 107 deletions

View File

@ -43,5 +43,5 @@ if len(host_info_colors) != 4:
# this should probably be put somewhere else, but if it's in the config helpers, there is a circular import # this should probably be put somewhere else, but if it's in the config helpers, there is a circular import
def process_secret(text): def process_secret(text):
hidden = text[:reveal_chars_of_pwd] reveal = text[:reveal_chars_of_pwd]
return text if not audit_mode else hidden + audit_mode * 8 return text if not audit_mode else reveal + (audit_mode if len(audit_mode) > 1 else audit_mode * 8)

97
nxc/database.py Normal file
View File

@ -0,0 +1,97 @@
import sys
import configparser
import shutil
from sqlalchemy import create_engine
from sqlite3 import connect
from os import mkdir
from os.path import exists
from os.path import join as path_join
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.paths import WORKSPACE_DIR
def create_db_engine(db_path):
return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
def open_config(config_path):
try:
config = configparser.ConfigParser()
config.read(config_path)
except Exception as e:
print(f"[-] Error reading nxc.conf: {e}")
sys.exit(1)
return config
def get_workspace(config):
return config.get("nxc", "workspace")
def set_workspace(config_path, workspace_name):
config = open_config(config_path)
config.set("nxc", "workspace", workspace_name)
write_configfile(config, config_path)
print(f"[*] Workspace set to {workspace_name}")
def get_db(config):
return config.get("nxc", "last_used_db")
def write_configfile(config, config_path):
with open(config_path, "w") as configfile:
config.write(configfile)
def create_workspace(workspace_name, p_loader=None):
"""
Create a new workspace with the given name.
Args:
----
workspace_name (str): The name of the workspace.
Returns:
-------
None
"""
if exists(path_join(WORKSPACE_DIR, workspace_name)):
print(f"[-] Workspace {workspace_name} already exists")
else:
print(f"[*] Creating {workspace_name} workspace")
mkdir(path_join(WORKSPACE_DIR, workspace_name))
if p_loader is None:
p_loader = ProtocolLoader()
protocols = p_loader.get_protocols()
for protocol in protocols:
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db")
if not exists(proto_db_path):
print(f"[*] Initializing {protocol.upper()} protocol database")
conn = connect(proto_db_path)
c = conn.cursor()
# try to prevent some weird sqlite I/O errors
c.execute("PRAGMA journal_mode = OFF")
c.execute("PRAGMA foreign_keys = 1")
protocol_object.database.db_schema(c)
# commit the changes and close everything off
conn.commit()
conn.close()
def delete_workspace(workspace_name):
shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name))
print(f"[*] Workspace {workspace_name} deleted")
def initialize_db():
if not exists(path_join(WORKSPACE_DIR, "default")):
create_workspace("default")

View File

@ -3,7 +3,7 @@ from os.path import exists
from os.path import join as path_join from os.path import join as path_join
import shutil import shutil
from nxc.paths import NXC_PATH, CONFIG_PATH, TMP_PATH, DATA_PATH from nxc.paths import NXC_PATH, CONFIG_PATH, TMP_PATH, DATA_PATH
from nxc.nxcdb import initialize_db from nxc.database import initialize_db
from nxc.logger import nxc_logger from nxc.logger import nxc_logger
@ -29,7 +29,7 @@ def first_run_setup(logger=nxc_logger):
logger.display(f"Creating missing folder {folder}") logger.display(f"Creating missing folder {folder}")
mkdir(path_join(NXC_PATH, folder)) mkdir(path_join(NXC_PATH, folder))
initialize_db(logger) initialize_db()
if not exists(CONFIG_PATH): if not exists(CONFIG_PATH):
logger.display("Copying default configuration file") logger.display("Copying default configuration file")

View File

@ -12,6 +12,7 @@ from nxc.paths import NXC_PATH
from nxc.console import nxc_console from nxc.console import nxc_console
from nxc.logger import nxc_logger from nxc.logger import nxc_logger
from nxc.config import nxc_config, nxc_workspace, config_log, ignore_opsec from nxc.config import nxc_config, nxc_workspace, config_log, ignore_opsec
from nxc.database import create_db_engine
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
import asyncio import asyncio
from nxc.helpers import powershell from nxc.helpers import powershell
@ -21,7 +22,6 @@ from os.path import exists
from os.path import join as path_join from os.path import join as path_join
from sys import exit from sys import exit
import logging import logging
import sqlalchemy
from rich.progress import Progress from rich.progress import Progress
import platform import platform
@ -38,17 +38,13 @@ if platform.system() != "Windows":
resource.setrlimit(resource.RLIMIT_NOFILE, file_limit) 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)
async def start_run(protocol_obj, args, db, targets): async def start_run(protocol_obj, args, db, targets):
futures = []
nxc_logger.debug("Creating ThreadPoolExecutor") nxc_logger.debug("Creating ThreadPoolExecutor")
if args.no_progress or len(targets) == 1: if args.no_progress or len(targets) == 1:
with ThreadPoolExecutor(max_workers=args.threads + 1) as executor: with ThreadPoolExecutor(max_workers=args.threads + 1) as executor:
nxc_logger.debug(f"Creating thread for {protocol_obj}") nxc_logger.debug(f"Creating thread for {protocol_obj}")
_ = [executor.submit(protocol_obj, args, db, target) for target in targets] futures = [executor.submit(protocol_obj, args, db, target) for target in targets]
else: else:
with Progress(console=nxc_console) as progress, ThreadPoolExecutor(max_workers=args.threads + 1) as executor: with Progress(console=nxc_console) as progress, ThreadPoolExecutor(max_workers=args.threads + 1) as executor:
current = 0 current = 0
@ -62,6 +58,11 @@ async def start_run(protocol_obj, args, db, targets):
for _ in as_completed(futures): for _ in as_completed(futures):
current += 1 current += 1
progress.update(tasks, completed=current) progress.update(tasks, completed=current)
for future in as_completed(futures):
try:
future.result()
except Exception:
nxc_logger.exception(f"Exception for target {targets[futures.index(future)]}: {future.exception()}")
def main(): def main():

View File

@ -1,31 +1,25 @@
import cmd import cmd
import configparser
import csv import csv
import sys
import os import os
import argparse
from os import listdir from os import listdir
from os.path import exists from os.path import exists
from os.path import join as path_join from os.path import join as path_join
import shutil
from sqlite3 import connect
import sys
from textwrap import dedent from textwrap import dedent
from requests import get, post, ConnectionError from requests import get, post, ConnectionError
from sqlalchemy import create_engine
from terminaltables import AsciiTable from terminaltables import AsciiTable
from termcolor import colored
from nxc.loaders.protocolloader import ProtocolLoader from nxc.loaders.protocolloader import ProtocolLoader
from nxc.paths import CONFIG_PATH, WS_PATH, WORKSPACE_DIR from nxc.paths import CONFIG_PATH, WORKSPACE_DIR
from nxc.database import create_db_engine, open_config, get_workspace, get_db, write_configfile, create_workspace, set_workspace
class UserExitedProto(Exception): class UserExitedProto(Exception):
pass pass
def create_db_engine(db_path):
return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
def print_table(data, title=None): def print_table(data, title=None):
print() print()
table = AsciiTable(data) table = AsciiTable(data)
@ -446,29 +440,15 @@ class NXCDBMenu(cmd.Cmd):
def __init__(self, config_path): def __init__(self, config_path):
cmd.Cmd.__init__(self) cmd.Cmd.__init__(self)
self.config_path = config_path self.config_path = config_path
try:
self.config = configparser.ConfigParser()
self.config.read(self.config_path)
except Exception as e:
print(f"[-] Error reading nxc.conf: {e}")
sys.exit(1)
self.conn = None self.conn = None
self.p_loader = ProtocolLoader() self.p_loader = ProtocolLoader()
self.protocols = self.p_loader.get_protocols() self.protocols = self.p_loader.get_protocols()
self.workspace = self.config.get("nxc", "workspace") self.config = open_config(self.config_path)
self.workspace = get_workspace(self.config)
self.db = get_db(self.config)
self.do_workspace(self.workspace) self.do_workspace(self.workspace)
self.db = self.config.get("nxc", "last_used_db")
if self.db:
self.do_proto(self.db)
def write_configfile(self):
with open(self.config_path, "w") as configfile:
self.config.write(configfile)
def do_proto(self, proto): def do_proto(self, proto):
if not proto: if not proto:
return return
@ -479,7 +459,7 @@ class NXCDBMenu(cmd.Cmd):
db_nav_object = self.p_loader.load_protocol(self.protocols[proto]["nvpath"]) db_nav_object = self.p_loader.load_protocol(self.protocols[proto]["nvpath"])
db_object = self.p_loader.load_protocol(self.protocols[proto]["dbpath"]) db_object = self.p_loader.load_protocol(self.protocols[proto]["dbpath"])
self.config.set("nxc", "last_used_db", proto) self.config.set("nxc", "last_used_db", proto)
self.write_configfile() write_configfile(self.config, self.config_path)
try: try:
proto_menu = db_nav_object.navigator(self, db_object.database(self.conn), proto) proto_menu = db_nav_object.navigator(self, db_object.database(self.conn), proto)
proto_menu.cmdloop() proto_menu.cmdloop()
@ -506,18 +486,18 @@ class NXCDBMenu(cmd.Cmd):
if subcommand == "create": if subcommand == "create":
new_workspace = line.split()[1].strip() new_workspace = line.split()[1].strip()
print(f"[*] Creating workspace '{new_workspace}'") print(f"[*] Creating workspace '{new_workspace}'")
self.create_workspace(new_workspace, self.p_loader, self.protocols) create_workspace(new_workspace, self.p_loader)
self.do_workspace(new_workspace) self.do_workspace(new_workspace)
elif subcommand == "list": elif subcommand == "list":
print("[*] Enumerating Workspaces") print("[*] Enumerating Workspaces")
for workspace in listdir(path_join(WORKSPACE_DIR)): for workspace in listdir(path_join(WORKSPACE_DIR)):
if workspace == self.workspace: if workspace == self.workspace:
print("==> " + workspace) print(f" * {colored(workspace, 'green')}")
else: else:
print(workspace) print(f" {workspace}")
elif exists(path_join(WORKSPACE_DIR, line)): elif exists(path_join(WORKSPACE_DIR, line)):
self.config.set("nxc", "workspace", line) self.config.set("nxc", "workspace", line)
self.write_configfile() write_configfile(self.config, self.config_path)
self.workspace = line self.workspace = line
self.prompt = f"nxcdb ({line}) > " self.prompt = f"nxcdb ({line}) > "
@ -538,65 +518,49 @@ class NXCDBMenu(cmd.Cmd):
Exits Exits
""" """
print_help(help_string) print_help(help_string)
@staticmethod
def create_workspace(workspace_name, p_loader, protocols):
os.mkdir(path_join(WORKSPACE_DIR, workspace_name))
for protocol in protocols:
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db")
if not exists(proto_db_path):
print(f"[*] Initializing {protocol.upper()} protocol database")
conn = connect(proto_db_path)
c = conn.cursor()
# try to prevent some weird sqlite I/O errors
c.execute("PRAGMA journal_mode = OFF")
c.execute("PRAGMA foreign_keys = 1")
protocol_object.database.db_schema(c)
# commit the changes and close everything off
conn.commit()
conn.close()
def delete_workspace(workspace_name):
shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name))
def initialize_db(logger):
if not exists(path_join(WS_PATH, "default")):
logger.debug("Creating default workspace")
os.mkdir(path_join(WS_PATH, "default"))
p_loader = ProtocolLoader()
protocols = p_loader.get_protocols()
for protocol in protocols:
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
proto_db_path = path_join(WS_PATH, "default", f"{protocol}.db")
if not exists(proto_db_path):
logger.debug(f"Initializing {protocol.upper()} protocol database")
conn = connect(proto_db_path)
c = conn.cursor()
# try to prevent some weird sqlite I/O errors
c.execute("PRAGMA journal_mode = OFF") # could try setting to PERSIST if DB corruption starts occurring
c.execute("PRAGMA foreign_keys = 1")
# set a small timeout (5s) so if another thread is writing to the database, the entire program doesn't crash
c.execute("PRAGMA busy_timeout = 5000")
protocol_object.database.db_schema(c)
# commit the changes and close everything off
conn.commit()
conn.close()
def main(): def main():
if not exists(CONFIG_PATH): if not exists(CONFIG_PATH):
print("[-] Unable to find config file") print("[-] Unable to find config file")
sys.exit(1) sys.exit(1)
parser = argparse.ArgumentParser(
description="NXCDB is a database navigator for NXC",
)
parser.add_argument(
"-gw",
"--get-workspace",
action="store_true",
help="get the current workspace",
)
parser.add_argument(
"-cw",
"--create-workspace",
help="create a new workspace",
)
parser.add_argument(
"-sw",
"--set-workspace",
help="set the current workspace",
)
args = parser.parse_args()
if args.create_workspace:
create_workspace(args.create_workspace)
sys.exit()
if args.set_workspace:
set_workspace(CONFIG_PATH, args.set_workspace)
sys.exit()
if args.get_workspace:
current_workspace = get_workspace(open_config(CONFIG_PATH))
for workspace in listdir(path_join(WORKSPACE_DIR)):
if workspace == current_workspace:
print(f" * {colored(workspace, 'green')}")
else:
print(f" {workspace}")
sys.exit()
try: try:
nxcdbnav = NXCDBMenu(CONFIG_PATH) nxcdbnav = NXCDBMenu(CONFIG_PATH)
nxcdbnav.cmdloop() nxcdbnav.cmdloop()

View File

@ -8,7 +8,7 @@ if os.name == "nt":
TMP_PATH = os.getenv("LOCALAPPDATA") + "\\Temp\\nxc_hosted" TMP_PATH = os.getenv("LOCALAPPDATA") + "\\Temp\\nxc_hosted"
if hasattr(sys, "getandroidapilevel"): if hasattr(sys, "getandroidapilevel"):
TMP_PATH = os.path.join("/data", "data", "com.termux", "files", "usr", "tmp", "nxc_hosted") TMP_PATH = os.path.join("/data", "data", "com.termux", "files", "usr", "tmp", "nxc_hosted")
WS_PATH = os.path.join(NXC_PATH, "workspaces")
CERT_PATH = os.path.join(NXC_PATH, "nxc.pem") CERT_PATH = os.path.join(NXC_PATH, "nxc.pem")
CONFIG_PATH = os.path.join(NXC_PATH, "nxc.conf") CONFIG_PATH = os.path.join(NXC_PATH, "nxc.conf")
WORKSPACE_DIR = os.path.join(NXC_PATH, "workspaces") WORKSPACE_DIR = os.path.join(NXC_PATH, "workspaces")

View File

@ -736,17 +736,23 @@ class smb(connection):
share_info["access"].append("READ") share_info["access"].append("READ")
except SessionError as e: except SessionError as e:
error = get_error_string(e) error = get_error_string(e)
self.logger.debug(f"Error checking READ access on share: {error}") self.logger.debug(f"Error checking READ access on share {share_name}: {error}")
if not self.args.no_write_check: if not self.args.no_write_check:
try: try:
self.conn.createDirectory(share_name, temp_dir) self.conn.createDirectory(share_name, temp_dir)
self.conn.deleteDirectory(share_name, temp_dir)
write = True write = True
share_info["access"].append("WRITE") share_info["access"].append("WRITE")
except SessionError as e: except SessionError as e:
error = get_error_string(e) error = get_error_string(e)
self.logger.debug(f"Error checking WRITE access on share: {error}") self.logger.debug(f"Error checking WRITE access on share {share_name}: {error}")
if write:
try:
self.conn.deleteDirectory(share_name, temp_dir)
except SessionError as e:
error = get_error_string(e)
self.logger.debug(f"Error DELETING created temp dir {temp_dir} on share {share_name}: {error}")
permissions.append(share_info) permissions.append(share_info)
@ -1258,12 +1264,11 @@ class smb(connection):
os.remove(download_path) os.remove(download_path)
def enable_remoteops(self): def enable_remoteops(self):
if self.remote_ops is not None and self.bootkey is not None:
return
try: try:
self.remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) self.remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost)
self.remote_ops.enableRegistry() self.remote_ops.enableRegistry()
self.bootkey = self.remote_ops.getBootKey() if self.bootkey is None:
self.bootkey = self.remote_ops.getBootKey()
except Exception as e: except Exception as e:
self.logger.fail(f"RemoteOperations failed: {e}") self.logger.fail(f"RemoteOperations failed: {e}")

View File

@ -7,13 +7,13 @@ from nxc.nxcdb import delete_workspace, NXCDBMenu
from nxc.first_run import first_run_setup from nxc.first_run import first_run_setup
from nxc.loaders.protocolloader import ProtocolLoader from nxc.loaders.protocolloader import ProtocolLoader
from nxc.logger import NXCAdapter from nxc.logger import NXCAdapter
from nxc.paths import WS_PATH from nxc.paths import WORKSPACE_DIR
from sqlalchemy.dialects.sqlite import Insert from sqlalchemy.dialects.sqlite import Insert
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def db_engine(): def db_engine():
db_path = os.path.join(WS_PATH, "test/smb.db") db_path = os.path.join(WORKSPACE_DIR, "test/smb.db")
db_engine = create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True) db_engine = create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
yield db_engine yield db_engine
db_engine.dispose() db_engine.dispose()