commit
2074c37081
|
@ -2,22 +2,22 @@ from http import HTTPStatus
|
|||
|
||||
from flask import make_response
|
||||
|
||||
from monkey_island.cc.repository import IAgentConfigurationRepository
|
||||
from monkey_island.cc.resources.AbstractResource import AbstractResource
|
||||
from monkey_island.cc.resources.request_authentication import jwt_required
|
||||
from monkey_island.cc.services import RepositoryService
|
||||
|
||||
|
||||
class ResetAgentConfiguration(AbstractResource):
|
||||
urls = ["/api/reset-agent-configuration"]
|
||||
|
||||
def __init__(self, agent_configuration_repository: IAgentConfigurationRepository):
|
||||
self._agent_configuration_repository = agent_configuration_repository
|
||||
def __init__(self, repository_service: RepositoryService):
|
||||
self._repository_service = repository_service
|
||||
|
||||
@jwt_required
|
||||
def post(self):
|
||||
"""
|
||||
Reset the agent configuration to its default values
|
||||
"""
|
||||
self._agent_configuration_repository.reset_to_default()
|
||||
self._repository_service.reset_agent_configuration()
|
||||
|
||||
return make_response({}, HTTPStatus.OK)
|
||||
|
|
|
@ -2,3 +2,4 @@ from .authentication_service import AuthenticationService
|
|||
|
||||
from .aws import AWSService
|
||||
from .island_mode_service import IslandModeService
|
||||
from .repository_service import RepositoryService
|
||||
|
|
|
@ -138,11 +138,6 @@ class ConfigService:
|
|||
ConfigService.set_config_value(PBA_LINUX_FILENAME_PATH, linux_filename)
|
||||
ConfigService.set_config_value(PBA_WINDOWS_FILENAME_PATH, windows_filename)
|
||||
|
||||
@staticmethod
|
||||
def init_config():
|
||||
if ConfigService.get_config(should_decrypt=False) != {}:
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def decrypt_config(config):
|
||||
ConfigService._encrypt_or_decrypt_config(config, True)
|
||||
|
|
|
@ -6,11 +6,11 @@ from monkey_island.cc.database import mongo
|
|||
from monkey_island.cc.models import Config
|
||||
from monkey_island.cc.models.agent_controls import AgentControls
|
||||
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# NOTE: This service is being replaced by the RepositoryService
|
||||
class Database(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
@ -24,7 +24,6 @@ class Database(object):
|
|||
for x in mongo.db.collection_names()
|
||||
if Database._should_drop(x, reset_config)
|
||||
]
|
||||
ConfigService.init_config()
|
||||
Database.init_agent_controls()
|
||||
logger.info("DB was reset")
|
||||
return jsonify(status="OK")
|
||||
|
|
|
@ -32,8 +32,7 @@ from monkey_island.cc.repository import (
|
|||
)
|
||||
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
|
||||
from monkey_island.cc.server_utils.encryption import ILockableEncryptor, RepositoryEncryptor
|
||||
from monkey_island.cc.services import AWSService, IslandModeService
|
||||
from monkey_island.cc.services.post_breach_files import PostBreachFilesService
|
||||
from monkey_island.cc.services import AWSService, IslandModeService, RepositoryService
|
||||
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
|
||||
from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import (
|
||||
CredentialsParser,
|
||||
|
@ -70,7 +69,6 @@ def initialize_services(data_dir: Path) -> DIContainer:
|
|||
_patch_credentials_parser(container)
|
||||
|
||||
# This is temporary until we get DI all worked out.
|
||||
PostBreachFilesService.initialize(container.resolve(IFileRepository))
|
||||
ReportService.initialize(container.resolve(AWSService))
|
||||
|
||||
return container
|
||||
|
@ -148,6 +146,7 @@ def _register_services(container: DIContainer):
|
|||
container.register_instance(LocalMonkeyRunService, container.resolve(LocalMonkeyRunService))
|
||||
container.register_instance(IslandModeService, container.resolve(IslandModeService))
|
||||
container.register_instance(AuthenticationService, container.resolve(AuthenticationService))
|
||||
container.register_instance(RepositoryService, container.resolve(RepositoryService))
|
||||
|
||||
|
||||
def _patch_credentials_parser(container: DIContainer):
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import logging
|
||||
|
||||
from monkey_island.cc.repository import IFileRepository
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# TODO: This service wraps an IFileStorageService for the sole purpose of making the
|
||||
# `remove_PBA_files()` method available to the ConfigService. This whole service can be
|
||||
# removed once ConfigService is refactored to be stateful (it already is but everything is
|
||||
# still statically/globally scoped) and use dependency injection.
|
||||
class PostBreachFilesService:
|
||||
_file_storage_service = None
|
||||
|
||||
# TODO: A number of these services should be instance objects instead of
|
||||
# static/singleton hybrids. At the moment, this requires invasive refactoring that's
|
||||
# not a priority.
|
||||
@classmethod
|
||||
def initialize(cls, file_storage_service: IFileRepository):
|
||||
cls._file_storage_service = file_storage_service
|
||||
|
||||
@classmethod
|
||||
def remove_PBA_files(cls):
|
||||
cls._file_storage_service.delete_all_files()
|
|
@ -0,0 +1,40 @@
|
|||
from monkey_island.cc.repository import IAgentConfigurationRepository, IFileRepository
|
||||
|
||||
|
||||
class RepositoryService:
|
||||
def __init__(
|
||||
self,
|
||||
agent_configuration_repository: IAgentConfigurationRepository,
|
||||
file_repository: IFileRepository,
|
||||
):
|
||||
self._agent_configuration_repository = agent_configuration_repository
|
||||
self._file_repository = file_repository
|
||||
|
||||
def reset_agent_configuration(self):
|
||||
# NOTE: This method will be replaced by an event when we implement pub/sub in the island.
|
||||
# Different plugins and components will be able to register for the event and reset
|
||||
# their configurations.
|
||||
self._remove_pba_files()
|
||||
self._agent_configuration_repository.reset_to_default()
|
||||
|
||||
def _remove_pba_files(self):
|
||||
agent_configuration = self._agent_configuration_repository.get_configuration()
|
||||
custom_pbas = agent_configuration.custom_pbas
|
||||
|
||||
if custom_pbas.linux_filename:
|
||||
self._file_repository.delete_file(custom_pbas.linux_filename)
|
||||
|
||||
if custom_pbas.windows_filename:
|
||||
self._file_repository.delete_file(custom_pbas.windows_filename)
|
||||
|
||||
def unlock(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def reset_key(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def clear_simulation_data(self):
|
||||
# NOTE: This method will be replaced by an event when we implement pub/sub in the island.
|
||||
# Different plugins and components will be able to register for the event and clear
|
||||
# any configuration data they've collected.
|
||||
raise NotImplementedError
|
|
@ -235,18 +235,9 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
this.props.onStatusChange();
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.removePBAfile(API_PBA_WINDOWS, this.setPbaFilenameWindows)
|
||||
this.removePBAfile(API_PBA_LINUX, this.setPbaFilenameLinux)
|
||||
})
|
||||
.then(this.authFetch(CONFIGURED_PROPAGATION_CREDENTIALS_URL, {method: 'DELETE'}));
|
||||
.then(this.authFetch(CONFIGURED_PROPAGATION_CREDENTIALS_URL, {method: 'DELETE'})) ;
|
||||
};
|
||||
|
||||
removePBAfile(apiEndpoint, setFilenameFnc) {
|
||||
this.sendPbaRemoveRequest(apiEndpoint)
|
||||
setFilenameFnc('')
|
||||
}
|
||||
|
||||
sendPbaRemoveRequest(apiEndpoint) {
|
||||
let request_options = {
|
||||
method: 'DELETE',
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
{
|
||||
"HTTP_PORTS": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008,
|
||||
7001,
|
||||
9200
|
||||
],
|
||||
"PBA_linux_filename": "test.sh",
|
||||
"PBA_windows_filename": "test.ps1",
|
||||
"blocked_ips": ["192.168.1.1", "192.168.1.100"],
|
||||
"custom_PBA_linux_cmd": "bash test.sh",
|
||||
"custom_PBA_windows_cmd": "powershell test.ps1",
|
||||
"depth": 2,
|
||||
"exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"],
|
||||
"exploit_ntlm_hash_list": ["nt_hash_1", "nt_hash_2", "nt_hash_3"],
|
||||
"exploit_password_list": [
|
||||
"test",
|
||||
"iloveyou",
|
||||
"12345"
|
||||
],
|
||||
"exploit_ssh_keys": [
|
||||
{
|
||||
"public_key": "my_public_key",
|
||||
"private_key": "my_private_key"
|
||||
}
|
||||
],
|
||||
"credential_collectors": ["MimikatzCollector", "SSHCollector"],
|
||||
"exploit_user_list": [
|
||||
"Administrator",
|
||||
"root",
|
||||
"user",
|
||||
"ubuntu"
|
||||
],
|
||||
"exploiter_classes": [
|
||||
"SmbExploiter",
|
||||
"WmiExploiter",
|
||||
"SSHExploiter",
|
||||
"ZerologonExploiter",
|
||||
"HadoopExploiter",
|
||||
"MSSQLExploiter",
|
||||
"PowerShellExploiter",
|
||||
"Log4ShellExploiter"
|
||||
],
|
||||
"finger_classes": [
|
||||
"SMBFinger",
|
||||
"SSHFinger",
|
||||
"HTTPFinger",
|
||||
"MSSQLFinger",
|
||||
"ElasticFinger"
|
||||
],
|
||||
"inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"],
|
||||
"keep_tunnel_open_time": 60,
|
||||
"local_network_scan": true,
|
||||
"ping_scan_timeout": 1000,
|
||||
"post_breach_actions": [
|
||||
"CommunicateAsBackdoorUser",
|
||||
"ModifyShellStartupFiles",
|
||||
"ScheduleJobs",
|
||||
"Timestomping",
|
||||
"AccountDiscovery"
|
||||
],
|
||||
"ransomware": {
|
||||
"encryption": {
|
||||
"enabled": true,
|
||||
"directories": {
|
||||
"linux_target_dir": "/tmp/ransomware-target",
|
||||
"windows_target_dir": "C:\\windows\\temp\\ransomware-target"
|
||||
}
|
||||
},
|
||||
"other_behaviors": {
|
||||
"readme": true
|
||||
}
|
||||
},
|
||||
"subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"],
|
||||
"tcp_scan_timeout": 3000,
|
||||
"tcp_target_ports": [
|
||||
22,
|
||||
2222,
|
||||
445,
|
||||
135,
|
||||
3389,
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008,
|
||||
3306,
|
||||
7001,
|
||||
8088
|
||||
]
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Callable, Dict
|
||||
|
||||
import pytest
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
@ -49,15 +47,6 @@ def monkey_configs_dir(data_for_tests_dir) -> Path:
|
|||
return data_for_tests_dir / "monkey_configs"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def load_monkey_config(data_for_tests_dir) -> Callable[[str], Dict]:
|
||||
def inner(filename: str) -> Dict:
|
||||
config_path = data_for_tests_dir / "monkey_configs" / filename
|
||||
return json.loads(open(config_path, "r").read())
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def default_agent_configuration() -> AgentConfiguration:
|
||||
return DEFAULT_AGENT_CONFIGURATION
|
||||
|
|
|
@ -4,11 +4,6 @@ from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401
|
|||
from monkey_island.cc.server_utils.encryption import unlock_datastore_encryptor
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def flat_monkey_config(load_monkey_config):
|
||||
return load_monkey_config("flat_config.json")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def uses_encryptor(data_for_tests_dir):
|
||||
secret = "m0nk3y_u53r:3cr3t_p455w0rd"
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import io
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from tests.utils import raise_
|
||||
|
||||
from monkey_island.cc.repository import LocalStorageFileRepository
|
||||
from monkey_island.cc.services.post_breach_files import PostBreachFilesService
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def local_storage_file_repository(tmp_path):
|
||||
return LocalStorageFileRepository(tmp_path)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def post_breach_files_service(local_storage_file_repository):
|
||||
PostBreachFilesService.initialize(local_storage_file_repository)
|
||||
|
||||
|
||||
def test_remove_pba_files(local_storage_file_repository, tmp_path):
|
||||
local_storage_file_repository.save_file("linux_file", io.BytesIO(b""))
|
||||
local_storage_file_repository.save_file("windows_file", io.BytesIO(b""))
|
||||
assert not dir_is_empty(tmp_path)
|
||||
|
||||
PostBreachFilesService.remove_PBA_files()
|
||||
|
||||
assert dir_is_empty(tmp_path)
|
||||
|
||||
|
||||
def dir_is_empty(dir_path):
|
||||
dir_contents = os.listdir(dir_path)
|
||||
return len(dir_contents) == 0
|
||||
|
||||
|
||||
def test_remove_failure(local_storage_file_repository, monkeypatch):
|
||||
monkeypatch.setattr(os, "remove", lambda x: raise_(OSError("Permission denied")))
|
||||
|
||||
try:
|
||||
local_storage_file_repository.save_file("windows_file", io.BytesIO(b""))
|
||||
PostBreachFilesService.remove_PBA_files()
|
||||
except Exception as ex:
|
||||
pytest.fail(f"Unxepected exception: {ex}")
|
|
@ -0,0 +1,57 @@
|
|||
from dataclasses import replace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from tests.monkey_island import InMemoryAgentConfigurationRepository
|
||||
|
||||
from common.configuration import AgentConfiguration
|
||||
from monkey_island.cc.repository import IAgentConfigurationRepository, IFileRepository
|
||||
from monkey_island.cc.services import RepositoryService
|
||||
|
||||
LINUX_FILENAME = "linux_pba_file.sh"
|
||||
WINDOWS_FILENAME = "windows_pba_file.ps1"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent_configuration(default_agent_configuration) -> AgentConfiguration:
|
||||
custom_pbas = replace(
|
||||
default_agent_configuration.custom_pbas,
|
||||
linux_filename=LINUX_FILENAME,
|
||||
windows_filename=WINDOWS_FILENAME,
|
||||
)
|
||||
return replace(default_agent_configuration, custom_pbas=custom_pbas)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent_configuration_repository(agent_configuration) -> IAgentConfigurationRepository:
|
||||
agent_configuration_repository = InMemoryAgentConfigurationRepository()
|
||||
agent_configuration_repository.store_configuration(agent_configuration)
|
||||
|
||||
return agent_configuration_repository
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_file_repository():
|
||||
return MagicMock(spec=IFileRepository)
|
||||
|
||||
|
||||
def test_reset_configuration__remove_pba_files(
|
||||
agent_configuration_repository, mock_file_repository
|
||||
):
|
||||
repository_service = RepositoryService(agent_configuration_repository, mock_file_repository)
|
||||
|
||||
repository_service.reset_agent_configuration()
|
||||
|
||||
assert mock_file_repository.delete_file.called_with(LINUX_FILENAME)
|
||||
assert mock_file_repository.delete_file.called_with(WINDOWS_FILENAME)
|
||||
|
||||
|
||||
def test_reset_configuration__agent_configuration_changed(
|
||||
agent_configuration_repository, agent_configuration, mock_file_repository
|
||||
):
|
||||
mock_file_repository = MagicMock(spec=IFileRepository)
|
||||
repository_service = RepositoryService(agent_configuration_repository, mock_file_repository)
|
||||
|
||||
repository_service.reset_agent_configuration()
|
||||
|
||||
assert agent_configuration_repository.get_configuration() != agent_configuration
|
|
@ -19,9 +19,6 @@ def patch_attack_mitigations_path(monkeypatch, data_for_tests_dir):
|
|||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def patch_dependencies(monkeypatch_session):
|
||||
monkeypatch_session.setattr(
|
||||
"monkey_island.cc.services.config.ConfigService.init_config", lambda: None
|
||||
)
|
||||
monkeypatch_session.setattr(
|
||||
"monkey_island.cc.services.database.jsonify", MagicMock(return_value=True)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue