From f418571d9318fb7527404297cb930e8053e6bd3e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 10:01:24 -0400 Subject: [PATCH 01/12] Common: Add HardwareID type --- monkey/common/__init__.py | 1 + monkey/common/types.py | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 monkey/common/types.py diff --git a/monkey/common/__init__.py b/monkey/common/__init__.py index c63b927d5..af48cd045 100644 --- a/monkey/common/__init__.py +++ b/monkey/common/__init__.py @@ -3,3 +3,4 @@ Used for a common things between agent and island """ from .di_container import DIContainer, UnresolvableDependencyError from .operating_system import OperatingSystem +from . import types diff --git a/monkey/common/types.py b/monkey/common/types.py new file mode 100644 index 000000000..6063611c9 --- /dev/null +++ b/monkey/common/types.py @@ -0,0 +1,3 @@ +from pydantic import PositiveInt + +HardwareID = PositiveInt From acc158db44b276fc2c5081fe6ab63e621ff5294c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 11:14:25 -0400 Subject: [PATCH 02/12] Island: Use HardwareId in Machine model --- monkey/monkey_island/cc/models/machine.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/models/machine.py b/monkey/monkey_island/cc/models/machine.py index 1c4c4009c..8e2c0d122 100644 --- a/monkey/monkey_island/cc/models/machine.py +++ b/monkey/monkey_island/cc/models/machine.py @@ -4,6 +4,7 @@ from typing import Optional, Sequence from pydantic import Field, PositiveInt, validator from common import OperatingSystem +from common.types import HardwareID from .base_models import MutableBaseModel from .transforms import make_immutable_sequence @@ -13,7 +14,7 @@ MachineID = PositiveInt class Machine(MutableBaseModel): id: MachineID = Field(..., allow_mutation=False) - hardware_id: Optional[PositiveInt] + hardware_id: Optional[HardwareID] network_interfaces: Sequence[IPv4Interface] operating_system: OperatingSystem operating_system_version: str From 3a94be87665a7f4ee8f767de9b12e0bf5a5a6db6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 11:19:15 -0400 Subject: [PATCH 03/12] Common: Move base_models.py to common --- monkey/common/__init__.py | 1 + monkey/{monkey_island/cc/models => common}/base_models.py | 0 monkey/monkey_island/cc/models/agent.py | 3 ++- monkey/monkey_island/cc/models/machine.py | 2 +- monkey/monkey_island/cc/models/node.py | 3 ++- 5 files changed, 6 insertions(+), 3 deletions(-) rename monkey/{monkey_island/cc/models => common}/base_models.py (100%) diff --git a/monkey/common/__init__.py b/monkey/common/__init__.py index af48cd045..5dee4b146 100644 --- a/monkey/common/__init__.py +++ b/monkey/common/__init__.py @@ -4,3 +4,4 @@ Used for a common things between agent and island from .di_container import DIContainer, UnresolvableDependencyError from .operating_system import OperatingSystem from . import types +from . import base_models diff --git a/monkey/monkey_island/cc/models/base_models.py b/monkey/common/base_models.py similarity index 100% rename from monkey/monkey_island/cc/models/base_models.py rename to monkey/common/base_models.py diff --git a/monkey/monkey_island/cc/models/agent.py b/monkey/monkey_island/cc/models/agent.py index 73ff8d693..b3e79c4e2 100644 --- a/monkey/monkey_island/cc/models/agent.py +++ b/monkey/monkey_island/cc/models/agent.py @@ -4,8 +4,9 @@ from uuid import UUID from pydantic import Field +from common.base_models import MutableBaseModel + from . import MachineID -from .base_models import MutableBaseModel class Agent(MutableBaseModel): diff --git a/monkey/monkey_island/cc/models/machine.py b/monkey/monkey_island/cc/models/machine.py index 8e2c0d122..1ead17cc6 100644 --- a/monkey/monkey_island/cc/models/machine.py +++ b/monkey/monkey_island/cc/models/machine.py @@ -4,9 +4,9 @@ from typing import Optional, Sequence from pydantic import Field, PositiveInt, validator from common import OperatingSystem +from common.base_models import MutableBaseModel from common.types import HardwareID -from .base_models import MutableBaseModel from .transforms import make_immutable_sequence MachineID = PositiveInt diff --git a/monkey/monkey_island/cc/models/node.py b/monkey/monkey_island/cc/models/node.py index 6fa307734..136633aa6 100644 --- a/monkey/monkey_island/cc/models/node.py +++ b/monkey/monkey_island/cc/models/node.py @@ -2,8 +2,9 @@ from typing import Sequence, Tuple from pydantic import Field, validator +from common.base_models import MutableBaseModel + from . import CommunicationType, MachineID -from .base_models import MutableBaseModel from .transforms import make_immutable_nested_sequence ConnectionsSequence = Sequence[Tuple[MachineID, Sequence[CommunicationType]]] From fc14a1f573612d8a479db2816452ecb1e7c2ffc1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 11:34:31 -0400 Subject: [PATCH 04/12] Common: Make InfectionMonkeyModelConfig immutable --- monkey/common/base_models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/common/base_models.py b/monkey/common/base_models.py index 5d82be324..9521e4e68 100644 --- a/monkey/common/base_models.py +++ b/monkey/common/base_models.py @@ -5,6 +5,7 @@ from pydantic import BaseModel, Extra, ValidationError class InfectionMonkeyModelConfig: + allow_mutation = False underscore_attrs_are_private = True extra = Extra.forbid From 0607f36404d7431731fedda304359d166550ca93 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 12:33:36 -0400 Subject: [PATCH 05/12] Common: Move transforms from monkey_island to common --- monkey/common/__init__.py | 2 ++ monkey/{monkey_island/cc/models => common}/transforms.py | 0 monkey/monkey_island/cc/models/machine.py | 3 +-- monkey/monkey_island/cc/models/node.py | 2 +- .../{monkey_island/cc/models => common}/test_transforms.py | 5 +---- 5 files changed, 5 insertions(+), 7 deletions(-) rename monkey/{monkey_island/cc/models => common}/transforms.py (100%) rename monkey/tests/unit_tests/{monkey_island/cc/models => common}/test_transforms.py (92%) diff --git a/monkey/common/__init__.py b/monkey/common/__init__.py index 5dee4b146..ff871b4c4 100644 --- a/monkey/common/__init__.py +++ b/monkey/common/__init__.py @@ -1,7 +1,9 @@ """ Used for a common things between agent and island """ +from . import transforms from .di_container import DIContainer, UnresolvableDependencyError from .operating_system import OperatingSystem from . import types from . import base_models +from .agent_registration_message import AgentRegistrationMessage diff --git a/monkey/monkey_island/cc/models/transforms.py b/monkey/common/transforms.py similarity index 100% rename from monkey/monkey_island/cc/models/transforms.py rename to monkey/common/transforms.py diff --git a/monkey/monkey_island/cc/models/machine.py b/monkey/monkey_island/cc/models/machine.py index 1ead17cc6..a7c513484 100644 --- a/monkey/monkey_island/cc/models/machine.py +++ b/monkey/monkey_island/cc/models/machine.py @@ -5,10 +5,9 @@ from pydantic import Field, PositiveInt, validator from common import OperatingSystem from common.base_models import MutableBaseModel +from common.transforms import make_immutable_sequence from common.types import HardwareID -from .transforms import make_immutable_sequence - MachineID = PositiveInt diff --git a/monkey/monkey_island/cc/models/node.py b/monkey/monkey_island/cc/models/node.py index 136633aa6..74eb48964 100644 --- a/monkey/monkey_island/cc/models/node.py +++ b/monkey/monkey_island/cc/models/node.py @@ -3,9 +3,9 @@ from typing import Sequence, Tuple from pydantic import Field, validator from common.base_models import MutableBaseModel +from common.transforms import make_immutable_nested_sequence from . import CommunicationType, MachineID -from .transforms import make_immutable_nested_sequence ConnectionsSequence = Sequence[Tuple[MachineID, Sequence[CommunicationType]]] diff --git a/monkey/tests/unit_tests/monkey_island/cc/models/test_transforms.py b/monkey/tests/unit_tests/common/test_transforms.py similarity index 92% rename from monkey/tests/unit_tests/monkey_island/cc/models/test_transforms.py rename to monkey/tests/unit_tests/common/test_transforms.py index b0f71cee4..3163f5afb 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/models/test_transforms.py +++ b/monkey/tests/unit_tests/common/test_transforms.py @@ -3,10 +3,7 @@ from typing import MutableSequence, Sequence import pytest -from monkey_island.cc.models.transforms import ( - make_immutable_nested_sequence, - make_immutable_sequence, -) +from common.transforms import make_immutable_nested_sequence, make_immutable_sequence def test_make_immutable_sequence__list(): From 00514a3c0d17c5e2515baeb237df8245a32fe9e6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 12:41:39 -0400 Subject: [PATCH 06/12] Common: Add AgentRegistrationData --- monkey/common/__init__.py | 2 +- monkey/common/agent_registration_data.py | 23 ++++ .../models/test_agent_registration_message.py | 103 ++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 monkey/common/agent_registration_data.py create mode 100644 monkey/tests/unit_tests/monkey_island/cc/models/test_agent_registration_message.py diff --git a/monkey/common/__init__.py b/monkey/common/__init__.py index ff871b4c4..4898b2df8 100644 --- a/monkey/common/__init__.py +++ b/monkey/common/__init__.py @@ -6,4 +6,4 @@ from .di_container import DIContainer, UnresolvableDependencyError from .operating_system import OperatingSystem from . import types from . import base_models -from .agent_registration_message import AgentRegistrationMessage +from .agent_registration_data import AgentRegistrationData diff --git a/monkey/common/agent_registration_data.py b/monkey/common/agent_registration_data.py new file mode 100644 index 000000000..6d45df89f --- /dev/null +++ b/monkey/common/agent_registration_data.py @@ -0,0 +1,23 @@ +from datetime import datetime +from ipaddress import IPv4Interface +from typing import Optional, Sequence +from uuid import UUID + +from pydantic import validator + +from .base_models import InfectionMonkeyBaseModel +from .transforms import make_immutable_sequence +from .types import HardwareID + + +class AgentRegistrationData(InfectionMonkeyBaseModel): + id: UUID + machine_hardware_id: HardwareID + start_time: datetime + parent_id: Optional[UUID] + cc_server: str + network_interfaces: Sequence[IPv4Interface] + + _make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)( + make_immutable_sequence + ) diff --git a/monkey/tests/unit_tests/monkey_island/cc/models/test_agent_registration_message.py b/monkey/tests/unit_tests/monkey_island/cc/models/test_agent_registration_message.py new file mode 100644 index 000000000..2b755038c --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/models/test_agent_registration_message.py @@ -0,0 +1,103 @@ +from datetime import datetime, timezone +from ipaddress import IPv4Interface +from typing import MutableSequence, Sequence +from uuid import UUID + +import pytest + +from common import AgentRegistrationData + +AGENT_ID = UUID("012e7238-7b81-4108-8c7f-0787bc3f3c10") +PARENT_ID = UUID("0fc9afcb-1902-436b-bd5c-1ad194252484") + +AGENT_REGISTRATION_MESSAGE_OBJECT_DICT = { + "id": AGENT_ID, + "machine_hardware_id": 2, + "start_time": datetime.fromtimestamp(1660848408, tz=timezone.utc), + "parent_id": PARENT_ID, + "cc_server": "192.168.1.1:5000", + "network_interfaces": [IPv4Interface("10.0.0.1/24"), IPv4Interface("192.168.5.32/16")], +} + +AGENT_REGISTRATION_MESSAGE_SIMPLE_DICT = { + "id": str(AGENT_ID), + "machine_hardware_id": 2, + "start_time": "2022-08-18T18:46:48+00:00", + "parent_id": str(PARENT_ID), + "cc_server": "192.168.1.1:5000", + "network_interfaces": ["10.0.0.1/24", "192.168.5.32/16"], +} + + +def test_to_dict(): + a = AgentRegistrationData(**AGENT_REGISTRATION_MESSAGE_OBJECT_DICT) + simple_dict = AGENT_REGISTRATION_MESSAGE_SIMPLE_DICT.copy() + + assert a.dict(simplify=True) == simple_dict + + +def test_from_serialized(): + from_serialized = AgentRegistrationData(**AGENT_REGISTRATION_MESSAGE_SIMPLE_DICT) + from_objects = AgentRegistrationData(**AGENT_REGISTRATION_MESSAGE_OBJECT_DICT) + + assert from_serialized == from_objects + + +@pytest.mark.parametrize( + "key, value", + [ + ("id", 1), + ("machine_hardware_id", "not-an-int"), + ("start_time", None), + ("parent_id", 2.1), + ("cc_server", []), + ("network_interfaces", "not-a-list"), + ], +) +def test_construct_invalid_field__type_error(key, value): + invalid_type_dict = AGENT_REGISTRATION_MESSAGE_SIMPLE_DICT.copy() + invalid_type_dict[key] = value + + with pytest.raises(TypeError): + AgentRegistrationData(**invalid_type_dict) + + +@pytest.mark.parametrize( + "key, value", + [ + ("machine_hardware_id", -1), + ("start_time", "not-a-date-time"), + ("network_interfaces", [1, "stuff", 3]), + ], +) +def test_construct_invalid_field__value_error(key, value): + invalid_value_dict = AGENT_REGISTRATION_MESSAGE_SIMPLE_DICT.copy() + invalid_value_dict[key] = value + + with pytest.raises(ValueError): + AgentRegistrationData(**invalid_value_dict) + + +@pytest.mark.parametrize( + "key, value", + [ + ("id", PARENT_ID), + ("machine_hardware_id", 99), + ("start_time", 0), + ("parent_id", AGENT_ID), + ("cc_server", "10.0.0.1:4999"), + ("network_interfaces", ["10.0.0.1/24"]), + ], +) +def test_fields_immutable(key, value): + a = AgentRegistrationData(**AGENT_REGISTRATION_MESSAGE_OBJECT_DICT) + + with pytest.raises(TypeError): + setattr(a, key, value) + + +def test_network_interfaces_sequence_immutable(): + a = AgentRegistrationData(**AGENT_REGISTRATION_MESSAGE_OBJECT_DICT) + + assert isinstance(a.network_interfaces, Sequence) + assert not isinstance(a.network_interfaces, MutableSequence) From 78f792aee9c2b6a575b2543c0455dda27eb2f8d0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 13:23:08 -0400 Subject: [PATCH 07/12] Island: Add /api/agents endpoint --- CHANGELOG.md | 1 + monkey/monkey_island/cc/app.py | 2 + monkey/monkey_island/cc/resources/__init__.py | 1 + monkey/monkey_island/cc/resources/agents.py | 22 ++++++++++ .../monkey_island/cc/resources/test_agents.py | 42 +++++++++++++++++++ 5 files changed, 68 insertions(+) create mode 100644 monkey/monkey_island/cc/resources/agents.py create mode 100644 monkey/tests/unit_tests/monkey_island/cc/resources/test_agents.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 095c32baa..8a7e48601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - `/api/events` endpoint. #2155 - The ability to customize the file extension used by ransomware when encrypting files. #1242 +- `/api/agents` endpoint. ### Changed - Reset workflow. Now it's possible to delete data gathered by agents without diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index d27fd24bf..353b4ca73 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -13,6 +13,7 @@ from monkey_island.cc.database import database, mongo from monkey_island.cc.resources import ( AgentBinaries, AgentConfiguration, + Agents, ClearSimulationData, Events, IPAddresses, @@ -162,6 +163,7 @@ def init_restful_endpoints(api: FlaskDIWrapper): api.add_resource(Register) api.add_resource(RegistrationStatus) api.add_resource(Authenticate) + api.add_resource(Agents) api.add_resource(Monkey) api.add_resource(LocalRun) api.add_resource(Telemetry) diff --git a/monkey/monkey_island/cc/resources/__init__.py b/monkey/monkey_island/cc/resources/__init__.py index b58e159bc..980414a8f 100644 --- a/monkey/monkey_island/cc/resources/__init__.py +++ b/monkey/monkey_island/cc/resources/__init__.py @@ -9,3 +9,4 @@ from .agent_configuration import AgentConfiguration from .pba_file_upload import PBAFileUpload, LINUX_PBA_TYPE, WINDOWS_PBA_TYPE from .pba_file_download import PBAFileDownload from .events import Events +from .agents import Agents diff --git a/monkey/monkey_island/cc/resources/agents.py b/monkey/monkey_island/cc/resources/agents.py new file mode 100644 index 000000000..79d6fc872 --- /dev/null +++ b/monkey/monkey_island/cc/resources/agents.py @@ -0,0 +1,22 @@ +import json +from http import HTTPStatus + +from flask import make_response, request + +from common import AgentRegistrationData +from monkey_island.cc.resources.AbstractResource import AbstractResource + + +class Agents(AbstractResource): + urls = ["/api/agents"] + + def post(self): + try: + # Just parse for now + AgentRegistrationData(**request.json) + return make_response({}, HTTPStatus.NO_CONTENT) + except (TypeError, ValueError, json.JSONDecodeError) as err: + return make_response( + {"error": f"Invalid configuration supplied: {err}"}, + HTTPStatus.BAD_REQUEST, + ) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_agents.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_agents.py new file mode 100644 index 000000000..3ab90feb6 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_agents.py @@ -0,0 +1,42 @@ +from http import HTTPStatus +from uuid import UUID + +from tests.unit_tests.monkey_island.conftest import get_url_for_resource + +from monkey_island.cc.resources import Agents + +AGENTS_URL = get_url_for_resource(Agents) + +AGENT_REGISTRATION_DICT = { + "id": UUID("6bfd8b64-43d8-4449-8c70-d898aca74ad8"), + "machine_hardware_id": 1, + "start_time": 0, + "parent_id": UUID("9d55ba33-95c2-417d-bd86-d3d11e47daeb"), + "cc_server": "10.0.0.1:5000", + "network_interfaces": ["10.1.1.2/24"], +} + + +def test_agent_registration(flask_client): + print(AGENTS_URL) + resp = flask_client.post( + AGENTS_URL, + json=AGENT_REGISTRATION_DICT, + follow_redirects=True, + ) + + assert resp.status_code == HTTPStatus.NO_CONTENT + + +def test_agent_registration_invalid_data(flask_client): + agent_registration_dict = AGENT_REGISTRATION_DICT.copy() + + agent_registration_dict["id"] = 1 + + resp = flask_client.post( + AGENTS_URL, + json=agent_registration_dict, + follow_redirects=True, + ) + + assert resp.status_code == HTTPStatus.BAD_REQUEST From 160d2d11cf0918ab58067c52e146f73f0483e5a9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 14:02:18 -0400 Subject: [PATCH 08/12] Agent: Add get_agent_id() --- monkey/infection_monkey/utils/agent_id.py | 17 +++++++++++++++++ .../infection_monkey/utils/test_agent_id.py | 10 ++++++++++ 2 files changed, 27 insertions(+) create mode 100644 monkey/infection_monkey/utils/agent_id.py create mode 100644 monkey/tests/unit_tests/infection_monkey/utils/test_agent_id.py diff --git a/monkey/infection_monkey/utils/agent_id.py b/monkey/infection_monkey/utils/agent_id.py new file mode 100644 index 000000000..0a889a114 --- /dev/null +++ b/monkey/infection_monkey/utils/agent_id.py @@ -0,0 +1,17 @@ +from uuid import UUID, uuid4 + + +def get_agent_id() -> UUID: + """ + Get the agent ID for the current running agent + + Each time an agent process starts, the return value of this function will be unique. Subsequent + calls to this function from within the same process will have the same return value. + """ + if get_agent_id._id is None: + get_agent_id._id = uuid4() + + return get_agent_id._id + + +get_agent_id._id = None diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_agent_id.py b/monkey/tests/unit_tests/infection_monkey/utils/test_agent_id.py new file mode 100644 index 000000000..c4a95b73c --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_agent_id.py @@ -0,0 +1,10 @@ +from uuid import UUID + +from infection_monkey.utils.agent_id import get_agent_id + + +def test_get_agent_id(): + agent_id = get_agent_id() + + assert isinstance(agent_id, UUID) + assert agent_id == get_agent_id() From 09739268fa58af4f6b1bea0e89cde49ab25c05af Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 14:07:09 -0400 Subject: [PATCH 09/12] Agent: Add get_machine_id() --- .../utils/{agent_id.py => ids.py} | 7 ++++++- .../infection_monkey/utils/test_agent_id.py | 10 ---------- .../infection_monkey/utils/test_ids.py | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) rename monkey/infection_monkey/utils/{agent_id.py => ids.py} (70%) delete mode 100644 monkey/tests/unit_tests/infection_monkey/utils/test_agent_id.py create mode 100644 monkey/tests/unit_tests/infection_monkey/utils/test_ids.py diff --git a/monkey/infection_monkey/utils/agent_id.py b/monkey/infection_monkey/utils/ids.py similarity index 70% rename from monkey/infection_monkey/utils/agent_id.py rename to monkey/infection_monkey/utils/ids.py index 0a889a114..04f9aea29 100644 --- a/monkey/infection_monkey/utils/agent_id.py +++ b/monkey/infection_monkey/utils/ids.py @@ -1,4 +1,4 @@ -from uuid import UUID, uuid4 +from uuid import UUID, getnode, uuid4 def get_agent_id() -> UUID: @@ -15,3 +15,8 @@ def get_agent_id() -> UUID: get_agent_id._id = None + + +def get_machine_id() -> int: + """Get an integer that uniquely defines the machine the agent is running on""" + return getnode() diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_agent_id.py b/monkey/tests/unit_tests/infection_monkey/utils/test_agent_id.py deleted file mode 100644 index c4a95b73c..000000000 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_agent_id.py +++ /dev/null @@ -1,10 +0,0 @@ -from uuid import UUID - -from infection_monkey.utils.agent_id import get_agent_id - - -def test_get_agent_id(): - agent_id = get_agent_id() - - assert isinstance(agent_id, UUID) - assert agent_id == get_agent_id() diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_ids.py b/monkey/tests/unit_tests/infection_monkey/utils/test_ids.py new file mode 100644 index 000000000..28fa9a64e --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_ids.py @@ -0,0 +1,17 @@ +from uuid import UUID + +from infection_monkey.utils.ids import get_agent_id, get_machine_id + + +def test_get_agent_id(): + agent_id = get_agent_id() + + assert isinstance(agent_id, UUID) + assert agent_id == get_agent_id() + + +def test_get_machine_id(): + machine_id = get_machine_id() + + assert isinstance(machine_id, int) + assert machine_id == get_machine_id() From 80b4b05b653b54a8cb6f2f451b969b9a120fd0c6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 14:32:15 -0400 Subject: [PATCH 10/12] Agent: Add pydantic as a dependency --- monkey/infection_monkey/Pipfile | 1 + monkey/infection_monkey/Pipfile.lock | 44 ++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index 73923cd43..0158a7870 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -27,6 +27,7 @@ paramiko = {editable = true, ref = "2.10.3.dev1", git = "https://github.com/Vaka marshmallow = "*" marshmallow-enum = "*" pypubsub = "*" +pydantic = "*" [dev-packages] ldap3 = "*" diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index 7d2e940ef..4bc8564ce 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4b34d3b744fa5d28b36d6f1dae271474b6a92db3a221b8c14d3c04eedfab5d9d" + "sha256": "0ae0a7c88cba4dbd3ad91fd472f6bf12399a9819931d1bf3a936623fa2bfcb6d" }, "pipfile-spec": 6, "requires": { @@ -630,6 +630,47 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==3.15.0" }, + "pydantic": { + "hashes": [ + "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44", + "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d", + "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84", + "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555", + "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7", + "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131", + "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8", + "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3", + "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56", + "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0", + "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4", + "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453", + "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044", + "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e", + "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15", + "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb", + "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001", + "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d", + "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3", + "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e", + "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f", + "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c", + "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b", + "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8", + "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567", + "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979", + "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326", + "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb", + "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f", + "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa", + "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747", + "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801", + "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55", + "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08", + "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76" + ], + "index": "pypi", + "version": "==1.9.2" + }, "pyinstaller": { "hashes": [ "sha256:24035eb9fffa2e3e288b4c1c9710043819efc7203cae5c8c573bec16f4a8e98f", @@ -935,7 +976,6 @@ "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6" ], - "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==1.5.1" }, From 9c32dee739b69c639e191626ff54a4f06780d728 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 14:37:34 -0400 Subject: [PATCH 11/12] Agent: Register agent on startup --- monkey/infection_monkey/i_control_channel.py | 14 +++++++- .../master/control_channel.py | 34 ++++++++++++++++++- monkey/infection_monkey/monkey.py | 1 + 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/i_control_channel.py b/monkey/infection_monkey/i_control_channel.py index 25135231f..39075750a 100644 --- a/monkey/infection_monkey/i_control_channel.py +++ b/monkey/infection_monkey/i_control_channel.py @@ -1,11 +1,23 @@ import abc -from typing import Sequence +from typing import Optional, Sequence +from uuid import UUID from common.agent_configuration import AgentConfiguration from common.credentials import Credentials class IControlChannel(metaclass=abc.ABCMeta): + @abc.abstractmethod + def register_agent(self, parent_id: Optional[UUID] = None): + """ + Registers this agent with the Island when this agent starts + + :param parent: The ID of the parent that spawned this agent, or None if this agent has no + parent + :raises IslandCommunicationError: If the agent cannot be successfully registered + """ + pass + @abc.abstractmethod def should_agent_stop(self) -> bool: """ diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py index a4709b124..8fa64d461 100644 --- a/monkey/infection_monkey/master/control_channel.py +++ b/monkey/infection_monkey/master/control_channel.py @@ -1,14 +1,18 @@ import json import logging from pprint import pformat -from typing import Mapping, Sequence +from typing import Mapping, Optional, Sequence +from uuid import UUID import requests +from common import AgentRegistrationData from common.agent_configuration import AgentConfiguration from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT from common.credentials import Credentials from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError +from infection_monkey.utils import agent_process +from infection_monkey.utils.ids import get_agent_id, get_machine_id requests.packages.urllib3.disable_warnings() @@ -21,6 +25,34 @@ class ControlChannel(IControlChannel): self._control_channel_server = server self._proxies = proxies + def register_agent(self, parent: Optional[UUID] = None): + agent_registration_data = AgentRegistrationData( + id=get_agent_id(), + machine_hardware_id=get_machine_id(), + start_time=agent_process.get_start_time(), + parent_id=parent, + cc_server=self._control_channel_server, + network_interfaces=[], # TODO: Populate this + ) + + try: + url = f"https://{self._control_channel_server}/api/agents" + response = requests.post( # noqa: DUO123 + url, + json=agent_registration_data.dict(simplify=True), + verify=False, + proxies=self._proxies, + timeout=SHORT_REQUEST_TIMEOUT, + ) + response.raise_for_status() + except ( + requests.exceptions.ConnectionError, + requests.exceptions.Timeout, + requests.exceptions.TooManyRedirects, + requests.exceptions.HTTPError, + ) as e: + raise IslandCommunicationError(e) + def should_agent_stop(self) -> bool: if not self._control_channel_server: logger.error("Agent should stop because it can't connect to the C&C server.") diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 98b2cab78..7d6962e88 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -178,6 +178,7 @@ class InfectionMonkey: control_channel = ControlChannel( self._control_client.server_address, GUID, self._control_client.proxies ) + control_channel.register_agent(self._opts.parent) config = control_channel.get_config() self._monkey_inbound_tunnel = self._control_client.create_control_tunnel( From ab399e4f34e8c02bceb6a9566c2f099c98a2e3a6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Aug 2022 14:49:31 -0400 Subject: [PATCH 12/12] Island: Add debug logging to Agents resource --- monkey/monkey_island/cc/resources/agents.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/agents.py b/monkey/monkey_island/cc/resources/agents.py index 79d6fc872..b727bd9ae 100644 --- a/monkey/monkey_island/cc/resources/agents.py +++ b/monkey/monkey_island/cc/resources/agents.py @@ -1,4 +1,5 @@ import json +import logging from http import HTTPStatus from flask import make_response, request @@ -6,6 +7,8 @@ from flask import make_response, request from common import AgentRegistrationData from monkey_island.cc.resources.AbstractResource import AbstractResource +logger = logging.getLogger(__name__) + class Agents(AbstractResource): urls = ["/api/agents"] @@ -13,7 +16,10 @@ class Agents(AbstractResource): def post(self): try: # Just parse for now - AgentRegistrationData(**request.json) + agent_registration_data = AgentRegistrationData(**request.json) + + logger.debug(f"Agent registered: {agent_registration_data}") + return make_response({}, HTTPStatus.NO_CONTENT) except (TypeError, ValueError, json.JSONDecodeError) as err: return make_response(