From 28b6ffb21143881b86a068c50d1edee613fb7853 Mon Sep 17 00:00:00 2001 From: Thorsten Sick Date: Wed, 12 May 2021 12:10:11 +0200 Subject: [PATCH] added verbosity switch --- app/attack_log.py | 22 +++++- app/calderacontrol.py | 31 ++++---- app/experimentcontrol.py | 79 ++++++++++--------- app/machinecontrol.py | 72 +++++++++-------- app/pluginmanager.py | 8 +- caldera_control.py | 6 +- experiment_control.py | 3 +- machine_control.py | 8 +- plugin_manager.py | 10 ++- plugins/base/kali.py | 14 ---- plugins/base/machinery.py | 4 +- plugins/base/plugin_base.py | 45 +++++++++-- plugins/base/ssh_features.py | 32 ++++---- .../vm_controller/vagrant/vagrant_plugin.py | 2 +- .../linux_sshd_config_issues.py | 4 - .../weak_user_passwords.py | 6 -- .../windows_rdp_config/windows_rdp_config.py | 4 - 17 files changed, 201 insertions(+), 149 deletions(-) diff --git a/app/attack_log.py b/app/attack_log.py index 68b2a4f..000c74c 100644 --- a/app/attack_log.py +++ b/app/attack_log.py @@ -25,8 +25,13 @@ def __mitre_fix_ttp__(ttp): class AttackLog(): """ A specific logger class to log the progress of the attack steps """ - def __init__(self): + def __init__(self, verbosity=0): + """ + + @param verbosity: verbosity setting from 0 to 3 for stdout printing + """ self.log = [] + self.verbosity = verbosity def start_caldera_attack(self, source, paw, group, ability_id, ttp=None, name=None, description=None): # pylint: disable=too-many-arguments """ Mark the start of a caldera attack @@ -141,3 +146,18 @@ class AttackLog(): """ Return logged data in dict format """ return self.log + + def vprint(self, text, verbosity): + """ verbosity based stdout printing + + 0: Errors only + 1: Main colored information + 2: Detailed progress information + 3: Debug logs, data dumps, everything + + @param text: The text to print + @param verbosity: the verbosity level the text has. + """ + + if verbosity <= self.verbosity: + print(text) diff --git a/app/calderacontrol.py b/app/calderacontrol.py index 3a5782d..3ccdf9e 100644 --- a/app/calderacontrol.py +++ b/app/calderacontrol.py @@ -23,15 +23,16 @@ from pprint import pprint class CalderaControl(): """ Remote control Caldera through REST api """ - def __init__(self, server, config=None, apikey=None): + def __init__(self, server, attack_logger, config=None, apikey=None): """ - @param server: Caldera server url/ip + @param attack_logger: The attack logger to use @param config: The configuration """ # print(server) self.url = server if server.endswith("/") else server + "/" + self.attack_logger = attack_logger self.config = config @@ -516,28 +517,28 @@ class CalderaControl(): # ##### Create / Run Operation - print(f"New adversary generated. ID: {adid}, ability: {ability_id} group: {group}") + self.attack_logger.vprint(f"New adversary generated. ID: {adid}, ability: {ability_id} group: {group}", 2) res = self.add_operation(operation_name, advid=adid, group=group) - print("Add operation: ") - pprint(res) + # print("Add operation: ") + # pprint(res) opid = self.get_operation(operation_name)["id"] - print("New operation created. OpID: " + str(opid)) + self.attack_logger.vprint("New operation created. OpID: " + str(opid), 3) self.execute_operation(opid) - print("Execute operation") + self.attack_logger.vprint("Execute operation",3 ) retries = 30 ability_name = self.get_ability(ability_id)[0]["name"] ability_description = self.get_ability(ability_id)[0]["description"] - print(f"{CommandlineColors.OKBLUE}Executed attack operation{CommandlineColors.ENDC}") - print(f"{CommandlineColors.BACKGROUND_BLUE} PAW: {paw} Group: {group} Ability: {ability_id} {CommandlineColors.ENDC}") - print(f"{CommandlineColors.BACKGROUND_BLUE} {ability_name}: {ability_description} {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Executed attack operation{CommandlineColors.ENDC}",1) + self.attack_logger.vprint(f"{CommandlineColors.BACKGROUND_BLUE} PAW: {paw} Group: {group} Ability: {ability_id} {CommandlineColors.ENDC}", 1) + self.attack_logger.vprint(f"{CommandlineColors.BACKGROUND_BLUE} {ability_name}: {ability_description} {CommandlineColors.ENDC}", 1) while not self.is_operation_finished(opid) and retries > 0: - print(f".... waiting for Caldera to finish {retries}") + self.attack_logger.vprint(f".... waiting for Caldera to finish {retries}", 2) time.sleep(10) retries -= 1 if retries <= 0: - print(f"{CommandlineColors.FAIL}Ran into retry timeout waiting for attack to finish{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.FAIL}Ran into retry timeout waiting for attack to finish{CommandlineColors.ENDC}", 1) # TODO: Handle outout from several clients @@ -548,7 +549,7 @@ class CalderaControl(): retries -= 1 time.sleep(10) output = self.view_operation_output(opid, paw, ability_id) - print(f".... getting Caldera output {retries}") + self.attack_logger.vprint(f".... getting Caldera output {retries}", 2) if output: break except CalderaError: @@ -556,10 +557,10 @@ class CalderaControl(): if output is None: output = str(self.get_operation_by_id(opid)) - print(f"{CommandlineColors.FAIL}Failed getting operation data. We just have: {output} from get_operation_by_id{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.FAIL}Failed getting operation data. We just have: {output} from get_operation_by_id{CommandlineColors.ENDC}", 0) else: outp = str(output) - print(f"{CommandlineColors.BACKGROUND_GREEN} Output: {outp} {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.BACKGROUND_GREEN} Output: {outp} {CommandlineColors.ENDC}", 2) pprint(output) # ######## Cleanup diff --git a/app/experimentcontrol.py b/app/experimentcontrol.py index 675b8cd..ca6e082 100644 --- a/app/experimentcontrol.py +++ b/app/experimentcontrol.py @@ -22,21 +22,22 @@ from app.exceptions import ServerError class Experiment(): """ Class handling experiments """ - def __init__(self, configfile): + def __init__(self, configfile, verbosity=0): """ - @param configfile: Path to the configfile to load """ - + @param configfile: Path to the configfile to load + @param verbosity: verbosity level between 0 and 3 + """ self.attacker_1 = None self.experiment_control = ExperimentConfig(configfile) - self.attack_logger = AttackLog() + self.attack_logger = AttackLog(verbosity) self.__start_attacker() caldera_url = "http://" + self.attacker_1.getip() + ":8888" - caldera_control = CalderaControl(caldera_url, config=self.experiment_control) + caldera_control = CalderaControl(caldera_url, attack_logger=self.attack_logger, config=self.experiment_control) # Deleting all currently registered Caldera gents - print(caldera_control.kill_all_agents()) - print(caldera_control.delete_all_agents()) + self.attack_logger.vprint(caldera_control.kill_all_agents(), 3) + self.attack_logger.vprint(caldera_control.delete_all_agents(), 3) self.starttime = datetime.now().strftime("%Y_%m_%d___%H_%M_%S") self.lootdir = os.path.join(self.experiment_control.loot_dir(), self.starttime) @@ -50,8 +51,8 @@ class Experiment(): tname = target_conf.vmname() - print(f"{CommandlineColors.OKBLUE}preparing target {tname} ....{CommandlineColors.ENDC}") - target_1 = Machine(target_conf) + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}preparing target {tname} ....{CommandlineColors.ENDC}", 1) + target_1 = Machine(target_conf, attack_logger=self.attack_logger) target_1.set_caldera_server(self.attacker_1.getip()) try: if not target_conf.use_existing_machine(): @@ -64,43 +65,43 @@ class Experiment(): needs_reboot = target_1.prime_sensors() if needs_reboot: target_1.reboot() - print(f"{CommandlineColors.OKGREEN}Target is up: {tname} {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Target is up: {tname} {CommandlineColors.ENDC}",1) self.targets.append(target_1) # Install vulnerabilities for a_target in self.targets: - print(f"Installing vulnerabilities on {a_target.get_paw()}") + self.attack_logger.vprint(f"Installing vulnerabilities on {a_target.get_paw()}",2) a_target.install_vulnerabilities() a_target.start_vulnerabilities() # Install sensor plugins for a_target in self.targets: - print(f"Installing sensors on {a_target.get_paw()}") + self.attack_logger.vprint(f"Installing sensors on {a_target.get_paw()}",2) a_target.install_sensors() a_target.start_sensors() # First start of caldera implants for target_1 in self.targets: target_1.start_caldera_client() - print(f"{CommandlineColors.OKGREEN}Initial start of caldera client: {tname} {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Initial start of caldera client: {tname} {CommandlineColors.ENDC}", 1) time.sleep(20) # Wait for all the clients to contact the caldera server - print(f"{CommandlineColors.OKBLUE}Contacting caldera agents on all targets ....{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Contacting caldera agents on all targets ....{CommandlineColors.ENDC}", 1) # Wait until all targets are registered as Caldera targets for target_1 in self.targets: running_agents = caldera_control.list_paws_of_running_agents() - print(f"Agents currently running: {running_agents}") + self.attack_logger.vprint(f"Agents currently running: {running_agents}", 2) while target_1.get_paw() not in running_agents: - print(f"Connecting to caldera {caldera_url}, running agents are: {running_agents}") - print(f"Missing agent: {target_1.get_paw()} ...") + self.attack_logger.vprint(f"Connecting to caldera {caldera_url}, running agents are: {running_agents}", 3) + self.attack_logger.vprint(f"Missing agent: {target_1.get_paw()} ...", 3) target_1.start_caldera_client() - print(f"Restarted caldera agent: {target_1.get_paw()} ...") + self.attack_logger.vprint(f"Restarted caldera agent: {target_1.get_paw()} ...", ) time.sleep(120) # Was 30, but maybe there are timing issues running_agents = caldera_control.list_paws_of_running_agents() - print(f"{CommandlineColors.OKGREEN}Caldera agents reached{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Caldera agents reached{CommandlineColors.ENDC}", 1) # Attack them - print(f"{CommandlineColors.OKBLUE}Running Caldera attacks{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Running Caldera attacks{CommandlineColors.ENDC}",1 ) for target_1 in self.targets: # Run caldera attacks caldera_attacks = self.experiment_control.get_caldera_attacks(target_1.get_os()) @@ -108,8 +109,8 @@ class Experiment(): for attack in caldera_attacks: # TODO: Work with snapshots # TODO: If we have several targets in the same group, it is nonsense to attack each one separately. Make this smarter - print(f"Attacking machine with PAW: {target_1.get_paw()} with {attack}") - caldera_control = CalderaControl("http://" + self.attacker_1.getip() + ":8888", config=self.experiment_control) + self.attack_logger.vprint(f"Attacking machine with PAW: {target_1.get_paw()} with {attack}", 2) + caldera_control = CalderaControl("http://" + self.attacker_1.getip() + ":8888", self.attack_logger, config=self.experiment_control) caldera_control.attack(attack_logger=self.attack_logger, paw=target_1.get_paw(), ability_id=attack, group=target_1.get_group()) @@ -119,38 +120,38 @@ class Experiment(): # Fix: Caldera sometimes gets stuck. This is why we better re-start the caldera server and wait till all the implants re-connected # Reason: In some scenarios we keep the infra up for hours or days. No re-creation like intended. This can cause Caldera to hick up - print(f"{CommandlineColors.OKBLUE}Restarting caldera server and waiting for clients to re-connect{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Restarting caldera server and waiting for clients to re-connect{CommandlineColors.ENDC}", 1) self.attacker_1.start_caldera_server() - print(f"Pausing before next attack (config: nap_time): {self.experiment_control.get_nap_time()}") + self.attack_logger.vprint(f"Pausing before next attack (config: nap_time): {self.experiment_control.get_nap_time()}", 2) time.sleep(self.experiment_control.get_nap_time()) retries = 100 for target_system in self.targets: running_agents = caldera_control.list_paws_of_running_agents() - print(f"Agents currently connected to the server: {running_agents}") + self.attack_logger.vprint(f"Agents currently connected to the server: {running_agents}", 2) while target_system.get_paw() not in running_agents: time.sleep(1) running_agents = caldera_control.list_paws_of_running_agents() retries -= 1 - print(f"Waiting for clients to re-connect ({retries}, {running_agents}) ") + self.attack_logger.vprint(f"Waiting for clients to re-connect ({retries}, {running_agents}) ", 3) if retries <= 0: raise ServerError - print(f"{CommandlineColors.OKGREEN}Restarted caldera server clients re-connected{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Restarted caldera server clients re-connected{CommandlineColors.ENDC}", 1) # End of fix - print(f"{CommandlineColors.OKGREEN}Finished Caldera attacks{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Finished Caldera attacks{CommandlineColors.ENDC}", 1) # Run Kali attacks - print(f"{CommandlineColors.OKBLUE}Running Kali attacks{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Running Kali attacks{CommandlineColors.ENDC}", 1) for target_1 in self.targets: kali_attacks = self.experiment_control.get_kali_attacks(target_1.get_os()) for attack in kali_attacks: # TODO: Work with snapshots - print(f"Attacking machine with PAW: {target_1.get_paw()} with attack: {attack}") + self.attack_logger.vprint(f"Attacking machine with PAW: {target_1.get_paw()} with attack: {attack}", 1) self.attacker_1.kali_attack(attack, target_1.getip(), self.experiment_control) - print(f"Pausing before next attack (config: nap_time): {self.experiment_control.get_nap_time()}") + self.attack_logger.vprint(f"Pausing before next attack (config: nap_time): {self.experiment_control.get_nap_time()}", 3) time.sleep(self.experiment_control.get_nap_time()) - print(f"{CommandlineColors.OKGREEN}Finished Kali attacks{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Finished Kali attacks{CommandlineColors.ENDC}", 1) # Stop sensor plugins # Collect data @@ -160,8 +161,10 @@ class Experiment(): # Uninstall vulnerabilities for a_target in self.targets: - print(f"Uninstalling vulnerabilities on {a_target.get_paw()}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE} Uninstalling vulnerabilities on {a_target.get_paw()} {CommandlineColors.ENDC}", 1) a_target.stop_vulnerabilities() + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN} Done uninstalling vulnerabilities on {a_target.get_paw()} {CommandlineColors.ENDC}", + 1) # Stop target machines for target_1 in self.targets: @@ -181,14 +184,14 @@ class Experiment(): ] - print(f"Creating zip file {filename}") + self.attack_logger.vprint(f"Creating zip file {filename}", 1) with zipfile.ZipFile(filename, "w") as zfh: for a_glob in globs: a_glob = self.lootdir + a_glob for a_file in glob.iglob(a_glob, recursive=True): if a_file != filename: - print(a_file) + self.attack_logger.vprint(a_file, 2) zfh.write(a_file) @staticmethod @@ -226,13 +229,13 @@ class Experiment(): except FileExistsError: pass for a_file in self.__get_results_files(root): - print("Copy {} {}".format(a_file, os.path.abspath(self.experiment_control.loot_dir()))) + self.attack_logger.vprint("Copy {} {}".format(a_file, os.path.abspath(self.experiment_control.loot_dir())), 3) def __start_attacker(self): """ Start the attacking VM """ # Preparing attacker - self.attacker_1 = Machine(self.experiment_control.attacker(0).raw_config) + self.attacker_1 = Machine(self.experiment_control.attacker(0).raw_config, attack_logger=self.attack_logger) if not self.experiment_control.attacker(0).use_existing_machine(): try: @@ -248,7 +251,7 @@ class Experiment(): self.attacker_1.install_caldera_server(cleanup=False) self.attacker_1.start_caldera_server() - self.attacker_1.set_attack_logger(self.attack_logger) + # self.attacker_1.set_attack_logger(self.attack_logger) def __stop_attacker(self): """ Stop the attacking VM """ diff --git a/app/machinecontrol.py b/app/machinecontrol.py index 1dc23ed..f8b6f14 100644 --- a/app/machinecontrol.py +++ b/app/machinecontrol.py @@ -21,22 +21,24 @@ from plugins.base.vulnerability_plugin import VulnerabilityPlugin class Machine(): """ A virtual machine. Attacker or target. Abstracting stuff away. """ - def __init__(self, config, calderakey="ADMIN123"): + def __init__(self, config, attack_logger, calderakey="ADMIN123",): """ @param config: The machine configuration as dict + @param attack_logger: The attack logger to use @param calderakey: Key to the caldera controller """ self.vm_manager = None self.attack_logger = None + self.set_attack_logger(attack_logger) if isinstance(config, MachineConfig): self.config = config else: self.config = MachineConfig(config) - self.plugin_manager = PluginManager() + self.plugin_manager = PluginManager(self.attack_logger) # TODO: Read config from plugin if self.config.vmcontroller() == "vagrant": @@ -111,7 +113,7 @@ class Machine(): while not res: time.sleep(5) res = self.vm_manager.__call_connect__() - print("Re-connecting....") + self.attack_logger.vprint("Re-connecting....", 3) def up(self): # pylint: disable=invalid-name """ Starts a VM. Creates it if not already created """ @@ -159,13 +161,13 @@ class Machine(): for plugin in self.plugin_manager.get_plugins(KaliPlugin, [attack]): name = plugin.get_name() - print(f"{CommandlineColors.OKBLUE}Running Kali plugin {name}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Running Kali plugin {name}{CommandlineColors.ENDC}", 2) syscon = {"abs_machinepath_internal": self.abs_machinepath_internal, "abs_machinepath_external": self.abs_machinepath_external} plugin.set_sysconf(syscon) - plugin.process_config(config.kali_conf(name)) + plugin.process_config(config.kali_conf(plugin.get_config_section_name())) plugin.set_machine_plugin(self.vm_manager) - plugin.__set_logger__(self.attack_logger) + # plugin.__set_logger__(self.attack_logger) plugin.__execute__([target]) def load_machine_plugin(self): @@ -174,13 +176,15 @@ class Machine(): for plugin in self.plugin_manager.get_plugins(MachineryPlugin, [self.config.vmcontroller()]): name = plugin.get_name() - print(f"{CommandlineColors.OKBLUE}Installing machinery: {name}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Installing machinery: {name}{CommandlineColors.ENDC}", 1) syscon = {"abs_machinepath_internal": self.abs_machinepath_internal, "abs_machinepath_external": self.abs_machinepath_external} plugin.set_sysconf(syscon) plugin.__call_process_config__(self.config) self.vm_manager = plugin + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Installed machinery: {name}{CommandlineColors.ENDC}", + 1) break def prime_sensors(self): @@ -195,7 +199,7 @@ class Machine(): for plugin in self.plugin_manager.get_plugins(SensorPlugin, self.config.sensors()): name = plugin.get_name() # if name in self.config.sensors(): - print(f"{CommandlineColors.OKBLUE}Priming sensor: {name}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Priming sensor: {name}{CommandlineColors.ENDC}", 2) syscon = {"abs_machinepath_internal": self.abs_machinepath_internal, "abs_machinepath_external": self.abs_machinepath_external, } @@ -206,7 +210,7 @@ class Machine(): plugin.setup() reboot |= plugin.prime() self.sensors.append(plugin) - print(f"{CommandlineColors.OKGREEN}Primed sensor: {name}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Primed sensor: {name}{CommandlineColors.ENDC}", 2) return reboot def install_sensors(self): @@ -219,7 +223,7 @@ class Machine(): for plugin in self.get_sensors(): name = plugin.get_name() - print(f"{CommandlineColors.OKBLUE}Installing sensor: {name}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Installing sensor: {name}{CommandlineColors.ENDC}", 2) syscon = {"abs_machinepath_internal": self.abs_machinepath_internal, "abs_machinepath_external": self.abs_machinepath_external, } @@ -228,7 +232,7 @@ class Machine(): plugin.process_config(self.config.raw_config.get(name, {})) # plugin specific configuration plugin.setup() plugin.install() - print(f"{CommandlineColors.OKGREEN}Installed sensor: {name}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Installed sensor: {name}{CommandlineColors.ENDC}", 2) def get_sensors(self) -> [SensorPlugin]: """ Returns a list of running sensors """ @@ -241,10 +245,10 @@ class Machine(): """ for plugin in self.get_sensors(): - print(f"{CommandlineColors.OKBLUE}Starting sensor: {plugin.get_name()}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Starting sensor: {plugin.get_name()}{CommandlineColors.ENDC}", 2) plugin.set_machine_plugin(self.vm_manager) plugin.start() - print(f"{CommandlineColors.OKGREEN}Started sensor: {plugin.get_name()}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Started sensor: {plugin.get_name()}{CommandlineColors.ENDC}", 2) def stop_sensors(self): """ Stop sensors @@ -254,10 +258,10 @@ class Machine(): """ for plugin in self.get_sensors(): - print(f"{CommandlineColors.OKBLUE}Stopping sensor: {plugin.get_name()}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Stopping sensor: {plugin.get_name()}{CommandlineColors.ENDC}", 2) plugin.set_machine_plugin(self.vm_manager) plugin.stop() - print(f"{CommandlineColors.OKGREEN}Stopped sensor: {plugin.get_name()}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Stopped sensor: {plugin.get_name()}{CommandlineColors.ENDC}", 2) def collect_sensors(self, lootdir): """ Collect data from sensors @@ -271,10 +275,10 @@ class Machine(): os.mkdir(machine_specific_path) for plugin in self.get_sensors(): - print(f"{CommandlineColors.OKBLUE}Collecting sensor: {plugin.get_name()}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Collecting sensor: {plugin.get_name()}{CommandlineColors.ENDC}",2 ) plugin.set_machine_plugin(self.vm_manager) plugin.__call_collect__(machine_specific_path) - print(f"{CommandlineColors.OKGREEN}Collected sensor: {plugin.get_name()}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Collected sensor: {plugin.get_name()}{CommandlineColors.ENDC}",2 ) ############ @@ -287,8 +291,8 @@ class Machine(): for plugin in self.plugin_manager.get_plugins(VulnerabilityPlugin, self.config.vulnerabilities()): name = plugin.get_name() - print(f"Configured vulnerabilities: {self.config.vulnerabilities()}") - print(f"{CommandlineColors.OKBLUE}Installing vulnerability: {name}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"Configured vulnerabilities: {self.config.vulnerabilities()}",3 ) + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Installing vulnerability: {name}{CommandlineColors.ENDC}", 2) syscon = {"abs_machinepath_internal": self.abs_machinepath_internal, "abs_machinepath_external": self.abs_machinepath_external} plugin.set_sysconf(syscon) @@ -309,7 +313,7 @@ class Machine(): """ for plugin in self.get_vulnerabilities(): - print(f"{CommandlineColors.OKBLUE}Activating vulnerability: {plugin.get_name()}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Activating vulnerability: {plugin.get_name()}{CommandlineColors.ENDC}",2 ) plugin.set_machine_plugin(self.vm_manager) plugin.start() @@ -320,7 +324,7 @@ class Machine(): """ for plugin in self.get_vulnerabilities(): - print(f"{CommandlineColors.OKBLUE}Uninstalling vulnerability: {plugin.get_name()}{CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Uninstalling vulnerability: {plugin.get_name()}{CommandlineColors.ENDC}", 2) plugin.set_machine_plugin(self.vm_manager) plugin.stop() @@ -342,7 +346,7 @@ class Machine(): @param version: Caldera version to use. Check Caldera git for potential branches to use """ # https://github.com/mitre/caldera.git - print(f"{CommandlineColors.OKBLUE}Installing Caldera server {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Installing Caldera server {CommandlineColors.ENDC}", 1) if cleanup: cleanupcmd = "rm -rf caldera;" @@ -350,7 +354,7 @@ class Machine(): cleanupcmd = "" cmd = f"cd {self.caldera_basedir}; {cleanupcmd} git clone https://github.com/mitre/caldera.git --recursive --branch {version}; cd caldera; pip3 install -r requirements.txt" - print(f"{CommandlineColors.OKGREEN}Caldera server installed {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Caldera server installed {CommandlineColors.ENDC}", 1) res = self.vm_manager.__call_remote_run__(cmd) return "Result installing caldera server " + str(res) @@ -362,14 +366,14 @@ class Machine(): for i in range(timeout): time.sleep(10) caldera_url = "http://" + self.getip() + ":8888" - caldera_control = CalderaControl(caldera_url, apikey=self.calderakey) - print(f"{i} Trying to connect to {caldera_url} Caldera API") + caldera_control = CalderaControl(caldera_url, self.attack_logger, apikey=self.calderakey) + self.attack_logger.vprint(f"{i} Trying to connect to {caldera_url} Caldera API", 3) try: caldera_control.list_adversaries() except requests.exceptions.ConnectionError: pass else: - print("Caldera: All systems nominal") + self.attack_logger.vprint("Caldera: All systems nominal", 3) return True raise ServerError @@ -377,14 +381,14 @@ class Machine(): """ Start the caldera server on the VM. Required for an attacker VM """ # https://github.com/mitre/caldera.git - print(f"{CommandlineColors.OKBLUE}Starting Caldera server {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Starting Caldera server {CommandlineColors.ENDC}", 1) # The pkill was added because the server sometimes gets stuck. And we can not re-create the attacking machines in all cases self.vm_manager.__call_remote_run__(" pkill -f server.py;", disown=False) cmd = f"cd {self.caldera_basedir}; cd caldera ; nohup python3 server.py --insecure &" self.vm_manager.__call_remote_run__(cmd, disown=True) self.wait_for_caldera_server() - print(f"{CommandlineColors.OKGREEN}Caldera server started. Confirmed it is running. {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Caldera server started. Confirmed it is running. {CommandlineColors.ENDC}", 1) def create_start_caldera_client_cmd(self): """ Creates a command to start the caldera client """ @@ -410,11 +414,11 @@ class Machine(): """ Install caldera client. Required on targets """ name = self.vm_manager.get_vm_name() - print(f"{CommandlineColors.OKBLUE}Starting Caldera client {name} {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Starting Caldera client {name} {CommandlineColors.ENDC}", 1) if self.get_os() == "windows": url = "http://" + self.caldera_server + ":8888" - caldera_control = CalderaControl(url, apikey=self.calderakey) + caldera_control = CalderaControl(url, self.attack_logger, apikey=self.calderakey) caldera_control.fetch_client(platform="windows", file="sandcat.go", target_dir=self.abs_machinepath_external, @@ -440,7 +444,7 @@ class Machine(): print(cmd) self.vm_manager.remote_run(cmd, disown=True) - print(f"{CommandlineColors.OKGREEN}Caldera client started {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Caldera client started {CommandlineColors.ENDC}", 1) def get_os(self): """ Returns the OS of the machine """ @@ -484,7 +488,7 @@ nohup ./sandcat.go -server $server -group {self.config.caldera_group()} -v -paw else: playground = "" url = "http://" + self.caldera_server + ":8888" - caldera_control = CalderaControl(url, apikey=self.calderakey) + caldera_control = CalderaControl(url, self.attack_logger, apikey=self.calderakey) filename = caldera_control.fetch_client(platform="windows", file="sandcat.go", target_dir=self.abs_machinepath_external, @@ -502,7 +506,7 @@ START {playground}{filename} -server {url} -group {self.config.caldera_group()} content = self.__install_caldera_service_cmd() - print(f"{CommandlineColors.OKBLUE}Installing Caldera service {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Installing Caldera service {CommandlineColors.ENDC}", 1) if self.get_os() == "linux": filename = os.path.join(self.abs_machinepath_external, "caldera_agent.sh") @@ -510,7 +514,7 @@ START {playground}{filename} -server {url} -group {self.config.caldera_group()} filename = os.path.join(self.abs_machinepath_external, "caldera_agent.bat") with open(filename, "wt") as fh: fh.write(content) - print(f"{CommandlineColors.OKGREEN}Installed Caldera server {CommandlineColors.ENDC}") + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Installed Caldera service {CommandlineColors.ENDC}", 1) def set_caldera_server(self, server): """ Set the local caldera server config """ diff --git a/app/pluginmanager.py b/app/pluginmanager.py index 140c516..a40634b 100644 --- a/app/pluginmanager.py +++ b/app/pluginmanager.py @@ -26,8 +26,13 @@ sections = [{"name": "Vulnerabilities", class PluginManager(): """ Manage plugins """ - def __init__(self): + def __init__(self, attack_logger): + """ + + @param attack_logger: The attack logger to use + """ self.base = "plugins/**/*.py" + self.attack_logger = attack_logger def get_plugins(self, subclass, name_filter=None) -> [BasePlugin]: """ Returns a list plugins matching specified criteria @@ -53,6 +58,7 @@ class PluginManager(): handlers = get_handlers(plugins) for plugin in handlers: + plugin.set_logger(self.attack_logger) if name_filter is None: res.append(plugin) else: diff --git a/caldera_control.py b/caldera_control.py index e9a3291..2d8a326 100644 --- a/caldera_control.py +++ b/caldera_control.py @@ -5,6 +5,7 @@ import argparse from app.calderacontrol import CalderaControl +from app.attack_log import AttackLog # https://caldera.readthedocs.io/en/latest/The-REST-API.html @@ -74,6 +75,8 @@ def create_parser(): """ Creates the parser for the command line arguments""" main_parser = argparse.ArgumentParser("Controls a Caldera server to attack other systems") + main_parser.add_argument('--verbose', '-v', action='count', default=0) + subparsers = main_parser.add_subparsers(help="sub-commands") # Sub parser for attacks @@ -113,7 +116,8 @@ if __name__ == "__main__": print(args.caldera_url) - caldera_control = CalderaControl(args.caldera_url, config=None, apikey=args.apikey) + attack_logger = AttackLog(args.verbose) + caldera_control = CalderaControl(args.caldera_url, attack_logger, config=None, apikey=args.apikey) print("Caldera Control ready") str(args.func(caldera_control, args)) diff --git a/experiment_control.py b/experiment_control.py index f19423f..c98e6ee 100644 --- a/experiment_control.py +++ b/experiment_control.py @@ -20,7 +20,7 @@ def run(args): @param args: arguments from the argparse parser """ - Experiment(args.configfile) + Experiment(args.configfile, args.verbose) def create_parser(): @@ -29,6 +29,7 @@ def create_parser(): subparsers = parser.add_subparsers(help="sub-commands") parser.set_defaults(func=explain) + parser.add_argument('--verbose', '-v', action='count', default=0) # Sub parser for machine creation parser_run = subparsers.add_parser("run", help="run experiments") diff --git a/machine_control.py b/machine_control.py index cba28fa..d282315 100644 --- a/machine_control.py +++ b/machine_control.py @@ -7,6 +7,7 @@ import yaml from app.calderacontrol import CalderaControl from app.machinecontrol import Machine +from app.attack_log import AttackLog def create_machines(arguments): @@ -19,7 +20,8 @@ def create_machines(arguments): with open(arguments.configfile) as fh: config = yaml.safe_load(fh) - target_ = Machine(config["targets"]["target1"]) + attack_logger = AttackLog(arguments.verbose) + target_ = Machine(config["targets"]["target1"], attack_logger) attacker_1 = Machine(config["attackers"]["attacker"]) print("Got them") @@ -54,7 +56,8 @@ def download_caldera_client(arguments): @param arguments: The arguments from argparse """ - caldera_control = CalderaControl(args.ip, None) + attack_logger = AttackLog(arguments.verbose) + caldera_control = CalderaControl(args.ip, attack_logger, None) caldera_control.fetch_client(platform=arguments.platform, file=arguments.file, target_dir=arguments.target_dir, @@ -65,6 +68,7 @@ def create_parser(): """ Creates the parser for the command line arguments""" main_parser = argparse.ArgumentParser("Controls a Caldera server to attack other systems") + main_parser.add_argument('--verbose', '-v', action='count', default=0) subparsers = main_parser.add_subparsers(help="sub-commands") # Sub parser for machine creation diff --git a/plugin_manager.py b/plugin_manager.py index 43d8119..e45bdcf 100644 --- a/plugin_manager.py +++ b/plugin_manager.py @@ -3,17 +3,22 @@ import argparse from app.pluginmanager import PluginManager +from app.attack_log import AttackLog def list_plugins(arguments): """ List plugins """ - p = PluginManager() + + attack_logger = AttackLog(arguments.verbose) + p = PluginManager(attack_logger) p.print_list() def get_default_config(arguments): """ print default config of a specific plugin """ - p = PluginManager() + + attack_logger = AttackLog(arguments.verbose) + p = PluginManager(attack_logger) p.print_default_config(arguments.subclass_name, arguments.plugin_name) @@ -21,6 +26,7 @@ def create_parser(): """ Creates the parser for the command line arguments""" main_parser = argparse.ArgumentParser("Manage plugins") + main_parser.add_argument('--verbose', '-v', action='count', default=0) subparsers = main_parser.add_subparsers(help="sub-commands") # Sub parser for machine creation diff --git a/plugins/base/kali.py b/plugins/base/kali.py index 43ba51a..3016dc8 100644 --- a/plugins/base/kali.py +++ b/plugins/base/kali.py @@ -21,7 +21,6 @@ class KaliPlugin(BasePlugin): super().__init__() self.conf = {} # Plugin specific configuration self.sysconf = {} # System configuration. common for all plugins - self.attack_logger = None def teardown(self): """ Cleanup afterwards """ @@ -47,19 +46,6 @@ class KaliPlugin(BasePlugin): self.attack_logger.stop_kali_attack(self.machine_plugin.config.vmname(), targets, self.name, ttp=self.get_ttp()) return res - def command(self, targets, config): - """ Generate command - - @param targets: A list of targets, ip addresses will do - @param config: dict with command specific configuration - """ - - raise NotImplementedError - - def __set_logger__(self, attack_logger): - """ Set the attack logger for this machine """ - self.attack_logger = attack_logger - def get_ttp(self): """ Returns the ttp of the plugin, please set in boilerplate """ if self.ttp: diff --git a/plugins/base/machinery.py b/plugins/base/machinery.py index 3804bf2..e85b18f 100644 --- a/plugins/base/machinery.py +++ b/plugins/base/machinery.py @@ -117,9 +117,9 @@ class MachineryPlugin(BasePlugin): # This is the interface from the main code to the plugin system. Do not touch def __call_halt__(self): """ Wrapper around halt """ - print(f"{CommandlineColors.OKBLUE}Stopping machine: {self.config.vmname()} {CommandlineColors.ENDC}") + self.vprint(f"{CommandlineColors.OKBLUE}Stopping machine: {self.config.vmname()} {CommandlineColors.ENDC}", 1) self.halt() - print(f"{CommandlineColors.OKGREEN}Machine stopped: {self.config.vmname()}{CommandlineColors.ENDC}") + self.vprint(f"{CommandlineColors.OKGREEN}Machine stopped: {self.config.vmname()}{CommandlineColors.ENDC}", 1) def __call_process_config__(self, config: MachineConfig): """ Wrapper around process_config """ diff --git a/plugins/base/plugin_base.py b/plugins/base/plugin_base.py index 93fe328..abc813f 100644 --- a/plugins/base/plugin_base.py +++ b/plugins/base/plugin_base.py @@ -22,15 +22,28 @@ class BasePlugin(): self.machine_plugin = None self.sysconf = {} self.conf = {} + self.attack_logger = None self.default_config_name = "default_config.yaml" + def get_playground(self): + """ Returns the machine specific playground + + Which is the folder on the machine where we run our tasks in + """ + + return self.machine_plugin.get_playground() + + def set_logger(self, attack_logger): + """ Set the attack logger for this machine """ + self.attack_logger = attack_logger + def setup(self): """ Prepare everything for the plugin """ for a_file in self.required_files: src = os.path.join(os.path.dirname(self.plugin_path), a_file) - print(src) + self.vprint(src, 3) self.copy_to_machine(src) def set_machine_plugin(self, machine_plugin): @@ -61,9 +74,6 @@ class BasePlugin(): self.conf = {**self.conf, **config} - print("\n\n\n\n\n BASE plugin") - print(self.conf) - def copy_to_machine(self, filename): """ Copies a file shipped with the plugin to the machine share folder @@ -83,7 +93,7 @@ class BasePlugin(): @param disown: Run in background """ - print(f" Plugin running command {command}") + self.vprint(f" Plugin running command {command}", 3) res = self.machine_plugin.__call_remote_run__(command, disown=disown) return res @@ -138,11 +148,32 @@ class BasePlugin(): filename = self.get_default_config_filename() if not os.path.isfile(filename): - print(f"Did not find default config {filename}") + self.vprint(f"Did not find default config {filename}", 3) self.conf = {} else: with open(filename) as fh: - print(f"Loading default config {filename}") + self.vprint(f"Loading default config {filename}", 3) self.conf = yaml.safe_load(fh) if self.conf is None: self.conf = {} + + def get_config_section_name(self): + """ Returns the name for the config sub-section to use for this plugin. + + Defaults to the name of the plugin. This method should be overwritten if it gets more complicated """ + + return self.get_name() + + def vprint(self, text, verbosity): + """ verbosity based stdout printing + + 0: Errors only + 1: Main colored information + 2: Detailed progress information + 3: Debug logs, data dumps, everything + + @param text: The text to print + @param verbosity: the verbosity level the text has. + """ + + self.attack_logger.vprint(text, verbosity) diff --git a/plugins/base/ssh_features.py b/plugins/base/ssh_features.py index b29a16e..5e43723 100644 --- a/plugins/base/ssh_features.py +++ b/plugins/base/ssh_features.py @@ -29,7 +29,7 @@ class SSHFeatures(BasePlugin): try: if self.config.os() == "linux": uhp = self.get_ip() - print(f"Connecting to {uhp}") + self.vprint(f"Connecting to {uhp}", 3) self.c = Connection(uhp, connect_timeout=timeout) if self.config.os() == "windows": @@ -39,20 +39,20 @@ class SSHFeatures(BasePlugin): args["key_filename"] = self.config.ssh_keyfile() if self.config.ssh_password(): args["password"] = self.config.ssh_password() - print(args) + self.vprint(args, 3) uhp = self.get_ip() - print(uhp) + self.vprint(uhp, 3) self.c = Connection(uhp, connect_timeout=timeout, user=self.config.ssh_user(), connect_kwargs=args) except (paramiko.ssh_exception.SSHException, socket.timeout): - print(f"Failed to connect, will retry {retries} times. Timeout: {timeout}") + self.vprint(f"Failed to connect, will retry {retries} times. Timeout: {timeout}", 0) retries -= 1 timeout += 10 time.sleep(retry_sleep) else: - print(f"Connection: {self.c}") + self.vprint(f"Connection: {self.c}", 3) return self.c - print("SSH network error") + self.vprint("SSH network error", 0) raise NetworkError def remote_run(self, cmd, disown=False): @@ -67,8 +67,8 @@ class SSHFeatures(BasePlugin): self.connect() cmd = cmd.strip() - print("Running SSH remote run: " + cmd) - print("Disown: " + str(disown)) + self.vprint("Running SSH remote run: " + cmd, 3) + self.vprint("Disown: " + str(disown), 3) result = None retry = 2 while retry > 0: @@ -83,12 +83,12 @@ class SSHFeatures(BasePlugin): self.disconnect() self.connect() retry -= 1 - print("Got some SSH errors. Retrying") + self.vprint("Got some SSH errors. Retrying", 2) else: break if result and result.stderr: - print("Debug: Stderr: " + str(result.stderr.strip())) + self.vprint("Debug: Stderr: " + str(result.stderr.strip()), 0) if result: return result.stdout.strip() @@ -103,7 +103,7 @@ class SSHFeatures(BasePlugin): """ self.connect() - print(f"PUT {src} -> {dst}") + self.vprint(f"PUT {src} -> {dst}", 3) res = "" retries = 10 @@ -113,18 +113,18 @@ class SSHFeatures(BasePlugin): try: res = self.c.put(src, dst) except (paramiko.ssh_exception.SSHException, socket.timeout, UnexpectedExit): - print(f"PUT Failed to connect, will retry {retries} times. Timeout: {timeout}") + self.vprint(f"PUT Failed to connect, will retry {retries} times. Timeout: {timeout}", 3) retries -= 1 timeout += 10 time.sleep(retry_sleep) self.disconnect() self.connect() except FileNotFoundError as e: - print(f"File not found: {e}") + self.vprint(f"File not found: {e}", 0) break else: return res - print("SSH network error on PUT command") + self.vprint("SSH network error on PUT command", 0) raise NetworkError def get(self, src, dst): @@ -146,9 +146,9 @@ class SSHFeatures(BasePlugin): self.disconnect() self.connect() retry -= 1 - print("Got some SSH errors. Retrying") + self.vprint("Got some SSH errors. Retrying", 2) except FileNotFoundError as e: - print(e) + self.vprint(e, 0) break else: break diff --git a/plugins/default/vm_controller/vagrant/vagrant_plugin.py b/plugins/default/vm_controller/vagrant/vagrant_plugin.py index 4822ddb..f2f6717 100644 --- a/plugins/default/vm_controller/vagrant/vagrant_plugin.py +++ b/plugins/default/vm_controller/vagrant/vagrant_plugin.py @@ -84,7 +84,7 @@ class VagrantPlugin(SSHFeatures, MachineryPlugin): return self.c uhp = self.v.user_hostname_port(vm_name=self.config.vmname()) - print(f"Connecting to {uhp}") + self.vprint(f"Connecting to {uhp}", 3) self.c = Connection(uhp, connect_kwargs={"key_filename": self.v.keyfile(vm_name=self.config.vmname())}) return self.c diff --git a/plugins/default/vulnerabilities/linux_sshd_config/linux_sshd_config_issues.py b/plugins/default/vulnerabilities/linux_sshd_config/linux_sshd_config_issues.py index c814ceb..9399914 100644 --- a/plugins/default/vulnerabilities/linux_sshd_config/linux_sshd_config_issues.py +++ b/plugins/default/vulnerabilities/linux_sshd_config/linux_sshd_config_issues.py @@ -23,22 +23,18 @@ class SshdVulnerability(VulnerabilityPlugin): # allow password access via ssh cmd = "sudo sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config" - print(cmd) self.run_cmd(cmd) # Restart ssh cmd = "sudo service ssh restart" - print(cmd) self.run_cmd(cmd) def stop(self): # Re-configure sshd to stable state cmd = "sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config" - print(cmd) self.run_cmd(cmd) # Restart ssh cmd = "sudo service ssh restart" - print(cmd) self.run_cmd(cmd) diff --git a/plugins/default/vulnerabilities/weak_user_passwords/weak_user_passwords.py b/plugins/default/vulnerabilities/weak_user_passwords/weak_user_passwords.py index e29359b..f0307a3 100644 --- a/plugins/default/vulnerabilities/weak_user_passwords/weak_user_passwords.py +++ b/plugins/default/vulnerabilities/weak_user_passwords/weak_user_passwords.py @@ -28,7 +28,6 @@ class WeakPasswordVulnerabilityVulnerability(VulnerabilityPlugin): for user in self.conf["linux"]: cmd = f"sudo useradd -m -p '{user['password']}' -s /bin/bash {user['name']}" - print(cmd) self.run_cmd(cmd) elif self.machine_plugin.config.os() == "windows": @@ -36,13 +35,11 @@ class WeakPasswordVulnerabilityVulnerability(VulnerabilityPlugin): for user in self.conf["windows"]: # net user username password /add cmd = f"net user {user['name']} {user['password']} /add" - print(cmd) self.run_cmd(cmd) for user in self.conf["windows"]: # Adding the new users to RDP (just in case we want to test RDP) cmd = f"""NET LOCALGROUP "Remote Desktop Users" {user['name']} /ADD""" - print(cmd) self.run_cmd(cmd) else: @@ -54,21 +51,18 @@ class WeakPasswordVulnerabilityVulnerability(VulnerabilityPlugin): for user in self.conf["linux"]: # Remove user cmd = f"sudo userdel -r {user['name']}" - print(cmd) self.run_cmd(cmd) elif self.machine_plugin.config.os() == "windows": for user in self.conf["windows"]: # net user username /delete cmd = f"net user {user['name']} /delete" - print(cmd) self.run_cmd(cmd) # Remove the new users to RDP (just in case we want to test RDP) for user in self.conf["windows"]: # net user username /delete cmd = f""""NET LOCALGROUP "Remote Desktop Users" {user['name']} /DELETE""" - print(cmd) self.run_cmd(cmd) else: diff --git a/plugins/default/vulnerabilities/windows_rdp_config/windows_rdp_config.py b/plugins/default/vulnerabilities/windows_rdp_config/windows_rdp_config.py index ecf7e4f..33777c0 100644 --- a/plugins/default/vulnerabilities/windows_rdp_config/windows_rdp_config.py +++ b/plugins/default/vulnerabilities/windows_rdp_config/windows_rdp_config.py @@ -24,22 +24,18 @@ class RDPVulnerability(VulnerabilityPlugin): # allow password access via rdp cmd = r"""reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f""" - print(cmd) self.run_cmd(cmd) # Open Firewall cmd = """netsh advfirewall firewall set rule group="remote desktop" new enable=Yes""" - print(cmd) self.run_cmd(cmd) def stop(self): # Re-configure sshd to stable state cmd = r"""reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 1 /f""" - print(cmd) self.run_cmd(cmd) # Reset firewall cmd = """netsh advfirewall firewall set rule group="remote desktop" new enable=No""" - print(cmd) self.run_cmd(cmd)