From ebd98544135fcf8d495b851aeec5dd35ef57d6b7 Mon Sep 17 00:00:00 2001 From: Thorsten Sick Date: Thu, 27 May 2021 07:45:03 +0200 Subject: [PATCH] Adding Caldera parameter handling --- app/calderacontrol.py | 95 +++++++++++++++++++++++++++++++++++++++--- caldera_control.py | 37 ++++++++++++++++ plugins/base/attack.py | 8 +++- 3 files changed, 133 insertions(+), 7 deletions(-) diff --git a/app/calderacontrol.py b/app/calderacontrol.py index 7db5d18..8cea5e6 100644 --- a/app/calderacontrol.py +++ b/app/calderacontrol.py @@ -134,6 +134,44 @@ class CalderaControl(): agents = self.__contact_server__(payload) return agents + def list_sources(self): + """ List stored facts + + """ + # TODO: Add filters for specific platforms/executors : , platform_filter=None, executor_filter=None as parameters + # curl -H 'KEY: ADMIN123' http://192.168.178.102:8888/api/rest -H 'Content-Type: application/json' -d '{"index":"agents"}' + payload = {"index": "sources"} + + facts = self.__contact_server__(payload) + return facts + + def list_sources_for_name(self, name): + """ List facts in a source pool with a specific name """ + + for i in self.list_sources(): + if i["name"] == name: + return i + return None + + def list_facts_for_name(self, name): + """ Pretty format for facts + + @param name: Name of the source ot look into + """ + + source = self.list_sources_for_name(name) + + if source is None: + return {} + + res = {} + for i in source["facts"]: + res[i["trait"]] = {"value": i["value"], + "technique_id": i["technique_id"], + "collected_by": i["collected_by"] + } + return res + def list_paws_of_running_agents(self): """ Returns a list of all paws of running agents """ return [i["paw"] for i in self.list_agents()] @@ -185,6 +223,12 @@ class CalderaControl(): # ######### Get by id + def get_source(self, source_name): + + payload = {"index": "sources", + "name": source_name} + return self.__contact_server__(payload) + def get_ability(self, abid): """" Return an ability by id @@ -290,7 +334,26 @@ class CalderaControl(): # ######### Add - def add_operation(self, name, advid, group="red", state="running", obfuscator="plain-text", jitter='4/8'): + def add_sources(self, name, parameters): + """ Adds a data source and seeds it with facts """ + + payload = {"index": "sources", + "name": name, + # "id": "123456-1234-1234-1234-12345678", + "rules": [], + "relationships": [] + } + + if parameters is not None: + facts = [] + for k, v in parameters.items(): + facts.append({"trait": k, "value": v}) + payload["facts"] = facts + + print(payload) + return self.__contact_server__(payload, method="put") + + def add_operation(self, name, advid, group="red", state="running", obfuscator="plain-text", jitter='4/8', parameters=None): """ Adds a new operation @param name: Name of the operation @@ -299,10 +362,18 @@ class CalderaControl(): @param state: state to initially set @param obfuscator: obfuscator to use for the attack @param jitter: jitter to use for the attack + @param parameters: parameters to pass to the ability """ # Add operation: curl -X PUT -H "KEY:$KEY" http://127.0.0.1:8888/api/rest -d '{"index":"operations","name":"testoperation1"}' # observed from GUI sniffing: PUT {'name': 'schnuffel2', 'group': 'red', 'adversary_id': '0f4c3c67-845e-49a0-927e-90ed33c044e0', 'state': 'running', 'planner': 'atomic', 'autonomous': '1', 'obfuscator': 'plain-text', 'auto_close': '1', 'jitter': '4/8', 'source': 'Alice Filters', 'visibility': '50'} + + sources_name = "source_" + name + self.add_sources(sources_name, parameters) + + print("Got:") + print(self.get_source("source_name")) + payload = {"index": "operations", "name": name, "state": state, @@ -310,7 +381,7 @@ class CalderaControl(): 'obfuscator': obfuscator, 'auto_close': '1', 'jitter': jitter, - 'source': 'Alice Filters', + 'source': sources_name, 'visibility': '50', "group": group, # @@ -348,12 +419,13 @@ class CalderaControl(): # TODO View the abilities a given agent could execute. curl -H "key:$API_KEY" -X POST localhost:8888/plugin/access/abilities -d '{"paw":"$PAW"}' - def execute_ability(self, paw, ability_id, obfuscator="plain-text"): + def execute_ability(self, paw, ability_id, obfuscator="plain-text", parameters=None): """ Executes an ability on a target. This happens outside of the scop of an operation. You will get no result of the ability back @param paw: Paw of the target @param ability_id: ability to execute @param obfuscator: Obfuscator to use + @param parameters: parameters to pass to the ability """ # curl -H "key:ADMIN123" -X POST localhost:8888/plugin/access/exploit -d '{"paw":"$PAW","ability_id":"$ABILITY_ID"}'``` @@ -362,6 +434,15 @@ class CalderaControl(): payload = {"paw": paw, "ability_id": ability_id, "obfuscator": obfuscator} + + if parameters is not None: + facts = [] + for k, v in parameters.items(): + facts.append({"trait": k, "value": v}) + payload["facts"] = facts + + print(payload) + return self.__contact_server__(payload, rest_path="plugin/access/exploit_ex") def execute_operation(self, operation_id, state="running"): @@ -506,7 +587,7 @@ class CalderaControl(): # ######## All inclusive methods - def attack(self, attack_logger: AttackLog = None, paw="kickme", ability_id="bd527b63-9f9e-46e0-9816-b8434d2b8989", group="red", target_platform=None): + def attack(self, attack_logger: AttackLog = None, paw="kickme", ability_id="bd527b63-9f9e-46e0-9816-b8434d2b8989", group="red", target_platform=None, parameters = None): """ Attacks a system and returns results @param attack_logger: An attack logger class to log attacks with @@ -514,6 +595,7 @@ class CalderaControl(): @param group: Group to attack. Paw must be in the group @param ability_id: Ability to run against the target @param target_platform: Platform of the target machine. Optional. Used for quick-outs + @param parameters: Dict containing key-values of parameters to pass to the ability @:return : True if the attack was executed. False if it was not. For example the target os is not supported by this attack """ @@ -561,7 +643,8 @@ class CalderaControl(): advid=adid, group=group, obfuscator=obfuscator, - jitter=jitter + jitter=jitter, + parameters=parameters ) self.attack_logger.vprint(pformat(res), 3) @@ -606,6 +689,8 @@ class CalderaControl(): self.attack_logger.vprint(f"{CommandlineColors.BACKGROUND_GREEN} Output: {outp} {CommandlineColors.ENDC}", 2) pprint(output) + self.attack_logger.vprint(self.list_facts_for_name("source_"+operation_name), 2) + # ######## Cleanup self.execute_operation(opid, "cleanup") self.delete_adversary(adid) diff --git a/caldera_control.py b/caldera_control.py index 8506fcc..8dab345 100644 --- a/caldera_control.py +++ b/caldera_control.py @@ -24,6 +24,36 @@ def list_agents(calcontrol, arguments): # pylint: disable=unused-argument print(f"Running agents: {calcontrol.list_agents()}") +def list_facts(calcontrol, arguments): # pylint: disable=unused-argument + """ Call list fact stores ("sources") in caldera control + + @param calcontrol: Connection to the caldera server + @param arguments: Parser command line arguments + """ + + + printme = "No found" + + if arguments.name: + printme = calcontrol.list_facts_for_name(arguments.name) + else: + printme = calcontrol.list_sources() + + print(f"Stored facts: {printme}") + + +def add_facts(calcontrol, arguments): # pylint: disable=unused-argument + """ Generate new facts in caldera + + @param calcontrol: Connection to the caldera server + @param arguments: Parser command line arguments + """ + name = "Test" + data = {"foo": "bar"} + + print(f'Created fact: {calcontrol.add_sources(name, data)}') + + def delete_agents(calcontrol, arguments): # pylint: disable=unused-argument """ Call list agents in caldera control @@ -103,6 +133,13 @@ def create_parser(): parser_delete_agents.add_argument("--paw", default=None, help="PAW to delete. if not set it will delete all agents") parser_delete_agents.set_defaults(func=delete_agents) + parser_facts = subparsers.add_parser("facts", help="facts") + parser_facts.set_defaults(func=list_facts) + parser_facts.add_argument("--name", default=None, help="Name of a fact source to focus on") + + parser_facts = subparsers.add_parser("add_facts", help="facts") + parser_facts.set_defaults(func=add_facts) + # For all parsers main_parser.add_argument("--caldera_url", help="caldera url, including port", default="http://192.168.178.125:8888/") main_parser.add_argument("--apikey", help="caldera api key", default="ADMIN123") diff --git a/plugins/base/attack.py b/plugins/base/attack.py index 5143071..a1db0b2 100644 --- a/plugins/base/attack.py +++ b/plugins/base/attack.py @@ -98,16 +98,20 @@ class AttackPlugin(BasePlugin): """ self.caldera = caldera - def caldera_attack(self, target, ability_id): + def caldera_attack(self, target, ability_id, parameters=None): """ Attack a single target using caldera + @param target: Target machine object + @param ability_id: Ability if od caldera ability to run + @param parameters: parameters to pass to the ability """ self.caldera.attack(self.attack_logger, paw=target.get_paw(), ability_id=ability_id, group=target.get_group(), - target_platform=target.get_os() + target_platform=target.get_os(), + parameters=parameters ) def get_attacker_playground(self):