Skip to content

Commit 5763477

Browse files
committed
Merge branch '3167-ui-credentials-collectors' into 3167-mock-credential-collector
Issue #3167 PR #3258
2 parents 2dc194e + 88b38f9 commit 5763477

File tree

22 files changed

+151
-124
lines changed

22 files changed

+151
-124
lines changed

envs/monkey_zoo/blackbox/test_configurations/credentials_reuse_ssh_key.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import dataclasses
22
from typing import Dict, Mapping
33

4-
from common.agent_configuration import AgentConfiguration, PluginConfiguration
4+
from common.agent_configuration import AgentConfiguration
55
from common.credentials import Credentials, Password, Username
66

77
from .noop import noop_test_configuration
@@ -34,10 +34,7 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
3434

3535

3636
def _add_credentials_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
37-
credentials_collectors = [
38-
PluginConfiguration(name="SSHCollector", options={}),
39-
]
40-
37+
credentials_collectors: Dict[str, Mapping] = {"SSHCollector": {}}
4138
return add_credentials_collectors(
4239
agent_configuration, credentials_collectors=credentials_collectors
4340
)

envs/monkey_zoo/blackbox/test_configurations/depth_1_a.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
6464

6565

6666
def _add_credentials_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
67+
credentials_collectors: Dict[str, Mapping] = {"MimikatzCollector": {}}
6768
return add_credentials_collectors(
68-
agent_configuration, [PluginConfiguration(name="MimikatzCollector", options={})]
69+
agent_configuration, credentials_collectors=credentials_collectors
6970
)
7071

7172

envs/monkey_zoo/blackbox/test_configurations/noop.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
_agent_configuration = AgentConfiguration(
4141
keep_tunnel_open_time=0,
42-
credentials_collectors=[],
42+
credentials_collectors={},
4343
payloads={},
4444
propagation=_propagation_configuration,
4545
)

envs/monkey_zoo/blackbox/test_configurations/utils.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ def add_subnets(
4545

4646

4747
def add_credentials_collectors(
48-
agent_configuration: AgentConfiguration, credentials_collectors: Sequence[PluginConfiguration]
48+
agent_configuration: AgentConfiguration, credentials_collectors: Dict[str, Mapping]
4949
) -> AgentConfiguration:
5050
agent_configuration_copy = agent_configuration.copy(deep=True)
51-
agent_configuration_copy.credentials_collectors = tuple(credentials_collectors)
51+
agent_configuration_copy.credentials_collectors = credentials_collectors
5252

5353
return agent_configuration_copy
5454

envs/monkey_zoo/blackbox/test_configurations/wmi_mimikatz.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import dataclasses
22
from typing import Dict, Mapping
33

4-
from common.agent_configuration import AgentConfiguration, PluginConfiguration
4+
from common.agent_configuration import AgentConfiguration
55
from common.credentials import Credentials, Password, Username
66

77
from .noop import noop_test_configuration
@@ -33,8 +33,9 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
3333

3434

3535
def _add_credentials_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
36+
credentials_collectors: Dict[str, Mapping] = {"MimikatzCollector": {}}
3637
return add_credentials_collectors(
37-
agent_configuration, [PluginConfiguration(name="MimikatzCollector", options={})]
38+
agent_configuration, credentials_collectors=credentials_collectors
3839
)
3940

4041

@@ -48,7 +49,6 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
4849
test_agent_configuration = _add_subnets(test_agent_configuration)
4950
test_agent_configuration = _add_credentials_collectors(test_agent_configuration)
5051
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
51-
test_agent_configuration = _add_credentials_collectors(test_agent_configuration)
5252

5353
CREDENTIALS = (
5454
Credentials(identity=Username(username="Administrator"), secret=None),

monkey/common/agent_configuration/agent_configuration.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from typing import Dict, Tuple
1+
from typing import Dict
22

33
from pydantic import Field, confloat
44

55
from common.base_models import MutableInfectionMonkeyBaseModel
66

7-
from .agent_sub_configurations import PluginConfiguration, PropagationConfiguration
7+
from .agent_sub_configurations import PropagationConfiguration
88

99

1010
class AgentConfiguration(MutableInfectionMonkeyBaseModel):
@@ -15,9 +15,11 @@ class AgentConfiguration(MutableInfectionMonkeyBaseModel):
1515
"seconds)",
1616
default=30,
1717
)
18-
credentials_collectors: Tuple[PluginConfiguration, ...] = Field(
19-
title="Credentials collectors",
20-
description="Configure options for the attack’s credentials collection stage",
18+
credentials_collectors: Dict[str, Dict] = Field(
19+
title="Enabled credentials collectors",
20+
description="Click on a credentials collector to get more information"
21+
" about it. \n \u26A0 Note that using unsafe options may"
22+
" result in unexpected behavior on the machine.",
2123
)
2224
payloads: Dict[str, Dict] = Field(
2325
title="Payloads", description="Configure payloads that Agents will execute"

monkey/common/agent_configuration/default_agent_configuration.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from copy import deepcopy
2+
from typing import Dict
23

34
from . import AgentConfiguration
45
from .agent_sub_configurations import (
@@ -12,11 +13,7 @@
1213
TCPScanConfiguration,
1314
)
1415

15-
CREDENTIALS_COLLECTORS = ("MimikatzCollector", "SSHCollector")
16-
17-
CREDENTIALS_COLLECTOR_CONFIGURATION = tuple(
18-
PluginConfiguration(name=collector, options={}) for collector in CREDENTIALS_COLLECTORS
19-
)
16+
CREDENTIALS_COLLECTORS: Dict[str, Dict] = {"MimikatzCollector": {}, "SSHCollector": {}}
2017

2118
RANSOMWARE_OPTIONS = {
2219
"encryption": {
@@ -93,10 +90,10 @@
9390

9491
DEFAULT_AGENT_CONFIGURATION = AgentConfiguration(
9592
keep_tunnel_open_time=30,
96-
credentials_collectors=CREDENTIALS_COLLECTOR_CONFIGURATION,
93+
credentials_collectors=CREDENTIALS_COLLECTORS,
9794
payloads=PAYLOAD_CONFIGURATION,
9895
propagation=PROPAGATION_CONFIGURATION,
9996
)
10097

10198
DEFAULT_RANSOMWARE_AGENT_CONFIGURATION = deepcopy(DEFAULT_AGENT_CONFIGURATION)
102-
DEFAULT_RANSOMWARE_AGENT_CONFIGURATION.credentials_collectors = tuple()
99+
DEFAULT_RANSOMWARE_AGENT_CONFIGURATION.credentials_collectors = {}

monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def __init__(self, agent_event_queue: IAgentEventQueue, agent_id: AgentID):
3131
self._agent_event_queue = agent_event_queue
3232
self._agent_id = agent_id
3333

34-
def collect_credentials(self, options=None) -> Sequence[Credentials]:
34+
def run(self, options=None, interrupt=None) -> Sequence[Credentials]:
3535
logger.info("Attempting to collect windows credentials with pypykatz.")
3636
windows_credentials = pypykatz_handler.get_windows_creds()
3737

monkey/infection_monkey/credential_collectors/ssh_collector/ssh_credential_collector.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def __init__(self, agent_event_queue: IAgentEventQueue, agent_id: AgentID):
1919
self._agent_event_queue = agent_event_queue
2020
self._agent_id = agent_id
2121

22-
def collect_credentials(self, _options=None) -> Sequence[Credentials]:
22+
def run(self, options=None, interrupt=None) -> Sequence[Credentials]:
2323
logger.info("Started scanning for SSH credentials")
2424
ssh_info = ssh_handler.get_ssh_info(self._agent_event_queue, self._agent_id)
2525
logger.info("Finished scanning for SSH credentials")

monkey/infection_monkey/i_puppet/i_credentials_collector.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from typing import Mapping, Optional, Sequence
33

44
from common.credentials import Credentials
5+
from common.types import Event
56

67

78
class ICredentialsCollector(ABC):
89
@abstractmethod
9-
def collect_credentials(self, options: Optional[Mapping]) -> Sequence[Credentials]:
10+
def run(self, options: Optional[Mapping], interrupt: Event) -> Sequence[Credentials]:
1011
pass

monkey/infection_monkey/master/automated_master.py

+5-27
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
import multiprocessing
33
import time
44
from ipaddress import IPv4Interface
5-
from typing import Any, Callable, Collection, Dict, List, Optional, Sequence
5+
from typing import Any, Callable, Dict, List, Optional, Sequence
66

77
from egg_timer import EggTimer
88

9-
from common.agent_configuration import PluginConfiguration
109
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
1110
from infection_monkey.i_master import IMaster
1211
from infection_monkey.i_puppet import IPuppet
@@ -127,7 +126,7 @@ def _run_simulation(self):
127126
return
128127

129128
credentials_collector_thread = create_daemon_thread(
130-
target=self._run_plugins_legacy,
129+
target=self._run_plugins,
131130
name="CredentialsCollectorThread",
132131
args=(
133132
config.credentials_collectors,
@@ -156,13 +155,13 @@ def _run_simulation(self):
156155
payload_thread.start()
157156
payload_thread.join()
158157

159-
def _collect_credentials(self, collector: PluginConfiguration):
158+
def _collect_credentials(self, collector_name: str, collector_options: Dict[str, Any]):
160159
credentials = self._puppet.run_credentials_collector(
161-
collector.name, collector.options, self._stop
160+
collector_name, collector_options, self._stop
162161
)
163162

164163
if not credentials:
165-
logger.debug(f"No credentials were collected by {collector}")
164+
logger.debug(f"No credentials were collected by {collector_name}")
166165

167166
def _run_payload(self, name: str, options: Dict):
168167
self._puppet.run_payload(name, options, self._stop)
@@ -188,26 +187,5 @@ def _run_plugins(
188187

189188
logger.info(f"Finished running {plugin_type}s")
190189

191-
def _run_plugins_legacy(
192-
self,
193-
plugins: Collection[PluginConfiguration],
194-
plugin_type: str,
195-
callback: Callable[[Any], None],
196-
):
197-
logger.info(f"Running {plugin_type}s")
198-
logger.debug(f"Found {len(plugins)} {plugin_type}(s) to run")
199-
200-
interrupted_message = f"Received a stop signal, skipping remaining {plugin_type}s"
201-
for p in interruptible_iter(plugins, self._stop, interrupted_message):
202-
try:
203-
callback(p)
204-
except Exception:
205-
logger.exception(
206-
f"Got unhandled exception when running {plugin_type} plugin {p}. "
207-
f"Plugin was passed to {callback}"
208-
)
209-
210-
logger.info(f"Finished running {plugin_type}s")
211-
212190
def cleanup(self):
213191
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Form from '@rjsf/bootstrap-4';
2+
import React, {useState} from 'react';
3+
import _ from 'lodash';
4+
5+
6+
export default function FormConfig(props) {
7+
const {
8+
fullUiSchema,
9+
selectedPlugins,
10+
setSelectedPlugins,
11+
selectedSection
12+
} = props;
13+
14+
const [formUiSchema, setFormUiSchema] = useState(fullUiSchema);
15+
16+
const setUiSchemaForCurrentSection = (uiSubschema, path) => {
17+
if(path !== selectedSection){
18+
let newSchema = _.cloneDeep(formUiSchema);
19+
_.set(newSchema, path, uiSubschema);
20+
setFormUiSchema(newSchema);
21+
}else {
22+
setFormUiSchema(uiSubschema);
23+
}
24+
}
25+
26+
return (<div>
27+
<Form {...props}
28+
uiSchema={formUiSchema}
29+
formContext={{
30+
'selectedPlugins': selectedPlugins,
31+
'setSelectedPlugins': setSelectedPlugins,
32+
'setUiSchema': setUiSchemaForCurrentSection,
33+
'section': selectedSection
34+
}}>
35+
<button type='submit' className={'hidden'}>Submit</button>
36+
</Form>
37+
</div>)
38+
}

monkey/monkey_island/cc/ui/src/components/configuration-components/PluginSelectorTemplate.tsx

+19-15
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import {InfoPane, WarningType} from '../ui-components/InfoPane';
88
import {EXPLOITERS_PATH_PROPAGATION} from './PropagationConfig';
99
import MarkdownDescriptionTemplate from './MarkdownDescriptionTemplate';
1010

11+
export const CREDENTIALS_COLLECTORS_CONFIG_PATH = 'credentials_collectors';
12+
const PLUGIN_SCHEMA_PATH = {'propagation': EXPLOITERS_PATH_PROPAGATION, 'credentials_collectors': CREDENTIALS_COLLECTORS_CONFIG_PATH}
13+
1114

1215
export default function PluginSelectorTemplate(props: ObjectFieldTemplateProps) {
1316

1417
let [activePlugin, setActivePlugin] = useState(null);
1518

16-
useEffect(() => updateUISchema(), [props.formContext.selectedExploiters]);
19+
useEffect(() => updateUISchema(), [props.formContext.selectedPlugins]);
1720

1821
function getPluginDisplay(plugin, allPlugins) {
1922
let activePlugins = allPlugins.filter((pluginInArray) => pluginInArray.name == plugin);
@@ -41,26 +44,26 @@ export default function PluginSelectorTemplate(props: ObjectFieldTemplateProps)
4144
}
4245

4346
function togglePluggin(pluginName) {
44-
let plugins = new Set(props.formContext.selectedExploiters);
45-
if (props.formContext.selectedExploiters.has(pluginName)) {
47+
let plugins = new Set(props.formContext.selectedPlugins);
48+
if (props.formContext.selectedPlugins.has(pluginName)) {
4649
plugins.delete(pluginName);
4750
} else {
4851
plugins.add(pluginName);
4952
}
50-
props.formContext.setSelectedExploiters(plugins);
53+
props.formContext.setSelectedPlugins(plugins, props.formContext.section);
5154
}
5255

5356
function updateUISchema(){
5457
let uiSchema = _.cloneDeep(props.uiSchema);
5558
for(let pluginName of Object.keys(generateDefaultConfig())) {
56-
if(!props.formContext.selectedExploiters.has(pluginName)){
59+
if(!props.formContext.selectedPlugins.has(pluginName)){
5760
uiSchema[pluginName] = {"ui:readonly": true,
5861
'ui:DescriptionFieldTemplate': MarkdownDescriptionTemplate};
5962
} else {
6063
uiSchema[pluginName] = {'ui:DescriptionFieldTemplate': MarkdownDescriptionTemplate};
6164
}
6265
}
63-
props.formContext.setUiSchema(uiSchema, EXPLOITERS_PATH_PROPAGATION);
66+
props.formContext.setUiSchema(uiSchema, PLUGIN_SCHEMA_PATH[props.formContext.section]);
6467
}
6568

6669
function getMasterCheckboxState(selectValues) {
@@ -82,11 +85,12 @@ export default function PluginSelectorTemplate(props: ObjectFieldTemplateProps)
8285
}
8386

8487
function onMasterPluginCheckboxClick() {
85-
let checkboxState = getMasterCheckboxState([...props.formContext.selectedExploiters]);
88+
let checkboxState = getMasterCheckboxState([...props.formContext.selectedPlugins]);
89+
let selectedSection = props.formContext.section
8690
if (checkboxState == MasterCheckboxState.ALL) {
87-
props.formContext.setSelectedExploiters(new Set());
91+
props.formContext.setSelectedPlugins(new Set(), selectedSection);
8892
} else {
89-
props.formContext.setSelectedExploiters(new Set(Object.keys(generateDefaultConfig())));
93+
props.formContext.setSelectedPlugins(new Set(Object.keys(generateDefaultConfig())), selectedSection);
9094
}
9195
}
9296

@@ -104,9 +108,9 @@ export default function PluginSelectorTemplate(props: ObjectFieldTemplateProps)
104108
}
105109

106110
function onResetClick() {
107-
let safePluginNames = [...props.formContext.selectedExploiters].filter(
111+
let safePluginNames = [...props.formContext.selectedPlugins].filter(
108112
pluginName => isPluginSafe(pluginName));
109-
props.formContext.setSelectedExploiters(new Set(safePluginNames));
113+
props.formContext.setSelectedPlugins(new Set(safePluginNames), props.formContext.section);
110114
}
111115

112116
return (
@@ -115,14 +119,14 @@ export default function PluginSelectorTemplate(props: ObjectFieldTemplateProps)
115119
onCheckboxClick={onMasterPluginCheckboxClick}
116120
checkboxState={
117121
getMasterCheckboxState(
118-
[...props.formContext.selectedExploiters])}
122+
[...props.formContext.selectedPlugins])}
119123
hideReset={getHideResetState(
120-
[...props.formContext.selectedExploiters])}
124+
[...props.formContext.selectedPlugins])}
121125
onResetClick={onResetClick}
122-
resetButtonTitle={'Disable unsafe exploiters'}/>
126+
resetButtonTitle={'Disable unsafe'}/>
123127
<ChildCheckboxContainer multiple={true} required={false}
124128
autoFocus={true}
125-
selectedValues={[...props.formContext.selectedExploiters]}
129+
selectedValues={[...props.formContext.selectedPlugins]}
126130
onCheckboxClick={togglePluggin}
127131
isSafe={isPluginSafe}
128132
onPaneClick={setActivePlugin}

monkey/monkey_island/cc/ui/src/components/configuration-components/PropagationConfig.tsx

+7-5
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ export default function PropagationConfig(props) {
2626
configuration,
2727
credentials,
2828
onCredentialChange,
29-
selectedExploiters,
30-
setSelectedExploiters,
29+
selectedPlugins,
30+
setSelectedPlugins,
31+
selectedConfigSection,
3132
validator
3233
} = props;
3334

@@ -90,9 +91,10 @@ export default function PropagationConfig(props) {
9091
// children={true} hides the submit button
9192
children={true}
9293
formContext={{
93-
'selectedExploiters': selectedExploiters,
94-
'setSelectedExploiters': setSelectedExploiters,
95-
'setUiSchema': setUiSchemaForCurrentSection
94+
'selectedPlugins': selectedPlugins[selectedConfigSection],
95+
'setSelectedPlugins': setSelectedPlugins,
96+
'section': selectedConfigSection,
97+
setUiSchema: setUiSchemaForCurrentSection
9698
}}/>
9799
}
98100
}

0 commit comments

Comments
 (0)