Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app/objects/c_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,12 +338,13 @@ async def report(self, file_svc, data_svc, output=False):
step_report['output'] = json.loads(results.replace('\\r\\n', '').replace('\\n', ''))
if step.agent_reported_time:
step_report['agent_reported_time'] = step.agent_reported_time.strftime(self.TIME_FORMAT)
agents_steps[step.paw]['steps'].append(step_report)
agents_steps.setdefault(step.paw, {'steps': []})['steps'].append(step_report)
report['steps'] = agents_steps
report['skipped_abilities'] = await self.get_skipped_abilities_by_agent(data_svc)
return report
except Exception:
logging.error('Error saving operation report (%s)' % self.name, exc_info=True)
logging.error('Error generating operation report (%s)' % self.name, exc_info=True)
raise
Comment on lines +346 to +347

async def event_logs(self, file_svc, data_svc, output=False):
# Ignore discarded / high visibility links that did not actually run.
Expand Down Expand Up @@ -471,7 +472,9 @@ async def _get_all_possible_abilities_by_agent(self, data_svc):
for link in self.chain:
if link.ability.ability_id not in self.adversary.atomic_ordering:
matching_abilities = await data_svc.locate('abilities', match=dict(ability_id=link.ability.ability_id))
abilities_by_agent[link.paw]['all_abilities'].extend(matching_abilities)
entry = abilities_by_agent.get(link.paw)
if entry:
entry['all_abilities'].extend(matching_abilities)
return abilities_by_agent

def _check_reason_skipped(self, agent, ability, op_facts, state, agent_executors, agent_ran):
Expand Down
41 changes: 41 additions & 0 deletions tests/objects/test_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,47 @@ async def test_add_ignored_link(self, make_test_link, operation_agent):
assert test_link.id in op.ignored_links
assert len(op.ignored_links) == 1

async def test_report_includes_steps_for_agents_not_in_host_group(
self, operation_agent, operation_adversary, executor, ability, operation_link,
encoded_command, parse_datestring, file_svc, data_svc, knowledge_svc, fire_event_mock):
"""Regression test for issue #3048: a link whose paw is absent from operation.agents
must not cause report() to silently return None (i.e. download as 'Null')."""
Comment on lines +629 to +633
from app.objects.c_planner import Planner
from app.objects.c_objective import Objective

op = Operation(name='report-test', agents=[operation_agent], adversary=operation_adversary)
op.set_start_details()
op.planner = Planner(planner_id='testplanner', name='test_planner', module='test', params=None)
op.objective = Objective(id='obj1', name='test objective')

exe = executor(name='psh', platform='windows', command='whoami')
ab = ability(ability_id='rep123', tactic='test tactic', technique_id='T0000',
technique_name='test technique', name='test ability',
description='test desc', executors=[exe])

known_link = operation_link(
command=encoded_command('whoami'),
plaintext_command=encoded_command('whoami'),
paw=operation_agent.paw,
ability=ab, executor=exe, status=0, host=operation_agent.host, pid=1,
decide=parse_datestring(LINK1_DECIDE_TIME),
)
orphan_paw = 'orphan-paw-not-in-agents'
orphan_link = operation_link(
command=encoded_command('id'),
plaintext_command=encoded_command('id'),
paw=orphan_paw,
ability=ab, executor=exe, status=0, host='orphan-host', pid=2,
decide=parse_datestring(LINK2_DECIDE_TIME),
)
op.chain = [known_link, orphan_link]

report = await op.report(file_svc, data_svc, output=False)
assert report is not None, 'report() must not return None when a link paw is absent from agents'
assert 'steps' in report
assert orphan_paw in report['steps'], 'orphan paw steps must appear in report'
assert operation_agent.paw in report['steps'], 'known agent paw steps must appear in report'

async def test_operation_cleanup_status(self, fake_planning_svc, operation_agent):
services = {'planning_svc': fake_planning_svc}
op = Operation(name='test with cleanup', agents=[operation_agent], state='running')
Expand Down
Loading