|
1 | 1 | import glob |
2 | 2 | import json |
| 3 | +import logging |
3 | 4 | import yaml |
4 | 5 |
|
5 | 6 | from unittest import mock |
|
76 | 77 | } |
77 | 78 |
|
78 | 79 |
|
| 80 | +ABILITY_YAMLS = { |
| 81 | + 'plugins/testing/data/discovery/764efa883dda1e11db47671c4a3bbd9e.yml': [yaml.safe_load(''' |
| 82 | +--- |
| 83 | +
|
| 84 | +- id: 764efa883dda1e11db47671c4a3bbd9e |
| 85 | + name: Find deletable dirs (per user) |
| 86 | + description: Discover all directories containing deletable files by user |
| 87 | + tactic: discovery |
| 88 | + technique: |
| 89 | + attack_id: T1082 |
| 90 | + name: System Information Discovery |
| 91 | + platforms: |
| 92 | + darwin: |
| 93 | + sh: |
| 94 | + command: | |
| 95 | + testcommand |
| 96 | + linux: |
| 97 | + sh: |
| 98 | + command: | |
| 99 | + testcommand |
| 100 | +''')], |
| 101 | + 'plugins/testing/data/discovery/848aa201-4b00-4f08-ae3a-3e84dfb5065c.yml': [yaml.safe_load(''' |
| 102 | +--- |
| 103 | +
|
| 104 | +- id: 848aa201-4b00-4f08-ae3a-3e84dfb5065c |
| 105 | + name: Find deletable dirs (per user) |
| 106 | + description: Discover all directories containing deletable files by user |
| 107 | + tactic: discovery |
| 108 | + technique: |
| 109 | + attack_id: T1082 |
| 110 | + name: System Information Discovery |
| 111 | + platforms: |
| 112 | + darwin: |
| 113 | + sh: |
| 114 | + command: | |
| 115 | + testcommand |
| 116 | + linux: |
| 117 | + sh: |
| 118 | + command: | |
| 119 | + testcommand |
| 120 | +''')], |
| 121 | + 'plugins/testing/data/discovery/101.yml': [yaml.safe_load(''' |
| 122 | +--- |
| 123 | +
|
| 124 | +- id: 101 |
| 125 | + name: Find deletable dirs (per user) |
| 126 | + description: Discover all directories containing deletable files by user |
| 127 | + tactic: purposefullywrongtactic |
| 128 | + technique: |
| 129 | + attack_id: T1082 |
| 130 | + name: System Information Discovery |
| 131 | + platforms: |
| 132 | + darwin: |
| 133 | + sh: |
| 134 | + command: | |
| 135 | + testcommand |
| 136 | + linux: |
| 137 | + sh: |
| 138 | + command: | |
| 139 | + testcommand |
| 140 | +''')], |
| 141 | + 'plugins/testing/data/discovery/102.yml': [yaml.safe_load(''' |
| 142 | +malformed |
| 143 | +''')], |
| 144 | +} |
| 145 | + |
| 146 | + |
79 | 147 | def strip_payload_yaml(path): |
80 | 148 | return PAYLOAD_CONFIG_YAMLS.get(path, []) |
81 | 149 |
|
82 | 150 |
|
| 151 | +def strip_ability_yaml(path): |
| 152 | + return ABILITY_YAMLS.get(path, []) |
| 153 | + |
| 154 | + |
83 | 155 | class TestDataService: |
84 | 156 | mock_payload_config = dict() |
85 | 157 |
|
@@ -234,3 +306,74 @@ def _mock_apply_payload_config(config=None, **_): |
234 | 306 | } |
235 | 307 | } |
236 | 308 | mock_apply_config2.assert_called_once_with(name='payloads', config=expected_config_part2) |
| 309 | + |
| 310 | + @mock.patch.object(logging.Logger, 'warn') |
| 311 | + @mock.patch.object(BaseWorld, 'strip_yml', wraps=strip_ability_yaml) |
| 312 | + async def test_load_ability_file(self, mock_strip_yml, mock_warn, data_svc): |
| 313 | + want_executors = [ |
| 314 | + Executor(name='sh', platform='darwin', command='testcommand', |
| 315 | + code=None, language=None, build_target=None, |
| 316 | + payloads=None, uploads=None, timeout=60, |
| 317 | + parsers=[], cleanup=None, variations=[]), |
| 318 | + Executor(name='sh', platform='linux', command='testcommand', |
| 319 | + code=None, language=None, build_target=None, |
| 320 | + payloads=None, uploads=None, timeout=60, |
| 321 | + parsers=[], cleanup=None, variations=[]) |
| 322 | + ] |
| 323 | + with patch.object(DataService, '_create_ability', return_value=None) as mock_create_ability: |
| 324 | + await data_svc.load_ability_file('plugins/testing/data/discovery/764efa883dda1e11db47671c4a3bbd9e.yml', BaseWorld.Access.RED) |
| 325 | + mock_create_ability.assert_called_once_with(ability_id='764efa883dda1e11db47671c4a3bbd9e', name='Find deletable dirs (per user)', |
| 326 | + description='Discover all directories containing deletable files by user', |
| 327 | + tactic='discovery', technique_id='T1082', technique_name='System Information Discovery', |
| 328 | + executors=want_executors, requirements=[], privilege=None, |
| 329 | + repeatable=False, buckets=['discovery'], access=BaseWorld.Access.RED, singleton=False, plugin='testing') |
| 330 | + |
| 331 | + with patch.object(DataService, '_create_ability', return_value=None) as mock_create_ability: |
| 332 | + await data_svc.load_ability_file('plugins/testing/data/discovery/848aa201-4b00-4f08-ae3a-3e84dfb5065c.yml', BaseWorld.Access.RED) |
| 333 | + mock_create_ability.assert_called_once_with(ability_id='848aa201-4b00-4f08-ae3a-3e84dfb5065c', name='Find deletable dirs (per user)', |
| 334 | + description='Discover all directories containing deletable files by user', |
| 335 | + tactic='discovery', technique_id='T1082', technique_name='System Information Discovery', |
| 336 | + executors=want_executors, requirements=[], privilege=None, |
| 337 | + repeatable=False, buckets=['discovery'], access=BaseWorld.Access.RED, singleton=False, plugin='testing') |
| 338 | + |
| 339 | + with patch.object(DataService, '_create_ability', return_value=None) as mock_create_ability: |
| 340 | + await data_svc.load_ability_file('plugins/testing/data/discovery/101.yml', BaseWorld.Access.RED) |
| 341 | + mock_warn.assert_any_call('Tactic for ability=101 is not in the ability file path plugins/testing/data/discovery/101.yml.') |
| 342 | + mock_warn.assert_called_with('Please check that the ability is labeled with the correct tactic and is in the correct location.') |
| 343 | + mock_create_ability.assert_called_once_with(ability_id='101', name='Find deletable dirs (per user)', |
| 344 | + description='Discover all directories containing deletable files by user', |
| 345 | + tactic='purposefullywrongtactic', technique_id='T1082', technique_name='System Information Discovery', |
| 346 | + executors=want_executors, requirements=[], privilege=None, |
| 347 | + repeatable=False, buckets=['purposefullywrongtactic'], access=BaseWorld.Access.RED, singleton=False, plugin='testing') |
| 348 | + |
| 349 | + with patch.object(DataService, '_create_ability', return_value=None) as mock_create_ability: |
| 350 | + with patch.object(logging.Logger, 'error') as mock_error: |
| 351 | + await data_svc.load_ability_file('plugins/testing/data/discovery/102.yml', BaseWorld.Access.RED) |
| 352 | + mock_create_ability.assert_not_called() |
| 353 | + assert mock_error.called |
| 354 | + |
| 355 | + # Test exception |
| 356 | + with patch.object(DataService, '_create_ability', side_effect=Exception('mockexception')): |
| 357 | + with patch.object(logging.Logger, 'exception') as mock_exception: |
| 358 | + await data_svc.load_ability_file('plugins/testing/data/discovery/101.yml', BaseWorld.Access.RED) |
| 359 | + mock_exception.assert_called_once_with(mock.ANY) |
| 360 | + assert 'Failed to load ability file plugins/testing/data/discovery/101.yml' in mock_exception.call_args.args[0] |
| 361 | + |
| 362 | + def test_get_plugin_name(self, data_svc): |
| 363 | + assert 'test' == data_svc._get_plugin_name('plugins/test') |
| 364 | + assert 'test' == data_svc._get_plugin_name('plugins/test/') |
| 365 | + assert 'test' == data_svc._get_plugin_name('plugins/test/data') |
| 366 | + assert 'test' == data_svc._get_plugin_name('plugins/test/data/abilities') |
| 367 | + assert 'test' == data_svc._get_plugin_name('plugins/test/data/abilities/collection/123.yml') |
| 368 | + assert 'test' == data_svc._get_plugin_name('/full/path/to/plugins/test/data/abilities/collection/123.yml') |
| 369 | + assert '' == data_svc._get_plugin_name('test') |
| 370 | + assert '' == data_svc._get_plugin_name('plugins') |
| 371 | + assert '' == data_svc._get_plugin_name('plugins/') |
| 372 | + assert '' == data_svc._get_plugin_name('/full/path/to/plugins') |
| 373 | + assert '' == data_svc._get_plugin_name('/full/path/to/plugins/') |
| 374 | + assert '' == data_svc._get_plugin_name('plugin/test') |
| 375 | + assert '' == data_svc._get_plugin_name('plugin/test/') |
| 376 | + assert '' == data_svc._get_plugin_name('plugin/test/data') |
| 377 | + assert '' == data_svc._get_plugin_name('plugin/test/data/abilities') |
| 378 | + assert '' == data_svc._get_plugin_name('plugin/test/data/abilities/collection/123.yml') |
| 379 | + assert '' == data_svc._get_plugin_name('/full/path/to/plugin/test/data/abilities/collection/123.yml') |
0 commit comments