async def test_trim_links(self, setup_planning_test, planning_svc): """ This test covers both remove_links_with_unset_variables and remove_links_missing_requirements. It uses a fact set that causes add_test_variants to create three links. One of which is the original that has not been populated with facts, this one gets pruned off by remove_links_with_unset_variables. Of the remaining two links that are populated, one is pruned off by a requirement that requires that the character 0 is in the link's command. The tests show that only one link is returned by trim_links and that the returned link is the one that is populated and adheres to the requirement. """ ability, agent, operation, _ = setup_planning_test link = Link.load(dict(command=BaseWorld.encode_string(test_string), paw=agent.paw, ability=ability, executor=next(ability.executors), status=0)) facts = [ Fact(trait='1_2_3', value='0'), Fact(trait='1_2_3', value='4'), Fact(trait='a.b.c', value='1'), Fact(trait='a.b.d', value='2'), Fact(trait='a.b.e', value='3'), ] operation.all_facts = async_wrapper(facts) operation.planner = MagicMock() planning_svc.load_module = async_wrapper(RequirementFake()) link.ability.requirements = [Requirement('fake_requirement', [{'fake': 'relationship'}])] trimmed_links = await planning_svc.trim_links(operation, [link], agent) assert len(trimmed_links) == 1 assert BaseWorld.decode_bytes(trimmed_links[0].display['command']) == target_string
def test_filter_bs(self, loop, setup_planning_test, planning_svc): _, agent, operation, ability = setup_planning_test link = Link.load( dict(command=BaseWorld.encode_string(test_string), paw=agent.paw, ability=ability, executor=next(ability.executors), status=0)) f0 = Fact(trait='1_2_3', value='0') f1 = Fact(trait='a.b.c', value='1') f2 = Fact(trait='a.b.d', value='2') f3 = Fact(trait='a.b.e', value='3') f4 = Fact(trait='a.b.e', value='4') f5 = Fact(trait='a.b.e', value='5') f6 = Fact(trait='a.b.e', value='6') gen = loop.run_until_complete( planning_svc.add_test_variants([link], agent, facts=[f0, f1, f2, f3, f4, f5, f6])) assert len(gen) == 4 assert BaseWorld.decode_bytes( gen[1].display['command']) == target_string
def setup_planning_test(loop, ability, agent, operation, data_svc, init_base_world): tability = ability(ability_id='123', executor='sh', platform='darwin', test=BaseWorld.encode_string('mkdir test'), cleanup=BaseWorld.encode_string('rm -rf test'), variations=[]) tagent = agent(sleep_min=1, sleep_max=2, watchdog=0, executors=['sh'], platform='darwin') tsource = Source(id='123', name='test', facts=[], adjustments=[]) toperation = operation(name='test1', agents=tagent, adversary='hunter', source=tsource) loop.run_until_complete(data_svc.store(tability)) loop.run_until_complete( data_svc.store( Obfuscator( name='plain-text', description= 'Does no obfuscation to any command, instead running it in plain text', module='plugins.stockpile.app.obfuscators.plain_text'))) yield (tability, tagent, toperation)
def dns_c2(loop, app_svc, contact_svc, data_svc, obfuscator): BaseWorld.apply_config(name='main', config={ 'app.contact.dns.domain': 'mycaldera.caldera', 'app.contact.dns.socket': '0.0.0.0:53', 'plugins': ['sandcat', 'stockpile'], 'crypt_salt': 'BLAH', 'api_key': 'ADMIN123', 'encryption_key': 'ADMIN123', 'exfil_dir': '/tmp' }) BaseWorld.apply_config(name='agents', config={ 'sleep_max': 5, 'sleep_min': 5, 'untrusted_timer': 90, 'watchdog': 0, 'implant_name': 'splunkd', 'bootstrap_abilities': ['43b3754c-def4-4699-a673-1d85648fda6a'] }) services = app_svc(loop).get_services() dns_c2 = DnsContact(services) return dns_c2
async def test_trait_with_multiple_variations_of_parts( self, setup_planning_test, planning_svc): _, agent, operation, ability = setup_planning_test encoded_command = BaseWorld.encode_string('#{a} #{a.b} #{a.b.c}') link = Link.load( dict(command=encoded_command, paw=agent.paw, ability=ability, executor=next(ability.executors), status=0)) input_facts = [ Fact(trait='a', value='1'), Fact(trait='a.b', value='2'), Fact(trait='a.b.c', value='3'), Fact(trait='server', value='5') ] new_links = await planning_svc.add_test_variants([link], agent, facts=input_facts) assert len(new_links) == 2 found_commands = set(x.command for x in new_links) assert len(found_commands) == 2 # the original and the replaced assert encoded_command in found_commands assert BaseWorld.encode_string('1 2 3') in found_commands
async def initialize(): with open(Path(__file__).parents[2] / 'conf' / 'default.yml', 'r') as fle: BaseWorld.apply_config('main', yaml.safe_load(fle)) with open(Path(__file__).parents[2] / 'conf' / 'payloads.yml', 'r') as fle: BaseWorld.apply_config('payloads', yaml.safe_load(fle)) app_svc = AppService(web.Application()) _ = DataService() _ = RestService() _ = PlanningService() _ = LearningService() auth_svc = AuthService() _ = ContactService() _ = FileSvc() services = app_svc.get_services() os.chdir(str(Path(__file__).parents[2])) await app_svc.register_contacts() await app_svc.load_plugins(['sandcat', 'ssl']) _ = await RestApi(services).enable() await auth_svc.apply(app_svc.application, auth_svc.get_config('users')) await auth_svc.set_login_handlers(services) return app_svc.application
def ssh_contact_base_world(): BaseWorld.apply_config(name='main', config={ 'app.contact.tunnel.ssh.user_name': 'sandcat', 'app.contact.tunnel.ssh.user_password': '******', 'app.contact.tunnel.ssh.socket': '0.0.0.0:8122', 'app.contact.tunnel.ssh.host_key_file': 'REPLACE_WITH_KEY_FILE_PATH,', 'app.contact.tunnel.ssh.host_key_passphrase': 'REPLACE_WITH_KEY_FILE_PASSPHRASE', 'plugins': ['sandcat', 'stockpile'], 'crypt_salt': 'BLAH', 'api_key': 'ADMIN123', 'encryption_key': 'ADMIN123', 'exfil_dir': '/tmp' }) BaseWorld.apply_config(name='agents', config={ 'sleep_max': 5, 'sleep_min': 5, 'untrusted_timer': 90, 'watchdog': 0, 'implant_name': 'splunkd', 'bootstrap_abilities': ['43b3754c-def4-4699-a673-1d85648fda6a'] })
def setup_rest_svc_test(loop, data_svc): BaseWorld.apply_config(name='default', config={ 'app.contact.http': '0.0.0.0', 'plugins': ['sandcat', 'stockpile'], 'crypt_salt': 'BLAH', 'api_key': 'ADMIN123', 'encryption_key': 'ADMIN123', 'exfil_dir': '/tmp' }) loop.run_until_complete( data_svc.store( Ability(ability_id='123', test=BaseWorld.encode_string('curl #{app.contact.http}'), variations=[]))) loop.run_until_complete( data_svc.store( Adversary(adversary_id='123', name='test', description='test', phases=[]))) loop.run_until_complete( data_svc.store(Agent(paw='123', sleep_min=2, sleep_max=8, watchdog=0))) loop.run_until_complete( data_svc.store( Planner(planner_id='123', name='test', module='test', params=dict()))) loop.run_until_complete( data_svc.store(Source(identifier='123', name='test', facts=[])))
def dns_contact_base_world(): BaseWorld.apply_config(name='main', config={ 'app.contact.dns.domain': 'mycaldera.caldera', 'app.contact.dns.socket': '0.0.0.0:53', 'plugins': ['sandcat', 'stockpile'], 'crypt_salt': 'BLAH', 'api_key': 'ADMIN123', 'encryption_key': 'ADMIN123', 'exfil_dir': '/tmp' }) BaseWorld.apply_config(name='agents', config={ 'sleep_max': 5, 'sleep_min': 5, 'untrusted_timer': 90, 'watchdog': 0, 'implant_name': 'splunkd', 'bootstrap_abilities': ['43b3754c-def4-4699-a673-1d85648fda6a'] })
async def test_same_fact_different_agents(self, setup_learning_service, learning_svc, knowledge_svc): operation, link1, link2 = setup_learning_service link1.id = 'link1' link2.id = 'link2' operation.add_link(link1) all_facts = await operation.all_facts() await learning_svc.learn( facts=all_facts, link=link1, blob=BaseWorld.encode_string( 'i contain 1 ip address 192.168.0.1 and one file /etc/host.txt. that is all.' ), operation=operation) operation.add_link(link2) all_facts = await operation.all_facts() await learning_svc.learn( facts=all_facts, link=link2, blob=BaseWorld.encode_string( 'i contain 1 ip address 192.168.0.1 and one file /etc/host.txt. that is all.' ), operation=operation) knowledge_facts = await knowledge_svc.get_facts( dict(source=operation.id)) assert len(link1.facts) == 2 assert len(link2.facts) == 2 assert len(knowledge_facts) == 2 assert len(knowledge_facts[0].collected_by) == 2
def test_update_config_plugin(self): # update plugin property self.assertEqual(['sandcat', 'stockpile'], BaseWorld.get_config('plugins')) self.run_async( self.rest_svc.update_config(data=dict(prop='plugin', value='ssl'))) self.assertEqual(['sandcat', 'stockpile', 'ssl'], BaseWorld.get_config('plugins'))
async def start_server(): await auth_svc.apply(app_svc.application, BaseWorld.get_config('users')) app_svc.application.router.add_static('/docs/', 'docs/_build/html', append_version=True) runner = web.AppRunner(app_svc.application) await runner.setup() await web.TCPSite(runner, '0.0.0.0', BaseWorld.get_config('port')).start()
def _register_agent(ability_id): """ Registers an agent with caldera -- the agent's launch commands and variations will be displayed in the 'Deploy Agent' modal of the web interface. """ agents = set(BaseWorld.get_config(name='agents', prop='deployments')) agents.add(ability_id) BaseWorld.set_config(name='agents', prop='deployments', value=list(agents))
def test_apply_and_retrieve_config(self): new_config = dict(name='newconfig', config={ 'app.unit.test': 'abcd12345', 'plugins': ['stockpile'] }) BaseWorld.apply_config(**new_config) assert BaseWorld.get_config(name='newconfig') == new_config['config']
def test_encode_and_decode_string(self): plaintext = 'unit testing string' encoded_text = 'dW5pdCB0ZXN0aW5nIHN0cmluZw==' encoded_str = BaseWorld.encode_string(plaintext) assert encoded_str == encoded_text decoded_str = BaseWorld.decode_bytes(encoded_text) assert decoded_str == plaintext
def test_update_config_plugin(self, loop, rest_svc): internal_rest_svc = rest_svc(loop) # update plugin property assert ['sandcat', 'stockpile'] == BaseWorld.get_config('plugins') loop.run_until_complete( internal_rest_svc.update_config( data=dict(prop='plugin', value='ssl'))) assert ['sandcat', 'stockpile', 'ssl'] == BaseWorld.get_config('plugins')
async def wrapper(*args, **kwargs): agent, instructions = await func(*args, **kwargs) log = dict(paw=agent.paw, instructions=[ BaseWorld.decode_bytes(i.command) for i in instructions ], date=BaseWorld.get_current_timestamp()) args[0].report[agent.contact.upper()].append(log) return agent, instructions
def setup_rest_svc_test(loop, data_svc): BaseWorld.apply_config(name='main', config={ 'app.contact.http': '0.0.0.0', 'plugins': ['sandcat', 'stockpile'], 'crypt_salt': 'BLAH', 'api_key': 'ADMIN123', 'encryption_key': 'ADMIN123', 'exfil_dir': '/tmp' }) loop.run_until_complete( data_svc.store( Ability(ability_id='123', test=BaseWorld.encode_string('curl #{app.contact.http}'), variations=[], executor='psh', platform='windows'))) adversary = Adversary(adversary_id='123', name='test', description='test', atomic_ordering=[]) loop.run_until_complete(data_svc.store(adversary)) agent = Agent(paw='123', sleep_min=2, sleep_max=8, watchdog=0, executors=['pwsh', 'psh'], platform='windows') loop.run_until_complete(data_svc.store(agent)) loop.run_until_complete( data_svc.store( Planner(planner_id='123', name='test', module='test', params=dict()))) source = Source(id='123', name='test', facts=[], adjustments=[]) loop.run_until_complete(data_svc.store(source)) loop.run_until_complete( data_svc.store( Operation(name='test', agents=[agent], adversary=adversary, id='123', source=source))) loop.run_until_complete( data_svc.store( Obfuscator( name='plain-text', description= 'Does no obfuscation to any command, instead running it in plain text', module='plugins.stockpile.app.obfuscators.plain_text')))
def test_retrieve_config(self, loop, app_svc): BaseWorld.apply_config(name='main', config={'app.contact.gist': 'arandomkeythatisusedtoconnecttogithubapi', 'plugins': ['sandcat', 'stockpile'], 'crypt_salt': 'BLAH', 'api_key': 'ADMIN123', 'encryption_key': 'ADMIN123', 'exfil_dir': '/tmp'}) gist_c2 = Gist(app_svc(loop).get_services()) loop.run_until_complete(gist_c2.start()) assert gist_c2.retrieve_config() == 'arandomkeythatisusedtoconnecttogithubapi'
def setup_planning_test(loop, ability, agent, operation, data_svc): tability = ability(ability_id='123', executor='sh', test=BaseWorld.encode_string('mkdir test'), cleanup=BaseWorld.encode_string('rm -rf test'), variations=[]) tagent = agent(sleep_min=1, sleep_max=2, watchdog=0) toperation = operation(name='test1', agents=tagent, adversary='hunter') loop.run_until_complete(data_svc.store(tability)) yield (tability, tagent, toperation)
async def enable(services): BaseWorld.apply_config('gameboard', BaseWorld.strip_yml('plugins/gameboard/conf/gameboard.yml')[0]) gameboard_svc = GameboardService(services) gameboard_api = GameboardApi(services) app = services.get('app_svc').application app.router.add_route('GET', '/plugin/gameboard/gui', gameboard_api.splash) app.router.add_route('POST', '/plugin/gameboard/pieces', gameboard_api.get_pieces) app.router.add_route('POST', '/plugin/gameboard/pin', gameboard_api.update_pin) app.router.add_route('POST', '/plugin/gameboard/analytic', gameboard_api.analytic) app.router.add_route('POST', '/plugin/gameboard/detection', gameboard_api.verify_detection)
async def enable(services): BaseWorld.apply_config('response', BaseWorld.strip_yml('plugins/response/conf/response.yml')[0]) response_svc = ResponseService(services) app = services.get('app_svc').application app.router.add_route('GET', '/plugin/responder/gui', response_svc.splash) app.router.add_route('POST', '/plugin/responder/update', response_svc.update_responder) _register_agent('1837b43e-4fff-46b2-a604-a602f7540469') # Elasticat agent await response_svc.register_handler(services.get('event_svc'))
async def _prepare_executor(test): command = _use_default_inputs(test, test['executor']['command']) command = _handle_multiline_commands(command) cleanup = _use_default_inputs(test, test['executor'].get('cleanup_command', '')) cleanup = _handle_multiline_commands(cleanup) encoded_command = BaseWorld.encode_string(command) encoded_cleanup = BaseWorld.encode_string(cleanup) return (encoded_command, encoded_cleanup)
def test_link_fact_coverage(self, loop, setup_planning_test, planning_svc): _, agent, operation, ability = setup_planning_test link = Link.load(dict(command=BaseWorld.encode_string(test_string), paw=agent.paw, ability=ability, status=0)) f1 = Fact(trait='a.b.c', value='1') f2 = Fact(trait='a.b.d', value='2') f3 = Fact(trait='a.b.e', value='3') gen = loop.run_until_complete(planning_svc.add_test_variants([link], agent, facts=[f1, f2, f3])) assert len(gen) == 2 assert BaseWorld.decode_bytes(gen[1].display['command']) == target_string
async def enable(services): environments = BaseWorld.strip_yml( 'plugins/builder/conf/environments.yml')[0] BaseWorld.apply_config('build', environments) build_svc = BuildService(services) await build_svc.stage_enabled_dockers() envs = environments['enabled'] builder_gui = BuilderGUI(services, name, description, envs) app = services.get('app_svc').application app.router.add_route('GET', '/plugin/builder/gui', builder_gui.splash)
def setUp(self): self.initialize() self.ability = Ability(ability_id='123', executor='sh', test=BaseWorld.encode_string('mkdir test'), cleanup=BaseWorld.encode_string('rm -rf test')) self.agent = Agent(sleep_min=1, sleep_max=2, watchdog=0) self.operation = Operation(name='test1', agents=self.agent, adversary='hunter') self.run_async(self.data_svc.store(self.ability))
def setUp(self): self.initialize() BaseWorld.apply_config({ 'app.contact.http': '0.0.0.0', 'plugins': ['sandcat', 'stockpile'] }) self.run_async( self.data_svc.store( Ability( ability_id='123', test=BaseWorld.encode_string('curl #{app.contact.http}'))))
def base_world(): BaseWorld.apply_config( name='main', config={ 'app.foo': 'foo', 'app.bar': 'bar', 'auth.baz': 'not an app. item' } ) yield BaseWorld BaseWorld.clear_config()
def test_update_config(self): # check that an ability reflects the value in app. property pre_ability = self.run_async(self.data_svc.locate('abilities', dict(ability_id='123'))) self.assertEqual('0.0.0.0', BaseWorld.get_config('app.contact.http')) self.assertEqual('curl 0.0.0.0', BaseWorld.decode_bytes(pre_ability[0].test)) # update property self.run_async(self.rest_svc.update_config(data=dict(prop='app.contact.http', value='127.0.0.1'))) # verify ability reflects new value post_ability = self.run_async(self.data_svc.locate('abilities', dict(ability_id='123'))) self.assertEqual('127.0.0.1', BaseWorld.get_config('app.contact.http')) self.assertEqual('curl 127.0.0.1', BaseWorld.decode_bytes(post_ability[0].test))
async def enable(services): BaseWorld.apply_config('debrief', BaseWorld.strip_yml('plugins/debrief/conf/default.yml')[0]) app = services.get('app_svc').application debrief_gui = DebriefGui(services) app.router.add_static('/debrief', 'plugins/debrief/static/', append_version=True) app.router.add_static('/logodebrief', 'plugins/debrief/uploads/', append_version=True) app.router.add_route('GET', '/plugin/debrief/gui', debrief_gui.splash) app.router.add_route('POST', '/plugin/debrief/report', debrief_gui.report) app.router.add_route('*', '/plugin/debrief/graph', debrief_gui.graph) app.router.add_route('POST', '/plugin/debrief/pdf', debrief_gui.download_pdf) app.router.add_route('POST', '/plugin/debrief/json', debrief_gui.download_json) app.router.add_route('GET', '/plugin/debrief/logos', debrief_gui.all_logos) app.router.add_route('POST', '/plugin/debrief/logo', debrief_gui.upload_logo)