Extracted exploiters from reports in front end and back end

This commit is contained in:
VakarisZ 2021-02-23 08:21:49 +02:00
parent 8437f8d838
commit 8efc3e654c
35 changed files with 962 additions and 1055 deletions

View File

@ -0,0 +1,32 @@
from dataclasses import dataclass
from enum import Enum
from typing import Type
from monkey_island.cc.services.reporting.issue_processing.exploit_processing import CredExploitProcessor
from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor
from monkey_island.cc.services.reporting.issue_processing.exploit_processing import \
ShellShockExploitProcessor
@dataclass
class ExploiterDescriptor:
# Must match with class names of exploiters in Infection Monkey code
class_name: str
display_name: str
processor: Type[ExploitProcessor]
class ExploiterDescriptorEnum(Enum):
SMB = ExploiterDescriptor('SmbExploiter', 'SMB Exploiter', CredExploitProcessor)
WMI = ExploiterDescriptor('WmiExploiter', 'WMI Exploiter', CredExploitProcessor)
SSH = ExploiterDescriptor('SSHExploiter', 'SSH Exploiter', CredExploitProcessor)
SAMBACRY = ExploiterDescriptor('SambaCryExploiter', 'SambaCry Exploiter', CredExploitProcessor)
ELASTIC = ExploiterDescriptor('ElasticGroovyExploiter', 'Elastic Groovy Exploiter', ExploitProcessor)
MS08_067 = ExploiterDescriptor('Ms08_067_Exploiter', 'Conficker Exploiter', ExploitProcessor)
SHELLSHOCK = ExploiterDescriptor('ShellShockExploiter', 'ShellShock Exploiter', ShellShockExploitProcessor)
STRUTS2 = ExploiterDescriptor('Struts2Exploiter', 'Struts2 Exploiter', ExploitProcessor)
WEBLOGIC = ExploiterDescriptor('WebLogicExploiter', 'Oracle WebLogic Exploiter', ExploitProcessor)
HADOOP = ExploiterDescriptor('HadoopExploiter', 'Hadoop/Yarn Exploiter', ExploitProcessor)
MSSQL = ExploiterDescriptor('MSSQLExploiter', 'MSSQL Exploiter', ExploitProcessor)
VSFTPD = ExploiterDescriptor('VSFTPDExploiter', 'VSFTPD Backdoor Exploiter', CredExploitProcessor)
DRUPAL = ExploiterDescriptor('DrupalExploiter', 'Drupal Server Exploiter', ExploitProcessor)

View File

@ -0,0 +1,33 @@
from __future__ import annotations
from enum import Enum
from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor, \
ExploiterReportInfo
class CredentialType(Enum):
PASSWORD = 'password'
HASH = 'hash'
KEY = 'key'
class CredExploitProcessor(ExploitProcessor):
@staticmethod
def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo:
exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict)
for attempt in exploit_dict['data']['attempts']:
if attempt['result']:
exploit_info.username = attempt['user']
if attempt['password']:
exploit_info.credential_type = CredentialType.PASSWORD.value
exploit_info.password = attempt['password']
elif attempt['ssh_key']:
exploit_info.credential_type = CredentialType.KEY.value
exploit_info.ssh_key = attempt['ssh_key']
else:
exploit_info.credential_type = CredentialType.HASH.value
return exploit_info
return exploit_info

View File

@ -0,0 +1,21 @@
from __future__ import annotations
from dataclasses import dataclass
from monkey_island.cc.services.node import NodeService
@dataclass
class ExploiterReportInfo:
machine: str
ip_address: str
type: str
class ExploitProcessor:
@staticmethod
def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo:
ip_addr = exploit_dict['data']['machine']['ip_addr']
machine = NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr))
return ExploiterReportInfo(ip_address=ip_addr, machine=machine, type=class_name)

View File

@ -0,0 +1,16 @@
from __future__ import annotations
from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor, \
ExploiterReportInfo
class ShellShockExploitProcessor(ExploitProcessor):
@staticmethod
def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo:
exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict)
urls = exploit_dict['data']['info']['vulnerable_urls']
exploit_info.port = urls[0].split(':')[2].split('/')[0]
exploit_info.paths = ['/' + url.split(':')[2].split('/')[1] for url in urls]
return exploit_info

View File

@ -2,7 +2,7 @@ import functools
import ipaddress import ipaddress
import itertools import itertools
import logging import logging
from enum import Enum from typing import Dict, List
from bson import json_util from bson import json_util
@ -17,6 +17,10 @@ from common.config_value_paths import (EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCA
USER_LIST_PATH) USER_LIST_PATH)
from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum, \
ExploiterDescriptor
from monkey_island.cc.services.reporting.issue_processing.exploit_processing import CredentialType
from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploiterReportInfo
from monkey_island.cc.services.reporting.pth_report import PTHReportService from monkey_island.cc.services.reporting.pth_report import PTHReportService
from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager
from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_regular_report from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_regular_report
@ -26,52 +30,19 @@ __author__ = "itay.mizeretz"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def build_exploiter_descriptor_dict() -> Dict[str, ExploiterDescriptor]:
descriptor_dict = {}
for descriptor in ExploiterDescriptorEnum:
descriptor_dict[descriptor.value.class_name] = descriptor
return descriptor_dict
class ReportService: class ReportService:
def __init__(self): exploiter_descriptors = build_exploiter_descriptor_dict()
pass
EXPLOIT_DISPLAY_DICT = \ class DerivedIssueEnum:
{ WEAK_PASSWORD = "weak_password"
'SmbExploiter': 'SMB Exploiter', STOLEN_CREDS = "stolen_creds"
'WmiExploiter': 'WMI Exploiter',
'SSHExploiter': 'SSH Exploiter',
'SambaCryExploiter': 'SambaCry Exploiter',
'ElasticGroovyExploiter': 'Elastic Groovy Exploiter',
'Ms08_067_Exploiter': 'Conficker Exploiter',
'ShellShockExploiter': 'ShellShock Exploiter',
'Struts2Exploiter': 'Struts2 Exploiter',
'WebLogicExploiter': 'Oracle WebLogic Exploiter',
'HadoopExploiter': 'Hadoop/Yarn Exploiter',
'MSSQLExploiter': 'MSSQL Exploiter',
'VSFTPDExploiter': 'VSFTPD Backdoor Exploiter',
'DrupalExploiter': 'Drupal Server Exploiter',
'ZerologonExploiter': 'Windows Server Zerologon Exploiter'
}
class ISSUES_DICT(Enum):
WEAK_PASSWORD = 0
STOLEN_CREDS = 1
ELASTIC = 2
SAMBACRY = 3
SHELLSHOCK = 4
CONFICKER = 5
AZURE = 6
STOLEN_SSH_KEYS = 7
STRUTS2 = 8
WEBLOGIC = 9
HADOOP = 10
PTH_CRIT_SERVICES_ACCESS = 11
MSSQL = 12
VSFTPD = 13
DRUPAL = 14
ZEROLOGON = 15
ZEROLOGON_PASSWORD_RESTORE_FAILED = 16
class WARNINGS_DICT(Enum):
CROSS_SEGMENT = 0
TUNNEL = 1
SHARED_LOCAL_ADMIN = 2
SHARED_PASSWORDS = 3
@staticmethod @staticmethod
def get_first_monkey_time(): def get_first_monkey_time():
@ -169,9 +140,7 @@ class ReportService:
'label': exploited_node['label'], 'label': exploited_node['label'],
'ip_addresses': exploited_node['ip_addresses'], 'ip_addresses': exploited_node['ip_addresses'],
'domain_name': exploited_node['domain_name'], 'domain_name': exploited_node['domain_name'],
'exploits': list(set( 'exploits': ReportService.get_exploits_used_on_node(exploited_node)
[ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in exploited_node['exploits']
if exploit['result']]))
} }
for exploited_node in exploited] for exploited_node in exploited]
@ -179,6 +148,10 @@ class ReportService:
return exploited return exploited
@staticmethod
def get_exploits_used_on_node(node: dict) -> List[str]:
return list(set([exploit['exploiter'] for exploit in node['exploits'] if exploit['result']]))
@staticmethod @staticmethod
def get_stolen_creds(): def get_stolen_creds():
creds = [] creds = []
@ -281,148 +254,12 @@ class ReportService:
return creds return creds
@staticmethod @staticmethod
def process_general_exploit(exploit): def process_exploit(exploit) -> ExploiterReportInfo:
ip_addr = exploit['data']['machine']['ip_addr']
return {'machine': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr)),
'ip_address': ip_addr}
@staticmethod
def process_general_creds_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
for attempt in exploit['data']['attempts']:
if attempt['result']:
processed_exploit['username'] = attempt['user']
if attempt['password']:
processed_exploit['type'] = 'password'
processed_exploit['password'] = attempt['password']
elif attempt['ssh_key']:
processed_exploit['type'] = 'ssh_key'
processed_exploit['ssh_key'] = attempt['ssh_key']
else:
processed_exploit['type'] = 'hash'
return processed_exploit
return processed_exploit
@staticmethod
def process_smb_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
if processed_exploit['type'] == 'password':
processed_exploit['type'] = 'smb_password'
else:
processed_exploit['type'] = 'smb_pth'
return processed_exploit
@staticmethod
def process_wmi_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
if processed_exploit['type'] == 'password':
processed_exploit['type'] = 'wmi_password'
else:
processed_exploit['type'] = 'wmi_pth'
return processed_exploit
@staticmethod
def process_ssh_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
# Check if it's ssh key or ssh login credentials exploit
if processed_exploit['type'] == 'ssh_key':
return processed_exploit
else:
processed_exploit['type'] = 'ssh'
return processed_exploit
@staticmethod
def process_vsftpd_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
processed_exploit['type'] = 'vsftp'
return processed_exploit
@staticmethod
def process_sambacry_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
processed_exploit['type'] = 'sambacry'
return processed_exploit
@staticmethod
def process_elastic_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'elastic'
return processed_exploit
@staticmethod
def process_conficker_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'conficker'
return processed_exploit
@staticmethod
def process_shellshock_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'shellshock'
urls = exploit['data']['info']['vulnerable_urls']
processed_exploit['port'] = urls[0].split(':')[2].split('/')[0]
processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls]
return processed_exploit
@staticmethod
def process_struts2_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'struts2'
return processed_exploit
@staticmethod
def process_weblogic_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'weblogic'
return processed_exploit
@staticmethod
def process_hadoop_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'hadoop'
return processed_exploit
@staticmethod
def process_mssql_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'mssql'
return processed_exploit
@staticmethod
def process_drupal_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'drupal'
return processed_exploit
@staticmethod
def process_zerologon_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'zerologon'
processed_exploit['password_restored'] = exploit['data']['info']['password_restored']
return processed_exploit
@staticmethod
def process_exploit(exploit):
exploiter_type = exploit['data']['exploiter'] exploiter_type = exploit['data']['exploiter']
EXPLOIT_PROCESS_FUNCTION_DICT = { exploiter_descriptor = ReportService.exploiter_descriptors[exploiter_type].value
'SmbExploiter': ReportService.process_smb_exploit, processor = exploiter_descriptor.processor()
'WmiExploiter': ReportService.process_wmi_exploit, exploiter_info = processor.get_exploit_info_by_dict(exploiter_descriptor.class_name, exploit)
'SSHExploiter': ReportService.process_ssh_exploit, return exploiter_info
'SambaCryExploiter': ReportService.process_sambacry_exploit,
'ElasticGroovyExploiter': ReportService.process_elastic_exploit,
'Ms08_067_Exploiter': ReportService.process_conficker_exploit,
'ShellShockExploiter': ReportService.process_shellshock_exploit,
'Struts2Exploiter': ReportService.process_struts2_exploit,
'WebLogicExploiter': ReportService.process_weblogic_exploit,
'HadoopExploiter': ReportService.process_hadoop_exploit,
'MSSQLExploiter': ReportService.process_mssql_exploit,
'VSFTPDExploiter': ReportService.process_vsftpd_exploit,
'DrupalExploiter': ReportService.process_drupal_exploit,
'ZerologonExploiter': ReportService.process_zerologon_exploit
}
return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit)
@staticmethod @staticmethod
def get_exploits(): def get_exploits():
@ -585,7 +422,8 @@ class ReportService:
@staticmethod @staticmethod
def get_cross_segment_issues(): def get_cross_segment_issues():
scans = mongo.db.telemetry.find({'telem_category': 'scan'}, scans = mongo.db.telemetry.find({'telem_category': 'scan'},
{'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1, 'data.machine.icmp': 1}) {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1,
'data.machine.icmp': 1})
cross_segment_issues = [] cross_segment_issues = []
@ -627,32 +465,6 @@ class ReportService:
else: else:
return None return None
@staticmethod
def get_issues():
ISSUE_GENERATORS = [
ReportService.get_exploits,
ReportService.get_tunnels,
ReportService.get_island_cross_segment_issues,
ReportService.get_azure_issues,
PTHReportService.get_duplicated_passwords_issues,
PTHReportService.get_strong_users_on_crit_issues
]
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
issues_dict = {}
for issue in issues:
if issue.get('is_local', True):
machine = issue.get('machine').upper()
aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine'))
if machine not in issues_dict:
issues_dict[machine] = []
if aws_instance_id:
issue['aws_instance_id'] = aws_instance_id
issues_dict[machine].append(issue)
logger.info('Issues generated for reporting')
return issues_dict
@staticmethod @staticmethod
def get_manual_monkeys(): def get_manual_monkeys():
return [monkey['hostname'] for monkey in mongo.db.monkey.find({}, {'hostname': 1, 'parent': 1, 'guid': 1}) if return [monkey['hostname'] for monkey in mongo.db.monkey.find({}, {'hostname': 1, 'parent': 1, 'guid': 1}) if
@ -677,8 +489,8 @@ class ReportService:
if exploits == default_exploits: if exploits == default_exploits:
return ['default'] return ['default']
return [ReportService.EXPLOIT_DISPLAY_DICT[exploit] for exploit in # TODO investigate strange code
exploits] return [exploit for exploit in exploits]
@staticmethod @staticmethod
def get_config_ips(): def get_config_ips():
@ -689,68 +501,37 @@ class ReportService:
return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True, True) return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True, True)
@staticmethod @staticmethod
def get_issues_overview(issues, config_users, config_passwords): def get_issue_set(issues, config_users, config_passwords):
issues_byte_array = [False] * len(ReportService.ISSUES_DICT) issue_set = set()
for machine in issues: for machine in issues:
for issue in issues[machine]: for issue in issues[machine]:
if issue['type'] == 'elastic': # TODO check if this actually works, because stolen passwords get added to config
issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True # so any password will be in config. We need to separate stolen passwords from initial
elif issue['type'] == 'sambacry': # passwords in config.
issues_byte_array[ReportService.ISSUES_DICT.SAMBACRY.value] = True if ReportService._is_weak_credential_issue(issue, config_users, config_passwords):
elif issue['type'] == 'vsftp': issue_set.add(ReportService.DerivedIssueEnum.WEAK_PASSWORD)
issues_byte_array[ReportService.ISSUES_DICT.VSFTPD.value] = True elif ReportService._is_stolen_credential_issue(issue):
elif issue['type'] == 'shellshock': issue_set.add(ReportService.DerivedIssueEnum.STOLEN_CREDS)
issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True
elif issue['type'] == 'conficker':
issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True
elif issue['type'] == 'azure_password':
issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True
elif issue['type'] == 'ssh_key':
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True
elif issue['type'] == 'struts2':
issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True
elif issue['type'] == 'weblogic':
issues_byte_array[ReportService.ISSUES_DICT.WEBLOGIC.value] = True
elif issue['type'] == 'mssql':
issues_byte_array[ReportService.ISSUES_DICT.MSSQL.value] = True
elif issue['type'] == 'hadoop':
issues_byte_array[ReportService.ISSUES_DICT.HADOOP.value] = True
elif issue['type'] == 'drupal':
issues_byte_array[ReportService.ISSUES_DICT.DRUPAL.value] = True
elif issue['type'] == 'zerologon':
if not issue['password_restored']:
issues_byte_array[ReportService.ISSUES_DICT.ZEROLOGON_PASSWORD_RESTORE_FAILED.value] = True
issues_byte_array[ReportService.ISSUES_DICT.ZEROLOGON.value] = True
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
issue['username'] in config_users or issue['type'] == 'ssh':
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
elif issue['type'] == 'strong_users_on_crit':
issues_byte_array[ReportService.ISSUES_DICT.PTH_CRIT_SERVICES_ACCESS.value] = True
elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'):
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True
return issues_byte_array issue_set.add(issue['type'])
return issue_set
@staticmethod @staticmethod
def get_warnings_overview(issues, cross_segment_issues): def _is_weak_credential_issue(issue: dict, config_usernames: List[str], config_passwords: List[str]) -> bool:
warnings_byte_array = [False] * len(ReportService.WARNINGS_DICT) # Only credential exploiter issues have 'credential_type'
return 'credential_type' in issue and \
issue['credential_type'] == CredentialType.PASSWORD.value and \
issue['password'] in config_passwords and \
issue['username'] in config_usernames
for machine in issues: @staticmethod
for issue in issues[machine]: def _is_stolen_credential_issue(issue: dict) -> bool:
if issue['type'] == 'island_cross_segment': # Only credential exploiter issues have 'credential_type'
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True return 'credential_type' in issue and \
elif issue['type'] == 'tunnel': (issue['credential_type'] == CredentialType.PASSWORD.value or
warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True issue['credential_type'] == CredentialType.HASH.value)
elif issue['type'] == 'shared_admins':
warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_LOCAL_ADMIN.value] = True
elif issue['type'] == 'shared_passwords':
warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_PASSWORDS.value] = True
if len(cross_segment_issues) != 0:
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
return warnings_byte_array
@staticmethod @staticmethod
def is_report_generated(): def is_report_generated():
@ -780,8 +561,7 @@ class ReportService:
'config_scan': ReportService.get_config_scan(), 'config_scan': ReportService.get_config_scan(),
'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"), 'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"),
'monkey_duration': ReportService.get_monkey_duration(), 'monkey_duration': ReportService.get_monkey_duration(),
'issues': ReportService.get_issues_overview(issues, config_users, config_passwords), 'issues': ReportService.get_issue_set(issues, config_users, config_passwords),
'warnings': ReportService.get_warnings_overview(issues, cross_segment_issues),
'cross_segment_issues': cross_segment_issues 'cross_segment_issues': cross_segment_issues
}, },
'glance': 'glance':

View File

@ -0,0 +1,51 @@
import datetime
from copy import deepcopy
from monkey_island.cc.services.reporting.report import ReportService
NODE_DICT = {
'id': '602f62118e30cf35830ff8e4',
'label': 'WinDev2010Eval.mshome.net',
'group': 'monkey_windows',
'os': 'windows',
'dead': True,
'exploits': [{'result': True,
'exploiter': 'DrupalExploiter',
'info': {'display_name': 'Drupal Server',
'started': datetime.datetime(2021, 2, 19, 9, 0, 14, 950000),
'finished': datetime.datetime(2021, 2, 19, 9, 0, 14, 950000),
'vulnerable_urls': [],
'vulnerable_ports': [],
'executed_cmds': []},
'attempts': [],
'timestamp': datetime.datetime(2021, 2, 19, 9, 0, 14, 984000),
'origin': 'MonkeyIsland : 192.168.56.1'},
{'result': True,
'exploiter': 'ElasticGroovyExploiter',
'info': {'display_name': 'Elastic search',
'started': datetime.datetime(2021, 2, 19, 9, 0, 15, 16000),
'finished': datetime.datetime(2021, 2, 19, 9, 0, 15, 17000),
'vulnerable_urls': [], 'vulnerable_ports': [], 'executed_cmds': []},
'attempts': [],
'timestamp': datetime.datetime(2021, 2, 19, 9, 0, 15, 60000),
'origin': 'MonkeyIsland : 192.168.56.1'}]
}
NODE_DICT_DUPLICATE_EXPLOITS = deepcopy(NODE_DICT)
NODE_DICT_DUPLICATE_EXPLOITS['exploits'][1] = NODE_DICT_DUPLICATE_EXPLOITS['exploits'][0]
NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT)
NODE_DICT_FAILED_EXPLOITS['exploits'][0]['result'] = False
NODE_DICT_FAILED_EXPLOITS['exploits'][1]['result'] = False
def test_get_exploits_used_on_node():
exploits = ReportService.get_exploits_used_on_node(NODE_DICT)
assert sorted(exploits) == sorted(['ElasticGroovyExploiter', 'DrupalExploiter'])
exploits = ReportService.get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS)
assert exploits == ['DrupalExploiter']
exploits = ReportService.get_exploits_used_on_node(NODE_DICT_FAILED_EXPLOITS)
assert exploits == []

View File

@ -0,0 +1,7 @@
class IssueDescriptor {
constructor(name, overviewComponent, reportComponent) {
this.name = name;
this.overviewComponent = overviewComponent;
this.reportComponent = reportComponent;
}
}

View File

@ -0,0 +1,22 @@
import React from 'react';
export function AzurePasswordIssueOverview() {
return (<li>Azure machines expose plaintext passwords. (<a
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
>More info</a>)</li>)
}
export function AzurePasswordIssueReport(issue) {
return (
<>
Delete VM Access plugin configuration files.
<CollapsibleWellComponent>
Credentials could be stolen from <span
className="badge badge-primary">{issue.machine}</span> for the following users <span
className="badge badge-primary">{issue.users}</span>. Read more about the security issue and remediation <a
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
>here</a>.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,84 @@
import React from 'react';
import CollapsibleWellComponent from '../CollapsibleWell';
import {generateInfoBadges} from './utils';
export function crossSegmentIssueOverview() {
return (<li key="segmentation">Weak segmentation - Machines from
different segments are able to communicate.</li>)
}
export function generateCrossSegmentIssue(crossSegmentIssue) {
let crossSegmentIssueOverview = 'Communication possible from '
+ `${crossSegmentIssue['source_subnet']} to ${crossSegmentIssue['target_subnet']}`;
return (
<li key={crossSegmentIssueOverview}>
{crossSegmentIssueOverview}
<CollapsibleWellComponent>
<ul className='cross-segment-issues'>
{crossSegmentIssue['issues'].map(
issue => generateCrossSegmentIssueListItem(issue)
)}
</ul>
</CollapsibleWellComponent>
</li>
);
}
export function generateCrossSegmentIssueListItem(issue) {
if (issue['is_self']) {
return this.generateCrossSegmentSingleHostMessage(issue);
}
return this.generateCrossSegmentMultiHostMessage(issue);
}
export function generateCrossSegmentSingleHostMessage(issue) {
return (
<li key={issue['hostname']}>
{`Machine ${issue['hostname']} has both ips: ${issue['source']} and ${issue['target']}`}
</li>
);
}
export function generateCrossSegmentMultiHostMessage(issue) {
return (
<li key={issue['source'] + issue['target']}>
IP {issue['source']} ({issue['hostname']}) was able to communicate with
IP {issue['target']} using:
<ul>
{issue['icmp'] && <li key='icmp'>ICMP</li>}
{this.generateCrossSegmentServiceListItems(issue)}
</ul>
</li>
);
}
export function generateCrossSegmentServiceListItems(issue) {
let service_list_items = [];
for (const [service, info] of Object.entries(issue['services'])) {
service_list_items.push(
<li key={service}>
<span className='cross-segment-service'>{service}</span> ({info['display_name']})
</li>
);
}
return service_list_items;
}
export function generateIslandCrossSegmentIssue(issue) {
return (
<>
Segment your network and make sure there is no communication between machines from different segments.
<CollapsibleWellComponent>
The network can probably be segmented. A monkey instance on <span
className="badge badge-primary">{issue.machine}</span> in the
networks {generateInfoBadges(issue.networks)}
could directly access the Monkey Island server in the
networks {generateInfoBadges(issue.server_networks)}.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,23 @@
import React from 'react';
export function DrupalIssueOverview() {
return (<li>Drupal server/s are vulnerable to <a
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6340">CVE-2019-6340</a>.</li>)
}
export function DrupalIssueReport(issue) {
return (
<>
Upgrade Drupal server to versions 8.5.11, 8.6.10, or later.
<CollapsibleWellComponent>
Drupal server at <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
className="badge badge-danger">remote command execution</span> attack.
<br/>
The attack was made possible because the server is using an old version of Drupal, for which REST API is
enabled. For possible workarounds, fixes and more info read
<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6340">here</a>.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,22 @@
import React from 'react';
export function ElasticIssueOverview() {
return (<li>Elasticsearch servers are vulnerable to <a
href="https://www.cvedetails.com/cve/cve-2015-1427">CVE-2015-1427</a>.
</li>)
}
export function ElasticIssueReport(issue) {
return (
<>
Update your Elastic Search server to version 1.4.3 and up.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to an <span
className="badge badge-danger">Elastic Groovy</span> attack.
<br/>
The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,22 @@
import React from 'react';
export function HadoopIssueOverview() {
return (<li>Hadoop/Yarn servers are vulnerable to remote code execution.</li>)
}
export function HadoopIssueReport(issue) {
return (
<>
Run Hadoop in secure mode (<a
href="http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html">
add Kerberos authentication</a>).
<CollapsibleWellComponent>
The Hadoop server at <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
className="badge badge-danger">remote code execution</span> attack.
<br/>
The attack was made possible due to default Hadoop/Yarn configuration being insecure.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,23 @@
import React from 'react';
export function MS08_067IssueOverview() {
return (<li>Machines are vulnerable to Conficker (<a
href="https://docs.microsoft.com/en-us/security-updates/SecurityBulletins/2008/ms08-067"
>MS08-067</a>). </li>)
}
export function MS08_067IssueReport(issue) {
return (
<>
Install the latest Windows updates or upgrade to a newer operating system.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">Conficker</span> attack.
<br/>
The attack was made possible because the target machine used an outdated and unpatched operating system
vulnerable to Conficker.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,23 @@
import React from 'react';
export function MssqlIssueOverview() {
return (<li>MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.</li>)
}
export function MssqlIssueReport(issue) {
return (
<>
Disable the xp_cmdshell option.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">MSSQL exploit attack</span>.
<br/>
The attack was made possible because the target machine used an outdated MSSQL server configuration allowing
the usage of the xp_cmdshell command. To learn more about how to disable this feature, read <a
href="https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/xp-cmdshell-server-configuration-option?view=sql-server-2017">
Microsoft's documentation. </a>
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,6 @@
import React from 'react';
export function PthCriticalServiceIssueOverview() {
return (<li>Mimikatz found login credentials of a user who has admin access to a server defined as
critical.</li>)
}

View File

@ -0,0 +1,27 @@
import React from 'react';
export function SambacryIssueOverview() {
return (<li>Samba servers are vulnerable to SambaCry (<a
href="https://www.samba.org/samba/security/CVE-2017-7494.html"
>CVE-2017-7494</a>).</li>)
}
export function SambacryIssueReport(issue) {
return (
<>
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<br/>
Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SambaCry</span> attack.
<br/>
The Monkey authenticated over the SMB protocol with user <span
className="badge badge-success">{issue.username}</span> and its password, and used the SambaCry
vulnerability.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,49 @@
import React from 'react';
export function sharedPasswordsIssueOverview() {
return (<li key={"shared_passwords"}>Multiple users have the same password</li>)
}
export function sharedAdminsDomainIssueOverview() {
return (<li key={"admin_domains"}>Shared local administrator account - Different machines have the same account as a local
administrator.</li>)
}
export function generateSharedCredsDomainIssue(issue) {
return (
<>
Some domain users are sharing passwords, this should be fixed by changing passwords.
<CollapsibleWellComponent>
These users are sharing access password:
{this.generateInfoBadges(issue.shared_with)}.
</CollapsibleWellComponent>
</>
);
}
export function generateSharedCredsIssue(issue) {
return (
<>
Some users are sharing passwords, this should be fixed by changing passwords.
<CollapsibleWellComponent>
These users are sharing access password:
{this.generateInfoBadges(issue.shared_with)}.
</CollapsibleWellComponent>
</>
);
}
export function generateSharedLocalAdminsIssue(issue) {
return (
<>
Make sure the right administrator accounts are managing the right machines, and that there isnt an
unintentional local
admin sharing.
<CollapsibleWellComponent>
Here is a list of machines which the account <span
className="badge badge-primary">{issue.username}</span> is defined as an administrator:
{this.generateInfoBadges(issue.shared_machines)}
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,29 @@
import React from 'react';
export function ShellShockIssueOverview() {
return (<li>Machines are vulnerable to Shellshock (<a
href="https://www.cvedetails.com/cve/CVE-2014-6271">CVE-2014-6271</a>).
</li>)
}
function generateShellshockPathListBadges(paths) {
return paths.map(path => <span className="badge badge-warning" style={{margin: '2px'}} key={path}>{path}</span>);
}
export function ShellShockIssueReport(issue) {
return (
<>
Update your Bash to a ShellShock-patched version.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">ShellShock</span> attack.
<br/>
The attack was made possible because the HTTP server running on TCP port <span
className="badge badge-info">{issue.port}</span> was vulnerable to a shell injection attack on the
paths: {generateShellshockPathListBadges(issue.paths)}.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,36 @@
import React from 'react';
import CollapsibleWellComponent from '../CollapsibleWell';
export function generateSmbPasswordReport(issue) {
return (
<>
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SMB</span> attack.
<br/>
The Monkey authenticated over the SMB protocol with user <span
className="badge badge-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</>
);
}
export function generateSmbPthReport(issue) {
return (
<>
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SMB</span> attack.
<br/>
The Monkey used a pass-the-hash attack over SMB protocol with user <span
className="badge badge-success">{issue.username}</span>.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,38 @@
import React from 'react';
export function SshIssueOverview() {
return (<li>Stolen SSH keys are used to exploit other machines.</li>)
}
export function ShhIssueReport(issue) {
return (
<>
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SSH</span> attack.
<br/>
The Monkey authenticated over the SSH protocol with user <span
className="badge badge-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</>
);
}
export function generateSshKeysReport(issue) {
return (
<>
Protect <span className="badge badge-success">{issue.ssh_key}</span> private key with a pass phrase.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SSH</span> attack.
<br/>
The Monkey authenticated over the SSH protocol with private key <span
className="badge badge-success">{issue.ssh_key}</span>.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,5 @@
import React from 'react';
export function StolenCredsIssueOverview() {
return (<li>Stolen credentials are used to exploit other machines.</li>)
}

View File

@ -0,0 +1,15 @@
import React from 'react';
export function generateStrongUsersOnCritIssue(issue) {
return (
<>
This critical machine is open to attacks via strong users with access to it.
<CollapsibleWellComponent>
The services: {this.generateInfoBadges(issue.services)} have been found on the machine
thus classifying it as a critical machine.
These users has access to it:
{this.generateInfoBadges(issue.threatening_users)}.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,25 @@
import React from 'react';
export function Struts2IssueOverview() {
return (<li>Struts2 servers are vulnerable to remote code execution. (<a
href="https://cwiki.apache.org/confluence/display/WW/S2-045">
CVE-2017-5638</a>)</li>)
}
export function Struts2IssueReport(issue) {
return (
<>
Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.
<CollapsibleWellComponent>
Struts2 server at <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
className="badge badge-danger">remote code execution</span> attack.
<br/>
The attack was made possible because the server is using an old version of Jakarta based file upload
Multipart parser. For possible work-arounds and more info read <a
href="https://cwiki.apache.org/confluence/display/WW/S2-045"
>here</a>.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,18 @@
import React from 'react';
export function generateTunnelIssueOverview(){
return (<li key="tunnel">Weak segmentation - Machines were able to communicate over unused ports.</li>)
}
export function generateTunnelIssue(issue) {
return (
<>
Use micro-segmentation policies to disable communication other than the required.
<CollapsibleWellComponent>
Machines are not locked down at port level. Network tunnel was set up from <span
className="badge badge-primary">{issue.machine}</span> to <span
className="badge badge-primary">{issue.dest}</span>.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,35 @@
import React from 'react';
export function VsftpdIssueOverview() {
return (<li>VSFTPD is vulnerable to <a
href="https://www.rapid7.com/db/modules/exploit/unix/ftp/vsftpd_234_backdoor">CVE-2011-2523</a>.
</li>)
}
export function VsftpdIssueReport(issue) {
return (
<>
Update your VSFTPD server to the latest version vsftpd-3.0.3.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) has a backdoor running at
port <span
className="badge badge-danger">6200</span>.
<br/>
The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523.
<br/><br/>In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been
compromised.
Users logging into a compromised vsftpd-2.3.4 server may issue a ":)" smileyface as the username and gain a
command
shell on port 6200.
<br/><br/>
The Monkey executed commands by first logging in with ":)" in the username and then sending commands to the
backdoor
at port 6200.
<br/><br/>Read more about the security issue and remediation <a
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-2523"
>here</a>.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,6 @@
import React from 'react';
export function WeakPasswordIssueOverview() {
return (<li>Machines are accessible using passwords supplied by the user during the Monkeys
configuration.</li>)
}

View File

@ -0,0 +1,22 @@
import React from 'react';
export function WebLogicIssueOverview() {
return (<li>Oracle WebLogic servers are susceptible to a remote code execution vulnerability.</li>)
}
export function WebLogicIssueReport(issue) {
return (
<>
Update Oracle WebLogic server to the latest supported version.
<CollapsibleWellComponent>
Oracle WebLogic server at <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to one of <span
className="badge badge-danger">remote code execution</span> attacks.
<br/>
The attack was made possible due to one of the following vulnerabilities:
<a href={'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-10271'}> CVE-2017-10271</a> or
<a href={'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-2725'}> CVE-2019-2725</a>
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,36 @@
import React from 'react';
import CollapsibleWellComponent from '../CollapsibleWell';
export function generateWmiPasswordIssue(issue) {
return (
<>
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">WMI</span> attack.
<br/>
The Monkey authenticated over the WMI protocol with user <span
className="badge badge-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</>
);
}
export function generateWmiPthIssue(issue) {
return (
<>
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">WMI</span> attack.
<br/>
The Monkey used a pass-the-hash attack over WMI protocol with user <span
className="badge badge-success">{issue.username}</span>.
</CollapsibleWellComponent>
</>
);
}

View File

@ -0,0 +1,6 @@
import React from 'react';
export function generateInfoBadges(data_array) {
return data_array.map(badge_data => <span key={badge_data} className="badge badge-info"
style={{margin: '2px'}}>{badge_data}</span>);
}