diff --git a/nxc/config.py b/nxc/config.py index 477bdaf5c2..79065b0375 100644 --- a/nxc/config.py +++ b/nxc/config.py @@ -39,6 +39,9 @@ host_info_colors = literal_eval(nxc_config.get("nxc", "host_info_colors", fallback=["green", "red", "yellow", "cyan"])) check_guest_account = nxc_config.getboolean("nxc", "check_guest_account", fallback=False) +# MSSQL required options +discover_sql_browser = nxc_config.getboolean("MSSQL", "discover_sql_browser", fallback=True) + if len(host_info_colors) != 4: nxc_logger.error("Config option host_info_colors must have 4 values! Using default values.") host_info_colors = nxc_default_config.get("nxc", "host_info_colors") diff --git a/nxc/data/nxc.conf b/nxc/data/nxc.conf index 6eb6e50094..c1303c1ad5 100755 --- a/nxc/data/nxc.conf +++ b/nxc/data/nxc.conf @@ -8,6 +8,9 @@ log_mode = False host_info_colors = ["green", "red", "yellow", "cyan"] check_guest_account = False +[MSSQL] +discover_sql_browser = True + [BloodHound] bh_enabled = False bh_uri = 127.0.0.1 diff --git a/nxc/parsers/ip.py b/nxc/parsers/ip.py index 7106b83c42..4e92d3c75e 100755 --- a/nxc/parsers/ip.py +++ b/nxc/parsers/ip.py @@ -19,7 +19,7 @@ def parse_targets(target): if ip_interface(target).ip.version == 6 and ip_address(target).is_link_local: yield str(target) else: - for ip in ip_network(target, strict=False): + for ip in ip_network(target, strict=False).hosts(): yield str(ip) except ValueError: yield str(target) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 4fc40e98d3..c9136d0a40 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -3,7 +3,7 @@ import contextlib from termcolor import colored -from nxc.config import process_secret, host_info_colors +from nxc.config import process_secret, host_info_colors, discover_sql_browser from nxc.connection import connection from nxc.connection import requires_admin from nxc.helpers.misc import gen_random_string @@ -43,6 +43,8 @@ def __init__(self, args, db, host): self.os_arch = None self.lmhash = "" self.nthash = "" + self.is_mssql = False + self.sqlbrowser_enabled = False self.no_ntlm = False connection.__init__(self, args, db, host) @@ -59,10 +61,22 @@ def proto_logger(self): def create_conn_obj(self): try: + # Connects to default port or --port self.conn = tds.MSSQL(self.host, self.port, self.remoteName) self.conn.connect(self.args.mssql_timeout) + except Exception as e: self.logger.debug(f"Error connecting to MSSQL service on host: {self.host}, reason: {e}") + + # Even if the TCP connection failed, we still check whether the SQL Browser is running + # This check is only run if the nxc.config discover_sql_browser is set to True + if discover_sql_browser: + # Ignore broadcast targets (UDP) + self.mssql_instances = self.conn.getInstances(self.args.mssql_timeout) + if len(self.mssql_instances) > 0: + self.sqlbrowser_enabled = True + self.list_instances() + with contextlib.suppress(Exception): self.conn.disconnect() return False @@ -116,7 +130,9 @@ def enum_host_info(self): login["Length"] = len(login.getData()) # Get number of mssql instance - self.mssql_instances = self.conn.getInstances(0) + self.mssql_instances = self.conn.getInstances(self.args.mssql_timeout) + if len(self.mssql_instances) > 0: + self.sqlbrowser_enabled = True # Send the NTLMSSP Negotiate or SQL Auth Packet self.conn.sendTDS(tds.TDS_LOGIN7, login.getData()) @@ -163,8 +179,9 @@ def enum_host_info(self): def print_host_info(self): encryption = colored(f"EncryptionReq:{self.encryption}", host_info_colors[0 if self.encryption else 1], attrs=["bold"]) + sqlbrowser = colored(f"SqlBrowser:{self.sqlbrowser_enabled}", host_info_colors[0 if self.sqlbrowser_enabled else 1], attrs=["bold"]) ntlm = colored(f"(NTLM:{not self.no_ntlm})", host_info_colors[2], attrs=["bold"]) if self.no_ntlm else "" - self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain}) ({encryption}) {ntlm}") + self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain}) ({encryption}) ({sqlbrowser}) ({ntlm})") @reconnect_mssql def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): @@ -285,6 +302,23 @@ def hash_login(self, domain, username, ntlm_hash): self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.nthash)} {error_msg if error_msg else ''}") return False + def list_instances(self): + if self.sqlbrowser_enabled is False: + self.logger.fail("MSSQL browser is not enabled, cannot enumerate...") + return + + if len(self.mssql_instances) > 0: + self.logger.display("SQL Browser is enabled, listing instances :") + # Get information about instances + for index, instance in enumerate(self.mssql_instances): + instance_name = instance.get("InstanceName") + instance_port = instance.get("tcp", None) + instance_np = instance.get("np", None) + instance_version = instance.get("Version", None) + self.logger.success(f"#{index} {instance_name} (port:{instance_port}) (np:{instance_np}) (version:{instance_version})") + else: + self.logger.fail("No instance to enumerate") + def query(self): if self.conn.lastError: # Invalid connection diff --git a/nxc/protocols/mssql/proto_args.py b/nxc/protocols/mssql/proto_args.py index 9d494ed44e..828ac7bfea 100644 --- a/nxc/protocols/mssql/proto_args.py +++ b/nxc/protocols/mssql/proto_args.py @@ -8,6 +8,7 @@ def proto_args(parser, parents): mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout", type=int, default=5) mssql_parser.add_argument("-q", "--query", metavar="QUERY", type=str, help="execute the specified query against the mssql db") mssql_parser.add_argument("--database", nargs="?", const=True, metavar="NAME", help="list databases or list tables for NAME") + mssql_parser.add_argument("--list-instances", action="store_true", default=False, help="Lists availables MSSQL instances over the SQL browser") dgroup = mssql_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain name")