UI: Separate credential configuration and config

This commit is contained in:
vakarisz 2022-07-15 17:41:49 +03:00 committed by Ilija Lazoroski
parent 47a87e14e6
commit 0cee5ac00d
5 changed files with 205 additions and 42 deletions
monkey/monkey_island/cc/ui/src
components
services/configuration/propagation

View File

@ -0,0 +1,27 @@
import Form from 'react-jsonschema-form-bs4';
import React from 'react';
import _ from 'lodash';
export default function CredentialsConfig(props) {
const {
schema,
uiSchema,
credentials,
onChange,
customFormats,
className
} = props;
let credentialsCopy = _.clone(credentials);
return (<>
<Form schema={schema}
uiSchema={uiSchema}
formData={credentialsCopy}
onChange={(formData) => {onChange(formData.formData)}}
customFormats={customFormats}
className={className}
liveValidate
children={true}/>
</>)
}

View File

@ -2,6 +2,7 @@ import Form from 'react-jsonschema-form-bs4';
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect} from 'react';
import {Nav} from 'react-bootstrap'; import {Nav} from 'react-bootstrap';
import _ from 'lodash'; import _ from 'lodash';
import CredentialsConfig from './CredentialsConfig';
const sectionOrder = [ const sectionOrder = [
'exploitation', 'exploitation',
@ -19,30 +20,27 @@ export default function PropagationConfig(props) {
onChange, onChange,
customFormats, customFormats,
className, className,
formData configuration,
credentials,
onCredentialChange
} = props; } = props;
const [selectedSection, setSelectedSection] = useState(initialSection); const [selectedSection, setSelectedSection] = useState(initialSection);
const [displayedSchema, setDisplayedSchema] = useState(getSchemaByKey(schema, initialSection)); const [displayedSchema, setDisplayedSchema] = useState(getSchemaByKey(schema, initialSection));
const [displayedSchemaUi, setDisplayedSchemaUi] = useState(getUiSchemaByKey(uiSchema, initialSection)); const [displayedSchemaUi, setDisplayedSchemaUi] = useState(getUiSchemaByKey(uiSchema, initialSection));
const [localFormData, setLocalFormData] = useState(formData[initialSection]); const [localFormData, setLocalFormData] = useState(configuration[initialSection]);
useEffect(() => { useEffect(() => {
setLocalFormData(formData[selectedSection]); setLocalFormData(configuration[selectedSection]);
setDisplayedSchema(getSchemaByKey(schema, selectedSection)); setDisplayedSchema(getSchemaByKey(schema, selectedSection));
setDisplayedSchemaUi(getUiSchemaByKey(uiSchema, selectedSection)); setDisplayedSchemaUi(getUiSchemaByKey(uiSchema, selectedSection));
setLocalFormData(formData[selectedSection]);
}, [selectedSection]) }, [selectedSection])
useEffect(() => { const onFormDataChange = (formData) => {
setLocalFormData(formData[selectedSection]); let formDataClone = _.clone(formData.formData);
}, [formData]) let configurationClone = _.clone(configuration);
const onInnerDataChange = (innerData) => { configurationClone[selectedSection] = formDataClone;
let innerDataClone = _.clone(innerData); onChange(configurationClone);
let formDataClone = _.clone(formData);
formDataClone[selectedSection] = innerDataClone.formData;
onChange({formData: formDataClone});
} }
const setSection = (sectionKey) => { const setSection = (sectionKey) => {
@ -50,7 +48,7 @@ export default function PropagationConfig(props) {
} }
const renderNav = () => { const renderNav = () => {
return (<Nav variant='tabs' return (<Nav variant="tabs"
fill fill
activeKey={selectedSection} onSelect={setSection} activeKey={selectedSection} onSelect={setSection}
style={{'marginBottom': '2em'}} style={{'marginBottom': '2em'}}
@ -64,18 +62,30 @@ export default function PropagationConfig(props) {
</Nav>) </Nav>)
} }
const getForm = () => {
if (selectedSection === 'credentials') {
return <CredentialsConfig schema={displayedSchema}
uiSchema={displayedSchemaUi}
credentials={credentials}
onChange={onCredentialChange}
customFormats={customFormats}
className={className}/>
} else {
return <Form schema={displayedSchema}
uiSchema={displayedSchemaUi}
formData={localFormData}
onChange={onFormDataChange}
customFormats={customFormats}
className={className}
liveValidate
// children={true} hides the submit button
children={true}/>
}
}
return (<div> return (<div>
{renderNav()} {renderNav()}
<Form schema={displayedSchema} {getForm()}
uiSchema={displayedSchemaUi}
formData={localFormData}
onChange={onInnerDataChange}
customFormats={customFormats}
className={className}
liveValidate>
<button type='submit' className={'hidden'}>Submit</button>
</Form>
</div>) </div>)
} }
@ -91,8 +101,5 @@ function getNavTitle(schema, key) {
if (key === 'maximum_depth') { if (key === 'maximum_depth') {
return 'General'; return 'General';
} }
if (key === 'credentials') {
return 'Credentials';
}
return schema['properties'][key].title; return schema['properties'][key].title;
} }

View File

@ -1,8 +1,85 @@
import {defaultCredentials} from '../../services/configuration/propagation/credentials';
import _ from 'lodash';
export function reformatConfig(config, reverse = false) { export function reformatConfig(config, reverse = false) {
if (reverse) { if (reverse) {
config['payloads'] = [{'name': 'ransomware', 'options': config['payloads']}] config['payloads'] = [{'name': 'ransomware', 'options': config['payloads']}]
} else { } else {
config['payloads'] = config['payloads'][0]['options']; config['payloads'] = config['payloads'][0]['options'];
}
return config;
} }
return config;
}
export function formatCredentialsForForm(credentials) {
console.log(credentials);
let formattedCredentials = _.clone(defaultCredentials);
for (let i = 0; i < credentials.length; i++) {
for (let j = 0; j < credentials[i].identities.length; j++) {
formattedCredentials['exploit_user_list'].push(credentials[i]['identities'][j].username)
}
for (let j = 0; j < credentials[i].secrets.length; j++) {
console.log(credentials[i])
let secret = credentials[i]['secrets'][j];
if (secret['credential_type'] === 'PASSWORD') {
formattedCredentials['exploit_password_list'].push(secret['password'])
}
if (secret['credential_type'] === 'NT_HASH') {
formattedCredentials['exploit_ntlm_hash_list'].push(secret['nt_hash'])
}
if (secret['credential_type'] === 'LM_HASH') {
formattedCredentials['exploit_lm_hash_list'].push(secret['lm_hash'])
}
if (secret['credential_type'] === 'SSH_KEY') {
let keypair = {'public_key': secret['public_key'], 'private_key': secret['private_key']}
formattedCredentials['exploit_ssh_keys'].push(keypair)
}
}
}
return formattedCredentials;
}
export function formatCredentialsForIsland(credentials) {
let formattedCredentials = [];
let usernames = credentials['exploit_user_list'];
for (let i = 0; i < usernames.length; i++) {
formattedCredentials.push({
'identities': [{'username': usernames[i], 'credential_type': 'USERNAME'}],
'secrets': []
})
}
let passwords = credentials['exploit_password_list'];
for (let i = 0; i < passwords.length; i++) {
formattedCredentials.push({
'identities': [],
'secrets': [{'credential_type': 'PASSWORD', 'password': passwords[i]}]
})
}
let nt_hashes = credentials['exploit_ntlm_hash_list'];
for (let i = 0; i < nt_hashes.length; i++) {
formattedCredentials.push({
'identities': [],
'secrets': [{'credential_type': 'NT_HASH', 'nt_hash': nt_hashes[i]}]
})
}
let lm_hashes = credentials['exploit_lm_hash_list'];
for (let i = 0; i < lm_hashes.length; i++) {
formattedCredentials.push({
'identities': [],
'secrets': [{'credential_type': 'LM_HASH', 'lm_hash': lm_hashes[i]}]
})
}
let ssh_keys = credentials['exploit_ssh_keys'];
for (let i = 0; i < ssh_keys.length; i++) {
formattedCredentials.push({
'identities': [],
'secrets': [{'credential_type': 'SSH_KEYPAIR', 'private_key': ssh_keys[i]['private_key'],
'public_key': ssh_keys[i]['public_key']}]
})
}
return formattedCredentials;
}

View File

@ -18,7 +18,11 @@ import applyUiSchemaManipulators from '../configuration-components/UISchemaManip
import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js'; import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js';
import CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js'; import CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js';
import {SCHEMA} from '../../services/configuration/configSchema.js'; import {SCHEMA} from '../../services/configuration/configSchema.js';
import {reformatConfig} from '../configuration-components/ReformatHook'; import {
reformatConfig,
formatCredentialsForForm,
formatCredentialsForIsland
} from '../configuration-components/ReformatHook';
const CONFIG_URL = '/api/agent-configuration'; const CONFIG_URL = '/api/agent-configuration';
const RESET_URL = '/api/reset-agent-configuration'; const RESET_URL = '/api/reset-agent-configuration';
@ -39,6 +43,7 @@ class ConfigurePageComponent extends AuthComponent {
this.state = { this.state = {
configuration: {}, configuration: {},
credentials: {},
currentFormData: {}, currentFormData: {},
importCandidateConfig: null, importCandidateConfig: null,
lastAction: 'none', lastAction: 'none',
@ -94,6 +99,7 @@ class ConfigurePageComponent extends AuthComponent {
currentFormData: monkeyConfig[this.state.selectedSection] currentFormData: monkeyConfig[this.state.selectedSection]
}) })
}); });
this.updateCredentials();
}; };
onUnsafeConfirmationCancelClick = () => { onUnsafeConfirmationCancelClick = () => {
@ -110,7 +116,19 @@ class ConfigurePageComponent extends AuthComponent {
} }
} }
updateCredentials = () => {
this.authFetch(CREDENTIALS_URL)
.then(res => res.json())
.then(credentials => {
credentials = formatCredentialsForForm(credentials);
this.setState({
credentials: credentials
});
});
}
updateConfig = () => { updateConfig = () => {
this.updateCredentials();
this.authFetch(CONFIG_URL) this.authFetch(CONFIG_URL)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
@ -120,8 +138,8 @@ class ConfigurePageComponent extends AuthComponent {
configuration: data, configuration: data,
currentFormData: data[this.state.selectedSection] currentFormData: data[this.state.selectedSection]
}); });
}) });
}; }
onSubmit = () => { onSubmit = () => {
this.setState({lastAction: configSubmitAction}, this.attemptConfigSubmit) this.setState({lastAction: configSubmitAction}, this.attemptConfigSubmit)
@ -144,8 +162,7 @@ class ConfigurePageComponent extends AuthComponent {
} }
configSubmit() { configSubmit() {
return this.sendConfig() this.sendConfig()
.then(res => res.json())
.then(() => { .then(() => {
this.setState({ this.setState({
lastAction: configSaveAction lastAction: configSaveAction
@ -158,12 +175,17 @@ class ConfigurePageComponent extends AuthComponent {
}); });
} }
onChange = ({formData}) => { onChange = (formData) => {
let data = formData;
let configuration = this.state.configuration; let configuration = this.state.configuration;
configuration[this.state.selectedSection] = formData; configuration[this.state.selectedSection] = data;
this.setState({currentFormData: formData, configuration: configuration}); this.setState({currentFormData: data, configuration: configuration});
}; };
onCredentialChange = (credentials) => {
this.setState({credentials: credentials});
}
updateConfigSection = () => { updateConfigSection = () => {
let newConfig = this.state.configuration; let newConfig = this.state.configuration;
@ -256,6 +278,22 @@ class ConfigurePageComponent extends AuthComponent {
let config = JSON.parse(JSON.stringify(this.state.configuration)) let config = JSON.parse(JSON.stringify(this.state.configuration))
config = reformatConfig(config, true); config = reformatConfig(config, true);
this.authFetch(CREDENTIALS_URL,
{
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(formatCredentialsForIsland(this.state.credentials))
})
.then(res => {
if (!res.ok) {
throw Error()
}
return res;
}).catch((error) => {
console.log(`bad configuration ${error}`);
this.setState({lastAction: 'invalid_configuration'});
});
return ( return (
this.authFetch(CONFIG_URL, this.authFetch(CONFIG_URL,
{ {
@ -285,21 +323,27 @@ class ConfigurePageComponent extends AuthComponent {
setPbaFilenameLinux: this.setPbaFilenameLinux setPbaFilenameLinux: this.setPbaFilenameLinux
}) })
formProperties['fields'] = {DescriptionField: HtmlFieldDescription}; formProperties['fields'] = {DescriptionField: HtmlFieldDescription};
formProperties['formData'] = this.state.currentFormData;
formProperties['onChange'] = this.onChange; formProperties['onChange'] = this.onChange;
formProperties['onFocus'] = this.resetLastAction; formProperties['onFocus'] = this.resetLastAction;
formProperties['customFormats'] = formValidationFormats; formProperties['customFormats'] = formValidationFormats;
formProperties['transformErrors'] = transformErrors; formProperties['transformErrors'] = transformErrors;
formProperties['className'] = 'config-form'; formProperties['className'] = 'config-form';
formProperties['liveValidate'] = true; formProperties['liveValidate'] = true;
formProperties['formData'] = this.state.currentFormData;
applyUiSchemaManipulators(this.state.selectedSection, applyUiSchemaManipulators(this.state.selectedSection,
formProperties['formData'], formProperties['formData'],
formProperties['uiSchema']); formProperties['uiSchema']);
if (this.state.selectedSection === 'propagation') { if (this.state.selectedSection === 'propagation') {
return (<PropagationConfig {...formProperties}/>) delete Object.assign(formProperties, {'configuration': formProperties.formData}).formData;
return (<PropagationConfig {...formProperties}
credentials={this.state.credentials}
onCredentialChange={this.onCredentialChange}/>)
} else { } else {
formProperties['onChange'] = (formData) => {
this.onChange(formData.formData)
};
return ( return (
<div> <div>
<Form {...formProperties} key={displayedSchema.title}> <Form {...formProperties} key={displayedSchema.title}>

View File

@ -59,4 +59,12 @@ const CREDENTIALS = {
} }
} }
export const defaultCredentials = {
'exploit_user_list': [],
'exploit_password_list': [],
'exploit_lm_hash_list': [],
'exploit_ntlm_hash_list': [],
'exploit_ssh_keys': []
}
export default CREDENTIALS; export default CREDENTIALS;