Refactored fingerprint scanners to add port on init

This commit is contained in:
VakarisZ 2019-05-06 14:57:17 +03:00
parent 44077e6bfe
commit 7f5c07c1fd
23 changed files with 224 additions and 196 deletions

View File

@ -0,0 +1,10 @@
# abstract, static method decorator
class abstractstatic(staticmethod):
__slots__ = ()
def __init__(self, function):
super(abstractstatic, self).__init__(function)
function.__isabstractmethod__ = True
__isabstractmethod__ = True

View File

@ -89,7 +89,10 @@ class SambaCryExploiter(HostExploiter):
LOG.info(
"Shares triggered successfully on host %s: %s" % (
self.host.ip_addr, str(successfully_triggered_shares)))
self.add_vuln_port(str(writable_shares_creds_dict))
# TODO: add vulnerable url
#for share, fullpath in successfully_triggered_shares:
# self.add_vuln_url("smb://<username>@<hostname/ip>:<port>/<share_name>" % False,
# self.host.ip_addr, False, share)
return True
else:
LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr)

View File

@ -18,19 +18,10 @@ class HostFinger(object):
def _SCANNED_SERVICE(self):
pass
def init_service(self, services, service_key):
def init_service(self, services, service_key, port):
services[service_key] = {}
services[service_key]['display_name'] = self._SCANNED_SERVICE
def add_found_port(self, services, port, key=None):
if key:
services[key]['port'] = port
else:
for service in services:
if services[service]['display_name'] == self._SCANNED_SERVICE:
service[service]['port'] = port
return
raise KeyError
services[service_key]['port'] = port
@abstractmethod
def get_host_fingerprint(self, host):

View File

@ -36,11 +36,10 @@ class ElasticFinger(HostFinger):
url = 'http://%s:%s/' % (host.ip_addr, ES_PORT)
with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req:
data = json.loads(req.text)
self.init_service(host.services, ES_SERVICE)
self.init_service(host.services, ES_SERVICE, ES_PORT)
host.services[ES_SERVICE]['cluster_name'] = data['cluster_name']
host.services[ES_SERVICE]['name'] = data['name']
host.services[ES_SERVICE]['version'] = data['version']['number']
self.add_found_port(host.services, ES_PORT)
return True
except Timeout:
LOG.debug("Got timeout while trying to read header information")

View File

@ -37,9 +37,8 @@ class HTTPFinger(HostFinger):
with closing(head(url, verify=False, timeout=1)) as req:
server = req.headers.get('Server')
ssl = True if 'https://' in url else False
self.init_service(host.services, ('tcp-' + port[1]))
self.init_service(host.services, ('tcp-' + port[1]), port[0])
host.services['tcp-' + port[1]]['name'] = 'http'
self.add_found_port(host.services, port[0], ('tcp-' + port[1]))
host.services['tcp-' + port[1]]['data'] = (server,ssl)
LOG.info("Port %d is open on host %s " % (port[0], host))
break # https will be the same on the same port

View File

@ -63,7 +63,7 @@ class MSSQLFinger(HostFinger):
sock.close()
return False
self.init_service(host.services, self._SCANNED_SERVICE)
self.init_service(host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT)
# Loop through the server data
instances_list = data[3:].decode().split(';;')
@ -76,7 +76,6 @@ class MSSQLFinger(HostFinger):
# Each instance's info is nested under its own name, if there are multiple instances
# each will appear under its own name
host.services[self._SCANNED_SERVICE][instance_info[1]][instance_info[i - 1]] = instance_info[i]
self.add_found_port(host.services, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT)
# Close the socket
sock.close()

View File

@ -51,14 +51,13 @@ class MySQLFinger(HostFinger):
version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing
version = version[0]
self.init_service(host.services, SQL_SERVICE)
self.init_service(host.services, SQL_SERVICE, MYSQL_PORT)
host.services[SQL_SERVICE]['version'] = version
version = version.split('-')[0].split('.')
host.services[SQL_SERVICE]['major_version'] = version[0]
host.services[SQL_SERVICE]['minor_version'] = version[1]
host.services[SQL_SERVICE]['build_version'] = version[2]
thread_id, curpos = struct_unpack_tracker(data, curpos, "<I") # ignore thread id
self.add_found_port(host.services, MYSQL_PORT)
# protocol parsing taken from
# https://nmap.org/nsedoc/scripts/mysql-info.html
if protocol == 10:

View File

@ -114,7 +114,7 @@ class SMBFinger(HostFinger):
s.settimeout(0.7)
s.connect((host.ip_addr, SMB_PORT))
self.init_service(host.services, SMB_SERVICE)
self.init_service(host.services, SMB_SERVICE, SMB_PORT)
h = SMBHeader(cmd="\x72", flag1="\x18", flag2="\x53\xc8")
n = SMBNego(data=SMBNegoFingerData())
@ -152,7 +152,6 @@ class SMBFinger(HostFinger):
host.os['version'] = os_version
else:
host.services[SMB_SERVICE]['os-version'] = os_version
self.add_found_port(host.services, SMB_PORT)
return True
except Exception as exc:
LOG.debug("Error getting smb fingerprint: %s", exc)

View File

@ -46,13 +46,12 @@ class SSHFinger(HostFinger):
is_open, banner = check_tcp_port(host.ip_addr, SSH_PORT, TIMEOUT, True)
if is_open:
self.init_service(host.services, SSH_SERVICE_DEFAULT)
self.init_service(host.services, SSH_SERVICE_DEFAULT, SSH_PORT)
if banner:
host.services[SSH_SERVICE_DEFAULT]['banner'] = banner
if self._banner_regex.search(banner):
self._banner_match(SSH_SERVICE_DEFAULT, host, banner)
self.add_found_port(host.services, SSH_PORT)
return True
return False

View File

@ -4,8 +4,6 @@ from random import shuffle
import infection_monkey.config
from infection_monkey.network import HostScanner, HostFinger
from infection_monkey.network.tools import check_tcp_ports, tcp_port_to_service
from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem
from common.utils.attack_utils import ScanStatus
__author__ = 'itamar'
@ -38,8 +36,7 @@ class TcpScanner(HostScanner, HostFinger):
self._config.tcp_scan_get_banner)
for target_port, banner in izip_longest(ports, banners, fillvalue=None):
service = tcp_port_to_service(target_port)
self.init_service(host.services, service)
self.add_found_port(host.services, target_port, key=service)
self.init_service(host.services, service, target_port)
if banner:
host.services[service]['banner'] = banner
if only_one_port:

View File

@ -56,12 +56,10 @@ class Root(flask_restful.Resource):
infection_done = NodeService.is_monkey_finished_running()
if not infection_done:
report_done = False
attack_report_done = False
else:
if is_any_exists:
ReportService.get_report()
AttackReportService.get_latest_report()
report_done = ReportService.is_report_generated()
attack_report_done = AttackReportService.is_report_generated()
return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done,
report_done=report_done, attack_report_done=attack_report_done)
report_done=report_done)

View File

@ -23,14 +23,14 @@ class AttackConfig(object):
"""
Gets technique by id
:param technique_id: E.g. T1210
:return: Technique object or false if technique is not found
:return: Technique object or None if technique is not found
"""
attack_config = get_config()
attack_config = AttackConfig.get_config()
for key, attack_type in attack_config['properties'].items():
for key, technique in attack_type['properties'].items():
if key == technique_id:
return technique
return False
return None
@staticmethod
def get_config_schema():

View File

@ -1,7 +1,7 @@
import logging
from monkey_island.cc.services.attack.technique_reports import T1210, T1197
from monkey_island.cc.services.attack.attack_telem import get_latest_telem
from monkey_island.cc.services.attack.attack_config import get_technique_values
from monkey_island.cc.services.attack.attack_telem import AttackTelemService
from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
@ -9,8 +9,8 @@ __author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
TECHNIQUES = {'T1210': T1210,
'T1197': T1197}
TECHNIQUES = {'T1210': T1210.T1210,
'T1197': T1197.T1197}
REPORT_NAME = 'new_report'
@ -25,8 +25,8 @@ class AttackReportService:
Generates new report based on telemetries, replaces old report in db with new one.
:return: Report object
"""
report = {'techniques': {}, 'meta': get_latest_telem(), 'name': REPORT_NAME}
for tech_id, value in get_technique_values().items():
report = {'techniques': {}, 'meta': AttackTelemService.get_latest_telem(), 'name': REPORT_NAME}
for tech_id, value in AttackConfig.get_technique_values().items():
if value:
try:
report['techniques'].update({tech_id: TECHNIQUES[tech_id].get_report_data()})
@ -43,7 +43,7 @@ class AttackReportService:
:return: report dict.
"""
if AttackReportService.is_report_generated():
telem_time = get_latest_telem()
telem_time = AttackTelemService.get_latest_telem()
latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME})
if telem_time and latest_report['meta'] and telem_time['time'] == latest_report['meta']['time']:
return latest_report

View File

@ -1,25 +1,25 @@
from monkey_island.cc.services.attack.technique_reports.technique_service import *
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
__author__ = "VakarisZ"
TECHNIQUE = "T1197"
MESSAGES = {
'unscanned': "Monkey didn't try to use any bits jobs.",
'scanned': "Monkey tried to use bits jobs but failed.",
'used': "Monkey successfully used bits jobs at least once in the network."
}
class T1197(AttackTechnique):
tech_id = "T1197"
unscanned_msg = "Monkey didn't try to use any bits jobs."
scanned_msg = "Monkey tried to use bits jobs but failed."
used_msg = "Monkey successfully used bits jobs at least once in the network."
def get_report_data():
data = get_tech_base_data(TECHNIQUE, MESSAGES)
bits_results = mongo.db.attack_results.aggregate([{'$match': {'technique': TECHNIQUE}},
{'$group': {'_id': {'ip_addr': '$machine.ip_addr', 'usage': '$usage'},
'ip_addr': {'$first': '$machine.ip_addr'},
'domain_name': {'$first': '$machine.domain_name'},
'usage': {'$first': '$usage'},
'time': {'$first': '$time'}}
}])
bits_results = list(bits_results)
data.update({'bits_jobs': bits_results})
return data
@staticmethod
def get_report_data():
data = T1197.get_tech_base_data(T1197)
bits_results = mongo.db.attack_results.aggregate([{'$match': {'technique': T1197.tech_id}},
{'$group': {'_id': {'ip_addr': '$machine.ip_addr', 'usage': '$usage'},
'ip_addr': {'$first': '$machine.ip_addr'},
'domain_name': {'$first': '$machine.domain_name'},
'usage': {'$first': '$usage'},
'time': {'$first': '$time'}}
}])
bits_results = list(bits_results)
data.update({'bits_jobs': bits_results})
return data

View File

@ -1,5 +1,6 @@
from monkey_island.cc.services.attack.technique_reports.technique_service import *
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
@ -11,35 +12,43 @@ MESSAGES = {
}
def get_report_data():
data = {'title': technique_title(TECHNIQUE)}
scanned_services = get_scanned_services()
exploited_services = get_exploited_services()
if exploited_services:
data.update({'status': ScanStatus.USED.name, 'message': MESSAGES['used']})
elif scanned_services:
data.update({'status': ScanStatus.SCANNED.name, 'message': MESSAGES['scanned']})
else:
data.update({'status': ScanStatus.UNSCANNED.name, 'message': MESSAGES['unscanned']})
data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
return data
class T1210(AttackTechnique):
tech_id = "T1210"
unscanned_msg = "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?"
scanned_msg = "Monkey scanned for remote services on the network, but couldn't exploit any of them."
used_msg = "Monkey scanned for remote services and exploited some on the network."
def get_scanned_services():
results = mongo.db.telemetry.aggregate([{'$match': {'telem_type': 'scan'}},
{'$sort': {'data.service_count': -1}},
{'$group': {
'_id': {'ip_addr': '$data.machine.ip_addr'},
'machine': {'$first': '$data.machine'},
'time': {'$first': '$timestamp'}}}])
return list(results)
@staticmethod
def get_report_data():
data = {'title': T1210.technique_title(TECHNIQUE)}
scanned_services = T1210.get_scanned_services()
exploited_services = T1210.get_exploited_services()
if exploited_services:
data.update({'status': ScanStatus.USED.name, 'message': T1210.used_msg})
elif scanned_services:
data.update({'status': ScanStatus.SCANNED.name, 'message': T1210.scanned_msg})
else:
data.update({'status': ScanStatus.UNSCANNED.name, 'message': T1210.unscanned_msg})
data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
return data
@staticmethod
def get_scanned_services():
results = mongo.db.telemetry.aggregate([{'$match': {'telem_type': 'scan'}},
{'$sort': {'data.service_count': -1}},
{'$group': {
'_id': {'ip_addr': '$data.machine.ip_addr'},
'machine': {'$first': '$data.machine'},
'time': {'$first': '$timestamp'}}}])
return list(results)
def get_exploited_services():
results = mongo.db.telemetry.aggregate([{'$match': {'telem_type': 'exploit', 'data.result': True}},
{'$group': {
'_id': {'ip_addr': '$data.machine.ip_addr'},
'service': {'$first': '$data.info'},
'machine': {'$first': '$data.machine'},
'time': {'$first': '$timestamp'}}}])
return list(results)
@staticmethod
def get_exploited_services():
results = mongo.db.telemetry.aggregate([{'$match': {'telem_type': 'exploit', 'data.result': True}},
{'$group': {
'_id': {'ip_addr': '$data.machine.ip_addr'},
'service': {'$first': '$data.info'},
'machine': {'$first': '$data.machine'},
'time': {'$first': '$timestamp'}}}])
return list(results)

View File

@ -1 +1,63 @@
__author__ = 'VakarisZ'
import abc
from monkey_island.cc.database import mongo
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.attack_config import AttackConfig
from common.utils.code_utils import abstractstatic
class AttackTechnique(object):
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def unscanned_msg(self):
pass
@abc.abstractproperty
def scanned_msg(self):
pass
@abc.abstractproperty
def used_msg(self):
pass
@abc.abstractproperty
def tech_id(self):
pass
@staticmethod
@abstractstatic
def get_report_data():
pass
@staticmethod
def technique_status(technique):
"""
Gets status of certain attack technique. If
:param technique:
:return:
"""
if mongo.db.attack_results.find_one({'status': ScanStatus.USED.value, 'technique': technique}):
return ScanStatus.USED
elif mongo.db.attack_results.find_one({'status': ScanStatus.SCANNED.value, 'technique': technique}):
return ScanStatus.SCANNED
else:
return ScanStatus.UNSCANNED
@staticmethod
def technique_title(technique):
return AttackConfig.get_technique(technique)['title']
@staticmethod
def get_tech_base_data(technique):
data = {}
status = AttackTechnique.technique_status(technique.tech_id)
title = AttackTechnique.technique_title(technique.tech_id)
data.update({'status': status.name, 'title': title})
if status == ScanStatus.UNSCANNED:
data.update({'message': technique.unscanned_msg})
elif status == ScanStatus.SCANNED:
data.update({'message': technique.scanned_msg})
else:
data.update({'message': technique.used_msg})
return data

View File

@ -1,37 +0,0 @@
from monkey_island.cc.database import mongo
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.attack_config import get_technique
__author__ = "VakarisZ"
def technique_status(technique):
"""
Gets status of certain attack technique. If
:param technique:
:return:
"""
if mongo.db.attack_results.find_one({'status': ScanStatus.USED.value, 'technique': technique}):
return ScanStatus.USED
elif mongo.db.attack_results.find_one({'status': ScanStatus.SCANNED.value, 'technique': technique}):
return ScanStatus.SCANNED
else:
return ScanStatus.UNSCANNED
def technique_title(technique):
return get_technique(technique)['title']
def get_tech_base_data(technique, messages):
data = {}
status = technique_status(technique)
title = technique_title(technique)
data.update({'status': status.name, 'title': title})
if status == ScanStatus.UNSCANNED:
data.update({'message': messages['unscanned']})
elif status == ScanStatus.SCANNED:
data.update({'message': messages['scanned']})
else:
data.update({'message': messages['used']})
return data

View File

@ -14,7 +14,6 @@ import ReportPage from 'components/pages/ReportPage';
import LicensePage from 'components/pages/LicensePage';
import AuthComponent from 'components/AuthComponent';
import LoginPageComponent from 'components/pages/LoginPage';
import AttackReportPage from 'components/pages/AttackReportPage';
import 'normalize.css/normalize.css';
import 'react-data-components/css/table-twbs.css';
@ -85,7 +84,6 @@ class AppComponent extends AuthComponent {
run_monkey: false,
infection_done: false,
report_done: false,
attack_report_done:false,
isLoggedIn: undefined
}
};
@ -153,15 +151,6 @@ class AppComponent extends AuthComponent {
: ''}
</NavLink>
</li>
<li>
<NavLink to="/attack_report">
<span className="number">5.</span>
ATT&CK report
{this.state.completedSteps.attack_report_done ?
<Icon name="check" className="pull-right checkmark text-success"/>
: ''}
</NavLink>
</li>
<li>
<NavLink to="/start-over">
<span className="number"><i className="fa fa-undo" style={{'marginLeft': '-1px'}}/></span>
@ -196,7 +185,6 @@ class AppComponent extends AuthComponent {
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/report', <ReportPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/attack_report', <AttackReportPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
</Col>
</Row>

View File

@ -1,46 +0,0 @@
import React from 'react';
import '../../styles/Collapse.scss'
import ReactTable from "react-table";
let renderMachine = function (val) {
return (
<span>{val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}</span>
)
};
const columns = [
{
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachine(x), style: { 'whiteSpace': 'unset' }, width: 200},
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170},
{Header: 'Usage', id: 'usage', accessor: x => x.usage, style: { 'whiteSpace': 'unset' }}
]
}
];
class T1210 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="data-table-container">
<div>
<div>{this.props.data.message}</div>
{this.props.data.bits_jobs.length > 0 ? <div>BITS jobs were used in these machines: </div> : ''}
</div>
<br/>
<ReactTable
columns={columns}
data={this.props.data.bits_jobs}
showPagination={false}
defaultPageSize={this.props.data.bits_jobs.length}
/>
</div>
);
}
}
export default T1210;

View File

@ -0,0 +1,49 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
class T1210 extends React.Component {
constructor(props) {
super(props);
this.columns = [ {Header: 'Machine',
id: 'machine', accessor: x => T1210.renderMachine(x),
style: { 'whiteSpace': 'unset' },
width: 200},
{Header: 'Time',
id: 'time', accessor: x => x.time,
style: { 'whiteSpace': 'unset' },
width: 170},
{Header: 'Usage',
id: 'usage', accessor: x => x.usage,
style: { 'whiteSpace': 'unset' }}
]
}
static renderMachine = (val) => {
return (
<span>{val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}</span>
)
};
render() {
return (
<div className="data-table-container">
<div>
<div>{this.props.data.message}</div>
{this.props.data.bits_jobs.length > 0 ? <div>BITS jobs were used in these machines: </div> : ''}
</div>
<br/>
<ReactTable
columns={this.columns}
data={this.props.data.bits_jobs}
showPagination={false}
defaultPageSize={this.props.data.bits_jobs.length}
/>
</div>
);
}
}
export default T1210;

View File

@ -1,5 +1,5 @@
import React from 'react';
import '../../styles/Collapse.scss'
import '../../../styles/Collapse.scss'
import {Link} from "react-router-dom";
import ReactTable from "react-table";

View File

@ -11,6 +11,7 @@ import {Line} from 'rc-progress';
import AuthComponent from '../AuthComponent';
import PassTheHashMapPageComponent from "./PassTheHashMapPage";
import StrongUsers from "components/report-components/StrongUsers";
import AttackReport from "components/report-components/AttackReport";
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
let monkeyLogoImage = require('../../images/monkey-icon.svg');
@ -140,6 +141,7 @@ class ReportPageComponent extends AuthComponent {
{this.generateReportFindingsSection()}
{this.generateReportRecommendationsSection()}
{this.generateReportGlanceSection()}
{this.generateAttackSection()}
{this.generateReportFooter()}
</div>
<div className="text-center no-print" style={{marginTop: '20px'}}>
@ -503,6 +505,21 @@ class ReportPageComponent extends AuthComponent {
);
}
generateAttackSection() {
return (<div id="attack">
<h3>
ATT&CK report
</h3>
<p>
This report shows information about ATT&CK techniques used by Infection Monkey.
</p>
<div>
<AttackReport/>
</div>
<br />
</div>)
}
generateReportFooter() {
return (
<div id="footer" className="text-center" style={{marginTop: '20px'}}>

View File

@ -4,8 +4,8 @@ import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {edgeGroupToColor, options} from 'components/map/MapOptions';
import AuthComponent from '../AuthComponent';
import Collapse from '@kunukn/react-collapse';
import T1210 from '../attack/T1210';
import T1197 from '../attack/T1197';
import T1210 from '../attack/techniques/T1210';
import T1197 from '../attack/techniques/T1197';
import '../../styles/Collapse.scss'
const tech_components = {
@ -146,14 +146,7 @@ class AttackReportPageComponent extends AuthComponent {
} else {
content = this.generateReportContent();
}
return (
<Col xs={12} lg={8}>
<h1 className="page-title no-print">5. ATT&CK Report</h1>
<div style={{'fontSize': '1.2em'}}>
{content}
</div>
</Col>
);
return ( <div> {content} </div> );
}
}