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
import flask_restful
from cc.database import mongo
from cc.services.edge import EdgeService
__author__ = 'Barak'
@ -11,7 +9,6 @@ __author__ = 'Barak'
class Edge(flask_restful.Resource):
def get(self):
edge_id = request.args.get('id')
if 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
import flask_restful
from cc.database import mongo
from cc.services.edge import EdgeService
from cc.services.node import NodeService
__author__ = 'Barak'

View File

@ -169,11 +169,11 @@ class EdgeService:
@staticmethod
def get_edge_group(edge):
if edge["exploited"]:
if edge.get("exploited"):
return "exploited"
if edge["tunnel"]:
if edge.get("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 "empty"

View File

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

View File

@ -70,8 +70,6 @@
"react-bootstrap": "^0.31.2",
"react-copy-to-clipboard": "^5.0.0",
"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-fa": "^4.2.0",
"react-graph-vis": "^0.1.3",

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react';
import Form from 'react-jsonschema-form';
import {Col} from 'react-bootstrap';
import {Col, Nav, NavItem} from 'react-bootstrap';
class ConfigurePageComponent extends React.Component {
constructor(props) {
@ -47,9 +47,9 @@ class ConfigurePageComponent extends React.Component {
});
};
setSelectedSection = (event) => {
setSelectedSection = (key) => {
this.setState({
selectedSection: event.target.value
selectedSection: key
});
};
@ -57,17 +57,14 @@ class ConfigurePageComponent extends React.Component {
return (
<Col xs={8}>
<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}
className="form-control input-lg" style={{'margin-bottom': '1em'}}>
<Nav bsStyle="tabs" justified
activeKey={this.state.selectedSection} onSelect={this.setSelectedSection}
style={{'marginBottom': '2em'}}>
{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 ?
<Form schema={this.state.schema.properties[this.state.selectedSection]}
@ -75,6 +72,11 @@ class ConfigurePageComponent extends React.Component {
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 ?
<p>Configuration saved successfully.</p>
: ''}

View File

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

View File

@ -1,8 +1,9 @@
import React from 'react';
import {Col} from 'react-bootstrap';
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 = {
layout: {
@ -44,7 +45,9 @@ class MapPageComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
graph: {nodes: [], edges: []}
graph: {nodes: [], edges: []},
selected: null,
selectedType: null
};
}
@ -60,13 +63,19 @@ class MapPageComponent extends React.Component {
selectionChanged(event) {
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) {
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 {
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>
</Col>
<Col xs={8}>
<div className="pull-left">
<input placeholder="Search" />
</div>
<Graph graph={this.state.graph} options={options} events={this.events}/>
</Col>
<Col xs={4}>
<div className="panel panel-default preview">
<div className="panel-heading">
<h3>
<Icon name="fire"/>
vm4
<small>Infected Asset</small>
</h3>
</div>
<input className="form-control input-block"
placeholder="Find on map"
style={{'marginBottom': '1em'}}/>
<div className="panel-body">
<h4>Machine Info</h4>
<p>...</p>
<PreviewPane item={this.state.selected} type={this.state.selectedType} />
<h4 style={{'marginTop': '2em'}}>Exploit Method</h4>
<p>...</p>
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
<ul className="timeline">
<li>
<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>
<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">
<Icon name="stop-circle" style={{'marginRight': '0.5em'}}></Icon>
Kill All Monkeys
</button>
</div>
</Col>
</div>

View File

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

View File

@ -21,7 +21,10 @@ class RunServerPageComponent extends React.Component {
<div style={{'fontSize': '1.5em'}}>
<p>Your Monkey Island server is up and running on <b>{this.state.ip}</b> &#x1F44F; &#x1F44F;</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>
</div>
</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 {
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;
}
.run-monkey-snippets .well {
margin-bottom: 0;
}
/*
* Map Preview Pane
*/
@ -172,10 +192,16 @@ body {
border-bottom: 3px solid #e8e8e8;
padding: 1.5em 1em;
border-radius: 8px;
margin-bottom: 2em;
}
.preview-pane hr {
margin: 10px 0;
}
.preview-pane h3 {
margin: 0;
font-size: 20px;
}
.preview-pane h3 small {
@ -191,6 +217,15 @@ body {
font-size: 1em;
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 {
margin-left: 1em;
}
@ -220,6 +255,7 @@ body {
.timeline .bullet {
width: 16px;
height: 16px;
background: #ccc;
position: absolute;
right: 100%;
@ -247,8 +283,16 @@ body {
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;
}
#search-field , #page-menu {
margin-left: 0.5em;
margin-bottom: 1em;
height: 34px;
padding: 6px 12px;
@ -265,3 +309,11 @@ body {
-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;
}
#search-field {
width: 100px;
}
.data-table-container .pagination {
margin: 0 !important;
}