Skip to content
Closed
93 changes: 87 additions & 6 deletions nxc/protocols/mssql.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,34 @@ def __init__(self, args, db, host):
self.lmhash = ""
self.nthash = ""
self.is_mssql = False

connection.__init__(self, args, db, host)
self.sqlbrowser_enabled = False
self.sqlbrowser_logger = NXCAdapter(
extra={
"protocol": "SQLBROWSER",
"host": host,
"port": "1434",
"hostname": "None",
}
)
# --browser
if args.browser:
args.port = 0 # Override port number with dummy one
connection.__init__(self, args, db, host)
self.discover_sqlbrowser()

if args.browser == "all":
for instance in self.mssql_instances:
self.instance_connect(instance)
else:
try:
index = int(args.browser)
instance = self.mssql_instances[index]
self.instance_connect(instance)
except ValueError:
self.sqlbrowser_logger.fail("Instance argument must be an integer index or 'all'")
return
else:
connection.__init__(self, args, db, host)

def proto_logger(self):
self.logger = NXCAdapter(
Expand All @@ -59,6 +85,7 @@ def proto_logger(self):

def create_conn_obj(self):
try:
# TODO: handle named pipes once supported by impacket
self.conn = tds.MSSQL(self.host, self.port, self.remoteName)
self.conn.connect(self.args.mssql_timeout)
except Exception as e:
Expand All @@ -71,13 +98,21 @@ def create_conn_obj(self):
return True

def reconnect_mssql(func):

def wrapper(self, *args, **kwargs):
with contextlib.suppress(Exception):
self.conn.disconnect()
self.create_conn_obj()
return func(self, *args, **kwargs)
return wrapper

def reconnect_sqlbrowser(func):
def wrapper(self, *args, **kwargs):
with contextlib.suppress(Exception):
self.sqlbrowser_conn.disconnect()
return func(self, *args, **kwargs)
return wrapper

def check_if_admin(self):
self.admin_privs = False
try:
Expand Down Expand Up @@ -116,9 +151,6 @@ def enum_host_info(self):
login["SSPI"] = auth.getData()
login["Length"] = len(login.getData())

# Get number of mssql instance
self.mssql_instances = self.conn.getInstances(0)

# Send the NTLMSSP Negotiate or SQL Auth Packet
self.conn.sendTDS(tds.TDS_LOGIN7, login.getData())

Expand All @@ -139,7 +171,8 @@ def enum_host_info(self):
self.hostname = ntlm_info["hostname"]
self.server_os = ntlm_info["os_version"]
self.logger.extra["hostname"] = self.hostname
self.db.add_host(self.host, self.hostname, self.targetDomain, self.server_os, len(self.mssql_instances),)

self.db.add_host(self.host, self.hostname, self.targetDomain, self.server_os, ",".join(str(instance for instance in self.mssql_instances)))

if self.args.domain:
self.domain = self.args.domain
Expand Down Expand Up @@ -605,3 +638,51 @@ def lsa(self):
)
LSA.dumpCachedHashes()
LSA.dumpSecrets()

def log_instance(self, index, instance):
instance_name = instance.get("InstanceName")
instance_port = instance.get("tcp", None)
instance_np = instance.get("np", None)
instance_version = instance.get("Version", None)
self.sqlbrowser_logger.success(f"#{index} {instance_name} (port:{instance_port}) (np:{instance_np}) (version:{instance_version})")

def discover_sqlbrowser(self):
self.sqlbrowser_conn = tds.MSSQL(self.host, 0, self.remoteName)
# No need to start the connection, we just need the tds object

# Ignore broadcast targets (UDP)
if self.host.endswith(".255"):
self.sqlbrowser_logger.debug("Target is a broadcast address, skipping SQL browser enumeration")
self.sqlbrowser_enabled = False
else:
self.sqlbrowser_logger.debug("Listing SQL browser instances")
self.mssql_instances = self.sqlbrowser_conn.getInstances(2)
if len(self.mssql_instances) > 0:
self.sqlbrowser_enabled = True
self.sqlbrowser_logger.success("SQL browser is enabled.")
for index, instance in enumerate(self.mssql_instances):
if self.args.browser == "all" or (self.args.browser.isdigit() and int(self.args.browser) == index):
self.log_instance(index, instance)
else:
self.sqlbrowser_enabled = False

self.sqlbrowser_conn.disconnect()

return self.sqlbrowser_enabled

@reconnect_sqlbrowser
def instance_connect(self, instance):
self.sqlbrowser_logger.debug(f"instance_connect to {instance}")
instance_port = instance.get("tcp", None)
instance_np = instance.get("np", None)
instance_arguments = self.args
# Drop instances and list-instances arguments
instance_arguments.instance = None
instance_arguments.browser = False
# Case #1, instance is listening on a port
if instance_port:
instance_arguments.port = instance_port
self.__init__(instance_arguments, self.db, self.host)
# Case #2, instance is listening on a named pipe
elif instance_np:
self.sqlbrowser_logger.fail(f"Named pipe connections are not supported yet, cannot connect to {instance_np}")
1 change: 1 addition & 0 deletions nxc/protocols/mssql/proto_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ def proto_args(parser, parents):

mapping_enum_group = mssql_parser.add_argument_group("Mapping/Enumeration")
mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs")
mapping_enum_group.add_argument("--browser", type=str, nargs="?", const="all", help="Enumerate or connect to MSSQL instances via SQL Browser. Use --browser <index> to connect to a specific instance, or --browser all (default) to connect to all instances")
return parser
4 changes: 4 additions & 0 deletions tests/e2e_commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M aws-cr
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex
netexec {DNS} mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --rid-brute
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --browser
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --browser all
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --browser 0
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --browser
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --database
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --lsa
Expand Down