diff --git a/nxc/protocols/ldap/database.py b/nxc/protocols/ldap/database.py index 1062db39..ddc7dd9f 100644 --- a/nxc/protocols/ldap/database.py +++ b/nxc/protocols/ldap/database.py @@ -1,13 +1,13 @@ import sys -from sqlalchemy import func, Table, select +from sqlalchemy import func, Table, select, delete from sqlalchemy.dialects.sqlite import Insert # used for upsert from sqlalchemy.exc import ( NoInspectionAvailable, NoSuchTableError, ) -from nxc.database import BaseDB +from nxc.database import BaseDB, format_host_query from nxc.logger import nxc_logger class database(BaseDB): @@ -166,6 +166,14 @@ class database(BaseDB): self.db_execute(q_groups, groups) + def remove_credentials(self, creds_id): + """Removes a credential ID from the database""" + del_hosts = [] + for cred_id in creds_id: + q = delete(self.UsersTable).filter(self.UsersTable.c.id == cred_id) + del_hosts.append(q) + self.db_execute(q) + def is_credential_valid(self, credential_id): """Check if this credential ID is valid.""" q = select(self.UsersTable).filter( @@ -200,4 +208,32 @@ class database(BaseDB): self.UsersTable.c.credtype == cred_type, ) results = self.db_execute(q).first() - return results.id \ No newline at end of file + return results.id + + def get_hosts(self, filter_term=None, domain=None): + """Return hosts from the database.""" + q = select(self.HostsTable) + + # if we're returning a single host by ID + if self.is_host_valid(filter_term): + q = q.filter(self.HostsTable.c.id == filter_term) + results = self.db_execute(q).first() + # all() returns a list, so we keep the return format the same so consumers don't have to guess + return [results] + elif filter_term is not None and filter_term.startswith("domain"): + domain = filter_term.split()[1] + like_term = func.lower(f"%{domain}%") + q = q.filter(self.HostsTable.c.domain.like(like_term)) + # if we're filtering by ip/hostname + elif filter_term and filter_term != "": + q = format_host_query(q, filter_term, self.HostsTable) + + results = self.db_execute(q).all() + nxc_logger.debug(f"ldap hosts() - results: {results}") + return results + + def is_host_valid(self, host_id): + """Check if this host ID is valid.""" + q = select(self.HostsTable).filter(self.HostsTable.c.id == host_id) + results = self.db_execute(q).all() + return len(results) > 0 \ No newline at end of file diff --git a/nxc/protocols/ldap/db_navigator.py b/nxc/protocols/ldap/db_navigator.py index c712309b..18a02be3 100644 --- a/nxc/protocols/ldap/db_navigator.py +++ b/nxc/protocols/ldap/db_navigator.py @@ -1,7 +1,183 @@ -from nxc.nxcdb import DatabaseNavigator, print_help +from nxc.helpers.misc import validate_ntlm +from nxc.nxcdb import DatabaseNavigator, print_table, print_help class navigator(DatabaseNavigator): + def display_hosts(self, hosts): + data = [ + [ + "HostID", + "IP", + "Hostname", + "Domain", + "OS" + ] + ] + + for host in hosts: + host_id = host[0] + ip = host[1] + hostname = host[2] + domain = host[3] + + try: + os = host[4].decode() + except Exception: + os = host[4] + + data.append( + [ + host_id, + ip, + hostname, + domain, + os + ] + ) + print_table(data, title="Hosts") + + def do_hosts(self, line): + filter_term = line.strip() + + if filter_term == "": + hosts = self.db.get_hosts() + self.display_hosts(hosts) + else: + hosts = self.db.get_hosts(filter_term=filter_term) + + if len(hosts) > 1: + self.display_hosts(hosts) + elif len(hosts) == 1: + data = [ + [ + "HostID", + "IP", + "Hostname", + "Domain", + "OS" + ] + ] + host_id_list = [] + + for host in hosts: + host_id = host[0] + host_id_list.append(host_id) + ip = host[1] + hostname = host[2] + domain = host[3] + + try: + os = host[4].decode() + except Exception: + os = host[4] + + data.append( + [ + host_id, + ip, + hostname, + domain, + os + ] + ) + print_table(data, title="Host") + + def help_hosts(self): + help_string = """ + hosts [filter_term] + By default prints all hosts + Table format: + | 'HostID', 'IP', 'Hostname', 'Domain', 'OS' | + Subcommands: + filter_term - filters hosts with filter_term + If a single host is returned (e.g. `hosts 15`, it prints the following tables: + Host | 'HostID', 'IP', 'Hostname', 'Domain', 'OS' | + Otherwise, it prints the default host table from a `like` query on the `ip` and `hostname` columns + """ + print_help(help_string) + + def display_creds(self, creds): + data = [["CredID", "CredType", "Domain", "UserName", "Password"]] + + for cred in creds: + cred_id = cred[0] + domain = cred[1] + username = cred[2] + password = cred[3] + credtype = cred[4] + + data.append( + [ + cred_id, + credtype, + domain, + username, + password + ] + ) + print_table(data, title="Credentials") + + def do_creds(self, line): + filter_term = line.strip() + + if filter_term == "": + creds = self.db.get_credentials() + self.display_creds(creds) + elif filter_term.split()[0].lower() == "add": + args = filter_term.split()[1:] + + if len(args) == 3: + domain, username, password = args + if validate_ntlm(password): + self.db.add_credential("hash", domain, username, password) + else: + self.db.add_credential("plaintext", domain, username, password) + else: + print("[!] Format is 'add domain username password") + return + elif filter_term.split()[0].lower() == "remove": + args = filter_term.split()[1:] + + if len(args) != 1: + print("[!] Format is 'remove '") + return + else: + self.db.remove_credentials(args) + elif filter_term.split()[0].lower() == "plaintext": + creds = self.db.get_credentials(cred_type="plaintext") + self.display_creds(creds) + elif filter_term.split()[0].lower() == "hash": + creds = self.db.get_credentials(cred_type="hash") + self.display_creds(creds) + else: + creds = self.db.get_credentials(filter_term=filter_term) + data = [["CredID", "CredType", "Domain", "UserName", "Password"]] + cred_id_list = [] + + for cred in creds: + cred_id_list.append(cred[0]) + data.append([cred[0], cred[1], cred[2], cred[3], cred[4]]) + + print_table(data, title="Credential(s)") + + def help_creds(self): + help_string = """ + creds [add|remove|plaintext|hash|filter_term] + By default prints all creds + Table format: + | 'CredID', 'CredType', 'Domain', 'UserName', 'Password' | + Subcommands: + add - format: "add domain username password " + remove - format: "remove " + plaintext - prints plaintext creds + hash - prints hashed creds + filter_term - filters creds with filter_term + If a single credential is returned (e.g. `creds 15`, it prints the following tables: + Credential(s) | 'CredID', 'CredType', 'Domain', 'UserName', 'Password' + Otherwise, it prints the default credential table from a `like` query on the `username` column + """ + print_help(help_string) + def do_clear_database(self, line): if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y": self.db.clear_database()