Improving kali plugins towards being more flexible and handling any kind of attacks. Step 1

pull/3/head
Thorsten Sick 4 years ago
parent 090cd7c16a
commit 95e9efb966

@ -15,6 +15,8 @@ from app.interface_sfx import CommandlineColors
from caldera_control import CalderaControl from caldera_control import CalderaControl
from machine_control import Machine from machine_control import Machine
from app.exceptions import ServerError from app.exceptions import ServerError
from plugins.base.kali import KaliPlugin
from app.pluginmanager import PluginManager
# TODO: Multi threading at least when starting machines # TODO: Multi threading at least when starting machines
@ -33,6 +35,7 @@ class Experiment():
self.experiment_config = ExperimentConfig(configfile) self.experiment_config = ExperimentConfig(configfile)
self.attack_logger = AttackLog(verbosity) self.attack_logger = AttackLog(verbosity)
self.plugin_manager = PluginManager(self.attack_logger)
self.__start_attacker() self.__start_attacker()
caldera_url = "http://" + self.attacker_1.getip() + ":8888" caldera_url = "http://" + self.attacker_1.getip() + ":8888"
caldera_control = CalderaControl(caldera_url, attack_logger=self.attack_logger, config=self.experiment_config) caldera_control = CalderaControl(caldera_url, attack_logger=self.attack_logger, config=self.experiment_config)
@ -155,7 +158,8 @@ class Experiment():
for attack in kali_attacks: for attack in kali_attacks:
# TODO: Work with snapshots # TODO: Work with snapshots
self.attack_logger.vprint(f"Attacking machine with PAW: {target_1.get_paw()} with attack: {attack}", 1) 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_config) # self.attacker_1.kali_attack(attack, target_1.getip(), self.experiment_config)
self.attack(target_1, attack)
self.attack_logger.vprint(f"Pausing before next attack (config: nap_time): {self.experiment_config.get_nap_time()}", 3) 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()) time.sleep(self.experiment_config.get_nap_time())
@ -181,6 +185,26 @@ class Experiment():
self.attack_logger.write_json(os.path.join(self.lootdir, "attack.json")) self.attack_logger.write_json(os.path.join(self.lootdir, "attack.json"))
self.zip_loot() self.zip_loot()
def attack(self, target, attack):
""" Pick an attack and run it
@param attack: Name of the attack to run
@param target: IP address of the target
@returns: The output of the cmdline attacking tool
"""
# TODO: Extend beyond Kali
for plugin in self.plugin_manager.get_plugins(KaliPlugin, [attack]):
name = plugin.get_name()
self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Running Kali plugin {name}{CommandlineColors.ENDC}", 2)
plugin.process_config(self.experiment_config.kali_conf(plugin.get_config_section_name())) # TODO: De-kalify
plugin.set_attacker_machine(self.attacker_1)
# plugin.__set_logger__(self.attack_logger)
plugin.__execute__([target.getip()])
def zip_loot(self): def zip_loot(self):
""" Zip the loot together """ """ Zip the loot together """

@ -7,12 +7,11 @@ import time
import requests import requests
from app.config import MachineConfig, ExperimentConfig from app.config import MachineConfig
from app.exceptions import ServerError, ConfigurationError from app.exceptions import ServerError, ConfigurationError
from app.pluginmanager import PluginManager from app.pluginmanager import PluginManager
from app.calderacontrol import CalderaControl from app.calderacontrol import CalderaControl
from app.interface_sfx import CommandlineColors from app.interface_sfx import CommandlineColors
from plugins.base.kali import KaliPlugin
from plugins.base.machinery import MachineryPlugin from plugins.base.machinery import MachineryPlugin
from plugins.base.sensor import SensorPlugin from plugins.base.sensor import SensorPlugin
from plugins.base.vulnerability_plugin import VulnerabilityPlugin from plugins.base.vulnerability_plugin import VulnerabilityPlugin
@ -149,27 +148,6 @@ class Machine():
return self.vm_manager.__call_remote_run__(cmd, disown) return self.vm_manager.__call_remote_run__(cmd, disown)
def kali_attack(self, attack, target, config: ExperimentConfig):
""" Pick a Kali attack and run it
@param attack: Name of the attack to run
@param target: IP address of the target
@param config: A full experiment config object that has the methog "kali_conf" (just in case I want to split the config later)
@returns: The output of the cmdline attacking tool
"""
for plugin in self.plugin_manager.get_plugins(KaliPlugin, [attack]):
name = plugin.get_name()
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(plugin.get_config_section_name()))
plugin.set_machine_plugin(self.vm_manager)
# plugin.__set_logger__(self.attack_logger)
plugin.__execute__([target])
def load_machine_plugin(self): def load_machine_plugin(self):
""" Loads the matching machine plugin """ """ Loads the matching machine plugin """

@ -2,6 +2,8 @@
""" Base class for Kali plugins """ """ Base class for Kali plugins """
from plugins.base.plugin_base import BasePlugin from plugins.base.plugin_base import BasePlugin
from app.exceptions import PluginError
import os
class KaliPlugin(BasePlugin): class KaliPlugin(BasePlugin):
@ -13,7 +15,9 @@ class KaliPlugin(BasePlugin):
ttp = None ttp = None
references = None references = None
required_files = [] required_files = [] # Better use the other required_files features
required_files_attacker = [] # a list of files to automatically install to the attacker
required_files_target = [] # a list of files to automatically copy to the targets
# TODO: parse results # TODO: parse results
@ -21,11 +25,80 @@ class KaliPlugin(BasePlugin):
super().__init__() super().__init__()
self.conf = {} # Plugin specific configuration self.conf = {} # Plugin specific configuration
self.sysconf = {} # System configuration. common for all plugins self.sysconf = {} # System configuration. common for all plugins
self.attacker_machine_plugin = None # The machine plugin referencing the attacker. The Kali machine should be the perfect candidate
self.target_machine_plugin = None # The machine plugin referencing the target
def copy_to_attacker_and_defender(self):
""" Copy attacker/defender specific files to the machines. Called by setup, do not call it yourself. template processing happens before """
for a_file in self.required_files_attacker:
src = os.path.join(os.path.dirname(self.plugin_path), a_file)
self.vprint(src, 3)
self.attacker_machine_plugin.put(src, self.attacker_machine_plugin.get_playground())
# TODO: add target(s)
def teardown(self): def teardown(self):
""" Cleanup afterwards """ """ Cleanup afterwards """
pass # pylint: disable=unnecessary-pass pass # pylint: disable=unnecessary-pass
def attacker_run_cmd(self, command, warn=True, disown=False):
""" Execute a command on the attacker
@param command: Command to execute
@param disown: Run in background
"""
if self.attacker_machine_plugin is None:
raise PluginError("machine to run command on is not registered")
self.vprint(f" Plugin running command {command}", 3)
res = self.attacker_machine_plugin.__call_remote_run__(command, disown=disown)
return res
def targets_run_cmd(self, command, warn=True, disown=False):
""" Execute a command on the target
@param command: Command to execute
@param disown: Run in background
"""
if self.target_machine_plugin is None:
raise PluginError("machine to run command on is not registered")
self.vprint(f" Plugin running command {command}", 3)
res = self.target_machine_plugin.__call_remote_run__(command, disown=disown)
return res
def set_target_machines(self, machine):
""" Set the machine to target
@param machine: Machine plugin to communicate with
"""
self.target_machine_plugin = machine.vm_manager
def set_attacker_machine(self, machine):
""" Set the machine plugin class to target
@param machine: Machine to communicate with
"""
self.attacker_machine_plugin = machine.vm_manager
def get_attacker_playground(self):
""" Returns the attacker machine specific playground
Which is the folder on the machine where we run our tasks in
"""
if self.attacker_machine_plugin is None:
raise PluginError("Attacker machine not configured.")
return self.attacker_machine_plugin.get_playground()
def run(self, targets): def run(self, targets):
""" Run the command """ Run the command
@ -40,10 +113,10 @@ class KaliPlugin(BasePlugin):
""" """
self.setup() self.setup()
self.attack_logger.start_kali_attack(self.machine_plugin.config.vmname(), targets, self.name, ttp=self.get_ttp()) self.attack_logger.start_kali_attack(self.attacker_machine_plugin.config.vmname(), targets, self.name, ttp=self.get_ttp())
res = self.run(targets) res = self.run(targets)
self.teardown() self.teardown()
self.attack_logger.stop_kali_attack(self.machine_plugin.config.vmname(), targets, self.name, ttp=self.get_ttp()) self.attack_logger.stop_kali_attack(self.attacker_machine_plugin.config.vmname(), targets, self.name, ttp=self.get_ttp())
return res return res
def get_ttp(self): def get_ttp(self):

@ -4,6 +4,7 @@
import os import os
import yaml import yaml
# from shutil import copy # from shutil import copy
from app.exceptions import PluginError
# TODO: Proper planning and re-building of plugin system. Especially the default config handling should be streamlined. All the plugin types should have a very similar programming interface. # TODO: Proper planning and re-building of plugin system. Especially the default config handling should be streamlined. All the plugin types should have a very similar programming interface.
@ -32,6 +33,9 @@ class BasePlugin():
Which is the folder on the machine where we run our tasks in Which is the folder on the machine where we run our tasks in
""" """
if self.machine_plugin is None:
raise PluginError("Default machine not configured. Maybe you are creating an attack plugin. Then there are special attack/target machines")
return self.machine_plugin.get_playground() return self.machine_plugin.get_playground()
def set_logger(self, attack_logger): def set_logger(self, attack_logger):
@ -43,6 +47,11 @@ class BasePlugin():
return return
def copy_to_attacker_and_defender(self):
""" Copy attacker/defender specific files to the machines """
return
def setup(self): def setup(self):
""" Prepare everything for the plugin """ """ Prepare everything for the plugin """
@ -53,6 +62,8 @@ class BasePlugin():
self.vprint(src, 3) self.vprint(src, 3)
self.copy_to_machine(src) self.copy_to_machine(src)
self.copy_to_attacker_and_defender()
def set_machine_plugin(self, machine_plugin): def set_machine_plugin(self, machine_plugin):
""" Set the machine plugin class to communicate with """ Set the machine plugin class to communicate with
@ -87,7 +98,8 @@ class BasePlugin():
@param filename: File from the plugin folder to copy to the machine share. @param filename: File from the plugin folder to copy to the machine share.
""" """
self.machine_plugin.put(filename, self.machine_plugin.get_playground()) if self.machine_plugin is not None:
self.machine_plugin.put(filename, self.machine_plugin.get_playground())
def get_from_machine(self, src, dst): def get_from_machine(self, src, dst):
""" Get a file from the machine """ """ Get a file from the machine """
@ -100,6 +112,9 @@ class BasePlugin():
@param disown: Run in background @param disown: Run in background
""" """
if self.machine_plugin is None:
raise PluginError("machine to run command on is not registered")
self.vprint(f" Plugin running command {command}", 3) self.vprint(f" Plugin running command {command}", 3)
res = self.machine_plugin.__call_remote_run__(command, disown=disown) res = self.machine_plugin.__call_remote_run__(command, disown=disown)

@ -13,7 +13,7 @@ class HydraPlugin(KaliPlugin):
ttp = "T1110" ttp = "T1110"
references = ["https://attack.mitre.org/techniques/T1110/"] references = ["https://attack.mitre.org/techniques/T1110/"]
required_files = ["passwords.txt", "users.txt"] # Files shipped with the plugin which are needed by the kali tool. Will be copied to the kali share required_files_attacker = ["passwords.txt", "users.txt"] # Files shipped with the plugin which are needed by the kali tool. Will be copied to the kali share
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -26,7 +26,7 @@ class HydraPlugin(KaliPlugin):
""" """
# Set defaults if not present in config # Set defaults if not present in config
playground = self.machine_plugin.get_playground() playground = self.attacker_machine_plugin.get_playground()
# Generate command # Generate command
cmd = f"cd {playground};" cmd = f"cd {playground};"
@ -35,6 +35,6 @@ class HydraPlugin(KaliPlugin):
for p in self.conf['protocols']: for p in self.conf['protocols']:
cmd += f"hydra -L {self.conf['userfile']} -P {self.conf['pwdfile']} {p}://{t};" cmd += f"hydra -L {self.conf['userfile']} -P {self.conf['pwdfile']} {p}://{t};"
res = self.run_cmd(cmd) or "" res = self.attacker_run_cmd(cmd) or ""
return res return res

@ -78,11 +78,13 @@ class NmapPlugin(KaliPlugin):
res = "" res = ""
cmd = f"cd {self.get_playground()};" pg = self.get_attacker_playground()
cmd = f"cd {pg};"
for t in targets: for t in targets:
cmd += f"nmap {t};" cmd += f"nmap {t};"
res += self.run_cmd(cmd) or "" res += self.attacker_run_cmd(cmd) or ""
return res return res

@ -26,12 +26,12 @@ class NmapSneakyPlugin(KaliPlugin):
""" """
res = "" res = ""
pg = self.get_attacker_playground()
cmd = f"cd {self.get_playground()};" cmd = f"cd {pg};"
for t in targets: for t in targets:
cmd += f"sudo nmap -T1 -F -D RND:5 -f --randomize-hosts {t};" cmd += f"sudo nmap -T1 -F -D RND:5 -f --randomize-hosts {t};"
res += self.run_cmd(cmd) or "" res += self.attacker_run_cmd(cmd) or ""
return res return res

@ -27,11 +27,13 @@ class NmapStresstestPlugin(KaliPlugin):
res = "" res = ""
cmd = f"cd {self.get_playground()};" pg = self.get_attacker_playground()
cmd = f"cd {pg};"
for t in targets: for t in targets:
cmd += f"nmap -T5 --min-parallelism 100 --max-scan-delay 1 {t};" cmd += f"nmap -T5 --min-parallelism 100 --max-scan-delay 1 {t};"
res += self.run_cmd(cmd) or "" res += self.attacker_run_cmd(cmd) or ""
return res return res

Loading…
Cancel
Save