forked from p15670423/monkey
Merge branch 'nadler/pth-map' into nadler/pth
This commit is contained in:
commit
2a12fefe6d
|
@ -1,4 +1,4 @@
|
||||||
let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
|
const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
|
||||||
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
|
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
|
||||||
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
|
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
|
||||||
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
|
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
|
||||||
|
@ -16,7 +16,22 @@ let getGroupsOptions = () => {
|
||||||
return groupOptions;
|
return groupOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const options = {
|
const groupNamesPth = ['normal', 'critical'];
|
||||||
|
|
||||||
|
let getGroupsOptionsPth = () => {
|
||||||
|
let groupOptions = {};
|
||||||
|
for (let groupName of groupNamesPth) {
|
||||||
|
groupOptions[groupName] =
|
||||||
|
{
|
||||||
|
shape: 'image',
|
||||||
|
size: 50,
|
||||||
|
image: require('../../images/nodes/pth/' + groupName + '.png')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return groupOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const basic_options = {
|
||||||
autoResize: true,
|
autoResize: true,
|
||||||
layout: {
|
layout: {
|
||||||
improvedLayout: false
|
improvedLayout: false
|
||||||
|
@ -33,10 +48,22 @@ export const options = {
|
||||||
avoidOverlap: 0.5
|
avoidOverlap: 0.5
|
||||||
},
|
},
|
||||||
minVelocity: 0.75
|
minVelocity: 0.75
|
||||||
},
|
}
|
||||||
groups: getGroupsOptions()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const options = (() => {
|
||||||
|
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||||
|
opts.groups = getGroupsOptions();
|
||||||
|
return opts;
|
||||||
|
})();
|
||||||
|
|
||||||
|
export const optionsPth = (() => {
|
||||||
|
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||||
|
opts.groups = getGroupsOptionsPth();
|
||||||
|
opts.physics.barnesHut.gravitationalConstant = -20000;
|
||||||
|
return opts;
|
||||||
|
})();
|
||||||
|
|
||||||
export function edgeGroupToColor(group) {
|
export function edgeGroupToColor(group) {
|
||||||
switch (group) {
|
switch (group) {
|
||||||
case 'exploited':
|
case 'exploited':
|
||||||
|
|
|
@ -0,0 +1,247 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Icon} from 'react-fa';
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||||
|
import download from 'downloadjs'
|
||||||
|
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||||
|
|
||||||
|
class InfMapPreviewPaneComponent extends PreviewPaneComponent {
|
||||||
|
|
||||||
|
osRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>Operating System</th>
|
||||||
|
<td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipsRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>IP Addresses</th>
|
||||||
|
<td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
servicesRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>Services</th>
|
||||||
|
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
accessibleRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Accessible From
|
||||||
|
{this.generateToolTip('List of machine which can access this one using a network protocol')}
|
||||||
|
</th>
|
||||||
|
<td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
statusRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
forceKill(event, asset) {
|
||||||
|
let newConfig = asset.config;
|
||||||
|
newConfig['alive'] = !event.target.checked;
|
||||||
|
this.authFetch('/api/monkey/' + asset.guid,
|
||||||
|
{
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({config: newConfig})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
forceKillRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Force Kill
|
||||||
|
{this.generateToolTip('If this is on, monkey will die next time it communicates')}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
|
||||||
|
onChange={(e) => this.forceKill(e, asset)}/>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unescapeLog(st) {
|
||||||
|
return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\r/g, "\r")
|
||||||
|
.replace(/\\t/g, "\t")
|
||||||
|
.replace(/\\b/g, "\b")
|
||||||
|
.replace(/\\f/g, "\f")
|
||||||
|
.replace(/\\"/g, '\"')
|
||||||
|
.replace(/\\'/g, "\'")
|
||||||
|
.replace(/\\&/g, "\&");
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadLog(asset) {
|
||||||
|
this.authFetch('/api/log?id=' + asset.id)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
let timestamp = res['timestamp'];
|
||||||
|
timestamp = timestamp.substr(0, timestamp.indexOf('.'));
|
||||||
|
let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
|
||||||
|
let logContent = this.unescapeLog(res['log']);
|
||||||
|
download(logContent, filename, 'text/plain');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadLogRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Download Log
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<a type="button" className="btn btn-primary"
|
||||||
|
disabled={!asset.has_log}
|
||||||
|
onClick={() => this.downloadLog(asset)}>Download</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
exploitsTimeline(asset) {
|
||||||
|
if (asset.exploits.length === 0) {
|
||||||
|
return (<div/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h4 style={{'marginTop': '2em'}}>
|
||||||
|
Exploit Timeline
|
||||||
|
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
||||||
|
</h4>
|
||||||
|
<ul className="timeline">
|
||||||
|
{asset.exploits.map(exploit =>
|
||||||
|
<li key={exploit.timestamp}>
|
||||||
|
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||||
|
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||||
|
<div>{exploit.origin}</div>
|
||||||
|
<div>{exploit.exploiter}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assetInfo(asset) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
{this.osRow(asset)}
|
||||||
|
{this.ipsRow(asset)}
|
||||||
|
{this.servicesRow(asset)}
|
||||||
|
{this.accessibleRow(asset)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{this.exploitsTimeline(asset)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
infectedAssetInfo(asset) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
{this.osRow(asset)}
|
||||||
|
{this.statusRow(asset)}
|
||||||
|
{this.ipsRow(asset)}
|
||||||
|
{this.servicesRow(asset)}
|
||||||
|
{this.accessibleRow(asset)}
|
||||||
|
{this.forceKillRow(asset)}
|
||||||
|
{this.downloadLogRow(asset)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{this.exploitsTimeline(asset)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
scanInfo(edge) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Operating System</th>
|
||||||
|
<td>{edge.os.type}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<td>{edge.ip_address}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Services</th>
|
||||||
|
<td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{
|
||||||
|
(edge.exploits.length === 0) ?
|
||||||
|
'' :
|
||||||
|
<div>
|
||||||
|
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
||||||
|
<ul className="timeline">
|
||||||
|
{edge.exploits.map(exploit =>
|
||||||
|
<li key={exploit.timestamp}>
|
||||||
|
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||||
|
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||||
|
<div>{exploit.origin}</div>
|
||||||
|
<div>{exploit.exploiter}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
islandEdgeInfo() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfoByProps() {
|
||||||
|
switch (this.props.type) {
|
||||||
|
case 'edge':
|
||||||
|
return this.scanInfo(this.props.item);
|
||||||
|
case 'node':
|
||||||
|
return this.props.item.group.includes('monkey', 'manual') ?
|
||||||
|
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
|
||||||
|
case 'island_edge':
|
||||||
|
return this.islandEdgeInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfMapPreviewPaneComponent;
|
|
@ -15,251 +15,25 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
osRow(asset) {
|
// This should be overridden
|
||||||
return (
|
getInfoByProps() {
|
||||||
<tr>
|
return null;
|
||||||
<th>Operating System</th>
|
|
||||||
<td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ipsRow(asset) {
|
getLabelByProps() {
|
||||||
return (
|
if (!this.props.item) {
|
||||||
<tr>
|
return '';
|
||||||
<th>IP Addresses</th>
|
} else if (this.props.item.hasOwnProperty('label')) {
|
||||||
<td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
|
return this.props.item['label'];
|
||||||
</tr>
|
} else if (this.props.item.hasOwnProperty('_label')) {
|
||||||
);
|
return this.props.item['_label'];
|
||||||
}
|
|
||||||
|
|
||||||
servicesRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>Services</th>
|
|
||||||
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
accessibleRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Accessible From
|
|
||||||
{this.generateToolTip('List of machine which can access this one using a network protocol')}
|
|
||||||
</th>
|
|
||||||
<td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
statusRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>Status</th>
|
|
||||||
<td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
forceKill(event, asset) {
|
|
||||||
let newConfig = asset.config;
|
|
||||||
newConfig['alive'] = !event.target.checked;
|
|
||||||
this.authFetch('/api/monkey/' + asset.guid,
|
|
||||||
{
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({config: newConfig})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
forceKillRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Force Kill
|
|
||||||
{this.generateToolTip('If this is on, monkey will die next time it communicates')}
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
|
|
||||||
onChange={(e) => this.forceKill(e, asset)}/>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unescapeLog(st) {
|
|
||||||
return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
|
|
||||||
.replace(/\\n/g, "\n")
|
|
||||||
.replace(/\\r/g, "\r")
|
|
||||||
.replace(/\\t/g, "\t")
|
|
||||||
.replace(/\\b/g, "\b")
|
|
||||||
.replace(/\\f/g, "\f")
|
|
||||||
.replace(/\\"/g, '\"')
|
|
||||||
.replace(/\\'/g, "\'")
|
|
||||||
.replace(/\\&/g, "\&");
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadLog(asset) {
|
|
||||||
this.authFetch('/api/log?id=' + asset.id)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
let timestamp = res['timestamp'];
|
|
||||||
timestamp = timestamp.substr(0, timestamp.indexOf('.'));
|
|
||||||
let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
|
|
||||||
let logContent = this.unescapeLog(res['log']);
|
|
||||||
download(logContent, filename, 'text/plain');
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadLogRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Download Log
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<a type="button" className="btn btn-primary"
|
|
||||||
disabled={!asset.has_log}
|
|
||||||
onClick={() => this.downloadLog(asset)}>Download</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
exploitsTimeline(asset) {
|
|
||||||
if (asset.exploits.length === 0) {
|
|
||||||
return (<div/>);
|
|
||||||
}
|
}
|
||||||
|
return '';
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h4 style={{'marginTop': '2em'}}>
|
|
||||||
Exploit Timeline
|
|
||||||
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
|
||||||
</h4>
|
|
||||||
<ul className="timeline">
|
|
||||||
{asset.exploits.map(exploit =>
|
|
||||||
<li key={exploit.timestamp}>
|
|
||||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
|
||||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
|
||||||
<div>{exploit.origin}</div>
|
|
||||||
<div>{exploit.exploiter}</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
assetInfo(asset) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-condensed">
|
|
||||||
<tbody>
|
|
||||||
{this.osRow(asset)}
|
|
||||||
{this.ipsRow(asset)}
|
|
||||||
{this.servicesRow(asset)}
|
|
||||||
{this.accessibleRow(asset)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{this.exploitsTimeline(asset)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
infectedAssetInfo(asset) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-condensed">
|
|
||||||
<tbody>
|
|
||||||
{this.osRow(asset)}
|
|
||||||
{this.statusRow(asset)}
|
|
||||||
{this.ipsRow(asset)}
|
|
||||||
{this.servicesRow(asset)}
|
|
||||||
{this.accessibleRow(asset)}
|
|
||||||
{this.forceKillRow(asset)}
|
|
||||||
{this.downloadLogRow(asset)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{this.exploitsTimeline(asset)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
scanInfo(edge) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-condensed">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Operating System</th>
|
|
||||||
<td>{edge.os.type}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>IP Address</th>
|
|
||||||
<td>{edge.ip_address}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Services</th>
|
|
||||||
<td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{
|
|
||||||
(edge.exploits.length === 0) ?
|
|
||||||
'' :
|
|
||||||
<div>
|
|
||||||
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
|
||||||
<ul className="timeline">
|
|
||||||
{edge.exploits.map(exploit =>
|
|
||||||
<li key={exploit.timestamp}>
|
|
||||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
|
||||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
|
||||||
<div>{exploit.origin}</div>
|
|
||||||
<div>{exploit.exploiter}</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
islandEdgeInfo() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let info = null;
|
let info = this.getInfoByProps();
|
||||||
switch (this.props.type) {
|
let label = this.getLabelByProps();
|
||||||
case 'edge':
|
|
||||||
info = this.scanInfo(this.props.item);
|
|
||||||
break;
|
|
||||||
case 'node':
|
|
||||||
info = this.props.item.group.includes('monkey', 'manual') ?
|
|
||||||
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
|
|
||||||
break;
|
|
||||||
case 'island_edge':
|
|
||||||
info = this.islandEdgeInfo();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let label = '';
|
|
||||||
if (!this.props.item) {
|
|
||||||
label = '';
|
|
||||||
} else if (this.props.item.hasOwnProperty('label')) {
|
|
||||||
label = this.props.item['label'];
|
|
||||||
} else if (this.props.item.hasOwnProperty('_label')) {
|
|
||||||
label = this.props.item['_label'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="preview-pane">
|
<div className="preview-pane">
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Icon} from 'react-fa';
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||||
|
import download from 'downloadjs'
|
||||||
|
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||||
|
|
||||||
|
class PthPreviewPaneComponent extends PreviewPaneComponent {
|
||||||
|
nodeInfo(asset) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Hostname</th>
|
||||||
|
<td>{asset.hostname}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>IP Addresses</th>
|
||||||
|
<td>{asset.ips.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>Compromised Users</th>
|
||||||
|
<td>{asset.users.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
edgeInfo(edge) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Compromised Users</th>
|
||||||
|
<td>{edge.users.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfoByProps() {
|
||||||
|
switch (this.props.type) {
|
||||||
|
case 'edge':
|
||||||
|
return this.edgeInfo(this.props.item);
|
||||||
|
case 'node':
|
||||||
|
return this.nodeInfo(this.props.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PthPreviewPaneComponent;
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import {Col} from 'react-bootstrap';
|
import {Col} from 'react-bootstrap';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import {Icon} from 'react-fa';
|
import {Icon} from 'react-fa';
|
||||||
import PreviewPane from 'components/map/preview-pane/PreviewPane';
|
import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
|
||||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
import {ModalContainer, ModalDialog} from 'react-modal-dialog';
|
import {ModalContainer, ModalDialog} from 'react-modal-dialog';
|
||||||
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
||||||
|
@ -190,7 +190,7 @@ class MapPageComponent extends AuthComponent {
|
||||||
</div>
|
</div>
|
||||||
: ''}
|
: ''}
|
||||||
|
|
||||||
<PreviewPane item={this.state.selected} type={this.state.selectedType}/>
|
<InfMapPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
||||||
</Col>
|
</Col>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,83 +1,57 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Col} from 'react-bootstrap';
|
|
||||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import Graph from 'react-graph-vis';
|
import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions';
|
||||||
|
import PreviewPane from "../map/preview-pane/PreviewPane";
|
||||||
const options = {
|
import {Col} from "react-bootstrap";
|
||||||
autoResize: true,
|
import {Link} from 'react-router-dom';
|
||||||
layout: {
|
import {Icon} from 'react-fa';
|
||||||
improvedLayout: false
|
import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane";
|
||||||
},
|
|
||||||
edges: {
|
|
||||||
width: 2,
|
|
||||||
smooth: {
|
|
||||||
type: 'curvedCW'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
physics: {
|
|
||||||
barnesHut: {
|
|
||||||
gravitationalConstant: -120000,
|
|
||||||
avoidOverlap: 0.5
|
|
||||||
},
|
|
||||||
minVelocity: 0.75
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class PassTheHashMapPageComponent extends AuthComponent {
|
class PassTheHashMapPageComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
graph: {nodes: [], edges: []},
|
graph: props.graph,
|
||||||
report: "",
|
|
||||||
selected: null,
|
selected: null,
|
||||||
selectedType: null,
|
selectedType: null
|
||||||
killPressed: false,
|
|
||||||
showKillDialog: false,
|
|
||||||
telemetry: [],
|
|
||||||
telemetryLastTimestamp: null
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
events = {
|
||||||
this.updateMapFromServer();
|
select: event => this.selectionChanged(event)
|
||||||
this.interval = setInterval(this.timedEvents, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
timedEvents = () => {
|
|
||||||
this.updateMapFromServer();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMapFromServer = () => {
|
selectionChanged(event) {
|
||||||
this.authFetch('/api/pthmap')
|
if (event.nodes.length === 1) {
|
||||||
.then(res => res.json())
|
let displayedNode = this.state.graph.nodes.find(
|
||||||
.then(res => {
|
function (node) {
|
||||||
this.setState({graph: res});
|
return node['id'] === event.nodes[0];
|
||||||
this.props.onStatusChange();
|
});
|
||||||
});
|
this.setState({selected: displayedNode, selectedType: 'node'})
|
||||||
this.authFetch('/api/pthreport')
|
}
|
||||||
.then(res => res.json())
|
else if (event.edges.length === 1) {
|
||||||
.then(res => {
|
let displayedEdge = this.state.graph.edges.find(
|
||||||
this.setState({report: res.html});
|
function (edge) {
|
||||||
this.props.onStatusChange();
|
return edge['id'] === event.edges[0];
|
||||||
});
|
});
|
||||||
};
|
this.setState({selected: displayedEdge, selectedType: 'edge'});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setState({selected: null, selectedType: null});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Col xs={12} lg={8}>
|
<Col xs={12}>
|
||||||
<h1 className="page-title">Pass The Hash Map</h1>
|
<div style={{height: '70vh'}}>
|
||||||
|
<ReactiveGraph graph={this.state.graph} options={optionsPth} events={this.events}/>
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={12}>
|
<Col xs={12}>
|
||||||
<div>
|
<PthPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
||||||
<Graph graph={this.state.graph} options={options} />
|
|
||||||
</div>
|
|
||||||
<div dangerouslySetInnerHTML={{__html: this.state.report}}></div>
|
|
||||||
</Col>
|
</Col>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,11 @@ import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||||
import StolenPasswords from 'components/report-components/StolenPasswords';
|
import StolenPasswords from 'components/report-components/StolenPasswords';
|
||||||
import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
|
import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
|
||||||
import {Line} from 'rc-progress';
|
import {Line} from 'rc-progress';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from 'components/AuthComponent';
|
||||||
|
import PassTheHashMapPageComponent from "./PassTheHashMapPage";
|
||||||
|
import SharedCreds from "components/report-components/SharedCreds";
|
||||||
|
import StrongUsers from "components/report-components/StrongUsers";
|
||||||
|
import SharedAdmins from "components/report-components/SharedAdmins";
|
||||||
|
|
||||||
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
||||||
let monkeyLogoImage = require('../../images/monkey-icon.svg');
|
let monkeyLogoImage = require('../../images/monkey-icon.svg');
|
||||||
|
@ -413,9 +417,70 @@ class ReportPageComponent extends AuthComponent {
|
||||||
<div style={{marginBottom: '20px'}}>
|
<div style={{marginBottom: '20px'}}>
|
||||||
<ScannedServers data={this.state.report.glance.scanned}/>
|
<ScannedServers data={this.state.report.glance.scanned}/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{this.generateReportPthMap()}
|
||||||
|
<div style={{marginBottom: '20px'}}>
|
||||||
<StolenPasswords data={this.state.report.glance.stolen_creds}/>
|
<StolenPasswords data={this.state.report.glance.stolen_creds}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{marginBottom: '20px'}}>
|
||||||
|
{ /* TODO: use dynamic data */}
|
||||||
|
<SharedCreds data = {[{cred_group: ['MyDomain\\user1', 'user2', 'user3']}, {cred_group: ['user2', 'user4']}]} />
|
||||||
|
</div>
|
||||||
|
<div style={{marginBottom: '20px'}}>
|
||||||
|
{ /* TODO: use dynamic data */}
|
||||||
|
<SharedAdmins data = {[
|
||||||
|
{
|
||||||
|
username: 'SharedLocalAdmin',
|
||||||
|
domain: 'MyDomain',
|
||||||
|
machines: ['hello : 1.2.3.4']
|
||||||
|
}
|
||||||
|
]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{ /* TODO: use dynamic data */}
|
||||||
|
<StrongUsers data = {[
|
||||||
|
{
|
||||||
|
username: 'SharedLocalAdmin',
|
||||||
|
domain: 'MyDomain',
|
||||||
|
machines: ['hello : 1.2.3.4'],
|
||||||
|
services: ['DC', 'DNS']
|
||||||
|
}
|
||||||
|
]} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateReportPthMap() {
|
||||||
|
// TODO: remove this and use updateMapFromSerever to get actual map data.
|
||||||
|
const my_map = {
|
||||||
|
nodes: [
|
||||||
|
{id: '1', label: 'MYPC-1', group: 'normal', users: ['MYPC-2\\user1', 'Dom\\user2'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa1'},
|
||||||
|
{id: 2, label: 'MYPC-2', group: 'critical', users: ['MYPC-2\\user1', 'Dom\\user2'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa2'},
|
||||||
|
{id: 3, label: 'MYPC-3', group: 'normal', users: ['MYPC-3\\user1', 'Dom\\user3'], ips: ['192.168.0.2'], services: ["DC", "SQL"], 'hostname': 'aaa3'},
|
||||||
|
{id: 4, label: 'MYPC-4', group: 'critical', users: ['MYPC-4\\user1', 'Dom\\user4'], ips: ['192.168.0.3', '192.168.0.4'], services: ["DC", "SQL"], 'hostname': 'aaa4'},
|
||||||
|
{id: 5, label: 'MYPC-5', group: 'normal', users: ['MYPC-5\\user1', 'Dom\\user5'], ips: ['192.168.0.1'], services: [], 'hostname': 'aaa5'},
|
||||||
|
{id: 6, label: 'MYPC-6', group: 'critical', users: ['MYPC-6\\user1', 'Dom\\user6'], ips: ['192.168.0.1'], services: ["DC"], 'hostname': 'aaa6'},
|
||||||
|
{id: 7, label: 'MYPC-7', group: 'critical', users: ['MYPC-7\\user1', 'Dom\\user7'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa7'}
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{id: 10, from: '1', to: 2, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla0'},
|
||||||
|
{id: 11, from: '1', to: 3, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla1'},
|
||||||
|
{id: 12, from: '1', to: 4, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla2'},
|
||||||
|
{id: 13, from: 5, to: 6, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla3'},
|
||||||
|
{id: 14, from: 6, to: 7, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla4'},
|
||||||
|
{id: 15, from: 6, to: 5, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla5'},
|
||||||
|
]
|
||||||
|
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div id="pth">
|
||||||
|
<h3>
|
||||||
|
Credential Map
|
||||||
|
</h3>
|
||||||
|
<div style={{position: 'relative', height: '100vh'}}>
|
||||||
|
<PassTheHashMapPageComponent graph={my_map} />
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@ import React from 'react';
|
||||||
import ReactTable from 'react-table'
|
import ReactTable from 'react-table'
|
||||||
|
|
||||||
let renderArray = function(val) {
|
let renderArray = function(val) {
|
||||||
if (val.length === 0) {
|
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return val.reduce((total, new_str) => total + ', ' + new_str);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
|
|
@ -2,10 +2,7 @@ import React from 'react';
|
||||||
import ReactTable from 'react-table'
|
import ReactTable from 'react-table'
|
||||||
|
|
||||||
let renderArray = function(val) {
|
let renderArray = function(val) {
|
||||||
if (val.length === 0) {
|
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return val.reduce((total, new_str) => total + ', ' + new_str);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactTable from 'react-table'
|
||||||
|
|
||||||
|
let renderArray = function(val) {
|
||||||
|
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
Header: 'Shared Admins Between Machines',
|
||||||
|
columns: [
|
||||||
|
{ Header: 'Username', accessor: 'username'},
|
||||||
|
{ Header: 'Domain', accessor: 'domain'},
|
||||||
|
{ Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
class SharedAdminsComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
|
||||||
|
let showPagination = this.props.data.length > pageSize;
|
||||||
|
return (
|
||||||
|
<div className="data-table-container">
|
||||||
|
<ReactTable
|
||||||
|
columns={columns}
|
||||||
|
data={this.props.data}
|
||||||
|
showPagination={showPagination}
|
||||||
|
defaultPageSize={defaultPageSize}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SharedAdminsComponent;
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactTable from 'react-table'
|
||||||
|
|
||||||
|
let renderArray = function(val) {
|
||||||
|
console.log(val);
|
||||||
|
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
Header: 'Shared Credentials',
|
||||||
|
columns: [
|
||||||
|
{Header: 'Credential Group', id: 'cred_group', accessor: x => renderArray(x.cred_group) }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
class SharedCredsComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
|
||||||
|
let showPagination = this.props.data.length > pageSize;
|
||||||
|
return (
|
||||||
|
<div className="data-table-container">
|
||||||
|
<ReactTable
|
||||||
|
columns={columns}
|
||||||
|
data={this.props.data}
|
||||||
|
showPagination={showPagination}
|
||||||
|
defaultPageSize={defaultPageSize}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SharedCredsComponent;
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactTable from 'react-table'
|
||||||
|
|
||||||
|
let renderArray = function(val) {
|
||||||
|
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
Header: 'Powerful Users',
|
||||||
|
columns: [
|
||||||
|
{ Header: 'Username', accessor: 'username'},
|
||||||
|
{ Header: 'Domain', accessor: 'domain'},
|
||||||
|
{ Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)},
|
||||||
|
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services)}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
class StrongUsersComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
|
||||||
|
let showPagination = this.props.data.length > pageSize;
|
||||||
|
return (
|
||||||
|
<div className="data-table-container">
|
||||||
|
<ReactTable
|
||||||
|
columns={columns}
|
||||||
|
data={this.props.data}
|
||||||
|
showPagination={showPagination}
|
||||||
|
defaultPageSize={defaultPageSize}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StrongUsersComponent;
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
Loading…
Reference in New Issue