Refactoring round 1 for experimentcontrol. Tested and works

more_unit_tests
Thorsten Sick 2 years ago
parent 848af1a65d
commit 51e75b8b22

@ -26,21 +26,32 @@ from plugins.base.attack import AttackPlugin
class Experiment():
""" Class handling experiments """
def __init__(self, configfile: str, verbosity=0, caldera_attacks: list = None):
def __init__(self, configfile: str, verbosity=0):
"""
:param configfile: Path to the configfile to load
:param verbosity: verbosity level between 0 and 3
:param caldera_attacks: an optional argument to override caldera attacks in the config file and run just this one caldera attack. A list of caldera ID
"""
self.start_time: Optional[datetime] = None #: time the experiment started
self.caldera_control: Optional[CalderaControl] = None
self.loot_dir: Optional[str] = None
self.targets = []
self.attacker_1: Optional[Machine] = None
self.experiment_config = ExperimentConfig(configfile)
self.attack_logger = AttackLog(verbosity)
self.plugin_manager = PluginManager(self.attack_logger)
def run(self, caldera_attacks: list = None):
"""
Run the experiment
:param caldera_attacks: an optional argument to override caldera attacks in the config file and run just this one caldera attack. A list of caldera ID
:return:
"""
self.__start_attacker()
if self.attacker_1 is None:
raise ServerError
caldera_url = "http://" + self.attacker_1.get_ip() + ":8888"
self.caldera_control = CalderaControl(caldera_url, attack_logger=self.attack_logger, config=self.experiment_config)
# self.caldera_control = CalderaControl("http://" + self.attacker_1.get_ip() + ":8888", self.attack_logger,
@ -49,92 +60,80 @@ class Experiment():
self.attack_logger.vprint(self.caldera_control.kill_all_agents(), 3)
self.attack_logger.vprint(self.caldera_control.delete_all_agents(), 3)
self.starttime = datetime.now().strftime("%Y_%m_%d___%H_%M_%S")
self.lootdir = os.path.join(self.experiment_config.loot_dir(), self.starttime)
os.makedirs(self.lootdir)
self.start_time = datetime.now().strftime("%Y_%m_%d___%H_%M_%S")
self.loot_dir = os.path.join(self.experiment_config.loot_dir(), self.start_time)
os.makedirs(self.loot_dir)
self.targets = []
# start target machines
for target_conf in self.experiment_config.targets():
if not target_conf.is_active():
continue
tname = target_conf.vmname()
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.get_ip())
try:
if not target_conf.use_existing_machine():
target_1.destroy()
except subprocess.CalledProcessError:
# Maybe the machine just does not exist yet
pass
if self.machine_needs_caldera(target_1, caldera_attacks):
target_1.install_caldera_service()
target_1.up()
target_1.reboot() # Kernel changes on system creation require a reboot
needs_reboot = target_1.prime_vulnerabilities()
needs_reboot |= target_1.prime_sensors()
if needs_reboot:
self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}rebooting target {tname} ....{CommandlineColors.ENDC}", 1)
target_1.reboot()
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Target is up: {tname} {CommandlineColors.ENDC}", 1)
self.targets.append(target_1)
tname = self.start_target_machines(caldera_attacks)
# Install vulnerabilities
for a_target in self.targets:
self.attack_logger.vprint(f"Installing vulnerabilities on {a_target.get_paw()}", 2)
a_target.install_vulnerabilities()
a_target.start_vulnerabilities()
self.install_vulnerabilities()
# Install sensor plugins
for a_target in self.targets:
self.attack_logger.vprint(f"Installing sensors on {a_target.get_paw()}", 2)
a_target.install_sensors()
a_target.start_sensors()
self.install_sensor_plugins()
# First start of caldera implants
at_least_one_caldera_started = False
for target_1 in self.targets:
if self.machine_needs_caldera(target_1, caldera_attacks):
target_1.start_caldera_client()
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Initial start of caldera client: {tname} {CommandlineColors.ENDC}", 1)
else:
at_least_one_caldera_started = True
if at_least_one_caldera_started:
time.sleep(20) # Wait for all the clients to contact the caldera server
# TODO: Smarter wait
self.first_start_of_caldera_implants(caldera_attacks, tname)
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 = self.caldera_control.list_paws_of_running_agents()
self.attack_logger.vprint(f"Agents currently running: {running_agents}", 2)
while target_1.get_paw() not in running_agents:
if self.machine_needs_caldera(target_1, caldera_attacks) == 0:
self.attack_logger.vprint(f"No caldera agent needed for: {target_1.get_paw()} ", 3)
break
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()
self.attack_logger.vprint(f"Restarted caldera agent: {target_1.get_paw()} ...", 3)
time.sleep(120) # Was 30, but maybe there are timing issues
running_agents = self.caldera_control.list_paws_of_running_agents()
self.wait_until_all_targets_have_caldera_implants(caldera_attacks, caldera_url)
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Caldera agents reached{CommandlineColors.ENDC}", 1)
# Add running machines to log
for target in self.targets:
i = target.get_machine_info()
i["role"] = "target"
self.attack_logger.add_machine_info(i)
i = self.attacker_1.get_machine_info()
i["role"] = "attacker"
self.attack_logger.add_machine_info(i)
self.add_running_machines_to_log()
# Attack them
self.run_caldera_attacks(caldera_attacks)
# Run plugin based attacks
self.run_plugin_attacks()
# Stop sensor plugins
# Collect data
zip_this = []
for a_target in self.targets:
a_target.stop_sensors()
zip_this += a_target.collect_sensors(self.loot_dir)
# Uninstall vulnerabilities
for a_target in self.targets:
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:
target_1.halt()
self.__stop_attacker()
self.attack_logger.post_process()
attack_log_file_path = os.path.join(self.loot_dir, "attack.json")
self.attack_logger.write_json(attack_log_file_path)
document_generator = DocGenerator()
document_generator.generate(attack_log_file_path)
document_generator.compile_documentation()
zip_this += document_generator.get_outfile_paths()
self.zip_loot(zip_this)
def run_plugin_attacks(self):
self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Running attack plugins{CommandlineColors.ENDC}", 1)
for target_1 in self.targets:
plugin_based_attacks = self.experiment_config.get_plugin_based_attacks(target_1.get_os())
metasploit_plugins = self.plugin_manager.count_caldera_requirements(AttackPlugin, plugin_based_attacks)
print(f"Plugins needing metasploit for {target_1.get_paw()} : {metasploit_plugins}")
for attack in plugin_based_attacks:
# TODO: Work with snapshots
self.attack_logger.vprint(f"Attacking machine with PAW: {target_1.get_paw()} with attack: {attack}", 1)
self.attack(target_1, attack)
self.attack_logger.vprint(
f"Pausing before next attack (config: nap_time): {self.experiment_config.get_nap_time()}", 3)
time.sleep(self.experiment_config.get_nap_time())
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Finished attack plugins{CommandlineColors.ENDC}", 1)
def run_caldera_attacks(self, caldera_attacks):
self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Running Caldera attacks{CommandlineColors.ENDC}", 1)
for target_1 in self.targets:
if caldera_attacks is None:
@ -161,9 +160,13 @@ 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
if it_worked:
self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Restarting caldera server and waiting for clients to re-connect{CommandlineColors.ENDC}", 1)
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()
self.attack_logger.vprint(f"Pausing before next attack (config: nap_time): {self.experiment_config.get_nap_time()}", 2)
self.attack_logger.vprint(
f"Pausing before next attack (config: nap_time): {self.experiment_config.get_nap_time()}",
2)
time.sleep(self.experiment_config.get_nap_time())
retries = 100
for target_system in self.targets:
@ -176,56 +179,96 @@ class Experiment():
time.sleep(1)
running_agents = self.caldera_control.list_paws_of_running_agents()
retries -= 1
self.attack_logger.vprint(f"Waiting for clients to re-connect ({retries}, {running_agents}) ", 3)
self.attack_logger.vprint(
f"Waiting for clients to re-connect ({retries}, {running_agents}) ", 3)
if retries <= 0:
raise ServerError
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Restarted caldera server clients re-connected{CommandlineColors.ENDC}", 1)
self.attack_logger.vprint(
f"{CommandlineColors.OKGREEN}Restarted caldera server clients re-connected{CommandlineColors.ENDC}",
1)
# End of fix
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Finished Caldera attacks{CommandlineColors.ENDC}", 1)
# Run plugin based attacks
self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Running attack plugins{CommandlineColors.ENDC}", 1)
for target_1 in self.targets:
plugin_based_attacks = self.experiment_config.get_plugin_based_attacks(target_1.get_os())
metasploit_plugins = self.plugin_manager.count_caldera_requirements(AttackPlugin, plugin_based_attacks)
print(f"Plugins needing metasploit for {target_1.get_paw()} : {metasploit_plugins}")
for attack in plugin_based_attacks:
# TODO: Work with snapshots
self.attack_logger.vprint(f"Attacking machine with PAW: {target_1.get_paw()} with attack: {attack}", 1)
def add_running_machines_to_log(self):
for target in self.targets:
i = target.get_machine_info()
i["role"] = "target"
self.attack_logger.add_machine_info(i)
i = self.attacker_1.get_machine_info()
i["role"] = "attacker"
self.attack_logger.add_machine_info(i)
self.attack(target_1, attack)
self.attack_logger.vprint(f"Pausing before next attack (config: nap_time): {self.experiment_config.get_nap_time()}", 3)
time.sleep(self.experiment_config.get_nap_time())
def wait_until_all_targets_have_caldera_implants(self, caldera_attacks, caldera_url):
for target_1 in self.targets:
running_agents = self.caldera_control.list_paws_of_running_agents()
self.attack_logger.vprint(f"Agents currently running: {running_agents}", 2)
while target_1.get_paw() not in running_agents:
if self.machine_needs_caldera(target_1, caldera_attacks) == 0:
self.attack_logger.vprint(f"No caldera agent needed for: {target_1.get_paw()} ", 3)
break
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()
self.attack_logger.vprint(f"Restarted caldera agent: {target_1.get_paw()} ...", 3)
time.sleep(120) # Was 30, but maybe there are timing issues
running_agents = self.caldera_control.list_paws_of_running_agents()
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Finished attack plugins{CommandlineColors.ENDC}", 1)
def first_start_of_caldera_implants(self, caldera_attacks, tname):
at_least_one_caldera_started = False
for target_1 in self.targets:
if self.machine_needs_caldera(target_1, caldera_attacks):
target_1.start_caldera_client()
self.attack_logger.vprint(
f"{CommandlineColors.OKGREEN}Initial start of caldera client: {tname} {CommandlineColors.ENDC}", 1)
else:
at_least_one_caldera_started = True
if at_least_one_caldera_started:
time.sleep(20) # Wait for all the clients to contact the caldera server
# TODO: Smarter wait
# Stop sensor plugins
# Collect data
zip_this = []
def install_sensor_plugins(self):
for a_target in self.targets:
a_target.stop_sensors()
zip_this += a_target.collect_sensors(self.lootdir)
self.attack_logger.vprint(f"Installing sensors on {a_target.get_paw()}", 2)
a_target.install_sensors()
a_target.start_sensors()
# Uninstall vulnerabilities
def install_vulnerabilities(self):
for a_target in self.targets:
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)
self.attack_logger.vprint(f"Installing vulnerabilities on {a_target.get_paw()}", 2)
a_target.install_vulnerabilities()
a_target.start_vulnerabilities()
# Stop target machines
for target_1 in self.targets:
target_1.halt()
self.__stop_attacker()
def start_target_machines(self, caldera_attacks):
for target_conf in self.experiment_config.targets():
if not target_conf.is_active():
continue
self.attack_logger.post_process()
attack_log_file_path = os.path.join(self.lootdir, "attack.json")
self.attack_logger.write_json(attack_log_file_path)
document_generator = DocGenerator()
document_generator.generate(attack_log_file_path)
document_generator.compile_documentation()
zip_this += document_generator.get_outfile_paths()
self.zip_loot(zip_this)
tname = target_conf.vmname()
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.get_ip())
try:
if not target_conf.use_existing_machine():
target_1.destroy()
except subprocess.CalledProcessError:
# Maybe the machine just does not exist yet
pass
if self.machine_needs_caldera(target_1, caldera_attacks):
target_1.install_caldera_service()
target_1.up()
target_1.reboot() # Kernel changes on system creation require a reboot
needs_reboot = target_1.prime_vulnerabilities()
needs_reboot |= target_1.prime_sensors()
if needs_reboot:
self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}rebooting target {tname} ....{CommandlineColors.ENDC}", 1)
target_1.reboot()
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Target is up: {tname} {CommandlineColors.ENDC}", 1)
self.targets.append(target_1)
return tname
def machine_needs_caldera(self, target, caldera_conf):
""" Counts the attacks and plugins needing caldera that are registered for this machine """
@ -267,7 +310,7 @@ class Experiment():
def zip_loot(self, zip_this):
""" Zip the loot together """
filename = os.path.join(self.lootdir, self.starttime + ".zip")
filename = os.path.join(self.loot_dir, self.start_time + ".zip")
self.attack_logger.vprint(f"Creating zip file {filename}", 1)
@ -277,10 +320,10 @@ class Experiment():
self.attack_logger.vprint(a_file, 2)
zfh.write(a_file)
zfh.write(os.path.join(self.lootdir, "attack.json"))
zfh.write(os.path.join(self.loot_dir, "attack.json"))
# For automation purpose we copy the file into a standard file name
defaultname = os.path.join(self.lootdir, "..", "most_recent.zip")
defaultname = os.path.join(self.loot_dir, "..", "most_recent.zip")
shutil.copyfile(filename, defaultname)
def __start_attacker(self):
@ -289,6 +332,9 @@ class Experiment():
# Preparing attacker
self.attacker_1 = Machine(self.experiment_config.attacker(0).raw_config, attack_logger=self.attack_logger)
if self.attacker_1 is None:
raise ServerError
if not self.experiment_config.attacker(0).use_existing_machine():
try:
self.attacker_1.destroy()
@ -303,6 +349,9 @@ class Experiment():
self.attacker_1.install_caldera_server(cleanup=False)
self.attacker_1.start_caldera_server()
if self.attacker_1 is None:
raise ServerError
# self.attacker_1.set_attack_logger(self.attack_logger)
def __stop_attacker(self):

@ -27,13 +27,15 @@ def run(args):
for line in fh:
line = line.strip()
print(f"Running calder attack {line}")
Experiment(args.configfile, args.verbose, [line])
exp = Experiment(args.configfile, args.verbose)
exp.run([line])
else:
caldera_attack = None
if args.caldera_attack:
caldera_attack = [args.caldera_attack]
Experiment(args.configfile, args.verbose, caldera_attack)
exp = Experiment(args.configfile, args.verbose)
exp.run(caldera_attack)
def create_parser():

Loading…
Cancel
Save