forked from p34709852/monkey
Merge branch 'develop' into feature/325-notification-when-done
This commit is contained in:
commit
6480cfe232
|
@ -8,3 +8,14 @@ class ScanStatus(Enum):
|
||||||
SCANNED = 1
|
SCANNED = 1
|
||||||
# Technique was attempted and succeeded
|
# Technique was attempted and succeeded
|
||||||
USED = 2
|
USED = 2
|
||||||
|
|
||||||
|
# Dict that describes what BITS job was used for
|
||||||
|
BITS_UPLOAD_STRING = {"usage": "BITS job was used to upload monkey to a remote system."}
|
||||||
|
|
||||||
|
|
||||||
|
def format_time(time):
|
||||||
|
return "%s-%s %s:%s:%s" % (time.date().month,
|
||||||
|
time.date().day,
|
||||||
|
time.time().hour,
|
||||||
|
time.time().minute,
|
||||||
|
time.time().second)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
|
# abstract, static method decorator
|
||||||
|
class abstractstatic(staticmethod):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __init__(self, function):
|
||||||
|
super(abstractstatic, self).__init__(function)
|
||||||
|
function.__isabstractmethod__ = True
|
||||||
|
__isabstractmethod__ = True
|
|
@ -1,4 +1,4 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||||
import infection_monkey.config
|
import infection_monkey.config
|
||||||
from common.utils.exploit_enum import ExploitType
|
from common.utils.exploit_enum import ExploitType
|
||||||
|
|
||||||
|
@ -13,9 +13,15 @@ class HostExploiter(object):
|
||||||
# Usual values are 'vulnerability' or 'brute_force'
|
# Usual values are 'vulnerability' or 'brute_force'
|
||||||
EXPLOIT_TYPE = ExploitType.VULNERABILITY
|
EXPLOIT_TYPE = ExploitType.VULNERABILITY
|
||||||
|
|
||||||
|
@abstractproperty
|
||||||
|
def _EXPLOITED_SERVICE(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
self._exploit_info = {}
|
self._exploit_info = {'display_name': self._EXPLOITED_SERVICE,
|
||||||
|
'vulnerable_urls': [],
|
||||||
|
'vulnerable_ports': []}
|
||||||
self._exploit_attempts = []
|
self._exploit_attempts = []
|
||||||
self.host = host
|
self.host = host
|
||||||
|
|
||||||
|
@ -37,6 +43,12 @@ class HostExploiter(object):
|
||||||
def exploit_host(self):
|
def exploit_host(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def add_vuln_url(self, url):
|
||||||
|
self._exploit_info['vulnerable_urls'].append(url)
|
||||||
|
|
||||||
|
def add_vuln_port(self, port):
|
||||||
|
self._exploit_info['vulnerable_ports'].append(port)
|
||||||
|
|
||||||
|
|
||||||
from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter
|
from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter
|
||||||
from infection_monkey.exploit.wmiexec import WmiExploiter
|
from infection_monkey.exploit.wmiexec import WmiExploiter
|
||||||
|
|
|
@ -11,6 +11,8 @@ from infection_monkey.exploit.web_rce import WebRCE
|
||||||
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
|
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
|
||||||
DOWNLOAD_TIMEOUT
|
DOWNLOAD_TIMEOUT
|
||||||
from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
|
from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
|
||||||
|
from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem
|
||||||
|
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@ -27,6 +29,7 @@ class ElasticGroovyExploiter(WebRCE):
|
||||||
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
|
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
|
||||||
|
|
||||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
_EXPLOITED_SERVICE = 'Elastic search'
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(ElasticGroovyExploiter, self).__init__(host)
|
super(ElasticGroovyExploiter, self).__init__(host)
|
||||||
|
@ -58,6 +61,12 @@ class ElasticGroovyExploiter(WebRCE):
|
||||||
return False
|
return False
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
|
def upload_monkey(self, url, commands=None):
|
||||||
|
result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands)
|
||||||
|
if 'windows' in self.host.os['type'] and result:
|
||||||
|
VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING).send()
|
||||||
|
return result
|
||||||
|
|
||||||
def get_results(self, response):
|
def get_results(self, response):
|
||||||
"""
|
"""
|
||||||
Extracts the result data from our attack
|
Extracts the result data from our attack
|
||||||
|
|
|
@ -21,6 +21,7 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
class HadoopExploiter(WebRCE):
|
class HadoopExploiter(WebRCE):
|
||||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
_EXPLOITED_SERVICE = 'Hadoop'
|
||||||
HADOOP_PORTS = [["8088", False]]
|
HADOOP_PORTS = [["8088", False]]
|
||||||
# How long we have our http server open for downloads in seconds
|
# How long we have our http server open for downloads in seconds
|
||||||
DOWNLOAD_TIMEOUT = 60
|
DOWNLOAD_TIMEOUT = 60
|
||||||
|
|
|
@ -16,6 +16,7 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MSSQLExploiter(HostExploiter):
|
class MSSQLExploiter(HostExploiter):
|
||||||
|
|
||||||
|
_EXPLOITED_SERVICE = 'MSSQL'
|
||||||
_TARGET_OS_TYPE = ['windows']
|
_TARGET_OS_TYPE = ['windows']
|
||||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||||
LOGIN_TIMEOUT = 15
|
LOGIN_TIMEOUT = 15
|
||||||
|
|
|
@ -17,6 +17,8 @@ from infection_monkey.network.tools import check_tcp_port
|
||||||
from infection_monkey.exploit.tools import build_monkey_commandline
|
from infection_monkey.exploit.tools import build_monkey_commandline
|
||||||
from infection_monkey.utils import utf_to_ascii
|
from infection_monkey.utils import utf_to_ascii
|
||||||
from common.utils.exploit_enum import ExploitType
|
from common.utils.exploit_enum import ExploitType
|
||||||
|
from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem
|
||||||
|
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -237,6 +239,7 @@ class RdpExploiter(HostExploiter):
|
||||||
|
|
||||||
_TARGET_OS_TYPE = ['windows']
|
_TARGET_OS_TYPE = ['windows']
|
||||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||||
|
_EXPLOITED_SERVICE = 'RDP'
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(RdpExploiter, self).__init__(host)
|
super(RdpExploiter, self).__init__(host)
|
||||||
|
@ -312,6 +315,9 @@ class RdpExploiter(HostExploiter):
|
||||||
client_factory.done_event.wait()
|
client_factory.done_event.wait()
|
||||||
|
|
||||||
if client_factory.success:
|
if client_factory.success:
|
||||||
|
if not self._config.rdp_use_vbs_download:
|
||||||
|
VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING).send()
|
||||||
|
self.add_vuln_port(RDP_PORT)
|
||||||
exploited = True
|
exploited = True
|
||||||
self.report_login_attempt(True, user, password)
|
self.report_login_attempt(True, user, password)
|
||||||
break
|
break
|
||||||
|
|
|
@ -4,7 +4,6 @@ import posixpath
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from os import path
|
|
||||||
|
|
||||||
import impacket.smbconnection
|
import impacket.smbconnection
|
||||||
from impacket.nmb import NetBIOSError
|
from impacket.nmb import NetBIOSError
|
||||||
|
@ -35,6 +34,7 @@ class SambaCryExploiter(HostExploiter):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_TARGET_OS_TYPE = ['linux']
|
_TARGET_OS_TYPE = ['linux']
|
||||||
|
_EXPLOITED_SERVICE = "Samba"
|
||||||
# Name of file which contains the monkey's commandline
|
# Name of file which contains the monkey's commandline
|
||||||
SAMBACRY_COMMANDLINE_FILENAME = "monkey_commandline.txt"
|
SAMBACRY_COMMANDLINE_FILENAME = "monkey_commandline.txt"
|
||||||
# Name of file which contains the runner's result
|
# Name of file which contains the runner's result
|
||||||
|
@ -51,6 +51,8 @@ class SambaCryExploiter(HostExploiter):
|
||||||
SAMBACRY_MONKEY_COPY_FILENAME_32 = "monkey32_2"
|
SAMBACRY_MONKEY_COPY_FILENAME_32 = "monkey32_2"
|
||||||
# Monkey copy filename on share (64 bit)
|
# Monkey copy filename on share (64 bit)
|
||||||
SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2"
|
SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2"
|
||||||
|
# Supported samba port
|
||||||
|
SAMBA_PORT = 445
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(SambaCryExploiter, self).__init__(host)
|
super(SambaCryExploiter, self).__init__(host)
|
||||||
|
@ -80,6 +82,11 @@ class SambaCryExploiter(HostExploiter):
|
||||||
trigger_result is not None, creds['username'], creds['password'], creds['lm_hash'], creds['ntlm_hash'])
|
trigger_result is not None, creds['username'], creds['password'], creds['lm_hash'], creds['ntlm_hash'])
|
||||||
if trigger_result is not None:
|
if trigger_result is not None:
|
||||||
successfully_triggered_shares.append((share, trigger_result))
|
successfully_triggered_shares.append((share, trigger_result))
|
||||||
|
url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % {'username': creds['username'],
|
||||||
|
'host': self.host.ip_addr,
|
||||||
|
'port': self.SAMBA_PORT,
|
||||||
|
'share_name': share}
|
||||||
|
self.add_vuln_url(url)
|
||||||
self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share])
|
self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share])
|
||||||
|
|
||||||
for share, fullpath in successfully_triggered_shares:
|
for share, fullpath in successfully_triggered_shares:
|
||||||
|
@ -89,6 +96,7 @@ class SambaCryExploiter(HostExploiter):
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Shares triggered successfully on host %s: %s" % (
|
"Shares triggered successfully on host %s: %s" % (
|
||||||
self.host.ip_addr, str(successfully_triggered_shares)))
|
self.host.ip_addr, str(successfully_triggered_shares)))
|
||||||
|
self.add_vuln_port(self.SAMBA_PORT)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr)
|
LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr)
|
||||||
|
|
|
@ -26,6 +26,7 @@ class ShellShockExploiter(HostExploiter):
|
||||||
}
|
}
|
||||||
|
|
||||||
_TARGET_OS_TYPE = ['linux']
|
_TARGET_OS_TYPE = ['linux']
|
||||||
|
_EXPLOITED_SERVICE = 'Bash'
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(ShellShockExploiter, self).__init__(host)
|
super(ShellShockExploiter, self).__init__(host)
|
||||||
|
@ -143,7 +144,6 @@ class ShellShockExploiter(HostExploiter):
|
||||||
if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)):
|
if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)):
|
||||||
LOG.info("Log file does not exist, monkey might not have run")
|
LOG.info("Log file does not exist, monkey might not have run")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -17,6 +17,7 @@ LOG = getLogger(__name__)
|
||||||
class SmbExploiter(HostExploiter):
|
class SmbExploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ['windows']
|
_TARGET_OS_TYPE = ['windows']
|
||||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||||
|
_EXPLOITED_SERVICE = 'SMB'
|
||||||
KNOWN_PROTOCOLS = {
|
KNOWN_PROTOCOLS = {
|
||||||
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
|
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
|
||||||
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
|
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
|
||||||
|
@ -68,6 +69,8 @@ class SmbExploiter(HostExploiter):
|
||||||
LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)",
|
LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)",
|
||||||
self.host, user, password, lm_hash, ntlm_hash)
|
self.host, user, password, lm_hash, ntlm_hash)
|
||||||
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
|
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
|
||||||
|
self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
|
||||||
|
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
|
||||||
exploited = True
|
exploited = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -137,4 +140,6 @@ class SmbExploiter(HostExploiter):
|
||||||
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||||
remote_full_path, self.host, cmdline)
|
remote_full_path, self.host, cmdline)
|
||||||
|
|
||||||
|
self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
|
||||||
|
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -22,6 +22,7 @@ TRANSFER_UPDATE_RATE = 15
|
||||||
class SSHExploiter(HostExploiter):
|
class SSHExploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ['linux', None]
|
_TARGET_OS_TYPE = ['linux', None]
|
||||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||||
|
_EXPLOITED_SERVICE = 'SSH'
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(SSHExploiter, self).__init__(host)
|
super(SSHExploiter, self).__init__(host)
|
||||||
|
@ -81,6 +82,7 @@ class SSHExploiter(HostExploiter):
|
||||||
LOG.debug("Successfully logged in %r using SSH (%s : %s)",
|
LOG.debug("Successfully logged in %r using SSH (%s : %s)",
|
||||||
self.host, user, curpass)
|
self.host, user, curpass)
|
||||||
exploited = True
|
exploited = True
|
||||||
|
self.add_vuln_port(port)
|
||||||
self.report_login_attempt(True, user, curpass)
|
self.report_login_attempt(True, user, curpass)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ DOWNLOAD_TIMEOUT = 300
|
||||||
|
|
||||||
class Struts2Exploiter(WebRCE):
|
class Struts2Exploiter(WebRCE):
|
||||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
_EXPLOITED_SERVICE = 'Struts2'
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(Struts2Exploiter, self).__init__(host, None)
|
super(Struts2Exploiter, self).__init__(host, None)
|
||||||
|
|
|
@ -4,19 +4,12 @@
|
||||||
only vulnerable version is "2.3.4"
|
only vulnerable version is "2.3.4"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import StringIO
|
|
||||||
import logging
|
|
||||||
import paramiko
|
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
from common.utils.exploit_enum import ExploitType
|
|
||||||
from infection_monkey.exploit import HostExploiter
|
from infection_monkey.exploit import HostExploiter
|
||||||
from infection_monkey.exploit.tools import build_monkey_commandline
|
from infection_monkey.exploit.tools import build_monkey_commandline
|
||||||
from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
|
from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
|
||||||
from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT
|
from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT
|
||||||
from infection_monkey.network.tools import check_tcp_port
|
|
||||||
from infection_monkey.exploit.web_rce import WebRCE
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
LOG = getLogger(__name__)
|
LOG = getLogger(__name__)
|
||||||
|
@ -33,10 +26,12 @@ USERNAME = b'USER D3fa1t:)' # Ftp Username should end with :) to tri
|
||||||
PASSWORD = b'PASS please' # Ftp Password
|
PASSWORD = b'PASS please' # Ftp Password
|
||||||
FTP_TIME_BUFFER = 1 # In seconds
|
FTP_TIME_BUFFER = 1 # In seconds
|
||||||
|
|
||||||
|
|
||||||
class VSFTPDExploiter(HostExploiter):
|
class VSFTPDExploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ['linux']
|
_TARGET_OS_TYPE = ['linux']
|
||||||
|
_EXPLOITED_SERVICE = 'VSFTPD'
|
||||||
|
|
||||||
def __init__ (self, host):
|
def __init__(self, host):
|
||||||
self._update_timestamp = 0
|
self._update_timestamp = 0
|
||||||
super(VSFTPDExploiter, self).__init__(host)
|
super(VSFTPDExploiter, self).__init__(host)
|
||||||
self.skip_exist = self._config.skip_exploit_if_file_exist
|
self.skip_exist = self._config.skip_exploit_if_file_exist
|
||||||
|
@ -78,7 +73,7 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
ftp_socket.close()
|
ftp_socket.close()
|
||||||
LOG.info('Backdoor Enabled, Now we can run commands')
|
LOG.info('Backdoor Enabled, Now we can run commands')
|
||||||
else:
|
else:
|
||||||
LOG.error('Failed to trigger backdoor on %s' , self.host.ip_addr)
|
LOG.error('Failed to trigger backdoor on %s', self.host.ip_addr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
LOG.info('Attempting to connect to backdoor...')
|
LOG.info('Attempting to connect to backdoor...')
|
||||||
|
@ -96,7 +91,7 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
# command execution is successful
|
# command execution is successful
|
||||||
self.host.os['machine'] = response.lower().strip()
|
self.host.os['machine'] = response.lower().strip()
|
||||||
self.host.os['type'] = 'linux'
|
self.host.os['type'] = 'linux'
|
||||||
else :
|
else:
|
||||||
LOG.info("Failed to execute command uname -m on victim %r ", self.host)
|
LOG.info("Failed to execute command uname -m on victim %r ", self.host)
|
||||||
|
|
||||||
src_path = get_target_monkey(self.host)
|
src_path = get_target_monkey(self.host)
|
||||||
|
@ -136,14 +131,13 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters}
|
run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters}
|
||||||
|
|
||||||
# Set unlimited to memory
|
# Set unlimited to memory
|
||||||
run_monkey = ULIMIT_V + UNLIMITED + run_monkey # we don't have to revert the ulimit because it just applies to the shell obtained by our exploit
|
# we don't have to revert the ulimit because it just applies to the shell obtained by our exploit
|
||||||
|
run_monkey = ULIMIT_V + UNLIMITED + run_monkey
|
||||||
run_monkey = str.encode(str(run_monkey) + '\n')
|
run_monkey = str.encode(str(run_monkey) + '\n')
|
||||||
time.sleep(FTP_TIME_BUFFER)
|
time.sleep(FTP_TIME_BUFFER)
|
||||||
if backdoor_socket.send(run_monkey):
|
if backdoor_socket.send(run_monkey):
|
||||||
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux, self.host, run_monkey)
|
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux,
|
||||||
|
self.host, run_monkey)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ from infection_monkey.exploit import HostExploiter
|
||||||
from infection_monkey.model import *
|
from infection_monkey.model import *
|
||||||
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools
|
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools
|
||||||
from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service
|
from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service
|
||||||
|
from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem
|
||||||
|
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
|
||||||
|
|
||||||
__author__ = 'VakarisZ'
|
__author__ = 'VakarisZ'
|
||||||
|
|
||||||
|
@ -207,13 +209,12 @@ class WebRCE(HostExploiter):
|
||||||
"""
|
"""
|
||||||
for url in urls:
|
for url in urls:
|
||||||
if self.check_if_exploitable(url):
|
if self.check_if_exploitable(url):
|
||||||
|
self.add_vuln_url(url)
|
||||||
self.vulnerable_urls.append(url)
|
self.vulnerable_urls.append(url)
|
||||||
if stop_checking:
|
if stop_checking:
|
||||||
break
|
break
|
||||||
if not self.vulnerable_urls:
|
if not self.vulnerable_urls:
|
||||||
LOG.info("No vulnerable urls found, skipping.")
|
LOG.info("No vulnerable urls found, skipping.")
|
||||||
# We add urls to param used in reporting
|
|
||||||
self._exploit_info['vulnerable_urls'] = self.vulnerable_urls
|
|
||||||
|
|
||||||
def get_host_arch(self, url):
|
def get_host_arch(self, url):
|
||||||
"""
|
"""
|
||||||
|
@ -307,6 +308,7 @@ class WebRCE(HostExploiter):
|
||||||
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
|
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
|
||||||
LOG.info("Powershell not found in host. Using bitsadmin to download.")
|
LOG.info("Powershell not found in host. Using bitsadmin to download.")
|
||||||
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
|
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
|
||||||
|
VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING).send()
|
||||||
resp = self.exploit(url, backup_command)
|
resp = self.exploit(url, backup_command)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ HEADERS = {
|
||||||
|
|
||||||
class WebLogicExploiter(WebRCE):
|
class WebLogicExploiter(WebRCE):
|
||||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
_EXPLOITED_SERVICE = 'Weblogic'
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(WebLogicExploiter, self).__init__(host, {'linux': '/tmp/monkey.sh',
|
super(WebLogicExploiter, self).__init__(host, {'linux': '/tmp/monkey.sh',
|
||||||
|
@ -67,6 +68,7 @@ class WebLogicExploiter(WebRCE):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('[!] Connection Error')
|
print('[!] Connection Error')
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def add_vulnerable_urls(self, urls, stop_checking=False):
|
def add_vulnerable_urls(self, urls, stop_checking=False):
|
||||||
|
|
|
@ -17,6 +17,7 @@ LOG = logging.getLogger(__name__)
|
||||||
class WmiExploiter(HostExploiter):
|
class WmiExploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ['windows']
|
_TARGET_OS_TYPE = ['windows']
|
||||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||||
|
_EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)'
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(WmiExploiter, self).__init__(host)
|
super(WmiExploiter, self).__init__(host)
|
||||||
|
@ -103,6 +104,8 @@ class WmiExploiter(HostExploiter):
|
||||||
if (0 != result.ProcessId) and (0 == result.ReturnValue):
|
if (0 != result.ProcessId) and (0 == result.ReturnValue):
|
||||||
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
|
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
|
||||||
remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
|
remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
|
||||||
|
|
||||||
|
self.add_vuln_port(port='unknown')
|
||||||
success = True
|
success = True
|
||||||
else:
|
else:
|
||||||
LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
|
LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
|
||||||
|
|
|
@ -158,7 +158,7 @@ class InfectionMonkey(object):
|
||||||
finger.get_host_fingerprint(machine)
|
finger.get_host_fingerprint(machine)
|
||||||
|
|
||||||
ControlClient.send_telemetry('scan', {'machine': machine.as_dict(),
|
ControlClient.send_telemetry('scan', {'machine': machine.as_dict(),
|
||||||
})
|
'service_count': len(machine.services)})
|
||||||
|
|
||||||
# skip machines that we've already exploited
|
# skip machines that we've already exploited
|
||||||
if machine in self._exploited_machines:
|
if machine in self._exploited_machines:
|
||||||
|
@ -186,11 +186,9 @@ class InfectionMonkey(object):
|
||||||
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
|
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
|
||||||
if self.try_exploiting(machine, exploiter):
|
if self.try_exploiting(machine, exploiter):
|
||||||
host_exploited = True
|
host_exploited = True
|
||||||
VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send()
|
|
||||||
break
|
break
|
||||||
if not host_exploited:
|
if not host_exploited:
|
||||||
self._fail_exploitation_machines.add(machine)
|
self._fail_exploitation_machines.add(machine)
|
||||||
VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send()
|
|
||||||
if not self._keep_running:
|
if not self._keep_running:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -14,10 +14,20 @@ class HostScanner(object):
|
||||||
class HostFinger(object):
|
class HostFinger(object):
|
||||||
__metaclass__ = ABCMeta
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
|
@abstractproperty
|
||||||
|
def _SCANNED_SERVICE(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def init_service(self, services, service_key, port):
|
||||||
|
services[service_key] = {}
|
||||||
|
services[service_key]['display_name'] = self._SCANNED_SERVICE
|
||||||
|
services[service_key]['port'] = port
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_host_fingerprint(self, host):
|
def get_host_fingerprint(self, host):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
from infection_monkey.network.ping_scanner import PingScanner
|
from infection_monkey.network.ping_scanner import PingScanner
|
||||||
from infection_monkey.network.tcp_scanner import TcpScanner
|
from infection_monkey.network.tcp_scanner import TcpScanner
|
||||||
from infection_monkey.network.smbfinger import SMBFinger
|
from infection_monkey.network.smbfinger import SMBFinger
|
||||||
|
|
|
@ -20,6 +20,7 @@ class ElasticFinger(HostFinger):
|
||||||
"""
|
"""
|
||||||
Fingerprints elastic search clusters, only on port 9200
|
Fingerprints elastic search clusters, only on port 9200
|
||||||
"""
|
"""
|
||||||
|
_SCANNED_SERVICE = 'Elastic search'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
@ -35,7 +36,7 @@ class ElasticFinger(HostFinger):
|
||||||
url = 'http://%s:%s/' % (host.ip_addr, ES_PORT)
|
url = 'http://%s:%s/' % (host.ip_addr, ES_PORT)
|
||||||
with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req:
|
with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req:
|
||||||
data = json.loads(req.text)
|
data = json.loads(req.text)
|
||||||
host.services[ES_SERVICE] = {}
|
self.init_service(host.services, ES_SERVICE, ES_PORT)
|
||||||
host.services[ES_SERVICE]['cluster_name'] = data['cluster_name']
|
host.services[ES_SERVICE]['cluster_name'] = data['cluster_name']
|
||||||
host.services[ES_SERVICE]['name'] = data['name']
|
host.services[ES_SERVICE]['name'] = data['name']
|
||||||
host.services[ES_SERVICE]['version'] = data['version']['number']
|
host.services[ES_SERVICE]['version'] = data['version']['number']
|
||||||
|
|
|
@ -10,6 +10,7 @@ class HTTPFinger(HostFinger):
|
||||||
"""
|
"""
|
||||||
Goal is to recognise HTTP servers, where what we currently care about is apache.
|
Goal is to recognise HTTP servers, where what we currently care about is apache.
|
||||||
"""
|
"""
|
||||||
|
_SCANNED_SERVICE = 'HTTP'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
@ -36,7 +37,7 @@ class HTTPFinger(HostFinger):
|
||||||
with closing(head(url, verify=False, timeout=1)) as req:
|
with closing(head(url, verify=False, timeout=1)) as req:
|
||||||
server = req.headers.get('Server')
|
server = req.headers.get('Server')
|
||||||
ssl = True if 'https://' in url else False
|
ssl = True if 'https://' in url else False
|
||||||
host.services['tcp-' + port[1]] = {}
|
self.init_service(host.services, ('tcp-' + port[1]), port[0])
|
||||||
host.services['tcp-' + port[1]]['name'] = 'http'
|
host.services['tcp-' + port[1]]['name'] = 'http'
|
||||||
host.services['tcp-' + port[1]]['data'] = (server,ssl)
|
host.services['tcp-' + port[1]]['data'] = (server,ssl)
|
||||||
LOG.info("Port %d is open on host %s " % (port[0], host))
|
LOG.info("Port %d is open on host %s " % (port[0], host))
|
||||||
|
|
|
@ -16,7 +16,7 @@ class MSSQLFinger(HostFinger):
|
||||||
SQL_BROWSER_DEFAULT_PORT = 1434
|
SQL_BROWSER_DEFAULT_PORT = 1434
|
||||||
BUFFER_SIZE = 4096
|
BUFFER_SIZE = 4096
|
||||||
TIMEOUT = 5
|
TIMEOUT = 5
|
||||||
SERVICE_NAME = 'MSSQL'
|
_SCANNED_SERVICE = 'MSSQL'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
@ -63,7 +63,7 @@ class MSSQLFinger(HostFinger):
|
||||||
sock.close()
|
sock.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
host.services[self.SERVICE_NAME] = {}
|
self.init_service(host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT)
|
||||||
|
|
||||||
# Loop through the server data
|
# Loop through the server data
|
||||||
instances_list = data[3:].decode().split(';;')
|
instances_list = data[3:].decode().split(';;')
|
||||||
|
@ -71,12 +71,11 @@ class MSSQLFinger(HostFinger):
|
||||||
for instance in instances_list:
|
for instance in instances_list:
|
||||||
instance_info = instance.split(';')
|
instance_info = instance.split(';')
|
||||||
if len(instance_info) > 1:
|
if len(instance_info) > 1:
|
||||||
host.services[self.SERVICE_NAME][instance_info[1]] = {}
|
host.services[self._SCANNED_SERVICE][instance_info[1]] = {}
|
||||||
for i in range(1, len(instance_info), 2):
|
for i in range(1, len(instance_info), 2):
|
||||||
# Each instance's info is nested under its own name, if there are multiple instances
|
# Each instance's info is nested under its own name, if there are multiple instances
|
||||||
# each will appear under its own name
|
# each will appear under its own name
|
||||||
host.services[self.SERVICE_NAME][instance_info[1]][instance_info[i - 1]] = instance_info[i]
|
host.services[self._SCANNED_SERVICE][instance_info[1]][instance_info[i - 1]] = instance_info[i]
|
||||||
|
|
||||||
# Close the socket
|
# Close the socket
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_
|
||||||
|
|
||||||
MYSQL_PORT = 3306
|
MYSQL_PORT = 3306
|
||||||
SQL_SERVICE = 'mysqld-3306'
|
SQL_SERVICE = 'mysqld-3306'
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ class MySQLFinger(HostFinger):
|
||||||
"""
|
"""
|
||||||
Fingerprints mysql databases, only on port 3306
|
Fingerprints mysql databases, only on port 3306
|
||||||
"""
|
"""
|
||||||
|
_SCANNED_SERVICE = 'MySQL'
|
||||||
SOCKET_TIMEOUT = 0.5
|
SOCKET_TIMEOUT = 0.5
|
||||||
HEADER_SIZE = 4 # in bytes
|
HEADER_SIZE = 4 # in bytes
|
||||||
|
|
||||||
|
@ -52,14 +51,13 @@ class MySQLFinger(HostFinger):
|
||||||
|
|
||||||
version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing
|
version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing
|
||||||
version = version[0]
|
version = version[0]
|
||||||
host.services[SQL_SERVICE] = {}
|
self.init_service(host.services, SQL_SERVICE, MYSQL_PORT)
|
||||||
host.services[SQL_SERVICE]['version'] = version
|
host.services[SQL_SERVICE]['version'] = version
|
||||||
version = version.split('-')[0].split('.')
|
version = version.split('-')[0].split('.')
|
||||||
host.services[SQL_SERVICE]['major_version'] = version[0]
|
host.services[SQL_SERVICE]['major_version'] = version[0]
|
||||||
host.services[SQL_SERVICE]['minor_version'] = version[1]
|
host.services[SQL_SERVICE]['minor_version'] = version[1]
|
||||||
host.services[SQL_SERVICE]['build_version'] = version[2]
|
host.services[SQL_SERVICE]['build_version'] = version[2]
|
||||||
thread_id, curpos = struct_unpack_tracker(data, curpos, "<I") # ignore thread id
|
thread_id, curpos = struct_unpack_tracker(data, curpos, "<I") # ignore thread id
|
||||||
|
|
||||||
# protocol parsing taken from
|
# protocol parsing taken from
|
||||||
# https://nmap.org/nsedoc/scripts/mysql-info.html
|
# https://nmap.org/nsedoc/scripts/mysql-info.html
|
||||||
if protocol == 10:
|
if protocol == 10:
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import logging
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from common.network.network_range import *
|
from common.network.network_range import *
|
||||||
from infection_monkey.config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
from infection_monkey.network.info import local_ips, get_interfaces_ranges
|
from infection_monkey.network.info import local_ips, get_interfaces_ranges
|
||||||
from infection_monkey.model import VictimHost
|
from infection_monkey.model import VictimHost
|
||||||
from infection_monkey.network import HostScanner
|
|
||||||
from infection_monkey.network import TcpScanner, PingScanner
|
from infection_monkey.network import TcpScanner, PingScanner
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
@ -66,7 +64,6 @@ class NetworkScanner(object):
|
||||||
def get_victim_machines(self, max_find=5, stop_callback=None):
|
def get_victim_machines(self, max_find=5, stop_callback=None):
|
||||||
"""
|
"""
|
||||||
Finds machines according to the ranges specified in the object
|
Finds machines according to the ranges specified in the object
|
||||||
:param scan_type: A hostscanner class, will be instanced and used to scan for new machines
|
|
||||||
:param max_find: Max number of victims to find regardless of ranges
|
:param max_find: Max number of victims to find regardless of ranges
|
||||||
:param stop_callback: A callback to check at any point if we should stop scanning
|
:param stop_callback: A callback to check at any point if we should stop scanning
|
||||||
:return: yields a sequence of VictimHost instances
|
:return: yields a sequence of VictimHost instances
|
||||||
|
|
|
@ -20,6 +20,9 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PingScanner(HostScanner, HostFinger):
|
class PingScanner(HostScanner, HostFinger):
|
||||||
|
|
||||||
|
_SCANNED_SERVICE = ''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
self._devnull = open(os.devnull, "w")
|
self._devnull = open(os.devnull, "w")
|
||||||
|
|
|
@ -100,6 +100,8 @@ class SMBSessionFingerData(Packet):
|
||||||
|
|
||||||
|
|
||||||
class SMBFinger(HostFinger):
|
class SMBFinger(HostFinger):
|
||||||
|
_SCANNED_SERVICE = 'SMB'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
from infection_monkey.config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
self._config = WormConfiguration
|
self._config = WormConfiguration
|
||||||
|
@ -112,7 +114,7 @@ class SMBFinger(HostFinger):
|
||||||
s.settimeout(0.7)
|
s.settimeout(0.7)
|
||||||
s.connect((host.ip_addr, SMB_PORT))
|
s.connect((host.ip_addr, SMB_PORT))
|
||||||
|
|
||||||
host.services[SMB_SERVICE] = {}
|
self.init_service(host.services, SMB_SERVICE, SMB_PORT)
|
||||||
|
|
||||||
h = SMBHeader(cmd="\x72", flag1="\x18", flag2="\x53\xc8")
|
h = SMBHeader(cmd="\x72", flag1="\x18", flag2="\x53\xc8")
|
||||||
n = SMBNego(data=SMBNegoFingerData())
|
n = SMBNego(data=SMBNegoFingerData())
|
||||||
|
@ -150,7 +152,6 @@ class SMBFinger(HostFinger):
|
||||||
host.os['version'] = os_version
|
host.os['version'] = os_version
|
||||||
else:
|
else:
|
||||||
host.services[SMB_SERVICE]['os-version'] = os_version
|
host.services[SMB_SERVICE]['os-version'] = os_version
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug("Error getting smb fingerprint: %s", exc)
|
LOG.debug("Error getting smb fingerprint: %s", exc)
|
||||||
|
|
|
@ -14,6 +14,8 @@ LINUX_DIST_SSH = ['ubuntu', 'debian']
|
||||||
|
|
||||||
|
|
||||||
class SSHFinger(HostFinger):
|
class SSHFinger(HostFinger):
|
||||||
|
_SCANNED_SERVICE = 'SSH'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE)
|
self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE)
|
||||||
|
@ -38,12 +40,13 @@ class SSHFinger(HostFinger):
|
||||||
banner = data.get('banner', '')
|
banner = data.get('banner', '')
|
||||||
if self._banner_regex.search(banner):
|
if self._banner_regex.search(banner):
|
||||||
self._banner_match(name, host, banner)
|
self._banner_match(name, host, banner)
|
||||||
|
host.services[SSH_SERVICE_DEFAULT]['display_name'] = self._SCANNED_SERVICE
|
||||||
return
|
return
|
||||||
|
|
||||||
is_open, banner = check_tcp_port(host.ip_addr, SSH_PORT, TIMEOUT, True)
|
is_open, banner = check_tcp_port(host.ip_addr, SSH_PORT, TIMEOUT, True)
|
||||||
|
|
||||||
if is_open:
|
if is_open:
|
||||||
host.services[SSH_SERVICE_DEFAULT] = {}
|
self.init_service(host.services, SSH_SERVICE_DEFAULT, SSH_PORT)
|
||||||
|
|
||||||
if banner:
|
if banner:
|
||||||
host.services[SSH_SERVICE_DEFAULT]['banner'] = banner
|
host.services[SSH_SERVICE_DEFAULT]['banner'] = banner
|
||||||
|
|
|
@ -11,6 +11,9 @@ BANNER_READ = 1024
|
||||||
|
|
||||||
|
|
||||||
class TcpScanner(HostScanner, HostFinger):
|
class TcpScanner(HostScanner, HostFinger):
|
||||||
|
|
||||||
|
_SCANNED_SERVICE = 'unknown(TCP)'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
|
||||||
|
@ -33,7 +36,7 @@ class TcpScanner(HostScanner, HostFinger):
|
||||||
self._config.tcp_scan_get_banner)
|
self._config.tcp_scan_get_banner)
|
||||||
for target_port, banner in izip_longest(ports, banners, fillvalue=None):
|
for target_port, banner in izip_longest(ports, banners, fillvalue=None):
|
||||||
service = tcp_port_to_service(target_port)
|
service = tcp_port_to_service(target_port)
|
||||||
host.services[service] = {}
|
self.init_service(host.services, service, target_port)
|
||||||
if banner:
|
if banner:
|
||||||
host.services[service]['banner'] = banner
|
host.services[service]['banner'] = banner
|
||||||
if only_one_port:
|
if only_one_port:
|
||||||
|
|
|
@ -16,7 +16,7 @@ class AttackTelem(object):
|
||||||
Default ATT&CK telemetry constructor
|
Default ATT&CK telemetry constructor
|
||||||
:param technique: Technique ID. E.g. T111
|
:param technique: Technique ID. E.g. T111
|
||||||
:param status: int from ScanStatus Enum
|
:param status: int from ScanStatus Enum
|
||||||
:param data: Other data relevant to the attack technique
|
:param data: Dictionary of other relevant info. E.g. {'brute_force_blocked': True}
|
||||||
"""
|
"""
|
||||||
self.technique = technique
|
self.technique = technique
|
||||||
self.result = status
|
self.result = status
|
||||||
|
|
|
@ -14,5 +14,5 @@ class VictimHostTelem(AttackTelem):
|
||||||
:param data: Other data relevant to the attack technique
|
:param data: Other data relevant to the attack technique
|
||||||
"""
|
"""
|
||||||
super(VictimHostTelem, self).__init__(technique, status, data)
|
super(VictimHostTelem, self).__init__(technique, status, data)
|
||||||
victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr}
|
victim_host = {'domain_name': machine.domain_name, 'ip_addr': machine.ip_addr}
|
||||||
self.data.update({'machine': victim_host})
|
self.data.update({'machine': victim_host})
|
||||||
|
|
|
@ -2,7 +2,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import struct
|
import struct
|
||||||
|
import datetime
|
||||||
from infection_monkey.config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,9 @@ from monkey_island.cc.services.database import Database
|
||||||
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
||||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||||
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
||||||
from monkey_island.cc.resources.attack_telem import AttackTelem
|
from monkey_island.cc.resources.attack.attack_telem import AttackTelem
|
||||||
from monkey_island.cc.resources.attack_config import AttackConfiguration
|
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
||||||
|
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
@ -133,6 +134,7 @@ def init_api_resources(api):
|
||||||
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
||||||
api.add_resource(AttackConfiguration, '/api/attack')
|
api.add_resource(AttackConfiguration, '/api/attack')
|
||||||
api.add_resource(AttackTelem, '/api/attack/<string:technique>')
|
api.add_resource(AttackTelem, '/api/attack/<string:technique>')
|
||||||
|
api.add_resource(AttackReport, '/api/attack/report')
|
||||||
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,3 +25,14 @@ def is_db_server_up(mongo_url):
|
||||||
return True
|
return True
|
||||||
except ServerSelectionTimeoutError:
|
except ServerSelectionTimeoutError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_version(mongo_url):
|
||||||
|
"""
|
||||||
|
Return the mongo db version
|
||||||
|
:param mongo_url: Which mongo to check.
|
||||||
|
:return: version as a tuple (e.g. `(u'4', u'0', u'8')`)
|
||||||
|
"""
|
||||||
|
client = MongoClient(mongo_url, serverSelectionTimeoutMS=100)
|
||||||
|
server_version = tuple(client.server_info()['version'].split('.'))
|
||||||
|
return server_version
|
||||||
|
|
|
@ -6,6 +6,8 @@ import sys
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
MINIMUM_MONGO_DB_VERSION_REQUIRED = "3.6.0"
|
||||||
|
|
||||||
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
if BASE_PATH not in sys.path:
|
if BASE_PATH not in sys.path:
|
||||||
|
@ -22,7 +24,7 @@ from monkey_island.cc.app import init_app
|
||||||
from monkey_island.cc.exporter_init import populate_exporter_list
|
from monkey_island.cc.exporter_init import populate_exporter_list
|
||||||
from monkey_island.cc.utils import local_ip_addresses
|
from monkey_island.cc.utils import local_ip_addresses
|
||||||
from monkey_island.cc.environment.environment import env
|
from monkey_island.cc.environment.environment import env
|
||||||
from monkey_island.cc.database import is_db_server_up
|
from monkey_island.cc.database import is_db_server_up, get_db_version
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -31,10 +33,8 @@ def main():
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
|
|
||||||
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
|
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
|
||||||
|
wait_for_mongo_db_server(mongo_url)
|
||||||
while not is_db_server_up(mongo_url):
|
assert_mongo_db_version(mongo_url)
|
||||||
logger.info('Waiting for MongoDB server')
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
populate_exporter_list()
|
populate_exporter_list()
|
||||||
app = init_app(mongo_url)
|
app = init_app(mongo_url)
|
||||||
|
@ -55,5 +55,27 @@ def main():
|
||||||
IOLoop.instance().start()
|
IOLoop.instance().start()
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_mongo_db_server(mongo_url):
|
||||||
|
while not is_db_server_up(mongo_url):
|
||||||
|
logger.info('Waiting for MongoDB server on {0}'.format(mongo_url))
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_mongo_db_version(mongo_url):
|
||||||
|
"""
|
||||||
|
Checks if the mongodb version is new enough for running the app.
|
||||||
|
If the DB is too old, quits.
|
||||||
|
:param mongo_url: URL to the mongo the Island will use
|
||||||
|
"""
|
||||||
|
required_version = tuple(MINIMUM_MONGO_DB_VERSION_REQUIRED.split("."))
|
||||||
|
server_version = get_db_version(mongo_url)
|
||||||
|
if server_version < required_version:
|
||||||
|
logger.error(
|
||||||
|
'Mongo DB version too old. {0} is required, but got {1}'.format(str(required_version), str(server_version)))
|
||||||
|
sys.exit(-1)
|
||||||
|
else:
|
||||||
|
logger.info('Mongo DB version OK. Got {0}'.format(str(server_version)))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'VakarisZ'
|
|
@ -0,0 +1,13 @@
|
||||||
|
import flask_restful
|
||||||
|
from flask import jsonify
|
||||||
|
from monkey_island.cc.auth import jwt_required
|
||||||
|
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
|
||||||
|
class AttackReport(flask_restful.Resource):
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
|
def get(self):
|
||||||
|
return jsonify(AttackReportService.get_latest_report()['techniques'])
|
|
@ -8,6 +8,7 @@ from monkey_island.cc.auth import jwt_required
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.services.node import NodeService
|
from monkey_island.cc.services.node import NodeService
|
||||||
from monkey_island.cc.services.report import ReportService
|
from monkey_island.cc.services.report import ReportService
|
||||||
|
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
||||||
from monkey_island.cc.utils import local_ip_addresses
|
from monkey_island.cc.utils import local_ip_addresses
|
||||||
from monkey_island.cc.services.database import Database
|
from monkey_island.cc.services.database import Database
|
||||||
|
|
||||||
|
@ -58,5 +59,7 @@ class Root(flask_restful.Resource):
|
||||||
else:
|
else:
|
||||||
if is_any_exists:
|
if is_any_exists:
|
||||||
ReportService.get_report()
|
ReportService.get_report()
|
||||||
|
AttackReportService.get_latest_report()
|
||||||
report_done = ReportService.is_report_generated()
|
report_done = ReportService.is_report_generated()
|
||||||
return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done)
|
return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done,
|
||||||
|
report_done=report_done)
|
||||||
|
|
|
@ -18,6 +18,20 @@ class AttackConfig(object):
|
||||||
config = mongo.db.attack.find_one({'name': 'newconfig'})
|
config = mongo.db.attack.find_one({'name': 'newconfig'})
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_technique(technique_id):
|
||||||
|
"""
|
||||||
|
Gets technique by id
|
||||||
|
:param technique_id: E.g. T1210
|
||||||
|
:return: Technique object or None if technique is not found
|
||||||
|
"""
|
||||||
|
attack_config = AttackConfig.get_config()
|
||||||
|
for key, attack_type in attack_config['properties'].items():
|
||||||
|
for key, technique in attack_type['properties'].items():
|
||||||
|
if key == technique_id:
|
||||||
|
return technique
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_schema():
|
def get_config_schema():
|
||||||
return SCHEMA
|
return SCHEMA
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import logging
|
||||||
|
from monkey_island.cc.services.attack.technique_reports import T1210, T1197
|
||||||
|
from monkey_island.cc.services.attack.attack_telem import AttackTelemService
|
||||||
|
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||||
|
from monkey_island.cc.database import mongo
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TECHNIQUES = {'T1210': T1210.T1210,
|
||||||
|
'T1197': T1197.T1197}
|
||||||
|
|
||||||
|
REPORT_NAME = 'new_report'
|
||||||
|
|
||||||
|
|
||||||
|
class AttackReportService:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_new_report():
|
||||||
|
"""
|
||||||
|
Generates new report based on telemetries, replaces old report in db with new one.
|
||||||
|
:return: Report object
|
||||||
|
"""
|
||||||
|
report = {'techniques': {}, 'meta': AttackTelemService.get_latest_telem(), 'name': REPORT_NAME}
|
||||||
|
for tech_id, value in AttackConfig.get_technique_values().items():
|
||||||
|
if value:
|
||||||
|
try:
|
||||||
|
report['techniques'].update({tech_id: TECHNIQUES[tech_id].get_report_data()})
|
||||||
|
except KeyError as e:
|
||||||
|
LOG.error("Attack technique does not have it's report component added "
|
||||||
|
"to attack report service. %s" % e)
|
||||||
|
mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True)
|
||||||
|
return report
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_latest_report():
|
||||||
|
"""
|
||||||
|
Gets latest report (by retrieving it from db or generating a new one).
|
||||||
|
:return: report dict.
|
||||||
|
"""
|
||||||
|
if AttackReportService.is_report_generated():
|
||||||
|
telem_time = AttackTelemService.get_latest_telem()
|
||||||
|
latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME})
|
||||||
|
if telem_time and latest_report['meta'] and telem_time['time'] == latest_report['meta']['time']:
|
||||||
|
return latest_report
|
||||||
|
return AttackReportService.generate_new_report()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_report_generated():
|
||||||
|
"""
|
||||||
|
Checks if report is generated
|
||||||
|
:return: True if report exists, False otherwise
|
||||||
|
"""
|
||||||
|
generated_report = mongo.db.attack_report.find_one({})
|
||||||
|
return generated_report is not None
|
|
@ -75,7 +75,7 @@ SCHEMA = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"T1197": {
|
"T1197": {
|
||||||
"title": "T1197 Bits jobs",
|
"title": "T1197 BITS jobs",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"value": True,
|
"value": True,
|
||||||
"necessary": True,
|
"necessary": True,
|
||||||
|
|
|
@ -22,3 +22,9 @@ class AttackTelemService(object):
|
||||||
"""
|
"""
|
||||||
data.update({'technique': technique})
|
data.update({'technique': technique})
|
||||||
mongo.db.attack_results.insert(data)
|
mongo.db.attack_results.insert(data)
|
||||||
|
mongo.db.attack_results.update({'name': 'latest'}, {'name': 'latest', 'time': data['time']}, upsert=True)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_latest_telem():
|
||||||
|
return mongo.db.attack_results.find_one({'name': 'latest'})
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
from monkey_island.cc.database import mongo
|
||||||
|
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
|
||||||
|
class T1197(AttackTechnique):
|
||||||
|
tech_id = "T1197"
|
||||||
|
unscanned_msg = "Monkey didn't try to use any bits jobs."
|
||||||
|
scanned_msg = "Monkey tried to use bits jobs but failed."
|
||||||
|
used_msg = "Monkey successfully used bits jobs at least once in the network."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_report_data():
|
||||||
|
data = T1197.get_tech_base_data(T1197)
|
||||||
|
bits_results = mongo.db.attack_results.aggregate([{'$match': {'technique': T1197.tech_id}},
|
||||||
|
{'$group': {'_id': {'ip_addr': '$machine.ip_addr', 'usage': '$usage'},
|
||||||
|
'ip_addr': {'$first': '$machine.ip_addr'},
|
||||||
|
'domain_name': {'$first': '$machine.domain_name'},
|
||||||
|
'usage': {'$first': '$usage'},
|
||||||
|
'time': {'$first': '$time'}}
|
||||||
|
}])
|
||||||
|
bits_results = list(bits_results)
|
||||||
|
data.update({'bits_jobs': bits_results})
|
||||||
|
return data
|
|
@ -0,0 +1,54 @@
|
||||||
|
from common.utils.attack_utils import ScanStatus
|
||||||
|
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
|
||||||
|
from monkey_island.cc.database import mongo
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
TECHNIQUE = "T1210"
|
||||||
|
MESSAGES = {
|
||||||
|
'unscanned': "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?",
|
||||||
|
'scanned': "Monkey scanned for remote services on the network, but couldn't exploit any of them.",
|
||||||
|
'used': "Monkey scanned for remote services and exploited some on the network."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class T1210(AttackTechnique):
|
||||||
|
|
||||||
|
tech_id = "T1210"
|
||||||
|
unscanned_msg = "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?"
|
||||||
|
scanned_msg = "Monkey scanned for remote services on the network, but couldn't exploit any of them."
|
||||||
|
used_msg = "Monkey scanned for remote services and exploited some on the network."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_report_data():
|
||||||
|
data = {'title': T1210.technique_title(TECHNIQUE)}
|
||||||
|
scanned_services = T1210.get_scanned_services()
|
||||||
|
exploited_services = T1210.get_exploited_services()
|
||||||
|
if exploited_services:
|
||||||
|
data.update({'status': ScanStatus.USED.name, 'message': T1210.used_msg})
|
||||||
|
elif scanned_services:
|
||||||
|
data.update({'status': ScanStatus.SCANNED.name, 'message': T1210.scanned_msg})
|
||||||
|
else:
|
||||||
|
data.update({'status': ScanStatus.UNSCANNED.name, 'message': T1210.unscanned_msg})
|
||||||
|
data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
|
||||||
|
return data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_scanned_services():
|
||||||
|
results = mongo.db.telemetry.aggregate([{'$match': {'telem_type': 'scan'}},
|
||||||
|
{'$sort': {'data.service_count': -1}},
|
||||||
|
{'$group': {
|
||||||
|
'_id': {'ip_addr': '$data.machine.ip_addr'},
|
||||||
|
'machine': {'$first': '$data.machine'},
|
||||||
|
'time': {'$first': '$timestamp'}}}])
|
||||||
|
return list(results)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_exploited_services():
|
||||||
|
results = mongo.db.telemetry.aggregate([{'$match': {'telem_type': 'exploit', 'data.result': True}},
|
||||||
|
{'$group': {
|
||||||
|
'_id': {'ip_addr': '$data.machine.ip_addr'},
|
||||||
|
'service': {'$first': '$data.info'},
|
||||||
|
'machine': {'$first': '$data.machine'},
|
||||||
|
'time': {'$first': '$timestamp'}}}])
|
||||||
|
return list(results)
|
|
@ -0,0 +1,88 @@
|
||||||
|
import abc
|
||||||
|
|
||||||
|
from monkey_island.cc.database import mongo
|
||||||
|
from common.utils.attack_utils import ScanStatus
|
||||||
|
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||||
|
from common.utils.code_utils import abstractstatic
|
||||||
|
|
||||||
|
|
||||||
|
class AttackTechnique(object):
|
||||||
|
""" Abstract class for ATT&CK report components """
|
||||||
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def unscanned_msg(self):
|
||||||
|
"""
|
||||||
|
:return: Message that will be displayed in case attack technique was not scanned.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def scanned_msg(self):
|
||||||
|
"""
|
||||||
|
:return: Message that will be displayed in case attack technique was scanned.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def used_msg(self):
|
||||||
|
"""
|
||||||
|
:return: Message that will be displayed in case attack technique was used by the scanner.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def tech_id(self):
|
||||||
|
"""
|
||||||
|
:return: Message that will be displayed in case of attack technique not being scanned.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractstatic
|
||||||
|
def get_report_data():
|
||||||
|
"""
|
||||||
|
:return: Report data aggregated from the database.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def technique_status(technique):
|
||||||
|
"""
|
||||||
|
Gets the status of a certain attack technique.
|
||||||
|
:param technique: technique's id.
|
||||||
|
:return: ScanStatus Enum object
|
||||||
|
"""
|
||||||
|
if mongo.db.attack_results.find_one({'status': ScanStatus.USED.value, 'technique': technique}):
|
||||||
|
return ScanStatus.USED
|
||||||
|
elif mongo.db.attack_results.find_one({'status': ScanStatus.SCANNED.value, 'technique': technique}):
|
||||||
|
return ScanStatus.SCANNED
|
||||||
|
else:
|
||||||
|
return ScanStatus.UNSCANNED
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def technique_title(technique):
|
||||||
|
"""
|
||||||
|
:param technique: Technique's id. E.g. T1110
|
||||||
|
:return: techniques title. E.g. "T1110 Brute force"
|
||||||
|
"""
|
||||||
|
return AttackConfig.get_technique(technique)['title']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_tech_base_data(technique):
|
||||||
|
"""
|
||||||
|
Gathers basic attack technique data into a dict.
|
||||||
|
:param technique: Technique's id. E.g. T1110
|
||||||
|
:return: dict E.g. {'message': 'Brute force used', 'status': 'Used', 'title': 'T1110 Brute force'}
|
||||||
|
"""
|
||||||
|
data = {}
|
||||||
|
status = AttackTechnique.technique_status(technique.tech_id)
|
||||||
|
title = AttackTechnique.technique_title(technique.tech_id)
|
||||||
|
data.update({'status': status.name, 'title': title})
|
||||||
|
if status == ScanStatus.UNSCANNED:
|
||||||
|
data.update({'message': technique.unscanned_msg})
|
||||||
|
elif status == ScanStatus.SCANNED:
|
||||||
|
data.update({'message': technique.scanned_msg})
|
||||||
|
else:
|
||||||
|
data.update({'message': technique.used_msg})
|
||||||
|
return data
|
File diff suppressed because it is too large
Load Diff
|
@ -65,6 +65,8 @@
|
||||||
"webpack-dev-server": "^3.1.9"
|
"webpack-dev-server": "^3.1.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@kunukn/react-collapse": "^1.0.5",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"core-js": "^2.5.7",
|
"core-js": "^2.5.7",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React from 'react';
|
||||||
|
import '../../../styles/Collapse.scss'
|
||||||
|
import ReactTable from "react-table";
|
||||||
|
|
||||||
|
|
||||||
|
class T1210 extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.columns = [ {Header: 'Machine',
|
||||||
|
id: 'machine', accessor: x => T1210.renderMachine(x),
|
||||||
|
style: { 'whiteSpace': 'unset' },
|
||||||
|
width: 200},
|
||||||
|
{Header: 'Time',
|
||||||
|
id: 'time', accessor: x => x.time,
|
||||||
|
style: { 'whiteSpace': 'unset' },
|
||||||
|
width: 170},
|
||||||
|
{Header: 'Usage',
|
||||||
|
id: 'usage', accessor: x => x.usage,
|
||||||
|
style: { 'whiteSpace': 'unset' }}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
static renderMachine(val){
|
||||||
|
return (
|
||||||
|
<span>{val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}</span>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
renderExploitedMachines(){
|
||||||
|
if (this.props.data.bits_jobs.length === 0){
|
||||||
|
return (<div />)
|
||||||
|
} else {
|
||||||
|
return (<ReactTable
|
||||||
|
columns={this.columns}
|
||||||
|
data={this.props.data.bits_jobs}
|
||||||
|
showPagination={false}
|
||||||
|
defaultPageSize={this.props.data.bits_jobs.length}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="data-table-container">
|
||||||
|
<div>
|
||||||
|
<div>{this.props.data.message}</div>
|
||||||
|
{this.props.data.bits_jobs.length > 0 ? <div>BITS jobs were used in these machines: </div> : ''}
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
{this.renderExploitedMachines()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default T1210;
|
|
@ -0,0 +1,100 @@
|
||||||
|
import React from 'react';
|
||||||
|
import '../../../styles/Collapse.scss'
|
||||||
|
import ReactTable from "react-table";
|
||||||
|
|
||||||
|
|
||||||
|
class T1210 extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getScanColumns() {
|
||||||
|
return ([{
|
||||||
|
columns: [
|
||||||
|
{Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine),
|
||||||
|
style: { 'whiteSpace': 'unset' }, width: 200},
|
||||||
|
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170},
|
||||||
|
{Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }},
|
||||||
|
{Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }}
|
||||||
|
]
|
||||||
|
}])}
|
||||||
|
|
||||||
|
static getExploitColumns() {
|
||||||
|
return ([{
|
||||||
|
columns: [
|
||||||
|
{Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine),
|
||||||
|
style: { 'whiteSpace': 'unset' }, width: 200},
|
||||||
|
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170},
|
||||||
|
{Header: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' }},
|
||||||
|
{Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }}
|
||||||
|
]
|
||||||
|
}])};
|
||||||
|
|
||||||
|
static renderMachine(val){
|
||||||
|
return (
|
||||||
|
<span>{val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}</span>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
static renderEndpoint(val){
|
||||||
|
return (
|
||||||
|
<span>{(val.vulnerable_urls.length !== 0 ? val.vulnerable_urls[0] : val.vulnerable_ports[0])}</span>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
static formatScanned(data){
|
||||||
|
let result = [];
|
||||||
|
for(let service in data.machine.services){
|
||||||
|
let scanned_service = {'machine': data.machine,
|
||||||
|
'time': data.time,
|
||||||
|
'service': {'port': [data.machine.services[service].port],
|
||||||
|
'display_name': data.machine.services[service].display_name}};
|
||||||
|
result.push(scanned_service)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
};
|
||||||
|
|
||||||
|
renderScannedServices(data) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<br/>
|
||||||
|
<div>Found services: </div>
|
||||||
|
<ReactTable
|
||||||
|
columns={T1210.getScanColumns()}
|
||||||
|
data={data}
|
||||||
|
showPagination={false}
|
||||||
|
defaultPageSize={data.length}
|
||||||
|
/>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderExploitedServices(data) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<br/>
|
||||||
|
<div>Exploited services: </div>
|
||||||
|
<ReactTable
|
||||||
|
columns={T1210.getExploitColumns()}
|
||||||
|
data={data}
|
||||||
|
showPagination={false}
|
||||||
|
defaultPageSize={data.length}
|
||||||
|
/>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let scanned_services = this.props.data.scanned_services.map(T1210.formatScanned).flat();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>{this.props.data.message}</div>
|
||||||
|
{scanned_services.length > 0 ?
|
||||||
|
this.renderScannedServices(scanned_services) : ''}
|
||||||
|
{this.props.data.exploited_services.length > 0 ?
|
||||||
|
this.renderExploitedServices(this.props.data.exploited_services) : ''}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default T1210;
|
|
@ -11,6 +11,7 @@ import {Line} from 'rc-progress';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import PassTheHashMapPageComponent from "./PassTheHashMapPage";
|
import PassTheHashMapPageComponent from "./PassTheHashMapPage";
|
||||||
import StrongUsers from "components/report-components/StrongUsers";
|
import StrongUsers from "components/report-components/StrongUsers";
|
||||||
|
import AttackReport from "components/report-components/AttackReport";
|
||||||
|
|
||||||
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
||||||
let monkeyLogoImage = require('../../images/monkey-icon.svg');
|
let monkeyLogoImage = require('../../images/monkey-icon.svg');
|
||||||
|
@ -141,6 +142,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
{this.generateReportFindingsSection()}
|
{this.generateReportFindingsSection()}
|
||||||
{this.generateReportRecommendationsSection()}
|
{this.generateReportRecommendationsSection()}
|
||||||
{this.generateReportGlanceSection()}
|
{this.generateReportGlanceSection()}
|
||||||
|
{this.generateAttackSection()}
|
||||||
{this.generateReportFooter()}
|
{this.generateReportFooter()}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center no-print" style={{marginTop: '20px'}}>
|
<div className="text-center no-print" style={{marginTop: '20px'}}>
|
||||||
|
@ -509,6 +511,21 @@ class ReportPageComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateAttackSection() {
|
||||||
|
return (<div id="attack">
|
||||||
|
<h3>
|
||||||
|
ATT&CK report
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
This report shows information about ATT&CK techniques used by Infection Monkey.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<AttackReport/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
|
||||||
generateReportFooter() {
|
generateReportFooter() {
|
||||||
return (
|
return (
|
||||||
<div id="footer" className="text-center" style={{marginTop: '20px'}}>
|
<div id="footer" className="text-center" style={{marginTop: '20px'}}>
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Col} from 'react-bootstrap';
|
||||||
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
|
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
import Collapse from '@kunukn/react-collapse';
|
||||||
|
import T1210 from '../attack/techniques/T1210';
|
||||||
|
import T1197 from '../attack/techniques/T1197';
|
||||||
|
import '../../styles/Collapse.scss'
|
||||||
|
|
||||||
|
const tech_components = {
|
||||||
|
'T1210': T1210,
|
||||||
|
'T1197': T1197
|
||||||
|
};
|
||||||
|
|
||||||
|
const classNames = require('classnames');
|
||||||
|
|
||||||
|
class AttackReportPageComponent extends AuthComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
report: false,
|
||||||
|
allMonkeysAreDead: false,
|
||||||
|
runStarted: true,
|
||||||
|
collapseOpen: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.updateMonkeysRunning().then(res => this.getReportFromServer(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMonkeysRunning = () => {
|
||||||
|
return this.authFetch('/api')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
// This check is used to prevent unnecessary re-rendering
|
||||||
|
this.setState({
|
||||||
|
allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']),
|
||||||
|
runStarted: res['completed_steps']['run_monkey']
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getReportFromServer(res) {
|
||||||
|
if (res['completed_steps']['run_monkey']) {
|
||||||
|
this.authFetch('/api/attack/report')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
this.setState({
|
||||||
|
report: res
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggle = technique =>
|
||||||
|
this.setState(state => ({ collapseOpen: state.collapseOpen === technique ? null : technique }));
|
||||||
|
|
||||||
|
getComponentClass(tech_id){
|
||||||
|
switch (this.state.report[tech_id].status) {
|
||||||
|
case 'SCANNED':
|
||||||
|
return 'collapse-info';
|
||||||
|
case 'USED':
|
||||||
|
return 'collapse-danger';
|
||||||
|
default:
|
||||||
|
return 'collapse-default';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTechniqueCollapse(tech_id){
|
||||||
|
return (
|
||||||
|
<div key={tech_id} className={classNames("collapse-item", { "item--active": this.state.collapseOpen === tech_id })}>
|
||||||
|
<button className={classNames("btn-collapse", this.getComponentClass(tech_id))} onClick={() => this.onToggle(tech_id)}>
|
||||||
|
<span>{this.state.report[tech_id].title}</span>
|
||||||
|
<span>
|
||||||
|
<i className={classNames("fa", this.state.collapseOpen === tech_id ? "fa-chevron-down" : "fa-chevron-up")}></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<Collapse
|
||||||
|
className="collapse-comp"
|
||||||
|
isOpen={this.state.collapseOpen === tech_id}
|
||||||
|
onChange={({ collapseState }) => {
|
||||||
|
this.setState({ tech_id: collapseState });
|
||||||
|
}}
|
||||||
|
onInit={({ collapseState }) => {
|
||||||
|
this.setState({ tech_id: collapseState });
|
||||||
|
}}
|
||||||
|
render={collapseState => this.createTechniqueContent(collapseState, tech_id)}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createTechniqueContent(collapseState, technique) {
|
||||||
|
const TechniqueComponent = tech_components[technique];
|
||||||
|
return (
|
||||||
|
<div className={`content ${collapseState}`}>
|
||||||
|
<TechniqueComponent data={this.state.report[technique]} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLegend() {
|
||||||
|
return( <div id="header" className="row justify-content-between attack-legend">
|
||||||
|
<Col xs={4}>
|
||||||
|
<i className="fa fa-circle icon-default"></i>
|
||||||
|
<span> - Unscanned</span>
|
||||||
|
</Col>
|
||||||
|
<Col xs={4}>
|
||||||
|
<i className="fa fa-circle icon-info"></i>
|
||||||
|
<span> - Scanned</span>
|
||||||
|
</Col>
|
||||||
|
<Col xs={4}>
|
||||||
|
<i className="fa fa-circle icon-danger"></i>
|
||||||
|
<span> - Used</span>
|
||||||
|
</Col>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
generateReportContent(){
|
||||||
|
let content = [];
|
||||||
|
Object.keys(this.state.report).forEach((tech_id) => {
|
||||||
|
content.push(this.getTechniqueCollapse(tech_id))
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderLegend()}
|
||||||
|
<section className="app">{content}</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let content;
|
||||||
|
if (! this.state.runStarted)
|
||||||
|
{
|
||||||
|
content =
|
||||||
|
<p className="alert alert-warning">
|
||||||
|
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
|
||||||
|
You have to run a monkey before generating a report!
|
||||||
|
</p>;
|
||||||
|
} else if (this.state.report === false){
|
||||||
|
content = (<h1>Generating Report...</h1>);
|
||||||
|
} else if (Object.keys(this.state.report).length === 0) {
|
||||||
|
if (this.state.runStarted) {
|
||||||
|
content = (<h1>No techniques were scanned</h1>);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = this.generateReportContent();
|
||||||
|
}
|
||||||
|
return ( <div> {content} </div> );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AttackReportPageComponent;
|
|
@ -520,11 +520,27 @@ body {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Attack config page */
|
/* Attack pages */
|
||||||
.attack-matrix .messages {
|
.attack-matrix .messages {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-info {
|
||||||
|
color: #ade3eb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-warning {
|
||||||
|
color: #f0ad4e !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-danger {
|
||||||
|
color: #d9acac !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-default {
|
||||||
|
color: #e0ddde !important;
|
||||||
|
}
|
||||||
|
|
||||||
.attack-legend {
|
.attack-legend {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
@ -539,3 +555,9 @@ body {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attack-report.footer-text{
|
||||||
|
text-align: right;
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
$transition: 500ms cubic-bezier(0.4, 0.1, 0.1, 0.5);
|
||||||
|
|
||||||
|
$danger-color: #d9acac;
|
||||||
|
$info-color: #ade3eb;
|
||||||
|
$default-color: #e0ddde;
|
||||||
|
|
||||||
|
.collapse-item button {
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: none;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-item button span:first-child{
|
||||||
|
text-align:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-item button {
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 2px 6px #ccc;
|
||||||
|
border: none;
|
||||||
|
transition: background-color $transition;
|
||||||
|
display: flex;
|
||||||
|
font-family: inherit;
|
||||||
|
> span {
|
||||||
|
display: inline-block;
|
||||||
|
flex: 4;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-danger {
|
||||||
|
background-color: $danger-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-info {
|
||||||
|
background-color: $info-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-default {
|
||||||
|
background-color: $default-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-item {
|
||||||
|
padding: 0.5rem;
|
||||||
|
&--active {
|
||||||
|
.btn-collapse {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-item .collapse-comp {
|
||||||
|
padding: 0 7px 7px 7px;
|
||||||
|
border: 2px solid rgb(232, 228, 228);
|
||||||
|
border-top: 0;
|
||||||
|
display:block !important;
|
||||||
|
transition: height $transition;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-item .content {
|
||||||
|
padding: 2rem 0;
|
||||||
|
transition: transform $transition;
|
||||||
|
will-change: transform;
|
||||||
|
$offset: 10px;
|
||||||
|
|
||||||
|
&.collapsing {
|
||||||
|
transform: translateY(-$offset);
|
||||||
|
}
|
||||||
|
&.collapse-comp {
|
||||||
|
transform: translateY(-$offset);
|
||||||
|
}
|
||||||
|
&.expanding {
|
||||||
|
transform: translateX(0px);
|
||||||
|
}
|
||||||
|
&.expanded {
|
||||||
|
transform: translateX(0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-item .text {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-item .state {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 6em;
|
||||||
|
}
|
Loading…
Reference in New Issue