forked from p15670423/monkey
Merge pull request #1000 from guardicore/unsafe-options-confirmation
Unsafe options confirmation
This commit is contained in:
commit
cfaf4a15c3
|
@ -11,6 +11,9 @@ from monkey_island.cc.services.config_schema.monkey import MONKEY
|
|||
SCHEMA = {
|
||||
"title": "Monkey",
|
||||
"type": "object",
|
||||
# Newly added definitions should also be added to
|
||||
# monkey/monkey_island/cc/ui/src/components/utils/SafeOptionValidator.js so that
|
||||
# users will not accidentally chose unsafe options
|
||||
"definitions": {
|
||||
"exploiter_classes": EXPLOITER_CLASSES,
|
||||
"system_info_collector_classes": SYSTEM_INFO_COLLECTOR_CLASSES,
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import {Modal, Button} from 'react-bootstrap';
|
||||
|
||||
function UnsafeOptionsConfirmationModal(props) {
|
||||
return (
|
||||
<Modal show={props.show}>
|
||||
<Modal.Body>
|
||||
<h2>
|
||||
<div className='text-center'>Warning</div>
|
||||
</h2>
|
||||
<p className='text-center' style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
|
||||
Some of the selected options could cause systems to become unstable or malfunction.
|
||||
Are you sure you want to submit the selected settings?
|
||||
</p>
|
||||
<div className='text-center'>
|
||||
<Button type='button'
|
||||
className='btn btn-secondary'
|
||||
size='lg'
|
||||
style={{margin: '5px'}}
|
||||
onClick={props.onCancelClick}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type='button'
|
||||
className='btn btn-danger'
|
||||
size='lg'
|
||||
style={{margin: '5px'}}
|
||||
onClick={props.onContinueClick}>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default UnsafeOptionsConfirmationModal;
|
|
@ -11,6 +11,8 @@ import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamati
|
|||
import {formValidationFormats} from '../configuration-components/ValidationFormats';
|
||||
import transformErrors from '../configuration-components/ValidationErrorMessages';
|
||||
import InternalConfig from '../configuration-components/InternalConfig';
|
||||
import UnsafeOptionsConfirmationModal from '../configuration-components/UnsafeOptionsConfirmationModal.js';
|
||||
import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js';
|
||||
|
||||
const ATTACK_URL = '/api/attack';
|
||||
const CONFIG_URL = '/api/configuration/island';
|
||||
|
@ -28,13 +30,15 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'internal'];
|
||||
|
||||
this.state = {
|
||||
schema: {},
|
||||
configuration: {},
|
||||
attackConfig: {},
|
||||
configuration: {},
|
||||
importCandidateConfig: null,
|
||||
lastAction: 'none',
|
||||
schema: {},
|
||||
sections: [],
|
||||
selectedSection: 'attack',
|
||||
showAttackAlert: false
|
||||
showAttackAlert: false,
|
||||
showUnsafeOptionsConfirmation: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -74,6 +78,20 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
});
|
||||
};
|
||||
|
||||
onUnsafeConfirmationCancelClick = () => {
|
||||
this.setState({showUnsafeOptionsConfirmation: false});
|
||||
}
|
||||
|
||||
onUnsafeConfirmationContinueClick = () => {
|
||||
this.setState({showUnsafeOptionsConfirmation: false});
|
||||
|
||||
if (this.state.lastAction == 'submit_attempt') {
|
||||
this.configSubmit();
|
||||
} else if (this.state.lastAction == 'import_attempt') {
|
||||
this.setConfigFromImportCandidate();
|
||||
}
|
||||
}
|
||||
|
||||
updateConfig = () => {
|
||||
this.authFetch(CONFIG_URL)
|
||||
.then(res => res.json())
|
||||
|
@ -85,12 +103,16 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
|
||||
onSubmit = () => {
|
||||
if (this.state.selectedSection === 'attack') {
|
||||
this.matrixSubmit()
|
||||
this.matrixSubmit();
|
||||
} else {
|
||||
this.configSubmit()
|
||||
this.attemptConfigSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
canSafelySubmitConfig(config) {
|
||||
return !isUnsafeOptionSelected(this.state.schema, config);
|
||||
}
|
||||
|
||||
matrixSubmit = () => {
|
||||
// Submit attack matrix
|
||||
this.authFetch(ATTACK_URL,
|
||||
|
@ -116,9 +138,19 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
});
|
||||
};
|
||||
|
||||
configSubmit = () => {
|
||||
// Submit monkey configuration
|
||||
attemptConfigSubmit() {
|
||||
this.updateConfigSection();
|
||||
this.setState({lastAction: 'submit_attempt'}, () => {
|
||||
if (this.canSafelySubmitConfig(this.state.configuration)) {
|
||||
this.configSubmit();
|
||||
} else {
|
||||
this.setState({showUnsafeOptionsConfirmation: true});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
configSubmit() {
|
||||
this.sendConfig()
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
|
@ -133,7 +165,7 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
console.log('Bad configuration: ' + error.toString());
|
||||
this.setState({lastAction: 'invalid_configuration'});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Alters attack configuration when user toggles technique
|
||||
attackTechniqueChange = (technique, value, mapped = false) => {
|
||||
|
@ -201,6 +233,16 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
</Modal>)
|
||||
};
|
||||
|
||||
renderUnsafeOptionsConfirmationModal() {
|
||||
return (
|
||||
<UnsafeOptionsConfirmationModal
|
||||
show={this.state.showUnsafeOptionsConfirmation}
|
||||
onCancelClick={this.onUnsafeConfirmationCancelClick}
|
||||
onContinueClick={this.onUnsafeConfirmationContinueClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
userChangedConfig() {
|
||||
if (JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)) {
|
||||
if (Object.keys(this.currentFormData).length === 0 ||
|
||||
|
@ -276,18 +318,33 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
|
||||
setConfigOnImport = (event) => {
|
||||
try {
|
||||
this.setState({
|
||||
configuration: JSON.parse(event.target.result),
|
||||
lastAction: 'import_success'
|
||||
}, () => {
|
||||
this.sendConfig();
|
||||
this.setInitialConfig(JSON.parse(event.target.result))
|
||||
});
|
||||
this.currentFormData = {};
|
||||
var newConfig = JSON.parse(event.target.result);
|
||||
} catch (SyntaxError) {
|
||||
this.setState({lastAction: 'import_failure'});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
this.setState({lastAction: 'import_attempt', importCandidateConfig: newConfig},
|
||||
() => {
|
||||
if (this.canSafelySubmitConfig(newConfig)) {
|
||||
this.setConfigFromImportCandidate();
|
||||
} else {
|
||||
this.setState({showUnsafeOptionsConfirmation: true});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setConfigFromImportCandidate(){
|
||||
this.setState({
|
||||
configuration: this.state.importCandidateConfig,
|
||||
lastAction: 'import_success'
|
||||
}, () => {
|
||||
this.sendConfig();
|
||||
this.setInitialConfig(this.state.importCandidateConfig);
|
||||
});
|
||||
this.currentFormData = {};
|
||||
}
|
||||
|
||||
exportConfig = () => {
|
||||
this.updateConfigSection();
|
||||
|
@ -410,6 +467,7 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
lg={{offset: 3, span: 8}} xl={{offset: 2, span: 8}}
|
||||
className={'main'}>
|
||||
{this.renderAttackAlertModal()}
|
||||
{this.renderUnsafeOptionsConfirmationModal()}
|
||||
<h1 className='page-title'>Monkey Configuration</h1>
|
||||
{this.renderNav()}
|
||||
{content}
|
||||
|
@ -424,7 +482,7 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
<div className='text-center'>
|
||||
<button onClick={() => document.getElementById('uploadInputInternal').click()}
|
||||
className='btn btn-info btn-lg' style={{margin: '5px'}}>
|
||||
Import Config
|
||||
Import config
|
||||
</button>
|
||||
<input id='uploadInputInternal' type='file' accept='.conf' onChange={this.importConfig}
|
||||
style={{display: 'none'}}/>
|
||||
|
|
|
@ -48,13 +48,14 @@ class AdvancedMultiSelect extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
// Sort options alphabetically. "Unsafe" options float to the bottom"
|
||||
// Sort options alphabetically. "Unsafe" options float to the top so that they
|
||||
// do not get selected and hidden at the bottom of the list.
|
||||
compareOptions = (a, b) => {
|
||||
// Apparently, you can use additive operators with boolean types. Ultimately,
|
||||
// the ToNumber() abstraction operation is called to convert the booleans to
|
||||
// numbers: https://tc39.es/ecma262/#sec-tonumeric
|
||||
if (this.isSafe(b.value) - this.isSafe(a.value) !== 0) {
|
||||
return this.isSafe(b.value) - this.isSafe(a.value);
|
||||
if (this.isSafe(a.value) - this.isSafe(b.value) !== 0) {
|
||||
return this.isSafe(a.value) - this.isSafe(b.value);
|
||||
}
|
||||
|
||||
return a.value.localeCompare(b.value);
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
function getPluginDescriptors(schema, config) {
|
||||
return ([
|
||||
{
|
||||
name: 'Exploiters',
|
||||
allPlugins: schema.definitions.exploiter_classes.anyOf,
|
||||
selectedPlugins: config.basic.exploiters.exploiter_classes
|
||||
},
|
||||
{
|
||||
name: 'Fingerprinters',
|
||||
allPlugins: schema.definitions.finger_classes.anyOf,
|
||||
selectedPlugins: config.internal.classes.finger_classes
|
||||
},
|
||||
{
|
||||
name: 'PostBreachActions',
|
||||
allPlugins: schema.definitions.post_breach_actions.anyOf,
|
||||
selectedPlugins: config.monkey.post_breach.post_breach_actions
|
||||
},
|
||||
{
|
||||
name: 'SystemInfoCollectors',
|
||||
allPlugins: schema.definitions.system_info_collector_classes.anyOf,
|
||||
selectedPlugins: config.monkey.system_info.system_info_collector_classes
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
function isUnsafeOptionSelected(schema, config) {
|
||||
let pluginDescriptors = getPluginDescriptors(schema, config);
|
||||
|
||||
for (let descriptor of pluginDescriptors) {
|
||||
if (isUnsafePluginSelected(descriptor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isUnsafePluginSelected(pluginDescriptor) {
|
||||
let pluginSafety = new Map();
|
||||
pluginDescriptor.allPlugins.forEach(i => pluginSafety[i.enum[0]] = i.safe);
|
||||
|
||||
for (let selected of pluginDescriptor.selectedPlugins) {
|
||||
if (!pluginSafety[selected]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default isUnsafeOptionSelected;
|
Loading…
Reference in New Issue