ui improvements post review

This commit is contained in:
Barak Argaman 2017-09-16 04:37:39 +03:00
parent f0c43f8bb5
commit 0846258bbd
14 changed files with 278 additions and 126 deletions

View File

@ -1,8 +1,6 @@
from bson import ObjectId
from flask import request from flask import request
import flask_restful import flask_restful
from cc.database import mongo
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
__author__ = 'Barak' __author__ = 'Barak'
@ -11,7 +9,6 @@ __author__ = 'Barak'
class Edge(flask_restful.Resource): class Edge(flask_restful.Resource):
def get(self): def get(self):
edge_id = request.args.get('id') edge_id = request.args.get('id')
if edge_id: if edge_id:
return {"edge": EdgeService.get_displayed_edge_by_id(edge_id)} return {"edge": EdgeService.get_displayed_edge_by_id(edge_id)}

View File

@ -1,9 +1,6 @@
from bson import ObjectId
from flask import request from flask import request
import flask_restful import flask_restful
from cc.database import mongo
from cc.services.edge import EdgeService
from cc.services.node import NodeService from cc.services.node import NodeService
__author__ = 'Barak' __author__ = 'Barak'

View File

@ -169,11 +169,11 @@ class EdgeService:
@staticmethod @staticmethod
def get_edge_group(edge): def get_edge_group(edge):
if edge["exploited"]: if edge.get("exploited"):
return "exploited" return "exploited"
if edge["tunnel"]: if edge.get("tunnel"):
return "tunnel" return "tunnel"
if (len(edge["scans"]) > 0) or (len(edge["exploits"]) > 0): if (len(edge.get("scans", [])) > 0) or (len(edge.get("exploits", [])) > 0):
return "scan" return "scan"
return "empty" return "empty"

View File

@ -101,10 +101,7 @@ class NodeService:
@staticmethod @staticmethod
def get_node_group(node): def get_node_group(node):
if node["exploited"]: return "exploited" if node.get("exploited") else "clean"
return "exploited"
else:
return "clean"
@staticmethod @staticmethod
def monkey_to_net_node(monkey): def monkey_to_net_node(monkey):

View File

@ -70,8 +70,6 @@
"react-bootstrap": "^0.31.2", "react-bootstrap": "^0.31.2",
"react-copy-to-clipboard": "^5.0.0", "react-copy-to-clipboard": "^5.0.0",
"react-data-components": "^1.1.1", "react-data-components": "^1.1.1",
"react-data-grid": "^2.0.58",
"react-data-grid-addons": "^2.0.58",
"react-dom": "^15.6.1", "react-dom": "^15.6.1",
"react-fa": "^4.2.0", "react-fa": "^4.2.0",
"react-graph-vis": "^0.1.3", "react-graph-vis": "^0.1.3",

View File

@ -14,6 +14,7 @@ require('react-data-components/css/table-twbs.css');
require('styles/App.css'); require('styles/App.css');
let logoImage = require('../images/monkey-logo.png'); let logoImage = require('../images/monkey-logo.png');
let guardicoreLogoImage = require('../images/guardicore-logo.png');
class AppComponent extends React.Component { class AppComponent extends React.Component {
render() { render() {
@ -24,52 +25,56 @@ class AppComponent extends React.Component {
<Col sm={3} md={2} className="sidebar"> <Col sm={3} md={2} className="sidebar">
<div className="header"> <div className="header">
<img src={logoImage} alt="Infection Monkey"/> <img src={logoImage} alt="Infection Monkey"/>
by GuardiCore
</div> </div>
<ul className="navigation"> <ul className="navigation">
<li> <li>
<NavLink to="/" exact={true}> <NavLink to="/" exact={true}>
<span className="number">1.</span> <span className="number">1.</span>
Run Server Run C&C Server
<Icon name="check" className="pull-right checkmark text-success"/> <Icon name="check" className="pull-right checkmark text-success"/>
</NavLink> </NavLink>
</li> </li>
<li>
<NavLink to="/configure">
<span className="number">2.</span>
Configure
</NavLink>
</li>
<li> <li>
<NavLink to="/run-monkey"> <NavLink to="/run-monkey">
<span className="number">3.</span> <span className="number">2.</span>
Run Monkey Run Monkey
</NavLink> </NavLink>
</li> </li>
<li> <li>
<a className="disabled"> <NavLink to="/infection/map">
<span className="number">4.</span> <span className="number">3.</span>
Infection Infection Map
</a> </NavLink>
<ul>
<li><NavLink to="/infection/map">Map</NavLink></li>
<li><NavLink to="/infection/logs">Full Logs</NavLink></li>
</ul>
</li> </li>
<li> <li>
<NavLink to="/report"> <NavLink to="/report">
<span className="number">5.</span> <span className="number">4.</span>
Pen. Test Report Pen. Test Report
</NavLink> </NavLink>
</li> </li>
<li>
<NavLink to="/start-over">
<span className="number">5.</span>
Start Over
</NavLink>
</li>
</ul> </ul>
<hr/> <hr/>
<ul> <ul>
<li><a>Clear DB</a></li> <li><NavLink to="/configure">Configuration</NavLink></li>
<li><a>Kill All Monkeys</a></li> <li><NavLink to="/infection/logs">Monkey Telemetry</NavLink></li>
</ul> </ul>
<hr/>
<div className="guardicore-link">
<span>Powered by</span>
<a href="http://www.guardicore.com" target="_blank">
<img src={guardicoreLogoImage} alt="GuardiCore"/>
</a>
</div>
</Col> </Col>
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main"> <Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
<Route exact path="/" component={RunServerPage}/> <Route exact path="/" component={RunServerPage}/>

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import Form from 'react-jsonschema-form'; import Form from 'react-jsonschema-form';
import {Col} from 'react-bootstrap'; import {Col, Nav, NavItem} from 'react-bootstrap';
class ConfigurePageComponent extends React.Component { class ConfigurePageComponent extends React.Component {
constructor(props) { constructor(props) {
@ -47,9 +47,9 @@ class ConfigurePageComponent extends React.Component {
}); });
}; };
setSelectedSection = (event) => { setSelectedSection = (key) => {
this.setState({ this.setState({
selectedSection: event.target.value selectedSection: key
}); });
}; };
@ -57,17 +57,14 @@ class ConfigurePageComponent extends React.Component {
return ( return (
<Col xs={8}> <Col xs={8}>
<h1 className="page-title">Monkey Configuration</h1> <h1 className="page-title">Monkey Configuration</h1>
<div className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
This configuration will only apply on new infections.
</div>
<select value={this.state.selectedSection} onChange={this.setSelectedSection} <Nav bsStyle="tabs" justified
className="form-control input-lg" style={{'margin-bottom': '1em'}}> activeKey={this.state.selectedSection} onSelect={this.setSelectedSection}
style={{'marginBottom': '2em'}}>
{this.state.sections.map(section => {this.state.sections.map(section =>
<option value={section.key}>{section.title}</option> <NavItem key={section.key} eventKey={section.key}>{section.title}</NavItem>
)} )}
</select> </Nav>
{ this.state.selectedSection ? { this.state.selectedSection ?
<Form schema={this.state.schema.properties[this.state.selectedSection]} <Form schema={this.state.schema.properties[this.state.selectedSection]}
@ -75,6 +72,11 @@ class ConfigurePageComponent extends React.Component {
onSubmit={this.onSubmit}/> onSubmit={this.onSubmit}/>
: ''} : ''}
<div className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
This configuration will only apply to new infections.
</div>
{ this.state.saved ? { this.state.saved ?
<p>Configuration saved successfully.</p> <p>Configuration saved successfully.</p>
: ''} : ''}

View File

@ -3,13 +3,13 @@ import {Col} from 'react-bootstrap';
import JSONTree from 'react-json-tree' import JSONTree from 'react-json-tree'
import {DataTable} from 'react-data-components'; import {DataTable} from 'react-data-components';
const renderJson = (val, row) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true} />; const renderJson = (val) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true} />;
const renderTime = (val, row) => val.split('.')[0]; const renderTime = (val) => val.split('.')[0];
const columns = [ const columns = [
{ title: 'Type', prop: 'telem_type' },
{ title: 'Monkey ID', prop: 'monkey_guid' },
{ title: 'Time', prop: 'timestamp', render: renderTime}, { title: 'Time', prop: 'timestamp', render: renderTime},
{ title: 'Monkey ID', prop: 'monkey_guid' },
{ title: 'Type', prop: 'telem_type' },
{ title: 'More Info', prop: 'data', render: renderJson, width: '40%' } { title: 'More Info', prop: 'data', render: renderJson, width: '40%' }
]; ];

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import {Col} from 'react-bootstrap'; import {Col} from 'react-bootstrap';
import Graph from 'react-graph-vis'; import Graph from 'react-graph-vis';
import {Icon} from 'react-fa' import PreviewPane from 'components/preview-pane/PreviewPane';
import {Link} from 'react-router-dom';
import {Icon} from 'react-fa';
let options = { let options = {
layout: { layout: {
@ -44,7 +45,9 @@ class MapPageComponent extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
graph: {nodes: [], edges: []} graph: {nodes: [], edges: []},
selected: null,
selectedType: null
}; };
} }
@ -60,13 +63,19 @@ class MapPageComponent extends React.Component {
selectionChanged(event) { selectionChanged(event) {
if (event.nodes.length === 1) { if (event.nodes.length === 1) {
console.log('selected node:', event.nodes[0]); console.log('selected node:', event.nodes[0]); // eslint-disable-line no-console
fetch('/api/netmap/node?id='+event.nodes[0])
.then(res => res.json())
.then(res => this.setState({selected: res, selectedType: 'node'}));
} }
else if (event.edges.length === 1) { else if (event.edges.length === 1) {
console.log('selected edge:', event.edges[0]); fetch('/api/netmap/edge?id='+event.edges[0])
.then(res => res.json())
.then(res => this.setState({selected: res.edge, selectedType: 'edge'}));
} }
else { else {
console.log('no preview.'); console.log('selection cleared.'); // eslint-disable-line no-console
this.setState({selected: null, selectedType: null});
} }
} }
@ -77,44 +86,21 @@ class MapPageComponent extends React.Component {
<h1 className="page-title">Infection Map</h1> <h1 className="page-title">Infection Map</h1>
</Col> </Col>
<Col xs={8}> <Col xs={8}>
<div className="pull-left">
<input placeholder="Search" />
</div>
<Graph graph={this.state.graph} options={options} events={this.events}/> <Graph graph={this.state.graph} options={options} events={this.events}/>
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<div className="panel panel-default preview"> <input className="form-control input-block"
<div className="panel-heading"> placeholder="Find on map"
<h3> style={{'marginBottom': '1em'}}/>
<Icon name="fire"/>
vm4
<small>Infected Asset</small>
</h3>
</div>
<div className="panel-body"> <PreviewPane item={this.state.selected} type={this.state.selectedType} />
<h4>Machine Info</h4>
<p>...</p>
<h4 style={{'marginTop': '2em'}}>Exploit Method</h4> <div>
<p>...</p> <Link to="/infection/logs" className="btn btn-default btn-block" style={{'marginBottom': '0.5em'}}>Monkey Telemetry</Link>
<button onClick={this.killAllMonkeys} className="btn btn-danger btn-block">
<h4 style={{'marginTop': '2em'}}>Timeline</h4> <Icon name="stop-circle" style={{'marginRight': '0.5em'}}></Icon>
<ul className="timeline"> Kill All Monkeys
<li> </button>
<div className="bullet"></div>
failed attempt1
</li>
<li>
<div className="bullet"></div>
failed attempt2
</li>
<li>
<div className="bullet bad"></div>
Infection!
</li>
</ul>
</div>
</div> </div>
</Col> </Col>
</div> </div>

View File

@ -2,6 +2,7 @@ import React from 'react';
import {Button, Col, Well} from 'react-bootstrap'; import {Button, Col, Well} from 'react-bootstrap';
import CopyToClipboard from 'react-copy-to-clipboard'; import CopyToClipboard from 'react-copy-to-clipboard';
import {Icon} from 'react-fa'; import {Icon} from 'react-fa';
import {Link} from "react-router-dom";
class RunMonkeyPageComponent extends React.Component { class RunMonkeyPageComponent extends React.Component {
constructor(props) { constructor(props) {
@ -9,7 +10,8 @@ class RunMonkeyPageComponent extends React.Component {
this.state = { this.state = {
ips: [], ips: [],
selectedIp: '0.0.0.0', selectedIp: '0.0.0.0',
isRunning: true isRunningOnIsland: false,
isRunningLocally: false
}; };
} }
@ -17,14 +19,13 @@ class RunMonkeyPageComponent extends React.Component {
fetch('/api') fetch('/api')
.then(res => res.json()) .then(res => res.json())
.then(res => this.setState({ .then(res => this.setState({
ips: res['ip_addresses'], ips: res['ip_addresses']
selectedIp: res['ip_addresses'][0]
})); }));
fetch('/api/local-monkey') fetch('/api/local-monkey')
.then(res => res.json()) .then(res => res.json())
.then(res => this.setState({ .then(res => this.setState({
isRunning: res['is_running'] isRunningOnIsland: res['is_running']
})); }));
} }
@ -37,50 +38,64 @@ class RunMonkeyPageComponent extends React.Component {
{ {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'run', ip: this.state.selectedIp}) body: JSON.stringify({action: 'run'})
}) })
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
this.setState({ this.setState({
isRunning: res['is_running'] isRunningOnIsland: res['is_running']
}); });
}); });
}; };
setSelectedIp = (event) => {
this.setState({selectedIp: event.target.value});
};
render() { render() {
return ( return (
<Col xs={8}> <Col xs={8}>
<h1 className="page-title">Run the Monkey</h1> <h1 className="page-title">Run the Monkey</h1>
<p> <p style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
Select one of the server's IP addresses: You can run the monkey on the C&C server, on your local machine and basically everywhere.
<select value={this.state.selectedIp} onChange={this.setSelectedIp} The more the merrier &#x1F604;
className="form-control inline-select">
{this.state.ips.map(ip =>
<option value={ip}>{ip}</option>
)}
</select>
<br/>That address will be used as the monkey's C&C address.
</p> </p>
<p>Run this snippet on a host for manually infecting it with a Monkey:</p> <p style={{'marginBottom': '2em'}}>
<Well> <button onClick={this.runLocalMonkey}
<CopyToClipboard text={this.generateCmd(this.state.selectedIp)} className="pull-right"> className="btn btn-default"
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard"> disabled={this.state.isRunningOnIsland}>
<Icon name="clipboard"/> Run on C&C Server
</Button> { !this.state.isRunningOnIsland ?
</CopyToClipboard> <Icon name="check" className="text-success" style={{'marginLeft': '5px'}}/>
<code>{this.generateCmd(this.state.selectedIp)}</code> : ''}
</Well> </button>
<p> <a href="/download-monkey"
Or simply click here to <a onClick={this.runLocalMonkey} className="btn btn-default"
className="btn btn-default btn-sm" disabled={this.state.isRunningLocally}
style={{'marginLeft': '0.2em'}}>Run on <b>{this.state.selectedIp}</b></a> style={{'margin-left': '1em'}}>
{ this.state.isRunning ? Download and run locally
<i className="text-success" style={{'marginLeft': '5px'}}>Running...</i> { !this.state.isRunningLocally ?
: ''} <Icon name="check" className="text-success" style={{'marginLeft': '5px'}}/>
: ''}
</a>
</p>
<div className="run-monkey-snippets" style={{'marginBottom': '3em'}}>
<p>
Run one of those snippets on a host for infecting it with a Monkey:
<br/>
<span className="text-muted">(The IP address is used as the monkey's C&C address)</span>
</p>
<Well className="well-sm">
{this.state.ips.map(ip =>
<div style={{'overflow': 'auto', 'padding': '0.5em'}}>
<CopyToClipboard text={this.generateCmd(ip)} className="pull-right btn-sm">
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
<Icon name="clipboard"/>
</Button>
</CopyToClipboard>
<code>{this.generateCmd(ip)}</code>
</div>
)}
</Well>
</div>
<p style={{'fontSize': '1.2em'}}>
Go ahead and monitor the ongoing infection in the <Link to="/infection/map">Infection Map</Link> view.
</p> </p>
</Col> </Col>
); );

View File

@ -21,7 +21,10 @@ class RunServerPageComponent extends React.Component {
<div style={{'fontSize': '1.5em'}}> <div style={{'fontSize': '1.5em'}}>
<p>Your Monkey Island server is up and running on <b>{this.state.ip}</b> &#x1F44F; &#x1F44F;</p> <p>Your Monkey Island server is up and running on <b>{this.state.ip}</b> &#x1F44F; &#x1F44F;</p>
<p> <p>
Now <Link to="/configure">configure the monkey</Link> (or just stick with the default configuration) and <Link to="/run-monkey">run the monkey</Link>. Go ahead and <Link to="/run-monkey">run the monkey</Link>.
</p>
<p style={{'marginTop': '30px'}}>
<i>(You can make further adjustments by <Link to="/configure">configuring the monkey</Link>)</i>
</p> </p>
</div> </div>
</Col> </Col>

View File

@ -0,0 +1,100 @@
import React from 'react';
import {Icon} from "react-fa";
class PreviewPaneComponent extends React.Component {
assetInfo(asset) {
return (
<div>
<table className="table table-condensed">
<tbody>
<tr>
<th>Operating System</th>
<td>{asset.os}</td>
</tr>
<tr>
<th>IP Addresses</th>
<td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
</tr>
<tr>
<th>Services</th>
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
</tr>
<tr>
<th>Accessible From</th>
<td>{asset.accessible_from_nodes.map(val => <div key={val.id}>{val.id}</div>)}</td>
</tr>
</tbody>
</table>
</div>
);
}
infectedAssetInfo(asset) {
return (
<div>
{this.assetInfo(asset)}
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
<ul className="timeline">
{ asset.exploits.map(exploit =>
<li key={exploit.start_timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')}></div>
<div>{exploit.start_timestamp}</div>
<div>{exploit.origin}</div>
<div>{exploit.exploiter}</div>
</li>
)}
</ul>
</div>
);
}
infectionInfo(edge) {
return (
<div>
</div>
);
}
scanInfo(edge) {
return (
<div>
</div>
);
}
render() {
let info = null;
switch (this.props.type) {
case 'edge':
info = this.props.item.exploits.length ?
this.infectionInfo(this.props.item) : this.scanInfo(this.props.item);
case 'node':
info = this.props.item.exploits.some(exploit => exploit.result) ?
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
}
return (
<div className="preview-pane">
{ !this.props.item ?
<span>
<Icon name="hand-o-left" style={{'marginRight': '0.5em'}}></Icon>
Select an item on the map for a preview
</span>
:
<div>
<h3>
<b>{this.props.item.label}</b>
</h3>
<hr/>
{info}
</div>
}
</div>
);
}
}
export default PreviewPaneComponent;

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -91,6 +91,22 @@ body {
li .checkmark { li .checkmark {
font-size: 1.3em; font-size: 1.3em;
margin-right: -15px;
}
hr {
border-top-color: #ccc !important;
}
.guardicore-link span {
color: #999;
vertical-align: middle;
}
.guardicore-link img {
height: 24px;
margin-left: 8px;
vertical-align: middle;
} }
} }
@ -162,6 +178,10 @@ body {
margin-left: 1em; margin-left: 1em;
} }
.run-monkey-snippets .well {
margin-bottom: 0;
}
/* /*
* Map Preview Pane * Map Preview Pane
*/ */
@ -172,10 +192,16 @@ body {
border-bottom: 3px solid #e8e8e8; border-bottom: 3px solid #e8e8e8;
padding: 1.5em 1em; padding: 1.5em 1em;
border-radius: 8px; border-radius: 8px;
margin-bottom: 2em;
}
.preview-pane hr {
margin: 10px 0;
} }
.preview-pane h3 { .preview-pane h3 {
margin: 0; margin: 0;
font-size: 20px;
} }
.preview-pane h3 small { .preview-pane h3 small {
@ -191,6 +217,15 @@ body {
font-size: 1em; font-size: 1em;
margin-top: 0; margin-top: 0;
} }
.preview-pane .table tr:first-child th , .preview-pane .table tr:first-child td {
border-top: 0;
}
.preview-pane .table th {
text-align: left;
}
.preview-pane p, .preview-pane .timeline { .preview-pane p, .preview-pane .timeline {
margin-left: 1em; margin-left: 1em;
} }
@ -220,6 +255,7 @@ body {
.timeline .bullet { .timeline .bullet {
width: 16px; width: 16px;
height: 16px;
background: #ccc; background: #ccc;
position: absolute; position: absolute;
right: 100%; right: 100%;
@ -247,8 +283,16 @@ body {
padding: 15px 8px; padding: 15px 8px;
} }
#search-field , #page-menu { .data-table-container > .container > .row:first-child > div:first-child > div {
display: inline-block;
}
.data-table-container > .container > .row:first-child > div:first-child > div:last-child {
margin-left: 1em; margin-left: 1em;
}
#search-field , #page-menu {
margin-left: 0.5em;
margin-bottom: 1em; margin-bottom: 1em;
height: 34px; height: 34px;
padding: 6px 12px; padding: 6px 12px;
@ -265,3 +309,11 @@ body {
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
} }
#search-field {
width: 100px;
}
.data-table-container .pagination {
margin: 0 !important;
}