added verbosity switch

pull/3/head
Thorsten Sick 3 years ago
parent 73f0e40d99
commit 28b6ffb211

@ -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)

@ -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

@ -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 """

@ -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 """

@ -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:

@ -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))

@ -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")

@ -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

@ -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

@ -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:

@ -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 """

@ -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)

@ -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

@ -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

@ -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)

@ -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:

@ -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)

Loading…
Cancel
Save