forked from p34709852/monkey
Merge pull request #2096 from guardicore/1965-credentials-in-config
1965 credentials in config
This commit is contained in:
commit
f086637758
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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}/>
|
||||||
|
</>)
|
||||||
|
}
|
|
@ -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],
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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': [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue