forked from p15670423/monkey
Merge pull request #2005 from guardicore/1974-refactor-agent-binaries-resource
Island: Refactor agent-binaries resource
This commit is contained in:
commit
ae2d212253
|
@ -1,48 +1,40 @@
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from flask import make_response, send_from_directory
|
from flask import make_response, send_file
|
||||||
|
|
||||||
|
from monkey_island.cc.repository import AgentRetrievalError, IAgentBinaryRepository
|
||||||
from monkey_island.cc.resources.AbstractResource import AbstractResource
|
from monkey_island.cc.resources.AbstractResource import AbstractResource
|
||||||
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
AGENTS = {
|
|
||||||
"linux": "monkey-linux-64",
|
|
||||||
"windows": "monkey-windows-64.exe",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedOSError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AgentBinaries(AbstractResource):
|
class AgentBinaries(AbstractResource):
|
||||||
urls = ["/api/agent-binaries/<string:os>"]
|
urls = ["/api/agent-binaries/<string:os>"]
|
||||||
|
|
||||||
|
def __init__(self, agent_binary_repository: IAgentBinaryRepository):
|
||||||
|
self._agent_binary_repository = agent_binary_repository
|
||||||
|
|
||||||
# Used by monkey. can't secure.
|
# Used by monkey. can't secure.
|
||||||
def get(self, os):
|
def get(self, os):
|
||||||
|
"""
|
||||||
|
Gets the agent binary for the specified OS
|
||||||
|
|
||||||
|
:param os: Operating systems. Supported OS are: 'linux' and 'windows'
|
||||||
|
:return: an agent binary file
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
path = get_agent_executable_path(os)
|
agent_binaries = {
|
||||||
return send_from_directory(path.parent, path.name)
|
"linux": self._agent_binary_repository.get_linux_binary,
|
||||||
except UnsupportedOSError as ex:
|
"windows": self._agent_binary_repository.get_windows_binary,
|
||||||
logger.error(ex)
|
}
|
||||||
return make_response({"error": str(ex)}, 404)
|
|
||||||
|
|
||||||
|
file = agent_binaries[os]()
|
||||||
|
|
||||||
def get_agent_executable_path(os: str) -> Path:
|
return send_file(file, mimetype="application/octet-stream")
|
||||||
try:
|
except AgentRetrievalError as err:
|
||||||
agent_path = get_executable_full_path(AGENTS[os])
|
logger.error(err)
|
||||||
logger.debug(f'Local path for {os} executable is "{agent_path}"')
|
return make_response({"error": str(err)}, 500)
|
||||||
if not agent_path.is_file():
|
except KeyError as err:
|
||||||
logger.error(f"File {agent_path} not found")
|
error_msg = f'No Agents are available for unsupported operating system "{os}": {err}'
|
||||||
|
logger.error(error_msg)
|
||||||
return agent_path
|
return make_response({"error": error_msg}, 404)
|
||||||
except KeyError:
|
|
||||||
logger.warning(f"No monkey executables could be found for the host os: {os}")
|
|
||||||
raise UnsupportedOSError(f'No Agents are available for unsupported operating system "{os}"')
|
|
||||||
|
|
||||||
|
|
||||||
def get_executable_full_path(executable_filename: str) -> Path:
|
|
||||||
return Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries" / executable_filename
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
|
||||||
class LocalRun(AbstractResource):
|
class LocalRun(AbstractResource):
|
||||||
urls = ["/api/local-monkey"]
|
urls = ["/api/local-monkey"]
|
||||||
|
|
||||||
|
def __init__(self, local_monkey_run_service: LocalMonkeyRunService):
|
||||||
|
self._local_monkey_run_service = local_monkey_run_service
|
||||||
|
|
||||||
# API Spec: Both of these methods should be separated to their own resources
|
# API Spec: Both of these methods should be separated to their own resources
|
||||||
|
|
||||||
# API Spec: This should be a REST endpoint, /api/monkeys or something
|
# API Spec: This should be a REST endpoint, /api/monkeys or something
|
||||||
|
@ -30,7 +33,7 @@ class LocalRun(AbstractResource):
|
||||||
def post(self):
|
def post(self):
|
||||||
body = json.loads(request.data)
|
body = json.loads(request.data)
|
||||||
if body.get("action") == "run":
|
if body.get("action") == "run":
|
||||||
local_run = LocalMonkeyRunService.run_local_monkey()
|
local_run = self._local_monkey_run_service.run_local_monkey()
|
||||||
# API Spec: Feels weird to return "error_text" even when "is_running" is True
|
# API Spec: Feels weird to return "error_text" even when "is_running" is True
|
||||||
return jsonify(is_running=local_run[0], error_text=local_run[1])
|
return jsonify(is_running=local_run[0], error_text=local_run[1])
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,11 @@ AGENT_BINARIES_PATH = Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries"
|
||||||
|
|
||||||
def initialize_services(data_dir: Path) -> DIContainer:
|
def initialize_services(data_dir: Path) -> DIContainer:
|
||||||
container = DIContainer()
|
container = DIContainer()
|
||||||
|
|
||||||
|
# TODO: everything that is build with DI and expects Path in the constructor
|
||||||
|
# will use the same data_dir. Come up with a better way to inject
|
||||||
|
# the data_dir in the things that needed
|
||||||
|
container.register_instance(Path, data_dir)
|
||||||
container.register_instance(AWSInstance, AWSInstance())
|
container.register_instance(AWSInstance, AWSInstance())
|
||||||
|
|
||||||
container.register_instance(
|
container.register_instance(
|
||||||
|
@ -33,10 +38,10 @@ def initialize_services(data_dir: Path) -> DIContainer:
|
||||||
)
|
)
|
||||||
container.register_instance(AWSService, container.resolve(AWSService))
|
container.register_instance(AWSService, container.resolve(AWSService))
|
||||||
container.register_instance(IAgentBinaryRepository, _build_agent_binary_repository())
|
container.register_instance(IAgentBinaryRepository, _build_agent_binary_repository())
|
||||||
|
container.register_instance(LocalMonkeyRunService, container.resolve(LocalMonkeyRunService))
|
||||||
|
|
||||||
# This is temporary until we get DI all worked out.
|
# This is temporary until we get DI all worked out.
|
||||||
PostBreachFilesService.initialize(container.resolve(IFileRepository))
|
PostBreachFilesService.initialize(container.resolve(IFileRepository))
|
||||||
LocalMonkeyRunService.initialize(data_dir)
|
|
||||||
AuthenticationService.initialize(data_dir, JsonFileUserDatastore(data_dir))
|
AuthenticationService.initialize(data_dir, JsonFileUserDatastore(data_dir))
|
||||||
ReportService.initialize(container.resolve(AWSService))
|
ReportService.initialize(container.resolve(AWSService))
|
||||||
|
|
||||||
|
|
|
@ -1,44 +1,58 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import platform
|
import platform
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shutil import copyfile
|
from shutil import copyfileobj
|
||||||
|
|
||||||
from monkey_island.cc.resources.agent_binaries import get_agent_executable_path
|
from monkey_island.cc.repository import AgentRetrievalError, IAgentBinaryRepository
|
||||||
from monkey_island.cc.server_utils.consts import ISLAND_PORT
|
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__)
|
||||||
|
|
||||||
|
AGENT_NAMES = {"linux": "monkey-linux-64", "windows": "monkey-windows-64.exe"}
|
||||||
|
|
||||||
|
|
||||||
class LocalMonkeyRunService:
|
class LocalMonkeyRunService:
|
||||||
DATA_DIR = None
|
def __init__(self, data_dir: Path, agent_binary_repository: IAgentBinaryRepository):
|
||||||
|
self._data_dir = data_dir
|
||||||
|
self._agent_binary_repository = agent_binary_repository
|
||||||
|
|
||||||
# TODO: A number of these services should be instance objects instead of
|
def run_local_monkey(self):
|
||||||
# static/singleton hybrids. At the moment, this requires invasive refactoring that's
|
|
||||||
# not a priority.
|
|
||||||
@classmethod
|
|
||||||
def initialize(cls, data_dir: Path):
|
|
||||||
cls.DATA_DIR = data_dir
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def run_local_monkey():
|
|
||||||
# get the monkey executable suitable to run on the server
|
# get the monkey executable suitable to run on the server
|
||||||
|
operating_system = platform.system().lower()
|
||||||
try:
|
try:
|
||||||
src_path = get_agent_executable_path(platform.system().lower())
|
agents = {
|
||||||
except Exception as ex:
|
"linux": self._agent_binary_repository.get_linux_binary,
|
||||||
logger.error(f"Error running agent from island: {ex}")
|
"windows": self._agent_binary_repository.get_windows_binary,
|
||||||
return False, str(ex)
|
}
|
||||||
|
|
||||||
dest_path = LocalMonkeyRunService.DATA_DIR / src_path.name
|
agent_binary = agents[platform.system().lower()]()
|
||||||
|
except AgentRetrievalError as err:
|
||||||
|
logger.error(
|
||||||
|
f"No Agent can be retrieved for the specified operating system"
|
||||||
|
f'"{operating_system}"'
|
||||||
|
)
|
||||||
|
return False, str(err)
|
||||||
|
except KeyError as err:
|
||||||
|
logger.error(
|
||||||
|
f"No Agents are available for unsupported operating system" f'"{operating_system}"'
|
||||||
|
)
|
||||||
|
return False, str(err)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Error running agent from island: {err}")
|
||||||
|
return False, str(err)
|
||||||
|
|
||||||
|
dest_path = self._data_dir / AGENT_NAMES[operating_system]
|
||||||
|
|
||||||
# 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)
|
||||||
try:
|
try:
|
||||||
copyfile(src_path, dest_path)
|
with open(dest_path, "wb") as dest_agent:
|
||||||
os.chmod(dest_path, stat.S_IRWXU | stat.S_IRWXG)
|
copyfileobj(agent_binary, dest_agent)
|
||||||
|
|
||||||
|
dest_path.chmod(stat.S_IRWXU | stat.S_IRWXG)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("Copy file failed", exc_info=True)
|
logger.error("Copy file failed", exc_info=True)
|
||||||
return False, "Copy file failed: %s" % exc
|
return False, "Copy file failed: %s" % exc
|
||||||
|
@ -49,7 +63,7 @@ class LocalMonkeyRunService:
|
||||||
port = ISLAND_PORT
|
port = ISLAND_PORT
|
||||||
|
|
||||||
args = [str(dest_path), "m0nk3y", "-s", f"{ip}:{port}"]
|
args = [str(dest_path), "m0nk3y", "-s", f"{ip}:{port}"]
|
||||||
subprocess.Popen(args, cwd=LocalMonkeyRunService.DATA_DIR)
|
subprocess.Popen(args, cwd=self._data_dir)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("popen failed", exc_info=True)
|
logger.error("popen failed", exc_info=True)
|
||||||
return False, "popen failed: %s" % exc
|
return False, "popen failed: %s" % exc
|
||||||
|
|
|
@ -6,7 +6,6 @@ const sectionOrder = [
|
||||||
'network',
|
'network',
|
||||||
'island_server',
|
'island_server',
|
||||||
'exploits',
|
'exploits',
|
||||||
'dropper',
|
|
||||||
'classes',
|
'classes',
|
||||||
'general',
|
'general',
|
||||||
'testing'
|
'testing'
|
||||||
|
|
Loading…
Reference in New Issue