Merge branch 'run_page_ui_improvements' into 519/scoutsuite-integration

# Conflicts:
#	monkey/monkey_island/cc/ui/src/styles/Main.scss
This commit is contained in:
VakarisZ 2020-09-18 16:49:28 +03:00
commit bd80823c77
27 changed files with 707 additions and 483 deletions

View File

@ -57,6 +57,7 @@ A quick reference for usernames on different machines (if in doubt check officia
- Ubuntu: ubuntu
- Oracle: clckwrk
- CentOS: centos
- Debian: admin
- Everything else: ec2-user
To manually verify the machine is compatible use commands to download and execute the monkey.

View File

@ -4,7 +4,7 @@ import {Container} from 'react-bootstrap';
import RunServerPage from 'components/pages/RunServerPage';
import ConfigurePage from 'components/pages/ConfigurePage';
import RunMonkeyPage from 'components/pages/RunMonkeyPage';
import RunMonkeyPage from 'components/pages/RunMonkeyPage/RunMonkeyPage';
import MapPage from 'components/pages/MapPage';
import TelemetryPage from 'components/pages/TelemetryPage';
import StartOverPage from 'components/pages/StartOverPage';

View File

@ -1,481 +0,0 @@
import React from 'react';
import {css} from '@emotion/core';
import {Button, Col, Card, Nav, Collapse, Row} from 'react-bootstrap';
import CopyToClipboard from 'react-copy-to-clipboard';
import GridLoader from 'react-spinners/GridLoader';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
import {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent';
import AwsRunTable from '../run-monkey/AwsRunTable';
import MissingBinariesModal from '../ui-components/MissingBinariesModal';
const loading_css_override = css`
display: block;
margin-right: auto;
margin-left: auto;
`;
class RunMonkeyPageComponent extends AuthComponent {
constructor(props) {
super(props);
this.state = {
ips: [],
runningOnIslandState: 'not_running',
runningOnClientState: 'not_running',
awsClicked: false,
selectedIp: '0.0.0.0',
selectedOs: 'windows-32',
showManual: false,
showAws: false,
isOnAws: false,
awsUpdateClicked: false,
awsUpdateFailed: false,
awsMachines: [],
isLoadingAws: true,
isErrorWhileCollectingAwsMachines: false,
awsMachineCollectionErrorMsg: '',
showModal: false,
errorDetails: ''
};
this.closeModal = this.closeModal.bind(this);
}
componentDidMount() {
this.authFetch('/api')
.then(res => res.json())
.then(res => this.setState({
ips: res['ip_addresses'],
selectedIp: res['ip_addresses'][0]
}));
this.authFetch('/api/local-monkey')
.then(res => res.json())
.then(res => {
if (res['is_running']) {
this.setState({runningOnIslandState: 'running'});
} else {
this.setState({runningOnIslandState: 'not_running'});
}
});
this.fetchAwsInfo();
this.fetchConfig();
this.authFetch('/api/client-monkey')
.then(res => res.json())
.then(res => {
if (res['is_running']) {
this.setState({runningOnClientState: 'running'});
} else {
this.setState({runningOnClientState: 'not_running'});
}
});
this.props.onStatusChange();
}
fetchAwsInfo() {
return this.authFetch('/api/remote-monkey?action=list_aws')
.then(res => res.json())
.then(res => {
let is_aws = res['is_aws'];
if (is_aws) {
// On AWS!
// Checks if there was an error while collecting the aws machines.
let is_error_while_collecting_aws_machines = (res['error'] != null);
if (is_error_while_collecting_aws_machines) {
// There was an error. Finish loading, and display error message.
this.setState({
isOnAws: true,
isErrorWhileCollectingAwsMachines: true,
awsMachineCollectionErrorMsg: res['error'],
isLoadingAws: false
});
} else {
// No error! Finish loading and display machines for user
this.setState({isOnAws: true, awsMachines: res['instances'], isLoadingAws: false});
}
} else {
// Not on AWS. Finish loading and don't display the AWS div.
this.setState({isOnAws: false, isLoadingAws: false});
}
});
}
static generateLinuxCmd(ip, is32Bit) {
let bitText = is32Bit ? '32' : '64';
return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000`
}
static generateWindowsCmd(ip, is32Bit) {
let bitText = is32Bit ? '32' : '64';
return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe','.\\monkey.exe'); ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`;
}
runLocalMonkey = () => {
this.authFetch('/api/local-monkey',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'run'})
})
.then(res => res.json())
.then(res => {
if (res['is_running']) {
this.setState({
runningOnIslandState: 'installing'
});
} else {
/* If Monkey binaries are missing, change the state accordingly */
if (res['error_text'].startsWith('Copy file failed')) {
this.setState({
showModal: true,
errorDetails: res['error_text']
}
);
}
this.setState({
runningOnIslandState: 'not_running'
});
}
this.props.onStatusChange();
});
};
generateCmdDiv() {
let isLinux = (this.state.selectedOs.split('-')[0] === 'linux');
let is32Bit = (this.state.selectedOs.split('-')[1] === '32');
let cmdText = '';
if (isLinux) {
cmdText = RunMonkeyPageComponent.generateLinuxCmd(this.state.selectedIp, is32Bit);
} else {
cmdText = RunMonkeyPageComponent.generateWindowsCmd(this.state.selectedIp, is32Bit);
}
return (
<Card key={'cmdDiv' + this.state.selectedIp} style={{'margin': '0.5em'}}>
<div style={{'overflow': 'auto', 'padding': '0.5em'}}>
<CopyToClipboard text={cmdText} className="pull-right btn-sm">
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
<FontAwesomeIcon icon={faClipboard}/>
</Button>
</CopyToClipboard>
<code>{cmdText}</code>
</div>
</Card>
)
}
setSelectedOs = (key) => {
this.setState({
selectedOs: key
});
};
setSelectedIp = (key) => {
this.setState({
selectedIp: key
});
};
static renderIconByState(state) {
if (state === 'running') {
return (<FontAwesomeIcon icon={faCheck} className="text-success" style={{'marginLeft': '5px'}}/>)
} else if (state === 'installing') {
return (<FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/>)
} else {
return '';
}
}
toggleManual = () => {
this.setState({
showManual: !this.state.showManual
});
};
toggleAws = () => {
this.setState({
showAws: !this.state.showAws
});
};
runOnAws = () => {
this.setState({
awsClicked: true
});
let instances = this.awsTable.state.selection.map(x => this.instanceIdToInstance(x));
this.authFetch('/api/remote-monkey',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({type: 'aws', instances: instances, island_ip: this.state.selectedIp})
}).then(res => res.json())
.then(res => {
let result = res['result'];
// update existing state, not run-over
let prevRes = this.awsTable.state.result;
for (let key in result) {
if (Object.prototype.hasOwnProperty.call(result, key)) {
prevRes[key] = result[key];
}
}
this.awsTable.setState({
result: prevRes,
selection: [],
selectAll: false
});
this.setState({
awsClicked: false
});
});
};
fetchConfig() {
return this.authFetch('/api/configuration/island')
.then(res => res.json())
.then(res => {
return res.configuration;
})
}
instanceIdToInstance = (instance_id) => {
let instance = this.state.awsMachines.find(
function (inst) {
return inst['instance_id'] === instance_id;
});
return {'instance_id': instance_id, 'os': instance['os']}
};
renderAwsMachinesDiv() {
return (
<div style={{'marginBottom': '2em'}}>
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<p className="alert alert-info">
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
Not sure what this is? Not seeing your AWS EC2 instances? <a
href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances"
rel="noopener noreferrer" target="_blank">Read the documentation</a>!
</p>
</div>
{
this.state.ips.length > 1 ?
<Nav variant="pills" activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
style={{'marginBottom': '2em'}}>
{this.state.ips.map(ip => <Nav.Item key={ip}><Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
</Nav>
: <div style={{'marginBottom': '2em'}}/>
}
<AwsRunTable
data={this.state.awsMachines}
ref={r => (this.awsTable = r)}
/>
<div style={{'marginTop': '1em'}}>
<Button
onClick={this.runOnAws}
className={'btn btn-default btn-md center-block'}
disabled={this.state.awsClicked}>
Run on selected machines
{this.state.awsClicked ?
<FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/> : null}
</Button>
</div>
</div>
)
}
closeModal = () => {
this.setState({
showModal: false
})
};
render() {
return (
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
className={'main'}>
<h1 className="page-title">1. Run Monkey</h1>
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}>
Go ahead and run the monkey!
<i> (Or <Link to="/configure">configure the monkey</Link> to fine tune its behavior)</i>
</p>
<p className={'text-center'}>
<Button onClick={this.runLocalMonkey}
variant={'outline-monkey'}
size='lg'
disabled={this.state.runningOnIslandState !== 'not_running'}
>
Run on Monkey Island Server
{RunMonkeyPageComponent.renderIconByState(this.state.runningOnIslandState)}
</Button>
<MissingBinariesModal
showModal={this.state.showModal}
onClose={this.closeModal}
errorDetails={this.state.errorDetails}/>
{
// TODO: implement button functionality
/*
<button
className="btn btn-default"
disabled={this.state.runningOnClientState !== 'not_running'}
style={{'marginLeft': '1em'}}>
Download and run locally
{ this.renderIconByState(this.state.runningOnClientState) }
</button>
*/
}
</p>
<p className="text-center">
OR
</p>
<p className={'text-center'}
style={this.state.showManual || !this.state.isOnAws ? {'marginBottom': '2em'} : {}}>
<Button onClick={this.toggleManual}
variant={'outline-monkey'}
size='lg'
className={(this.state.showManual ? 'active' : '')}>
Run on a machine of your choice
</Button>
</p>
<Collapse in={this.state.showManual}>
<div style={{'marginBottom': '2em'}}>
<p style={{'fontSize': '1.2em'}}>
Choose the operating system where you want to run the monkey:
</p>
<Row>
<Col>
<Nav variant='pills' fill id={'bootstrap-override'} className={'run-on-os-buttons'}
activeKey={this.state.selectedOs} onSelect={this.setSelectedOs}>
<Nav.Item>
<Nav.Link eventKey={'windows-32'}>
Windows (32 bit)
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey='windows-64'>
Windows (64 bit)
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey='linux-32'>
Linux (32 bit)
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey='linux-64'>
Linux (64 bit)
</Nav.Link>
</Nav.Item>
</Nav>
</Col>
</Row>
{this.state.ips.length > 1 ?
<div>
<Row>
<Col>
<p style={{'fontSize': '1.2em'}}>
Choose the interface to communicate with:
</p>
</Col>
</Row>
<Row>
<Col>
<Nav variant="pills" fill activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
className={'run-on-os-buttons'}>
{this.state.ips.map(ip => <Nav.Item key={ip}>
<Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
</Nav>
</Col>
</Row>
</div>
: <div style={{'marginBottom': '2em'}}/>
}
<p style={{'fontSize': '1.2em'}}>
Copy the following command to your machine and run it with Administrator or root privileges.
</p>
{this.generateCmdDiv()}
</div>
</Collapse>
{
this.state.isLoadingAws ?
<div style={{'marginBottom': '2em', 'align': 'center'}}>
<div className='sweet-loading'>
<GridLoader
css={loading_css_override}
sizeUnit={'px'}
size={30}
color={'#ffcc00'}
loading={this.state.loading}
/>
</div>
</div>
: null
}
{
this.state.isOnAws ?
<p className="text-center">
OR
</p>
:
null
}
{
this.state.isOnAws ?
<p style={{'marginBottom': '2em'}} className={'text-center'}>
<Button onClick={this.toggleAws}
className={(this.state.showAws ? ' active' : '')}
size='lg'
variant={'outline-monkey'}>
Run on AWS machine of your choice
</Button>
</p>
:
null
}
<Collapse in={this.state.showAws}>
{
this.state.isErrorWhileCollectingAwsMachines ?
<div style={{'marginTop': '1em'}}>
<p className="alert alert-danger">
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
Error while collecting AWS machine data. Error
message: <code>{this.state.awsMachineCollectionErrorMsg}</code><br/>
Are you sure you've set the correct role on your Island AWS machine?<br/>
Not sure what this is? <a
href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances">Read
the documentation</a>!
</p>
</div>
:
this.renderAwsMachinesDiv()
}
</Collapse>
<p style={{'fontSize': '1.2em'}}>
Go ahead and monitor the ongoing infection in the <Link to="/infection/map">Infection Map</Link> view.
</p>
</Col>
);
}
}
export default RunMonkeyPageComponent;

View File

@ -0,0 +1,63 @@
import {Button, Card, Nav} from 'react-bootstrap';
import CopyToClipboard from 'react-copy-to-clipboard';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard';
import React, {useEffect, useState} from 'react';
import PropTypes from 'prop-types';
export default function commandDisplay(props) {
const [selectedCommand, setSelectedCommand] = useState(props.commands[0]);
function setSelectedCommandByName(type){
setSelectedCommand(getCommandByName(props.commands, type));
}
function getCommandByName(commands, type){
return commands.find((command) => {return command.type === type});
}
useEffect(() => {
let sameTypeCommand = getCommandByName(props.commands, selectedCommand.type);
if( sameTypeCommand !== undefined){
setSelectedCommand(sameTypeCommand);
} else {
setSelectedCommand(props.commands[0]);
}
}, [props.commands]);
function renderNav() {
return (
<Nav variant='tabs' activeKey={selectedCommand.type} onSelect={setSelectedCommandByName}>
{props.commands.map(command => {
return (
<Nav.Item key={command.type}>
<Nav.Link eventKey={command.type}>{command.type}</Nav.Link>
</Nav.Item>);
})}
</Nav>);
}
return (
<div className={'command-display'}>
{renderNav()}
<Card>
<div style={{'overflow': 'auto', 'padding': '0.5em'}}>
<CopyToClipboard text={selectedCommand.type} className="pull-right btn-sm">
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
<FontAwesomeIcon icon={faClipboard}/>
</Button>
</CopyToClipboard>
<code>{selectedCommand.command}</code>
</div>
</Card>
</div>
)
}
commandDisplay.propTypes = {
commands: PropTypes.arrayOf(PropTypes.exact({
type: PropTypes.string,
command: PropTypes.string
}))
}

View File

@ -0,0 +1,29 @@
import React, {useEffect, useState} from 'react';
import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import CommandSection from '../../ui-components/inline-selection/CommandSection';
import LocalManualRunOptions from './LocalManualRunOptions';
function InterfaceSelection(props) {
return InlineSelection(getContents, props, LocalManualRunOptions)
}
const getContents = (props) => {
const ips = props.ips.map((ip) =>
<div>{ip}</div>
);
return (<div>{ips}</div>);
}
const setCommandAsContent = (props) => {
let commandComponent = () => InlineSelection(CommandSection,
{
commands: win64commands,
setComponent: props.setComponent
},
LocalManualRunOptions
);
props.setComponent(commandComponent, props);
}
export default InterfaceSelection;

View File

@ -0,0 +1,60 @@
import React, {useEffect, useState} from 'react';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import DropdownSelect from '../../ui-components/DropdownSelect';
import {OS_TYPES} from './OsTypes';
import GenerateLocalWindowsCmd from './commands/local_windows_cmd';
import GenerateLocalWindowsPowershell from './commands/local_windows_powershell';
import GenerateLocalLinuxWget from './commands/local_linux_wget';
import GenerateLocalLinuxCurl from './commands/local_linux_curl';
import CommandDisplay from './CommandDisplay';
import RunOptions from './RunOptions';
const LocalManualRunOptions = (props) => {
return InlineSelection(getContents, {
...props,
onBackButtonClick: () => {props.setComponent()}
})
}
const getContents = (props) => {
const osTypes = {
[OS_TYPES.WINDOWS_64]: 'Windows 64bit',
[OS_TYPES.WINDOWS_32]: 'Windows 32bit',
[OS_TYPES.LINUX_64]: 'Linux 64bit',
[OS_TYPES.LINUX_32]: 'Linux 32bit'
}
const [osType, setOsType] = useState(OS_TYPES.WINDOWS_64);
const [selectedIp, setSelectedIp] = useState(props.ips[0]);
const [commands, setCommands] = useState(generateCommands());
useEffect(() => {
setCommands(generateCommands());
}, [osType, selectedIp])
function setIp(index) {
setSelectedIp(props.ips[index]);
}
function generateCommands() {
if (osType === OS_TYPES.WINDOWS_64 || osType === OS_TYPES.WINDOWS_32) {
return [{type: 'CMD', command: GenerateLocalWindowsCmd(selectedIp, osType)},
{type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, osType)}]
} else {
return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, osType)},
{type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, osType)}]
}
}
return (
<>
<DropdownSelect defaultKey={'win64'} options={osTypes} onClick={setOsType} variant={'outline-monkey'}/>
<DropdownSelect defaultKey={0} options={props.ips} onClick={setIp} variant={'outline-monkey'}/>
<CommandDisplay commands={commands}/>
</>
)
}
export default LocalManualRunOptions;

View File

@ -0,0 +1,6 @@
export const OS_TYPES = {
WINDOWS_32: 'win32',
WINDOWS_64: 'win64',
LINUX_32: 'linux32',
LINUX_64: 'linux64'
}

View File

@ -0,0 +1,26 @@
import React from 'react';
import {Col} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import AuthComponent from '../../AuthComponent';
import RunOptions from './RunOptions';
class RunMonkeyPageComponent extends AuthComponent {
render() {
return (
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
className={'main'}>
<h1 className="page-title">1. Run Monkey</h1>
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}>
Go ahead and run the monkey!
<i> (Or <Link to="/configure">configure the monkey</Link> to fine tune its behavior)</i>
</p>
<RunOptions />
</Col>
);
}
}
export default RunMonkeyPageComponent;

View File

@ -0,0 +1,113 @@
import React from 'react';
import {Button, Col, Row} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
import AuthComponent from '../../AuthComponent';
import MissingBinariesModal from '../../ui-components/MissingBinariesModal';
import '../../../styles/components/RunOnIslandButton.scss';
class RunOnIslandButton extends AuthComponent {
constructor(props) {
super(props);
this.state = {
runningOnIslandState: 'not_running',
showModal: false,
errorDetails: ''
};
this.closeModal = this.closeModal.bind(this);
}
componentDidMount() {
this.authFetch('/api/local-monkey')
.then(res => res.json())
.then(res => {
if (res['is_running']) {
this.setState({runningOnIslandState: 'running'});
} else {
this.setState({runningOnIslandState: 'not_running'});
}
});
}
runIslandMonkey = () => {
this.setState({runningOnIslandState: 'installing'}, this.sendRunMonkeyRequest)
};
sendRunMonkeyRequest = () => {
this.authFetch('/api/local-monkey',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'run'})
})
.then(res => res.json())
.then(res => {
if (res['is_running']) {
this.setState({
runningOnIslandState: 'running'
});
} else {
/* If Monkey binaries are missing, change the state accordingly */
if (res['error_text'].startsWith('Copy file failed')) {
this.setState({
showModal: true,
errorDetails: res['error_text']
}
);
}
this.setState({
runningOnIslandState: 'not_running'
});
}
});
}
closeModal = () => {
this.setState({
showModal: false
})
};
getMonkeyRunStateIcon = () => {
if (this.state.runningOnIslandState === 'running') {
return (<FontAwesomeIcon icon={faCheck}
className={`monkey-on-island-run-state-icon text-success`}/>)
} else if (this.state.runningOnIslandState === 'installing') {
return (<FontAwesomeIcon icon={faSync}
className={`monkey-on-island-run-state-icon text-success spinning-icon`}/>)
} else {
return '';
}
}
render() {
let description = this.props.description !== undefined ? (<p>{this.props.description}</p>) : ''
let icon = this.props.icon !== undefined ? (<FontAwesomeIcon icon={this.props.icon}/>) : ''
return (
<Row>
<Col>
<MissingBinariesModal
showModal={this.state.showModal}
onClose={this.closeModal}
errorDetails={this.state.errorDetails}/>
<Button variant={'outline-monkey'} size='lg' className={'selection-button'}
onClick={this.runIslandMonkey}>
{icon}
<h1>{this.props.title}</h1>
{description}
{this.getMonkeyRunStateIcon()}
</Button>
</Col>
</Row>
);
}
}
export default RunOnIslandButton;

View File

@ -0,0 +1,71 @@
import React, {useEffect, useState} from 'react';
import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
import LocalManualRunOptions from './LocalManualRunOptions';
import AuthComponent from '../../AuthComponent';
import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import {cloneDeep} from 'lodash';
import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
import RunOnIslandButton from './RunOnIslandButton';
function RunOptions(props) {
const [currentContent, setCurrentContent] = useState(loadingContents());
const [ips, setIps] = useState([]);
const [initialized, setInitialized] = useState(false);
const authComponent = new AuthComponent({})
useEffect(() => {
if (initialized === false) {
authComponent.authFetch('/api')
.then(res => res.json())
.then(res => {
setIps([res['ip_addresses']][0]);
setInitialized(true);
});
}
})
useEffect(() => {
setCurrentContent(getDefaultContents());
}, [initialized])
function setComponent(component, props) {
if (component === undefined) {
setCurrentContent(getDefaultContents())
} else {
setCurrentContent(component({...props}))
}
}
function loadingContents() {
return (<div>Loading</div>)
}
function getDefaultContents() {
const newProps = cloneDeep({...props});
return InlineSelection(defaultContents, newProps);
}
function defaultContents() {
return (
<>
<RunOnIslandButton title={'From Island'}
description={'Start on Monkey Island server.'}
icon={faExpandArrowsAlt}/>
<NextSelectionButton title={'Manual'}
description={'Run on a machine via command.'}
icon={faLaptopCode}
onButtonClick={() => {
setComponent(LocalManualRunOptions,
{ips: ips, setComponent: setComponent})
}}/>
</>
);
}
return currentContent;
}
export default RunOptions;

View File

@ -0,0 +1,13 @@
import {OS_TYPES} from '../OsTypes';
export default function generateLocalLinuxCurl(ip, osType) {
let bitText = osType === OS_TYPES.LINUX_32 ? '32' : '64';
return `curl https://${ip}:5000/api/monkey/download/monkey-linux-${bitText} -k
-o monkey-linux-${bitText};
chmod +x monkey-linux-${bitText};
./monkey-linux-${bitText} m0nk3y -s ${ip}:5000\`;`;
}

View File

@ -0,0 +1,10 @@
import {OS_TYPES} from '../OsTypes';
export default function generateLocalLinuxWget(ip, osType) {
let bitText = osType === OS_TYPES.LINUX_32 ? '32' : '64';
return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/
monkey-linux-${bitText};
chmod +x monkey-linux-${bitText};
./monkey-linux-${bitText} m0nk3y -s ${ip}:5000`;
}

View File

@ -0,0 +1,10 @@
import {OS_TYPES} from '../OsTypes';
export default function generateLocalWindowsCmd(ip, osType) {
let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64';
return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};
(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/
monkey-windows-${bitText}.exe','.\\monkey.exe');
;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`;
}

View File

@ -0,0 +1,10 @@
import {OS_TYPES} from '../OsTypes';
export default function generateLocalWindowsPowershell(ip, osType) {
let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64';
return `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};
(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/
monkey-windows-${bitText}.exe','.\\monkey.exe');
;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`;
}

View File

@ -0,0 +1,63 @@
import React, {useState} from 'react';
import {Dropdown} from 'react-bootstrap';
import PropTypes from 'prop-types';
export default function DropdownSelect(props) {
const [selectedOption, setSelectedOption] = useState(props.defaultKey);
function generateDropdownItems(data) {
if (Array.isArray(data)) {
return generateDropdownItemsFromArray(data);
} else if (typeof data === 'object') {
return generateDropdownItemsFromObject(data);
} else {
throw 'Component can only generate dropdown items from arrays and objects.'
}
}
function generateDropdownItemsFromArray(data) {
const dropdownItems = [];
for (let i = 0; i < data.length; i++) {
dropdownItems.push(generateDropdownItem(i, data[i]));
}
return dropdownItems;
}
function generateDropdownItemsFromObject(data) {
const dropdownItems = [];
for (let [key, value] of Object.entries(data)) {
dropdownItems.push(generateDropdownItem(key, value));
}
return dropdownItems;
}
function generateDropdownItem(key, value) {
return (
<Dropdown.Item onClick={() => { setSelectedOption(key);
props.onClick(key)}}
active={(key === selectedOption)}
key={value}>
{value}
</Dropdown.Item>);
}
return (
<>
<Dropdown>
<Dropdown.Toggle variant={props.variant !== undefined ? props.variant : 'success'} id='dropdown-basic'>
{props.options[selectedOption]}
</Dropdown.Toggle>
<Dropdown.Menu>
{generateDropdownItems(props.options)}
</Dropdown.Menu>
</Dropdown>
</>
)
}
DropdownSelect.propTypes = {
options: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
defaultKey: PropTypes.oneOfType([PropTypes.string,PropTypes.number]),
onClick: PropTypes.func
}

View File

@ -0,0 +1,12 @@
import React from 'react';
const Emoji = props => (
<span
className="emoji"
role="img"
aria-label={props.label ? props.label : ""}
aria-hidden={props.label ? "false" : "true"}
>
{props.symbol}
</span>
);
export default Emoji;

View File

@ -0,0 +1,22 @@
import {Button, Col, Row} from 'react-bootstrap';
import React from 'react';
import PropTypes from 'prop-types';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCaretLeft} from '@fortawesome/free-solid-svg-icons/faCaretLeft';
export default function backButton(props) {
return (
<Row>
<Col>
<Button variant={'outline-dark'} onClick={props.onClick} className={'back-button'}>
<FontAwesomeIcon icon={faCaretLeft} />
<h1>Back</h1>
</Button>
</Col>
</Row>
)
}
backButton.propTypes = {
onClick: PropTypes.func
}

View File

@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
export default function CommandSection(props){
return (
<div className={'command-section'}>
{props.commands[0].name}
{props.commands[0].command}
</div>
)
}
CommandSection.propTypes = {
commands: PropTypes.arrayOf(PropTypes.exact({
name: PropTypes.string,
command: PropTypes.string
}))
}

View File

@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import BackButton from './BackButton';
import {Col, Row, Container} from 'react-bootstrap';
export default function InlineSelection(WrappedComponent, props) {
return (
<Container className={'inline-selection-component'}>
<Row>
<Col lg={8} md={10} sm={12}>
<WrappedComponent {...props}/>
{renderBackButton(props)}
</Col>
</Row>
</Container>
)
}
function renderBackButton(props){
if(props.onBackButtonClick !== undefined){
return (<BackButton onClick={props.onBackButtonClick}/>);
}
}
InlineSelection.propTypes = {
setComponent: PropTypes.func,
ips: PropTypes.arrayOf(PropTypes.string),
onBackButtonClick: PropTypes.func
}

View File

@ -0,0 +1,30 @@
import {Button, Row, Col} from 'react-bootstrap';
import React from 'react';
import PropTypes from 'prop-types';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faAngleRight} from '@fortawesome/free-solid-svg-icons';
export default function nextSelectionButton(props) {
let description = props.description !== undefined ? (<p>{props.description}</p>) : ''
let icon = props.icon !== undefined ? (<FontAwesomeIcon icon={props.icon}/>) : ''
return (
<Row>
<Col>
<Button variant={'outline-monkey'} size='lg' className={'selection-button'}
onClick={props.onButtonClick}>
{icon}
<h1>{props.title}</h1>
{description}
<FontAwesomeIcon icon={faAngleRight} className={'angle-right'}/>
</Button>
</Col>
</Row>
)
}
nextSelectionButton.propTypes = {
title: PropTypes.string,
icon: FontAwesomeIcon,
description: PropTypes.string,
onButtonClick: PropTypes.func
}

View File

@ -13,6 +13,11 @@
@import 'components/AdvancedMultiSelect';
@import 'components/particle-component/ParticleBackground';
@import 'components/scoutsuite/ResourceDropdown';
@import 'components/inline-selection/InlineSelection';
@import 'components/inline-selection/NextSelectionButton';
@import 'components/inline-selection/BackButton';
@import 'components/inline-selection/CommandDisplay';
@import 'components/Icons';
// Define custom elements after bootstrap import

View File

@ -0,0 +1,13 @@
.spinning-icon {
animation: spin-animation 0.5s infinite;
display: inline-block;
}
@keyframes spin-animation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}

View File

@ -0,0 +1,7 @@
.monkey-on-island-run-state-icon {
display: inline-block;
position: absolute;
right: 23px;
top: 28%;
font-size: 1.1em;
}

View File

@ -0,0 +1,20 @@
.inline-selection-component .back-button {
width: 100%;
text-align: left;
}
.inline-selection-component .back-button h1{
font-size: 1.3em;
margin-top: 5px;
margin-bottom: 5px;
text-align: left;
display: inline-block;
}
.inline-selection-component .back-button svg{
font-size: 1.5em;
display: inline-block;
margin-right: 10px;
position: relative;
top: 1px;
}

View File

@ -0,0 +1,21 @@
.command-display {
margin-top: 30px;
}
.command-display .nav-tabs .nav-item a{
font-size: 0.8em;
color: $monkey-black;
}
.command-display .nav-tabs .nav-item a.active{
color: $monkey-alt;
}
.command-display .nav-tabs{
border-bottom: none;
}
.command-display div.card{
margin: 0;
border-top-left-radius: 0;
}

View File

@ -0,0 +1,17 @@
.inline-selection-component.container{
padding: 0;
margin-left: 15px;
}
.inline-selection-component .selection-button {
width: 100%;
}
.inline-selection-component .dropdown {
display: inline-block;
margin-right: 10px;
}
.inline-selection-component .command-display {
margin-bottom: 20px;
}

View File

@ -0,0 +1,34 @@
.inline-selection-component .selection-button {
width: 100%;
text-align: left;
margin-bottom: 20px;
padding-right: 40px;
}
.inline-selection-component .selection-button svg,
.inline-selection-component .selection-button h1 {
display: inline-block;
}
.inline-selection-component .selection-button h1 {
margin: 0;
font-size: 1.3em;
}
.inline-selection-component .selection-button p {
margin: 0;
font-size: 0.8em;
}
.inline-selection-component .selection-button svg {
margin-bottom: 1px;
margin-right: 7px;
}
.inline-selection-component .selection-button .angle-right {
display: inline-block;
position: absolute;
right: 23px;
top: 22%;
font-size: 1.7em;
}