Unit tests ok, integration test works

caldera_4
Thorsten Sick 2 years ago
parent dc2989b7ff
commit 896bfa8ff8

@ -0,0 +1,231 @@
#!/usr/bin/env python3
""" Direct API to the caldera server. Not abstract simplification methods. Compatible with Caldera 2.8.1 """
import requests
import json
import simplejson
class CalderaAPI:
""" API to Caldera 2.8.1 """
def __init__(self, server: str, 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
if self.config:
self.apikey = self.config.caldera_apikey()
else:
self.apikey = apikey
def __contact_server__(self, payload, rest_path: str = "api/rest", method: str = "post"):
"""
@param payload: payload as dict to send to the server
@param rest_path: specific path for this rest api
@param method: http method to use
"""
url = self.url + rest_path
header = {"KEY": self.apikey,
"Content-Type": "application/json"}
if method.lower() == "post":
request = requests.post(url, headers=header, data=json.dumps(payload))
elif method.lower() == "put":
request = requests.put(url, headers=header, data=json.dumps(payload))
elif method.lower() == "get":
request = requests.get(url, headers=header, data=json.dumps(payload))
elif method.lower() == "delete":
request = requests.delete(url, headers=header, data=json.dumps(payload))
else:
raise ValueError
try:
res = request.json()
except simplejson.errors.JSONDecodeError as exception: # type: ignore
print("!!! Error !!!!")
print(payload)
print(request.text)
print("!!! Error !!!!")
raise exception
return res
def list_operations(self):
""" Return operations """
payload = {"index": "operations"}
return self.__contact_server__(payload)
def list_abilities(self):
""" Return all ablilities """
# curl -H 'KEY: ADMIN123' http://192.168.178.102:8888/api/rest -H 'Content-Type: application/json' -d '{"index":"abilities"}'
payload = {"index": "abilities"}
return self.__contact_server__(payload)
def list_agents(self):
""" List running agents
"""
# 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": "agents"}
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_adversaries(self):
""" List registered adversaries """
# curl -H 'KEY: ADMIN123' http://192.168.178.102:8888/api/rest -H 'Content-Type: application/json' -d '{"index":"adversaries"}'
payload = {"index": "adversaries"}
return self.__contact_server__(payload)
def list_objectives(self):
""" List registered objectives """
# curl -H 'KEY: ADMIN123' http://192.168.178.102:8888/api/rest -H 'Content-Type: application/json' -d '{"index":"objectives"}'
payload = {"index": "objectives"}
return self.__contact_server__(payload)
def add_sources(self, name: str, parameters):
""" Adds a data source and seeds it with facts """
payload = {"index": "sources",
"name": name,
# "id": "123456-1234-1234-1234-12345678",
"rules": [],
"relationships": []
}
facts = []
if parameters is not None:
for key, value in parameters.items():
facts.append({"trait": key, "value": value})
# TODO: We need something better than a dict here as payload to have strong typing
payload["facts"] = facts # type: ignore
print(payload)
return self.__contact_server__(payload, method="put")
def add_operation(self, name: str, advid: str, group: str = "red", state: str = "running", obfuscator: str = "plain-text", jitter: str = '4/8', parameters=None):
""" Adds a new operation
@param name: Name of the operation
@param advid: Adversary id
@param group: agent group to attack
@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)
# To verify:
# print(self.get_source(sources_name))
payload = {"index": "operations",
"name": name,
"state": state,
"autonomous": 1,
'obfuscator': obfuscator,
'auto_close': '1',
'jitter': jitter,
'source': sources_name,
'visibility': '50',
"group": group,
#
"planner": "atomic",
"adversary_id": advid,
}
return self.__contact_server__(payload, method="put")
def add_adversary(self, name: str, ability: str, description: str = "created automatically"):
""" Adds a new adversary
@param name: Name of the adversary
@param ability: One ability for this adversary
@param description: Description of this adversary
"""
# Add operation: curl -X PUT -H "KEY:$KEY" http://127.0.0.1:8888/api/rest -d '{"index":"operations","name":"testoperation1"}'
# Sniffed from gui:
# Rest core: PUT adversaries {'name': 'removeme', 'description': 'description', 'atomic_ordering': [{'id': 'bd527b63-9f9e-46e0-9816-b8434d2b8989'}], 'id': '558932cb-3ac6-43d2-b821-2db0fa8ad469', 'objective': ''}
# Returns: [{'name': 'removeme', 'adversary_id': '558932cb-3ac6-43d2-b821-2db0fa8ad469', 'description': 'description', 'tags': [], 'atomic_ordering': ['bd527b63-9f9e-46e0-9816-b8434d2b8989'], 'objective': '495a9828-cab1-44dd-a0ca-66e58177d8cc'}]
payload = {"index": "adversaries",
"name": name,
"description": description,
"atomic_ordering": [{"id": ability}],
#
"objective": '495a9828-cab1-44dd-a0ca-66e58177d8cc' # default objective
# "objective": ''
}
return self.__contact_server__(payload, method="put")
# curl -X DELETE http://localhost:8888/api/rest -d '{"index":"operations","id":"$operation_id"}'
def delete_operation(self, opid: str):
""" Delete operation by id
@param opid: Operation id
"""
payload = {"index": "operations",
"id": opid}
return self.__contact_server__(payload, method="delete")
def delete_adversary(self, adid: str):
""" Delete adversary by id
@param adid: Adversary id
"""
payload = {"index": "adversaries",
"adversary_id": [{"adversary_id": adid}]}
return self.__contact_server__(payload, method="delete")
def delete_agent(self, paw: str):
""" Delete a specific agent from the kali db. implant may still be running and reconnect
@param paw: The Id of the agent to delete
"""
payload = {"index": "adversaries",
"paw": paw}
return self.__contact_server__(payload, method="delete")
def kill_agent(self, paw: str):
""" Send a message to an agent to kill itself
@param paw: The Id of the agent to delete
"""
payload = {"index": "agents",
"paw": paw,
"watchdog": 1,
"sleep_min": 3,
"sleep_max": 3}
return self.__contact_server__(payload, method="put")

@ -3,18 +3,16 @@
""" Remote control a caldera 4 server. Starting compatible to the old control 2.8 calderacontrol. Maybe it will stop being compatible if refactoring is an option """
import json
import os
import time
from pprint import pprint, pformat
from pprint import pformat
import requests
import simplejson
from typing import Optional, Union
from pydantic.dataclasses import dataclass
from pydantic import conlist, constr # pylint: disable=no-name-in-module
from pydantic import conlist # pylint: disable=no-name-in-module
from app.exceptions import CalderaError
from app.interface_sfx import CommandlineColors
# from app.exceptions import CalderaError
# from app.interface_sfx import CommandlineColors
# TODO: Ability deserves an own class.
@ -25,6 +23,7 @@ class Variation:
description: str
command: str
@dataclass
class ParserConfig:
source: str
@ -32,23 +31,27 @@ class ParserConfig:
target: str
custom_parser_vals: dict # undocumented ! Needs improvement ! TODO
@dataclass
class Parser:
module: str
relationships: list[ParserConfig] # undocumented ! Needs improvement ! TODO
parserconfigs: Optional[list[ParserConfig]] = None
@dataclass
class Requirement:
module: str
relationship_match: list[dict]
@dataclass
class AdditionalInfo:
additionalProp1: Optional[str] = None
additionalProp2: Optional[str] = None
additionalProp3: Optional[str] = None
@dataclass
class Executor:
build_target: Optional[str] # Why can this be None ?
@ -65,6 +68,7 @@ class Executor:
platform: str
command: Optional[str]
@dataclass
class Ability:
description: str
@ -95,10 +99,12 @@ class Obfuscator:
name: str
module: Optional[str] = None # Documentation error !!!
@dataclass
class ObfuscatorList:
obfuscators: conlist(Obfuscator, min_items=1)
@dataclass
class Adversary:
has_repeatable_abilities: bool
@ -110,6 +116,7 @@ class Adversary:
tags: list[str]
plugin: Optional[str] = None
@dataclass
class AdversaryList:
adversaries: conlist(Adversary, min_items=1)
@ -175,13 +182,12 @@ class Link:
deadman: bool
@dataclass
class Agent:
paw: str
location: str
platform: str
last_seen: str # Error in document
last_seen: str # Error in document
host_ip_addrs: list[str]
group: str
architecture: str
@ -212,7 +218,7 @@ class Agent:
@dataclass
class AgentList:
agents: conlist(Agent, min_items=1)
agents: conlist(Agent)
@dataclass
@ -292,7 +298,7 @@ class Operation:
name: str
source: Source
adversary: Adversary
objective: Union[Objective, str] # Maybe Error in caldera 4: Creating a Operation returns a objective ID, not an objective object
objective: Union[Objective, str] # Maybe Error in caldera 4: Creating a Operation returns a objective ID, not an objective object
host_group: list[Agent]
start: str
group: str
@ -308,6 +314,12 @@ class Operation:
class OperationList:
operations: conlist(Operation)
@dataclass
class ObjectiveList:
objectives: conlist(Objective)
class CalderaControl():
""" Remote control Caldera through REST api """
@ -340,7 +352,7 @@ class CalderaControl():
print(url)
header = {"KEY": "ADMIN123",
"accept": "application/json",
"Content-Type": "application/json",}
"Content-Type": "application/json"}
if method.lower() == "post":
j = json.dumps(payload)
print(j)
@ -353,6 +365,8 @@ class CalderaControl():
request = requests.head(url, headers=header, data=json.dumps(payload))
elif method.lower() == "delete":
request = requests.delete(url, headers=header, data=json.dumps(payload))
elif method.lower() == "patch":
request = requests.patch(url, headers=header, data=json.dumps(payload))
else:
raise ValueError
try:
@ -429,14 +443,22 @@ class CalderaControl():
payload = None
data = {"agents": self.__contact_server__(payload, method="get", rest_path="api/v2/agents")}
print(data)
# print(data)
agents = AgentList(**data)
return agents
def list_objectives(self):
""" Return all objectivs """
payload = None
data = {"objectives": self.__contact_server__(payload, method="get", rest_path="api/v2/objectives")}
print(data)
objectives = ObjectiveList(**data)
return objectives
# TODO: list_sources_for_name
# TODO: list_facts_for_name
# TODO: list_paws_of_running_agents
# TODO: list_objectives
# TODO: get_operation
# TODO: get_adversary
# TODO: get_source
@ -445,16 +467,12 @@ class CalderaControl():
# TODO: get_operation_by_id
# TODO: view_operation_report
# TODO: view_operation_output
# TODO: add_sources
# TODO: add_sources (maybe not needed anymore)
# TODO: execute_operation
# TODO: delete_operation
# TODO: delete_agent
# TODO: kill_agent
# TODO is_operation_finished
# TODO: attack
def add_adversary(self, name: str, ability: str, description: str = "created automatically"):
payload = {
# "adversary_id": "string",
@ -477,7 +495,23 @@ class CalderaControl():
def delete_adversary(self, adversary_id: str):
payload = None
data = {"agents": self.__contact_server__(payload, method="delete", rest_path=f"api/v2/adversaries/{adversary_id}")}
print(data)
# print(data)
# agents = AgentList(**data)
return data
def delete_agent(self, agent_paw: str):
payload = None
data = {"agents": self.__contact_server__(payload, method="delete", rest_path=f"api/v2/agents/{agent_paw}")}
# print(data)
# agents = AgentList(**data)
return data
def kill_agent(self, agent_paw: str):
payload = {"watchdog": 1,
"sleep_min": 3,
"sleep_max": 3}
data = self.__contact_server__(payload, method="patch", rest_path=f"api/v2/agents/{agent_paw}")
# print(data)
# agents = AgentList(**data)
return data
@ -496,8 +530,15 @@ class CalderaControl():
"jitter": jitter,
"visibility": "51"}
data = {"operations": [self.__contact_server__(payload, method="post", rest_path="api/v2/operations")]}
print (data)
operations = OperationList(**data)
return operations
def delete_operation(self, id):
payload = {}
data = self.__contact_server__(payload, method="delete", rest_path=f"api/v2/operations/{id}")
return data
def get_ability(self, abid: str):
@ -530,6 +571,6 @@ class CalderaControl():
Tactic: {tactic}
Name: {name}
ID: {ability_id}
Description: {description}
Description: {description}
""".format(**abi))
""".format(**abi))

@ -2,23 +2,23 @@
""" Remote control a caldera server """
import json
import os
import time
from pprint import pprint, pformat
from typing import Optional
import requests
import simplejson
from app.exceptions import CalderaError
from app.interface_sfx import CommandlineColors
from app.calderaapi_2 import CalderaAPI
# TODO: Ability deserves an own class.
# TODO: Support all Caldera agents: "Sandcat (GoLang)","Elasticat (Blue Python/ Elasticsearch)","Manx (Reverse Shell TCP)","Ragdoll (Python/HTML)"
class CalderaControl():
class CalderaControl(CalderaAPI):
""" Remote control Caldera through REST api """
def __init__(self, server: str, attack_logger, config=None, apikey=None):
@ -28,16 +28,8 @@ class CalderaControl():
@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
if self.config:
self.apikey = self.config.caldera_apikey()
else:
self.apikey = apikey
super().__init__(server, attack_logger, config, apikey)
def fetch_client(self, platform: str = "windows", file: str = "sandcat.go", target_dir: str = ".", extension: str = ""):
""" Downloads the appropriate Caldera client
@ -57,93 +49,6 @@ class CalderaControl():
# print(r.headers)
return filename
def __contact_server__(self, payload, rest_path: str = "api/rest", method: str = "post"):
"""
@param payload: payload as dict to send to the server
@param rest_path: specific path for this rest api
@param method: http method to use
"""
url = self.url + rest_path
header = {"KEY": self.apikey,
"Content-Type": "application/json"}
if method.lower() == "post":
request = requests.post(url, headers=header, data=json.dumps(payload))
elif method.lower() == "put":
request = requests.put(url, headers=header, data=json.dumps(payload))
elif method.lower() == "get":
request = requests.get(url, headers=header, data=json.dumps(payload))
elif method.lower() == "delete":
request = requests.delete(url, headers=header, data=json.dumps(payload))
else:
raise ValueError
try:
res = request.json()
except simplejson.errors.JSONDecodeError as exception: # type: ignore
print("!!! Error !!!!")
print(payload)
print(request.text)
print("!!! Error !!!!")
raise exception
return res
# ############## List
def list_links(self, opid: str):
""" List links associated with an operation
@param opid: operation id to list links for
"""
payload = {"index": "link",
"op_id": opid}
return self.__contact_server__(payload)
def list_results(self, linkid: str):
""" List results for a link
@param linkid: ID of the link
"""
payload = {"index": "result",
"link_id": linkid}
return self.__contact_server__(payload)
def list_operations(self):
""" Return operations """
payload = {"index": "operations"}
return self.__contact_server__(payload)
def list_abilities(self):
""" Return all ablilities """
# curl -H 'KEY: ADMIN123' http://192.168.178.102:8888/api/rest -H 'Content-Type: application/json' -d '{"index":"abilities"}'
payload = {"index": "abilities"}
return self.__contact_server__(payload)
def list_agents(self):
""" List running agents
"""
# 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": "agents"}
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: str):
""" List facts in a source pool with a specific name """
@ -175,18 +80,6 @@ class CalderaControl():
""" Returns a list of all paws of running agents """
return [i["paw"] for i in self.list_agents()]
def list_adversaries(self):
""" List registered adversaries """
# curl -H 'KEY: ADMIN123' http://192.168.178.102:8888/api/rest -H 'Content-Type: application/json' -d '{"index":"adversaries"}'
payload = {"index": "adversaries"}
return self.__contact_server__(payload)
def list_objectives(self):
""" List registered objectives """
# curl -H 'KEY: ADMIN123' http://192.168.178.102:8888/api/rest -H 'Content-Type: application/json' -d '{"index":"objectives"}'
payload = {"index": "objectives"}
return self.__contact_server__(payload)
# ######### Get one specific item
def get_operation(self, name: str):
@ -220,18 +113,6 @@ class CalderaControl():
return objective
return None
# ######### Get by id
def get_source(self, source_name: str):
""" Retrieves data source and detailed facts
@param: The name of the source
"""
payload = {"index": "sources",
"name": source_name}
return self.__contact_server__(payload)
def get_ability(self, abid: str):
"""" Return an ability by id
@ -276,18 +157,13 @@ class CalderaControl():
@param op_id: Operation id
"""
payload = {"index": "operations",
"id": op_id}
return self.__contact_server__(payload)
ops = self.list_operations()
def get_result_by_id(self, linkid: str):
""" Get the result from a link id
@param linkid: link id
"""
payload = {"index": "result",
"link_id": linkid}
return self.__contact_server__(payload)
if ops is not None:
for op in ops:
if op["id"] == op_id:
return [op]
return []
def get_linkid(self, op_id: str, paw: str, ability_id: str):
""" Get the id of a link identified by paw and ability_id
@ -347,123 +223,6 @@ class CalderaControl():
# print(f"Did not find ability {ability_id} in caldera operation output")
return None
# ######### Add
def add_sources(self, name: str, parameters):
""" Adds a data source and seeds it with facts """
payload = {"index": "sources",
"name": name,
# "id": "123456-1234-1234-1234-12345678",
"rules": [],
"relationships": []
}
facts = []
if parameters is not None:
for key, value in parameters.items():
facts.append({"trait": key, "value": value})
# TODO: We need something better than a dict here as payload to have strong typing
payload["facts"] = facts # type: ignore
print(payload)
return self.__contact_server__(payload, method="put")
def add_operation(self, name: str, advid: str, group: str = "red", state: str = "running", obfuscator: str = "plain-text", jitter: str = '4/8', parameters=None):
""" Adds a new operation
@param name: Name of the operation
@param advid: Adversary id
@param group: agent group to attack
@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)
# To verify:
# print(self.get_source(sources_name))
payload = {"index": "operations",
"name": name,
"state": state,
"autonomous": 1,
'obfuscator': obfuscator,
'auto_close': '1',
'jitter': jitter,
'source': sources_name,
'visibility': '50',
"group": group,
#
"planner": "atomic",
"adversary_id": advid,
}
return self.__contact_server__(payload, method="put")
def add_adversary(self, name: str, ability: str, description: str = "created automatically"):
""" Adds a new adversary
@param name: Name of the adversary
@param ability: One ability for this adversary
@param description: Description of this adversary
"""
# Add operation: curl -X PUT -H "KEY:$KEY" http://127.0.0.1:8888/api/rest -d '{"index":"operations","name":"testoperation1"}'
# Sniffed from gui:
# Rest core: PUT adversaries {'name': 'removeme', 'description': 'description', 'atomic_ordering': [{'id': 'bd527b63-9f9e-46e0-9816-b8434d2b8989'}], 'id': '558932cb-3ac6-43d2-b821-2db0fa8ad469', 'objective': ''}
# Returns: [{'name': 'removeme', 'adversary_id': '558932cb-3ac6-43d2-b821-2db0fa8ad469', 'description': 'description', 'tags': [], 'atomic_ordering': ['bd527b63-9f9e-46e0-9816-b8434d2b8989'], 'objective': '495a9828-cab1-44dd-a0ca-66e58177d8cc'}]
payload = {"index": "adversaries",
"name": name,
"description": description,
"atomic_ordering": [{"id": ability}],
#
"objective": '495a9828-cab1-44dd-a0ca-66e58177d8cc' # default objective
# "objective": ''
}
return self.__contact_server__(payload, method="put")
# ######### Execute
# 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: str, ability_id: str, obfuscator: str = "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"}'```
# You can optionally POST an obfuscator and/or a facts dictionary with key/value pairs to fill in any variables the chosen ability requires.
# {"paw":"$PAW","ability_id":"$ABILITY_ID","obfuscator":"base64","facts":[{"trait":"username","value":"admin"},{"trait":"password", "value":"123"}]}
payload = {"paw": paw,
"ability_id": ability_id,
"obfuscator": obfuscator}
facts = []
if parameters is not None:
for key, value in parameters.items():
facts.append({"trait": key, "value": value})
# TODO. We need something better than a dict here for strong typing
payload["facts"] = facts # type: ignore
# print(payload)
return self.__contact_server__(payload, rest_path="plugin/access/exploit_ex")
def execute_operation(self, operation_id: str, state: str = "running"):
""" Executes an operation on a server
@ -484,48 +243,6 @@ class CalderaControl():
# ######### Delete
# curl -X DELETE http://localhost:8888/api/rest -d '{"index":"operations","id":"$operation_id"}'
def delete_operation(self, opid: str):
""" Delete operation by id
@param opid: Operation id
"""
payload = {"index": "operations",
"id": opid}
return self.__contact_server__(payload, method="delete")
def delete_adversary(self, adid: str):
""" Delete adversary by id
@param adid: Adversary id
"""
payload = {"index": "adversaries",
"adversary_id": [{"adversary_id": adid}]}
return self.__contact_server__(payload, method="delete")
def delete_agent(self, paw: str):
""" Delete a specific agent from the kali db. implant may still be running and reconnect
@param paw: The Id of the agent to delete
"""
payload = {"index": "adversaries",
"paw": paw}
return self.__contact_server__(payload, method="delete")
def kill_agent(self, paw: str):
""" Send a message to an agent to kill itself
@param paw: The Id of the agent to delete
"""
payload = {"index": "agents",
"paw": paw,
"watchdog": 1,
"sleep_min": 3,
"sleep_max": 3}
return self.__contact_server__(payload, method="put")
def delete_all_agents(self):
""" Delete all agents from kali db """

@ -16,8 +16,8 @@ from app.interface_sfx import CommandlineColors
from app.exceptions import ServerError
from app.pluginmanager import PluginManager
from app.doc_generator import DocGenerator
from caldera_control import CalderaControl
from machine_control import Machine
from app.calderacontrol import CalderaControl
from app.machinecontrol import Machine
from plugins.base.attack import AttackPlugin

@ -4,8 +4,8 @@
import argparse
#from app.calderacontrol import CalderaControl
from app.calderacontrol_4 import CalderaControl
# from app.calderacontrol import CalderaControl
from app.calderaapi_4 import CalderaControl
from pprint import pprint
from app.attack_log import AttackLog
@ -20,6 +20,7 @@ class CmdlineArgumentException(Exception):
# TODO: Get results of a specific attack
# Arpgparse handling
def agents(calcontrol, arguments): # pylint: disable=unused-argument
""" Agents in caldera control
@ -29,9 +30,12 @@ def agents(calcontrol, arguments): # pylint: disable=unused-argument
"""
if arguments.list:
for agent in calcontrol.list_agents().__dict__["agents"]:
print(agent)
agents = calcontrol.list_agents().__dict__["agents"]
print(agents)
if arguments.delete:
print(calcontrol.delete_agent(arguments.paw))
if arguments.kill:
print(calcontrol.kill_agent(arguments.paw))
def list_facts(calcontrol, arguments): # pylint: disable=unused-argument
@ -63,23 +67,6 @@ def add_facts(calcontrol, arguments): # pylint: disable=unused-argument
print(f'Created fact: {calcontrol.add_sources(name, data)}')
def delete_agents(calcontrol, arguments): # pylint: disable=unused-argument
""" Call list agents in caldera control
@param calcontrol: Connection to the caldera server
@param arguments: Parser command line arguments
"""
print(calcontrol.list_paws_of_running_agents())
if arguments.paw:
print(calcontrol.kill_agent(paw=arguments.paw))
print(calcontrol.delete_agent(paw=arguments.paw))
else:
print(calcontrol.kill_all_agents())
print(calcontrol.delete_all_agents())
def list_abilities(calcontrol, arguments):
""" Call list abilities in caldera control
@ -98,10 +85,6 @@ def list_abilities(calcontrol, arguments):
for parser in executor.parsers:
pprint(parser.relationships)
#for aid in abilities:
# for ability in calcontrol.get_ability(aid):
# calcontrol.pretty_print_ability(ability)
def obfuscators(calcontrol, arguments):
""" Manage obfuscators caldera control
@ -119,6 +102,22 @@ def obfuscators(calcontrol, arguments):
print(ob)
def objectives(calcontrol, arguments):
""" Manage objectives caldera control
@param calcontrol: Connection to the caldera server
@param arguments: Parser command line arguments
"""
if arguments.list:
objectives = calcontrol.list_objectives().__dict__["objectives"]
# ob_ids = [aid.ability_id for aid in objectives]
# print(ob_ids)
for ob in objectives:
print(ob)
def adversaries(calcontrol, arguments):
""" Manage adversaries caldera control
@ -138,11 +137,11 @@ def adversaries(calcontrol, arguments):
raise CmdlineArgumentException("Creating an adversary requires an ability id")
if arguments.name is None:
raise CmdlineArgumentException("Creating an adversary requires an adversary name")
res = calcontrol.add_adversary(arguments.name, arguments.ability_id)
calcontrol.add_adversary(arguments.name, arguments.ability_id)
if arguments.delete:
if arguments.adversary_id is None:
raise CmdlineArgumentException("Deleting an adversary requires an adversary id")
res = calcontrol.delete_adversary(arguments.adversary_id)
calcontrol.delete_adversary(arguments.adversary_id)
def sources(calcontrol, arguments):
@ -201,6 +200,12 @@ def operations(calcontrol, arguments):
ops = calcontrol.add_operation(arguments.name, arguments.adversary_id, arguments.source_id, arguments.planner_id, arguments.group, arguments.state, arguments.obfuscator, arguments.jitter)
print(ops)
if arguments.delete:
if arguments.id is None:
raise CmdlineArgumentException("Deleting an operation requires its id")
ops = calcontrol.delete_operation(arguments.id)
print(ops)
def attack(calcontrol, arguments):
""" Calling attack
@ -243,10 +248,9 @@ def create_parser():
parser_agents = subparsers.add_parser("agents", help="agents")
parser_agents.set_defaults(func=agents)
parser_agents.add_argument("--list", default=False, action="store_true", help="List all agents")
parser_delete_agents = subparsers.add_parser("delete_agents", help="agents")
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_agents.add_argument("--delete", default=False, action="store_true", help="Delete agent")
parser_agents.add_argument("--kill", default=False, action="store_true", help="Delete agent")
parser_agents.add_argument("--paw", default=None, help="PAW to delete. if not set it will delete all agents")
parser_facts = subparsers.add_parser("facts", help="facts")
parser_facts.set_defaults(func=list_facts)
@ -259,7 +263,13 @@ def create_parser():
parser_obfuscators = subparsers.add_parser("obfuscators", help="obfuscators")
parser_obfuscators.set_defaults(func=obfuscators)
parser_obfuscators.add_argument("--list", default=False, action="store_true",
help="List all obfuscators")
help="List all obfuscators")
# Sub parser for objectives
parser_objectives = subparsers.add_parser("objectives", help="objectives")
parser_objectives.set_defaults(func=objectives)
parser_objectives.add_argument("--list", default=False, action="store_true",
help="List all objectives")
# Sub parser for adversaries
parser_adversaries = subparsers.add_parser("adversaries", help="adversaries")
@ -278,9 +288,11 @@ def create_parser():
parser_operations = subparsers.add_parser("operations", help="operations")
parser_operations.set_defaults(func=operations)
parser_operations.add_argument("--list", default=False, action="store_true",
help="List all operations")
help="List all operations")
parser_operations.add_argument("--add", default=False, action="store_true",
help="Add a new operations")
help="Add a new operations")
parser_operations.add_argument("--delete", default=False, action="store_true",
help="Delete an operation")
parser_operations.add_argument("--name", default=None, help="Name of the operation")
parser_operations.add_argument("--adversary_id", "--advid", default=None, help="Adversary ID")
parser_operations.add_argument("--source_id", "--sourceid", default="basic", help="'Source' ID")
@ -289,18 +301,19 @@ def create_parser():
parser_operations.add_argument("--state", default="running", help="State to start the operation in")
parser_operations.add_argument("--obfuscator", default="plain-text", help="Obfuscator to use for this attack")
parser_operations.add_argument("--jitter", default="4/8", help="Jitter to use")
parser_operations.add_argument("--id", default=None, help="ID of operation to delete")
# Sub parser for sources
parser_sources = subparsers.add_parser("sources", help="sources")
parser_sources.set_defaults(func=sources)
parser_sources.add_argument("--list", default=False, action="store_true",
help="List all sources")
help="List all sources")
# Sub parser for planners
parser_sources = subparsers.add_parser("planners", help="planners")
parser_sources.set_defaults(func=planners)
parser_sources.add_argument("--list", default=False, action="store_true",
help="List all planners")
help="List all planners")
# For all parsers
main_parser.add_argument("--caldera_url", help="caldera url, including port", default="http://localhost:8888/")

@ -17,30 +17,6 @@ class TestExample(unittest.TestCase):
def tearDown(self) -> None:
pass
# List links sends the right commands and post
def test_list_links(self):
with patch.object(self.cc, "__contact_server__", return_value=None) as mock_method:
self.cc.list_links("asd")
mock_method.assert_called_once_with({"index": "link", "op_id": "asd"})
# List links gets an Exception and does not handle it (as expected)
def test_list_links_with_exception(self):
with self.assertRaises(JSONDecodeError):
with patch.object(self.cc, "__contact_server__", side_effect=JSONDecodeError("foo", "bar", 2)):
self.cc.list_links("asd")
# list results sends the right commands and post
def test_list_results(self):
with patch.object(self.cc, "__contact_server__", return_value=None) as mock_method:
self.cc.list_results("asd")
mock_method.assert_called_once_with({"index": "result", "link_id": "asd"})
# List results gets an Exception and does not handle it (as expected)
def test_list_results_with_exception(self):
with self.assertRaises(JSONDecodeError):
with patch.object(self.cc, "__contact_server__", side_effect=JSONDecodeError("foo", "bar", 2)):
self.cc.list_results("asd")
# list_operations
def test_list_operations(self):
with patch.object(self.cc, "__contact_server__", return_value=None) as mock_method:
@ -162,26 +138,7 @@ class TestExample(unittest.TestCase):
opid = "FooBar"
with patch.object(self.cc, "__contact_server__", return_value=None) as mock_method:
self.cc.get_operation_by_id(opid)
mock_method.assert_called_once_with({"index": "operations", "id": opid})
# get_operation_by_id gets the expected exception
def test_get_operation_by_id_with_exception(self):
with self.assertRaises(JSONDecodeError):
with patch.object(self.cc, "__contact_server__", side_effect=JSONDecodeError("foo", "bar", 2)):
self.cc.get_result_by_id("FooBar")
# get_result_by_id
def test_get_result_by_id(self):
opid = "FooBar"
with patch.object(self.cc, "__contact_server__", return_value=None) as mock_method:
self.cc.get_result_by_id(opid)
mock_method.assert_called_once_with({"index": "result", "link_id": opid})
# get_result_by_id gets the expected exception
def test_get_result_by_id_with_exception(self):
with self.assertRaises(JSONDecodeError):
with patch.object(self.cc, "__contact_server__", side_effect=JSONDecodeError("foo", "bar", 2)):
self.cc.get_result_by_id("FooBar")
mock_method.assert_called_once_with({"index": "operations"})
# get_linkid
def test_get_linkid(self):
@ -347,32 +304,6 @@ class TestExample(unittest.TestCase):
self.cc.add_adversary(name, ability)
mock_method.assert_called_once_with(exp, method="put")
# execute_ability
def test_execute_ability(self):
paw = "test_paw"
ability_id = "test_ability"
obfuscator = "plain-text"
exp = {"paw": paw,
"ability_id": ability_id,
"obfuscator": obfuscator,
"facts": []}
with patch.object(self.cc, "__contact_server__", return_value=None) as mock_method:
self.cc.execute_ability(paw, ability_id, obfuscator)
mock_method.assert_called_once_with(exp, rest_path="plugin/access/exploit_ex")
def test_execute_ability_default(self):
paw = "test_paw"
ability_id = "test_ability"
exp = {"paw": paw,
"ability_id": ability_id,
"obfuscator": "plain-text",
"facts": []}
with patch.object(self.cc, "__contact_server__", return_value=None) as mock_method:
self.cc.execute_ability(paw, ability_id)
mock_method.assert_called_once_with(exp, rest_path="plugin/access/exploit_ex")
# execute_operation
def test_execute_operation(self):
operation_id = "test_opid"

Loading…
Cancel
Save