diff --git a/.gitignore b/.gitignore index 403d090ad..44ae856a5 100644 --- a/.gitignore +++ b/.gitignore @@ -62,9 +62,9 @@ docs/_build/ # PyBuilder target/ -db bin -/monkey_island/cc/server.key -/monkey_island/cc/server.crt -/monkey_island/cc/server.csr -monkey_island/cc/ui/node_modules/ +/monkey/monkey_island/db +/monkey/monkey_island/cc/server.key +/monkey/monkey_island/cc/server.crt +/monkey/monkey_island/cc/server.csr +/monkey/monkey_island/cc/ui/node_modules/ diff --git a/infection_monkey/network/__init__.py b/infection_monkey/network/__init__.py deleted file mode 100644 index fa15e357c..000000000 --- a/infection_monkey/network/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from abc import ABCMeta, abstractmethod - -__author__ = 'itamar' - - -class HostScanner(object): - __metaclass__ = ABCMeta - - @abstractmethod - def is_host_alive(self, host): - raise NotImplementedError() - - -class HostFinger(object): - __metaclass__ = ABCMeta - - @abstractmethod - def get_host_fingerprint(self, host): - raise NotImplementedError() - -from ping_scanner import PingScanner -from tcp_scanner import TcpScanner -from smbfinger import SMBFinger -from sshfinger import SSHFinger -from httpfinger import HTTPFinger -from elasticfinger import ElasticFinger -from mysqlfinger import MySQLFinger -from info import local_ips -from info import get_free_tcp_port -from mssql_fingerprint import MSSQLFinger \ No newline at end of file diff --git a/infection_monkey/transport/__init__.py b/infection_monkey/transport/__init__.py deleted file mode 100644 index d0408a309..000000000 --- a/infection_monkey/transport/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from http import HTTPServer, LockedHTTPServer - -__author__ = 'hoffer' diff --git a/common/__init__.py b/monkey/__init__.py similarity index 100% rename from common/__init__.py rename to monkey/__init__.py diff --git a/common/network/__init__.py b/monkey/common/__init__.py similarity index 100% rename from common/network/__init__.py rename to monkey/common/__init__.py diff --git a/monkey/common/network/__init__.py b/monkey/common/network/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/monkey/common/network/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/common/network/network_range.py b/monkey/common/network/network_range.py similarity index 100% rename from common/network/network_range.py rename to monkey/common/network/network_range.py diff --git a/monkey/infection_monkey.py b/monkey/infection_monkey.py new file mode 100644 index 000000000..86e5f5657 --- /dev/null +++ b/monkey/infection_monkey.py @@ -0,0 +1,4 @@ +import infection_monkey.main + +if "__main__" == __name__: + infection_monkey.main.main() diff --git a/monkey/infection_monkey/__init__.py b/monkey/infection_monkey/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/monkey/infection_monkey/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/infection_monkey/build_linux.sh b/monkey/infection_monkey/build_linux.sh similarity index 100% rename from infection_monkey/build_linux.sh rename to monkey/infection_monkey/build_linux.sh diff --git a/infection_monkey/build_windows.bat b/monkey/infection_monkey/build_windows.bat similarity index 100% rename from infection_monkey/build_windows.bat rename to monkey/infection_monkey/build_windows.bat diff --git a/infection_monkey/config.py b/monkey/infection_monkey/config.py similarity index 75% rename from infection_monkey/config.py rename to monkey/infection_monkey/config.py index 4f7f4bcd9..4a63c082b 100644 --- a/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -1,15 +1,13 @@ import os -import struct +import json import sys import types import uuid from abc import ABCMeta from itertools import product +import importlib -from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ - SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter, WebLogicExploiter, HadoopExploiter -from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ - MSSQLFinger +importlib.import_module('infection_monkey', 'network') __author__ = 'itamar' @@ -18,57 +16,47 @@ GUID = str(uuid.getnode()) EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin') -def _cast_by_example(value, example): - """ - a method that casts a value to the type of the parameter given as example - """ - example_type = type(example) - if example_type is str: - return os.path.expandvars(value).encode("utf8") - elif example_type is tuple and len(example) != 0: - if value is None or value == tuple([None]): - return tuple() - return tuple([_cast_by_example(x, example[0]) for x in value]) - elif example_type is list and len(example) != 0: - if value is None or value == [None]: - return [] - return [_cast_by_example(x, example[0]) for x in value] - elif example_type is type(value): - return value - elif example_type is bool: - return value.lower() == 'true' - elif example_type is int: - return int(value) - elif example_type is float: - return float(value) - elif example_type in (type, ABCMeta): - return globals()[value] - else: - return None - - class Configuration(object): - def from_dict(self, data): - """ - Get a dict of config variables, set known variables as attributes on self. - Return dict of unknown variables encountered. - """ - unknown_variables = {} - for key, value in data.items(): + + def from_kv(self, formatted_data): + # now we won't work at <2.7 for sure + network_import = importlib.import_module('infection_monkey.network') + exploit_import = importlib.import_module('infection_monkey.exploit') + + unknown_items = [] + for key, value in formatted_data.items(): if key.startswith('_'): continue if key in ["name", "id", "current_server"]: continue if self._depth_from_commandline and key == "depth": continue - try: - default_value = getattr(Configuration, key) - except AttributeError: - unknown_variables[key] = value - continue + # handle in cases + if key == 'finger_classes': + class_objects = [getattr(network_import, val) for val in value] + setattr(self, key, class_objects) + elif key == 'scanner_class': + scanner_object = getattr(network_import, value) + setattr(self, key, scanner_object) + elif key == 'exploiter_classes': + class_objects = [getattr(exploit_import, val) for val in value] + setattr(self, key, class_objects) + else: + if hasattr(self, key): + setattr(self, key, value) + else: + unknown_items.append(key) + return unknown_items - setattr(self, key, _cast_by_example(value, default_value)) - return unknown_variables + def from_json(self, json_data): + """ + Gets a json data object, parses it and applies it to the configuration + :param json_data: + :return: + """ + formatted_data = json.loads(json_data) + result = self.from_kv(formatted_data) + return result def as_dict(self): result = {} @@ -145,12 +133,9 @@ class Configuration(object): # how many scan iterations to perform on each run max_iterations = 1 - scanner_class = TcpScanner - finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger] - exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits - SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux - ElasticGroovyExploiter, Struts2Exploiter, WebLogicExploiter, HadoopExploiter # multi - ] + scanner_class = None + finger_classes = [] + exploiter_classes = [] # how many victims to look for in a single scan iteration victims_max_find = 30 diff --git a/infection_monkey/control.py b/monkey/infection_monkey/control.py similarity index 96% rename from infection_monkey/control.py rename to monkey/infection_monkey/control.py index d2cbc0cc0..7322322e7 100644 --- a/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -6,12 +6,12 @@ from socket import gethostname import requests from requests.exceptions import ConnectionError -import monkeyfs -import tunnel -from config import WormConfiguration, GUID -from network.info import local_ips, check_internet_access -from transport.http import HTTPConnectProxy -from transport.tcp import TcpProxy +import infection_monkey.monkeyfs as monkeyfs +import infection_monkey.tunnel as tunnel +from infection_monkey.config import WormConfiguration, GUID +from infection_monkey.network.info import local_ips, check_internet_access +from infection_monkey.transport.http import HTTPConnectProxy +from infection_monkey.transport.tcp import TcpProxy __author__ = 'hoffer' @@ -160,7 +160,7 @@ class ControlClient(object): return try: - unknown_variables = WormConfiguration.from_dict(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(),)) except Exception as exc: # we don't continue with default conf here because it might be dangerous diff --git a/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py similarity index 95% rename from infection_monkey/dropper.py rename to monkey/infection_monkey/dropper.py index c135dcddb..02bd649c2 100644 --- a/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -10,10 +10,10 @@ import time from ctypes import c_char_p import filecmp -from config import WormConfiguration -from exploit.tools import build_monkey_commandline_explicitly -from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX -from system_info import SystemInfoCollector, OperatingSystem +from infection_monkey.config import WormConfiguration +from infection_monkey.exploit.tools import build_monkey_commandline_explicitly +from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX +from infection_monkey.system_info import SystemInfoCollector, OperatingSystem if "win32" == sys.platform: from win32process import DETACHED_PROCESS diff --git a/infection_monkey/example.conf b/monkey/infection_monkey/example.conf similarity index 100% rename from infection_monkey/example.conf rename to monkey/infection_monkey/example.conf diff --git a/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py similarity index 55% rename from infection_monkey/exploit/__init__.py rename to monkey/infection_monkey/exploit/__init__.py index d4456d20e..470155020 100644 --- a/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -1,4 +1,5 @@ from abc import ABCMeta, abstractmethod +import infection_monkey.config __author__ = 'itamar' @@ -9,7 +10,7 @@ class HostExploiter(object): _TARGET_OS_TYPE = [] def __init__(self, host): - + self._config = infection_monkey.config.WormConfiguration self._exploit_info = {} self._exploit_attempts = [] self.host = host @@ -18,7 +19,7 @@ class HostExploiter(object): return self.host.os.get('type') in self._TARGET_OS_TYPE def send_exploit_telemetry(self, result): - from control import ControlClient + from infection_monkey.control import ControlClient ControlClient.send_telemetry( 'exploit', {'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__, @@ -33,14 +34,14 @@ class HostExploiter(object): raise NotImplementedError() -from win_ms08_067 import Ms08_067_Exploiter -from wmiexec import WmiExploiter -from smbexec import SmbExploiter -from rdpgrinder import RdpExploiter -from sshexec import SSHExploiter -from shellshock import ShellShockExploiter -from sambacry import SambaCryExploiter -from elasticgroovy import ElasticGroovyExploiter -from struts2 import Struts2Exploiter -from weblogic import WebLogicExploiter -from hadoop import HadoopExploiter +from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter +from infection_monkey.exploit.wmiexec import WmiExploiter +from infection_monkey.exploit.smbexec import SmbExploiter +from infection_monkey.exploit.rdpgrinder import RdpExploiter +from infection_monkey.exploit.sshexec import SSHExploiter +from infection_monkey.exploit.shellshock import ShellShockExploiter +from infection_monkey.exploit.sambacry import SambaCryExploiter +from infection_monkey.exploit.elasticgroovy import ElasticGroovyExploiter +from infection_monkey.exploit.struts2 import Struts2Exploiter +from infection_monkey.exploit.weblogic import WebLogicExploiter +from infection_monkey.exploit.hadoop import HadoopExploiter diff --git a/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py similarity index 92% rename from infection_monkey/exploit/elasticgroovy.py rename to monkey/infection_monkey/exploit/elasticgroovy.py index c4cc3e4a7..9eb64682b 100644 --- a/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -7,9 +7,9 @@ import json import logging import requests -from exploit.web_rce import WebRCE -from model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP -from network.elasticfinger import ES_PORT, ES_SERVICE +from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP +from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE import re diff --git a/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py similarity index 94% rename from infection_monkey/exploit/hadoop.py rename to monkey/infection_monkey/exploit/hadoop.py index c41badd52..0605614ee 100644 --- a/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -8,10 +8,11 @@ import json import random import string import logging -from exploit.web_rce import WebRCE -from tools import HTTPTools, build_monkey_commandline, get_monkey_depth import posixpath -from model import MONKEY_ARG, ID_STRING + +from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth +from infection_monkey.model import MONKEY_ARG, ID_STRING __author__ = 'VakarisZ' diff --git a/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py similarity index 96% rename from infection_monkey/exploit/rdpgrinder.py rename to monkey/infection_monkey/exploit/rdpgrinder.py index 5d73c8279..3873a8ce3 100644 --- a/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -9,12 +9,12 @@ from rdpy.core.error import RDPSecurityNegoFail from rdpy.protocol.rdp import rdp from twisted.internet import reactor -from exploit import HostExploiter -from exploit.tools import HTTPTools, get_monkey_depth -from exploit.tools import get_target_monkey -from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS -from network.tools import check_tcp_port -from tools import build_monkey_commandline +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth +from infection_monkey.exploit.tools import get_target_monkey +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.exploit.tools import build_monkey_commandline __author__ = 'hoffer' @@ -237,8 +237,6 @@ class RdpExploiter(HostExploiter): def __init__(self, host): super(RdpExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self._guid = __import__('config').GUID def is_os_supported(self): if super(RdpExploiter, self).is_os_supported(): diff --git a/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py similarity index 98% rename from infection_monkey/exploit/sambacry.py rename to monkey/infection_monkey/exploit/sambacry.py index bddac84a1..f55b43553 100644 --- a/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -15,12 +15,12 @@ from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_ SMB2Packet, SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE from impacket.smbconnection import SMBConnection -import monkeyfs -from exploit import HostExploiter -from model import DROPPER_ARG -from network.smbfinger import SMB_SERVICE -from tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth -from pyinstaller_utils import get_binary_file_path +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.exploit import HostExploiter +from infection_monkey.model import DROPPER_ARG +from infection_monkey.network.smbfinger import SMB_SERVICE +from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth +from infection_monkey.pyinstaller_utils import get_binary_file_path __author__ = 'itay.mizeretz' @@ -53,7 +53,6 @@ class SambaCryExploiter(HostExploiter): def __init__(self, host): super(SambaCryExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration def exploit_host(self): if not self.is_vulnerable(): diff --git a/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py similarity index 96% rename from infection_monkey/exploit/shellshock.py rename to monkey/infection_monkey/exploit/shellshock.py index e1ef246b6..dd80af22c 100644 --- a/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -6,11 +6,11 @@ from random import choice import requests -from exploit import HostExploiter -from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth -from model import DROPPER_ARG -from shellshock_resources import CGI_FILES -from tools import build_monkey_commandline +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth +from infection_monkey.model import DROPPER_ARG +from infection_monkey.exploit.shellshock_resources import CGI_FILES +from infection_monkey.exploit.tools import build_monkey_commandline __author__ = 'danielg' @@ -29,7 +29,6 @@ class ShellShockExploiter(HostExploiter): def __init__(self, host): super(ShellShockExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.success_flag = ''.join( choice(string.ascii_uppercase + string.digits diff --git a/infection_monkey/exploit/shellshock_resources.py b/monkey/infection_monkey/exploit/shellshock_resources.py similarity index 100% rename from infection_monkey/exploit/shellshock_resources.py rename to monkey/infection_monkey/exploit/shellshock_resources.py diff --git a/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py similarity index 92% rename from infection_monkey/exploit/smbexec.py rename to monkey/infection_monkey/exploit/smbexec.py index d3b27f79d..7528e08ba 100644 --- a/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -3,12 +3,12 @@ from logging import getLogger from impacket.dcerpc.v5 import transport, scmr from impacket.smbconnection import SMB_DIALECT -from exploit import HostExploiter -from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth -from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS -from network import SMBFinger -from network.tools import check_tcp_port -from tools import build_monkey_commandline +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth +from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS +from infection_monkey.network import SMBFinger +from infection_monkey.network.tools import check_tcp_port +from infection_monkey.exploit.tools import build_monkey_commandline LOG = getLogger(__name__) @@ -23,8 +23,6 @@ class SmbExploiter(HostExploiter): def __init__(self, host): super(SmbExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self._guid = __import__('config').GUID def is_os_supported(self): if super(SmbExploiter, self).is_os_supported(): diff --git a/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py similarity index 95% rename from infection_monkey/exploit/sshexec.py rename to monkey/infection_monkey/exploit/sshexec.py index 7c6cc6509..82dd1f4d7 100644 --- a/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -4,12 +4,12 @@ import time import paramiko import StringIO -import monkeyfs -from exploit import HostExploiter -from exploit.tools import get_target_monkey, get_monkey_depth -from model import MONKEY_ARG -from network.tools import check_tcp_port -from tools import build_monkey_commandline +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth +from infection_monkey.model import MONKEY_ARG +from infection_monkey.network.tools import check_tcp_port +from infection_monkey.exploit.tools import build_monkey_commandline __author__ = 'hoffer' @@ -23,7 +23,6 @@ class SSHExploiter(HostExploiter): def __init__(self, host): super(SSHExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration self._update_timestamp = 0 self.skip_exist = self._config.skip_exploit_if_file_exist diff --git a/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py similarity index 98% rename from infection_monkey/exploit/struts2.py rename to monkey/infection_monkey/exploit/struts2.py index fe4a73c09..18f3d3a7e 100644 --- a/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -9,7 +9,7 @@ import unicodedata import re import logging -from web_rce import WebRCE +from infection_monkey.exploit.web_rce import WebRCE __author__ = "VakarisZ" diff --git a/infection_monkey/exploit/tools.py b/monkey/infection_monkey/exploit/tools.py similarity index 96% rename from infection_monkey/exploit/tools.py rename to monkey/infection_monkey/exploit/tools.py index 16a0950c0..a7a137557 100644 --- a/infection_monkey/exploit/tools.py +++ b/monkey/infection_monkey/exploit/tools.py @@ -17,11 +17,12 @@ from impacket.dcerpc.v5.dtypes import NULL from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 from impacket.smbconnection import SMBConnection, SMB_DIALECT -import monkeyfs -from network import local_ips -from network.firewall import app as firewall -from network.info import get_free_tcp_port, get_routes -from transport import HTTPServer, LockedHTTPServer +import infection_monkey.config +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.network import local_ips +from infection_monkey.network.firewall import app as firewall +from infection_monkey.network.info import get_free_tcp_port, get_routes +from infection_monkey.transport import HTTPServer, LockedHTTPServer from threading import Lock @@ -174,8 +175,7 @@ class SmbTools(object): @staticmethod def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60): assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) - - config = __import__('config').WormConfiguration + config = infection_monkey.config.WormConfiguration src_file_size = monkeyfs.getsize(src_path) smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) @@ -407,6 +407,7 @@ class HTTPTools(object): local_ip = get_interface_to_target(host.ip_addr) if not firewall.listen_allowed(): + LOG.error("Firewall is not allowed to listen for incomming ports. Aborting") return None, None httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) @@ -444,7 +445,7 @@ def get_interface_to_target(dst): def get_target_monkey(host): - from control import ControlClient + from infection_monkey.control import ControlClient import platform import sys @@ -470,7 +471,7 @@ def get_target_monkey(host): def get_target_monkey_by_os(is_windows, is_32bit): - from control import ControlClient + from infection_monkey.control import ControlClient return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) @@ -494,13 +495,13 @@ def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, d def build_monkey_commandline(target_host, depth, location=None): - from config import GUID + from infection_monkey.config import GUID return build_monkey_commandline_explicitly( GUID, target_host.default_tunnel, target_host.default_server, depth, location) def get_monkey_depth(): - from config import WormConfiguration + from infection_monkey.config import WormConfiguration return WormConfiguration.depth @@ -510,7 +511,7 @@ def get_monkey_dest_path(url_to_monkey): :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe :return: Corresponding monkey path from configuration """ - from config import WormConfiguration + from infection_monkey.config import WormConfiguration if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey): LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey) return False diff --git a/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py similarity index 97% rename from infection_monkey/exploit/web_rce.py rename to monkey/infection_monkey/exploit/web_rce.py index 97708ab24..a8dfd97c5 100644 --- a/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -1,12 +1,12 @@ import logging - -from exploit import HostExploiter -from model import * -from posixpath import join import re +from posixpath import join from abc import abstractmethod -from exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools -from network.tools import check_tcp_port, tcp_port_to_service + +from infection_monkey.exploit import HostExploiter +from infection_monkey.model import * +from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools +from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service __author__ = 'VakarisZ' @@ -95,7 +95,7 @@ class WebRCE(HostExploiter): # Upload the right monkey to target data = self.upload_monkey(self.vulnerable_urls[0], exploit_config['upload_commands']) - if data is not False and data['response'] is False: + if data is False: return False # Change permissions to transform monkey into executable file @@ -341,7 +341,11 @@ class WebRCE(HostExploiter): http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() LOG.info("Uploading process finished") - return {'response': resp, 'path': paths['dest_path']} + # If response is false exploiter failed + if resp is False: + return resp + else: + return {'response': resp, 'path': paths['dest_path']} def change_permissions(self, url, path, command=None): """ diff --git a/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py similarity index 98% rename from infection_monkey/exploit/weblogic.py rename to monkey/infection_monkey/exploit/weblogic.py index 24e99424c..ac78555af 100644 --- a/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -7,8 +7,8 @@ # CVE: CVE-2017-10271 from requests import post, exceptions -from web_rce import WebRCE -from exploit.tools import get_free_tcp_port, get_interface_to_target +from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import threading diff --git a/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py similarity index 96% rename from infection_monkey/exploit/win_ms08_067.py rename to monkey/infection_monkey/exploit/win_ms08_067.py index 85086bce7..9f8837157 100644 --- a/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -14,11 +14,11 @@ from enum import IntEnum from impacket import uuid from impacket.dcerpc.v5 import transport -from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth -from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS -from network import SMBFinger -from network.tools import check_tcp_port -from tools import build_monkey_commandline +from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth +from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS +from infection_monkey.network import SMBFinger +from infection_monkey.network.tools import check_tcp_port +from infection_monkey.exploit.tools import build_monkey_commandline from . import HostExploiter LOG = getLogger(__name__) @@ -158,8 +158,6 @@ class Ms08_067_Exploiter(HostExploiter): def __init__(self, host): super(Ms08_067_Exploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self._guid = __import__('config').GUID def is_os_supported(self): if self.host.os.get('type') in self._TARGET_OS_TYPE and \ diff --git a/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py similarity index 93% rename from infection_monkey/exploit/wmiexec.py rename to monkey/infection_monkey/exploit/wmiexec.py index 0f9b2ee4c..1a8cb3386 100644 --- a/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -5,10 +5,10 @@ import traceback from impacket.dcerpc.v5.rpcrt import DCERPCException -from exploit import HostExploiter -from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, get_monkey_depth -from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS -from tools import build_monkey_commandline +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \ + get_monkey_depth, build_monkey_commandline +from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS LOG = logging.getLogger(__name__) @@ -18,8 +18,6 @@ class WmiExploiter(HostExploiter): def __init__(self, host): super(WmiExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self._guid = __import__('config').GUID @WmiTools.dcom_wrap def exploit_host(self): diff --git a/infection_monkey/main.py b/monkey/infection_monkey/main.py similarity index 92% rename from infection_monkey/main.py rename to monkey/infection_monkey/main.py index 51fd6b9f7..be45afce4 100644 --- a/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -8,14 +8,11 @@ import os import sys import traceback -from config import WormConfiguration, EXTERNAL_CONFIG_FILE -from dropper import MonkeyDrops -from model import MONKEY_ARG, DROPPER_ARG -from monkey import InfectionMonkey -import utils - -if __name__ == "__main__": - sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import infection_monkey.utils as utils +from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE +from infection_monkey.dropper import MonkeyDrops +from infection_monkey.model import MONKEY_ARG, DROPPER_ARG +from infection_monkey.monkey import InfectionMonkey __author__ = 'itamar' @@ -63,7 +60,7 @@ def main(): try: with open(config_file) as config_fo: json_dict = json.load(config_fo) - WormConfiguration.from_dict(json_dict) + WormConfiguration.from_kv(json_dict) except ValueError as e: print("Error loading config: %s, using default" % (e,)) else: diff --git a/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py similarity index 97% rename from infection_monkey/model/__init__.py rename to monkey/infection_monkey/model/__init__.py index 4a8218a2e..f2217623a 100644 --- a/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -1,4 +1,4 @@ -from host import VictimHost +from infection_monkey.model.host import VictimHost __author__ = 'itamar' diff --git a/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py similarity index 100% rename from infection_monkey/model/host.py rename to monkey/infection_monkey/model/host.py diff --git a/infection_monkey/monkey-linux.spec b/monkey/infection_monkey/monkey-linux.spec similarity index 93% rename from infection_monkey/monkey-linux.spec rename to monkey/infection_monkey/monkey-linux.spec index fac69536e..61a2725c4 100644 --- a/infection_monkey/monkey-linux.spec +++ b/monkey/infection_monkey/monkey-linux.spec @@ -4,7 +4,7 @@ block_cipher = None a = Analysis(['main.py'], - pathex=['.', '..'], + pathex=['..'], binaries=None, datas=None, hiddenimports=['_cffi_backend'], diff --git a/infection_monkey/monkey.ico b/monkey/infection_monkey/monkey.ico similarity index 100% rename from infection_monkey/monkey.ico rename to monkey/infection_monkey/monkey.ico diff --git a/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py similarity index 95% rename from infection_monkey/monkey.py rename to monkey/infection_monkey/monkey.py index 8ad1baf8c..efdb43a3c 100644 --- a/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -4,18 +4,18 @@ import os import subprocess import sys import time - -import tunnel -import utils -from config import WormConfiguration -from control import ControlClient -from model import DELAY_DELETE_CMD -from network.firewall import app as firewall -from network.network_scanner import NetworkScanner from six.moves import xrange -from system_info import SystemInfoCollector -from system_singleton import SystemSingleton -from windows_upgrader import WindowsUpgrader + +import infection_monkey.tunnel as tunnel +import infection_monkey.utils as utils +from infection_monkey.config import WormConfiguration +from infection_monkey.control import ControlClient +from infection_monkey.model import DELAY_DELETE_CMD +from infection_monkey.network.firewall import app as firewall +from infection_monkey.network.network_scanner import NetworkScanner +from infection_monkey.system_info import SystemInfoCollector +from infection_monkey.system_singleton import SystemSingleton +from infection_monkey.windows_upgrader import WindowsUpgrader __author__ = 'itamar' diff --git a/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec similarity index 97% rename from infection_monkey/monkey.spec rename to monkey/infection_monkey/monkey.spec index b4449361b..f539d61fa 100644 --- a/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -14,7 +14,7 @@ def get_mimikatz_zip_path(): a = Analysis(['main.py'], - pathex=['.', '..'], + pathex=['..'], hiddenimports=['_cffi_backend', 'queue'], hookspath=None, runtime_hooks=None) diff --git a/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh similarity index 100% rename from infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh rename to monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh diff --git a/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c similarity index 100% rename from infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c rename to monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c diff --git a/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h similarity index 100% rename from infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h rename to monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h diff --git a/infection_monkey/monkeyfs.py b/monkey/infection_monkey/monkeyfs.py similarity index 100% rename from infection_monkey/monkeyfs.py rename to monkey/infection_monkey/monkeyfs.py diff --git a/monkey/infection_monkey/network/__init__.py b/monkey/infection_monkey/network/__init__.py new file mode 100644 index 000000000..e43fa7073 --- /dev/null +++ b/monkey/infection_monkey/network/__init__.py @@ -0,0 +1,29 @@ +from abc import ABCMeta, abstractmethod + +__author__ = 'itamar' + + +class HostScanner(object): + __metaclass__ = ABCMeta + + @abstractmethod + def is_host_alive(self, host): + raise NotImplementedError() + + +class HostFinger(object): + __metaclass__ = ABCMeta + + @abstractmethod + def get_host_fingerprint(self, host): + raise NotImplementedError() + +from infection_monkey.network.ping_scanner import PingScanner +from infection_monkey.network.tcp_scanner import TcpScanner +from infection_monkey.network.smbfinger import SMBFinger +from infection_monkey.network.sshfinger import SSHFinger +from infection_monkey.network.httpfinger import HTTPFinger +from infection_monkey.network.elasticfinger import ElasticFinger +from infection_monkey.network.mysqlfinger import MySQLFinger +from infection_monkey.network.info import local_ips, get_free_tcp_port +from infection_monkey.network.mssql_fingerprint import MSSQLFinger \ No newline at end of file diff --git a/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py similarity index 88% rename from infection_monkey/network/elasticfinger.py rename to monkey/infection_monkey/network/elasticfinger.py index 730decf4f..3d62de687 100644 --- a/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -5,8 +5,9 @@ from contextlib import closing import requests from requests.exceptions import Timeout, ConnectionError -from model.host import VictimHost -from network import HostFinger +import infection_monkey.config +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostFinger ES_PORT = 9200 ES_SERVICE = 'elastic-search-9200' @@ -21,7 +22,7 @@ class ElasticFinger(HostFinger): """ def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration def get_host_fingerprint(self, host): """ diff --git a/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py similarity index 100% rename from infection_monkey/network/firewall.py rename to monkey/infection_monkey/network/firewall.py diff --git a/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py similarity index 89% rename from infection_monkey/network/httpfinger.py rename to monkey/infection_monkey/network/httpfinger.py index 437edbf6c..829c6b1b5 100644 --- a/infection_monkey/network/httpfinger.py +++ b/monkey/infection_monkey/network/httpfinger.py @@ -1,16 +1,18 @@ -from network import HostFinger -from model.host import VictimHost +import infection_monkey.config +from infection_monkey.network import HostFinger +from infection_monkey.model.host import VictimHost import logging LOG = logging.getLogger(__name__) + class HTTPFinger(HostFinger): """ Goal is to recognise HTTP servers, where what we currently care about is apache. """ def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration self.HTTP = [(port, str(port)) for port in self._config.HTTP_PORTS] @staticmethod diff --git a/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py similarity index 100% rename from infection_monkey/network/info.py rename to monkey/infection_monkey/network/info.py diff --git a/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py similarity index 93% rename from infection_monkey/network/mssql_fingerprint.py rename to monkey/infection_monkey/network/mssql_fingerprint.py index e34ed7a54..75fde7465 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -1,8 +1,9 @@ import logging import socket -from model.host import VictimHost -from network import HostFinger +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostFinger +import infection_monkey.config __author__ = 'Maor Rayzin' @@ -18,7 +19,7 @@ class MSSQLFinger(HostFinger): SERVICE_NAME = 'MSSQL' def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration def get_host_fingerprint(self, host): """Gets Microsoft SQL Server instance information by querying the SQL Browser service. diff --git a/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py similarity index 91% rename from infection_monkey/network/mysqlfinger.py rename to monkey/infection_monkey/network/mysqlfinger.py index 39baa05ac..70080c12b 100644 --- a/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -1,9 +1,10 @@ import logging import socket -from model.host import VictimHost -from network import HostFinger -from .tools import struct_unpack_tracker, struct_unpack_tracker_string +import infection_monkey.config +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostFinger +from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string MYSQL_PORT = 3306 SQL_SERVICE = 'mysqld-3306' @@ -20,7 +21,7 @@ class MySQLFinger(HostFinger): HEADER_SIZE = 4 # in bytes def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration def get_host_fingerprint(self, host): """ diff --git a/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py similarity index 85% rename from infection_monkey/network/network_scanner.py rename to monkey/infection_monkey/network/network_scanner.py index 20133a9a7..2ccdfe74c 100644 --- a/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -1,11 +1,11 @@ import logging import time -from config import WormConfiguration -from info import local_ips, get_interfaces_ranges from common.network.network_range import * -from model import VictimHost -from . import HostScanner +from infection_monkey.config import WormConfiguration +from infection_monkey.network.info import local_ips, get_interfaces_ranges +from infection_monkey.model import VictimHost +from infection_monkey.network import HostScanner __author__ = 'itamar' @@ -63,7 +63,15 @@ class NetworkScanner(object): return subnets_to_scan def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): - assert issubclass(scan_type, HostScanner) + """ + Finds machines according to the ranges specified in the object + :param scan_type: A hostscanner class, will be instanced and used to scan for new machines + :param max_find: Max number of victims to find regardless of ranges + :param stop_callback: A callback to check at any point if we should stop scanning + :return: yields a sequence of VictimHost instances + """ + if not scan_type: + return scanner = scan_type() victims_count = 0 diff --git a/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py similarity index 91% rename from infection_monkey/network/ping_scanner.py rename to monkey/infection_monkey/network/ping_scanner.py index 7162c36f3..075b57669 100644 --- a/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -4,8 +4,9 @@ import re import subprocess import sys -from model.host import VictimHost -from . import HostScanner, HostFinger +import infection_monkey.config +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostScanner, HostFinger __author__ = 'itamar' @@ -20,7 +21,7 @@ LOG = logging.getLogger(__name__) class PingScanner(HostScanner, HostFinger): def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration self._devnull = open(os.devnull, "w") self._ttl_regex = re.compile(TTL_REGEX_STR, re.IGNORECASE) diff --git a/infection_monkey/network/smbfinger.py b/monkey/infection_monkey/network/smbfinger.py similarity index 96% rename from infection_monkey/network/smbfinger.py rename to monkey/infection_monkey/network/smbfinger.py index 9ccb52422..ab92f2761 100644 --- a/infection_monkey/network/smbfinger.py +++ b/monkey/infection_monkey/network/smbfinger.py @@ -1,10 +1,11 @@ import socket import struct import logging -from network import HostFinger -from model.host import VictimHost from odict import odict +from infection_monkey.network import HostFinger +from infection_monkey.model.host import VictimHost + SMB_PORT = 445 SMB_SERVICE = 'tcp-445' @@ -100,7 +101,8 @@ class SMBSessionFingerData(Packet): class SMBFinger(HostFinger): def __init__(self): - self._config = __import__('config').WormConfiguration + from infection_monkey.config import WormConfiguration + self._config = WormConfiguration def get_host_fingerprint(self, host): assert isinstance(host, VictimHost) diff --git a/infection_monkey/network/sshfinger.py b/monkey/infection_monkey/network/sshfinger.py similarity index 85% rename from infection_monkey/network/sshfinger.py rename to monkey/infection_monkey/network/sshfinger.py index 89c3092d7..21deb8814 100644 --- a/infection_monkey/network/sshfinger.py +++ b/monkey/infection_monkey/network/sshfinger.py @@ -1,8 +1,9 @@ import re -from model.host import VictimHost -from network import HostFinger -from network.tools import check_tcp_port +import infection_monkey.config +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostFinger +from infection_monkey.network.tools import check_tcp_port SSH_PORT = 22 SSH_SERVICE_DEFAULT = 'tcp-22' @@ -14,7 +15,7 @@ LINUX_DIST_SSH = ['ubuntu', 'debian'] class SSHFinger(HostFinger): def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE) @staticmethod diff --git a/infection_monkey/network/tcp_scanner.py b/monkey/infection_monkey/network/tcp_scanner.py similarity index 85% rename from infection_monkey/network/tcp_scanner.py rename to monkey/infection_monkey/network/tcp_scanner.py index 625173e97..d864e3e73 100644 --- a/infection_monkey/network/tcp_scanner.py +++ b/monkey/infection_monkey/network/tcp_scanner.py @@ -1,8 +1,9 @@ from itertools import izip_longest from random import shuffle -from network import HostScanner, HostFinger -from network.tools import check_tcp_ports, tcp_port_to_service +import infection_monkey.config +from infection_monkey.network import HostScanner, HostFinger +from infection_monkey.network.tools import check_tcp_ports, tcp_port_to_service __author__ = 'itamar' @@ -11,7 +12,7 @@ BANNER_READ = 1024 class TcpScanner(HostScanner, HostFinger): def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration def is_host_alive(self, host): return self.get_host_fingerprint(host, True) diff --git a/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py similarity index 100% rename from infection_monkey/network/tools.py rename to monkey/infection_monkey/network/tools.py diff --git a/infection_monkey/pyinstaller_utils.py b/monkey/infection_monkey/pyinstaller_utils.py similarity index 100% rename from infection_monkey/pyinstaller_utils.py rename to monkey/infection_monkey/pyinstaller_utils.py diff --git a/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt similarity index 100% rename from infection_monkey/readme.txt rename to monkey/infection_monkey/readme.txt diff --git a/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt similarity index 100% rename from infection_monkey/requirements.txt rename to monkey/infection_monkey/requirements.txt diff --git a/infection_monkey/system_info/SSH_info_collector.py b/monkey/infection_monkey/system_info/SSH_info_collector.py similarity index 100% rename from infection_monkey/system_info/SSH_info_collector.py rename to monkey/infection_monkey/system_info/SSH_info_collector.py diff --git a/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py similarity index 95% rename from infection_monkey/system_info/__init__.py rename to monkey/infection_monkey/system_info/__init__.py index 667ff9890..fbfbcbd7a 100644 --- a/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -5,8 +5,8 @@ import sys import psutil from enum import IntEnum -from network.info import get_host_subnets -from azure_cred_collector import AzureCollector +from infection_monkey.network.info import get_host_subnets +from infection_monkey.system_info.azure_cred_collector import AzureCollector LOG = logging.getLogger(__name__) @@ -112,7 +112,7 @@ class InfoCollector(object): Updates the credentials structure, creating it if neccesary (compat with mimikatz) :return: None. Updates class information """ - from config import WormConfiguration + from infection_monkey.config import WormConfiguration if not WormConfiguration.extract_azure_creds: return LOG.debug("Harvesting creds if on an Azure machine") diff --git a/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py similarity index 100% rename from infection_monkey/system_info/azure_cred_collector.py rename to monkey/infection_monkey/system_info/azure_cred_collector.py diff --git a/infection_monkey/system_info/linux_info_collector.py b/monkey/infection_monkey/system_info/linux_info_collector.py similarity index 84% rename from infection_monkey/system_info/linux_info_collector.py rename to monkey/infection_monkey/system_info/linux_info_collector.py index d80efff6a..466177b49 100644 --- a/infection_monkey/system_info/linux_info_collector.py +++ b/monkey/infection_monkey/system_info/linux_info_collector.py @@ -1,7 +1,7 @@ import logging -from . import InfoCollector -from SSH_info_collector import SSHCollector +from infection_monkey.system_info import InfoCollector +from infection_monkey.system_info.SSH_info_collector import SSHCollector __author__ = 'uri' diff --git a/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py similarity index 95% rename from infection_monkey/system_info/mimikatz_collector.py rename to monkey/infection_monkey/system_info/mimikatz_collector.py index 6aa59f958..1d8294ce5 100644 --- a/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -4,7 +4,9 @@ import logging import socket import zipfile -from pyinstaller_utils import get_binary_file_path, get_binaries_dir_path +import infection_monkey.config + +from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path __author__ = 'itay.mizeretz' @@ -26,7 +28,7 @@ class MimikatzCollector(object): MIMIKATZ_ZIP_PASSWORD = r'VTQpsJPXgZuXhX6x3V84G' def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration self._isInit = False self._dll = None self._collect = None diff --git a/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py similarity index 96% rename from infection_monkey/system_info/windows_info_collector.py rename to monkey/infection_monkey/system_info/windows_info_collector.py index 15d8e2f92..3af347fd2 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -9,6 +9,9 @@ import _winreg from mimikatz_collector import MimikatzCollector from . import InfoCollector +import infection_monkey.config +from infection_monkey.system_info.mimikatz_collector import MimikatzCollector +from infection_monkey.system_info import InfoCollector LOG = logging.getLogger(__name__) LOG.info('started windows info collector') @@ -30,12 +33,12 @@ WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", "DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime", "DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp", "DS_lastLogoff", "DS_logonCount", "DS_accountExpires"), - + "ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName", "DS_sAMAccountType", "DS_objectSid", "DS_objectClass", "DS_name", "DS_memberOf", "DS_member", "DS_instanceType", "DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"), - + "ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires", "DS_adminDisplayName", "DS_badPasswordTime", "DS_badPwdCount", "DS_cn", "DS_distinguishedName", @@ -51,20 +54,20 @@ WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", def fix_obj_for_mongo(o): if type(o) == dict: return dict([(k, fix_obj_for_mongo(v)) for k, v in o.iteritems()]) - + elif type(o) in (list, tuple): return [fix_obj_for_mongo(i) for i in o] - + elif type(o) in (int, float, bool): return o - + elif type(o) in (str, unicode): # mongo dosn't like unprintable chars, so we use repr :/ return repr(o) - + elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object: return fix_wmi_obj_for_mongo(o) - + elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch: try: # objectSid property of ds_user is problematic and need thie special treatment. @@ -73,15 +76,15 @@ def fix_obj_for_mongo(o): return o.Value except: pass - + try: return o.GetObjectText_() except: pass - + return repr(o) - else: + else: return repr(o) def fix_wmi_obj_for_mongo(o): @@ -111,7 +114,7 @@ def fix_wmi_obj_for_mongo(o): value = method() value = fix_obj_for_mongo(value) row[method_name[3:]] = value - + except wmi.x_wmi: continue @@ -125,8 +128,8 @@ class WindowsInfoCollector(InfoCollector): def __init__(self): super(WindowsInfoCollector, self).__init__() + self._config = infection_monkey.config.WormConfiguration self.info['reg'] = {} - self._config = __import__('config').WormConfiguration def get_info(self): """ @@ -140,12 +143,12 @@ class WindowsInfoCollector(InfoCollector): self.get_process_list() self.get_network_info() self.get_azure_info() - + self.get_wmi_info() LOG.debug('finished get_wmi_info') #self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa") self.get_installed_packages() - + mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() if mimikatz_info: diff --git a/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py similarity index 98% rename from infection_monkey/system_singleton.py rename to monkey/infection_monkey/system_singleton.py index 970905a9c..9f56c238e 100644 --- a/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -3,7 +3,7 @@ import logging import sys from abc import ABCMeta, abstractmethod -from config import WormConfiguration +from infection_monkey.config import WormConfiguration __author__ = 'itamar' diff --git a/monkey/infection_monkey/transport/__init__.py b/monkey/infection_monkey/transport/__init__.py new file mode 100644 index 000000000..735ef670a --- /dev/null +++ b/monkey/infection_monkey/transport/__init__.py @@ -0,0 +1,4 @@ +from infection_monkey.transport.http import HTTPServer, LockedHTTPServer + + +__author__ = 'hoffer' diff --git a/infection_monkey/transport/base.py b/monkey/infection_monkey/transport/base.py similarity index 96% rename from infection_monkey/transport/base.py rename to monkey/infection_monkey/transport/base.py index dae0ff072..e6a5bc366 100644 --- a/infection_monkey/transport/base.py +++ b/monkey/infection_monkey/transport/base.py @@ -3,6 +3,7 @@ from threading import Thread g_last_served = None + class TransportProxyBase(Thread): def __init__(self, local_port, dest_host=None, dest_port=None, local_host=''): global g_last_served diff --git a/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py similarity index 98% rename from infection_monkey/transport/http.py rename to monkey/infection_monkey/transport/http.py index b65fda4e9..00ced7198 100644 --- a/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -8,8 +8,8 @@ from logging import getLogger from urlparse import urlsplit from threading import Lock -import monkeyfs -from base import TransportProxyBase, update_last_serve_time +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time __author__ = 'hoffer' diff --git a/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py similarity index 96% rename from infection_monkey/transport/tcp.py rename to monkey/infection_monkey/transport/tcp.py index eaa94de1c..e910e657f 100644 --- a/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -1,9 +1,10 @@ import socket import select from threading import Thread -from base import TransportProxyBase, update_last_serve_time from logging import getLogger +from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time + READ_BUFFER_SIZE = 8192 DEFAULT_TIMEOUT = 30 diff --git a/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py similarity index 95% rename from infection_monkey/tunnel.py rename to monkey/infection_monkey/tunnel.py index 9a50679ff..d589ac98b 100644 --- a/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -5,11 +5,11 @@ import time from difflib import get_close_matches from threading import Thread -from model import VictimHost -from network.firewall import app as firewall -from network.info import local_ips, get_free_tcp_port -from network.tools import check_tcp_port -from transport.base import get_last_serve_time +from infection_monkey.model import VictimHost +from infection_monkey.network.firewall import app as firewall +from infection_monkey.network.info import local_ips, get_free_tcp_port +from infection_monkey.network.tools import check_tcp_port +from infection_monkey.transport.base import get_last_serve_time __author__ = 'hoffer' diff --git a/infection_monkey/utils.py b/monkey/infection_monkey/utils.py similarity index 92% rename from infection_monkey/utils.py rename to monkey/infection_monkey/utils.py index b39cd44f5..3f04ed9fb 100644 --- a/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -2,7 +2,7 @@ import os import sys import struct -from config import WormConfiguration +from infection_monkey.config import WormConfiguration def get_monkey_log_path(): diff --git a/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py similarity index 82% rename from infection_monkey/windows_upgrader.py rename to monkey/infection_monkey/windows_upgrader.py index 4ee0462c5..67b1c3cbd 100644 --- a/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -5,12 +5,12 @@ import shutil import time -import monkeyfs -from config import WormConfiguration -from control import ControlClient -from exploit.tools import build_monkey_commandline_explicitly -from model import MONKEY_CMDLINE_WINDOWS -from utils import is_windows_os, is_64bit_windows_os, is_64bit_python +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.config import WormConfiguration +from infection_monkey.control import ControlClient +from infection_monkey.exploit.tools import build_monkey_commandline_explicitly +from infection_monkey.model import MONKEY_CMDLINE_WINDOWS +from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python __author__ = 'itay.mizeretz' diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py new file mode 100644 index 000000000..104b5efdf --- /dev/null +++ b/monkey/monkey_island.py @@ -0,0 +1,4 @@ +import monkey_island.cc.main + +if "__main__" == __name__: + monkey_island.cc.main.main() diff --git a/monkey/monkey_island/__init__.py b/monkey/monkey_island/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/monkey/monkey_island/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/monkey_island/cc/__init__.py b/monkey/monkey_island/cc/__init__.py similarity index 100% rename from monkey_island/cc/__init__.py rename to monkey/monkey_island/cc/__init__.py diff --git a/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py similarity index 89% rename from monkey_island/cc/app.py rename to monkey/monkey_island/cc/app.py index 6b9ac1154..f5823ef88 100644 --- a/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -4,7 +4,7 @@ from datetime import datetime import bson import flask_restful from bson.json_util import dumps -from flask import Flask, send_from_directory, make_response +from flask import Flask, send_from_directory, make_response, Response from werkzeug.exceptions import NotFound from cc.auth import init_jwt @@ -29,18 +29,24 @@ from cc.services.config import ConfigService __author__ = 'Barak' +HOME_FILE = 'index.html' + + def serve_static_file(static_path): if static_path.startswith('api/'): raise NotFound() try: - return send_from_directory('ui/dist', static_path) + return send_from_directory(os.path.join(os.getcwd(), 'monkey_island/cc/ui/dist'), static_path) except NotFound: # Because react uses various urls for same index page, this is probably the user's intention. + if static_path == HOME_FILE: + flask_restful.abort( + Response("Page not found. Make sure you ran the npm script and the cwd is monkey\\monkey.", 500)) return serve_home() def serve_home(): - return serve_static_file('index.html') + return serve_static_file(HOME_FILE) def normalize_obj(obj): diff --git a/monkey_island/cc/auth.py b/monkey/monkey_island/cc/auth.py similarity index 100% rename from monkey_island/cc/auth.py rename to monkey/monkey_island/cc/auth.py diff --git a/monkey_island/cc/binaries/.gitignore b/monkey/monkey_island/cc/binaries/.gitignore similarity index 100% rename from monkey_island/cc/binaries/.gitignore rename to monkey/monkey_island/cc/binaries/.gitignore diff --git a/monkey_island/cc/database.py b/monkey/monkey_island/cc/database.py similarity index 100% rename from monkey_island/cc/database.py rename to monkey/monkey_island/cc/database.py diff --git a/monkey_island/cc/encryptor.py b/monkey/monkey_island/cc/encryptor.py similarity index 96% rename from monkey_island/cc/encryptor.py rename to monkey/monkey_island/cc/encryptor.py index 90009d1b0..3a3d052f6 100644 --- a/monkey_island/cc/encryptor.py +++ b/monkey/monkey_island/cc/encryptor.py @@ -9,7 +9,7 @@ __author__ = "itay.mizeretz" class Encryptor: _BLOCK_SIZE = 32 - _DB_PASSWORD_FILENAME = "mongo_key.bin" + _DB_PASSWORD_FILENAME = "monkey_island/cc/mongo_key.bin" def __init__(self): self._load_key() diff --git a/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py similarity index 100% rename from monkey_island/cc/environment/__init__.py rename to monkey/monkey_island/cc/environment/__init__.py diff --git a/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py similarity index 100% rename from monkey_island/cc/environment/aws.py rename to monkey/monkey_island/cc/environment/aws.py diff --git a/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py similarity index 90% rename from monkey_island/cc/environment/environment.py rename to monkey/monkey_island/cc/environment/environment.py index 094b9c235..9e89208ef 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -13,7 +13,7 @@ ENV_DICT = { def load_env_from_file(): - with open('server_config.json', 'r') as f: + with open('monkey_island/cc/server_config.json', 'r') as f: config_content = f.read() config_json = json.loads(config_content) return config_json['server_config'] diff --git a/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py similarity index 100% rename from monkey_island/cc/environment/standard.py rename to monkey/monkey_island/cc/environment/standard.py diff --git a/monkey_island/cc/island_logger.py b/monkey/monkey_island/cc/island_logger.py similarity index 100% rename from monkey_island/cc/island_logger.py rename to monkey/monkey_island/cc/island_logger.py diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey/monkey_island/cc/island_logger_default_config.json similarity index 100% rename from monkey_island/cc/island_logger_default_config.json rename to monkey/monkey_island/cc/island_logger_default_config.json diff --git a/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py similarity index 84% rename from monkey_island/cc/main.py rename to monkey/monkey_island/cc/main.py index 74450cb29..a86d13913 100644 --- a/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -6,10 +6,6 @@ import time import logging BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -PARENT_PATH = os.path.dirname(BASE_PATH) - -if PARENT_PATH not in sys.path: - sys.path.insert(0, PARENT_PATH) if BASE_PATH not in sys.path: sys.path.insert(0, BASE_PATH) @@ -38,11 +34,11 @@ def main(): app = init_app(mongo_url) if env.is_debug(): - app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key')) + app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key')) else: http_server = HTTPServer(WSGIContainer(app), - ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'), - 'keyfile': os.environ.get('SERVER_KEY', 'server.key')}) + ssl_options={'certfile': os.environ.get('SERVER_CRT', 'monkey_island/cc/server.crt'), + 'keyfile': os.environ.get('SERVER_KEY', 'monkey_island/cc/server.key')}) http_server.listen(env.get_island_port()) logger.info( 'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) diff --git a/monkey_island/cc/resources/__init__.py b/monkey/monkey_island/cc/resources/__init__.py similarity index 100% rename from monkey_island/cc/resources/__init__.py rename to monkey/monkey_island/cc/resources/__init__.py diff --git a/monkey_island/cc/resources/client_run.py b/monkey/monkey_island/cc/resources/client_run.py similarity index 100% rename from monkey_island/cc/resources/client_run.py rename to monkey/monkey_island/cc/resources/client_run.py diff --git a/monkey_island/cc/resources/edge.py b/monkey/monkey_island/cc/resources/edge.py similarity index 100% rename from monkey_island/cc/resources/edge.py rename to monkey/monkey_island/cc/resources/edge.py diff --git a/monkey_island/cc/resources/island_logs.py b/monkey/monkey_island/cc/resources/island_logs.py similarity index 100% rename from monkey_island/cc/resources/island_logs.py rename to monkey/monkey_island/cc/resources/island_logs.py diff --git a/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py similarity index 92% rename from monkey_island/cc/resources/local_run.py rename to monkey/monkey_island/cc/resources/local_run.py index 7b8965e1e..6e7d44cb9 100644 --- a/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -26,8 +26,8 @@ def run_local_monkey(): if not result: return False, "OS Type not found" - monkey_path = os.path.join('binaries', result['filename']) - target_path = os.path.join(os.getcwd(), result['filename']) + monkey_path = os.path.join(os.getcwd(), 'monkey_island', 'cc', 'binaries', result['filename']) + target_path = os.path.join(os.getcwd(), 'monkey_island', result['filename']) # copy the executable to temp path (don't run the monkey from its current location as it may delete itself) try: diff --git a/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py similarity index 100% rename from monkey_island/cc/resources/log.py rename to monkey/monkey_island/cc/resources/log.py diff --git a/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py similarity index 100% rename from monkey_island/cc/resources/monkey.py rename to monkey/monkey_island/cc/resources/monkey.py diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py similarity index 100% rename from monkey_island/cc/resources/monkey_configuration.py rename to monkey/monkey_island/cc/resources/monkey_configuration.py diff --git a/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py similarity index 100% rename from monkey_island/cc/resources/monkey_download.py rename to monkey/monkey_island/cc/resources/monkey_download.py diff --git a/monkey_island/cc/resources/netmap.py b/monkey/monkey_island/cc/resources/netmap.py similarity index 100% rename from monkey_island/cc/resources/netmap.py rename to monkey/monkey_island/cc/resources/netmap.py diff --git a/monkey_island/cc/resources/node.py b/monkey/monkey_island/cc/resources/node.py similarity index 100% rename from monkey_island/cc/resources/node.py rename to monkey/monkey_island/cc/resources/node.py diff --git a/monkey_island/cc/resources/report.py b/monkey/monkey_island/cc/resources/report.py similarity index 100% rename from monkey_island/cc/resources/report.py rename to monkey/monkey_island/cc/resources/report.py diff --git a/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py similarity index 100% rename from monkey_island/cc/resources/root.py rename to monkey/monkey_island/cc/resources/root.py diff --git a/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py similarity index 100% rename from monkey_island/cc/resources/telemetry.py rename to monkey/monkey_island/cc/resources/telemetry.py diff --git a/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py similarity index 100% rename from monkey_island/cc/resources/telemetry_feed.py rename to monkey/monkey_island/cc/resources/telemetry_feed.py diff --git a/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json similarity index 100% rename from monkey_island/cc/server_config.json rename to monkey/monkey_island/cc/server_config.json diff --git a/monkey_island/cc/services/__init__.py b/monkey/monkey_island/cc/services/__init__.py similarity index 100% rename from monkey_island/cc/services/__init__.py rename to monkey/monkey_island/cc/services/__init__.py diff --git a/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py similarity index 100% rename from monkey_island/cc/services/config.py rename to monkey/monkey_island/cc/services/config.py diff --git a/monkey_island/cc/services/edge.py b/monkey/monkey_island/cc/services/edge.py similarity index 100% rename from monkey_island/cc/services/edge.py rename to monkey/monkey_island/cc/services/edge.py diff --git a/monkey/monkey_island/cc/services/group_info.py b/monkey/monkey_island/cc/services/group_info.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey_island/cc/services/island_logs.py b/monkey/monkey_island/cc/services/island_logs.py similarity index 100% rename from monkey_island/cc/services/island_logs.py rename to monkey/monkey_island/cc/services/island_logs.py diff --git a/monkey_island/cc/services/log.py b/monkey/monkey_island/cc/services/log.py similarity index 100% rename from monkey_island/cc/services/log.py rename to monkey/monkey_island/cc/services/log.py diff --git a/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py similarity index 100% rename from monkey_island/cc/services/node.py rename to monkey/monkey_island/cc/services/node.py diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py new file mode 100644 index 000000000..fe25fd494 --- /dev/null +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -0,0 +1,259 @@ +from cc.services.pth_report_utils import PassTheHashReport, Machine + + +class PTHReportService(object): + + """ + + """ + + def __init__(self): + pass + + @staticmethod + def get_duplicated_password_nodes(pth): + """ + + """ + + usernames_lists = [] + usernames_per_sid_list = [] + dups = dict(map(lambda x: (x, len(pth.GetSidsBySecret(x))), pth.GetAllSecrets())) + + for secret, count in sorted(dups.iteritems(), key=lambda (k, v): (v, k), reverse=True): + if count <= 1: + continue + for sid in pth.GetSidsBySecret(secret): + if sid: + usernames_per_sid_list.append(pth.GetUsernameBySid(sid)) + + usernames_lists.append({'cred_group': usernames_per_sid_list}) + + return usernames_lists + + @staticmethod + def get_shared_local_admins_nodes(pth): + dups = dict(map(lambda x: (x, len(pth.GetSharedAdmins(x))), pth.machines)) + shared_admin_machines = [] + for m, count in sorted(dups.iteritems(), key=lambda (k, v): (v, k), reverse=True): + if count <= 0: + continue + shared_admin_account_list = [] + + for sid in pth.GetSharedAdmins(m): + shared_admin_account_list.append(pth.GetUsernameBySid(sid)) + + machine = { + 'ip': m.GetIp(), + 'hostname': m.GetHostName(), + 'domain': m.GetDomainName(), + 'services_names': m.GetCriticalServicesInstalled(), + 'user_count': count, + 'admins_accounts': shared_admin_account_list + } + + shared_admin_machines.append(machine) + + return shared_admin_machines + + @staticmethod + def get_strong_users_on_crit_services_by_machine(pth): + threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetCritialServers())) + strong_users_crit_list = [] + + for m, count in sorted(threatening.iteritems(), key=lambda (k, v): (v, k), reverse=True): + if count <= 0: + continue + + threatening_users_attackers_dict = {} + for sid in pth.GetThreateningUsersByVictim(m): + username = pth.GetUsernameBySid(sid) + threatening_users_attackers_dict[username] = [] + for mm in pth.GetAttackersBySid(sid): + if m == mm: + continue + threatening_users_attackers_dict[username] = mm.GetIp() + + machine = { + 'ip': m.GetIp(), + 'hostname': m.GetHostName(), + 'domain': m.GetDomainName(), + 'services': m.GetCriticalServicesInstalled(), + 'threatening_users': threatening_users_attackers_dict + } + strong_users_crit_list.append(machine) + return strong_users_crit_list + + @staticmethod + def get_strong_users_on_crit_services_by_user(pth): + critical_servers = pth.GetCritialServers() + strong_users_dict = {} + + for server in critical_servers: + users = pth.GetThreateningUsersByVictim(server) + for sid in users: + username = pth.GetUsernameBySid(sid) + if username not in strong_users_dict: + strong_users_dict[username] = { + 'services_names': [], + 'machines': [] + } + strong_users_dict[username]['username'] = username + strong_users_dict[username]['domain'] = server.GetDomainName() + strong_users_dict[username]['services_names'] += server.GetCriticalServicesInstalled() + strong_users_dict[username]['machines'].append(server.GetHostName()) + + return strong_users_dict.values() + + @staticmethod + def get_strong_users_on_non_crit_services(pth): + threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetNonCritialServers())) + + strong_users_non_crit_list = [] + + for m, count in sorted(threatening.iteritems(), key=lambda (k, v): (v, k), reverse=True): + if count <= 0: + continue + + threatening_users_attackers_dict = {} + for sid in pth.GetThreateningUsersByVictim(m): + username = pth.GetUsernameBySid(sid) + threatening_users_attackers_dict[username] = [] + for mm in pth.GetAttackersBySid(sid): + if m == mm: + continue + threatening_users_attackers_dict[username] = mm.GetIp() + + machine = { + 'ip': m.GetIp(), + 'hostname': m.GetHostName(), + 'domain': m.GetDomainName(), + 'services_names': [], + 'user_count': count, + 'threatening_users': threatening_users_attackers_dict + } + strong_users_non_crit_list.append(machine) + return strong_users_non_crit_list + + @staticmethod + def get_duplicated_passwords_issues(pth, password_groups): + issues = [] + previeous_group = [] + for group in password_groups: + username = group['cred_group'][0] + if username in previeous_group: + continue + sid = list(pth.GetSidsByUsername(username.split('\\')[1])) + machine_info = pth.GetSidInfo(sid[0]) + issues.append( + { + 'type': 'shared_passwords', + 'machine': machine_info.get('hostname').split('.')[0], + 'shared_with': group['cred_group'] + } + ) + previeous_group += group['cred_group'] + + return issues + + @staticmethod + def get_shared_local_admins_issues(shared_admins_machines): + issues = [] + for machine in shared_admins_machines: + issues.append( + { + 'type': 'shared_admins', + 'machine': machine.get('hostname'), + 'shared_accounts': machine.get('admins_accounts'), + 'ip': machine.get('ip'), + } + ) + + return issues + + @staticmethod + def strong_users_on_crit_issues(strong_users): + issues = [] + for machine in strong_users: + issues.append( + { + 'type': 'strong_users_on_crit', + 'machine': machine.get('hostname'), + 'services': machine.get('services'), + 'ip': machine.get('ip'), + 'threatening_users': machine.get('threatening_users').keys() + } + ) + + return issues + + @staticmethod + def get_machine_details(node_id): + machine = Machine(node_id) + node = {} + if machine.latest_system_info: + node = { + "id": str(node_id), + "label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()), + 'group': 'critical' if machine.IsCriticalServer() else 'normal', + 'users': list(machine.GetCachedUsernames()), + 'ips': [machine.GetIp()], + 'services': machine.GetCriticalServicesInstalled(), + 'hostname': machine.GetHostName() + } + return node + + @staticmethod + def generate_map_nodes(pth): + nodes_list = [] + for node_id in pth.vertices: + node = PTHReportService.get_machine_details(node_id) + nodes_list.append(node) + + return nodes_list + + @staticmethod + def get_issues_list(issues): + issues_dict = {} + + for issue in issues: + machine = issue['machine'] + if machine not in issues_dict: + issues_dict[machine] = [] + issues_dict[machine].append(issue) + + return issues_dict + + @staticmethod + def get_report(): + + issues = [] + pth = PassTheHashReport() + + same_password = PTHReportService.get_duplicated_password_nodes(pth) + local_admin_shared = PTHReportService.get_shared_local_admins_nodes(pth) + strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services_by_user(pth) + strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth) + + issues += PTHReportService.get_duplicated_passwords_issues(pth, same_password) + issues += PTHReportService.get_shared_local_admins_issues(local_admin_shared) + issues += PTHReportService.strong_users_on_crit_issues( + PTHReportService.get_strong_users_on_crit_services_by_machine(pth)) + + report = \ + { + 'report_info': + { + 'same_password': same_password, + 'local_admin_shared': local_admin_shared, + 'strong_users_on_crit_services': strong_users_on_crit_services, + 'strong_users_on_non_crit_services': strong_users_on_non_crit_services, + 'pth_issues': issues + }, + 'pthmap': + { + 'nodes': PTHReportService.generate_map_nodes(pth), + 'edges': pth.edges + } + } + return report \ No newline at end of file diff --git a/monkey/monkey_island/cc/services/pth_report_utils.py b/monkey/monkey_island/cc/services/pth_report_utils.py new file mode 100644 index 000000000..61fe78765 --- /dev/null +++ b/monkey/monkey_island/cc/services/pth_report_utils.py @@ -0,0 +1,958 @@ +import hashlib +import binascii +import copy +import uuid + +from cc.database import mongo +from cc.services.node import NodeService + +DsRole_RoleStandaloneWorkstation = 0 +DsRole_RoleMemberWorkstation = 1 +DsRole_RoleStandaloneServer = 2 +DsRole_RoleMemberServer = 3 +DsRole_RoleBackupDomainController = 4 +DsRole_RolePrimaryDomainController = 5 + +SidTypeUser = 1 +SidTypeGroup = 2 +SidTypeDomain = 3 +SidTypeAlias = 4 +SidTypeWellKnownGroup = 5 +SidTypeDeletedAccount = 6 +SidTypeInvalid = 7 +SidTypeUnknown = 8 +SidTypeComputer = 9 + + +def is_group_sid_type(type): + return type in (SidTypeGroup, SidTypeAlias, SidTypeWellKnownGroup) + + +def myntlm(x): + hash = hashlib.new('md4', x.encode('utf-16le')).digest() + return str(binascii.hexlify(hash)) + + +def cache(foo): + def hash(o): + if type(o) in (int, float, str, unicode): + return repr(o) + + elif type(o) in (type(None),): + return "___None___" + + elif type(o) in (list, tuple, set): + hashed = tuple([hash(x) for x in o]) + + if "NotHashable" in hashed: + return "NotHashable" + + return hashed + + elif type(o) == dict: + hashed_keys = tuple([hash(k) for k, v in o.iteritems()]) + hashed_vals = tuple([hash(v) for k, v in o.iteritems()]) + + if "NotHashable" in hashed_keys or "NotHashable" in hashed_vals: + return "NotHashable" + + return tuple(zip(hashed_keys, hashed_vals)) + + elif type(o) == Machine: + return o.monkey_guid + + # elif type(o) == PthMap: + # return "PthMapSingleton" + + elif type(o) == PassTheHashReport: + return "PassTheHashReportSingleton" + + else: + assert False, "%s of type %s is not hashable" % (repr(o), type(o)) + return "NotHashable" + + def wrapper(*args, **kwargs): + hashed = (hash(args), hash(kwargs)) + + if "NotHashable" in hashed: + return foo(*args, **kwargs) + + if not hasattr(foo, "_mycache_"): + foo._mycache_ = dict() + + if hashed not in foo._mycache_.keys(): + foo._mycache_[hashed] = foo(*args, **kwargs) + + return copy.deepcopy(foo._mycache_[hashed]) + + return wrapper + + +class Machine(object): + def __init__(self, monkey_guid): + self.monkey_guid = str(monkey_guid) + + self.latest_system_info = mongo.db.telemetry.find( + {"telem_type": "system_info_collection", "monkey_guid": self.monkey_guid}).sort([("timestamp", -1)]).limit( + 1) + + if self.latest_system_info.count() > 0: + self.latest_system_info = self.latest_system_info[0] + else: + self.latest_system_info = None + + self.monkey_info = NodeService.get_monkey_by_guid(self.monkey_guid) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.monkey_guid == other.monkey_guid + else: + return False + + @cache + def GetMimikatzOutput(self): + doc = self.latest_system_info + + if not doc: + return None + + return doc.get("data").get("mimikatz") + + @cache + def GetHostName(self): + doc = self.latest_system_info + + for comp in doc.get("data").get("Win32_ComputerSystem", {}): + return eval(comp.get("Name")) + + return None + + @cache + def GetIp(self): + doc = self.latest_system_info + + for addr in doc.get("data").get("network_info", {}).get("networks", {}): + return str(addr["addr"]) + + return None + + @cache + def get_monkey_id(self): + doc = self.monkey_info + + return str(doc.get('_id')) + + @cache + def GetDomainName(self): + doc = self.latest_system_info + + for comp in doc.get("data").get("Win32_ComputerSystem", {}): + return eval(comp.get("Domain")) + + return None + + @cache + def GetDomainRole(self): + doc = self.latest_system_info + + for comp in doc.get("data").get("Win32_ComputerSystem", {}): + return comp.get("DomainRole") + + return None + + @cache + def IsDomainController(self): + return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController) + + #@cache + def GetSidByUsername(self, username, domain=None): + doc = self.latest_system_info + + for user in doc.get("data").get("Win32_UserAccount", {}): + if eval(user.get("Name")) != username: + continue + + if user.get("SIDType") != SidTypeUser: + continue + + if domain and user.get("Domain") != domain: + continue + + return eval(user.get("SID")) + + if not self.IsDomainController(): + for dc in self.GetDomainControllers(): + sid = dc.GetSidByUsername(username) + + if sid != None: + return sid + + return None + + @cache + def GetUsernameBySid(self, sid): + info = self.GetSidInfo(sid) + + if not info: + return None + + return str(info.get("Domain")) + "\\" + str(info.get("Username")) + + @cache + def GetSidInfo(self, sid): + doc = self.latest_system_info + + for user in doc.get("data").get("Win32_UserAccount",{}): + if eval(user.get("SID")) != sid: + continue + + if user.get("SIDType") != SidTypeUser: + continue + + return {"Domain": eval(user.get("Domain")), + "Username": eval(user.get("Name")), + "Disabled": user.get("Disabled") == "true", + "PasswordRequired": user.get("PasswordRequired") == "true", + "PasswordExpires": user.get("PasswordExpires") == "true", + 'hostname': doc.get('data').get('hostname'), } + + if not self.IsDomainController(): + for dc in self.GetDomainControllers(): + domain = dc.GetSidInfo(sid) + + if domain != None: + return domain + + return None + + @cache + def GetCriticalServicesInstalled(self): + def IsNameOfCriticalService(name): + services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS') + services = map(str.lower, services) + + if not name: + return False + + name = name.lower() + + return name in services + # for ser in services: + # if ser in name: + # return True + + return False + + doc = self.latest_system_info + found = [] + + if self.IsDomainController(): + found.append("Domain Controller") + + for product in doc.get("data").get("Win32_Product", {}): + service_name = eval(product.get("Name")) + + if not IsNameOfCriticalService(service_name): + continue + + found.append(service_name) + + for service in doc.get("data").get("Win32_Service", {}): + service_name = eval(service.get("Name")) + + if not IsNameOfCriticalService(service_name): + continue + + if eval(service.get("State")) != "Running": + continue + + found.append(service_name) + + return found + + @cache + def IsCriticalServer(self): + return len(self.GetCriticalServicesInstalled()) > 0 + + @cache + def GetUsernamesBySecret(self, secret): + sam = self.GetLocalSecrets() + + names = set() + + for username, user_secret in sam.iteritems(): + if secret == user_secret: + names.add(username) + + return names + + @cache + def GetSidsBySecret(self, secret): + usernames = self.GetUsernamesBySecret(secret) + return set(map(self.GetSidByUsername, usernames)) + + def GetGroupSidByGroupName(self, group_name): + doc = self.latest_system_info + + for group in doc.get('data').get("Win32_Group", {}): + if eval(group.get("Name")) != group_name: + continue + + if not is_group_sid_type(group.get("SIDType")): + continue + + return eval(group.get("SID")) + + return None + + def GetUsersByGroupSid(self, sid): + doc = self.latest_system_info + + users = dict() + + for group_user in doc.get('data').get("Win32_GroupUser", {}): + if eval(group_user.get("GroupComponent", {}).get("SID")) != sid: + continue + + if not is_group_sid_type(group_user.get("GroupComponent", {}).get("SIDType")): + continue + + if "PartComponent" not in group_user.keys(): + continue + + if type(group_user.get("PartComponent")) in (str, unicode): + # PartComponent is an id to Win32_UserAccount table + + wmi_id = group_user.get("PartComponent") + + if "cimv2:Win32_UserAccount" not in wmi_id: + continue + + username = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0] + domain = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-1] + + sid = self.GetSidByUsername(username, domain) + users[sid] = username + + else: + if group_user.get("PartComponent", {}).get("SIDType") != SidTypeUser: + continue + + users[eval(group_user.get("PartComponent", {}).get("SID"))] = eval(group_user.get("PartComponent") + .get("Name")) + + return users + + @cache + def GetDomainControllersMonkeyGuidByDomainName(self, domain_name): + cur = mongo.db.telemetry.find( + {"telem_type": "system_info_collection", "data.Win32_ComputerSystem.Domain": "u'%s'" % (domain_name,)}) + + GUIDs = set() + + for doc in cur: + if not Machine(doc.get("monkey_guid")).IsDomainController(): + continue + + GUIDs.add(doc.get("monkey_guid")) + + return GUIDs + + def GetLocalAdmins(self): + admins = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")) + + # debug = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Users")) + # admins.update(debug) + + return admins + + def GetLocalAdminSids(self): + return set(self.GetLocalAdmins().keys()) + + @cache + def GetLocalSids(self): + doc = self.latest_system_info + + SIDs = set() + + for user in doc.get('data').get("Win32_UserAccount", {}): + if user.get("SIDType") != SidTypeUser: + continue + + SIDs.add(eval(user.get("SID"))) + + return SIDs + + @cache + def GetLocalAdminNames(self): + return set(self.GetLocalAdmins().values()) + + @cache + def GetSam(self): + if not self.GetMimikatzOutput(): + return {} + + mimikatz = self.GetMimikatzOutput() + + if mimikatz.count("\n42.") != 2: + return {} + + try: + sam_users = mimikatz.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] + + sam = {} + + for sam_user_txt in sam_users: + sam_user = dict([map(unicode.strip, line.split(":")) for line in + filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) + + ntlm = sam_user.get("NTLM") + if "[hashed secret]" not in ntlm: + continue + + sam[sam_user.get("User")] = ntlm.replace("[hashed secret]", "").strip() + + return sam + + except: + return {} + + @cache + def GetNtds(self): + if not self.GetMimikatzOutput(): + return {} + + mimikatz = self.GetMimikatzOutput() + + if mimikatz.count("\n42.") != 2: + return {} + + ntds_users = mimikatz.split("\n42.")[2].split("\nRID :")[1:] + ntds = {} + + for ntds_user_txt in ntds_users: + user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip() + ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip() + ntlm = ntlm.replace("[hashed secret]", "").strip() + + if ntlm: + ntds[user] = ntlm + + return ntds + + @cache + def GetLocalSecrets(self): + sam = self.GetSam() + ntds = self.GetNtds() + + secrets = sam.copy() + secrets.update(ntds) + + return secrets + + @cache + def GetLocalAdminSecrets(self): + return set(self.GetLocalAdminCreds().values()) + + @cache + def GetLocalAdminCreds(self): + admin_names = self.GetLocalAdminNames() + sam = self.GetLocalSecrets() + + admin_creds = dict() + + for username, secret in sam.iteritems(): + if username not in admin_names: + continue + + admin_creds[username] = secret + + return admin_creds + + @cache + def GetCachedSecrets(self): + return set(self.GetCachedCreds().values()) + + @cache + def GetCachedCreds(self): + doc = self.latest_system_info + + creds = dict() + + if not self.GetMimikatzOutput(): + return {} + + mimikatz = self.GetMimikatzOutput() + + for user in mimikatz.split("\n42.")[0].split("Authentication Id")[1:]: + username = None + secret = None + + for line in user.splitlines(): + if "User Name" in line: + username = line.split(":")[1].strip() + + if ("NTLM" in line or "Password" in line) and "[hashed secret]" in line: + secret = line.split(":")[1].replace("[hashed secret]", "").strip() + + if username and secret: + creds[username] = secret + + return creds + + @cache + def GetDomainControllers(self): + domain_name = self.GetDomainName() + DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name) + return map(Machine, DCs) + + def GetDomainAdminsOfMachine(self): + DCs = self.GetDomainControllers() + + domain_admins = set() + + for dc in DCs: + domain_admins |= set(dc.GetUsersByGroupSid(self.GetGroupSidByGroupName("Domain Admins")).keys()) + + return domain_admins + + #@cache + def GetAdmins(self): + return self.GetLocalAdminSids() # | self.GetDomainAdminsOfMachine() + + @cache + def GetAdminNames(self): + return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins())) + + #@cache + def GetCachedSids(self): + doc = self.latest_system_info + + SIDs = set() + + for username in doc.get('data').get("credentials", {}): + sid = self.GetSidByUsername(username) + + if not sid: + sid = "__USERNAME__" + username + + SIDs.add(sid) + + return SIDs + + @cache + def GetCachedUsernames(self): + doc = self.latest_system_info + + names = set() + + for username in doc.get('data').get("credentials", {}): + names.add(username) + + return names + + +class PassTheHashReport(object): + + def __init__(self): + self.vertices = self.GetAllMachines() + + self.machines = map(Machine, self.vertices) + self.edges = self.get_edges_by_sid() # Useful for non-cached domain users + #self.edges |= self.GetEdgesBySamHash() # This will add edges based only on password hash without caring about username + + + def GetAllMachines(self): + cur = mongo.db.telemetry.find({"telem_type": "system_info_collection"}) + + GUIDs = set() + + for doc in cur: + GUIDs.add(doc.get("monkey_guid")) + + return GUIDs + + @cache + def ReprSidList(self, sid_list, victim): + users_list = [] + + for sid in sid_list: + username = Machine(victim).GetUsernameBySid(sid) + + if username: + users_list.append(username) + + return users_list + + @cache + def ReprSecretList(self, secret_list, victim): + relevant_users_list = [] + + for secret in secret_list: + relevant_users_list.append(Machine(victim).GetUsernamesBySecret(secret)) + + return relevant_users_list + + @staticmethod + def __get_edge_label(attacker, victim): + attacker_monkey = NodeService.get_monkey_by_guid(attacker) + victim_monkey = NodeService.get_monkey_by_guid(victim) + + attacker_label = NodeService.get_monkey_label(attacker_monkey) + victim_label = NodeService.get_monkey_label(victim_monkey) + + RIGHT_ARROW = u"\u2192" + return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label) + + def get_edges_by_sid(self): + edges_list = [] + + for attacker in self.vertices: + cached = list(self.GetCachedSids(Machine(attacker))) + + for victim in self.vertices: + if attacker == victim: + continue + + admins = list(Machine(victim).GetAdmins()) + + cached_admins = [i for i in cached if i in admins] + + if cached_admins: + relevant_users_list = self.ReprSidList(cached_admins, victim) + edges_list.append( + { + 'from': attacker, + 'to': victim, + 'users': relevant_users_list, + '_label': PassTheHashReport.__get_edge_label(attacker, victim), + 'id': str(uuid.uuid4()) + }) + + return edges_list + + @cache + def GetEdgesBySamHash(self): + edges = set() + + for attacker in self.vertices: + cached_creds = set(Machine(attacker).GetCachedCreds().items()) + + for victim in self.vertices: + if attacker == victim: + continue + + admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) + + if len(cached_creds & admin_creds) > 0: + label = self.ReprSecretList(set(dict(cached_creds & admin_creds).values()), victim) + edges.add((attacker, victim, label)) + + return edges + + @cache + def GetEdgesByUsername(self): + edges = set() + + for attacker in self.vertices: + cached = Machine(attacker).GetCachedUsernames() + + for victim in self.vertices: + if attacker == victim: + continue + + admins = Machine(victim).GetAdminNames() + + if len(cached & admins) > 0: + edges.add((attacker, victim)) + + return edges + + @cache + def GetPossibleAttackCountBySid(self, sid): + return len(self.GetPossibleAttacksBySid(sid)) + + @cache + def GetPossibleAttacksByAttacker(self, attacker): + attacks = set() + + cached_creds = set(Machine(attacker).GetCachedCreds().items()) + + for victim in self.vertices: + if attacker == victim: + continue + + admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) + + if len(cached_creds & admin_creds) > 0: + curr_attacks = dict(cached_creds & admin_creds) + attacks.add((attacker, victim, curr_attacks)) + + return attacks + + @cache + def GetPossibleAttacksBySid(self, sid): + attacks = set() + + for attacker in self.vertices: + tmp = self.GetPossibleAttacksByAttacker(attacker) + + for _, victim, curr_attacks in tmp: + for username, secret in curr_attacks.iteritems(): + if Machine(victim).GetSidByUsername(username) == sid: + attacks.add((attacker, victim)) + + return attacks + + @cache + def GetSecretBySid(self, sid): + for m in self.machines: + for user, user_secret in m.GetLocalSecrets().iteritems(): + if m.GetSidByUsername(user) == sid: + return user_secret + + return None + + @cache + def GetVictimCountBySid(self, sid): + return len(self.GetVictimsBySid(sid)) + + @cache + def GetVictimCountByMachine(self, attacker): + return len(self.GetVictimsByAttacker(attacker)) + + @cache + def GetAttackCountBySecret(self, secret): + return len(self.GetAttackersBySecret(secret)) + + @cache + def GetAllUsernames(self): + names = set() + + for sid in self.GetAllSids(): + names.add(self.GetUsernameBySid(sid)) + + return names + + @cache + def GetAllSids(self): + SIDs = set() + + for m in self.machines: + SIDs |= m.GetLocalSids() + + return SIDs + + @cache + def GetAllSecrets(self): + secrets = set() + + for m in self.machines: + for secret in m.GetLocalAdminSecrets(): + secrets.add(secret) + + return secrets + + @cache + def GetUsernameBySid(self, sid): + for m in self.machines: + username = m.GetUsernameBySid(sid) + + if username: + return username + + return None + + @cache + def GetSidInfo(self, sid): + for m in self.machines: + info = m.GetSidInfo(sid) + + if info: + return info + + return None + + @cache + def GetSidsBySecret(self, secret): + SIDs = set() + + for m in self.machines: + SIDs |= m.GetSidsBySecret(secret) + + return SIDs + + @cache + def GetAllDomainControllers(self): + DCs = set() + + for m in self.machines: + if m.IsDomainController(): + DCs.add(m) + + return DCs + + @cache + def GetSidsByUsername(self, username): + SIDs = set() + + for m in self.machines: + sid = m.GetSidByUsername(username) + if sid: + SIDs.add(sid) + + return SIDs + + @cache + def GetVictimsBySid(self, sid): + machines = set() + + for m in self.machines: + if sid in m.GetAdmins(): + machines.add(m) + + return machines + + @cache + def GetVictimsBySecret(self, secret): + machines = set() + + SIDs = self.GetSidsBySecret(secret) + + for m in self.machines: + if len(SIDs & m.GetAdmins()) > 0: + machines.add(m) + + return machines + + @cache + def GetAttackersBySecret(self, secret): + machines = set() + + for m in self.machines: + if secret in m.GetCachedSecrets(): + machines.add(m) + + return machines + + @cache + def GetAttackersByVictim(self, victim): + if type(victim) != unicode: + victim = victim.monkey_guid + + attackers = set() + + for edge in self.edges: + if edge.get('to', None) == victim: + attackers.add(edge.get('from', None)) + + return set(map(Machine, attackers)) + + @cache + def GetAttackersBySid(self, sid): + machines = set() + + for m in self.machines: + if sid in self.GetCachedSids(m): + machines.add(m) + + return machines + + @cache + def GetVictimsByAttacker(self, attacker): + if type(attacker) != unicode: + attacker = attacker.monkey_guid + + victims = set() + + for atck, vic, _ in self.edges: + if atck == attacker: + victims.add(vic) + + return set(map(Machine, victims)) + + @cache + def GetInPathCountByVictim(self, victim, already_processed=None): + if type(victim) != unicode: + victim = victim.monkey_guid + + if not already_processed: + already_processed = set([victim]) + + count = 0 + + for atck, vic, _ in self.edges: + if atck == vic: + continue + + if vic != victim: + continue + + if atck in already_processed: + continue + + count += 1 + + already_processed.add(atck) + count += self.GetInPathCountByVictim(atck, already_processed) + + return count + + @cache + def GetCritialServers(self): + machines = set() + + for m in self.machines: + if m.IsCriticalServer(): + machines.add(m) + + return machines + + @cache + def GetNonCritialServers(self): + return set(self.machines) - self.GetCritialServers() + + #@cache + def GetCachedSids(self, m): + sids = set() + tmp = m.GetCachedSids() + + for sid in tmp: + if sid.startswith("__USERNAME__"): + + s = self.GetSidsByUsername(sid[len("__USERNAME__"):]) + if len(s) == 1: + sids.add(s.pop()) + else: + sids.add(sid) + + else: + sids.add(sid) + + return sids + + @cache + def GetThreateningUsersByVictim(self, victim): + threatening_users = set() + + for attacker in self.GetAttackersByVictim(victim): + # For each attacker, get the cached users and check which of them is an admin on the victim + threatening_users |= (self.GetCachedSids(attacker) & victim.GetAdmins()) + + return threatening_users + + def GetSharedAdmins(self, m): + shared_admins = [] + + for other in self.machines: + if m == other: + continue + for sid in m.GetLocalAdminSids(): + if sid in other.GetLocalAdminSids(): + shared_admins.append(sid) + + #shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids()) + + shared_admins = [admin for admin in shared_admins if admin not in list(m.GetDomainAdminsOfMachine())] + + return shared_admins diff --git a/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py similarity index 100% rename from monkey_island/cc/services/report.py rename to monkey/monkey_island/cc/services/report.py diff --git a/monkey/monkey_island/cc/services/user_info.py b/monkey/monkey_island/cc/services/user_info.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey_island/cc/ui/.babelrc b/monkey/monkey_island/cc/ui/.babelrc similarity index 100% rename from monkey_island/cc/ui/.babelrc rename to monkey/monkey_island/cc/ui/.babelrc diff --git a/monkey_island/cc/ui/.editorconfig b/monkey/monkey_island/cc/ui/.editorconfig similarity index 100% rename from monkey_island/cc/ui/.editorconfig rename to monkey/monkey_island/cc/ui/.editorconfig diff --git a/monkey_island/cc/ui/.eslintrc b/monkey/monkey_island/cc/ui/.eslintrc similarity index 100% rename from monkey_island/cc/ui/.eslintrc rename to monkey/monkey_island/cc/ui/.eslintrc diff --git a/monkey_island/cc/ui/.gitignore b/monkey/monkey_island/cc/ui/.gitignore similarity index 100% rename from monkey_island/cc/ui/.gitignore rename to monkey/monkey_island/cc/ui/.gitignore diff --git a/monkey_island/cc/ui/.yo-rc.json b/monkey/monkey_island/cc/ui/.yo-rc.json similarity index 100% rename from monkey_island/cc/ui/.yo-rc.json rename to monkey/monkey_island/cc/ui/.yo-rc.json diff --git a/monkey_island/cc/ui/karma.conf.js b/monkey/monkey_island/cc/ui/karma.conf.js similarity index 100% rename from monkey_island/cc/ui/karma.conf.js rename to monkey/monkey_island/cc/ui/karma.conf.js diff --git a/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json similarity index 100% rename from monkey_island/cc/ui/package-lock.json rename to monkey/monkey_island/cc/ui/package-lock.json diff --git a/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json similarity index 100% rename from monkey_island/cc/ui/package.json rename to monkey/monkey_island/cc/ui/package.json diff --git a/monkey_island/cc/ui/server.js b/monkey/monkey_island/cc/ui/server.js similarity index 100% rename from monkey_island/cc/ui/server.js rename to monkey/monkey_island/cc/ui/server.js diff --git a/monkey_island/cc/ui/src/components/AuthComponent.js b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js similarity index 100% rename from monkey_island/cc/ui/src/components/AuthComponent.js rename to monkey/monkey_island/cc/ui/src/components/AuthComponent.js diff --git a/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js similarity index 100% rename from monkey_island/cc/ui/src/components/Main.js rename to monkey/monkey_island/cc/ui/src/components/Main.js diff --git a/monkey_island/cc/ui/src/components/map/MapOptions.js b/monkey/monkey_island/cc/ui/src/components/map/MapOptions.js similarity index 100% rename from monkey_island/cc/ui/src/components/map/MapOptions.js rename to monkey/monkey_island/cc/ui/src/components/map/MapOptions.js diff --git a/monkey/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js new file mode 100644 index 000000000..e06043c20 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js @@ -0,0 +1,247 @@ +import React from 'react'; +import {Icon} from 'react-fa'; +import Toggle from 'react-toggle'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import download from 'downloadjs' +import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane'; + +class InfMapPreviewPaneComponent extends PreviewPaneComponent { + + osRow(asset) { + return ( +
Operating System | +{edge.os.type} | +
---|---|
IP Address | +{edge.ip_address} | +
Services | +{edge.services.map(val => {val} )} |
+
Hostname | +{asset.hostname} | +
---|---|
IP Addresses | +{asset.ips.map(val => {val} )} |
+
Services | +{asset.services.map(val => {val} )} |
+
Compromised Users | +{asset.users.map(val => {val} )} |
+
Compromised Users | +{edge.users.map(val => {val} )} |
+
---|