diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 5e52022cd..2d2a93939 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -2,11 +2,9 @@ import hashlib import os import json import sys -import types import uuid from abc import ABCMeta from itertools import product -import importlib __author__ = 'itamar' @@ -20,10 +18,6 @@ HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden" class Configuration(object): 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 list(formatted_data.items()): if key.startswith('_'): @@ -32,15 +26,10 @@ class Configuration(object): continue if self._depth_from_commandline and key == "depth": continue - # handle in cases - elif key == 'exploiter_classes': - class_objects = [getattr(exploit_import, val) for val in value] - setattr(self, key, class_objects) + if hasattr(self, key): + setattr(self, key, value) else: - if hasattr(self, key): - setattr(self, key, value) - else: - unknown_items.append(key) + unknown_items.append(key) return unknown_items def from_json(self, json_data): diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py new file mode 100644 index 000000000..50f4167d8 --- /dev/null +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -0,0 +1,98 @@ +from abc import abstractmethod + +from infection_monkey.config import WormConfiguration +from common.utils.exploit_enum import ExploitType +from datetime import datetime + +from infection_monkey.utils.plugins.plugin import Plugin +import infection_monkey.exploit + +__author__ = 'itamar' + + +class HostExploiter(Plugin): + @staticmethod + def should_run(class_name): + """ + Decides if post breach action is enabled in config + :return: True if it needs to be ran, false otherwise + """ + return class_name in WormConfiguration.exploiter_classes + + @staticmethod + def base_package_file(): + return infection_monkey.exploit.__file__ + + @staticmethod + def base_package_name(): + return infection_monkey.exploit.__package__ + + _TARGET_OS_TYPE = [] + + # Usual values are 'vulnerability' or 'brute_force' + EXPLOIT_TYPE = ExploitType.VULNERABILITY + + @property + @abstractmethod + def _EXPLOITED_SERVICE(self): + pass + + def __init__(self, host): + self._config = WormConfiguration + self.exploit_info = {'display_name': self._EXPLOITED_SERVICE, + 'started': '', + 'finished': '', + 'vulnerable_urls': [], + 'vulnerable_ports': [], + 'executed_cmds': []} + self.exploit_attempts = [] + self.host = host + + def set_start_time(self): + self.exploit_info['started'] = datetime.now().isoformat() + + def set_finish_time(self): + self.exploit_info['finished'] = datetime.now().isoformat() + + def is_os_supported(self): + return self.host.os.get('type') in self._TARGET_OS_TYPE + + def send_exploit_telemetry(self, result): + from infection_monkey.telemetry.exploit_telem import ExploitTelem + ExploitTelem(self, result).send() + + def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''): + self.exploit_attempts.append({'result': result, 'user': user, 'password': password, + 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) + + def exploit_host(self): + self.pre_exploit() + try: + result = self._exploit_host() + finally: + self.post_exploit() + return result + + def pre_exploit(self): + self.set_start_time() + + def post_exploit(self): + self.set_finish_time() + + @abstractmethod + def _exploit_host(self): + raise NotImplementedError() + + def add_vuln_url(self, url): + self.exploit_info['vulnerable_urls'].append(url) + + def add_vuln_port(self, port): + self.exploit_info['vulnerable_ports'].append(port) + + def add_executed_cmd(self, cmd): + """ + Appends command to exploiter's info. + :param cmd: String of executed command. e.g. 'echo Example' + """ + powershell = True if "powershell" in cmd.lower() else False + self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell}) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 306350437..e69de29bb 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -1,92 +0,0 @@ -from abc import ABCMeta, abstractmethod, abstractproperty -import infection_monkey.config -from common.utils.exploit_enum import ExploitType -from datetime import datetime - -__author__ = 'itamar' - - -class HostExploiter(object, metaclass=ABCMeta): - _TARGET_OS_TYPE = [] - - # Usual values are 'vulnerability' or 'brute_force' - EXPLOIT_TYPE = ExploitType.VULNERABILITY - - @property - @abstractmethod - def _EXPLOITED_SERVICE(self): - pass - - def __init__(self, host): - self._config = infection_monkey.config.WormConfiguration - self.exploit_info = {'display_name': self._EXPLOITED_SERVICE, - 'started': '', - 'finished': '', - 'vulnerable_urls': [], - 'vulnerable_ports': [], - 'executed_cmds': []} - self.exploit_attempts = [] - self.host = host - - def set_start_time(self): - self.exploit_info['started'] = datetime.now().isoformat() - - def set_finish_time(self): - self.exploit_info['finished'] = datetime.now().isoformat() - - def is_os_supported(self): - return self.host.os.get('type') in self._TARGET_OS_TYPE - - def send_exploit_telemetry(self, result): - from infection_monkey.telemetry.exploit_telem import ExploitTelem - ExploitTelem(self, result).send() - - def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''): - self.exploit_attempts.append({'result': result, 'user': user, 'password': password, - 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) - - def exploit_host(self): - self.pre_exploit() - try: - result = self._exploit_host() - finally: - self.post_exploit() - return result - - def pre_exploit(self): - self.set_start_time() - - def post_exploit(self): - self.set_finish_time() - - @abstractmethod - def _exploit_host(self): - raise NotImplementedError() - - def add_vuln_url(self, url): - self.exploit_info['vulnerable_urls'].append(url) - - def add_vuln_port(self, port): - self.exploit_info['vulnerable_ports'].append(port) - - def add_executed_cmd(self, cmd): - """ - Appends command to exploiter's info. - :param cmd: String of executed command. e.g. 'echo Example' - """ - powershell = True if "powershell" in cmd.lower() else False - self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell}) - - -from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter -from infection_monkey.exploit.wmiexec import WmiExploiter -from infection_monkey.exploit.smbexec import SmbExploiter -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 -from infection_monkey.exploit.mssqlexec import MSSQLExploiter -from infection_monkey.exploit.vsftpd import VSFTPDExploiter diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 16e69dff0..9d2aff5b0 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -6,7 +6,7 @@ from time import sleep import pymssql from common.utils.exploit_enum import ExploitType -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 4e1d71fd9..4820d0f05 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -16,7 +16,7 @@ from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_ from impacket.smbconnection import SMBConnection import infection_monkey.monkeyfs as monkeyfs -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.model import DROPPER_ARG from infection_monkey.network.smbfinger import SMB_SERVICE from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 21237e2ab..718e10617 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -7,7 +7,7 @@ from random import choice import requests from common.utils.attack_utils import ScanStatus -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.shellshock_resources import CGI_FILES diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index fef8dad05..f53e1ac38 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -3,7 +3,7 @@ from logging import getLogger from impacket.dcerpc.v5 import transport, scmr from impacket.smbconnection import SMB_DIALECT -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index a7e9571b5..4fbc484eb 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -5,7 +5,7 @@ import time import paramiko import infection_monkey.monkeyfs as monkeyfs -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port, get_interface_to_target diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index d4116c96c..82954b99b 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -8,7 +8,7 @@ import socket import time from common.utils.attack_utils import ScanStatus -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, build_monkey_commandline, get_monkey_depth from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index b894acf43..bef428f4d 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -3,7 +3,7 @@ import re from posixpath import join from abc import abstractmethod -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import CHECK_COMMAND, ID_STRING, GET_ARCH_LINUX, GET_ARCH_WINDOWS, BITSADMIN_CMDLINE_HTTP, \ diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index ba2fbb352..f98cad534 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -7,7 +7,7 @@ from requests import post, exceptions from http.server import BaseHTTPRequestHandler, HTTPServer from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.network.tools import get_interface_to_target from infection_monkey.network.info import get_free_tcp_port from http.server import BaseHTTPRequestHandler, HTTPServer diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index b84cf3391..f296091d6 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -19,7 +19,7 @@ from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from infection_monkey.network.smbfinger import SMBFinger from infection_monkey.network.tools import check_tcp_port -from . import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter LOG = getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index cc286bfcd..adaf524e2 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -5,7 +5,7 @@ import traceback from impacket.dcerpc.v5.rpcrt import DCERPCException -from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, \ get_monkey_depth, build_monkey_commandline from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 3d0b9d3d7..80d2d8642 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -30,6 +30,7 @@ from infection_monkey.network.tools import get_interface_to_target from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from common.utils.attack_utils import ScanStatus, UsageEnum +from infection_monkey.exploit.HostExploiter import HostExploiter __author__ = 'itamar' @@ -144,10 +145,10 @@ class InfectionMonkey(object): self._network.initialize() - self._exploiters = WormConfiguration.exploiter_classes - self._fingerprint = HostFinger.get_instances() + self._exploiters = HostExploiter.get_classes() + if not self._keep_running or not WormConfiguration.alive: break diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py new file mode 100644 index 000000000..e759a4116 --- /dev/null +++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +hiddenimports = collect_submodules('infection_monkey.exploit') +datas = (collect_data_files('infection_monkey.exploit', include_py_files=True)) diff --git a/monkey/infection_monkey/utils/plugins/plugin.py b/monkey/infection_monkey/utils/plugins/plugin.py index 21d3134bf..d80ebccc1 100644 --- a/monkey/infection_monkey/utils/plugins/plugin.py +++ b/monkey/infection_monkey/utils/plugins/plugin.py @@ -4,7 +4,7 @@ import logging from abc import ABCMeta, abstractmethod from os.path import dirname, basename, isfile, join import glob -from typing import Sequence, TypeVar, Type +from typing import Sequence, TypeVar, Type, Callable LOG = logging.getLogger(__name__) @@ -14,7 +14,7 @@ def _get_candidate_files(base_package_file): return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')] -Plugin_type = TypeVar('Plugin_type', bound='Plugin') +PluginType = TypeVar('PluginType', bound='Plugin') class Plugin(metaclass=ABCMeta): @@ -25,11 +25,11 @@ class Plugin(metaclass=ABCMeta): raise NotImplementedError() @classmethod - def get_instances(cls) -> Sequence[Type[Plugin_type]]: + def get_classes(cls) -> Sequence[Callable]: """ - Returns the type objects from base_package_spec. + Returns the class objects from base_package_spec base_package name and file must refer to the same package otherwise bad results - :return: A list of parent_class objects. + :return: A list of parent_class classes. """ objects = [] candidate_files = _get_candidate_files(cls.base_package_file()) @@ -47,13 +47,29 @@ class Plugin(metaclass=ABCMeta): LOG.debug("Checking if should run object {}".format(class_object.__name__)) try: if class_object.should_run(class_object.__name__): - instance = class_object() - objects.append(instance) + objects.append(class_object) LOG.debug("Added {} to list".format(class_object.__name__)) except Exception as e: LOG.warning("Exception {} when checking if {} should run".format(str(e), class_object.__name__)) return objects + @classmethod + def get_instances(cls) -> Sequence[Type[PluginType]]: + """ + Returns the type objects from base_package_spec. + base_package name and file must refer to the same package otherwise bad results + :return: A list of parent_class objects. + """ + class_objects = cls.get_classes() + instances = [] + for class_object in class_objects: + try: + instance = class_object() + instances.append(instance) + except Exception as e: + LOG.warning("Exception {} when initializing {}".format(str(e), class_object.__name__)) + return instances + @staticmethod @abstractmethod def base_package_file(): diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py new file mode 100644 index 000000000..14508aec1 --- /dev/null +++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py @@ -0,0 +1,5 @@ +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin + + +class SomeDummyPlugin: + pass diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py new file mode 100644 index 000000000..18e83c052 --- /dev/null +++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py @@ -0,0 +1,7 @@ +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin + + +class BadPluginInit(TestPlugin): + + def __init__(self): + raise Exception("TestException") diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py new file mode 100644 index 000000000..2d73cd65b --- /dev/null +++ b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py @@ -0,0 +1,15 @@ +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin + + +class NoInheritance: + pass + + +class BadInit(TestPlugin): + + def __init__(self): + raise Exception("TestException") + + +class ProperClass(TestPlugin): + pass diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py new file mode 100644 index 000000000..a3785e875 --- /dev/null +++ b/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py @@ -0,0 +1,22 @@ +from infection_monkey.utils.plugins.plugin import Plugin +import infection_monkey.utils.plugins.pluginTests + + +class TestPlugin(Plugin): + classes_to_load = [] + + @staticmethod + def should_run(class_name): + """ + Decides if post breach action is enabled in config + :return: True if it needs to be ran, false otherwise + """ + return class_name in TestPlugin.classes_to_load + + @staticmethod + def base_package_file(): + return infection_monkey.utils.plugins.pluginTests.__file__ + + @staticmethod + def base_package_name(): + return infection_monkey.utils.plugins.pluginTests.__package__ diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py new file mode 100644 index 000000000..a3fe237b6 --- /dev/null +++ b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py @@ -0,0 +1,5 @@ +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin + + +class PluginWorking(TestPlugin): + pass diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/__init__.py b/monkey/infection_monkey/utils/plugins/pluginTests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/utils/plugins/plugin_test.py b/monkey/infection_monkey/utils/plugins/plugin_test.py new file mode 100644 index 000000000..7841c6c6a --- /dev/null +++ b/monkey/infection_monkey/utils/plugins/plugin_test.py @@ -0,0 +1,36 @@ +from unittest import TestCase + +from infection_monkey.utils.plugins.pluginTests.ComboFile import BadInit, ProperClass +from infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking +from infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin +from infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin + + +class PluginTester(TestCase): + + def test_combo_file(self): + TestPlugin.classes_to_load = [BadInit.__name__, ProperClass.__name__] + to_init = TestPlugin.get_classes() + self.assertEqual(len(to_init), 2) + objects = TestPlugin.get_instances() + self.assertEqual(len(objects), 1) + + def test_bad_init(self): + TestPlugin.classes_to_load = [BadPluginInit.__name__] + to_init = TestPlugin.get_classes() + self.assertEqual(len(to_init), 1) + objects = TestPlugin.get_instances() + self.assertEqual(len(objects), 0) + + def test_bad_import(self): + TestPlugin.classes_to_load = [SomeDummyPlugin.__name__] + to_init = TestPlugin.get_classes() + self.assertEqual(len(to_init), 0) + + def test_flow(self): + TestPlugin.classes_to_load = [PluginWorking.__name__] + to_init = TestPlugin.get_classes() + self.assertEqual(len(to_init), 1) + objects = TestPlugin.get_instances() + self.assertEqual(len(objects), 1)