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 - Ubuntu: ubuntu
- Oracle: clckwrk - Oracle: clckwrk
- CentOS: centos - CentOS: centos
- Debian: admin
- Everything else: ec2-user - Everything else: ec2-user
To manually verify the machine is compatible use commands to download and execute the monkey. 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 RunServerPage from 'components/pages/RunServerPage';
import ConfigurePage from 'components/pages/ConfigurePage'; 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 MapPage from 'components/pages/MapPage';
import TelemetryPage from 'components/pages/TelemetryPage'; import TelemetryPage from 'components/pages/TelemetryPage';
import StartOverPage from 'components/pages/StartOverPage'; import StartOverPage from 'components/pages/StartOverPage';
@ -30,7 +30,7 @@ const reportZeroTrustRoute = '/report/zeroTrust';
class AppComponent extends AuthComponent { class AppComponent extends AuthComponent {
updateStatus = () => { updateStatus = () => {
if (this.state.isLoggedIn === false){ if (this.state.isLoggedIn === false) {
return return
} }
this.auth.loggedIn() this.auth.loggedIn()

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/AdvancedMultiSelect';
@import 'components/particle-component/ParticleBackground'; @import 'components/particle-component/ParticleBackground';
@import 'components/scoutsuite/ResourceDropdown'; @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 // 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;
}