From 800e337f6f0e4fc3f9d25c5dff417ae3dc736a33 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz <itay.mizeretz@guardicore.com> Date: Thu, 19 Jul 2018 18:35:37 +0300 Subject: [PATCH] Add credential map to report. currently uses fake static data --- .../cc/ui/src/components/map/MapOptions.js | 35 ++- .../map/preview-pane/InfMapPreviewPane.js | 247 +++++++++++++++++ .../map/preview-pane/PreviewPane.js | 252 +----------------- .../map/preview-pane/PthPreviewPane.js | 63 +++++ .../cc/ui/src/components/pages/MapPage.js | 4 +- .../components/pages/PassTheHashMapPage.js | 94 +++---- .../cc/ui/src/components/pages/ReportPage.js | 46 ++++ .../cc/ui/src/images/nodes/pth/critical.png | Bin 0 -> 20067 bytes .../cc/ui/src/images/nodes/pth/normal.png | Bin 0 -> 19466 bytes 9 files changed, 436 insertions(+), 305 deletions(-) create mode 100644 monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js create mode 100644 monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js create mode 100644 monkey_island/cc/ui/src/images/nodes/pth/critical.png create mode 100644 monkey_island/cc/ui/src/images/nodes/pth/normal.png diff --git a/monkey_island/cc/ui/src/components/map/MapOptions.js b/monkey_island/cc/ui/src/components/map/MapOptions.js index 701adcf29..f6946ea31 100644 --- a/monkey_island/cc/ui/src/components/map/MapOptions.js +++ b/monkey_island/cc/ui/src/components/map/MapOptions.js @@ -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', 'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux', 'monkey_linux_running', 'monkey_windows', 'monkey_windows_running']; @@ -16,7 +16,22 @@ let getGroupsOptions = () => { 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, layout: { improvedLayout: false @@ -33,10 +48,22 @@ export const options = { avoidOverlap: 0.5 }, 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) { switch (group) { case 'exploited': diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js new file mode 100644 index 000000000..e06043c20 --- /dev/null +++ b/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js @@ -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; diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js index 64b228332..c38907eea 100644 --- a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js +++ b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js @@ -15,251 +15,25 @@ class PreviewPaneComponent extends AuthComponent { ); } - osRow(asset) { - return ( - <tr> - <th>Operating System</th> - <td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td> - </tr> - ); + // This should be overridden + getInfoByProps() { + return null; } - 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/>); + getLabelByProps() { + if (!this.props.item) { + return ''; + } else if (this.props.item.hasOwnProperty('label')) { + return this.props.item['label']; + } else if (this.props.item.hasOwnProperty('_label')) { + return this.props.item['_label']; } - - 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> - ); + return ''; } render() { - let info = null; - switch (this.props.type) { - 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']; - } + let info = this.getInfoByProps(); + let label = this.getLabelByProps(); return ( <div className="preview-pane"> diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js new file mode 100644 index 000000000..f9a5ae1bb --- /dev/null +++ b/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js @@ -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; diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index 4a54aeb8c..00c0cba3c 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -2,7 +2,7 @@ import React from 'react'; import {Col} from 'react-bootstrap'; import {Link} from 'react-router-dom'; 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 {ModalContainer, ModalDialog} from 'react-modal-dialog'; import {options, edgeGroupToColor} from 'components/map/MapOptions'; @@ -190,7 +190,7 @@ class MapPageComponent extends AuthComponent { </div> : ''} - <PreviewPane item={this.state.selected} type={this.state.selectedType}/> + <InfMapPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/> </Col> </div> ); diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js index 2ac43f094..8c7ded49b 100644 --- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js +++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -1,83 +1,57 @@ import React from 'react'; -import {Col} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import AuthComponent from '../AuthComponent'; -import Graph from 'react-graph-vis'; - -const options = { - autoResize: true, - layout: { - improvedLayout: false - }, - edges: { - width: 2, - smooth: { - type: 'curvedCW' - } - }, - physics: { - barnesHut: { - gravitationalConstant: -120000, - avoidOverlap: 0.5 - }, - minVelocity: 0.75 - } -}; +import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions'; +import PreviewPane from "../map/preview-pane/PreviewPane"; +import {Col} from "react-bootstrap"; +import {Link} from 'react-router-dom'; +import {Icon} from 'react-fa'; +import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane"; class PassTheHashMapPageComponent extends AuthComponent { constructor(props) { super(props); this.state = { - graph: {nodes: [], edges: []}, - report: "", + graph: props.graph, selected: null, - selectedType: null, - killPressed: false, - showKillDialog: false, - telemetry: [], - telemetryLastTimestamp: null + selectedType: null }; } - componentDidMount() { - this.updateMapFromServer(); - this.interval = setInterval(this.timedEvents, 1000); - } - - componentWillUnmount() { - clearInterval(this.interval); - } - - timedEvents = () => { - this.updateMapFromServer(); + events = { + select: event => this.selectionChanged(event) }; - updateMapFromServer = () => { - this.authFetch('/api/pthmap') - .then(res => res.json()) - .then(res => { - this.setState({graph: res}); - this.props.onStatusChange(); - }); - this.authFetch('/api/pthreport') - .then(res => res.json()) - .then(res => { - this.setState({report: res.html}); - this.props.onStatusChange(); - }); - }; + selectionChanged(event) { + if (event.nodes.length === 1) { + let displayedNode = this.state.graph.nodes.find( + function (node) { + return node['id'] === event.nodes[0]; + }); + this.setState({selected: displayedNode, selectedType: 'node'}) + } + else if (event.edges.length === 1) { + let displayedEdge = this.state.graph.edges.find( + function (edge) { + return edge['id'] === event.edges[0]; + }); + this.setState({selected: displayedEdge, selectedType: 'edge'}); + } + else { + this.setState({selected: null, selectedType: null}); + } + } render() { return ( <div> - <Col xs={12} lg={8}> - <h1 className="page-title">Pass The Hash Map</h1> + <Col xs={12}> + <div style={{height: '70vh'}}> + <ReactiveGraph graph={this.state.graph} options={optionsPth} events={this.events}/> + </div> </Col> <Col xs={12}> - <div> - <Graph graph={this.state.graph} options={options} /> - </div> - <div dangerouslySetInnerHTML={{__html: this.state.report}}></div> + <PthPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/> </Col> </div> ); diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index bec4f3625..adb024c72 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -8,6 +8,7 @@ import StolenPasswords from 'components/report-components/StolenPasswords'; import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; import {Line} from 'rc-progress'; import AuthComponent from '../AuthComponent'; +import PassTheHashMapPageComponent from "./PassTheHashMapPage"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); let monkeyLogoImage = require('../../images/monkey-icon.svg'); @@ -129,6 +130,7 @@ class ReportPageComponent extends AuthComponent { {this.generateReportFindingsSection()} {this.generateReportRecommendationsSection()} {this.generateReportGlanceSection()} + {this.generateReportPthSection()} {this.generateReportFooter()} </div> <div className="text-center no-print" style={{marginTop: '20px'}}> @@ -420,6 +422,50 @@ class ReportPageComponent extends AuthComponent { ); } + generateReportPthSection() { + // 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> + Pass The Hash !!!!!TODO: change this!!!!!!!! + </h3> + <div style={{position: 'relative', height: '100vh'}}> + <PassTheHashMapPageComponent graph={my_map} /> + </div> + <div> + TODO: put relevant tables and stuff here... + </div> + <div> + TODO: put relevant tables and stuff here... + </div> + <div> + TODO: put relevant tables and stuff here... + </div> + <br /> + </div> + ); + } + generateReportFooter() { return ( <div id="footer" className="text-center" style={{marginTop: '20px'}}> diff --git a/monkey_island/cc/ui/src/images/nodes/pth/critical.png b/monkey_island/cc/ui/src/images/nodes/pth/critical.png new file mode 100644 index 0000000000000000000000000000000000000000..0348a7f5d312b0400da47f60a5f2d81682bd19a6 GIT binary patch literal 20067 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 CHH7{E literal 0 HcmV?d00001 diff --git a/monkey_island/cc/ui/src/images/nodes/pth/normal.png b/monkey_island/cc/ui/src/images/nodes/pth/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..3b1e9b6387377d9fc2e39d7bede361041f3b6104 GIT binary patch literal 19466 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 literal 0 HcmV?d00001