Merge branch 2267-add-network-services into develop

PR #2398
This commit is contained in:
Mike Salvatore 2022-10-05 08:20:13 -04:00 committed by GitHub
commit 82217b4094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 121 additions and 20 deletions

View File

@ -10,6 +10,11 @@ class InfectionMonkeyModelConfig:
extra = Extra.forbid extra = Extra.forbid
class MutableInfectionMonkeyModelConfig(InfectionMonkeyModelConfig):
allow_mutation = True
validate_assignment = True
class InfectionMonkeyBaseModel(BaseModel): class InfectionMonkeyBaseModel(BaseModel):
class Config(InfectionMonkeyModelConfig): class Config(InfectionMonkeyModelConfig):
pass pass
@ -47,6 +52,5 @@ class InfectionMonkeyBaseModel(BaseModel):
class MutableInfectionMonkeyBaseModel(InfectionMonkeyBaseModel): class MutableInfectionMonkeyBaseModel(InfectionMonkeyBaseModel):
class Config(InfectionMonkeyModelConfig): class Config(MutableInfectionMonkeyModelConfig):
allow_mutation = True pass
validate_assignment = True

View File

@ -28,6 +28,17 @@ JSONSerializable = Union[ # type: ignore[misc]
] ]
class NetworkService(Enum):
"""
An Enum representing network services
This Enum represents all network services that Infection Monkey supports. The value of each
member is the member's name in all lower-case characters.
"""
UNKNOWN = "unknown"
class NetworkPort(ConstrainedInt): class NetworkPort(ConstrainedInt):
""" """
Define network port as constrainer integer. Define network port as constrainer integer.

View File

@ -1,15 +1,14 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict from typing import Dict
from common.types import PingScanData from common.types import NetworkPort, PingScanData
from infection_monkey.i_puppet import FingerprintData, PortScanData from infection_monkey.i_puppet import FingerprintData, PortScanData
Port = int
FingerprinterName = str FingerprinterName = str
@dataclass @dataclass
class IPScanResults: class IPScanResults:
ping_scan_data: PingScanData ping_scan_data: PingScanData
port_scan_data: Dict[Port, PortScanData] port_scan_data: Dict[NetworkPort, PortScanData]
fingerprint_data: Dict[FingerprinterName, FingerprintData] fingerprint_data: Dict[FingerprinterName, FingerprintData]

View File

@ -10,7 +10,7 @@ from common.agent_configuration import (
PropagationConfiguration, PropagationConfiguration,
ScanTargetConfiguration, ScanTargetConfiguration,
) )
from common.types import PingScanData, PortStatus from common.types import NetworkPort, PingScanData, PortStatus
from infection_monkey.i_puppet import ExploiterResultData, FingerprintData, PortScanData from infection_monkey.i_puppet import ExploiterResultData, FingerprintData, PortScanData
from infection_monkey.model import VictimHost, VictimHostFactory from infection_monkey.model import VictimHost, VictimHostFactory
from infection_monkey.network import NetworkAddress from infection_monkey.network import NetworkAddress
@ -21,7 +21,7 @@ from infection_monkey.telemetry.scan_telem import ScanTelem
from infection_monkey.utils.threading import create_daemon_thread from infection_monkey.utils.threading import create_daemon_thread
from . import Exploiter, IPScanner, IPScanResults from . import Exploiter, IPScanner, IPScanResults
from .ip_scan_results import FingerprinterName, Port from .ip_scan_results import FingerprinterName
logger = logging.getLogger() logger = logging.getLogger()
@ -146,7 +146,7 @@ class Propagator:
@staticmethod @staticmethod
def _process_tcp_scan_results( def _process_tcp_scan_results(
victim_host: VictimHost, port_scan_data: Mapping[Port, PortScanData] victim_host: VictimHost, port_scan_data: Mapping[NetworkPort, PortScanData]
): ):
for psd in filter( for psd in filter(
lambda scan_data: scan_data.status == PortStatus.OPEN, port_scan_data.values() lambda scan_data: scan_data.status == PortStatus.OPEN, port_scan_data.values()

View File

@ -1,19 +1,45 @@
import json
from ipaddress import IPv4Interface from ipaddress import IPv4Interface
from typing import Optional, Sequence from typing import Any, Dict, Mapping, Optional, Sequence
from pydantic import Field, validator from pydantic import Field, validator
from common import OperatingSystem from common import OperatingSystem
from common.base_models import MutableInfectionMonkeyBaseModel from common.base_models import MutableInfectionMonkeyBaseModel, MutableInfectionMonkeyModelConfig
from common.transforms import make_immutable_sequence from common.transforms import make_immutable_sequence
from common.types import HardwareID from common.types import HardwareID, NetworkService, SocketAddress
from . import MachineID from . import MachineID
def _serialize_network_services(machine_dict: Dict, *, default):
machine_dict["network_services"] = {
str(addr): val for addr, val in machine_dict["network_services"].items()
}
return json.dumps(machine_dict, default=default)
class Machine(MutableInfectionMonkeyBaseModel): class Machine(MutableInfectionMonkeyBaseModel):
"""Represents machines, VMs, or other network nodes discovered by Infection Monkey""" """Represents machines, VMs, or other network nodes discovered by Infection Monkey"""
class Config(MutableInfectionMonkeyModelConfig):
json_dumps = _serialize_network_services
@validator("network_services", pre=True)
def _socketaddress_from_string(cls, v: Any) -> Any:
if not isinstance(v, Mapping):
# Let pydantic's type validation handle this
return v
new_network_services = {}
for addr, service in v.items():
if isinstance(addr, SocketAddress):
new_network_services[addr] = service
else:
new_network_services[SocketAddress.from_string(addr)] = service
return new_network_services
id: MachineID = Field(..., allow_mutation=False) id: MachineID = Field(..., allow_mutation=False)
"""Uniquely identifies the machine within the island""" """Uniquely identifies the machine within the island"""
@ -35,6 +61,9 @@ class Machine(MutableInfectionMonkeyBaseModel):
hostname: str = "" hostname: str = ""
"""The hostname of the machine""" """The hostname of the machine"""
network_services: Mapping[SocketAddress, NetworkService] = Field(default_factory=dict)
"""All network services found running on the machine"""
_make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)( _make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)(
make_immutable_sequence make_immutable_sequence
) )

View File

@ -1,9 +1,10 @@
from typing import FrozenSet, Mapping from typing import FrozenSet, Mapping, Tuple
from pydantic import Field from pydantic import Field
from typing_extensions import TypeAlias from typing_extensions import TypeAlias
from common.base_models import MutableInfectionMonkeyBaseModel from common.base_models import MutableInfectionMonkeyBaseModel
from common.types import SocketAddress
from . import CommunicationType, MachineID from . import CommunicationType, MachineID
@ -24,3 +25,6 @@ class Node(MutableInfectionMonkeyBaseModel):
connections: NodeConnections connections: NodeConnections
"""All outbound connections from this node to other machines""" """All outbound connections from this node to other machines"""
tcp_connections: Mapping[MachineID, Tuple[SocketAddress, ...]] = {}
"""All successfull outbound TCP connections"""

View File

@ -6,8 +6,12 @@ from typing import MutableSequence
import pytest import pytest
from common import OperatingSystem from common import OperatingSystem
from common.types import NetworkService, SocketAddress
from monkey_island.cc.models import Machine from monkey_island.cc.models import Machine
SOCKET_ADDR_1 = "192.168.1.10:5000"
SOCKET_ADDR_2 = "192.168.1.10:8080"
MACHINE_OBJECT_DICT = MappingProxyType( MACHINE_OBJECT_DICT = MappingProxyType(
{ {
"id": 1, "id": 1,
@ -17,6 +21,10 @@ MACHINE_OBJECT_DICT = MappingProxyType(
"operating_system": OperatingSystem.WINDOWS, "operating_system": OperatingSystem.WINDOWS,
"operating_system_version": "eXtra Problems", "operating_system_version": "eXtra Problems",
"hostname": "my.host", "hostname": "my.host",
"network_services": {
SocketAddress.from_string(SOCKET_ADDR_1): NetworkService.UNKNOWN,
SocketAddress.from_string(SOCKET_ADDR_2): NetworkService.UNKNOWN,
},
} }
) )
@ -26,9 +34,13 @@ MACHINE_SIMPLE_DICT = MappingProxyType(
"hardware_id": uuid.getnode(), "hardware_id": uuid.getnode(),
"island": True, "island": True,
"network_interfaces": ["10.0.0.1/24", "192.168.5.32/16"], "network_interfaces": ["10.0.0.1/24", "192.168.5.32/16"],
"operating_system": "windows", "operating_system": OperatingSystem.WINDOWS.value,
"operating_system_version": "eXtra Problems", "operating_system_version": "eXtra Problems",
"hostname": "my.host", "hostname": "my.host",
"network_services": {
SOCKET_ADDR_1: NetworkService.UNKNOWN.value,
SOCKET_ADDR_2: NetworkService.UNKNOWN.value,
},
} }
) )
@ -60,6 +72,11 @@ def test_to_dict():
("operating_system", "bsd"), ("operating_system", "bsd"),
("operating_system_version", {}), ("operating_system_version", {}),
("hostname", []), ("hostname", []),
("network_services", 42),
("network_services", [SOCKET_ADDR_1]),
("network_services", None),
("network_services", {SOCKET_ADDR_1: "Hello"}),
("network_services", {SocketAddress.from_string(SOCKET_ADDR_1): "Hello"}),
], ],
) )
def test_construct_invalid_field__type_error(key, value): def test_construct_invalid_field__type_error(key, value):
@ -77,6 +94,7 @@ def test_construct_invalid_field__type_error(key, value):
("hardware_id", 0), ("hardware_id", 0),
("network_interfaces", [1, "stuff", 3]), ("network_interfaces", [1, "stuff", 3]),
("network_interfaces", ["10.0.0.1/16", 2, []]), ("network_interfaces", ["10.0.0.1/16", 2, []]),
("network_services", {"192.168.": NetworkService.UNKNOWN.value}),
], ],
) )
def test_construct_invalid_field__value_error(key, value): def test_construct_invalid_field__value_error(key, value):
@ -230,3 +248,19 @@ def test_hostname_default_value():
m = Machine(**missing_hostname_dict) m = Machine(**missing_hostname_dict)
assert m.hostname == "" assert m.hostname == ""
def test_set_network_services_validates():
m = Machine(**MACHINE_OBJECT_DICT)
with pytest.raises(ValueError):
m.network_services = {"not-an-ip": NetworkService.UNKNOWN.value}
def test_set_network_services_default_value():
missing_network_services = MACHINE_OBJECT_DICT.copy()
del missing_network_services["network_services"]
m = Machine(**missing_network_services)
assert m.network_services == {}

View File

@ -2,6 +2,7 @@ from typing import MutableSequence
import pytest import pytest
from common.types import SocketAddress
from monkey_island.cc.models import CommunicationType, Node from monkey_island.cc.models import CommunicationType, Node
@ -11,13 +12,21 @@ def test_constructor():
6: frozenset((CommunicationType.SCANNED,)), 6: frozenset((CommunicationType.SCANNED,)),
7: frozenset((CommunicationType.SCANNED, CommunicationType.EXPLOITED)), 7: frozenset((CommunicationType.SCANNED, CommunicationType.EXPLOITED)),
} }
tcp_connections = {
6: tuple(
(SocketAddress(ip="192.168.1.1", port=80), SocketAddress(ip="192.168.1.1", port=443))
),
7: tuple((SocketAddress(ip="192.168.1.2", port=22),)),
}
n = Node( n = Node(
machine_id=1, machine_id=machine_id,
connections=connections, connections=connections,
tcp_connections=tcp_connections,
) )
assert n.machine_id == machine_id assert n.machine_id == machine_id
assert n.connections == connections assert n.connections == connections
assert n.tcp_connections == tcp_connections
def test_serialization(): def test_serialization():
@ -27,9 +36,12 @@ def test_serialization():
"6": [CommunicationType.CC.value, CommunicationType.SCANNED.value], "6": [CommunicationType.CC.value, CommunicationType.SCANNED.value],
"7": [CommunicationType.EXPLOITED.value, CommunicationType.CC.value], "7": [CommunicationType.EXPLOITED.value, CommunicationType.CC.value],
}, },
"tcp_connections": {
"6": [{"ip": "192.168.1.1", "port": 80}, {"ip": "192.168.1.1", "port": 443}],
"7": [{"ip": "192.168.1.2", "port": 22}],
},
} }
# "6": frozenset((CommunicationType.CC, CommunicationType.SCANNED)),
# "7": frozenset((CommunicationType.EXPLOITED, CommunicationType.CC)),
n = Node(**node_dict) n = Node(**node_dict)
serialized_node = n.dict(simplify=True) serialized_node = n.dict(simplify=True)
@ -44,6 +56,8 @@ def test_serialization():
for key, value in serialized_node["connections"].items(): for key, value in serialized_node["connections"].items():
assert set(value) == set(node_dict["connections"][key]) assert set(value) == set(node_dict["connections"][key])
assert serialized_node["tcp_connections"] == node_dict["tcp_connections"]
def test_machine_id_immutable(): def test_machine_id_immutable():
n = Node(machine_id=1, connections={}) n = Node(machine_id=1, connections={})

View File

@ -9,14 +9,13 @@ from common.agent_configuration.agent_sub_configurations import (
) )
from common.agent_events import ExploitationEvent, PingScanEvent, PropagationEvent, TCPScanEvent from common.agent_events import ExploitationEvent, PingScanEvent, PropagationEvent, TCPScanEvent
from common.credentials import Credentials, LMHash, NTHash from common.credentials import Credentials, LMHash, NTHash
from common.types import NetworkPort
from infection_monkey.exploit.HostExploiter.HostExploiter import ( from infection_monkey.exploit.HostExploiter.HostExploiter import (
_publish_exploitation_event, _publish_exploitation_event,
_publish_propagation_event, _publish_propagation_event,
) )
from common.types import NetworkPort, NetworkService
from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory
from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue from monkey_island.cc.models import Machine, Node, Report
from monkey_island.cc.models import Report
from monkey_island.cc.models.networkmap import Arc, NetworkMap from monkey_island.cc.models.networkmap import Arc, NetworkMap
from monkey_island.cc.repository import MongoAgentRepository, MongoMachineRepository from monkey_island.cc.repository import MongoAgentRepository, MongoMachineRepository
from monkey_island.cc.repository.attack.IMitigationsRepository import IMitigationsRepository from monkey_island.cc.repository.attack.IMitigationsRepository import IMitigationsRepository
@ -340,3 +339,10 @@ SCANNED
EXPLOITED EXPLOITED
CC CC
CC_TUNNEL CC_TUNNEL
# TODO remove when 2267 is done
NetworkServiceNameEnum.UNKNOWN
Machine.network_services
Machine.config.json_dumps
Machine._socketaddress_from_string
Node.tcp_connections