blob: bf6528bb719f058a446243c1684d6387b8f73378 [file]
import asyncio
import glob
import json
import yaml
from unittest import mock
from unittest.mock import patch, call
from app.objects.c_ability import Ability
from app.objects.c_adversary import Adversary
from app.objects.c_agent import Agent
from app.objects.c_operation import Operation
from app.objects.c_planner import Planner
from app.objects.c_plugin import Plugin
from app.objects.secondclass.c_executor import Executor
from app.service.data_svc import DataService
from app.utility.base_world import BaseWorld
PAYLOAD_CONFIG_YAMLS = {
'path1': [yaml.safe_load('''
---
id: testid1
name: Plugin1 Payloads
standard_payloads:
file.exe:
description: file desc
id: fileid1
file.ps1:
description: file desc
id: fileid2
special_payloads:
file.go:
description: file desc
id: specialid1
service: stockpile_svc
function: funcname
extensions:
.donut: plugins.stockpile.app.donut.donut_handler
''')],
'path2': [yaml.safe_load('''
---
id: testid2
name: Plugin1 Payloads 2
standard_payloads:
file.py:
description: file desc
id: fileid3
file.txt:
description: file desc
id: fileid4
special_payloads:
file.cpp:
description: file desc
id: specialid2
service: stockpile_svc
function: funcname
''')],
'path3': [yaml.safe_load('''
---
id: testid3
name: Plugin2 Payloads
special_payloads:
special.py:
description: file desc
id: specialid3
service: stockpile_svc
function: funcname
file.cpp:
description: overridden desc
id: overridden
service: stockpile_svc
function: overridden
extensions:
.testext: handler
''')],
}
def strip_payload_yaml(path):
return PAYLOAD_CONFIG_YAMLS.get(path, [])
def async_mock_return(to_return):
mock_future = asyncio.Future()
mock_future.set_result(to_return)
return mock_future
class TestDataService:
mock_payload_config = dict()
def test_no_duplicate_adversary(self, event_loop, data_svc):
event_loop.run_until_complete(data_svc.store(
Adversary(adversary_id='123', name='test', description='test adversary', atomic_ordering=list())
))
event_loop.run_until_complete(data_svc.store(
Adversary(adversary_id='123', name='test', description='test adversary', atomic_ordering=list())
))
adversaries = event_loop.run_until_complete(data_svc.locate('adversaries'))
assert len(adversaries) == 1
for x in adversaries:
json.dumps(x.display)
def test_no_duplicate_planner(self, event_loop, data_svc):
event_loop.run_until_complete(data_svc.store(Planner(name='test', planner_id='some_id', module='some.path.here', params=None, description='description')))
event_loop.run_until_complete(data_svc.store(Planner(name='test', planner_id='some_id', module='some.path.here', params=None, description='description')))
planners = event_loop.run_until_complete(data_svc.locate('planners'))
assert len(planners) == 1
for x in planners:
json.dumps(x.display)
def test_multiple_agents(self, event_loop, data_svc):
event_loop.run_until_complete(data_svc.store(Agent(sleep_min=2, sleep_max=8, watchdog=0)))
event_loop.run_until_complete(data_svc.store(Agent(sleep_min=2, sleep_max=8, watchdog=0)))
agents = event_loop.run_until_complete(data_svc.locate('agents'))
assert len(agents) == 2
for x in agents:
json.dumps(x.display)
def test_no_duplicate_ability(self, event_loop, data_svc):
executor = Executor(name='special_executor', platform='darwin', command='whoami', payloads=['wifi.sh'])
event_loop.run_until_complete(data_svc.store(
Ability(ability_id='123', tactic='discovery', technique_id='1', technique_name='T1033', name='test',
description='find active user', privilege=None, executors=[executor])
))
event_loop.run_until_complete(data_svc.store(
Ability(ability_id='123', tactic='discovery', technique_id='1', technique_name='T1033', name='test',
description='find active user', privilege=None, executors=[executor])
))
abilities = event_loop.run_until_complete(data_svc.locate('abilities'))
assert len(abilities) == 1
def test_operation(self, event_loop, data_svc):
adversary = event_loop.run_until_complete(data_svc.store(
Adversary(adversary_id='123', name='test', description='test adversary', atomic_ordering=list())
))
event_loop.run_until_complete(data_svc.store(Operation(name='my first op', agents=[], adversary=adversary)))
operations = event_loop.run_until_complete(data_svc.locate('operations'))
assert len(operations) == 1
for x in operations:
json.dumps(x.display)
def test_remove(self, event_loop, data_svc):
a1 = event_loop.run_until_complete(data_svc.store(Agent(sleep_min=2, sleep_max=8, watchdog=0)))
agents = event_loop.run_until_complete(data_svc.locate('agents', match=dict(paw=a1.paw)))
assert len(agents) == 1
event_loop.run_until_complete(data_svc.remove('agents', match=dict(paw=a1.paw)))
agents = event_loop.run_until_complete(data_svc.locate('agents', match=dict(paw=a1.paw)))
assert len(agents) == 0
def test_no_autogen_cleanup_cmds(self, event_loop, data_svc):
cleanup_executor = Executor(name='sh', platform='linux', cleanup='rm #{payload}')
event_loop.run_until_complete(data_svc.store(
Ability(ability_id='4cd4eb44-29a7-4259-91ae-e457b283a880', tactic='defense-evasion', technique_id='T1070.004',
technique_name='Indicator Removal on Host: File Deletion', name='Delete payload',
description='Remove a downloaded payload file', privilege=None, executors=[cleanup_executor])
))
executor = Executor(name='special_executor', platform='darwin', command='whoami', payloads=['wifi.sh'])
event_loop.run_until_complete(data_svc.store(
Ability(ability_id='123', tactic='discovery', technique_id='1', technique_name='T1033', name='test',
description='find active user', privilege=None, executors=[executor])
))
event_loop.run_until_complete(data_svc._verify_abilities())
abilities = event_loop.run_until_complete(data_svc.locate('abilities', dict(ability_id='123')))
for ability in abilities:
for executor in ability.executors:
assert not executor.cleanup
@mock.patch.object(BaseWorld, 'strip_yml', wraps=strip_payload_yaml)
@mock.patch.object(DataService, '_apply_special_payload_hooks', return_value=async_mock_return(None))
@mock.patch.object(DataService, '_apply_special_extension_hooks', return_value=async_mock_return(None))
def test_load_payloads(self, mock_ext_hooks, mock_payload_hooks, mock_strip_yml, event_loop, data_svc):
def _mock_apply_payload_config(config=None, **_):
TestDataService.mock_payload_config = config
test_plugin = Plugin(data_dir='test_plugin/data')
test_plugin2 = Plugin(data_dir='test_plugin2/data')
with patch.object(glob, 'iglob', return_value=['path1', 'path2']) as mock_iglob:
with patch.object(BaseWorld, 'apply_config', wraps=_mock_apply_payload_config) as mock_apply_config:
with patch.object(DataService, 'get_config', return_value=self.mock_payload_config):
event_loop.run_until_complete(data_svc._load_payloads(test_plugin))
mock_iglob.assert_called_once_with('test_plugin/data/payloads/*.yml', recursive=False)
mock_strip_yml.assert_has_calls([call('path1'), call('path2')])
mock_payload_hooks.assert_has_calls([
call(PAYLOAD_CONFIG_YAMLS['path1'][0]['special_payloads']),
call(PAYLOAD_CONFIG_YAMLS['path2'][0]['special_payloads']),
], any_order=True)
mock_ext_hooks.assert_has_calls([
call(PAYLOAD_CONFIG_YAMLS['path1'][0]['extensions']),
], any_order=True)
expected_config_part1 = {
'standard_payloads': {
'file.exe': PAYLOAD_CONFIG_YAMLS['path1'][0]['standard_payloads']['file.exe'],
'file.ps1': PAYLOAD_CONFIG_YAMLS['path1'][0]['standard_payloads']['file.ps1'],
'file.py': PAYLOAD_CONFIG_YAMLS['path2'][0]['standard_payloads']['file.py'],
'file.txt': PAYLOAD_CONFIG_YAMLS['path2'][0]['standard_payloads']['file.txt'],
},
'special_payloads': {
'file.go': PAYLOAD_CONFIG_YAMLS['path1'][0]['special_payloads']['file.go'],
'file.cpp': PAYLOAD_CONFIG_YAMLS['path2'][0]['special_payloads']['file.cpp'],
},
'extensions': {
'.donut': PAYLOAD_CONFIG_YAMLS['path1'][0]['extensions']['.donut'],
}
}
mock_apply_config.assert_called_once_with(name='payloads', config=expected_config_part1)
with patch.object(glob, 'iglob', return_value=['path3']) as mock_iglob2:
with patch.object(BaseWorld, 'apply_config', wraps=_mock_apply_payload_config) as mock_apply_config2:
with patch.object(DataService, 'get_config', return_value=self.mock_payload_config):
event_loop.run_until_complete(data_svc._load_payloads(test_plugin2))
mock_iglob2.assert_called_once_with('test_plugin2/data/payloads/*.yml', recursive=False)
mock_strip_yml.assert_called_with('path3')
mock_payload_hooks.assert_called_with(PAYLOAD_CONFIG_YAMLS['path3'][0]['special_payloads'])
mock_ext_hooks.assert_called_with(PAYLOAD_CONFIG_YAMLS['path3'][0]['extensions'])
expected_config_part2 = {
'standard_payloads': {
'file.exe': PAYLOAD_CONFIG_YAMLS['path1'][0]['standard_payloads']['file.exe'],
'file.ps1': PAYLOAD_CONFIG_YAMLS['path1'][0]['standard_payloads']['file.ps1'],
'file.py': PAYLOAD_CONFIG_YAMLS['path2'][0]['standard_payloads']['file.py'],
'file.txt': PAYLOAD_CONFIG_YAMLS['path2'][0]['standard_payloads']['file.txt'],
},
'special_payloads': {
'file.go': PAYLOAD_CONFIG_YAMLS['path1'][0]['special_payloads']['file.go'],
# test override
'file.cpp': PAYLOAD_CONFIG_YAMLS['path3'][0]['special_payloads']['file.cpp'],
'special.py': PAYLOAD_CONFIG_YAMLS['path3'][0]['special_payloads']['special.py'],
},
'extensions': {
'.donut': PAYLOAD_CONFIG_YAMLS['path1'][0]['extensions']['.donut'],
'.testext': PAYLOAD_CONFIG_YAMLS['path3'][0]['extensions']['.testext'],
}
}
mock_apply_config2.assert_called_once_with(name='payloads', config=expected_config_part2)