diff --git a/CHANGELOG.md b/CHANGELOG.md index b40f94bcf..9dda4c7f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - The order and content of Monkey Island's initialization logging to give clearer instructions to the user and avoid confusion. #1684 - The process list collection system info collector to now be a post-breach action. #1697 +- The "/api/monkey/download" endpoint to accept an OS and return a file. #1675 ### Removed - VSFTPD exploiter. #1533 @@ -48,6 +49,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - ShellShock exploiter. #1733 - ElasticGroovy exploiter. #1732 - T1082 attack technique report. #1754 +- 32-bit agents. #1675 ### Fixed - A bug in network map page that caused delay of telemetry log loading. #1545 @@ -57,6 +59,10 @@ Changelog](https://keepachangelog.com/en/1.0.0/). ### Security +- Change SSH exploiter so that it does not set the permissions of the agent + binary in /tmp on the target system to 777, as this could allow a malicious + actor with local access to escalate their privileges. #1750 + ## [1.13.0] - 2022-01-25 ### Added - A new exploiter that allows propagation via the Log4Shell vulnerability diff --git a/build_scripts/common.sh b/build_scripts/common.sh index 2f244fd51..abeef5f0d 100644 --- a/build_scripts/common.sh +++ b/build_scripts/common.sh @@ -42,9 +42,7 @@ download_monkey_agent_binaries() { load_monkey_binary_config mkdir -p "${island_binaries_path}" || handle_error - curl -L -o "${island_binaries_path}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}" curl -L -o "${island_binaries_path}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}" - curl -L -o "${island_binaries_path}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}" curl -L -o "${island_binaries_path}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}" } diff --git a/deployment_scripts/config b/deployment_scripts/config index 9ef065ce9..2a0807ce0 100644 --- a/deployment_scripts/config +++ b/deployment_scripts/config @@ -25,15 +25,9 @@ get_latest_release() { MONKEY_LATEST_RELEASE=$(get_latest_release "guardicore/monkey") # Monkey binaries -export LINUX_32_BINARY_NAME="monkey-linux-32" -export LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-32" - export LINUX_64_BINARY_NAME="monkey-linux-64" export LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-64" -export WINDOWS_32_BINARY_NAME="monkey-windows-32.exe" -export WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-32.exe" - export WINDOWS_64_BINARY_NAME="monkey-windows-64.exe" export WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-64.exe" diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1 index e4dc7484b..2a647a328 100644 --- a/deployment_scripts/config.ps1 +++ b/deployment_scripts/config.ps1 @@ -12,12 +12,8 @@ $PYTHON_URL = "https://www.python.org/ftp/python/3.7.7/python-3.7.7-amd64.exe" # Monkey binaries -$LINUX_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-32" -$LINUX_32_BINARY_PATH = "monkey-linux-32" $LINUX_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-64" $LINUX_64_BINARY_PATH = "monkey-linux-64" -$WINDOWS_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-32.exe" -$WINDOWS_32_BINARY_PATH = "monkey-windows-32.exe" $WINDOWS_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-64.exe" $WINDOWS_64_BINARY_PATH = "monkey-windows-64.exe" diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 1826c4ffc..763bb9075 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -93,7 +93,7 @@ log_message "Cloning files from git" branch=${2:-"develop"} log_message "Branch selected: ${branch}" if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned - git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error + git clone --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error fi # Create folders @@ -161,20 +161,15 @@ agents=${3:-true} if [ "$agents" = true ] ; then log_message "Downloading binaries" if exists wget; then - wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL} wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL} - wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL} wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL} else - curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-32 ${LINUX_32_BINARY_URL} curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-64 ${LINUX_64_BINARY_URL} - curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-32.exe ${WINDOWS_32_BINARY_URL} curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-64.exe ${WINDOWS_64_BINARY_URL} fi fi # Allow them to be executed -chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME" chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME" # If a user haven't installed mongo manually check if we can install it with our script @@ -207,8 +202,7 @@ if ! exists npm; then fi pushd "$ISLAND_PATH/cc/ui" || handle_error -npm install sass-loader node-sass webpack --save-dev -npm update +npm ci log_message "Generating front end" npm run dist diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 22d228346..f5d313322 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -209,9 +209,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, "Adding binaries" $binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries") New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue - $webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH)) $webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH)) - $webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH)) $webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH)) } diff --git a/docs/content/development/setup-development-environment.md b/docs/content/development/setup-development-environment.md index f2e739f3a..94cf3acbe 100644 --- a/docs/content/development/setup-development-environment.md +++ b/docs/content/development/setup-development-environment.md @@ -16,7 +16,7 @@ The agent (which we sometimes refer to as the Infection Monkey) is a single Pyth In order to compile the Infection Monkey for distribution by the Monkey Island, you'll need to run the instructions listed in the [`readme.txt`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/readme.txt) on each supported environment. -This means setting up an environment with Linux 32/64-bit with Python installed and a Windows 64-bit machine with developer tools, along with 32/64-bit Python versions. +This means setting up an environment with Linux 64-bit with Python installed and a Windows 64-bit machine with developer tools, along with 64-bit Python versions. ## The Monkey Island diff --git a/docs/content/usage/getting-started.md b/docs/content/usage/getting-started.md index 6572e7b24..b6a90e793 100644 --- a/docs/content/usage/getting-started.md +++ b/docs/content/usage/getting-started.md @@ -7,11 +7,14 @@ pre: " " tags: ["usage"] --- + + + If you haven't deployed the Monkey Island yet, please [refer to our setup documentation](/setup). ## Using the Infection Monkey -After deploying the Monkey Island in your environment, navigate to `https://:5000`. +After deploying the Monkey Island in your environment, navigate to `https://:5000`. ### First-time login diff --git a/envs/monkey_zoo/blackbox/config_templates/base_template.py b/envs/monkey_zoo/blackbox/config_templates/base_template.py index dbc235cd7..5a1ce49a6 100644 --- a/envs/monkey_zoo/blackbox/config_templates/base_template.py +++ b/envs/monkey_zoo/blackbox/config_templates/base_template.py @@ -8,7 +8,7 @@ class BaseTemplate(ConfigTemplate): "basic.exploiters.exploiter_classes": [], "basic_network.scope.local_network_scan": False, "basic_network.scope.depth": 1, - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["HTTPFinger"], "internal.monkey.system_info.system_info_collector_classes": [], "monkey.post_breach.post_breach_actions": [], "internal.general.keep_tunnel_open_time": 0, diff --git a/envs/monkey_zoo/blackbox/config_templates/drupal.py b/envs/monkey_zoo/blackbox/config_templates/drupal.py index 388a47a42..2eefd6337 100644 --- a/envs/monkey_zoo/blackbox/config_templates/drupal.py +++ b/envs/monkey_zoo/blackbox/config_templates/drupal.py @@ -9,7 +9,7 @@ class Drupal(ConfigTemplate): config_values.update( { - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["HTTPFinger"], "basic.exploiters.exploiter_classes": ["DrupalExploiter"], "basic_network.scope.subnet_scan_list": ["10.2.2.28"], "internal.network.tcp_scanner.HTTP_PORTS": [80], diff --git a/envs/monkey_zoo/blackbox/config_templates/mssql.py b/envs/monkey_zoo/blackbox/config_templates/mssql.py index 13d1c728e..403fc0060 100644 --- a/envs/monkey_zoo/blackbox/config_templates/mssql.py +++ b/envs/monkey_zoo/blackbox/config_templates/mssql.py @@ -10,7 +10,7 @@ class Mssql(ConfigTemplate): config_values.update( { "basic.exploiters.exploiter_classes": ["MSSQLExploiter"], - "internal.classes.finger_classes": ["PingScanner"], + "internal.classes.finger_classes": [], "basic_network.scope.subnet_scan_list": ["10.2.2.16"], "basic.credentials.exploit_password_list": [ "Password1!", diff --git a/envs/monkey_zoo/blackbox/config_templates/powershell.py b/envs/monkey_zoo/blackbox/config_templates/powershell.py index a282b2a0a..95137d431 100644 --- a/envs/monkey_zoo/blackbox/config_templates/powershell.py +++ b/envs/monkey_zoo/blackbox/config_templates/powershell.py @@ -21,7 +21,7 @@ class PowerShell(ConfigTemplate): "basic.credentials.exploit_password_list": ["Passw0rd!"], "basic_network.scope.depth": 2, "basic.credentials.exploit_user_list": ["m0nk3y", "m0nk3y-user"], - "internal.classes.finger_classes": ["PingScanner"], + "internal.classes.finger_classes": [], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [], "internal.exploits.exploit_ntlm_hash_list": [ diff --git a/envs/monkey_zoo/blackbox/config_templates/powershell_credentials_reuse.py b/envs/monkey_zoo/blackbox/config_templates/powershell_credentials_reuse.py index d6113dc15..99e4ce282 100644 --- a/envs/monkey_zoo/blackbox/config_templates/powershell_credentials_reuse.py +++ b/envs/monkey_zoo/blackbox/config_templates/powershell_credentials_reuse.py @@ -14,7 +14,7 @@ class PowerShellCredentialsReuse(ConfigTemplate): "10.2.3.46", ], "basic_network.scope.depth": 2, - "internal.classes.finger_classes": ["PingScanner"], + "internal.classes.finger_classes": [], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [], } diff --git a/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py index 25003eb20..828d2da21 100644 --- a/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py +++ b/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py @@ -13,7 +13,7 @@ class SmbMimikatz(ConfigTemplate): "basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"], "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["SMBFinger", "HTTPFinger"], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [445], "monkey.system_info.system_info_collector_classes": [ diff --git a/envs/monkey_zoo/blackbox/config_templates/smb_pth.py b/envs/monkey_zoo/blackbox/config_templates/smb_pth.py index 89a379d15..cd9fed272 100644 --- a/envs/monkey_zoo/blackbox/config_templates/smb_pth.py +++ b/envs/monkey_zoo/blackbox/config_templates/smb_pth.py @@ -13,7 +13,7 @@ class SmbPth(ConfigTemplate): "basic_network.scope.subnet_scan_list": ["10.2.2.15"], "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["SMBFinger", "HTTPFinger"], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [445], "internal.classes.exploits.exploit_ntlm_hash_list": [ diff --git a/envs/monkey_zoo/blackbox/config_templates/ssh.py b/envs/monkey_zoo/blackbox/config_templates/ssh.py index 8099e50a6..5a519d5d1 100644 --- a/envs/monkey_zoo/blackbox/config_templates/ssh.py +++ b/envs/monkey_zoo/blackbox/config_templates/ssh.py @@ -14,7 +14,7 @@ class Ssh(ConfigTemplate): "basic.credentials.exploit_password_list": ["Password1!", "12345678", "^NgDvY59~8"], "basic_network.scope.depth": 2, "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["SSHFinger", "PingScanner"], + "internal.classes.finger_classes": ["SSHFinger"], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [22], } diff --git a/envs/monkey_zoo/blackbox/config_templates/tunneling.py b/envs/monkey_zoo/blackbox/config_templates/tunneling.py index 15fb967d5..d2dd663f5 100644 --- a/envs/monkey_zoo/blackbox/config_templates/tunneling.py +++ b/envs/monkey_zoo/blackbox/config_templates/tunneling.py @@ -28,7 +28,6 @@ class Tunneling(ConfigTemplate): "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], "internal.classes.finger_classes": [ "SSHFinger", - "PingScanner", "HTTPFinger", "SMBFinger", ], diff --git a/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py b/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py index 84e7f3f70..ff2078d72 100644 --- a/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py +++ b/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py @@ -13,7 +13,7 @@ class WmiPth(ConfigTemplate): "basic_network.scope.subnet_scan_list": ["10.2.2.15"], "basic.credentials.exploit_password_list": ["Password1!"], "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["HTTPFinger"], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [135], "internal.exploits.exploit_ntlm_hash_list": [ diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 2da9d0529..aba333c5e 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -89,7 +89,6 @@ class Configuration(object): dropper_set_date = True dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_linux = "/bin/sh" - dropper_target_path_win_32 = r"C:\Windows\temp\monkey32.exe" dropper_target_path_win_64 = r"C:\Windows\temp\monkey64.exe" dropper_target_path_linux = "/tmp/monkey" diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 5abb99fdb..90a7b6078 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -8,7 +8,6 @@ from urllib.parse import urljoin import requests from requests.exceptions import ConnectionError -import infection_monkey.monkeyfs as monkeyfs import infection_monkey.tunnel as tunnel from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT @@ -259,22 +258,6 @@ class ControlClient(object): ControlClient.load_control_config() return not WormConfiguration.alive - @staticmethod - def download_monkey_exe(host): - filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host(host) - if filename is None: - return None - return ControlClient.download_monkey_exe_by_filename(filename, size) - - @staticmethod - def download_monkey_exe_by_os(is_windows, is_32bit): - filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict( - ControlClient.spoof_host_os_info(is_windows, is_32bit) - ) - if filename is None: - return None - return ControlClient.download_monkey_exe_by_filename(filename, size) - @staticmethod def spoof_host_os_info(is_windows, is_32bit): if is_windows: @@ -292,70 +275,6 @@ class ControlClient(object): return {"os": {"type": os, "machine": arch}} - @staticmethod - def download_monkey_exe_by_filename(filename, size): - if not WormConfiguration.current_server: - return None - try: - dest_file = monkeyfs.virtual_path(filename) - if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): - return dest_file - else: - download = requests.get( # noqa: DUO123 - "https://%s/api/monkey/download/%s" - % (WormConfiguration.current_server, filename), - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) - - with monkeyfs.open(dest_file, "wb") as file_obj: - for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): - if chunk: - file_obj.write(chunk) - file_obj.flush() - if size == monkeyfs.getsize(dest_file): - return dest_file - - except Exception as exc: - logger.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc - ) - - @staticmethod - def get_monkey_exe_filename_and_size_by_host(host): - return ControlClient.get_monkey_exe_filename_and_size_by_host_dict(host.as_dict()) - - @staticmethod - def get_monkey_exe_filename_and_size_by_host_dict(host_dict): - if not WormConfiguration.current_server: - return None, None - try: - reply = requests.post( # noqa: DUO123 - "https://%s/api/monkey/download" % (WormConfiguration.current_server,), - data=json.dumps(host_dict), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=LONG_REQUEST_TIMEOUT, - ) - if 200 == reply.status_code: - result_json = reply.json() - filename = result_json.get("filename") - if not filename: - return None, None - size = result_json.get("size") - return filename, size - else: - return None, None - - except Exception as exc: - logger.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc - ) - - return None, None - @staticmethod def create_control_tunnel(): if not WormConfiguration.current_server: diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b1a25d51f..f370e5fdd 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -19,7 +19,6 @@ "dropper_log_path_windows": "%temp%\\~df1562.tmp", "dropper_log_path_linux": "/tmp/user-1562", "dropper_set_date": true, - "dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe", "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe", "dropper_target_path_linux": "/tmp/monkey", diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index b74dc3871..69924b61a 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -9,6 +9,8 @@ from infection_monkey.config import WormConfiguration from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from . import IAgentRepository + logger = logging.getLogger(__name__) @@ -67,9 +69,16 @@ class HostExploiter: ) # TODO: host should be VictimHost, at the moment it can't because of circular dependency - def exploit_host(self, host, telemetry_messenger: ITelemetryMessenger, options: Dict): + def exploit_host( + self, + host, + telemetry_messenger: ITelemetryMessenger, + agent_repository: IAgentRepository, + options: Dict, + ): self.host = host self.telemetry_messenger = telemetry_messenger + self.agent_repository = agent_repository self.options = options self.pre_exploit() diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 42d8d18bf..7e5733502 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -1 +1,3 @@ +from .i_agent_repository import IAgentRepository +from .caching_agent_repository import CachingAgentRepository from .exploiter_wrapper import ExploiterWrapper diff --git a/monkey/infection_monkey/exploit/caching_agent_repository.py b/monkey/infection_monkey/exploit/caching_agent_repository.py new file mode 100644 index 000000000..2e52990b9 --- /dev/null +++ b/monkey/infection_monkey/exploit/caching_agent_repository.py @@ -0,0 +1,37 @@ +import io +from functools import lru_cache +from typing import Mapping + +import requests + +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT + +from . import IAgentRepository + + +class CachingAgentRepository(IAgentRepository): + """ + CachingAgentRepository implements the IAgentRepository interface and downloads the requested + agent binary from the island on request. The agent binary is cached so that only one request is + actually sent to the island for each requested binary. + """ + + def __init__(self, island_url: str, proxies: Mapping[str, str]): + self._island_url = island_url + self._proxies = proxies + + def get_agent_binary(self, os: str, _: str = None) -> io.BytesIO: + return io.BytesIO(self._download_binary_from_island(os)) + + @lru_cache(maxsize=None) + def _download_binary_from_island(self, os: str) -> bytes: + response = requests.get( # noqa: DUO123 + f"{self._island_url}/api/monkey/download/{os}", + verify=False, + proxies=self._proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) + + response.raise_for_status() + + return response.content diff --git a/monkey/infection_monkey/exploit/exploiter_wrapper.py b/monkey/infection_monkey/exploit/exploiter_wrapper.py index 444c89b31..c621ecaea 100644 --- a/monkey/infection_monkey/exploit/exploiter_wrapper.py +++ b/monkey/infection_monkey/exploit/exploiter_wrapper.py @@ -3,6 +3,7 @@ from typing import Dict, Type from infection_monkey.model import VictimHost from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from . import IAgentRepository from .HostExploiter import HostExploiter @@ -16,17 +17,28 @@ class ExploiterWrapper: class Inner: def __init__( - self, exploit_class: Type[HostExploiter], telemetry_messenger: ITelemetryMessenger + self, + exploit_class: Type[HostExploiter], + telemetry_messenger: ITelemetryMessenger, + agent_repository: IAgentRepository, ): self._exploit_class = exploit_class self._telemetry_messenger = telemetry_messenger + self._agent_repository = agent_repository def exploit_host(self, host: VictimHost, options: Dict): exploiter = self._exploit_class() - return exploiter.exploit_host(host, self._telemetry_messenger, options) + return exploiter.exploit_host( + host, self._telemetry_messenger, self._agent_repository, options + ) - def __init__(self, telemetry_messenger: ITelemetryMessenger): + def __init__( + self, telemetry_messenger: ITelemetryMessenger, agent_repository: IAgentRepository + ): self._telemetry_messenger = telemetry_messenger + self._agent_repository = agent_repository def wrap(self, exploit_class: Type[HostExploiter]): - return ExploiterWrapper.Inner(exploit_class, self._telemetry_messenger) + return ExploiterWrapper.Inner( + exploit_class, self._telemetry_messenger, self._agent_repository + ) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 5a3c29b65..69e5c601b 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -42,13 +42,18 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return self.exploit_result - paths = self.get_monkey_paths() - if not paths: - return self.exploit_result - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"]) try: - command = self._build_command(paths["dest_path"], http_path) + dropper_target_path = self.monkey_target_paths[self.host.os["type"]] + except KeyError: + return self.exploit_result + + http_path, http_thread = HTTPTools.create_locked_transfer( + self.host, dropper_target_path, self.agent_repository + ) + + try: + command = self._build_command(dropper_target_path, http_path) if self.exploit(self.vulnerable_urls[0], command): self.add_executed_cmd(command) diff --git a/monkey/infection_monkey/exploit/i_agent_repository.py b/monkey/infection_monkey/exploit/i_agent_repository.py new file mode 100644 index 000000000..f63ca4038 --- /dev/null +++ b/monkey/infection_monkey/exploit/i_agent_repository.py @@ -0,0 +1,21 @@ +import abc +import io + + +class IAgentRepository(metaclass=abc.ABCMeta): + """ + IAgentRepository provides an interface for other components to access agent binaries. Notably, + this is used by exploiters during propagation to retrieve the appropriate agent binary so that + it can be uploaded to a victim and executed. + """ + + @abc.abstractmethod + def get_agent_binary(self, os: str, architecture: str = None) -> io.BytesIO: + """ + Retrieve the appropriate agent binary from the repository. + :param str os: The name of the operating system on which the agent binary will run + :param str architecture: Reserved + :return: A file-like object for the requested agent binary + :rtype: io.BytesIO + """ + pass diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py index 6db20b6a4..324ed0495 100644 --- a/monkey/infection_monkey/exploit/powershell.py +++ b/monkey/infection_monkey/exploit/powershell.py @@ -2,7 +2,6 @@ import logging import os from typing import List, Optional -import infection_monkey.monkeyfs as monkeyfs from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.consts import WIN_ARCH_32 from infection_monkey.exploit.HostExploiter import HostExploiter @@ -22,7 +21,7 @@ from infection_monkey.exploit.powershell_utils.powershell_client import ( IPowerShellClient, PowerShellClient, ) -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os +from infection_monkey.exploit.tools.helpers import get_monkey_depth from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.environment import is_windows_os @@ -186,11 +185,15 @@ class PowerShellExploiter(HostExploiter): return is_monkey_copy_successful def _write_virtual_file_to_local_path(self) -> None: + """ + # TODO: monkeyfs has been removed. Fix this in issue #1740. monkey_fs_path = get_target_monkey_by_os(is_windows=True, is_32bit=self.is_32bit) with monkeyfs.open(monkey_fs_path) as monkey_virtual_file: with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file: monkey_local_file.write(monkey_virtual_file.read()) + """ + pass def _run_monkey_executable_on_victim(self, executable_path) -> None: monkey_execution_command = build_monkey_execution_command( diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 4cbfd1e5c..0192ae3ed 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -4,12 +4,11 @@ import time import paramiko -import infection_monkey.monkeyfs as monkeyfs from common.utils.attack_utils import ScanStatus from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import get_monkey_depth from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port, get_interface_to_target @@ -133,7 +132,6 @@ class SSHExploiter(HostExploiter): _, stdout, _ = ssh.exec_command("uname -o") uname_os = stdout.read().lower().strip().decode() if "linux" in uname_os: - self.host.os["type"] = "linux" self.exploit_result.os = "linux" else: self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}" @@ -149,21 +147,9 @@ class SSHExploiter(HostExploiter): logger.error(self.exploit_result.error_message) return self.exploit_result - if not self.host.os.get("machine"): - try: - _, stdout, _ = ssh.exec_command("uname -m") - uname_machine = stdout.read().lower().strip().decode() - if "" != uname_machine: - self.host.os["machine"] = uname_machine - except Exception as exc: - self.exploit_result.error_message = ( - f"Error running uname machine command on victim {self.host}: ({exc})" - ) - logger.error(self.exploit_result.error_message) + agent_binary_file_object = self.agent_repository.get_agent_binary(self.exploit_result.os) - src_path = get_target_monkey(self.host) - - if not src_path: + if not agent_binary_file_object: self.exploit_result.error_message = ( f"Can't find suitable monkey executable for host {self.host}" ) @@ -172,26 +158,17 @@ class SSHExploiter(HostExploiter): return self.exploit_result try: - ftp = ssh.open_sftp() - - self._update_timestamp = time.time() - with monkeyfs.open(src_path) as file_obj: + with ssh.open_sftp() as ftp: + self._update_timestamp = time.time() ftp.putfo( - file_obj, + agent_binary_file_object, self.options["dropper_target_path_linux"], - file_size=monkeyfs.getsize(src_path), + file_size=len(agent_binary_file_object.getbuffer()), callback=self.log_transfer, ) - ftp.chmod(self.options["dropper_target_path_linux"], 0o777) - status = ScanStatus.USED - self.telemetry_messenger.send_telemetry( - T1222Telem( - ScanStatus.USED, - "chmod 0777 %s" % self.options["dropper_target_path_linux"], - self.host, - ) - ) - ftp.close() + self._set_executable_bit_on_agent_binary(ftp) + + status = ScanStatus.USED except Exception as exc: self.exploit_result.error_message = ( f"Error uploading file into victim {self.host}: ({exc})" @@ -201,7 +178,10 @@ class SSHExploiter(HostExploiter): self.telemetry_messenger.send_telemetry( T1105Telem( - status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path + status, + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, + self.options["dropper_target_path_linux"], ) ) if status == ScanStatus.SCANNED: @@ -233,3 +213,13 @@ class SSHExploiter(HostExploiter): logger.error(self.exploit_result.error_message) return self.exploit_result + + def _set_executable_bit_on_agent_binary(self, ftp: paramiko.sftp_client.SFTPClient): + ftp.chmod(self.options["dropper_target_path_linux"], 0o700) + self.telemetry_messenger.send_telemetry( + T1222Telem( + ScanStatus.USED, + "chmod 0700 %s" % self.options["dropper_target_path_linux"], + self.host, + ) + ) diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index c0f467bd4..d0af82304 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -11,38 +11,13 @@ def try_get_target_monkey(host): def get_target_monkey(host): - import platform - import sys - - from infection_monkey.control import ControlClient - - if host.monkey_exe: - return host.monkey_exe - - if not host.os.get("type"): - return None - - monkey_path = ControlClient.download_monkey_exe(host) - - if host.os.get("machine") and monkey_path: - host.monkey_exe = monkey_path - - if not monkey_path: - if host.os.get("type") == platform.system().lower(): - # if exe not found, and we have the same arch or arch is unknown and we are 32bit, - # use our exe - if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get( - "machine", "" - ).lower() == platform.machine().lower(): - monkey_path = sys.executable - - return monkey_path + raise NotImplementedError("get_target_monkey() has been retired. Use IAgentRepository instead.") def get_target_monkey_by_os(is_windows, is_32bit): - from infection_monkey.control import ControlClient - - return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) + raise NotImplementedError( + "get_target_monkey_by_os() has been retired. Use IAgentRepository instead." + ) def get_monkey_depth(): @@ -54,7 +29,7 @@ def get_monkey_depth(): def get_monkey_dest_path(url_to_monkey): """ Gets destination path from monkey's source url. - :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe + :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-64.exe :return: Corresponding monkey path from configuration """ from infection_monkey.config import WormConfiguration @@ -65,8 +40,6 @@ def get_monkey_dest_path(url_to_monkey): try: if "linux" in url_to_monkey: return WormConfiguration.dropper_target_path_linux - elif "windows-32" in url_to_monkey: - return WormConfiguration.dropper_target_path_win_32 elif "windows-64" in url_to_monkey: return WormConfiguration.dropper_target_path_win_64 else: diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index 25aca3321..467539180 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -11,33 +11,12 @@ from infection_monkey.model import DOWNLOAD_TIMEOUT from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port from infection_monkey.network.tools import get_interface_to_target -from infection_monkey.transport import HTTPServer, LockedHTTPServer +from infection_monkey.transport import LockedHTTPServer logger = logging.getLogger(__name__) class HTTPTools(object): - @staticmethod - def create_transfer(host, src_path, local_ip=None, local_port=None): - if not local_port: - local_port = get_free_tcp_port() - - if not local_ip: - local_ip = get_interface_to_target(host.ip_addr) - - if not firewall.listen_allowed(): - return None, None - - httpd = HTTPServer(local_ip, local_port, src_path) - httpd.daemon = True - httpd.start() - - return ( - "http://%s:%s/%s" - % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), - httpd, - ) - @staticmethod def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None): http_path, http_thread = HTTPTools.create_locked_transfer( @@ -49,7 +28,9 @@ class HTTPTools(object): return http_path, http_thread @staticmethod - def create_locked_transfer(host, src_path, local_ip=None, local_port=None): + def create_locked_transfer( + host, dropper_target_path, agent_repository, local_ip=None, local_port=None + ): """ Create http server for file transfer with a lock :param host: Variable with target's information @@ -71,12 +52,13 @@ class HTTPTools(object): logger.error("Firewall is not allowed to listen for incomming ports. Aborting") return None, None - httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) + httpd = LockedHTTPServer( + local_ip, local_port, host.os["type"], dropper_target_path, agent_repository, lock + ) httpd.start() lock.acquire() return ( - "http://%s:%s/%s" - % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), + "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"])), httpd, ) diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index 362c1b083..84e1b7e8b 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -6,7 +6,6 @@ from impacket.dcerpc.v5 import srvs, transport from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 from impacket.smbconnection import SMB_DIALECT, SMBConnection -import infection_monkey.monkeyfs as monkeyfs from common.utils.attack_utils import ScanStatus from infection_monkey.config import Configuration from infection_monkey.network.tools import get_interface_to_target @@ -20,7 +19,8 @@ class SmbTools(object): 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,) + # monkeyfs has been removed. Fix this in issue #1741 + # assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) smb, dialect = SmbTools.new_smb_connection( host, username, password, lm_hash, ntlm_hash, timeout @@ -138,10 +138,13 @@ class SmbTools(object): remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep)) try: + # monkeyfs has been removed. Fix this in issue #1741 + """ with monkeyfs.open(src_path, "rb") as source_file: # make sure of the timeout smb.setTimeout(timeout) smb.putFile(share_name, remote_path, source_file.read) + """ file_uploaded = True T1105Telem( diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 312ac3b57..4473a24f5 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -4,7 +4,6 @@ from posixpath import join from typing import List, Tuple from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus -from infection_monkey.exploit.consts import WIN_ARCH_64 from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.http_tools import HTTPTools @@ -35,7 +34,7 @@ class WebRCE(HostExploiter): def __init__(self, monkey_target_paths=None): """ :param monkey_target_paths: Where to upload the monkey at the target host system. - Dict in format {'linux': '/tmp/monkey.sh', 'win32': './monkey32.exe', 'win64':... } + Dict in format {'linux': '/tmp/monkey.sh', 'win64':... } """ super(WebRCE, self).__init__() self.monkey_target_paths = monkey_target_paths @@ -117,7 +116,6 @@ class WebRCE(HostExploiter): if not self.monkey_target_paths: self.monkey_target_paths = { "linux": self.options["dropper_target_path_linux"], - "win32": self.options["dropper_target_path_win_32"], "win64": self.options["dropper_target_path_win_64"], } self.HTTP = [str(port) for port in self.options["http_ports"]] @@ -276,7 +274,7 @@ class WebRCE(HostExploiter): "monkey_path": dest_path, "http_path": http_path, } - self.telemetry_messenger.send_telemtry( + self.telemetry_messenger.send_telemetry( T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING) ) resp = self.exploit(url, backup_command) @@ -294,11 +292,12 @@ class WebRCE(HostExploiter): if not self.host.os["type"]: logger.error("Unknown target's os type. Skipping.") return False - paths = self.get_monkey_paths() - if not paths: - return False + + dropper_target_path = self.monkey_target_paths[self.host.os["type"]] # Create server for http download and wait for it's startup. - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"]) + http_path, http_thread = HTTPTools.create_locked_transfer( + self.host, dropper_target_path, self.agent_repository + ) if not http_path: logger.debug("Exploiter failed, http transfer creation failed.") return False @@ -306,10 +305,10 @@ class WebRCE(HostExploiter): # Choose command: if not commands: commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD} - command = self.get_command(paths["dest_path"], http_path, commands) + command = self.get_command(dropper_target_path, http_path, commands) resp = self.exploit(url, command) self.add_executed_cmd(command) - resp = self.run_backup_commands(resp, url, paths["dest_path"], http_path) + resp = self.run_backup_commands(resp, url, dropper_target_path, http_path) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() @@ -318,7 +317,7 @@ class WebRCE(HostExploiter): if resp is False: return resp else: - return {"response": resp, "path": paths["dest_path"]} + return {"response": resp, "path": dropper_target_path} def change_permissions(self, url, path, command=None): """ @@ -336,10 +335,10 @@ class WebRCE(HostExploiter): command = CHMOD_MONKEY % {"monkey_path": path} try: resp = self.exploit(url, command) - self.telemetry_messenger.send_telemtry(T1222Telem(ScanStatus.USED, command, self.host)) + self.telemetry_messenger.send_telemetry(T1222Telem(ScanStatus.USED, command, self.host)) except Exception as e: logger.error("Something went wrong while trying to change permission: %s" % e) - self.telemetry_messenger.send_telemtry(T1222Telem(ScanStatus.SCANNED, "", self.host)) + self.telemetry_messenger.send_telemetry(T1222Telem(ScanStatus.SCANNED, "", self.host)) return False # If exploiter returns True / False if isinstance(resp, bool): @@ -412,7 +411,7 @@ class WebRCE(HostExploiter): """ Gets destination path from one of WEB_RCE predetermined paths(self.monkey_target_paths). :param url_to_monkey: Hosted monkey's url. egz : - http://localserver:9999/monkey/windows-32.exe + http://localserver:9999/monkey/windows-64.exe :return: Corresponding monkey path from self.monkey_target_paths """ if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey): @@ -423,9 +422,7 @@ class WebRCE(HostExploiter): try: if "linux" in url_to_monkey: return self.monkey_target_paths["linux"] - elif "windows-32" in url_to_monkey: - return self.monkey_target_paths["win32"] - elif "windows-64" in url_to_monkey: + elif "windows" in url_to_monkey: return self.monkey_target_paths["win64"] else: logger.error( @@ -435,9 +432,8 @@ class WebRCE(HostExploiter): return False except KeyError: logger.error( - 'Unknown key was found. Please use "linux", "win32" and "win64" keys to ' - "initialize " - "custom dict of monkey's destination paths" + 'Unknown key was found. Please use "linux" and "win64" keys to ' + "initialize custom dict of monkey's destination paths" ) return False @@ -470,13 +466,7 @@ class WebRCE(HostExploiter): if self.host.os["type"] == "linux": return self.options["dropper_target_path_linux"] if self.host.os["type"] == "windows": - try: - # remove now or when 32-bit binaries are removed? - if self.host.os["machine"] == WIN_ARCH_64: - return self.options["dropper_target_path_win_64"] - except KeyError: - logger.debug("Target's machine type was not set. Using win-32 dropper path.") - return self.options["dropper_target_path_win_32"] + return self.options["dropper_target_path_win_64"] def get_target_url(self): """ diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 3bbd1dfb8..95cc85810 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -8,7 +8,6 @@ class VictimHost(object): self.os = {} self.services = {} self.icmp = False - self.monkey_exe = None self.default_tunnel = None self.default_server = None @@ -42,7 +41,6 @@ class VictimHost(object): for k, v in list(self.services.items()): victim += "%s-%s " % (k, v) victim += "] ICMP: %s " % (self.icmp) - victim += "target monkey: %s" % self.monkey_exe return victim def set_island_address(self, ip: str, port: Optional[str]): diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 17dc5bc54..eaa6e0d90 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,7 +16,7 @@ from infection_monkey.credential_collectors import ( MimikatzCredentialCollector, SSHCredentialCollector, ) -from infection_monkey.exploit import ExploiterWrapper +from infection_monkey.exploit import CachingAgentRepository, ExploiterWrapper from infection_monkey.exploit.hadoop import HadoopExploiter from infection_monkey.exploit.sshexec import SSHExploiter from infection_monkey.i_puppet import IPuppet, PluginType @@ -46,7 +46,6 @@ from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.monkey_dir import get_monkey_dir_path, remove_monkey_dir from infection_monkey.utils.monkey_log_path import get_monkey_log_path from infection_monkey.utils.signal_handler import register_signal_handlers, reset_signal_handlers -from infection_monkey.windows_upgrader import WindowsUpgrader logger = logging.getLogger(__name__) @@ -101,11 +100,6 @@ class InfectionMonkey: logger.info("The Monkey Island has instructed this agent to stop") return - if InfectionMonkey._is_upgrade_to_64_needed(): - self._upgrade_to_64() - logger.info("32 bit Agent can't run on 64 bit system.") - return - self._setup() self._master.start() @@ -147,16 +141,6 @@ class InfectionMonkey: return False - @staticmethod - def _is_upgrade_to_64_needed(): - return WindowsUpgrader.should_upgrade() - - def _upgrade_to_64(self): - self._singleton.unlock() - logger.info("32bit monkey running on 64bit Windows. Upgrading.") - WindowsUpgrader.upgrade(self._opts) - logger.info("Finished upgrading from 32bit to 64bit.") - def _setup(self): logger.debug("Starting the setup phase.") @@ -216,7 +200,10 @@ class InfectionMonkey: puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER) - exploit_wrapper = ExploiterWrapper(self.telemetry_messenger) + agent_repoitory = CachingAgentRepository( + f"https://{self._default_server}", ControlClient.proxies + ) + exploit_wrapper = ExploiterWrapper(self.telemetry_messenger, agent_repoitory) puppet.load_plugin( "SSHExploiter", @@ -252,10 +239,6 @@ class InfectionMonkey: logger.info("Monkey cleanup started") self._wait_for_exploited_machine_connection() try: - if self._is_upgrade_to_64_needed(): - logger.debug("Cleanup not needed for 32 bit agent on 64 bit system(it didn't run)") - return - if self._master: self._master.cleanup() diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index dc9a90868..d79345d0a 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -43,10 +43,6 @@ def is_windows(): return platform.system().find("Windows") >= 0 -def is_32_bit(): - return sys.maxsize <= 2 ** 32 - - def get_bin_folder(): return os.path.join('.', 'bin') @@ -73,15 +69,10 @@ def get_hidden_imports(): def get_monkey_filename(): name = 'monkey-' if is_windows(): - name = name + "windows-" + name = name + "windows-64.exe" else: - name = name + "linux-" - if is_32_bit(): - name = name + "32" - else: - name = name + "64" - if is_windows(): - name = name + ".exe" + name = name + "linux-64" + return name diff --git a/monkey/infection_monkey/monkeyfs.py b/monkey/infection_monkey/monkeyfs.py deleted file mode 100644 index e056512d2..000000000 --- a/monkey/infection_monkey/monkeyfs.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -from io import BytesIO - -MONKEYFS_PREFIX = "monkeyfs://" - -open_orig = open - - -class VirtualFile(BytesIO): - _vfs = {} # virtual File-System - - def __init__(self, name, mode="r", buffering=None): - if not name.startswith(MONKEYFS_PREFIX): - name = MONKEYFS_PREFIX + name - self.name = name - if name in VirtualFile._vfs: - super(VirtualFile, self).__init__(self._vfs[name]) - else: - super(VirtualFile, self).__init__() - - def flush(self): - super(VirtualFile, self).flush() - VirtualFile._vfs[self.name] = self.getvalue() - - @staticmethod - def getsize(path): - return len(VirtualFile._vfs[path]) - - @staticmethod - def isfile(path): - return path in VirtualFile._vfs - - -def getsize(path): - if path.startswith(MONKEYFS_PREFIX): - return VirtualFile.getsize(path) - else: - return os.stat(path).st_size - - -def isfile(path): - if path.startswith(MONKEYFS_PREFIX): - return VirtualFile.isfile(path) - else: - return os.path.isfile(path) - - -def virtual_path(name): - return "%s%s" % (MONKEYFS_PREFIX, name) - - -# noinspection PyShadowingBuiltins -def open(name, mode="r", buffering=-1): - # use normal open for regular paths, and our "virtual" open for monkeyfs:// paths - if name.startswith(MONKEYFS_PREFIX): - return VirtualFile(name, mode, buffering) - else: - return open_orig(name, mode=mode, buffering=buffering) diff --git a/monkey/infection_monkey/readme.md b/monkey/infection_monkey/readme.md index 45488404f..8e4beb03e 100644 --- a/monkey/infection_monkey/readme.md +++ b/monkey/infection_monkey/readme.md @@ -26,7 +26,8 @@ The monkey is a PyInstaller compressed python archives. 1. To build the final exe: - `cd monkey\infection_monkey` - `build_windows.bat` - - output is placed under `dist\monkey32.exe` or `dist\monkey64.exe` depending on your version of Python + + Output is placed under `dist\monkey64.exe`. ## Linux @@ -51,7 +52,7 @@ Tested on Ubuntu 16.04. - `chmod +x build_linux.sh` - `pipenv run ./build_linux.sh` - output is placed under `dist/monkey32` or `dist/monkey64` depending on your version of python + Output is placed under `dist/monkey64`. ### Troubleshooting diff --git a/monkey/infection_monkey/transport/__init__.py b/monkey/infection_monkey/transport/__init__.py index 0dcbd56c6..960bce311 100644 --- a/monkey/infection_monkey/transport/__init__.py +++ b/monkey/infection_monkey/transport/__init__.py @@ -1,2 +1 @@ -from infection_monkey.transport.http import HTTPServer from infection_monkey.transport.http import LockedHTTPServer diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index f8ca906b0..5afb5c2d8 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -1,5 +1,4 @@ import http.server -import os.path import select import socket import threading @@ -7,7 +6,6 @@ import urllib from logging import getLogger from urllib.parse import urlsplit -import infection_monkey.monkeyfs as monkeyfs from infection_monkey.network.tools import get_interface_to_target from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time @@ -16,7 +14,8 @@ logger = getLogger(__name__) class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" - filename = "" + victim_os = "" + agent_repository = None def version_string(self): return "Microsoft-IIS/7.5." @@ -46,7 +45,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): total += chunk start_range += chunk - if f.tell() == monkeyfs.getsize(self.filename): + if f.tell() == len(f.getbuffer()): if self.report_download(self.client_address): self.close_connection = 1 @@ -59,15 +58,15 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): f.close() def send_head(self): - if self.path != "/" + urllib.parse.quote(os.path.basename(self.filename)): + if self.path != "/" + urllib.parse.quote(self.victim_os): self.send_error(500, "") return None, 0, 0 try: - f = monkeyfs.open(self.filename, "rb") + f = self.agent_repository.get_agent_binary(self.victim_os) except IOError: self.send_error(404, "File not found") return None, 0, 0 - size = monkeyfs.getsize(self.filename) + size = len(f.getbuffer()) start_range = 0 end_range = size @@ -157,50 +156,6 @@ class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler): ) -class HTTPServer(threading.Thread): - def __init__(self, local_ip, local_port, filename, max_downloads=1): - self._local_ip = local_ip - self._local_port = local_port - self._filename = filename - self.max_downloads = max_downloads - self.downloads = 0 - self._stopped = False - threading.Thread.__init__(self) - - def run(self): - class TempHandler(FileServHTTPRequestHandler): - from common.utils.attack_utils import ScanStatus - from infection_monkey.telemetry.attack.t1105_telem import T1105Telem - - filename = self._filename - - @staticmethod - def report_download(dest=None): - logger.info("File downloaded from (%s,%s)" % (dest[0], dest[1])) - TempHandler.T1105Telem( - TempHandler.ScanStatus.USED, - get_interface_to_target(dest[0]), - dest[0], - self._filename, - ).send() - self.downloads += 1 - if not self.downloads < self.max_downloads: - return True - return False - - httpd = http.server.HTTPServer((self._local_ip, self._local_port), TempHandler) - httpd.timeout = 0.5 # this is irrelevant? - - while not self._stopped and self.downloads < self.max_downloads: - httpd.handle_request() - - self._stopped = True - - def stop(self, timeout=60): - self._stopped = True - self.join(timeout) - - class LockedHTTPServer(threading.Thread): """ Same as HTTPServer used for file downloads just with locks to avoid racing conditions. @@ -213,10 +168,21 @@ class LockedHTTPServer(threading.Thread): # Seconds to wait until server stops STOP_TIMEOUT = 5 - def __init__(self, local_ip, local_port, filename, lock, max_downloads=1): + def __init__( + self, + local_ip, + local_port, + victim_os, + dropper_target_path, + agent_repository, + lock, + max_downloads=1, + ): self._local_ip = local_ip self._local_port = local_port - self._filename = filename + self._victim_os = victim_os + self._dropper_target_path = dropper_target_path + self._agent_repository = agent_repository self.max_downloads = max_downloads self.downloads = 0 self._stopped = False @@ -229,7 +195,8 @@ class LockedHTTPServer(threading.Thread): from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1105_telem import T1105Telem - filename = self._filename + victim_os = self._victim_os + agent_repository = self._agent_repository @staticmethod def report_download(dest=None): @@ -238,7 +205,7 @@ class LockedHTTPServer(threading.Thread): TempHandler.ScanStatus.USED, get_interface_to_target(dest[0]), dest[0], - self._filename, + self._dropper_target_path, ).send() self.downloads += 1 if not self.downloads < self.max_downloads: diff --git a/monkey/infection_monkey/utils/environment.py b/monkey/infection_monkey/utils/environment.py index 2ead5a837..195e54fd3 100644 --- a/monkey/infection_monkey/utils/environment.py +++ b/monkey/infection_monkey/utils/environment.py @@ -1,18 +1,5 @@ -import os -import struct import sys -def is_64bit_windows_os(): - """ - Checks for 64 bit Windows OS using environment variables. - """ - return "PROGRAMFILES(X86)" in os.environ - - -def is_64bit_python(): - return struct.calcsize("P") == 8 - - def is_windows_os(): return sys.platform.startswith("win") diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py deleted file mode 100644 index c72f970d9..000000000 --- a/monkey/infection_monkey/windows_upgrader.py +++ /dev/null @@ -1,69 +0,0 @@ -import logging -import shutil -import subprocess -import sys -import time - -import infection_monkey.monkeyfs as monkeyfs -from infection_monkey.config import WormConfiguration -from infection_monkey.control import ControlClient -from infection_monkey.utils.commands import ( - build_monkey_commandline_explicitly, - get_monkey_commandline_windows, -) -from infection_monkey.utils.environment import is_64bit_python, is_64bit_windows_os, is_windows_os - -logger = logging.getLogger(__name__) - -if "win32" == sys.platform: - from win32process import DETACHED_PROCESS -else: - DETACHED_PROCESS = 0 - - -class WindowsUpgrader(object): - __UPGRADE_WAIT_TIME__ = 3 - - @staticmethod - def should_upgrade(): - return is_windows_os() and is_64bit_windows_os() and not is_64bit_python() - - @staticmethod - def upgrade(opts): - try: - monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) - with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: - with open( - WormConfiguration.dropper_target_path_win_64, "wb" - ) as written_monkey_file: - shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) - except (IOError, AttributeError) as e: - logger.error("Failed to download the Monkey to the target path: %s." % e) - return - - monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, opts.depth - ) - - monkey_cmdline = get_monkey_commandline_windows( - WormConfiguration.dropper_target_path_win_64, monkey_options - ) - - monkey_process = subprocess.Popen( - monkey_cmdline, - stdin=None, - stdout=None, - stderr=None, - close_fds=True, - creationflags=DETACHED_PROCESS, - ) - - logger.info( - "Executed 64bit monkey process (PID=%d) with command line: %s", - monkey_process.pid, - " ".join(monkey_cmdline), - ) - - time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) - if monkey_process.poll() is not None: - logger.error("Seems like monkey died too soon") diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index d7a8227fb..863a88909 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -134,8 +134,7 @@ def init_api_resources(api): api.add_resource(ConfigurationImport, "/api/configuration/import") api.add_resource( MonkeyDownload, - "/api/monkey/download", - "/api/monkey/download/", + "/api/monkey/download/", ) api.add_resource(NetMap, "/api/netmap") api.add_resource(Edge, "/api/netmap/edge") diff --git a/monkey/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py index 24e03280c..99943aedb 100644 --- a/monkey/monkey_island/cc/resources/monkey_download.py +++ b/monkey/monkey_island/cc/resources/monkey_download.py @@ -1,101 +1,34 @@ import hashlib -import json import logging -import os +from pathlib import Path import flask_restful -from flask import request, send_from_directory +from flask import make_response, send_from_directory from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH logger = logging.getLogger(__name__) -MONKEY_DOWNLOADS = [ - { - "type": "linux", - "machine": "x86_64", - "filename": "monkey-linux-64", - }, - { - "type": "linux", - "machine": "i686", - "filename": "monkey-linux-32", - }, - { - "type": "linux", - "machine": "i386", - "filename": "monkey-linux-32", - }, - { - "type": "linux", - "filename": "monkey-linux-64", - }, - { - "type": "windows", - "machine": "x86", - "filename": "monkey-windows-32.exe", - }, - { - "type": "windows", - "machine": "amd64", - "filename": "monkey-windows-64.exe", - }, - { - "type": "windows", - "machine": "64", - "filename": "monkey-windows-64.exe", - }, - { - "type": "windows", - "machine": "32", - "filename": "monkey-windows-32.exe", - }, - { - "type": "windows", - "filename": "monkey-windows-32.exe", - }, -] +AGENTS = { + "linux": "monkey-linux-64", + "windows": "monkey-windows-64.exe", +} -def get_monkey_executable(host_os, machine): - for download in MONKEY_DOWNLOADS: - if host_os == download.get("type") and machine == download.get("machine"): - logger.info("Monkey exec found for os: {0} and machine: {1}".format(host_os, machine)) - return download - logger.warning( - "No monkey executables could be found for the host os or machine or both: host_os: {" - "0}, machine: {1}".format(host_os, machine) - ) - return None +class UnsupportedOSError(Exception): + pass class MonkeyDownload(flask_restful.Resource): # Used by monkey. can't secure. - def get(self, path): - return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries"), path) - - # Used by monkey. can't secure. - def post(self): - host_json = json.loads(request.data) - host_os = host_json.get("os") - if host_os: - result = get_monkey_executable(host_os.get("type"), host_os.get("machine")) - - if result: - # change resulting from new base path - executable_filename = result["filename"] - real_path = MonkeyDownload.get_executable_full_path(executable_filename) - if os.path.isfile(real_path): - result["size"] = os.path.getsize(real_path) - return result - - return {} - - @staticmethod - def get_executable_full_path(executable_filename): - real_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", executable_filename) - return real_path + def get(self, host_os): + try: + path = get_agent_executable_path(host_os) + return send_from_directory(path.parent, path.name) + except UnsupportedOSError as ex: + logger.error(ex) + return make_response({"error": str(ex)}, 404) @staticmethod def log_executable_hashes(): @@ -103,16 +36,30 @@ class MonkeyDownload(flask_restful.Resource): Logs all the hashes of the monkey executables for debugging ease (can check what Monkey version you have etc.). """ - filenames = set([x["filename"] for x in MONKEY_DOWNLOADS]) + filenames = set(AGENTS.values()) for filename in filenames: - filepath = MonkeyDownload.get_executable_full_path(filename) - if os.path.isfile(filepath): + filepath = get_executable_full_path(filename) + if filepath.is_file(): with open(filepath, "rb") as monkey_exec_file: file_contents = monkey_exec_file.read() - logger.debug( - "{} hashes:\nSHA-256 {}".format( - filename, hashlib.sha256(file_contents).hexdigest() - ) - ) + file_sha256_hash = hashlib.sha256(file_contents).hexdigest() + logger.debug(f"{filename} SHA-256 hash: {file_sha256_hash}") else: - logger.debug("No monkey executable for {}.".format(filepath)) + logger.debug(f"No monkey executable for {filepath}") + + +def get_agent_executable_path(host_os: str) -> Path: + try: + agent_path = get_executable_full_path(AGENTS[host_os]) + logger.debug(f"Monkey exec found for os: {host_os}, {agent_path}") + + return agent_path + except KeyError: + logger.warning(f"No monkey executables could be found for the host os: {host_os}") + raise UnsupportedOSError( + f'No Agents are available for unsupported operating system "{host_os}"' + ) + + +def get_executable_full_path(executable_filename: str) -> Path: + return Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries" / executable_filename diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 0fc3af855..94c4e96ec 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -606,7 +606,6 @@ class ConfigService: for dropper_target in [ "dropper_target_path_linux", - "dropper_target_path_win_32", "dropper_target_path_win_64", ]: exploit_options[dropper_target] = config.get(dropper_target, "") diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index c9325ab0e..d25856b39 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -174,14 +174,6 @@ INTERNAL = { "description": "Determines where should the dropper place the monkey on a " "Linux machine", }, - "dropper_target_path_win_32": { - "title": "Dropper target path on Windows (32bit)", - "type": "string", - "default": "C:\\Windows\\temp\\monkey32.exe", - "description": "Determines where should the dropper place the monkey on a " - "Windows machine " - "(32bit)", - }, "dropper_target_path_win_64": { "title": "Dropper target path on Windows (64bit)", "type": "string", diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py index ce6c98c61..6059ceb71 100644 --- a/monkey/monkey_island/cc/services/run_local_monkey.py +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -5,8 +5,8 @@ import stat import subprocess from shutil import copyfile -from monkey_island.cc.resources.monkey_download import get_monkey_executable -from monkey_island.cc.server_utils.consts import ISLAND_PORT, MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.resources.monkey_download import get_agent_executable_path +from monkey_island.cc.server_utils.consts import ISLAND_PORT from monkey_island.cc.services.utils.network_utils import local_ip_addresses logger = logging.getLogger(__name__) @@ -25,12 +25,13 @@ class LocalMonkeyRunService: @staticmethod def run_local_monkey(): # get the monkey executable suitable to run on the server - result = get_monkey_executable(platform.system().lower(), platform.machine().lower()) - if not result: - return False, "OS Type not found" + try: + src_path = get_agent_executable_path(platform.system().lower()) + except Exception as ex: + logger.error(f"Error running agent from island: {ex}") + return False, str(ex) - src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - dest_path = os.path.join(LocalMonkeyRunService.DATA_DIR, result["filename"]) + dest_path = LocalMonkeyRunService.DATA_DIR / src_path.name # copy the executable to temp path (don't run the monkey from its current location as it may # delete itself) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunManually/LocalManualRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunManually/LocalManualRunOptions.js index 116ba5440..9d0469aca 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunManually/LocalManualRunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunManually/LocalManualRunOptions.js @@ -20,9 +20,7 @@ const getContents = (props) => { const osTypes = { [OS_TYPES.WINDOWS_64]: 'Windows 64bit', - [OS_TYPES.WINDOWS_32]: 'Windows 32bit', - [OS_TYPES.LINUX_64]: 'Linux 64bit', - [OS_TYPES.LINUX_32]: 'Linux 32bit' + [OS_TYPES.LINUX_64]: 'Linux 64bit' } const [osType, setOsType] = useState(OS_TYPES.WINDOWS_64); @@ -48,11 +46,11 @@ const getContents = (props) => { } function generateCommands() { - if (osType === OS_TYPES.WINDOWS_64 || osType === OS_TYPES.WINDOWS_32) { - return [{type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, osType, customUsername)}] + if (osType === OS_TYPES.WINDOWS_64) { + return [{type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, customUsername)}] } else { - return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, osType, customUsername)}, - {type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, osType, customUsername)}] + return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, customUsername)}, + {type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, customUsername)}] } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js index 7c099f224..bbefb64ac 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -5,7 +5,7 @@ import AuthComponent from '../../AuthComponent'; import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode'; import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; import {cloneDeep} from 'lodash'; -import {faCloud, faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons'; +import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons'; import RunOnIslandButton from './RunOnIslandButton'; import AWSRunButton from './RunOnAWS/AWSRunButton'; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_curl.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_curl.js index ed9ffdec6..ceaeab393 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_curl.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_curl.js @@ -1,12 +1,8 @@ -import {OS_TYPES} from '../utils/OsTypes'; - - -export default function generateLocalLinuxCurl(ip, osType, username) { - let bitText = osType === OS_TYPES.LINUX_32 ? '32' : '64'; - let command = `curl https://${ip}:5000/api/monkey/download/monkey-linux-${bitText} -k ` - + `-o monkey-linux-${bitText}; ` - + `chmod +x monkey-linux-${bitText}; ` - + `./monkey-linux-${bitText} m0nk3y -s ${ip}:5000;`; +export default function generateLocalLinuxCurl(ip, username) { + let command = `curl https://${ip}:5000/api/monkey/download/monkey-linux-64 -k ` + + `-o monkey-linux-64; ` + + `chmod +x monkey-linux-64; ` + + `./monkey-linux-64 m0nk3y -s ${ip}:5000;`; if (username != '') { command = `su - ${username} -c "${command}"`; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_wget.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_wget.js index 3f47dc996..0540540e7 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_wget.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_wget.js @@ -1,12 +1,8 @@ -import {OS_TYPES} from '../utils/OsTypes'; - - -export default function generateLocalLinuxWget(ip, osType, username) { - let bitText = osType === OS_TYPES.LINUX_32 ? '32' : '64'; +export default function generateLocalLinuxWget(ip, username) { let command = `wget --no-check-certificate https://${ip}:5000/api/monkey/download/` - + `monkey-linux-${bitText}; ` - + `chmod +x monkey-linux-${bitText}; ` - + `./monkey-linux-${bitText} m0nk3y -s ${ip}:5000`; + + `monkey-linux-64; ` + + `chmod +x monkey-linux-64; ` + + `./monkey-linux-64 m0nk3y -s ${ip}:5000`; if (username != '') { command = `su - ${username} -c "${command}"`; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js index 5c7d5c9a6..de5346f30 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js @@ -1,18 +1,14 @@ -import {OS_TYPES} from '../utils/OsTypes'; - - -function getAgentDownloadCommand(ip, osType) { - let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64'; +function getAgentDownloadCommand(ip) { return `$execCmd = @"\r\n` + `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {\`$true};` - + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe',` + + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-64.exe',` + `"""$env:TEMP\\monkey.exe""");Start-Process -FilePath '$env:TEMP\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';` + `\r\n"@; \r\n` + `Start-Process -FilePath powershell.exe -ArgumentList $execCmd`; } -export default function generateLocalWindowsPowershell(ip, osType, username) { - let command = getAgentDownloadCommand(ip, osType) +export default function generateLocalWindowsPowershell(ip, username) { + let command = getAgentDownloadCommand(ip) if (username !== '') { command += ` -Credential ${username}`; } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/utils/OsTypes.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/utils/OsTypes.js index b24c9b302..1d898af01 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/utils/OsTypes.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/utils/OsTypes.js @@ -1,6 +1,4 @@ export const OS_TYPES = { - WINDOWS_32: 'win32', WINDOWS_64: 'win64', - LINUX_32: 'linux32', LINUX_64: 'linux64' } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js index 843ca89dd..0a41f2c24 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js @@ -46,7 +46,7 @@ function aggregateMultipleResultsPba(results) { } function modifyProcessListCollectionResult(result) { - result[0] = "Found " + Object.keys(result[0]).length.toString() + " running processes"; + result[0] = 'Found ' + Object.keys(result[0]).length.toString() + ' running processes'; } // check for pbas with multiple results and aggregate their results diff --git a/monkey/monkey_island/readme.md b/monkey/monkey_island/readme.md index 8de0a49a9..375af7d24 100644 --- a/monkey/monkey_island/readme.md +++ b/monkey/monkey_island/readme.md @@ -45,8 +45,6 @@ 1. Put Infection Monkey binaries inside monkey_island/cc/binaries (binaries can be found in releases on github or build from source) monkey-linux-64 - monkey binary for linux 64bit - monkey-linux-32 - monkey binary for linux 32bit - monkey-windows-32.exe - monkey binary for windows 32bit monkey-windows-64.exe - monkey binary for windows 64bit 1. Install npm @@ -95,15 +93,10 @@ monkey-linux-64 - monkey binary for linux 64bit - monkey-linux-32 - monkey binary for linux 32bit - - monkey-windows-32.exe - monkey binary for windows 32bit - monkey-windows-64.exe - monkey binary for windows 64bit Also, if you're going to run monkeys on local machine execute: - `chmod 755 ./monkey_island/cc/binaries/monkey-linux-64` - - `chmod 755 ./monkey_island/cc/binaries/monkey-linux-32` 1. Setup MongoDB (Use one of the two following options): - Download MongoDB and extract it to monkey/monkey_island/bin/mongodb: diff --git a/monkey/tests/data_for_tests/monkey_configs/flat_config.json b/monkey/tests/data_for_tests/monkey_configs/flat_config.json index acce7f2ae..fdac570f5 100644 --- a/monkey/tests/data_for_tests/monkey_configs/flat_config.json +++ b/monkey/tests/data_for_tests/monkey_configs/flat_config.json @@ -27,7 +27,6 @@ "dropper_log_path_windows": "%temp%\\~df1562.tmp", "dropper_set_date": true, "dropper_target_path_linux": "/tmp/monkey", - "dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe", "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe", "exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"], "exploit_ntlm_hash_list": ["nt_hash_1", "nt_hash_2", "nt_hash_3"], diff --git a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json index 658e4cc68..9891fef0c 100644 --- a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json +++ b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json @@ -104,7 +104,6 @@ "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", "dropper_date_reference_path_linux": "/bin/sh", "dropper_target_path_linux": "/tmp/monkey", - "dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe", "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe" }, "logging": { diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py index 9de7f8f54..10d2e6e1d 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py @@ -26,7 +26,6 @@ Config = namedtuple( "exploit_password_list", "exploit_lm_hash_list", "exploit_ntlm_hash_list", - "dropper_target_path_win_32", "dropper_target_path_win_64", ], ) @@ -52,7 +51,8 @@ def powershell_exploiter(monkeypatch): monkeypatch.setattr(powershell, "AuthenticationError", AuthenticationErrorForTests) monkeypatch.setattr(powershell, "is_windows_os", lambda: True) # It's regrettable to mock out a private method on the PowerShellExploiter instance object, but - # it's necessary to avoid having to deal with the monkeyfs + # it's necessary to avoid having to deal with the monkeyfs. TODO: monkeyfs has been removed, so + # fix this. monkeypatch.setattr(pe, "_write_virtual_file_to_local_path", lambda: None) return pe diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py index 5d6c81f56..600e1db20 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py @@ -16,7 +16,6 @@ HOST_AS_DICT = { "os": {}, "services": {}, "icmp": False, - "monkey_exe": None, "default_tunnel": None, "default_server": None, } @@ -37,7 +36,13 @@ ERROR_MSG = "failed because yolo" @pytest.fixture def exploit_telem_test_instance(): - return ExploitTelem(EXPLOITER_NAME, HOST, ExploiterResultData(RESULT, RESULT, OS_LINUX, EXPLOITER_INFO, EXPLOITER_ATTEMPTS, ERROR_MSG)) + return ExploitTelem( + EXPLOITER_NAME, + HOST, + ExploiterResultData( + RESULT, RESULT, OS_LINUX, EXPLOITER_INFO, EXPLOITER_ATTEMPTS, ERROR_MSG + ), + ) def test_exploit_telem_send(exploit_telem_test_instance, spy_send_telemetry): diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py index 07c6fbf41..a369fe4cf 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py @@ -14,7 +14,6 @@ HOST_AS_DICT = { "os": {}, "services": {}, "icmp": False, - "monkey_exe": None, "default_tunnel": None, "default_server": None, } diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py index 64bfd7bff..d8391717e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py @@ -173,7 +173,6 @@ def test_format_config_for_agent__exploiters(flat_monkey_config): expected_exploiters_config = { "options": { "dropper_target_path_linux": "/tmp/monkey", - "dropper_target_path_win_32": r"C:\Windows\temp\monkey32.exe", "dropper_target_path_win_64": r"C:\Windows\temp\monkey64.exe", "http_ports": [80, 443, 7001, 8008, 8080, 9200], },