Merge pull request #1068 from guardicore/report_refactoring
Report refactoring
This commit is contained in:
commit
c7a241e776
|
@ -0,0 +1,42 @@
|
|||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Type, Dict
|
||||
|
||||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import \
|
||||
CredExploitProcessor
|
||||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor
|
||||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import \
|
||||
ShellShockExploitProcessor
|
||||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import \
|
||||
ZerologonExploitProcessor
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExploiterDescriptor:
|
||||
# Must match with class names of exploiters in Infection Monkey code
|
||||
class_name: str
|
||||
display_name: str
|
||||
processor: Type[object] = 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)
|
||||
ZEROLOGON = ExploiterDescriptor('ZerologonExploiter', 'Zerologon Exploiter', ZerologonExploitProcessor)
|
||||
|
||||
@staticmethod
|
||||
def get_by_class_name(class_name: str) -> ExploiterDescriptor:
|
||||
return [descriptor.value
|
||||
for descriptor in ExploiterDescriptorEnum
|
||||
if descriptor.value.class_name == class_name][0]
|
|
@ -0,0 +1,23 @@
|
|||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Union, List
|
||||
|
||||
|
||||
class CredentialType(Enum):
|
||||
PASSWORD = 'password'
|
||||
HASH = 'hash'
|
||||
KEY = 'key'
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExploiterReportInfo:
|
||||
machine: str
|
||||
ip_address: str
|
||||
type: str
|
||||
username: Union[str, None] = None
|
||||
credential_type: Union[CredentialType, None] = None
|
||||
ssh_key: Union[str, None] = None
|
||||
password: Union[str, None] = None
|
||||
port: Union[str, None] = None
|
||||
paths: Union[List[str], None] = None
|
||||
password_restored: Union[bool, None] = None
|
|
@ -0,0 +1,24 @@
|
|||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import \
|
||||
ExploiterReportInfo, CredentialType
|
||||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor
|
||||
|
||||
|
||||
class CredExploitProcessor:
|
||||
|
||||
@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
|
|
@ -0,0 +1,12 @@
|
|||
from monkey_island.cc.services.node import NodeService
|
||||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import \
|
||||
ExploiterReportInfo
|
||||
|
||||
|
||||
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)
|
|
@ -0,0 +1,14 @@
|
|||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \
|
||||
ExploiterReportInfo, ExploitProcessor
|
||||
|
||||
|
||||
class ShellShockExploitProcessor:
|
||||
|
||||
@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
|
|
@ -0,0 +1,11 @@
|
|||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor, \
|
||||
ExploiterReportInfo
|
||||
|
||||
|
||||
class ZerologonExploitProcessor:
|
||||
|
||||
@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)
|
||||
exploit_info.password_restored = exploit_dict['data']['info']['password_restored']
|
||||
return exploit_info
|
|
@ -2,7 +2,7 @@ import functools
|
|||
import ipaddress
|
||||
import itertools
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from bson import json_util
|
||||
|
||||
|
@ -17,6 +17,11 @@ from common.config_value_paths import (EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCA
|
|||
USER_LIST_PATH)
|
||||
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.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum
|
||||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import \
|
||||
CredentialType
|
||||
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \
|
||||
ExploiterReportInfo
|
||||
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_generation_synchronisation import safe_generate_regular_report
|
||||
|
@ -27,51 +32,11 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class ReportService:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
EXPLOIT_DISPLAY_DICT = \
|
||||
{
|
||||
'SmbExploiter': 'SMB Exploiter',
|
||||
'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
|
||||
class DerivedIssueEnum:
|
||||
WEAK_PASSWORD = "weak_password"
|
||||
STOLEN_CREDS = "stolen_creds"
|
||||
ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
|
||||
|
||||
@staticmethod
|
||||
def get_first_monkey_time():
|
||||
|
@ -169,9 +134,7 @@ class ReportService:
|
|||
'label': exploited_node['label'],
|
||||
'ip_addresses': exploited_node['ip_addresses'],
|
||||
'domain_name': exploited_node['domain_name'],
|
||||
'exploits': list(set(
|
||||
[ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in exploited_node['exploits']
|
||||
if exploit['result']]))
|
||||
'exploits': ReportService.get_exploits_used_on_node(exploited_node)
|
||||
}
|
||||
for exploited_node in exploited]
|
||||
|
||||
|
@ -179,6 +142,12 @@ class ReportService:
|
|||
|
||||
return exploited
|
||||
|
||||
@staticmethod
|
||||
def get_exploits_used_on_node(node: dict) -> List[str]:
|
||||
return list(set([ExploiterDescriptorEnum.get_by_class_name(exploit['exploiter']).display_name
|
||||
for exploit in node['exploits']
|
||||
if exploit['result']]))
|
||||
|
||||
@staticmethod
|
||||
def get_stolen_creds():
|
||||
creds = []
|
||||
|
@ -281,151 +250,15 @@ class ReportService:
|
|||
return creds
|
||||
|
||||
@staticmethod
|
||||
def process_general_exploit(exploit):
|
||||
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):
|
||||
def process_exploit(exploit) -> ExploiterReportInfo:
|
||||
exploiter_type = exploit['data']['exploiter']
|
||||
EXPLOIT_PROCESS_FUNCTION_DICT = {
|
||||
'SmbExploiter': ReportService.process_smb_exploit,
|
||||
'WmiExploiter': ReportService.process_wmi_exploit,
|
||||
'SSHExploiter': ReportService.process_ssh_exploit,
|
||||
'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)
|
||||
exploiter_descriptor = ExploiterDescriptorEnum.get_by_class_name(exploiter_type)
|
||||
processor = exploiter_descriptor.processor()
|
||||
exploiter_info = processor.get_exploit_info_by_dict(exploiter_type, exploit)
|
||||
return exploiter_info
|
||||
|
||||
@staticmethod
|
||||
def get_exploits():
|
||||
def get_exploits() -> List[dict]:
|
||||
query = [{'$match': {'telem_category': 'exploit', 'data.result': True}},
|
||||
{'$group': {'_id': {'ip_address': '$data.machine.ip_addr'},
|
||||
'data': {'$first': '$$ROOT'},
|
||||
|
@ -435,7 +268,7 @@ class ReportService:
|
|||
for exploit in mongo.db.telemetry.aggregate(query):
|
||||
new_exploit = ReportService.process_exploit(exploit)
|
||||
if new_exploit not in exploits:
|
||||
exploits.append(new_exploit)
|
||||
exploits.append(new_exploit.__dict__)
|
||||
return exploits
|
||||
|
||||
@staticmethod
|
||||
|
@ -585,7 +418,8 @@ class ReportService:
|
|||
@staticmethod
|
||||
def get_cross_segment_issues():
|
||||
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 = []
|
||||
|
||||
|
@ -599,7 +433,6 @@ class ReportService:
|
|||
|
||||
@staticmethod
|
||||
def get_domain_issues():
|
||||
|
||||
ISSUE_GENERATORS = [
|
||||
PTHReportService.get_duplicated_passwords_issues,
|
||||
PTHReportService.get_shared_admins_issues,
|
||||
|
@ -627,32 +460,6 @@ class ReportService:
|
|||
else:
|
||||
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
|
||||
def get_manual_monkeys():
|
||||
return [monkey['hostname'] for monkey in mongo.db.monkey.find({}, {'hostname': 1, 'parent': 1, 'guid': 1}) if
|
||||
|
@ -677,8 +484,8 @@ class ReportService:
|
|||
if exploits == default_exploits:
|
||||
return ['default']
|
||||
|
||||
return [ReportService.EXPLOIT_DISPLAY_DICT[exploit] for exploit in
|
||||
exploits]
|
||||
return [ExploiterDescriptorEnum.get_by_class_name(exploit).display_name
|
||||
for exploit in exploits]
|
||||
|
||||
@staticmethod
|
||||
def get_config_ips():
|
||||
|
@ -689,68 +496,41 @@ class ReportService:
|
|||
return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True, True)
|
||||
|
||||
@staticmethod
|
||||
def get_issues_overview(issues, config_users, config_passwords):
|
||||
issues_byte_array = [False] * len(ReportService.ISSUES_DICT)
|
||||
def get_issue_set(issues, config_users, config_passwords):
|
||||
issue_set = set()
|
||||
|
||||
for machine in issues:
|
||||
for issue in issues[machine]:
|
||||
if issue['type'] == 'elastic':
|
||||
issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True
|
||||
elif issue['type'] == 'sambacry':
|
||||
issues_byte_array[ReportService.ISSUES_DICT.SAMBACRY.value] = True
|
||||
elif issue['type'] == 'vsftp':
|
||||
issues_byte_array[ReportService.ISSUES_DICT.VSFTPD.value] = True
|
||||
elif issue['type'] == 'shellshock':
|
||||
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
|
||||
if ReportService._is_weak_credential_issue(issue, config_users, config_passwords):
|
||||
issue_set.add(ReportService.DerivedIssueEnum.WEAK_PASSWORD)
|
||||
elif ReportService._is_stolen_credential_issue(issue):
|
||||
issue_set.add(ReportService.DerivedIssueEnum.STOLEN_CREDS)
|
||||
elif ReportService._is_zerologon_pass_restore_failed(issue):
|
||||
issue_set.add(ReportService.DerivedIssueEnum.ZEROLOGON_PASS_RESTORE_FAILED)
|
||||
|
||||
return issues_byte_array
|
||||
issue_set.add(issue['type'])
|
||||
|
||||
return issue_set
|
||||
|
||||
@staticmethod
|
||||
def get_warnings_overview(issues, cross_segment_issues):
|
||||
warnings_byte_array = [False] * len(ReportService.WARNINGS_DICT)
|
||||
def _is_weak_credential_issue(issue: dict, config_usernames: List[str], config_passwords: List[str]) -> bool:
|
||||
# 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:
|
||||
for issue in issues[machine]:
|
||||
if issue['type'] == 'island_cross_segment':
|
||||
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
|
||||
elif issue['type'] == 'tunnel':
|
||||
warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True
|
||||
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
|
||||
@staticmethod
|
||||
def _is_stolen_credential_issue(issue: dict) -> bool:
|
||||
# Only credential exploiter issues have 'credential_type'
|
||||
return 'credential_type' in issue and \
|
||||
(issue['credential_type'] == CredentialType.PASSWORD.value or
|
||||
issue['credential_type'] == CredentialType.HASH.value)
|
||||
|
||||
if len(cross_segment_issues) != 0:
|
||||
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
|
||||
|
||||
return warnings_byte_array
|
||||
@staticmethod
|
||||
def _is_zerologon_pass_restore_failed(issue: dict):
|
||||
return issue['type'] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name \
|
||||
and not issue['password_restored']
|
||||
|
||||
@staticmethod
|
||||
def is_report_generated():
|
||||
|
@ -763,6 +543,7 @@ class ReportService:
|
|||
issues = ReportService.get_issues()
|
||||
config_users = ReportService.get_config_users()
|
||||
config_passwords = ReportService.get_config_passwords()
|
||||
issue_set = ReportService.get_issue_set(issues, config_users, config_passwords)
|
||||
cross_segment_issues = ReportService.get_cross_segment_issues()
|
||||
monkey_latest_modify_time = Monkey.get_latest_modifytime()
|
||||
|
||||
|
@ -780,8 +561,7 @@ class ReportService:
|
|||
'config_scan': ReportService.get_config_scan(),
|
||||
'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"),
|
||||
'monkey_duration': ReportService.get_monkey_duration(),
|
||||
'issues': ReportService.get_issues_overview(issues, config_users, config_passwords),
|
||||
'warnings': ReportService.get_warnings_overview(issues, cross_segment_issues),
|
||||
'issues': issue_set,
|
||||
'cross_segment_issues': cross_segment_issues
|
||||
},
|
||||
'glance':
|
||||
|
@ -809,6 +589,32 @@ class ReportService:
|
|||
|
||||
return report
|
||||
|
||||
@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
|
||||
def encode_dot_char_before_mongo_insert(report_dict):
|
||||
"""
|
||||
|
|
|
@ -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(['Elastic Groovy Exploiter', 'Drupal Server Exploiter'])
|
||||
|
||||
exploits = ReportService.get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS)
|
||||
assert exploits == ['Drupal Server Exploiter']
|
||||
|
||||
exploits = ReportService.get_exploits_used_on_node(NODE_DICT_FAILED_EXPLOITS)
|
||||
assert exploits == []
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
|||
class IssueDescriptor {
|
||||
constructor(name, overviewComponent, reportComponent) {
|
||||
this.name = name;
|
||||
this.overviewComponent = overviewComponent;
|
||||
this.reportComponent = reportComponent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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 crossSegmentIssueReport(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 => getCrossSegmentIssueListItem(issue)
|
||||
)}
|
||||
</ul>
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export function getCrossSegmentIssueListItem(issue) {
|
||||
if (issue['is_self']) {
|
||||
return getCrossSegmentSingleHostMessage(issue);
|
||||
}
|
||||
|
||||
return getCrossSegmentMultiHostMessage(issue);
|
||||
}
|
||||
|
||||
export function getCrossSegmentSingleHostMessage(issue) {
|
||||
return (
|
||||
<li key={issue['hostname']}>
|
||||
{`Machine ${issue['hostname']} has both ips: ${issue['source']} and ${issue['target']}`}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export function getCrossSegmentMultiHostMessage(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>}
|
||||
{getCrossSegmentServiceListItems(issue)}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export function getCrossSegmentServiceListItems(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 islandCrossSegmentIssueReport(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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
import {generateInfoBadges} from './utils';
|
||||
|
||||
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 sharedCredsDomainIssueReport(issue) {
|
||||
return (
|
||||
<>
|
||||
Some domain users are sharing passwords, this should be fixed by changing passwords.
|
||||
<CollapsibleWellComponent>
|
||||
These users are sharing access password:
|
||||
{generateInfoBadges(issue.shared_with)}.
|
||||
</CollapsibleWellComponent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function sharedCredsIssueReport(issue) {
|
||||
return (
|
||||
<>
|
||||
Some users are sharing passwords, this should be fixed by changing passwords.
|
||||
<CollapsibleWellComponent>
|
||||
These users are sharing access password:
|
||||
{generateInfoBadges(issue.shared_with)}.
|
||||
</CollapsibleWellComponent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function sharedLocalAdminsIssueReport(issue) {
|
||||
return (
|
||||
<>
|
||||
Make sure the right administrator accounts are managing the right machines, and that there isn’t 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:
|
||||
{generateInfoBadges(issue.shared_machines)}
|
||||
</CollapsibleWellComponent>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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 getShellshockPathListBadges(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: {getShellshockPathListBadges(issue.paths)}.
|
||||
</CollapsibleWellComponent>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
export function smbPasswordReport(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 smbPthReport(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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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 sshKeysReport(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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import React from 'react';
|
||||
|
||||
export function stolenCredsIssueOverview() {
|
||||
return (<li>Stolen credentials are used to exploit other machines.</li>)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
export function strongUsersOnCritIssueReport(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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
export function tunnelIssueOverview(){
|
||||
return (<li key="tunnel">Weak segmentation - Machines were able to communicate over unused ports.</li>)
|
||||
}
|
||||
|
||||
export function tunnelIssueReport(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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
export function weakPasswordIssueOverview() {
|
||||
return (<li>Machines are accessible using passwords supplied by the user during the Monkey’s
|
||||
configuration.</li>)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
export function wmiPasswordIssueReport(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 wmiPthIssueReport(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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
import WarningIcon from '../../../ui-components/WarningIcon';
|
||||
import {Button} from 'react-bootstrap';
|
||||
|
||||
export function zerologonIssueOverview() {
|
||||
return (
|
||||
<li>
|
||||
Some Windows domain controllers are vulnerable to 'Zerologon' (
|
||||
<Button variant={'link'}
|
||||
href='https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1472'
|
||||
target={'_blank'}
|
||||
className={'security-report-link'}>
|
||||
CVE-2020-1472
|
||||
</Button>).
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export function zerologonOverviewWithFailedPassResetWarning() {
|
||||
let overview = [zerologonIssueOverview()];
|
||||
overview.push(
|
||||
<li>
|
||||
<span className={'zero-logon-overview-pass-restore-failed'}>
|
||||
<WarningIcon/>
|
||||
Automatic password restoration on a domain controller failed!
|
||||
<Button variant={'link'}
|
||||
href={'https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/zerologon/'}
|
||||
target={'_blank'}
|
||||
className={'security-report-link'}>
|
||||
Restore your domain controller's password manually.
|
||||
</Button>
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
return overview;
|
||||
}
|
||||
|
||||
export function zerologonIssueReport(issue) {
|
||||
return (
|
||||
<>
|
||||
Install Windows security updates.
|
||||
<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">Zerologon exploit</span>.
|
||||
<br/>
|
||||
The attack was possible because the latest security updates from Microsoft
|
||||
have not been applied to this machine. For more information about this
|
||||
vulnerability, read <a href="https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1472">
|
||||
Microsoft's documentation.</a>
|
||||
{!issue.password_restored ?
|
||||
<div className={'info-pane-warning'} key={'warning'}>
|
||||
<br/><WarningIcon/>
|
||||
<span>
|
||||
The domain controller's password was changed during the exploit and could not be restored successfully.
|
||||
Instructions on how to manually reset the domain controller's password can be found <a
|
||||
href="https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/zerologon/">here</a>.
|
||||
</span>
|
||||
</div> : null}
|
||||
</CollapsibleWellComponent>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>);
|
||||
}
|
Loading…
Reference in New Issue