Merge remote-tracking branch 'upstream/develop' into attack_exfiltration_c2_channel

# Conflicts:
#	monkey/monkey_island/cc/models/monkey.py
This commit is contained in:
VakarisZ 2019-08-19 17:55:10 +03:00
commit 4ae92af37d
46 changed files with 297 additions and 154 deletions

View File

@ -3,12 +3,6 @@ language: python
cache: pip cache: pip
python: python:
- 2.7 - 2.7
- 3.6
matrix:
include:
- python: 3.7
dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069)
sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069)
install: install:
#- pip install -r requirements.txt #- pip install -r requirements.txt
- pip install flake8 # pytest # add another testing frameworks later - pip install flake8 # pytest # add another testing frameworks later

View File

@ -13,9 +13,10 @@ Don't forget to add python to PATH or do so while installing it via this script.
## Linux ## Linux
You must have root permissions, but there is no need to run the script as root.<br> You must have root permissions, but don't run the script as root.<br>
Launch deploy_linux.sh from scripts directory.<br> Launch deploy_linux.sh from scripts directory.<br>
First argument is an empty directory (script can create one) and second is branch you want to clone. First argument should be an empty directory (script can create one, default is ./infection_monkey) and second is the branch you want to clone (develop by default).
Choose a directory where you have all the relevant permissions, for e.g. /home/your_username
Example usages:<br> Example usages:<br>
./deploy_linux.sh (deploys under ./infection_monkey)<br> ./deploy_linux.sh (deploys under ./infection_monkey)<br>
./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)<br> ./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)<br>

View File

@ -10,6 +10,20 @@ class ScanStatus(Enum):
USED = 2 USED = 2
class UsageEnum(Enum):
SMB = {ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.",
ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."}
MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."}
MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."}
DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
SINGLETON_WINAPI = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.",
ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
" for monkey process wasn't successful."}
DROPPER_WINAPI = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
# Dict that describes what BITS job was used for # Dict that describes what BITS job was used for
BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system." BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system."

View File

@ -1,3 +1,4 @@
import hashlib
import os import os
import json import json
import sys import sys
@ -13,9 +14,11 @@ GUID = str(uuid.getnode())
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin') EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list"]
HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
class Configuration(object): class Configuration(object):
def from_kv(self, formatted_data): def from_kv(self, formatted_data):
# now we won't work at <2.7 for sure # now we won't work at <2.7 for sure
network_import = importlib.import_module('infection_monkey.network') network_import = importlib.import_module('infection_monkey.network')
@ -53,6 +56,12 @@ class Configuration(object):
result = self.from_kv(formatted_data) result = self.from_kv(formatted_data)
return result return result
@staticmethod
def hide_sensitive_info(config_dict):
for field in SENSITIVE_FIELDS:
config_dict[field] = HIDDEN_FIELD_REPLACEMENT_CONTENT
return config_dict
def as_dict(self): def as_dict(self):
result = {} result = {}
for key in dir(Configuration): for key in dir(Configuration):
@ -272,5 +281,17 @@ class Configuration(object):
PBA_linux_filename = None PBA_linux_filename = None
PBA_windows_filename = None PBA_windows_filename = None
@staticmethod
def hash_sensitive_data(sensitive_data):
"""
Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is
saved on client machines plain-text.
:param sensitive_data: the data to hash.
:return: the hashed data.
"""
password_hashed = hashlib.sha512(sensitive_data).hexdigest()
return password_hashed
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -125,6 +125,7 @@ class ControlClient(object):
@staticmethod @staticmethod
def send_telemetry(telem_category, data): def send_telemetry(telem_category, data):
if not WormConfiguration.current_server: if not WormConfiguration.current_server:
LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category)
return return
try: try:
telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data} telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data}
@ -168,7 +169,8 @@ class ControlClient(object):
try: try:
unknown_variables = WormConfiguration.from_kv(reply.json().get('config')) unknown_variables = WormConfiguration.from_kv(reply.json().get('config'))
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),)) LOG.info("New configuration was loaded from server: %r" %
(WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),))
except Exception as exc: except Exception as exc:
# we don't continue with default conf here because it might be dangerous # we don't continue with default conf here because it might be dangerous
LOG.error("Error parsing JSON reply from control server %s (%s): %s", LOG.error("Error parsing JSON reply from control server %s (%s): %s",

View File

@ -15,7 +15,7 @@ from infection_monkey.exploit.tools.helpers import build_monkey_commandline_expl
from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
from infection_monkey.system_info import SystemInfoCollector, OperatingSystem from infection_monkey.system_info import SystemInfoCollector, OperatingSystem
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus, UsageEnum
if "win32" == sys.platform: if "win32" == sys.platform:
from win32process import DETACHED_PROCESS from win32process import DETACHED_PROCESS
@ -158,7 +158,6 @@ class MonkeyDrops(object):
else: else:
LOG.debug("Dropper source file '%s' is marked for deletion on next boot", LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
self._config['source_path']) self._config['source_path'])
T1106Telem(ScanStatus.USED, "WinAPI was used to mark monkey files" T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
" for deletion on next boot.").send()
except AttributeError: except AttributeError:
LOG.error("Invalid configuration options. Failing") LOG.error("Invalid configuration options. Failing")

View File

@ -124,8 +124,9 @@ class MSSQLExploiter(HostExploiter):
# Core steps # Core steps
# Trying to connect # Trying to connect
conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT) conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT)
LOG.info('Successfully connected to host: {0}, ' LOG.info(
'using user: {1}, password: {2}'.format(host, user, password)) 'Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}'.format(
host, user, self._config.hash_sensitive_data(password)))
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT) self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
self.report_login_attempt(True, user, password) self.report_login_attempt(True, user, password)
cursor = conn.cursor() cursor = conn.cursor()

View File

@ -9,6 +9,8 @@ from rdpy.core.error import RDPSecurityNegoFail
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
from twisted.internet import reactor from twisted.internet import reactor
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey, build_monkey_commandline from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey, build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.tools.http_tools import HTTPTools
@ -16,8 +18,6 @@ from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from infection_monkey.network.tools import check_tcp_port from infection_monkey.network.tools import check_tcp_port
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
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.attack_utils import ScanStatus, BITS_UPLOAD_STRING
__author__ = 'hoffer' __author__ = 'hoffer'
@ -298,8 +298,8 @@ class RdpExploiter(HostExploiter):
for user, password in user_password_pairs: for user, password in user_password_pairs:
try: try:
# run command using rdp. # run command using rdp.
LOG.info("Trying RDP logging into victim %r with user %s and password '%s'", LOG.info("Trying RDP logging into victim %r with user %s and password (SHA-512) '%s'",
self.host, user, password) self.host, user, self._config.hash_sensitive_data(password))
LOG.info("RDP connected to %r", self.host) LOG.info("RDP connected to %r", self.host)
@ -326,8 +326,8 @@ class RdpExploiter(HostExploiter):
except Exception as exc: except Exception as exc:
LOG.debug("Error logging into victim %r with user" LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", self.host, " %s and password (SHA-512) '%s': (%s)", self.host,
user, password, exc) user, self._config.hash_sensitive_data(password), exc)
continue continue
http_thread.join(DOWNLOAD_TIMEOUT) http_thread.join(DOWNLOAD_TIMEOUT)

View File

@ -11,7 +11,7 @@ from infection_monkey.network import SMBFinger
from infection_monkey.network.tools import check_tcp_port from infection_monkey.network.tools import check_tcp_port
from common.utils.exploit_enum import ExploitType from common.utils.exploit_enum import ExploitType
from infection_monkey.telemetry.attack.t1035_telem import T1035Telem from infection_monkey.telemetry.attack.t1035_telem import T1035Telem
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus, UsageEnum
LOG = getLogger(__name__) LOG = getLogger(__name__)
@ -68,8 +68,8 @@ class SmbExploiter(HostExploiter):
self._config.smb_download_timeout) self._config.smb_download_timeout)
if remote_full_path is not None: if remote_full_path is not None:
LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)", LOG.debug("Successfully logged in %r using SMB (%s : (SHA-512) %s : %s : %s)",
self.host, user, password, lm_hash, ntlm_hash) self.host, user, self._config.hash_sensitive_data(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], self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])) SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
@ -81,8 +81,8 @@ class SmbExploiter(HostExploiter):
except Exception as exc: except Exception as exc:
LOG.debug("Exception when trying to copy file using SMB to %r with user:" LOG.debug("Exception when trying to copy file using SMB to %r with user:"
" %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host, " %s, password (SHA-512): '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host,
user, password, lm_hash, ntlm_hash, exc) user, self._config.hash_sensitive_data(password), lm_hash, ntlm_hash, exc)
continue continue
if not exploited: if not exploited:
@ -133,11 +133,11 @@ class SmbExploiter(HostExploiter):
service = resp['lpServiceHandle'] service = resp['lpServiceHandle']
try: try:
scmr.hRStartServiceW(scmr_rpc, service) scmr.hRStartServiceW(scmr_rpc, service)
T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() status = ScanStatus.USED
except: except:
T1035Telem(ScanStatus.SCANNED, status = ScanStatus.SCANNED
"SMB exploiter failed to run the monkey by creating a service via MS-SCMR.").send()
pass pass
T1035Telem(status, UsageEnum.SMB).send()
scmr.hRDeleteService(scmr_rpc, service) scmr.hRDeleteService(scmr_rpc, service)
scmr.hRCloseServiceHandle(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service)

View File

@ -1,10 +1,11 @@
import StringIO
import logging import logging
import time import time
import paramiko import paramiko
import StringIO
import infection_monkey.monkeyfs as monkeyfs import infection_monkey.monkeyfs as monkeyfs
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.helpers import get_interface_to_target from infection_monkey.exploit.tools.helpers import get_interface_to_target
@ -73,26 +74,26 @@ class SSHExploiter(HostExploiter):
exploited = False exploited = False
for user, curpass in user_password_pairs: for user, current_password in user_password_pairs:
try: try:
ssh.connect(self.host.ip_addr, ssh.connect(self.host.ip_addr,
username=user, username=user,
password=curpass, password=current_password,
port=port, port=port,
timeout=None) timeout=None)
LOG.debug("Successfully logged in %r using SSH (%s : %s)", LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
self.host, user, curpass) self.host, user, self._config.hash_sensitive_data(current_password))
exploited = True exploited = True
self.add_vuln_port(port) self.add_vuln_port(port)
self.report_login_attempt(True, user, curpass) self.report_login_attempt(True, user, current_password)
break break
except Exception as exc: except Exception as exc:
LOG.debug("Error logging into victim %r with user" LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", self.host, " %s and password (SHA-512) '%s': (%s)", self.host,
user, curpass, exc) user, self._config.hash_sensitive_data(current_password), exc)
self.report_login_attempt(False, user, curpass) self.report_login_attempt(False, user, current_password)
continue continue
return exploited return exploited
@ -164,17 +165,17 @@ class SSHExploiter(HostExploiter):
ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer) callback=self.log_transfer)
ftp.chmod(self._config.dropper_target_path_linux, 0o777) ftp.chmod(self._config.dropper_target_path_linux, 0o777)
T1105Telem(ScanStatus.USED, status = ScanStatus.USED
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
src_path).send()
ftp.close() ftp.close()
except Exception as exc: except Exception as exc:
LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
T1105Telem(ScanStatus.SCANNED, status = ScanStatus.SCANNED
T1105Telem(status,
get_interface_to_target(self.host.ip_addr), get_interface_to_target(self.host.ip_addr),
self.host.ip_addr, self.host.ip_addr,
src_path).send() src_path).send()
if status == ScanStatus.SCANNED:
return False return False
try: try:

View File

@ -19,6 +19,7 @@ def get_interface_to_target(dst):
s.connect((dst, 1)) s.connect((dst, 1))
ip_to_dst = s.getsockname()[0] ip_to_dst = s.getsockname()[0]
except KeyError: except KeyError:
LOG.debug("Couldn't get an interface to the target, presuming that target is localhost.")
ip_to_dst = '127.0.0.1' ip_to_dst = '127.0.0.1'
finally: finally:
s.close() s.close()

View File

@ -36,8 +36,10 @@ class WmiExploiter(HostExploiter):
creds = self._config.get_exploit_user_password_or_hash_product() creds = self._config.get_exploit_user_password_or_hash_product()
for user, password, lm_hash, ntlm_hash in creds: for user, password, lm_hash, ntlm_hash in creds:
LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", password_hashed = self._config.hash_sensitive_data(password)
self.host, user, password, lm_hash, ntlm_hash) LOG.debug("Attempting to connect %r using WMI with "
"user,password (SHA-512),lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password_hashed, lm_hash, ntlm_hash)
wmi_connection = WmiTools.WmiConnection() wmi_connection = WmiTools.WmiConnection()
@ -47,23 +49,23 @@ class WmiExploiter(HostExploiter):
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with " LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash) self.host, user, password_hashed, lm_hash, ntlm_hash)
continue continue
except DCERPCException: except DCERPCException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with " LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash) self.host, user, password_hashed, lm_hash, ntlm_hash)
continue continue
except socket.error: except socket.error:
LOG.debug("Network error in WMI connection to %r with " LOG.debug("Network error in WMI connection to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash) self.host, user, password_hashed, lm_hash, ntlm_hash)
return False return False
except Exception as exc: except Exception as exc:
LOG.debug("Unknown WMI connection error to %r with " LOG.debug("Unknown WMI connection error to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s",
self.host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc()) self.host, user, password_hashed, lm_hash, ntlm_hash, exc, traceback.format_exc())
return False return False
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
@ -94,7 +96,8 @@ class WmiExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) build_monkey_commandline(
self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1) build_monkey_commandline(self.host, get_monkey_depth() - 1)
@ -121,3 +124,4 @@ class WmiExploiter(HostExploiter):
return success return success
return False return False

View File

@ -68,7 +68,7 @@ def main():
else: else:
print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,)) print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,))
print("Loaded Configuration: %r" % WormConfiguration.as_dict()) print("Loaded Configuration: %r" % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()))
# Make sure we're not in a machine that has the kill file # Make sure we're not in a machine that has the kill file
kill_path = os.path.expandvars( kill_path = os.path.expandvars(

View File

@ -184,7 +184,7 @@ class InfectionMonkey(object):
(':'+self._default_server_port if self._default_server_port else '')) (':'+self._default_server_port if self._default_server_port else ''))
else: else:
machine.set_default_server(self._default_server) machine.set_default_server(self._default_server)
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine)) LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
# Order exploits according to their type # Order exploits according to their type
if WormConfiguration.should_exploit: if WormConfiguration.should_exploit:
@ -255,6 +255,7 @@ class InfectionMonkey(object):
if WormConfiguration.self_delete_in_cleanup \ if WormConfiguration.self_delete_in_cleanup \
and -1 == sys.executable.find('python'): and -1 == sys.executable.find('python'):
try: try:
status = None
if "win32" == sys.platform: if "win32" == sys.platform:
from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
startupinfo = subprocess.STARTUPINFO() startupinfo = subprocess.STARTUPINFO()
@ -265,10 +266,12 @@ class InfectionMonkey(object):
close_fds=True, startupinfo=startupinfo) close_fds=True, startupinfo=startupinfo)
else: else:
os.remove(sys.executable) os.remove(sys.executable)
T1107Telem(ScanStatus.USED, sys.executable).send() status = ScanStatus.USED
except Exception as exc: except Exception as exc:
LOG.error("Exception in self delete: %s", exc) LOG.error("Exception in self delete: %s", exc)
T1107Telem(ScanStatus.SCANNED, sys.executable).send() status = ScanStatus.SCANNED
if status:
T1107Telem(status, sys.executable).send()
def send_log(self): def send_log(self):
monkey_log_path = utils.get_monkey_log_path() monkey_log_path = utils.get_monkey_log_path()

View File

@ -82,17 +82,22 @@ class UsersPBA(PBA):
pba_file_contents = ControlClient.get_pba_file(filename) pba_file_contents = ControlClient.get_pba_file(filename)
status = None
if not pba_file_contents or not pba_file_contents.content: if not pba_file_contents or not pba_file_contents.content:
LOG.error("Island didn't respond with post breach file.") LOG.error("Island didn't respond with post breach file.")
T1105Telem(ScanStatus.SCANNED, status = ScanStatus.SCANNED
if not status:
status = ScanStatus.USED
T1105Telem(status,
WormConfiguration.current_server.split(':')[0], WormConfiguration.current_server.split(':')[0],
get_interface_to_target(WormConfiguration.current_server.split(':')[0]), get_interface_to_target(WormConfiguration.current_server.split(':')[0]),
filename).send() filename).send()
if status == ScanStatus.SCANNED:
return False return False
T1105Telem(ScanStatus.USED,
WormConfiguration.current_server.split(':')[0],
get_interface_to_target(WormConfiguration.current_server.split(':')[0]),
filename).send()
try: try:
with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file:
written_PBA_file.write(pba_file_contents.content) written_PBA_file.write(pba_file_contents.content)

View File

@ -5,7 +5,7 @@ import socket
import zipfile import zipfile
import infection_monkey.config import infection_monkey.config
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus, UsageEnum
from infection_monkey.telemetry.attack.t1129_telem import T1129Telem from infection_monkey.telemetry.attack.t1129_telem import T1129Telem
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path
@ -47,16 +47,16 @@ class MimikatzCollector(object):
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p)
T1106Telem(ScanStatus.USED, "WinAPI was called to load mimikatz.").send()
self._collect = collect_proto(("collect", self._dll)) self._collect = collect_proto(("collect", self._dll))
self._get = get_proto(("get", self._dll)) self._get = get_proto(("get", self._dll))
self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll))
self._isInit = True self._isInit = True
T1129Telem(ScanStatus.USED, "Windows module loader was used to load Mimikatz DLL.").send() status = ScanStatus.USED
except Exception: except Exception:
LOG.exception("Error initializing mimikatz collector") LOG.exception("Error initializing mimikatz collector")
T1129Telem(ScanStatus.SCANNED, "Monkey tried to load Mimikatz DLL, but failed.").send() status = ScanStatus.SCANNED
T1106Telem(ScanStatus.SCANNED, "Monkey tried to call WinAPI to load mimikatz.").send() T1106Telem(status, UsageEnum.MIMIKATZ_WINAPI).send()
T1129Telem(status, UsageEnum.MIMIKATZ).send()
def get_logon_info(self): def get_logon_info(self):
""" """

View File

@ -5,7 +5,7 @@ from abc import ABCMeta, abstractmethod
from infection_monkey.config import WormConfiguration from infection_monkey.config import WormConfiguration
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus, UsageEnum
__author__ = 'itamar' __author__ = 'itamar'
@ -45,22 +45,25 @@ class WindowsSystemSingleton(_SystemSingleton):
ctypes.c_bool(True), ctypes.c_bool(True),
ctypes.c_char_p(self._mutex_name)) ctypes.c_char_p(self._mutex_name))
last_error = ctypes.windll.kernel32.GetLastError() last_error = ctypes.windll.kernel32.GetLastError()
status = None
if not handle: if not handle:
LOG.error("Cannot acquire system singleton %r, unknown error %d", LOG.error("Cannot acquire system singleton %r, unknown error %d",
self._mutex_name, last_error) self._mutex_name, last_error)
T1106Telem(ScanStatus.SCANNED, "WinAPI call to acquire system singleton " status = ScanStatus.SCANNED
"for monkey process wasn't successful.").send()
return False
if winerror.ERROR_ALREADY_EXISTS == last_error: if winerror.ERROR_ALREADY_EXISTS == last_error:
status = ScanStatus.SCANNED
LOG.debug("Cannot acquire system singleton %r, mutex already exist", LOG.debug("Cannot acquire system singleton %r, mutex already exist",
self._mutex_name) self._mutex_name)
if not status:
status = ScanStatus.USED
T1106Telem(status, UsageEnum.SINGLETON_WINAPI).send()
if status == ScanStatus.SCANNED:
return False return False
self._mutex_handle = handle self._mutex_handle = handle
T1106Telem(ScanStatus.USED, "WinAPI was called to acquire system singleton for monkey's process.").send()
LOG.debug("Global singleton mutex %r acquired", LOG.debug("Global singleton mutex %r acquired",
self._mutex_name) self._mutex_name)

View File

@ -6,6 +6,6 @@ class T1035Telem(UsageTelem):
""" """
T1035 telemetry. T1035 telemetry.
:param status: ScanStatus of technique :param status: ScanStatus of technique
:param usage: Usage string :param usage: Enum of UsageEnum type
""" """
super(T1035Telem, self).__init__('T1035', status, usage) super(T1035Telem, self).__init__('T1035', status, usage)

View File

@ -4,8 +4,8 @@ from infection_monkey.telemetry.attack.usage_telem import UsageTelem
class T1106Telem(UsageTelem): class T1106Telem(UsageTelem):
def __init__(self, status, usage): def __init__(self, status, usage):
""" """
T1129 telemetry. T1106 telemetry.
:param status: ScanStatus of technique :param status: ScanStatus of technique
:param usage: Usage string :param usage: Enum name of UsageEnum
""" """
super(T1106Telem, self).__init__("T1106", status, usage) super(T1106Telem, self).__init__("T1106", status, usage)

View File

@ -6,6 +6,6 @@ class T1129Telem(UsageTelem):
""" """
T1129 telemetry. T1129 telemetry.
:param status: ScanStatus of technique :param status: ScanStatus of technique
:param usage: Usage string :param usage: Enum of UsageEnum type
""" """
super(T1129Telem, self).__init__("T1129", status, usage) super(T1129Telem, self).__init__("T1129", status, usage)

View File

@ -5,12 +5,12 @@ class UsageTelem(AttackTelem):
def __init__(self, technique, status, usage): def __init__(self, technique, status, usage):
""" """
T1035 telemetry. :param technique: Id of technique
:param status: ScanStatus of technique :param status: ScanStatus of technique
:param usage: Usage string :param usage: Enum of UsageEnum type
""" """
super(UsageTelem, self).__init__(technique, status) super(UsageTelem, self).__init__(technique, status)
self.usage = usage self.usage = usage.name
def get_data(self): def get_data(self):
data = super(UsageTelem, self).get_data() data = super(UsageTelem, self).get_data()

View File

@ -3,3 +3,4 @@ import os
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), 'monkey_island') MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), 'monkey_island')
DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5

View File

@ -1,11 +1,11 @@
""" """
Define a Document Schema for the Monkey document. Define a Document Schema for the Monkey document.
""" """
import mongoengine
from mongoengine import Document, StringField, ListField, BooleanField, EmbeddedDocumentField, ReferenceField, \ from mongoengine import Document, StringField, ListField, BooleanField, EmbeddedDocumentField, ReferenceField, \
DateTimeField DateTimeField, DynamicField, DoesNotExist
from monkey_island.cc.models.monkey_ttl import MonkeyTtl from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.models.command_control_channel import CommandControlChannel from monkey_island.cc.models.command_control_channel import CommandControlChannel
@ -27,8 +27,11 @@ class Monkey(Document):
ip_addresses = ListField(StringField()) ip_addresses = ListField(StringField())
keepalive = DateTimeField() keepalive = DateTimeField()
modifytime = DateTimeField() modifytime = DateTimeField()
# TODO change this to an embedded document as well - RN it's an unnamed tuple which is confusing. # TODO make "parent" an embedded document, so this can be removed and the schema explained (and validated) verbosly.
parent = ListField(ListField(StringField())) # This is a temporary fix, since mongoengine doesn't allow for lists of strings to be null
# (even with required=False of null=True).
# See relevant issue: https://github.com/MongoEngine/mongoengine/issues/1904
parent = ListField(ListField(DynamicField()))
config_error = BooleanField() config_error = BooleanField()
critical_services = ListField(StringField()) critical_services = ListField(StringField())
pba_results = ListField() pba_results = ListField()
@ -40,13 +43,22 @@ class Monkey(Document):
@staticmethod @staticmethod
def get_single_monkey_by_id(db_id): def get_single_monkey_by_id(db_id):
try: try:
return Monkey.objects(id=db_id)[0] return Monkey.objects.get(id=db_id)
except IndexError: except DoesNotExist as ex:
raise MonkeyNotFoundError("id: {0}".format(str(db_id))) raise MonkeyNotFoundError("info: {0} | id: {1}".format(ex.message, str(db_id)))
@staticmethod
def get_single_monkey_by_guid(monkey_guid):
try:
return Monkey.objects.get(guid=monkey_guid)
except DoesNotExist as ex:
raise MonkeyNotFoundError("info: {0} | guid: {1}".format(ex.message, str(monkey_guid)))
@staticmethod @staticmethod
def get_latest_modifytime(): def get_latest_modifytime():
if Monkey.objects.count() > 0:
return Monkey.objects.order_by('-modifytime').first().modifytime return Monkey.objects.order_by('-modifytime').first().modifytime
return None
def is_dead(self): def is_dead(self):
monkey_is_dead = False monkey_is_dead = False
@ -57,7 +69,7 @@ class Monkey(Document):
if MonkeyTtl.objects(id=self.ttl_ref.id).count() == 0: if MonkeyTtl.objects(id=self.ttl_ref.id).count() == 0:
# No TTLs - monkey has timed out. The monkey is MIA. # No TTLs - monkey has timed out. The monkey is MIA.
monkey_is_dead = True monkey_is_dead = True
except (mongoengine.DoesNotExist, AttributeError): except (DoesNotExist, AttributeError):
# Trying to dereference unknown document - the monkey is MIA. # Trying to dereference unknown document - the monkey is MIA.
monkey_is_dead = True monkey_is_dead = True
return monkey_is_dead return monkey_is_dead
@ -70,6 +82,10 @@ class Monkey(Document):
os = "windows" os = "windows"
return os return os
def renew_ttl(self, duration=DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS):
self.ttl_ref = create_monkey_ttl_document(duration)
self.save()
@staticmethod @staticmethod
def get_tunneled_monkeys(): def get_tunneled_monkeys():
return Monkey.objects(tunnel__exists=True) return Monkey.objects(tunnel__exists=True)

View File

@ -38,3 +38,16 @@ class MonkeyTtl(Document):
} }
expire_at = DateTimeField() expire_at = DateTimeField()
def create_monkey_ttl_document(expiry_duration_in_seconds):
"""
Create a new Monkey TTL document and save it as a document.
:param expiry_duration_in_seconds: How long should the TTL last for. THIS IS A LOWER BOUND - depends on mongodb
performance.
:return: The TTL document. To get its ID use `.id`.
"""
# The TTL data uses the new `models` module which depends on mongoengine.
current_ttl = MonkeyTtl.create_ttl_expire_in(expiry_duration_in_seconds)
current_ttl.save()
return current_ttl

View File

@ -46,6 +46,19 @@ class TestMonkey(IslandTestCase):
self.assertTrue(mia_monkey.is_dead()) self.assertTrue(mia_monkey.is_dead())
self.assertFalse(alive_monkey.is_dead()) self.assertFalse(alive_monkey.is_dead())
def test_ttl_renewal(self):
self.fail_if_not_testing_env()
self.clean_monkey_db()
# Arrange
monkey = Monkey(guid=str(uuid.uuid4()))
monkey.save()
self.assertIsNone(monkey.ttl_ref)
# act + assert
monkey.renew_ttl()
self.assertIsNotNone(monkey.ttl_ref)
def test_get_single_monkey_by_id(self): def test_get_single_monkey_by_id(self):
self.fail_if_not_testing_env() self.fail_if_not_testing_env()
self.clean_monkey_db() self.clean_monkey_db()

View File

@ -5,26 +5,17 @@ import dateutil.parser
import flask_restful import flask_restful
from flask import request from flask import request
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.models.monkey_ttl import MonkeyTtl from monkey_island.cc.models.monkey_ttl import create_monkey_ttl_document
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService
MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5
__author__ = 'Barak' __author__ = 'Barak'
# TODO: separate logic from interface # TODO: separate logic from interface
def create_monkey_ttl():
# The TTL data uses the new `models` module which depends on mongoengine.
current_ttl = MonkeyTtl.create_ttl_expire_in(MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS)
current_ttl.save()
ttlid = current_ttl.id
return ttlid
class Monkey(flask_restful.Resource): class Monkey(flask_restful.Resource):
# Used by monkey. can't secure. # Used by monkey. can't secure.
@ -58,8 +49,8 @@ class Monkey(flask_restful.Resource):
tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "") tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip) NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip)
ttlid = create_monkey_ttl() ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS)
update['$set']['ttl_ref'] = ttlid update['$set']['ttl_ref'] = ttl.id
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False) return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
@ -120,7 +111,8 @@ class Monkey(flask_restful.Resource):
tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "") tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
monkey_json.pop('tunnel') monkey_json.pop('tunnel')
monkey_json['ttl_ref'] = create_monkey_ttl() ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS)
monkey_json['ttl_ref'] = ttl.id
mongo.db.monkey.update({"guid": monkey_json["guid"]}, mongo.db.monkey.update({"guid": monkey_json["guid"]},
{"$set": monkey_json}, {"$set": monkey_json},

View File

@ -15,10 +15,10 @@ from monkey_island.cc.services.edge import EdgeService
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService
from monkey_island.cc.encryptor import encryptor from monkey_island.cc.encryptor import encryptor
from monkey_island.cc.services.wmi_handler import WMIHandler from monkey_island.cc.services.wmi_handler import WMIHandler
from monkey_island.cc.models.monkey import Monkey
__author__ = 'Barak' __author__ = 'Barak'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -50,6 +50,9 @@ class Telemetry(flask_restful.Resource):
telemetry_json['timestamp'] = datetime.now() telemetry_json['timestamp'] = datetime.now()
telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host} telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host}
# Monkey communicated, so it's alive. Update the TTL.
Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).renew_ttl()
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
try: try:
@ -60,7 +63,7 @@ class Telemetry(flask_restful.Resource):
else: else:
logger.info('Got unknown type of telemetry: %s' % telem_category) 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. Info: {}".format(ex.message), exc_info=True)
telem_id = mongo.db.telemetry.insert(telemetry_json) telem_id = mongo.db.telemetry.insert(telemetry_json)
return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) return mongo.db.telemetry.find_one_or_404({"_id": telem_id})

View File

@ -1,3 +1,4 @@
import logging
from datetime import datetime from datetime import datetime
import dateutil import dateutil
@ -9,6 +10,8 @@ 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
logger = logging.getLogger(__name__)
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
@ -23,11 +26,15 @@ class TelemetryFeed(flask_restful.Resource):
telemetries = telemetries.sort([('timestamp', flask_pymongo.ASCENDING)]) telemetries = telemetries.sort([('timestamp', flask_pymongo.ASCENDING)])
try:
return \ return \
{ {
'telemetries': [TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries], 'telemetries': [TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries],
'timestamp': datetime.now().isoformat() 'timestamp': datetime.now().isoformat()
} }
except KeyError as err:
logger.error("Failed parsing telemetries. Error: {0}.".format(err.message))
return {'telemetries': [], 'timestamp': datetime.now().isoformat()}
@staticmethod @staticmethod
def get_displayed_telemetry(telem): def get_displayed_telemetry(telem):

View File

@ -1,10 +1,10 @@
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique from monkey_island.cc.services.attack.technique_reports import UsageTechnique
__author__ = "VakarisZ" __author__ = "VakarisZ"
class T1035(AttackTechnique): class T1035(UsageTechnique):
tech_id = "T1035" tech_id = "T1035"
unscanned_msg = "Monkey didn't try to interact with Windows services." unscanned_msg = "Monkey didn't try to interact with Windows services."
scanned_msg = "Monkey tried to interact with Windows services, but failed." scanned_msg = "Monkey tried to interact with Windows services, but failed."
@ -13,5 +13,5 @@ class T1035(AttackTechnique):
@staticmethod @staticmethod
def get_report_data(): def get_report_data():
data = T1035.get_tech_base_data() data = T1035.get_tech_base_data()
data.update({'services': list(mongo.db.telemetry.aggregate(T1035.get_usage_query()))}) data.update({'services': T1035.get_usage_data()})
return data return data

View File

@ -1,10 +1,9 @@
from monkey_island.cc.database import mongo from monkey_island.cc.services.attack.technique_reports import UsageTechnique
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
__author__ = "VakarisZ" __author__ = "VakarisZ"
class T1106(AttackTechnique): class T1106(UsageTechnique):
tech_id = "T1106" tech_id = "T1106"
unscanned_msg = "Monkey didn't try to directly use WinAPI." unscanned_msg = "Monkey didn't try to directly use WinAPI."
scanned_msg = "Monkey tried to use WinAPI, but failed." scanned_msg = "Monkey tried to use WinAPI, but failed."
@ -13,5 +12,5 @@ class T1106(AttackTechnique):
@staticmethod @staticmethod
def get_report_data(): def get_report_data():
data = T1106.get_tech_base_data() data = T1106.get_tech_base_data()
data.update({'api_uses': list(mongo.db.telemetry.aggregate(T1106.get_usage_query()))}) data.update({'api_uses': T1106.get_usage_data()})
return data return data

View File

@ -1,10 +1,10 @@
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique from monkey_island.cc.services.attack.technique_reports import UsageTechnique
__author__ = "VakarisZ" __author__ = "VakarisZ"
class T1129(AttackTechnique): class T1129(UsageTechnique):
tech_id = "T1129" tech_id = "T1129"
unscanned_msg = "Monkey didn't try to load any DLL's." unscanned_msg = "Monkey didn't try to load any DLL's."
scanned_msg = "Monkey tried to load DLL's, but failed." scanned_msg = "Monkey tried to load DLL's, but failed."
@ -13,5 +13,5 @@ class T1129(AttackTechnique):
@staticmethod @staticmethod
def get_report_data(): def get_report_data():
data = T1129.get_tech_base_data() data = T1129.get_tech_base_data()
data.update({'dlls': list(mongo.db.telemetry.aggregate(T1129.get_usage_query()))}) data.update({'dlls': T1129.get_usage_data()})
return data return data

View File

@ -1,10 +1,13 @@
import abc import abc
import logging
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus, UsageEnum
from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.services.attack.attack_config import AttackConfig
from common.utils.code_utils import abstractstatic from common.utils.code_utils import abstractstatic
logger = logging.getLogger(__name__)
class AttackTechnique(object): class AttackTechnique(object):
""" Abstract class for ATT&CK report components """ """ Abstract class for ATT&CK report components """
@ -113,6 +116,29 @@ class AttackTechnique(object):
data.update({'title': cls.technique_title()}) data.update({'title': cls.technique_title()})
return data return data
class UsageTechnique(AttackTechnique):
__metaclass__ = abc.ABCMeta
@staticmethod
def parse_usages(usage):
"""
Parses data from database and translates usage enums into strings
:param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1}
:return: usage string
"""
try:
usage['usage'] = UsageEnum[usage['usage']].value[usage['status']]
except KeyError:
logger.error("Error translating usage enum. into string. "
"Check if usage enum field exists and covers all telem. statuses.")
return usage
@classmethod
def get_usage_data(cls):
data = list(mongo.db.telemetry.aggregate(cls.get_usage_query()))
return list(map(cls.parse_usages, data))
@classmethod @classmethod
def get_usage_query(cls): def get_usage_query(cls):
""" """
@ -131,4 +157,5 @@ class AttackTechnique(object):
{'$addFields': {'_id': 0, {'$addFields': {'_id': 0,
'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
'monkey': 0}}, 'monkey': 0}},
{'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}] {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}},
{"$replaceRoot": {"newRoot": "$_id"}}]

View File

@ -389,7 +389,7 @@ SCHEMA = {
"self_delete_in_cleanup": { "self_delete_in_cleanup": {
"title": "Self delete on cleanup", "title": "Self delete on cleanup",
"type": "boolean", "type": "boolean",
"default": False, "default": True,
"description": "Should the monkey delete its executable when going down" "description": "Should the monkey delete its executable when going down"
}, },
"use_file_logging": { "use_file_logging": {

View File

@ -27,16 +27,16 @@ export function getUsageColumns() {
columns: [ columns: [
{Header: 'Machine', {Header: 'Machine',
id: 'machine', id: 'machine',
accessor: x => renderMachineFromSystemData(x._id.machine), accessor: x => renderMachineFromSystemData(x.machine),
style: { 'whiteSpace': 'unset' }, style: { 'whiteSpace': 'unset' },
width: 300}, width: 300},
{Header: 'Usage', {Header: 'Usage',
id: 'usage', id: 'usage',
accessor: x => x._id.usage, accessor: x => x.usage,
style: { 'whiteSpace': 'unset' }}] style: { 'whiteSpace': 'unset' }}]
}])} }])}
export const scanStatus = { export const ScanStatus = {
UNSCANNED: 0, UNSCANNED: 0,
SCANNED: 1, SCANNED: 1,
USED: 2 USED: 2

View File

@ -2,7 +2,7 @@ import React from 'react';
import '../../../styles/Collapse.scss' import '../../../styles/Collapse.scss'
import '../../report-components/StolenPasswords' import '../../report-components/StolenPasswords'
import StolenPasswordsComponent from "../../report-components/StolenPasswords"; import StolenPasswordsComponent from "../../report-components/StolenPasswords";
import {scanStatus} from "./Helpers" import {ScanStatus} from "./Helpers"
class T1003 extends React.Component { class T1003 extends React.Component {
@ -16,7 +16,7 @@ class T1003 extends React.Component {
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<br/> <br/>
{this.props.data.status === scanStatus.USED ? {this.props.data.status === ScanStatus.USED ?
<StolenPasswordsComponent data={this.props.reportData.glance.stolen_creds.concat(this.props.reportData.glance.ssh_keys)}/> <StolenPasswordsComponent data={this.props.reportData.glance.stolen_creds.concat(this.props.reportData.glance.ssh_keys)}/>
: ""} : ""}
</div> </div>

View File

@ -1,7 +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, scanStatus } from "./Helpers" import { renderMachine, ScanStatus } from "./Helpers"
class T1059 extends React.Component { class T1059 extends React.Component {
@ -25,7 +25,7 @@ class T1059 extends React.Component {
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<br/> <br/>
{this.props.data.status === scanStatus.USED ? {this.props.data.status === ScanStatus.USED ?
<ReactTable <ReactTable
columns={T1059.getCommandColumns()} columns={T1059.getCommandColumns()}
data={this.props.data.cmds} data={this.props.data.cmds}

View File

@ -1,7 +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, scanStatus } from "./Helpers" import { renderMachine, ScanStatus } from "./Helpers"
class T1075 extends React.Component { class T1075 extends React.Component {
@ -34,7 +34,7 @@ class T1075 extends React.Component {
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<br/> <br/>
{this.props.data.status !== scanStatus.UNSCANNED ? {this.props.data.status === ScanStatus.USED ?
<ReactTable <ReactTable
columns={T1075.getHashColumns()} columns={T1075.getHashColumns()}
data={this.props.data.successful_logins} data={this.props.data.successful_logins}

View File

@ -1,7 +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 { renderMachineFromSystemData, scanStatus } from "./Helpers" import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
class T1082 extends React.Component { class T1082 extends React.Component {
@ -33,7 +33,7 @@ class T1082 extends React.Component {
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<br/> <br/>
{this.props.data.status === scanStatus.USED ? {this.props.data.status === ScanStatus.USED ?
<ReactTable <ReactTable
columns={T1082.getSystemInfoColumns()} columns={T1082.getSystemInfoColumns()}
data={this.props.data.system_info} data={this.props.data.system_info}

View File

@ -1,7 +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, scanStatus } from "./Helpers" import { renderMachine, ScanStatus } from "./Helpers"
class T1086 extends React.Component { class T1086 extends React.Component {
@ -25,7 +25,7 @@ class T1086 extends React.Component {
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<br/> <br/>
{this.props.data.status === scanStatus.USED ? {this.props.data.status === ScanStatus.USED ?
<ReactTable <ReactTable
columns={T1086.getPowershellColumns()} columns={T1086.getPowershellColumns()}
data={this.props.data.cmds} data={this.props.data.cmds}

View File

@ -1,7 +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 { scanStatus } from "./Helpers" import { ScanStatus } from "./Helpers"
class T1105 extends React.Component { class T1105 extends React.Component {
@ -12,7 +12,7 @@ class T1105 extends React.Component {
static getFilesColumns() { static getFilesColumns() {
return ([{ return ([{
Header: 'Files copied.', Header: 'Files copied',
columns: [ columns: [
{Header: 'Src. Machine', id: 'srcMachine', accessor: x => x.src, style: { 'whiteSpace': 'unset'}, width: 170 }, {Header: 'Src. Machine', id: 'srcMachine', accessor: x => x.src, style: { 'whiteSpace': 'unset'}, width: 170 },
{Header: 'Dst. Machine', id: 'dstMachine', accessor: x => x.dst, style: { 'whiteSpace': 'unset'}, width: 170}, {Header: 'Dst. Machine', id: 'dstMachine', accessor: x => x.dst, style: { 'whiteSpace': 'unset'}, width: 170},
@ -25,7 +25,7 @@ class T1105 extends React.Component {
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<br/> <br/>
{this.props.data.status !== scanStatus.UNSCANNED ? {this.props.data.status !== ScanStatus.UNSCANNED ?
<ReactTable <ReactTable
columns={T1105.getFilesColumns()} columns={T1105.getFilesColumns()}
data={this.props.data.files} data={this.props.data.files}

View File

@ -1,7 +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 { renderMachineFromSystemData, scanStatus } from "./Helpers" import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
class T1107 extends React.Component { class T1107 extends React.Component {
@ -11,7 +11,7 @@ class T1107 extends React.Component {
} }
static renderDelete(status){ static renderDelete(status){
if(status === scanStatus.USED){ if(status === ScanStatus.USED){
return <span>Yes</span> return <span>Yes</span>
} else { } else {
return <span>No</span> return <span>No</span>

View File

@ -1,7 +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, scanStatus } from "./Helpers" import { renderMachine, ScanStatus } from "./Helpers"
class T1110 extends React.Component { class T1110 extends React.Component {
@ -32,7 +32,7 @@ class T1110 extends React.Component {
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<br/> <br/>
{this.props.data.status !== scanStatus.UNSCANNED ? {this.props.data.status !== ScanStatus.UNSCANNED ?
<ReactTable <ReactTable
columns={T1110.getServiceColumns()} columns={T1110.getServiceColumns()}
data={this.props.data.services} data={this.props.data.services}

View File

@ -1,7 +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 { renderMachineFromSystemData, scanStatus } from "./Helpers" import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
class T1145 extends React.Component { class T1145 extends React.Component {
@ -38,7 +38,7 @@ class T1145 extends React.Component {
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<br/> <br/>
{this.props.data.status === scanStatus.USED ? {this.props.data.status === ScanStatus.USED ?
<ReactTable <ReactTable
columns={T1145.getKeysInfoColumns()} columns={T1145.getKeysInfoColumns()}
data={this.props.data.ssh_info} data={this.props.data.ssh_info}

View File

@ -241,7 +241,7 @@ class RunMonkeyPageComponent extends AuthComponent {
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}> <div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<p className="alert alert-info"> <p className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/> <i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
Not sure what this is? Not seeing your AWS EC2 instances? <a href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances">Read the documentation</a>! Not sure what this is? Not seeing your AWS EC2 instances? <a href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances" target="_blank">Read the documentation</a>!
</p> </p>
</div> </div>
{ {

View File

@ -4,7 +4,7 @@ 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 '../../styles/Collapse.scss';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import {scanStatus} from "../attack/techniques/Helpers"; import {ScanStatus} from "../attack/techniques/Helpers";
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';
@ -94,9 +94,9 @@ class AttackReportPageComponent extends AuthComponent {
getComponentClass(tech_id){ getComponentClass(tech_id){
switch (this.state.report[tech_id].status) { switch (this.state.report[tech_id].status) {
case scanStatus.SCANNED: case ScanStatus.SCANNED:
return 'collapse-info'; return 'collapse-info';
case scanStatus.USED: case ScanStatus.USED:
return 'collapse-danger'; return 'collapse-danger';
default: default:
return 'collapse-default'; return 'collapse-default';

View File

@ -0,0 +1,23 @@
"""
Utility script for running a string through SHA3_512 hash.
Used for Monkey Island password hash, see
https://github.com/guardicore/monkey/wiki/Enabling-Monkey-Island-Password-Protection
for more details.
"""
import argparse
from Crypto.Hash import SHA3_512
def main():
parser = argparse.ArgumentParser()
parser.add_argument("string_to_sha", help="The string to do sha for")
args = parser.parse_args()
h = SHA3_512.new()
h.update(args.string_to_sha)
print(h.hexdigest())
if __name__ == '__main__':
main()