Merge pull request #2096 from guardicore/1965-credentials-in-config

1965 credentials in config
This commit is contained in:
Mike Salvatore 2022-07-21 10:52:01 -04:00 committed by GitHub
commit f086637758
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 345 additions and 101 deletions

View File

@ -84,7 +84,7 @@ class ControlChannel(IControlChannel):
) )
response.raise_for_status() response.raise_for_status()
return [Credentials.from_mapping(credentials) for credentials in response.json] return [Credentials.from_mapping(credentials) for credentials in response.json()]
except ( except (
requests.exceptions.JSONDecodeError, requests.exceptions.JSONDecodeError,
requests.exceptions.ConnectionError, requests.exceptions.ConnectionError,

View File

@ -14,14 +14,14 @@ class MongoCredentialsRepository(ICredentialsRepository):
""" """
def __init__(self, mongo: MongoClient, repository_encryptor: ILockableEncryptor): def __init__(self, mongo: MongoClient, repository_encryptor: ILockableEncryptor):
self._mongo = mongo self._database = mongo.monkeyisland
self._repository_encryptor = repository_encryptor self._repository_encryptor = repository_encryptor
def get_configured_credentials(self) -> Sequence[Credentials]: def get_configured_credentials(self) -> Sequence[Credentials]:
return self._get_credentials_from_collection(self._mongo.db.configured_credentials) return self._get_credentials_from_collection(self._database.configured_credentials)
def get_stolen_credentials(self) -> Sequence[Credentials]: def get_stolen_credentials(self) -> Sequence[Credentials]:
return self._get_credentials_from_collection(self._mongo.db.stolen_credentials) return self._get_credentials_from_collection(self._database.stolen_credentials)
def get_all_credentials(self) -> Sequence[Credentials]: def get_all_credentials(self) -> Sequence[Credentials]:
configured_credentials = self.get_configured_credentials() configured_credentials = self.get_configured_credentials()
@ -31,20 +31,16 @@ class MongoCredentialsRepository(ICredentialsRepository):
def save_configured_credentials(self, credentials: Sequence[Credentials]): def save_configured_credentials(self, credentials: Sequence[Credentials]):
# TODO: Fix deduplication of Credentials in mongo # TODO: Fix deduplication of Credentials in mongo
self._save_credentials_to_collection(credentials, self._mongo.db.configured_credentials) self._save_credentials_to_collection(credentials, self._database.configured_credentials)
def save_stolen_credentials(self, credentials: Sequence[Credentials]): def save_stolen_credentials(self, credentials: Sequence[Credentials]):
self._save_credentials_to_collection(credentials, self._mongo.db.stolen_credentials) self._save_credentials_to_collection(credentials, self._database.stolen_credentials)
def remove_configured_credentials(self): def remove_configured_credentials(self):
MongoCredentialsRepository._remove_credentials_fom_collection( self._remove_credentials_fom_collection(self._database.configured_credentials)
self._mongo.db.configured_credentials
)
def remove_stolen_credentials(self): def remove_stolen_credentials(self):
MongoCredentialsRepository._remove_credentials_fom_collection( self._remove_credentials_fom_collection(self._database.stolen_credentials)
self._mongo.db.stolen_credentials
)
def remove_all_credentials(self): def remove_all_credentials(self):
self.remove_configured_credentials() self.remove_configured_credentials()

View File

@ -42,6 +42,22 @@ class PropagationCredentials(AbstractResource):
return {}, HTTPStatus.NO_CONTENT return {}, HTTPStatus.NO_CONTENT
def put(self, collection=None):
credentials = [Credentials.from_mapping(c) for c in request.json]
if collection == _configured_collection:
self._credentials_repository.remove_configured_credentials()
self._credentials_repository.save_configured_credentials(credentials)
elif collection == _stolen_collection:
self._credentials_repository.remove_stolen_credentials()
self._credentials_repository.save_stolen_credentials(credentials)
elif collection is None:
return {}, HTTPStatus.METHOD_NOT_ALLOWED
else:
return {}, HTTPStatus.NOT_FOUND
return {}, HTTPStatus.NO_CONTENT
def delete(self, collection=None): def delete(self, collection=None):
if collection == _configured_collection: if collection == _configured_collection:
self._credentials_repository.remove_configured_credentials() self._credentials_repository.remove_configured_credentials()

View File

@ -0,0 +1,26 @@
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

@ -10,6 +10,7 @@ import {reformatConfig} from './ReformatHook';
type Props = { type Props = {
show: boolean, show: boolean,
configuration: object, configuration: object,
credentials: object,
onHide: () => void onHide: () => void
} }
@ -22,17 +23,17 @@ const ConfigExportModal = (props: Props) => {
} }
function onSubmit() { function onSubmit() {
let config = reformatConfig(props.configuration, true); let configuration = reformatConfig(props.configuration, true);
let config_export = {'metadata': {}, 'contents': null}; let credentials = props.credentials;
let metadata = {'encrypted': false};
if (radioValue === 'password') { if (radioValue === 'password') {
config_export.contents = encryptText(JSON.stringify(config), pass); configuration = encryptText(JSON.stringify(configuration), pass);
config_export.metadata = {'encrypted': true}; credentials = encryptText(JSON.stringify(credentials), pass);
} else { metadata = {'encrypted': true};
config_export.contents = config;
config_export.metadata = {'encrypted': false};
} }
let config_export = {'metadata': metadata, 'configuration': configuration, 'credentials': credentials};
let export_json = JSON.stringify(config_export, null, 2); let export_json = JSON.stringify(config_export, null, 2);
let export_blob = new Blob( let export_blob = new Blob(
[export_json], [export_json],

View File

@ -10,7 +10,10 @@ import UnsafeConfigOptionsConfirmationModal
import UploadStatusIcon, {UploadStatuses} from '../ui-components/UploadStatusIcon'; import UploadStatusIcon, {UploadStatuses} from '../ui-components/UploadStatusIcon';
import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js';
import {decryptText} from '../utils/PasswordBasedEncryptor'; import {decryptText} from '../utils/PasswordBasedEncryptor';
import {
reformatConfig,
formatCredentialsForIsland
} from '../configuration-components/ReformatHook';
type Props = { type Props = {
show: boolean, show: boolean,
@ -21,9 +24,11 @@ type Props = {
const ConfigImportModal = (props: Props) => { const ConfigImportModal = (props: Props) => {
const configImportEndpoint = '/api/agent-configuration'; const configImportEndpoint = '/api/agent-configuration';
const credentialsEndpoint = '/api/propagation-credentials/configured-credentials';
const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean);
const [configContents, setConfigContents] = useState(null); const [configContents, setConfigContents] = useState(null);
const [configCredentials, setConfigCredentials] = useState(null);
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [configEncrypted, setConfigEncrypted] = useState(false); const [configEncrypted, setConfigEncrypted] = useState(false);
@ -36,10 +41,10 @@ const ConfigImportModal = (props: Props) => {
const authComponent = new AuthComponent({}); const authComponent = new AuthComponent({});
useEffect(() => { useEffect(() => {
if (configContents !== null) { if (configContents !== null && configCredentials !== null) {
tryImport(); tryImport();
} }
}, [configContents, unsafeOptionsVerified]) }, [configContents, configCredentials, unsafeOptionsVerified])
function tryImport() { function tryImport() {
if (configEncrypted && !showPassword){ if (configEncrypted && !showPassword){
@ -47,8 +52,10 @@ const ConfigImportModal = (props: Props) => {
} else if (configEncrypted && showPassword) { } else if (configEncrypted && showPassword) {
try { try {
let decryptedConfig = JSON.parse(decryptText(configContents, password)); let decryptedConfig = JSON.parse(decryptText(configContents, password));
let decryptedConfigCredentials = JSON.parse(decryptText(configCredentials, password));
setConfigEncrypted(false); setConfigEncrypted(false);
setConfigContents(decryptedConfig); setConfigContents(decryptedConfig);
setConfigCredentials(decryptedConfigCredentials);
} catch (e) { } catch (e) {
setUploadStatus(UploadStatuses.error); setUploadStatus(UploadStatuses.error);
setErrorMessage('Decryption failed: Password is wrong or the file is corrupted'); setErrorMessage('Decryption failed: Password is wrong or the file is corrupted');
@ -61,16 +68,38 @@ const ConfigImportModal = (props: Props) => {
} }
} else { } else {
sendConfigToServer(); sendConfigToServer();
sendConfigCredentialsToServer();
setUploadStatus(UploadStatuses.success); setUploadStatus(UploadStatuses.success);
} }
} }
function sendConfigCredentialsToServer() {
let credentials = formatCredentialsForIsland(configCredentials);
authComponent.authFetch(credentialsEndpoint,
{
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(credentials)
}
).then(res => {
if (res.ok) {
resetState();
props.onClose(true);
} else {
setUploadStatus(UploadStatuses.error);
setErrorMessage("Configuration file is corrupt or in an outdated format.");
}
})
}
function sendConfigToServer() { function sendConfigToServer() {
let config = reformatConfig(configContents, true);
delete config['advanced'];
authComponent.authFetch(configImportEndpoint, authComponent.authFetch(configImportEndpoint,
{ {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify(configContents) body: JSON.stringify(config)
} }
).then(res => { ).then(res => {
if (res.ok) { if (res.ok) {
@ -92,6 +121,7 @@ const ConfigImportModal = (props: Props) => {
setUploadStatus(UploadStatuses.clean); setUploadStatus(UploadStatuses.clean);
setPassword(''); setPassword('');
setConfigContents(null); setConfigContents(null);
setConfigCredentials(null);
setErrorMessage(''); setErrorMessage('');
setShowPassword(false); setShowPassword(false);
setShowUnsafeOptionsConfirmation(false); setShowUnsafeOptionsConfirmation(false);
@ -113,7 +143,8 @@ const ConfigImportModal = (props: Props) => {
return return
} }
setConfigEncrypted(importContents['metadata']['encrypted']); setConfigEncrypted(importContents['metadata']['encrypted']);
setConfigContents(importContents['contents']); setConfigContents(importContents['configuration']);
setConfigCredentials(importContents['credentials']);
}; };
reader.readAsText(event.target.files[0]); reader.readAsText(event.target.files[0]);
} }

View File

@ -1,7 +1,8 @@
import Form from 'react-jsonschema-form-bs4'; import Form from 'react-jsonschema-form-bs4';
import React, {useState, useEffect} from 'react'; import React, {useState} 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,40 +20,24 @@ 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 [displayedSchemaUi, setDisplayedSchemaUi] = useState(getUiSchemaByKey(uiSchema, initialSection));
const [localFormData, setLocalFormData] = useState(formData[initialSection]);
useEffect(() => { const onFormDataChange = (formData) => {
setLocalFormData(formData[selectedSection]); let formDataClone = _.clone(formData.formData);
setDisplayedSchema(getSchemaByKey(schema, selectedSection)); let configurationClone = _.clone(configuration);
setDisplayedSchemaUi(getUiSchemaByKey(uiSchema, selectedSection));
setLocalFormData(formData[selectedSection]);
}, [selectedSection])
useEffect(() => { configurationClone[selectedSection] = formDataClone;
setLocalFormData(formData[selectedSection]); onChange(configurationClone);
}, [formData])
const onInnerDataChange = (innerData) => {
let innerDataClone = _.clone(innerData);
let formDataClone = _.clone(formData);
formDataClone[selectedSection] = innerDataClone.formData;
onChange({formData: formDataClone});
}
const setSection = (sectionKey) => {
setSelectedSection(sectionKey);
} }
const renderNav = () => { const renderNav = () => {
return (<Nav variant='tabs' return (<Nav variant="tabs"
fill fill
activeKey={selectedSection} onSelect={setSection} activeKey={selectedSection} onSelect={setSelectedSection}
style={{'marginBottom': '2em'}} style={{'marginBottom': '2em'}}
className={'config-nav'}> className={'config-nav'}>
{sectionOrder.map(section => { {sectionOrder.map(section => {
@ -64,18 +49,36 @@ export default function PropagationConfig(props) {
</Nav>) </Nav>)
} }
const getForm = () => {
let displayedSchema = getSchemaByKey(schema, selectedSection);
let displayedUiSchema = getUiSchemaByKey(uiSchema, selectedSection);
if (selectedSection === 'credentials') {
return <CredentialsConfig schema={displayedSchema}
uiSchema={displayedUiSchema}
credentials={credentials}
onChange={onCredentialChange}
customFormats={customFormats}
className={className}/>
} else {
let selectedSectionData = configuration[selectedSection];
return <Form schema={displayedSchema}
uiSchema={displayedUiSchema}
formData={selectedSectionData}
onChange={onFormDataChange}
customFormats={customFormats}
className={className}
// Each form must be a unique component
// which is defined by the selectedSection
key={selectedSection}
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 +94,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,101 @@
import {defaultCredentials} from '../../services/configuration/propagation/credentials';
import _ from 'lodash';
export function reformatConfig(config, reverse = false) { export function reformatConfig(config, reverse = false) {
if (reverse) { let formattedConfig = _.clone(config);
config['payloads'] = [{'name': 'ransomware', 'options': config['payloads']}]
if (reverse) {
if(formattedConfig['payloads'].length === 1){
// Second click on Export
formattedConfig['payloads'] = [{'name': 'ransomware', 'options': formattedConfig['payloads'][0]['options']}];
} else { } else {
config['payloads'] = config['payloads'][0]['options']; formattedConfig['payloads'] = [{'name': 'ransomware', 'options': formattedConfig['payloads']}];
} }
return config; formattedConfig['keep_tunnel_open_time'] = formattedConfig['advanced']['keep_tunnel_open_time'];
} else {
formattedConfig['payloads'] = formattedConfig['payloads'][0]['options'];
formattedConfig['advanced'] = {};
formattedConfig['advanced']['keep_tunnel_open_time'] = formattedConfig['keep_tunnel_open_time'];
} }
return formattedConfig;
}
export function formatCredentialsForForm(credentials) {
let formattedCredentials = _.clone(defaultCredentials);
for (let i = 0; i < credentials.length; i++) {
let identity = credentials[i]['identity'];
if(identity !== null) {
formattedCredentials['exploit_user_list'].push(identity.username)
}
let secret = credentials[i]['secret'];
if(secret !== null){
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)
}
}
}
formattedCredentials['exploit_user_list'] = [...new Set(formattedCredentials['exploit_user_list'])];
formattedCredentials['exploit_password_list'] = [...new Set(formattedCredentials['exploit_password_list'])];
formattedCredentials['exploit_ntlm_hash_list'] = [...new Set(formattedCredentials['exploit_ntlm_hash_list'])];
formattedCredentials['exploit_lm_hash_list'] = [...new Set(formattedCredentials['exploit_lm_hash_list'])];
return formattedCredentials;
}
export function formatCredentialsForIsland(credentials) {
let formattedCredentials = [];
let usernames = credentials['exploit_user_list'];
for (let i = 0; i < usernames.length; i++) {
formattedCredentials.push({
'identity': {'username': usernames[i], 'credential_type': 'USERNAME'},
'secret': null
})
}
let passwords = credentials['exploit_password_list'];
for (let i = 0; i < passwords.length; i++) {
formattedCredentials.push({
'identity': null,
'secret': {'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({
'identity': null,
'secret': {'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({
'identity': null,
'secret': {'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({
'identity': null,
'secret': {'credential_type': 'SSH_KEYPAIR', 'private_key': ssh_keys[i]['private_key'],
'public_key': ssh_keys[i]['public_key']}
})
}
return formattedCredentials;
}

View File

@ -3,6 +3,7 @@ import InfoBox from './InfoBox';
import TextBox from './TextBox.js'; import TextBox from './TextBox.js';
import PbaInput from './PbaInput'; import PbaInput from './PbaInput';
import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage'; import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage';
import SensitiveTextInput from '../ui-components/SensitiveTextInput';
export default function UiSchema(props) { export default function UiSchema(props) {
const UiSchema = { const UiSchema = {
@ -25,7 +26,6 @@ export default function UiSchema(props) {
options: { options: {
http_ports: { http_ports: {
items: { items: {
classNames: 'config-template-no-header'
} }
} }
} }
@ -38,6 +38,24 @@ export default function UiSchema(props) {
private_key: { private_key: {
} }
} }
},
exploit_password_list: {
items: {
classNames: 'config-template-no-header',
'ui:widget': SensitiveTextInput
}
},
exploit_lm_hash_list:{
items: {
classNames: 'config-template-no-header',
'ui:widget': SensitiveTextInput
}
},
exploit_ntlm_hash_list:{
items: {
classNames: 'config-template-no-header',
'ui:widget': SensitiveTextInput
}
} }
}, },
network_scan: { network_scan: {

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(CONFIGURED_PROPAGATION_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,26 +162,23 @@ class ConfigurePageComponent extends AuthComponent {
} }
configSubmit() { configSubmit() {
return this.sendConfig() this.sendCredentials().then(res => {
.then(res => res.json()) if(res.ok) {
.then(() => { this.sendConfig();
this.setState({ }
lastAction: configSaveAction });
});
this.setInitialConfig(this.state.configuration);
this.props.onStatusChange();
}).catch(error => {
console.log('Bad configuration: ' + error.toString());
this.setState({lastAction: 'invalid_configuration'});
});
} }
onChange = ({formData}) => { onChange = (formData) => {
let configuration = this.state.configuration; let configuration = this.state.configuration;
configuration[this.state.selectedSection] = formData; configuration[this.state.selectedSection] = formData;
this.setState({currentFormData: formData, configuration: configuration}); this.setState({currentFormData: formData, configuration: configuration});
}; };
onCredentialChange = (credentials) => {
this.setState({credentials: credentials});
}
updateConfigSection = () => { updateConfigSection = () => {
let newConfig = this.state.configuration; let newConfig = this.state.configuration;
@ -176,6 +191,7 @@ class ConfigurePageComponent extends AuthComponent {
renderConfigExportModal = () => { renderConfigExportModal = () => {
return (<ConfigExportModal show={this.state.showConfigExportModal} return (<ConfigExportModal show={this.state.showConfigExportModal}
configuration={this.state.configuration} configuration={this.state.configuration}
credentials={this.state.credentials}
onHide={() => { onHide={() => {
this.setState({showConfigExportModal: false}); this.setState({showConfigExportModal: false});
}}/>); }}/>);
@ -255,6 +271,7 @@ class ConfigurePageComponent extends AuthComponent {
sendConfig() { sendConfig() {
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);
delete config['advanced'];
return ( return (
this.authFetch(CONFIG_URL, this.authFetch(CONFIG_URL,
@ -265,13 +282,36 @@ class ConfigurePageComponent extends AuthComponent {
}) })
.then(res => { .then(res => {
if (!res.ok) { if (!res.ok) {
throw Error() console.log(`bad configuration submited ${res.status}`);
this.setState({lastAction: 'invalid_configuration'});
} else {
this.setState({
lastAction: configSaveAction
});
this.setInitialConfig(this.state.configuration);
this.props.onStatusChange();
} }
return res; return res;
}).catch((error) => { }));
console.log(`bad configuration ${error}`); }
this.setState({lastAction: 'invalid_configuration'});
})); sendCredentials() {
return (
this.authFetch(CONFIGURED_PROPAGATION_CREDENTIALS_URL,
{
method: 'PUT',
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'});
}));
} }
renderConfigContent = (displayedSchema) => { renderConfigContent = (displayedSchema) => {
@ -285,21 +325,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

@ -8,7 +8,10 @@ export function getAllUsernames(stolen, configured){
export function getCredentialsUsernames(credentials) { export function getCredentialsUsernames(credentials) {
let usernames = []; let usernames = [];
for(let i = 0; i < credentials.length; i++){ for(let i = 0; i < credentials.length; i++){
usernames.push(credentials[i]['identity']['username']); let username = credentials[i]['identity'];
if(username !== null) {
usernames.push(username['username']);
}
} }
return usernames; return usernames;
} }
@ -16,10 +19,16 @@ export function getCredentialsUsernames(credentials) {
export function getAllSecrets(stolen, configured){ export function getAllSecrets(stolen, configured){
let secrets = []; let secrets = [];
for(let i = 0; i < stolen.length; i++){ for(let i = 0; i < stolen.length; i++){
secrets.push(getSecretsFromCredential(stolen[i]['secret'])); let secret = stolen[i]['secret'];
if(secret !== null){
secrets.push(getSecretsFromCredential(secret));
}
} }
for(let i = 0; i < configured.length; i++){ for(let i = 0; i < configured.length; i++){
secrets.push(getSecretsFromCredential(configured[i]['secret'])); let secret = configured[i]['secret'];
if(secret !== null){
secrets.push(getSecretsFromCredential(secret));
}
} }
return secrets; return secrets;
} }

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;

View File

@ -15,9 +15,9 @@ const EXPLOITATION_CONFIGURATION_SCHEMA = {
'title': 'Vulnerability Exploiters', 'title': 'Vulnerability Exploiters',
'type': 'array', 'type': 'array',
'uniqueItems': true 'uniqueItems': true
} },
'options': EXPLOITATION_OPTIONS_CONFIGURATION_SCHEMA
}, },
'options': EXPLOITATION_OPTIONS_CONFIGURATION_SCHEMA,
'type': 'object' 'type': 'object'
}; };

View File

@ -4,11 +4,11 @@ const FINGERPRINTER_CLASSES = {
'Infection Monkey scans.', 'Infection Monkey scans.',
'type': 'string', 'type': 'string',
'pluginDefs': { 'pluginDefs': {
'smb' : {'name':'smb', 'options':''}, 'smb' : {'name':'smb', 'options':{}},
'ssh' : {'name':'ssh', 'options':''}, 'ssh' : {'name':'ssh', 'options':{}},
'http' : {'name':'http', 'options':''}, 'http' : {'name':'http', 'options':{}},
'mssql' : {'name':'mssql', 'options':''}, 'mssql' : {'name':'mssql', 'options':{}},
'elastic' : {'name':'elastic', 'options':''} 'elastic' : {'name':'elastic', 'options':{}}
}, },
'anyOf': [ 'anyOf': [
{ {