forked from p15670423/monkey
Merge pull request #2045 from guardicore/1960-configuration-object
1960 configuration object
This commit is contained in:
commit
5a0d891c35
|
@ -2,3 +2,4 @@
|
|||
Used for a common things between agent and island
|
||||
"""
|
||||
from .di_container import DIContainer, UnregisteredTypeError
|
||||
from .operating_systems import OperatingSystems
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from .agent_configuration import AgentConfiguration, InvalidConfigurationError
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
PluginConfiguration,
|
||||
ScanTargetConfiguration,
|
||||
ICMPScanConfiguration,
|
||||
TCPScanConfiguration,
|
||||
NetworkScanConfiguration,
|
||||
ExploitationOptionsConfiguration,
|
||||
ExploitationConfiguration,
|
||||
PropagationConfiguration,
|
||||
)
|
||||
from .default_agent_configuration import (
|
||||
DEFAULT_AGENT_CONFIGURATION,
|
||||
)
|
|
@ -0,0 +1,97 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, List, Mapping
|
||||
|
||||
from marshmallow import Schema, fields
|
||||
from marshmallow.exceptions import MarshmallowError
|
||||
|
||||
from .agent_sub_configuration_schemas import (
|
||||
CustomPBAConfigurationSchema,
|
||||
PluginConfigurationSchema,
|
||||
PropagationConfigurationSchema,
|
||||
)
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
PluginConfiguration,
|
||||
PropagationConfiguration,
|
||||
)
|
||||
|
||||
|
||||
class InvalidConfigurationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
INVALID_CONFIGURATION_ERROR_MESSAGE = (
|
||||
"Cannot construct an AgentConfiguration object with the supplied, invalid data:"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AgentConfiguration:
|
||||
keep_tunnel_open_time: float
|
||||
custom_pbas: CustomPBAConfiguration
|
||||
post_breach_actions: List[PluginConfiguration]
|
||||
credential_collectors: List[PluginConfiguration]
|
||||
payloads: List[PluginConfiguration]
|
||||
propagation: PropagationConfiguration
|
||||
|
||||
def __post_init__(self):
|
||||
# This will raise an exception if the object is invalid. Calling this in __post__init()
|
||||
# makes it impossible to construct an invalid object
|
||||
try:
|
||||
AgentConfigurationSchema().dump(self)
|
||||
except Exception as err:
|
||||
raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}")
|
||||
|
||||
@staticmethod
|
||||
def from_mapping(config_mapping: Mapping[str, Any]) -> AgentConfiguration:
|
||||
"""
|
||||
Construct an AgentConfiguration from a Mapping
|
||||
|
||||
:param config_mapping: A Mapping that represents an AgentConfiguration
|
||||
:return: An AgentConfiguration
|
||||
:raises: InvalidConfigurationError if the provided Mapping does not represent a valid
|
||||
AgentConfiguration
|
||||
"""
|
||||
|
||||
try:
|
||||
config_dict = AgentConfigurationSchema().load(config_mapping)
|
||||
return AgentConfiguration(**config_dict)
|
||||
except MarshmallowError as err:
|
||||
raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}")
|
||||
|
||||
@staticmethod
|
||||
def from_json(config_json: str) -> AgentConfiguration:
|
||||
"""
|
||||
Construct an AgentConfiguration from a JSON string
|
||||
|
||||
:param config_json: A JSON string that represents an AgentConfiguration
|
||||
:return: An AgentConfiguration
|
||||
:raises: InvalidConfigurationError if the provided JSON does not represent a valid
|
||||
AgentConfiguration
|
||||
"""
|
||||
try:
|
||||
config_dict = AgentConfigurationSchema().loads(config_json)
|
||||
return AgentConfiguration(**config_dict)
|
||||
except MarshmallowError as err:
|
||||
raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}")
|
||||
|
||||
@staticmethod
|
||||
def to_json(config: AgentConfiguration) -> str:
|
||||
"""
|
||||
Serialize an AgentConfiguration to JSON
|
||||
|
||||
:param config: An AgentConfiguration
|
||||
:return: A JSON string representing the AgentConfiguration
|
||||
"""
|
||||
return AgentConfigurationSchema().dumps(config)
|
||||
|
||||
|
||||
class AgentConfigurationSchema(Schema):
|
||||
keep_tunnel_open_time = fields.Float()
|
||||
custom_pbas = fields.Nested(CustomPBAConfigurationSchema)
|
||||
post_breach_actions = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
credential_collectors = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
payloads = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
propagation = fields.Nested(PropagationConfigurationSchema)
|
|
@ -0,0 +1,100 @@
|
|||
from marshmallow import Schema, fields, post_load
|
||||
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
ExploitationConfiguration,
|
||||
ExploitationOptionsConfiguration,
|
||||
ICMPScanConfiguration,
|
||||
NetworkScanConfiguration,
|
||||
PluginConfiguration,
|
||||
PropagationConfiguration,
|
||||
ScanTargetConfiguration,
|
||||
TCPScanConfiguration,
|
||||
)
|
||||
|
||||
|
||||
class CustomPBAConfigurationSchema(Schema):
|
||||
linux_command = fields.Str()
|
||||
linux_filename = fields.Str()
|
||||
windows_command = fields.Str()
|
||||
windows_filename = fields.Str()
|
||||
|
||||
@post_load
|
||||
def _make_custom_pba_configuration(self, data, **kwargs):
|
||||
return CustomPBAConfiguration(**data)
|
||||
|
||||
|
||||
class PluginConfigurationSchema(Schema):
|
||||
name = fields.Str()
|
||||
options = fields.Mapping()
|
||||
|
||||
@post_load
|
||||
def _make_plugin_configuration(self, data, **kwargs):
|
||||
return PluginConfiguration(**data)
|
||||
|
||||
|
||||
class ScanTargetConfigurationSchema(Schema):
|
||||
blocked_ips = fields.List(fields.Str())
|
||||
inaccessible_subnets = fields.List(fields.Str())
|
||||
local_network_scan = fields.Bool()
|
||||
subnets = fields.List(fields.Str())
|
||||
|
||||
@post_load
|
||||
def _make_scan_target_configuration(self, data, **kwargs):
|
||||
return ScanTargetConfiguration(**data)
|
||||
|
||||
|
||||
class ICMPScanConfigurationSchema(Schema):
|
||||
timeout = fields.Float()
|
||||
|
||||
@post_load
|
||||
def _make_icmp_scan_configuration(self, data, **kwargs):
|
||||
return ICMPScanConfiguration(**data)
|
||||
|
||||
|
||||
class TCPScanConfigurationSchema(Schema):
|
||||
timeout = fields.Float()
|
||||
ports = fields.List(fields.Int())
|
||||
|
||||
@post_load
|
||||
def _make_tcp_scan_configuration(self, data, **kwargs):
|
||||
return TCPScanConfiguration(**data)
|
||||
|
||||
|
||||
class NetworkScanConfigurationSchema(Schema):
|
||||
tcp = fields.Nested(TCPScanConfigurationSchema)
|
||||
icmp = fields.Nested(ICMPScanConfigurationSchema)
|
||||
fingerprinters = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
targets = fields.Nested(ScanTargetConfigurationSchema)
|
||||
|
||||
@post_load
|
||||
def _make_network_scan_configuration(self, data, **kwargs):
|
||||
return NetworkScanConfiguration(**data)
|
||||
|
||||
|
||||
class ExploitationOptionsConfigurationSchema(Schema):
|
||||
http_ports = fields.List(fields.Int())
|
||||
|
||||
@post_load
|
||||
def _make_exploitation_options_configuration(self, data, **kwargs):
|
||||
return ExploitationOptionsConfiguration(**data)
|
||||
|
||||
|
||||
class ExploitationConfigurationSchema(Schema):
|
||||
options = fields.Nested(ExploitationOptionsConfigurationSchema)
|
||||
brute_force = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
vulnerability = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
|
||||
@post_load
|
||||
def _make_exploitation_options_configuration(self, data, **kwargs):
|
||||
return ExploitationConfiguration(**data)
|
||||
|
||||
|
||||
class PropagationConfigurationSchema(Schema):
|
||||
maximum_depth = fields.Int()
|
||||
network_scan = fields.Nested(NetworkScanConfigurationSchema)
|
||||
exploitation = fields.Nested(ExploitationConfigurationSchema)
|
||||
|
||||
@post_load
|
||||
def _make_propagation_configuration(self, data, **kwargs):
|
||||
return PropagationConfiguration(**data)
|
|
@ -0,0 +1,62 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CustomPBAConfiguration:
|
||||
linux_command: str
|
||||
linux_filename: str
|
||||
windows_command: str
|
||||
windows_filename: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PluginConfiguration:
|
||||
name: str
|
||||
options: Dict
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ScanTargetConfiguration:
|
||||
blocked_ips: List[str]
|
||||
inaccessible_subnets: List[str]
|
||||
local_network_scan: bool
|
||||
subnets: List[str]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ICMPScanConfiguration:
|
||||
timeout: float
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TCPScanConfiguration:
|
||||
timeout: float
|
||||
ports: List[int]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NetworkScanConfiguration:
|
||||
tcp: TCPScanConfiguration
|
||||
icmp: ICMPScanConfiguration
|
||||
fingerprinters: List[PluginConfiguration]
|
||||
targets: ScanTargetConfiguration
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExploitationOptionsConfiguration:
|
||||
http_ports: List[int]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExploitationConfiguration:
|
||||
options: ExploitationOptionsConfiguration
|
||||
brute_force: List[PluginConfiguration]
|
||||
vulnerability: List[PluginConfiguration]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PropagationConfiguration:
|
||||
maximum_depth: int
|
||||
network_scan: NetworkScanConfiguration
|
||||
exploitation: ExploitationConfiguration
|
|
@ -0,0 +1,114 @@
|
|||
from . import AgentConfiguration
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
ExploitationConfiguration,
|
||||
ExploitationOptionsConfiguration,
|
||||
ICMPScanConfiguration,
|
||||
NetworkScanConfiguration,
|
||||
PluginConfiguration,
|
||||
PropagationConfiguration,
|
||||
ScanTargetConfiguration,
|
||||
TCPScanConfiguration,
|
||||
)
|
||||
|
||||
PBAS = [
|
||||
"CommunicateAsBackdoorUser",
|
||||
"ModifyShellStartupFiles",
|
||||
"HiddenFiles",
|
||||
"TrapCommand",
|
||||
"ChangeSetuidSetgid",
|
||||
"ScheduleJobs",
|
||||
"Timestomping",
|
||||
"AccountDiscovery",
|
||||
"ProcessListCollection",
|
||||
]
|
||||
|
||||
CREDENTIAL_COLLECTORS = ["MimikatzCollector", "SSHCollector"]
|
||||
|
||||
PBA_CONFIGURATION = [PluginConfiguration(pba, {}) for pba in PBAS]
|
||||
CREDENTIAL_COLLECTOR_CONFIGURATION = [
|
||||
PluginConfiguration(collector, {}) for collector in CREDENTIAL_COLLECTORS
|
||||
]
|
||||
|
||||
RANSOMWARE_OPTIONS = {
|
||||
"encryption": {
|
||||
"enabled": True,
|
||||
"directories": {"linux_target_dir": "", "windows_target_dir": ""},
|
||||
},
|
||||
"other_behaviors": {"readme": True},
|
||||
}
|
||||
|
||||
PAYLOAD_CONFIGURATION = [PluginConfiguration("ransomware", RANSOMWARE_OPTIONS)]
|
||||
|
||||
CUSTOM_PBA_CONFIGURATION = CustomPBAConfiguration(
|
||||
linux_command="", linux_filename="", windows_command="", windows_filename=""
|
||||
)
|
||||
|
||||
TCP_PORTS = [
|
||||
22,
|
||||
80,
|
||||
135,
|
||||
443,
|
||||
445,
|
||||
2222,
|
||||
3306,
|
||||
3389,
|
||||
5985,
|
||||
5986,
|
||||
7001,
|
||||
8008,
|
||||
8080,
|
||||
8088,
|
||||
8983,
|
||||
9200,
|
||||
9600,
|
||||
]
|
||||
|
||||
TCP_SCAN_CONFIGURATION = TCPScanConfiguration(timeout=3.0, ports=TCP_PORTS)
|
||||
ICMP_CONFIGURATION = ICMPScanConfiguration(timeout=1.0)
|
||||
HTTP_PORTS = [80, 443, 7001, 8008, 8080, 8983, 9200, 9600]
|
||||
FINGERPRINTERS = [
|
||||
PluginConfiguration("elastic", {}),
|
||||
PluginConfiguration("http", {"http_ports": HTTP_PORTS}),
|
||||
PluginConfiguration("mssql", {}),
|
||||
PluginConfiguration("smb", {}),
|
||||
PluginConfiguration("ssh", {}),
|
||||
]
|
||||
|
||||
SCAN_TARGET_CONFIGURATION = ScanTargetConfiguration([], [], True, [])
|
||||
NETWORK_SCAN_CONFIGURATION = NetworkScanConfiguration(
|
||||
TCP_SCAN_CONFIGURATION, ICMP_CONFIGURATION, FINGERPRINTERS, SCAN_TARGET_CONFIGURATION
|
||||
)
|
||||
|
||||
EXPLOITATION_OPTIONS_CONFIGURATION = ExploitationOptionsConfiguration(HTTP_PORTS)
|
||||
BRUTE_FORCE_EXPLOITERS = [
|
||||
PluginConfiguration("MSSQLExploiter", {}),
|
||||
PluginConfiguration("PowerShellExploiter", {}),
|
||||
PluginConfiguration("SSHExploiter", {}),
|
||||
PluginConfiguration("SmbExploiter", {"smb_download_timeout": 30}),
|
||||
PluginConfiguration("WmiExploiter", {"smb_download_timeout": 30}),
|
||||
]
|
||||
|
||||
VULNERABILITY_EXPLOITERS = [
|
||||
PluginConfiguration("Log4ShellExploiter", {}),
|
||||
PluginConfiguration("HadoopExploiter", {}),
|
||||
]
|
||||
|
||||
EXPLOITATION_CONFIGURATION = ExploitationConfiguration(
|
||||
EXPLOITATION_OPTIONS_CONFIGURATION, BRUTE_FORCE_EXPLOITERS, VULNERABILITY_EXPLOITERS
|
||||
)
|
||||
|
||||
PROPAGATION_CONFIGURATION = PropagationConfiguration(
|
||||
maximum_depth=2,
|
||||
network_scan=NETWORK_SCAN_CONFIGURATION,
|
||||
exploitation=EXPLOITATION_CONFIGURATION,
|
||||
)
|
||||
|
||||
DEFAULT_AGENT_CONFIGURATION = AgentConfiguration(
|
||||
keep_tunnel_open_time=30,
|
||||
custom_pbas=CUSTOM_PBA_CONFIGURATION,
|
||||
post_breach_actions=PBA_CONFIGURATION,
|
||||
credential_collectors=CREDENTIAL_COLLECTOR_CONFIGURATION,
|
||||
payloads=PAYLOAD_CONFIGURATION,
|
||||
propagation=PROPAGATION_CONFIGURATION,
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class OperatingSystems(Enum):
|
||||
"""
|
||||
An Enum representing all supported operating systems
|
||||
|
||||
This Enum represents all operating systems that Infection Monkey supports. The value of each
|
||||
member is the member's name in all lower-case characters.
|
||||
"""
|
||||
|
||||
LINUX = "linux"
|
||||
WINDOWS = "windows"
|
|
@ -38,5 +38,7 @@ class DomainControllerNameFetchError(FailedExploitationError):
|
|||
"""Raise on failed attempt to extract domain controller's name"""
|
||||
|
||||
|
||||
# TODO: This has been replaced by common.configuration.InvalidConfigurationError. Use that error
|
||||
# instead and remove this one.
|
||||
class InvalidConfigurationError(Exception):
|
||||
"""Raise when configuration is invalid"""
|
||||
|
|
|
@ -24,6 +24,8 @@ pywin32-ctypes = {version = "*", sys_platform = "== 'win32'"} # Pyinstaller requ
|
|||
pywin32 = {version = "*", sys_platform = "== 'win32'"} # Lock file is not created with sys_platform win32 requirement if not explicitly specified
|
||||
pefile = {version = "*", sys_platform = "== 'win32'"} # Pyinstaller requirement on windows
|
||||
paramiko = {editable = true, ref = "2.10.3.dev1", git = "https://github.com/VakarisZ/paramiko.git"}
|
||||
marshmallow = "*"
|
||||
marshmallow-enum = "*"
|
||||
|
||||
[dev-packages]
|
||||
ldap3 = "*"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "c1c28510b728242624129b39bd9ace9ba2629061bf64226cb43f1f857fc87212"
|
||||
"sha256": "202bc4667be3a990bdf39be19ce2ddbacc0b3189810d89f75464ad361c9142b2"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -71,26 +71,28 @@
|
|||
},
|
||||
"bcrypt": {
|
||||
"hashes": [
|
||||
"sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d",
|
||||
"sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29",
|
||||
"sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7",
|
||||
"sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34",
|
||||
"sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7",
|
||||
"sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55",
|
||||
"sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd",
|
||||
"sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6",
|
||||
"sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1",
|
||||
"sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"
|
||||
"sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521",
|
||||
"sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb",
|
||||
"sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e",
|
||||
"sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26",
|
||||
"sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a",
|
||||
"sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e",
|
||||
"sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa",
|
||||
"sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129",
|
||||
"sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb",
|
||||
"sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40",
|
||||
"sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.2.0"
|
||||
"version": "==3.2.2"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
|
||||
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
|
||||
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
|
||||
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
|
||||
],
|
||||
"version": "==2021.10.8"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2022.6.15"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
|
@ -160,24 +162,24 @@
|
|||
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
|
||||
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"markers": "python_full_version >= '3.5.0'",
|
||||
"version": "==2.0.12"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e",
|
||||
"sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"
|
||||
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
|
||||
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==8.1.2"
|
||||
"version": "==8.1.3"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
"sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da",
|
||||
"sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.4.4"
|
||||
"version": "==0.4.5"
|
||||
},
|
||||
"constantly": {
|
||||
"hashes": [
|
||||
|
@ -188,29 +190,31 @@
|
|||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b",
|
||||
"sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51",
|
||||
"sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7",
|
||||
"sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d",
|
||||
"sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6",
|
||||
"sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29",
|
||||
"sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9",
|
||||
"sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf",
|
||||
"sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815",
|
||||
"sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf",
|
||||
"sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85",
|
||||
"sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77",
|
||||
"sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86",
|
||||
"sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb",
|
||||
"sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e",
|
||||
"sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0",
|
||||
"sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3",
|
||||
"sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84",
|
||||
"sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2",
|
||||
"sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"
|
||||
"sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804",
|
||||
"sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178",
|
||||
"sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717",
|
||||
"sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982",
|
||||
"sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004",
|
||||
"sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe",
|
||||
"sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452",
|
||||
"sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336",
|
||||
"sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4",
|
||||
"sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15",
|
||||
"sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d",
|
||||
"sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c",
|
||||
"sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0",
|
||||
"sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06",
|
||||
"sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9",
|
||||
"sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1",
|
||||
"sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023",
|
||||
"sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de",
|
||||
"sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f",
|
||||
"sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181",
|
||||
"sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e",
|
||||
"sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==36.0.2"
|
||||
"version": "==37.0.2"
|
||||
},
|
||||
"dnspython": {
|
||||
"hashes": [
|
||||
|
@ -222,11 +226,11 @@
|
|||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264",
|
||||
"sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"
|
||||
"sha256:315ded2ddf8a6281567edb27393010fe3406188bafbfe65a3339d5787d89e477",
|
||||
"sha256:fad5b446feb0d6db6aec0c3184d16a8c1f6c3e464b511649c8918a9be100b4fe"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.1.1"
|
||||
"version": "==2.1.2"
|
||||
},
|
||||
"future": {
|
||||
"hashes": [
|
||||
|
@ -247,23 +251,23 @@
|
|||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"markers": "python_full_version >= '3.5.0'",
|
||||
"version": "==3.3"
|
||||
},
|
||||
"impacket": {
|
||||
"hashes": [
|
||||
"sha256:18d557d387f4914fafa739813b9172bc3f8bd9c036e93bf589a8e0ebb7304bba"
|
||||
"sha256:b8eb020a2cbb47146669cfe31c64bb2e7d6499d049c493d6418b9716f5c74583"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.9.24"
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6",
|
||||
"sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"
|
||||
"sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700",
|
||||
"sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==4.11.3"
|
||||
"version": "==4.11.4"
|
||||
},
|
||||
"incremental": {
|
||||
"hashes": [
|
||||
|
@ -290,11 +294,11 @@
|
|||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119",
|
||||
"sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"
|
||||
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
|
||||
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.1.1"
|
||||
"version": "==3.1.2"
|
||||
},
|
||||
"ldap3": {
|
||||
"hashes": [
|
||||
|
@ -368,6 +372,22 @@
|
|||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"marshmallow": {
|
||||
"hashes": [
|
||||
"sha256:53a1e0ee69f79e1f3e80d17393b25cfc917eda52f859e8183b4af72c3390c1f1",
|
||||
"sha256:a762c1d8b2bcb0e5c8e964850d03f9f3bffd6a12b626f3c14b9d6b1841999af5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.16.0"
|
||||
},
|
||||
"marshmallow-enum": {
|
||||
"hashes": [
|
||||
"sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58",
|
||||
"sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.5.1"
|
||||
},
|
||||
"minidump": {
|
||||
"hashes": [
|
||||
"sha256:6a9d2152f76ae633c609e09b48b42f55bd5a6b65f920dbbec756e5d9134a6201",
|
||||
|
@ -442,6 +462,14 @@
|
|||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==21.3"
|
||||
},
|
||||
"paramiko": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/VakarisZ/paramiko.git",
|
||||
|
@ -456,10 +484,11 @@
|
|||
},
|
||||
"pefile": {
|
||||
"hashes": [
|
||||
"sha256:344a49e40a94e10849f0fe34dddc80f773a12b40675bf2f7be4b8be578bdd94a"
|
||||
"sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==2021.9.3"
|
||||
"version": "==2022.5.30"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
|
@ -471,41 +500,41 @@
|
|||
},
|
||||
"psutil": {
|
||||
"hashes": [
|
||||
"sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5",
|
||||
"sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a",
|
||||
"sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4",
|
||||
"sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841",
|
||||
"sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d",
|
||||
"sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d",
|
||||
"sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0",
|
||||
"sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845",
|
||||
"sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf",
|
||||
"sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b",
|
||||
"sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07",
|
||||
"sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618",
|
||||
"sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2",
|
||||
"sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd",
|
||||
"sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666",
|
||||
"sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce",
|
||||
"sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3",
|
||||
"sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d",
|
||||
"sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25",
|
||||
"sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492",
|
||||
"sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b",
|
||||
"sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d",
|
||||
"sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2",
|
||||
"sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203",
|
||||
"sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2",
|
||||
"sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94",
|
||||
"sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9",
|
||||
"sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64",
|
||||
"sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56",
|
||||
"sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3",
|
||||
"sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c",
|
||||
"sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"
|
||||
"sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685",
|
||||
"sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc",
|
||||
"sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36",
|
||||
"sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1",
|
||||
"sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329",
|
||||
"sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81",
|
||||
"sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de",
|
||||
"sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4",
|
||||
"sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574",
|
||||
"sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237",
|
||||
"sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22",
|
||||
"sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b",
|
||||
"sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0",
|
||||
"sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954",
|
||||
"sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021",
|
||||
"sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537",
|
||||
"sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87",
|
||||
"sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0",
|
||||
"sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc",
|
||||
"sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af",
|
||||
"sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4",
|
||||
"sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453",
|
||||
"sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689",
|
||||
"sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8",
|
||||
"sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680",
|
||||
"sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e",
|
||||
"sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9",
|
||||
"sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b",
|
||||
"sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d",
|
||||
"sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2",
|
||||
"sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5",
|
||||
"sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.9.0"
|
||||
"version": "==5.9.1"
|
||||
},
|
||||
"pyasn1": {
|
||||
"hashes": [
|
||||
|
@ -603,11 +632,11 @@
|
|||
},
|
||||
"pyinstaller-hooks-contrib": {
|
||||
"hashes": [
|
||||
"sha256:9765e68552803327d58f6c5eca970bb245b7cdf073e2f912a2a3cb50360bc2d8",
|
||||
"sha256:9fa4ca03d058cba676c3cc16005076ce6a529f144c08b87c69998625fbd84e0a"
|
||||
"sha256:5fdb97dcae177955db7ab27840cba97b89dc0c7f4fd9142bba0f9b8d8df85c48",
|
||||
"sha256:6675634279cfe9e475580fb310c3d557037baefb065e6cb5a69a124361b926fd"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2022.3"
|
||||
"version": "==2022.7"
|
||||
},
|
||||
"pymssql": {
|
||||
"hashes": [
|
||||
|
@ -670,11 +699,11 @@
|
|||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954",
|
||||
"sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"
|
||||
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
|
||||
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.8'",
|
||||
"version": "==3.0.8"
|
||||
"version": "==3.0.9"
|
||||
},
|
||||
"pypsrp": {
|
||||
"hashes": [
|
||||
|
@ -694,48 +723,66 @@
|
|||
},
|
||||
"pysmb": {
|
||||
"hashes": [
|
||||
"sha256:298605b8f467ce15b412caaf9af331c135e88fa2172333af14b1b2916361cb6b"
|
||||
"sha256:3b07db16217465039d0c25694c0705b83663ca82259e209f3566d577536a7395"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.7"
|
||||
"version": "==1.2.8"
|
||||
},
|
||||
"pyspnego": {
|
||||
"hashes": [
|
||||
"sha256:05438a4e3e1526134bc2d72213417a06a2c3010f5b7271f3122e635e523c3790",
|
||||
"sha256:12e4da1cbbbd645c0624699a1d99f734161cb9095e9f1fc1c1982ed1b7a44abe",
|
||||
"sha256:185e0c576cde30d8853d9ea1d69c32cb93e98423934263d6c067bec7adc7dc4f",
|
||||
"sha256:3361027e7e86de6b784791e09a7b2ba73d06c0be40f027a7be09e45fc92325a5",
|
||||
"sha256:4971fb166dc9821c98d31d698722d48d0066f1bc63beff8bf3d2a2e60fe507d1",
|
||||
"sha256:58d352d901baab754f63cb0da790c1f798605eb634f7f922df9bb6822d3de3c5",
|
||||
"sha256:77b7c75bed737f24989aab453b9b8cd1c1512dfc5bed7a303a1cb1156fd59959",
|
||||
"sha256:adf2f3e09bc4751c06fab1fedfe734af7f232d79927c753d8981f75a25f791ec",
|
||||
"sha256:c6993ee6bcfe0036d6246324fcb7975daed858a476bfc7bf1d9334911d3dfca2",
|
||||
"sha256:e21fc7283caa16761d46bea54e78cbfe3177c21e3b2d17d9ef213edcd86e1250",
|
||||
"sha256:f05f1a6316a9baeaef243c9420d995c3dc34cfc91841f17db0c793e3fe557728",
|
||||
"sha256:fe8b2a0d7468d904c61ae63275f8234eb055767aaaba66f6d58d86f47a25aa8e"
|
||||
"sha256:1fed228edc4b1730844da8237b90489be28c55681cf3934cd04fceb2253e55bf",
|
||||
"sha256:25fbc90fc6bd16881480316739bab820cc91364765e46340da17f861f89691f1",
|
||||
"sha256:274b3a2d37e45ad4567bc5754be04b5fefad3f7cdea7d205f739d8a26b5a9189",
|
||||
"sha256:36db7ec38023a23a545114dfd23825639571f135c72fb4b13a1ed559a0a4d93c",
|
||||
"sha256:3b1ff3c1d5588b66f8e4ebafa3079a7bf0bdcc6fb144b944c5a101e688a5a280",
|
||||
"sha256:4b9bda51bd964f40322aa1b33dcfc5d68f23b0680b4b5158175f2e9a04119aa9",
|
||||
"sha256:5d6d91e35ee63a5de30eb70716bf25274bf16c2c472b046dd21fad60fe63b0b6",
|
||||
"sha256:7562bc640bf402bb2849f325b0bb41260bd2c0cb06e38b9a8c6f7021b452c873",
|
||||
"sha256:9c5bdb9f0207e2ce9e5410ee2205bf016755712018534c711ae6c1daff7fa7db",
|
||||
"sha256:a5c135d20819db3c48f65054d648317f369a61b7b22dc17b9e5ec9c0169541a0",
|
||||
"sha256:bd95633e7dce69e267579bdbe992fc081a14310236b4e84c3d179b1cf6439ca5",
|
||||
"sha256:eb41b970dbda0dfe07b1da6fc83fe9f534a66d8dea38c06c0155377697407d9a"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.5.1"
|
||||
"version": "==0.5.2"
|
||||
},
|
||||
"pywin32": {
|
||||
"sys_platform": "== 'win32'",
|
||||
"version": "==303"
|
||||
"hashes": [
|
||||
"sha256:25746d841201fd9f96b648a248f731c1dec851c9a08b8e33da8b56148e4c65cc",
|
||||
"sha256:30c53d6ce44c12a316a06c153ea74152d3b1342610f1b99d40ba2795e5af0269",
|
||||
"sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3",
|
||||
"sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48",
|
||||
"sha256:7ffa0c0fa4ae4077e8b8aa73800540ef8c24530057768c3ac57c609f99a14fd4",
|
||||
"sha256:94037b5259701988954931333aafd39cf897e990852115656b014ce72e052e96",
|
||||
"sha256:bb2ea2aa81e96eee6a6b79d87e1d1648d3f8b87f9a64499e0b92b30d141e76df",
|
||||
"sha256:be253e7b14bc601718f014d2832e4c18a5b023cbe72db826da63df76b77507a1",
|
||||
"sha256:cbbe34dad39bdbaa2889a424d28752f1b4971939b14b1bb48cbf0182a3bcfc43",
|
||||
"sha256:d24a3382f013b21aa24a5cfbfad5a2cd9926610c0affde3e8ab5b3d7dbcf4ac9",
|
||||
"sha256:d3ee45adff48e0551d1aa60d2ec066fec006083b791f5c3527c40cd8aefac71f",
|
||||
"sha256:de9827c23321dcf43d2f288f09f3b6d772fee11e809015bdae9e69fe13213988",
|
||||
"sha256:ead865a2e179b30fb717831f73cf4373401fc62fbc3455a0889a7ddac848f83e",
|
||||
"sha256:f64c0377cf01b61bd5e76c25e1480ca8ab3b73f0c4add50538d332afdf8f69c5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==304"
|
||||
},
|
||||
"pywin32-ctypes": {
|
||||
"hashes": [
|
||||
"sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942",
|
||||
"sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
|
||||
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
||||
"sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f",
|
||||
"sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.27.1"
|
||||
"version": "==2.28.0"
|
||||
},
|
||||
"service-identity": {
|
||||
"hashes": [
|
||||
|
@ -746,11 +793,11 @@
|
|||
},
|
||||
"setuptools": {
|
||||
"hashes": [
|
||||
"sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8",
|
||||
"sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592"
|
||||
"sha256:5a844ad6e190dccc67d6d7411d119c5152ce01f7c76be4d8a1eaa314501bba77",
|
||||
"sha256:bf8a748ac98b09d32c9a64a995a6b25921c96cc5743c1efa82763ba80ff54e91"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==62.1.0"
|
||||
"version": "==62.4.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
|
@ -770,30 +817,48 @@
|
|||
},
|
||||
"twisted": {
|
||||
"extras": [
|
||||
|
||||
"tls"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:a047990f57dfae1e0bd2b7df2526d4f16dcdc843774dc108b78c52f2a5f13680",
|
||||
"sha256:f9f7a91f94932477a9fc3b169d57f54f96c6e74a23d78d9ce54039a7f48928a2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_full_version >= '3.6.7'",
|
||||
"version": "==22.4.0"
|
||||
},
|
||||
"twisted-iocpsupport": {
|
||||
"hashes": [
|
||||
"sha256:306becd6e22ab6e8e4f36b6bdafd9c92e867c98a5ce517b27fdd27760ee7ae41",
|
||||
"sha256:3c61742cb0bc6c1ac117a7e5f422c129832f0c295af49e01d8a6066df8cfc04d",
|
||||
"sha256:72068b206ee809c9c596b57b5287259ea41ddb4774d86725b19f35bf56aa32a9",
|
||||
"sha256:7d972cfa8439bdcb35a7be78b7ef86d73b34b808c74be56dfa785c8a93b851bf",
|
||||
"sha256:81b3abe3527b367da0220482820cb12a16c661672b7bcfcde328902890d63323",
|
||||
"sha256:851b3735ca7e8102e661872390e3bce88f8901bece95c25a0c8bb9ecb8a23d32",
|
||||
"sha256:985c06a33f5c0dae92c71a036d1ea63872ee86a21dd9b01e1f287486f15524b4",
|
||||
"sha256:9dbb8823b49f06d4de52721b47de4d3b3026064ef4788ce62b1a21c57c3fff6f",
|
||||
"sha256:b435857b9efcbfc12f8c326ef0383f26416272260455bbca2cd8d8eca470c546",
|
||||
"sha256:b76b4eed9b27fd63ddb0877efdd2d15835fdcb6baa745cb85b66e5d016ac2878",
|
||||
"sha256:b9fed67cf0f951573f06d560ac2f10f2a4bbdc6697770113a2fc396ea2cb2565",
|
||||
"sha256:bf4133139d77fc706d8f572e6b7d82871d82ec7ef25d685c2351bdacfb701415"
|
||||
],
|
||||
"markers": "platform_system == 'Windows'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42",
|
||||
"sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"
|
||||
"sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708",
|
||||
"sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.1.1"
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"unicrypto": {
|
||||
"hashes": [
|
||||
"sha256:69240f260493346861e639697e2fda245f14656c7df20ae6d9e6b1bb36acb29e",
|
||||
"sha256:822bbf18ca6bc17f98c3029470bd8898e51fcb6a7716f6b1c95ed0bf9e0e4da5"
|
||||
"sha256:0487f9dd9009c326ee9531a79412ae18ad673425a1c800d64947b96fdeb04cdf",
|
||||
"sha256:fab49ee41926bb31be49552aa135f7aedc04436b49c8fe326d7b6a823810575e"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.0.5"
|
||||
"version": "==0.0.8"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
|
@ -812,27 +877,27 @@
|
|||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6",
|
||||
"sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"
|
||||
"sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6",
|
||||
"sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.1.1"
|
||||
"version": "==2.1.2"
|
||||
},
|
||||
"winacl": {
|
||||
"hashes": [
|
||||
"sha256:187b4394ef247806f50e1d8320bdb9e33ad1f759d9e61e2e391b97b9adf5f58a",
|
||||
"sha256:949a66b0f46015c8cf8d9c1bfdb3a5174e70c28ae1b096eb778bc2983ea7ce50"
|
||||
"sha256:1bac567a9d21300082aa2246bb0f94a591fca8e218e163bab18df0e32eefea06",
|
||||
"sha256:a0b76a327fd337d5ee707ccff95222e6b6ecaa6d887613a1c3d3437ce0be1d4d"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.1.2"
|
||||
"version": "==0.1.3"
|
||||
},
|
||||
"winsspi": {
|
||||
"hashes": [
|
||||
"sha256:a2ad9c0f6d70f6e0e0d1f54b8582054c62d8a09f346b5ccaf55da68628ca10e1",
|
||||
"sha256:a64624a25fc2d3663a2c5376c5291f3c7531e9c8051571de9ca9db8bf25746c2"
|
||||
"sha256:2f5a8d2c4b9f459144426909e26a74e550512e23b6cf9af52c2a00003c7c3fdb",
|
||||
"sha256:59b7c7595f91414528cfd80c6cfc77ec6f5e4e28185ebd6418f8368ddc7aca82"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.0.9"
|
||||
"version": "==0.0.10"
|
||||
},
|
||||
"winsys-3.x": {
|
||||
"hashes": [
|
||||
|
@ -846,6 +911,7 @@
|
|||
"sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942",
|
||||
"sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==1.5.1"
|
||||
},
|
||||
|
|
|
@ -1,69 +1,4 @@
|
|||
import uuid
|
||||
from abc import ABCMeta
|
||||
|
||||
# TODO: Find a better place for this
|
||||
GUID = str(uuid.getnode())
|
||||
|
||||
SENSITIVE_FIELDS = [
|
||||
"exploit_password_list",
|
||||
"exploit_user_list",
|
||||
"exploit_ssh_keys",
|
||||
]
|
||||
LOCAL_CONFIG_VARS = ["name", "id", "max_depth"]
|
||||
HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
|
||||
|
||||
|
||||
class Configuration(object):
|
||||
def from_kv(self, formatted_data):
|
||||
for key, value in list(formatted_data.items()):
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
if key in LOCAL_CONFIG_VARS:
|
||||
continue
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
if not self.max_depth:
|
||||
self.max_depth = self.depth
|
||||
|
||||
@staticmethod
|
||||
def hide_sensitive_info(config_dict):
|
||||
for field in SENSITIVE_FIELDS:
|
||||
config_dict[field] = HIDDEN_FIELD_REPLACEMENT_CONTENT
|
||||
return config_dict
|
||||
|
||||
def as_dict(self):
|
||||
result = {}
|
||||
for key in dir(Configuration):
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
value = getattr(self, key)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
val_type = type(value)
|
||||
|
||||
if callable(value):
|
||||
continue
|
||||
|
||||
if val_type in (type, ABCMeta):
|
||||
value = value.__name__
|
||||
elif val_type is tuple or val_type is list:
|
||||
if len(value) != 0 and type(value[0]) in (type, ABCMeta):
|
||||
value = val_type([x.__name__ for x in value])
|
||||
|
||||
result[key] = value
|
||||
|
||||
return result
|
||||
|
||||
###########################
|
||||
# monkey config
|
||||
###########################
|
||||
|
||||
# depth of propagation
|
||||
depth = 0
|
||||
max_depth = None
|
||||
|
||||
keep_tunnel_open_time = 30
|
||||
|
||||
|
||||
WormConfiguration = Configuration()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import json
|
||||
import logging
|
||||
import platform
|
||||
from pprint import pformat
|
||||
from socket import gethostname
|
||||
from typing import Mapping, Optional
|
||||
|
||||
|
@ -10,7 +9,7 @@ from requests.exceptions import ConnectionError
|
|||
|
||||
import infection_monkey.tunnel as tunnel
|
||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
||||
from infection_monkey.config import GUID, WormConfiguration
|
||||
from infection_monkey.config import GUID
|
||||
from infection_monkey.network.info import get_host_subnets, local_ips
|
||||
from infection_monkey.transport.http import HTTPConnectProxy
|
||||
from infection_monkey.transport.tcp import TcpProxy
|
||||
|
@ -151,38 +150,7 @@ class ControlClient:
|
|||
except Exception as exc:
|
||||
logger.warning(f"Error connecting to control server {self.server_address}: {exc}")
|
||||
|
||||
def load_control_config(self):
|
||||
if not self.server_address:
|
||||
return
|
||||
try:
|
||||
reply = requests.get( # noqa: DUO123
|
||||
f"https://{self.server_address}/api/agent/",
|
||||
verify=False,
|
||||
proxies=self.proxies,
|
||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
logger.warning(f"Error connecting to control server {self.server_address}: {exc}")
|
||||
return
|
||||
|
||||
try:
|
||||
WormConfiguration.from_kv(reply.json().get("config"))
|
||||
formatted_config = pformat(
|
||||
WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())
|
||||
)
|
||||
logger.info(f"New configuration was loaded from server:\n{formatted_config}")
|
||||
except Exception as exc:
|
||||
# we don't continue with default conf here because it might be dangerous
|
||||
logger.error(
|
||||
"Error parsing JSON reply from control server %s (%s): %s",
|
||||
self.server_address,
|
||||
reply._content,
|
||||
exc,
|
||||
)
|
||||
raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc)
|
||||
|
||||
def create_control_tunnel(self):
|
||||
def create_control_tunnel(self, keep_tunnel_open_time: int):
|
||||
if not self.server_address:
|
||||
return None
|
||||
|
||||
|
@ -200,7 +168,7 @@ class ControlClient:
|
|||
|
||||
return tunnel.MonkeyTunnel(
|
||||
proxy_class,
|
||||
keep_tunnel_open_time=WormConfiguration.keep_tunnel_open_time,
|
||||
keep_tunnel_open_time=keep_tunnel_open_time,
|
||||
target_addr=target_addr,
|
||||
target_port=target_port,
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import Mapping
|
|||
|
||||
import requests
|
||||
|
||||
from common import OperatingSystems
|
||||
from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT
|
||||
|
||||
from . import IAgentRepository
|
||||
|
@ -22,18 +23,22 @@ class CachingAgentRepository(IAgentRepository):
|
|||
self._proxies = proxies
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def get_agent_binary(self, os: str, architecture: str = None) -> io.BytesIO:
|
||||
def get_agent_binary(
|
||||
self, operating_system: OperatingSystems, architecture: str = None
|
||||
) -> io.BytesIO:
|
||||
# If multiple calls to get_agent_binary() are made simultaneously before the result of
|
||||
# _download_binary_from_island() is cached, then multiple requests will be sent to the
|
||||
# island. Add a mutex in front of the call to _download_agent_binary_from_island() so
|
||||
# that only one request per OS will be sent to the island.
|
||||
with self._lock:
|
||||
return io.BytesIO(self._download_binary_from_island(os))
|
||||
return io.BytesIO(self._download_binary_from_island(operating_system))
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _download_binary_from_island(self, os: str) -> bytes:
|
||||
def _download_binary_from_island(self, operating_system: OperatingSystems) -> bytes:
|
||||
os_name = operating_system.value
|
||||
|
||||
response = requests.get( # noqa: DUO123
|
||||
f"{self._island_url}/api/agent-binaries/{os}",
|
||||
f"{self._island_url}/api/agent-binaries/{os_name}",
|
||||
verify=False,
|
||||
proxies=self._proxies,
|
||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
|
|
|
@ -105,10 +105,10 @@ class HadoopExploiter(WebRCE):
|
|||
def _build_command(self, path, http_path):
|
||||
# Build command to execute
|
||||
monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1)
|
||||
if "linux" in self.host.os["type"]:
|
||||
base_command = HADOOP_LINUX_COMMAND
|
||||
else:
|
||||
if self.host.is_windows():
|
||||
base_command = HADOOP_WINDOWS_COMMAND
|
||||
else:
|
||||
base_command = HADOOP_LINUX_COMMAND
|
||||
|
||||
return base_command % {
|
||||
"monkey_path": path,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import abc
|
||||
import io
|
||||
|
||||
from common import OperatingSystems
|
||||
|
||||
# TODO: The Island also has an IAgentRepository with a totally different interface. At the moment,
|
||||
# the Island and Agent have different needs, but at some point we should unify these.
|
||||
|
||||
|
@ -13,12 +15,13 @@ class IAgentRepository(metaclass=abc.ABCMeta):
|
|||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_agent_binary(self, os: str, architecture: str = None) -> io.BytesIO:
|
||||
def get_agent_binary(
|
||||
self, operating_system: OperatingSystems, 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
|
||||
:param operating_system: The name of the operating system on which the agent binary will run
|
||||
:param architecture: Reserved
|
||||
:return: A file-like object for the requested agent binary
|
||||
:rtype: io.BytesIO
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
|||
import time
|
||||
from pathlib import PurePath
|
||||
|
||||
from common import OperatingSystems
|
||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
||||
from common.utils import Timer
|
||||
from infection_monkey.exploit.log4shell_utils import (
|
||||
|
@ -115,10 +116,10 @@ class Log4ShellExploiter(WebRCE):
|
|||
def _build_command(self, path: PurePath, http_path) -> str:
|
||||
# Build command to execute
|
||||
monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1, location=path)
|
||||
if "linux" in self.host.os["type"]:
|
||||
base_command = LOG4SHELL_LINUX_COMMAND
|
||||
else:
|
||||
if self.host.is_windows():
|
||||
base_command = LOG4SHELL_WINDOWS_COMMAND
|
||||
else:
|
||||
base_command = LOG4SHELL_LINUX_COMMAND
|
||||
|
||||
return base_command % {
|
||||
"monkey_path": path,
|
||||
|
@ -128,7 +129,7 @@ class Log4ShellExploiter(WebRCE):
|
|||
}
|
||||
|
||||
def _build_java_class(self, exploit_command: str) -> bytes:
|
||||
if "linux" in self.host.os["type"]:
|
||||
if OperatingSystems.LINUX == self.host.os["type"]:
|
||||
return build_exploit_bytecode(exploit_command, LINUX_EXPLOIT_TEMPLATE_PATH)
|
||||
else:
|
||||
return build_exploit_bytecode(exploit_command, WINDOWS_EXPLOIT_TEMPLATE_PATH)
|
||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
|||
from pathlib import Path, PurePath
|
||||
from typing import List, Optional
|
||||
|
||||
from common import OperatingSystems
|
||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions, get_auth_options
|
||||
from infection_monkey.exploit.powershell_utils.credentials import (
|
||||
|
@ -162,7 +163,7 @@ class PowerShellExploiter(HostExploiter):
|
|||
temp_monkey_binary_filepath.unlink()
|
||||
|
||||
def _create_local_agent_file(self, binary_path):
|
||||
agent_binary_bytes = self.agent_repository.get_agent_binary("windows")
|
||||
agent_binary_bytes = self.agent_repository.get_agent_binary(OperatingSystems.WINDOWS)
|
||||
with open(binary_path, "wb") as f:
|
||||
f.write(agent_binary_bytes.getvalue())
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ AGENT_BINARY_PATH_WIN64 = PureWindowsPath(r"C:\Windows\temp\monkey64.exe")
|
|||
|
||||
|
||||
def get_agent_dst_path(host: VictimHost) -> PurePath:
|
||||
if host.os["type"] == "windows":
|
||||
if host.is_windows():
|
||||
path = PureWindowsPath(AGENT_BINARY_PATH_WIN64)
|
||||
else:
|
||||
path = PurePosixPath(AGENT_BINARY_PATH_LINUX)
|
||||
|
|
|
@ -57,6 +57,6 @@ class HTTPTools(object):
|
|||
httpd.start()
|
||||
lock.acquire()
|
||||
return (
|
||||
"http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"])),
|
||||
f"http://{local_ip}:{local_port}/{urllib.parse.quote(host.os['type'].value)}",
|
||||
httpd,
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ from abc import abstractmethod
|
|||
from posixpath import join
|
||||
from typing import List, Tuple
|
||||
|
||||
from common import OperatingSystems
|
||||
from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus
|
||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||
from infection_monkey.exploit.tools.http_tools import HTTPTools
|
||||
|
@ -162,10 +163,10 @@ class WebRCE(HostExploiter):
|
|||
|
||||
def get_command(self, path, http_path, commands):
|
||||
try:
|
||||
if "linux" in self.host.os["type"]:
|
||||
command = commands["linux"]
|
||||
else:
|
||||
if self.host.is_windows():
|
||||
command = commands["windows"]
|
||||
else:
|
||||
command = commands["linux"]
|
||||
# Format command
|
||||
command = command % {"monkey_path": path, "http_path": http_path}
|
||||
except KeyError:
|
||||
|
@ -326,7 +327,7 @@ class WebRCE(HostExploiter):
|
|||
:return: response, False if failed and True if permission change is not needed
|
||||
"""
|
||||
logger.info("Changing monkey's permissions")
|
||||
if "windows" in self.host.os["type"]:
|
||||
if self.host.is_windows():
|
||||
logger.info("Permission change not required for windows")
|
||||
return True
|
||||
if not command:
|
||||
|
@ -411,13 +412,14 @@ class WebRCE(HostExploiter):
|
|||
:return: Default monkey's destination path for corresponding host or False if failed.
|
||||
"""
|
||||
if not self.host.os.get("type") or (
|
||||
self.host.os["type"] != "linux" and self.host.os["type"] != "windows"
|
||||
self.host.os["type"] != OperatingSystems.LINUX
|
||||
and self.host.os["type"] != OperatingSystems.WINDOWS
|
||||
):
|
||||
logger.error("Target's OS was either unidentified or not supported. Aborting")
|
||||
return False
|
||||
if self.host.os["type"] == "linux":
|
||||
if self.host.os["type"] == OperatingSystems.LINUX:
|
||||
return DROPPER_TARGET_PATH_LINUX
|
||||
if self.host.os["type"] == "windows":
|
||||
if self.host.os["type"] == OperatingSystems.WINDOWS:
|
||||
return DROPPER_TARGET_PATH_WIN64
|
||||
|
||||
def get_target_url(self):
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import abc
|
||||
|
||||
from common.configuration import AgentConfiguration
|
||||
|
||||
|
||||
class IControlChannel(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
|
@ -11,10 +13,10 @@ class IControlChannel(metaclass=abc.ABCMeta):
|
|||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_config(self) -> dict:
|
||||
def get_config(self) -> AgentConfiguration:
|
||||
"""
|
||||
:return: A dictionary containing Agent Configuration
|
||||
:rtype: dict
|
||||
:return: An AgentConfiguration object
|
||||
:rtype: AgentConfiguration
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
|
@ -5,13 +5,11 @@ import os
|
|||
import sys
|
||||
import traceback
|
||||
from multiprocessing import freeze_support
|
||||
from pprint import pformat
|
||||
|
||||
# dummy import for pyinstaller
|
||||
# noinspection PyUnresolvedReferences
|
||||
import infection_monkey.post_breach # noqa: F401
|
||||
from common.version import get_version
|
||||
from infection_monkey.config import WormConfiguration
|
||||
from infection_monkey.dropper import MonkeyDrops
|
||||
from infection_monkey.model import DROPPER_ARG, MONKEY_ARG
|
||||
from infection_monkey.monkey import InfectionMonkey
|
||||
|
@ -57,9 +55,6 @@ def main():
|
|||
mode_args, mode_specific_args = arg_parser.parse_known_args()
|
||||
mode = mode_args.mode
|
||||
|
||||
formatted_config = pformat(WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()))
|
||||
print(f"Loaded Configuration:\n{formatted_config}")
|
||||
|
||||
try:
|
||||
if MONKEY_ARG == mode:
|
||||
log_path = get_agent_log_path()
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import logging
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple
|
||||
from typing import Any, Callable, Iterable, List, Optional
|
||||
|
||||
from common.configuration import CustomPBAConfiguration, PluginConfiguration
|
||||
from common.utils import Timer
|
||||
from infection_monkey.credential_store import ICredentialsStore
|
||||
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
|
||||
|
@ -13,7 +14,7 @@ from infection_monkey.network import NetworkInterface
|
|||
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
|
||||
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
||||
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
|
||||
from infection_monkey.utils.propagation import should_propagate
|
||||
from infection_monkey.utils.propagation import maximum_depth_reached
|
||||
from infection_monkey.utils.threading import create_daemon_thread, interruptible_iter
|
||||
|
||||
from . import Exploiter, IPScanner, Propagator
|
||||
|
@ -111,7 +112,7 @@ class AutomatedMaster(IMaster):
|
|||
time.sleep(CHECK_FOR_TERMINATE_INTERVAL_SEC)
|
||||
|
||||
@staticmethod
|
||||
def _try_communicate_with_island(fn: Callable[[], Any], max_tries: int):
|
||||
def _try_communicate_with_island(fn: Callable[[], Any], max_tries: int) -> Any:
|
||||
tries = 0
|
||||
while tries < max_tries:
|
||||
try:
|
||||
|
@ -141,7 +142,7 @@ class AutomatedMaster(IMaster):
|
|||
try:
|
||||
config = AutomatedMaster._try_communicate_with_island(
|
||||
self._control_channel.get_config, CHECK_FOR_CONFIG_COUNT
|
||||
)["config"]
|
||||
)
|
||||
except IslandCommunicationError as e:
|
||||
logger.error(f"An error occurred while fetching configuration: {e}")
|
||||
return
|
||||
|
@ -150,7 +151,7 @@ class AutomatedMaster(IMaster):
|
|||
target=self._run_plugins,
|
||||
name="CredentialCollectorThread",
|
||||
args=(
|
||||
config["credential_collectors"],
|
||||
config.credential_collectors,
|
||||
"credential collector",
|
||||
self._collect_credentials,
|
||||
),
|
||||
|
@ -158,7 +159,7 @@ class AutomatedMaster(IMaster):
|
|||
pba_thread = create_daemon_thread(
|
||||
target=self._run_pbas,
|
||||
name="PBAThread",
|
||||
args=(config["post_breach_actions"].items(), self._run_pba, config["custom_pbas"]),
|
||||
args=(config.post_breach_actions, self._run_pba, config.custom_pbas),
|
||||
)
|
||||
|
||||
credential_collector_thread.start()
|
||||
|
@ -173,52 +174,56 @@ class AutomatedMaster(IMaster):
|
|||
current_depth = self._current_depth if self._current_depth is not None else 0
|
||||
logger.info(f"Current depth is {current_depth}")
|
||||
|
||||
if should_propagate(self._control_channel.get_config(), self._current_depth):
|
||||
self._propagator.propagate(config["propagation"], current_depth, self._stop)
|
||||
if maximum_depth_reached(config.propagation.maximum_depth, current_depth):
|
||||
self._propagator.propagate(config.propagation, current_depth, self._stop)
|
||||
else:
|
||||
logger.info("Skipping propagation: maximum depth reached")
|
||||
|
||||
payload_thread = create_daemon_thread(
|
||||
target=self._run_plugins,
|
||||
name="PayloadThread",
|
||||
args=(config["payloads"].items(), "payload", self._run_payload),
|
||||
args=(config.payloads, "payload", self._run_payload),
|
||||
)
|
||||
payload_thread.start()
|
||||
payload_thread.join()
|
||||
|
||||
pba_thread.join()
|
||||
|
||||
def _collect_credentials(self, collector: str):
|
||||
credentials = self._puppet.run_credential_collector(collector, {})
|
||||
def _collect_credentials(self, collector: PluginConfiguration):
|
||||
credentials = self._puppet.run_credential_collector(collector.name, collector.options)
|
||||
|
||||
if credentials:
|
||||
self._telemetry_messenger.send_telemetry(CredentialsTelem(credentials))
|
||||
else:
|
||||
logger.debug(f"No credentials were collected by {collector}")
|
||||
|
||||
def _run_pba(self, pba: Tuple[str, Dict]):
|
||||
name = pba[0]
|
||||
options = pba[1]
|
||||
|
||||
for pba_data in self._puppet.run_pba(name, options):
|
||||
def _run_pba(self, pba: PluginConfiguration):
|
||||
for pba_data in self._puppet.run_pba(pba.name, pba.options):
|
||||
self._telemetry_messenger.send_telemetry(PostBreachTelem(pba_data))
|
||||
|
||||
def _run_payload(self, payload: Tuple[str, Dict]):
|
||||
name = payload[0]
|
||||
options = payload[1]
|
||||
|
||||
self._puppet.run_payload(name, options, self._stop)
|
||||
def _run_payload(self, payload: PluginConfiguration):
|
||||
self._puppet.run_payload(payload.name, payload.options, self._stop)
|
||||
|
||||
def _run_pbas(
|
||||
self, plugins: Iterable[Any], callback: Callable[[Any], None], custom_pba_options: Mapping
|
||||
self,
|
||||
plugins: Iterable[PluginConfiguration],
|
||||
callback: Callable[[Any], None],
|
||||
custom_pba_options: CustomPBAConfiguration,
|
||||
):
|
||||
self._run_plugins(plugins, "post-breach action", callback)
|
||||
|
||||
if custom_pba_is_enabled(custom_pba_options):
|
||||
self._run_plugins([("CustomPBA", custom_pba_options)], "post-breach action", callback)
|
||||
self._run_plugins(
|
||||
[PluginConfiguration(name="CustomPBA", options=custom_pba_options.__dict__)],
|
||||
"post-breach action",
|
||||
callback,
|
||||
)
|
||||
|
||||
def _run_plugins(
|
||||
self, plugins: Iterable[Any], plugin_type: str, callback: Callable[[Any], None]
|
||||
self,
|
||||
plugins: Iterable[PluginConfiguration],
|
||||
plugin_type: str,
|
||||
callback: Callable[[Any], None],
|
||||
):
|
||||
logger.info(f"Running {plugin_type}s")
|
||||
logger.debug(f"Found {len(plugins)} {plugin_type}(s) to run")
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import json
|
||||
import logging
|
||||
from pprint import pformat
|
||||
from typing import Mapping
|
||||
|
||||
import requests
|
||||
|
||||
from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
|
||||
from common.configuration import AgentConfiguration
|
||||
from infection_monkey.custom_types import PropagationCredentials
|
||||
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
|
||||
|
||||
|
@ -47,7 +49,7 @@ class ControlChannel(IControlChannel):
|
|||
) as e:
|
||||
raise IslandCommunicationError(e)
|
||||
|
||||
def get_config(self) -> dict:
|
||||
def get_config(self) -> AgentConfiguration:
|
||||
try:
|
||||
response = requests.get( # noqa: DUO123
|
||||
f"https://{self._control_channel_server}/api/agent",
|
||||
|
@ -57,7 +59,10 @@ class ControlChannel(IControlChannel):
|
|||
)
|
||||
response.raise_for_status()
|
||||
|
||||
return json.loads(response.content.decode())
|
||||
config_dict = json.loads(response.text)["config"]
|
||||
logger.debug(f"Received configuration:\n{pformat(json.loads(response.text))}")
|
||||
|
||||
return AgentConfiguration.from_mapping(config_dict)
|
||||
except (
|
||||
json.JSONDecodeError,
|
||||
requests.exceptions.ConnectionError,
|
||||
|
|
|
@ -5,8 +5,13 @@ from copy import deepcopy
|
|||
from itertools import chain
|
||||
from queue import Queue
|
||||
from threading import Event
|
||||
from typing import Callable, Dict, List, Mapping
|
||||
from typing import Callable, Dict, Sequence
|
||||
|
||||
from common import OperatingSystems
|
||||
from common.configuration.agent_sub_configurations import (
|
||||
ExploitationConfiguration,
|
||||
PluginConfiguration,
|
||||
)
|
||||
from infection_monkey.custom_types import PropagationCredentials
|
||||
from infection_monkey.i_puppet import ExploiterResultData, IPuppet
|
||||
from infection_monkey.model import VictimHost
|
||||
|
@ -20,6 +25,18 @@ ExploiterName = str
|
|||
Callback = Callable[[ExploiterName, VictimHost, ExploiterResultData], None]
|
||||
|
||||
|
||||
SUPPORTED_OS = {
|
||||
"HadoopExploiter": [OperatingSystems.LINUX, OperatingSystems.WINDOWS],
|
||||
"Log4ShellExploiter": [OperatingSystems.LINUX, OperatingSystems.WINDOWS],
|
||||
"MSSQLExploiter": [OperatingSystems.WINDOWS],
|
||||
"PowerShellExploiter": [OperatingSystems.WINDOWS],
|
||||
"SSHExploiter": [OperatingSystems.LINUX],
|
||||
"SmbExploiter": [OperatingSystems.WINDOWS],
|
||||
"WmiExploiter": [OperatingSystems.WINDOWS],
|
||||
"ZerologonExploiter": [OperatingSystems.WINDOWS],
|
||||
}
|
||||
|
||||
|
||||
class Exploiter:
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -33,7 +50,7 @@ class Exploiter:
|
|||
|
||||
def exploit_hosts(
|
||||
self,
|
||||
exploiter_config: Dict,
|
||||
exploiter_config: ExploitationConfiguration,
|
||||
hosts_to_exploit: Queue,
|
||||
current_depth: int,
|
||||
results_callback: Callback,
|
||||
|
@ -43,7 +60,7 @@ class Exploiter:
|
|||
exploiters_to_run = self._process_exploiter_config(exploiter_config)
|
||||
logger.debug(
|
||||
"Agent is configured to run the following exploiters in order: "
|
||||
f"{', '.join([e['name'] for e in exploiters_to_run])}"
|
||||
f"{', '.join([e.name for e in exploiters_to_run])}"
|
||||
)
|
||||
|
||||
exploit_args = (
|
||||
|
@ -62,24 +79,26 @@ class Exploiter:
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def _process_exploiter_config(exploiter_config: Mapping) -> List[Mapping]:
|
||||
def _process_exploiter_config(
|
||||
exploiter_config: ExploitationConfiguration,
|
||||
) -> Sequence[PluginConfiguration]:
|
||||
# Run vulnerability exploiters before brute force exploiters to minimize the effect of
|
||||
# account lockout due to invalid credentials
|
||||
ordered_exploiters = chain(
|
||||
exploiter_config["vulnerability"], exploiter_config["brute_force"]
|
||||
)
|
||||
ordered_exploiters = chain(exploiter_config.vulnerability, exploiter_config.brute_force)
|
||||
exploiters_to_run = list(deepcopy(ordered_exploiters))
|
||||
|
||||
extended_exploiters = []
|
||||
for exploiter in exploiters_to_run:
|
||||
# This order allows exploiter-specific options to
|
||||
# override general options for all exploiters.
|
||||
exploiter["options"] = {**exploiter_config["options"], **exploiter["options"]}
|
||||
options = {**exploiter_config.options.__dict__, **exploiter.options}
|
||||
extended_exploiters.append(PluginConfiguration(exploiter.name, options))
|
||||
|
||||
return exploiters_to_run
|
||||
return extended_exploiters
|
||||
|
||||
def _exploit_hosts_on_queue(
|
||||
self,
|
||||
exploiters_to_run: List[Dict],
|
||||
exploiters_to_run: Sequence[PluginConfiguration],
|
||||
hosts_to_exploit: Queue,
|
||||
current_depth: int,
|
||||
results_callback: Callback,
|
||||
|
@ -106,7 +125,7 @@ class Exploiter:
|
|||
|
||||
def _run_all_exploiters(
|
||||
self,
|
||||
exploiters_to_run: List[Dict],
|
||||
exploiters_to_run: Sequence[PluginConfiguration],
|
||||
victim_host: VictimHost,
|
||||
current_depth: int,
|
||||
results_callback: Callback,
|
||||
|
@ -114,11 +133,11 @@ class Exploiter:
|
|||
):
|
||||
|
||||
for exploiter in interruptible_iter(exploiters_to_run, stop):
|
||||
exploiter_name = exploiter["name"]
|
||||
exploiter_name = exploiter.name
|
||||
victim_os = victim_host.os.get("type")
|
||||
|
||||
# We want to try all exploiters if the victim's OS is unknown
|
||||
if victim_os is not None and victim_os not in exploiter["supported_os"]:
|
||||
if victim_os is not None and victim_os not in SUPPORTED_OS[exploiter_name]:
|
||||
logger.debug(
|
||||
f"Skipping {exploiter_name} because it does not support "
|
||||
f"the victim's OS ({victim_os})"
|
||||
|
@ -126,7 +145,7 @@ class Exploiter:
|
|||
continue
|
||||
|
||||
exploiter_results = self._run_exploiter(
|
||||
exploiter_name, exploiter["options"], victim_host, current_depth, stop
|
||||
exploiter_name, exploiter.options, victim_host, current_depth, stop
|
||||
)
|
||||
results_callback(exploiter_name, victim_host, exploiter_results)
|
||||
|
||||
|
|
|
@ -3,8 +3,13 @@ import queue
|
|||
import threading
|
||||
from queue import Queue
|
||||
from threading import Event
|
||||
from typing import Any, Callable, Dict, List
|
||||
from typing import Callable, Dict, Sequence
|
||||
|
||||
from common.configuration.agent_sub_configurations import (
|
||||
NetworkScanConfiguration,
|
||||
PluginConfiguration,
|
||||
ScanTargetConfiguration,
|
||||
)
|
||||
from infection_monkey.i_puppet import (
|
||||
FingerprintData,
|
||||
IPuppet,
|
||||
|
@ -29,8 +34,8 @@ class IPScanner:
|
|||
|
||||
def scan(
|
||||
self,
|
||||
addresses_to_scan: List[NetworkAddress],
|
||||
options: Dict,
|
||||
addresses_to_scan: Sequence[NetworkAddress],
|
||||
options: ScanTargetConfiguration,
|
||||
results_callback: Callback,
|
||||
stop: Event,
|
||||
):
|
||||
|
@ -49,12 +54,16 @@ class IPScanner:
|
|||
)
|
||||
|
||||
def _scan_addresses(
|
||||
self, addresses: Queue, options: Dict, results_callback: Callback, stop: Event
|
||||
self,
|
||||
addresses: Queue,
|
||||
options: NetworkScanConfiguration,
|
||||
results_callback: Callback,
|
||||
stop: Event,
|
||||
):
|
||||
logger.debug(f"Starting scan thread -- Thread ID: {threading.get_ident()}")
|
||||
icmp_timeout = options["icmp"]["timeout_ms"] / 1000
|
||||
tcp_timeout = options["tcp"]["timeout_ms"] / 1000
|
||||
tcp_ports = options["tcp"]["ports"]
|
||||
logger.debug(f"Starting scan .read -- Thread ID: {threading.get_ident()}")
|
||||
icmp_timeout = options.icmp.timeout
|
||||
tcp_timeout = options.tcp.timeout
|
||||
tcp_ports = options.tcp.ports
|
||||
|
||||
try:
|
||||
while not stop.is_set():
|
||||
|
@ -66,7 +75,7 @@ class IPScanner:
|
|||
|
||||
fingerprint_data = {}
|
||||
if IPScanner.port_scan_found_open_port(port_scan_data):
|
||||
fingerprinters = options["fingerprinters"]
|
||||
fingerprinters = options.fingerprinters
|
||||
fingerprint_data = self._run_fingerprinters(
|
||||
address.ip, fingerprinters, ping_scan_data, port_scan_data, stop
|
||||
)
|
||||
|
@ -90,7 +99,7 @@ class IPScanner:
|
|||
def _run_fingerprinters(
|
||||
self,
|
||||
ip: str,
|
||||
fingerprinters: List[Dict[str, Any]],
|
||||
fingerprinters: Sequence[PluginConfiguration],
|
||||
ping_scan_data: PingScanData,
|
||||
port_scan_data: Dict[int, PortScanData],
|
||||
stop: Event,
|
||||
|
@ -98,8 +107,8 @@ class IPScanner:
|
|||
fingerprint_data = {}
|
||||
|
||||
for f in interruptible_iter(fingerprinters, stop):
|
||||
fingerprint_data[f["name"]] = self._puppet.fingerprint(
|
||||
f["name"], ip, ping_scan_data, port_scan_data, f["options"]
|
||||
fingerprint_data[f.name] = self._puppet.fingerprint(
|
||||
f.name, ip, ping_scan_data, port_scan_data, f.options
|
||||
)
|
||||
|
||||
return fingerprint_data
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
from typing import Dict
|
||||
|
||||
from common.configuration import CustomPBAConfiguration
|
||||
from infection_monkey.utils.environment import is_windows_os
|
||||
|
||||
|
||||
def custom_pba_is_enabled(pba_options: Dict) -> bool:
|
||||
def custom_pba_is_enabled(pba_options: CustomPBAConfiguration) -> bool:
|
||||
if not is_windows_os():
|
||||
if pba_options["linux_command"]:
|
||||
if pba_options.linux_command:
|
||||
return True
|
||||
else:
|
||||
if pba_options["windows_command"]:
|
||||
if pba_options.windows_command:
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import logging
|
||||
from queue import Queue
|
||||
from threading import Event
|
||||
from typing import Dict, List
|
||||
from typing import List
|
||||
|
||||
from common.configuration import (
|
||||
NetworkScanConfiguration,
|
||||
PropagationConfiguration,
|
||||
ScanTargetConfiguration,
|
||||
)
|
||||
from infection_monkey.i_puppet import (
|
||||
ExploiterResultData,
|
||||
FingerprintData,
|
||||
|
@ -39,14 +44,18 @@ class Propagator:
|
|||
self._local_network_interfaces = local_network_interfaces
|
||||
self._hosts_to_exploit = None
|
||||
|
||||
def propagate(self, propagation_config: Dict, current_depth: int, stop: Event):
|
||||
def propagate(
|
||||
self, propagation_config: PropagationConfiguration, current_depth: int, stop: Event
|
||||
):
|
||||
logger.info("Attempting to propagate")
|
||||
|
||||
network_scan_completed = Event()
|
||||
self._hosts_to_exploit = Queue()
|
||||
|
||||
scan_thread = create_daemon_thread(
|
||||
target=self._scan_network, name="PropagatorScanThread", args=(propagation_config, stop)
|
||||
target=self._scan_network,
|
||||
name="PropagatorScanThread",
|
||||
args=(propagation_config.network_scan, stop),
|
||||
)
|
||||
exploit_thread = create_daemon_thread(
|
||||
target=self._exploit_hosts,
|
||||
|
@ -64,22 +73,21 @@ class Propagator:
|
|||
|
||||
logger.info("Finished attempting to propagate")
|
||||
|
||||
def _scan_network(self, propagation_config: Dict, stop: Event):
|
||||
def _scan_network(self, scan_config: NetworkScanConfiguration, stop: Event):
|
||||
logger.info("Starting network scan")
|
||||
|
||||
target_config = propagation_config["targets"]
|
||||
scan_config = propagation_config["network_scan"]
|
||||
|
||||
addresses_to_scan = self._compile_scan_target_list(target_config)
|
||||
addresses_to_scan = self._compile_scan_target_list(scan_config.targets)
|
||||
self._ip_scanner.scan(addresses_to_scan, scan_config, self._process_scan_results, stop)
|
||||
|
||||
logger.info("Finished network scan")
|
||||
|
||||
def _compile_scan_target_list(self, target_config: Dict) -> List[NetworkAddress]:
|
||||
ranges_to_scan = target_config["subnet_scan_list"]
|
||||
inaccessible_subnets = target_config["inaccessible_subnets"]
|
||||
blocklisted_ips = target_config["blocked_ips"]
|
||||
enable_local_network_scan = target_config["local_network_scan"]
|
||||
def _compile_scan_target_list(
|
||||
self, target_config: ScanTargetConfiguration
|
||||
) -> List[NetworkAddress]:
|
||||
ranges_to_scan = target_config.subnets
|
||||
inaccessible_subnets = target_config.inaccessible_subnets
|
||||
blocklisted_ips = target_config.blocked_ips
|
||||
enable_local_network_scan = target_config.local_network_scan
|
||||
|
||||
return compile_scan_target_list(
|
||||
self._local_network_interfaces,
|
||||
|
@ -134,14 +142,14 @@ class Propagator:
|
|||
|
||||
def _exploit_hosts(
|
||||
self,
|
||||
propagation_config: Dict,
|
||||
propagation_config: PropagationConfiguration,
|
||||
current_depth: int,
|
||||
network_scan_completed: Event,
|
||||
stop: Event,
|
||||
):
|
||||
logger.info("Exploiting victims")
|
||||
|
||||
exploiter_config = propagation_config["exploiters"]
|
||||
exploiter_config = propagation_config.exploitation
|
||||
self._exploiter.exploit_hosts(
|
||||
exploiter_config,
|
||||
self._hosts_to_exploit,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from typing import Optional
|
||||
|
||||
from common import OperatingSystems
|
||||
|
||||
|
||||
class VictimHost(object):
|
||||
def __init__(self, ip_addr: str, domain_name: str = ""):
|
||||
|
@ -14,6 +16,9 @@ class VictimHost(object):
|
|||
def as_dict(self):
|
||||
return self.__dict__
|
||||
|
||||
def is_windows(self) -> bool:
|
||||
return OperatingSystems.WINDOWS == self.os["type"]
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.ip_addr)
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ from infection_monkey.utils.monkey_dir import (
|
|||
remove_monkey_dir,
|
||||
)
|
||||
from infection_monkey.utils.monkey_log_path import get_agent_log_path
|
||||
from infection_monkey.utils.propagation import should_propagate
|
||||
from infection_monkey.utils.propagation import maximum_depth_reached
|
||||
from infection_monkey.utils.signal_handler import register_signal_handlers, reset_signal_handlers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -152,7 +152,6 @@ class InfectionMonkey:
|
|||
raise Exception(f"Monkey couldn't find server with {self._opts.tunnel} default tunnel.")
|
||||
|
||||
self._control_client.wakeup(parent=self._opts.parent)
|
||||
self._control_client.load_control_config()
|
||||
|
||||
def _current_server_is_set(self) -> bool:
|
||||
if self._control_client.find_server(default_tunnel=self._opts.tunnel):
|
||||
|
@ -168,11 +167,17 @@ class InfectionMonkey:
|
|||
if firewall.is_enabled():
|
||||
firewall.add_firewall_rule()
|
||||
|
||||
self._monkey_inbound_tunnel = self._control_client.create_control_tunnel()
|
||||
config = ControlChannel(
|
||||
control_channel = ControlChannel(
|
||||
self._control_client.server_address, GUID, self._control_client.proxies
|
||||
).get_config()
|
||||
if self._monkey_inbound_tunnel and should_propagate(config, self._current_depth):
|
||||
)
|
||||
|
||||
config = control_channel.get_config()
|
||||
self._monkey_inbound_tunnel = self._control_client.create_control_tunnel(
|
||||
config.keep_tunnel_open_time
|
||||
)
|
||||
if self._monkey_inbound_tunnel and maximum_depth_reached(
|
||||
config.propagation.maximum_depth, self._current_depth
|
||||
):
|
||||
self._inbound_tunnel_opened = True
|
||||
self._monkey_inbound_tunnel.start()
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import re
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
from common import OperatingSystems
|
||||
from infection_monkey.i_puppet import PingScanData
|
||||
from infection_monkey.utils.environment import is_windows_os
|
||||
|
||||
|
@ -79,9 +80,9 @@ def _process_ping_command_output(ping_command_output: str) -> PingScanData:
|
|||
|
||||
operating_system = None
|
||||
if ttl <= LINUX_TTL:
|
||||
operating_system = "linux"
|
||||
operating_system = OperatingSystems.LINUX
|
||||
else: # as far we we know, could also be OSX/BSD, but lets handle that when it comes up.
|
||||
operating_system = "windows"
|
||||
operating_system = OperatingSystems.WINDOWS
|
||||
|
||||
return PingScanData(True, operating_system)
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import Dict
|
|||
|
||||
from odict import odict
|
||||
|
||||
from common import OperatingSystems
|
||||
from infection_monkey.i_puppet import (
|
||||
FingerprintData,
|
||||
IFingerprinter,
|
||||
|
@ -193,9 +194,9 @@ class SMBFingerprinter(IFingerprinter):
|
|||
logger.debug(f'os_version: "{os_version}", service_client: "{service_client}"')
|
||||
|
||||
if os_version.lower() != "unix":
|
||||
os_type = "windows"
|
||||
os_type = OperatingSystems.WINDOWS
|
||||
else:
|
||||
os_type = "linux"
|
||||
os_type = OperatingSystems.LINUX
|
||||
|
||||
smb_service["name"] = service_client
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import re
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from common import OperatingSystems
|
||||
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData
|
||||
|
||||
SSH_REGEX = r"SSH-\d\.\d-OpenSSH"
|
||||
|
@ -40,6 +41,6 @@ class SSHFingerprinter(IFingerprinter):
|
|||
for dist in LINUX_DIST_SSH:
|
||||
if banner.lower().find(dist) != -1:
|
||||
os_version = banner.split(" ").pop().strip()
|
||||
os = "linux"
|
||||
os = OperatingSystems.LINUX
|
||||
|
||||
return os, os_version
|
||||
|
|
|
@ -16,7 +16,7 @@ class ClearCommandHistory(PBA):
|
|||
super().__init__(telemetry_messenger, name=POST_BREACH_CLEAR_CMD_HISTORY)
|
||||
|
||||
def run(self, options: Dict) -> Iterable[PostBreachData]:
|
||||
results = [pba.run() for pba in self.clear_command_history_pba_list()]
|
||||
results = [pba.run(options) for pba in self.clear_command_history_pba_list()]
|
||||
if results:
|
||||
# `self.command` is empty here
|
||||
self.pba_data.append(PostBreachData(self.name, self.command, results))
|
||||
|
@ -53,7 +53,7 @@ class ClearCommandHistory(PBA):
|
|||
linux_cmd=linux_cmds,
|
||||
)
|
||||
|
||||
def run(self) -> Tuple[str, bool]:
|
||||
def run(self, options: Dict) -> Tuple[str, bool]:
|
||||
if self.command:
|
||||
try:
|
||||
output = subprocess.check_output( # noqa: DUO116
|
||||
|
|
|
@ -4,6 +4,7 @@ import logging
|
|||
|
||||
from infection_monkey.control import ControlClient
|
||||
from infection_monkey.telemetry.i_telem import ITelem
|
||||
from infection_monkey.telemetry.telem_encoder import TelemetryJSONEncoder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
LOGGED_DATA_LENGTH = 300 # How many characters of telemetry data will be logged
|
||||
|
@ -39,7 +40,7 @@ class BaseTelem(ITelem, metaclass=abc.ABCMeta):
|
|||
|
||||
@property
|
||||
def json_encoder(self):
|
||||
return json.JSONEncoder
|
||||
return TelemetryJSONEncoder
|
||||
|
||||
def _log_telem_sending(self, serialized_data: str, log_data=True):
|
||||
logger.debug(f"Sending {self.telem_category} telemetry.")
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import json
|
||||
|
||||
from common import OperatingSystems
|
||||
|
||||
|
||||
class TelemetryJSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, OperatingSystems):
|
||||
return obj.name
|
||||
return json.JSONEncoder.default(self, obj)
|
|
@ -62,7 +62,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
|||
f.close()
|
||||
|
||||
def send_head(self):
|
||||
if self.path != "/" + urllib.parse.quote(self.victim_os):
|
||||
if self.path != "/" + urllib.parse.quote(self.victim_os.value):
|
||||
self.send_error(500, "")
|
||||
return None, 0, 0
|
||||
try:
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
def should_propagate(config: dict, current_depth: int) -> bool:
|
||||
return config["config"]["depth"] > current_depth
|
||||
def maximum_depth_reached(maximum_depth: int, current_depth: int) -> bool:
|
||||
return maximum_depth > current_depth
|
||||
|
|
|
@ -28,6 +28,8 @@ cffi = "*" # Without explicit install: ModuleNotFoundError: No module named '_c
|
|||
pywin32-ctypes = {version = "*", sys_platform = "== 'win32'"} # Pyinstaller requirement on windows
|
||||
pywin32 = {version = "*", sys_platform = "== 'win32'"} # Lock file is not created with sys_platform win32 requirement if not explicitly specified
|
||||
pefile = {version = "*", sys_platform = "== 'win32'"} # Pyinstaller requirement on windows
|
||||
marshmallow = "*"
|
||||
marshmallow-enum = "*"
|
||||
|
||||
[dev-packages]
|
||||
virtualenv = ">=20.0.26"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "945e5bfe369347efb543a6c8f0ec2bb90df87284d0a65455944479ec2f78a5fa"
|
||||
"sha256": "04efa1f593acdfcdc48b7089108921a46421acbacec80d8a664ec674b221dd4b"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -72,11 +72,11 @@
|
|||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7",
|
||||
"sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"
|
||||
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
|
||||
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2022.5.18.1"
|
||||
"version": "==2022.6.15"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
|
@ -139,7 +139,7 @@
|
|||
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
|
||||
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"markers": "python_full_version >= '3.5.0'",
|
||||
"version": "==2.0.12"
|
||||
},
|
||||
"click": {
|
||||
|
@ -152,11 +152,11 @@
|
|||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
"sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da",
|
||||
"sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"
|
||||
],
|
||||
"markers": "platform_system == 'Windows'",
|
||||
"version": "==0.4.4"
|
||||
"version": "==0.4.5"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
|
@ -338,7 +338,7 @@
|
|||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"markers": "python_full_version >= '3.5.0'",
|
||||
"version": "==3.3"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
|
@ -435,6 +435,22 @@
|
|||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"marshmallow": {
|
||||
"hashes": [
|
||||
"sha256:53a1e0ee69f79e1f3e80d17393b25cfc917eda52f859e8183b4af72c3390c1f1",
|
||||
"sha256:a762c1d8b2bcb0e5c8e964850d03f9f3bffd6a12b626f3c14b9d6b1841999af5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.16.0"
|
||||
},
|
||||
"marshmallow-enum": {
|
||||
"hashes": [
|
||||
"sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58",
|
||||
"sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.5.1"
|
||||
},
|
||||
"mongoengine": {
|
||||
"hashes": [
|
||||
"sha256:6e127f45f71c2bc5e72461ec297a0c20f04c3ee0bf6dd869e336226e325db6ef",
|
||||
|
@ -479,6 +495,14 @@
|
|||
"index": "pypi",
|
||||
"version": "==0.11.0"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==21.3"
|
||||
},
|
||||
"pefile": {
|
||||
"hashes": [
|
||||
"sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b"
|
||||
|
@ -688,6 +712,14 @@
|
|||
],
|
||||
"version": "==3.12.3"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
|
||||
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.8'",
|
||||
"version": "==3.0.9"
|
||||
},
|
||||
"pyrsistent": {
|
||||
"hashes": [
|
||||
"sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c",
|
||||
|
@ -762,11 +794,11 @@
|
|||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
|
||||
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
||||
"sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f",
|
||||
"sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.27.1"
|
||||
"version": "==2.28.0"
|
||||
},
|
||||
"ring": {
|
||||
"hashes": [
|
||||
|
@ -785,11 +817,11 @@
|
|||
},
|
||||
"setuptools": {
|
||||
"hashes": [
|
||||
"sha256:68e45d17c9281ba25dc0104eadd2647172b3472d9e01f911efa57965e8d51a36",
|
||||
"sha256:a43bdedf853c670e5fed28e5623403bad2f73cf02f9a2774e91def6bda8265a7"
|
||||
"sha256:5a844ad6e190dccc67d6d7411d119c5152ce01f7c76be4d8a1eaa314501bba77",
|
||||
"sha256:bf8a748ac98b09d32c9a64a995a6b25921c96cc5743c1efa82763ba80ff54e91"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==62.3.2"
|
||||
"version": "==62.4.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
|
@ -928,11 +960,11 @@
|
|||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2",
|
||||
"sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"
|
||||
"sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51",
|
||||
"sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.10.1"
|
||||
"version": "==2.10.3"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
|
@ -965,18 +997,18 @@
|
|||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7",
|
||||
"sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"
|
||||
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
|
||||
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2022.5.18.1"
|
||||
"version": "==2022.6.15"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
|
||||
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"markers": "python_full_version >= '3.5.0'",
|
||||
"version": "==2.0.12"
|
||||
},
|
||||
"click": {
|
||||
|
@ -989,11 +1021,11 @@
|
|||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
"sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da",
|
||||
"sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"
|
||||
],
|
||||
"markers": "platform_system == 'Windows'",
|
||||
"version": "==0.4.4"
|
||||
"version": "==0.4.5"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
|
@ -1085,7 +1117,7 @@
|
|||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"markers": "python_full_version >= '3.5.0'",
|
||||
"version": "==3.3"
|
||||
},
|
||||
"imagesize": {
|
||||
|
@ -1291,11 +1323,11 @@
|
|||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
|
||||
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
||||
"sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f",
|
||||
"sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.27.1"
|
||||
"version": "==2.28.0"
|
||||
},
|
||||
"requests-mock": {
|
||||
"hashes": [
|
||||
|
@ -1313,11 +1345,11 @@
|
|||
},
|
||||
"setuptools": {
|
||||
"hashes": [
|
||||
"sha256:68e45d17c9281ba25dc0104eadd2647172b3472d9e01f911efa57965e8d51a36",
|
||||
"sha256:a43bdedf853c670e5fed28e5623403bad2f73cf02f9a2774e91def6bda8265a7"
|
||||
"sha256:5a844ad6e190dccc67d6d7411d119c5152ce01f7c76be4d8a1eaa314501bba77",
|
||||
"sha256:bf8a748ac98b09d32c9a64a995a6b25921c96cc5743c1efa82763ba80ff54e91"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==62.3.2"
|
||||
"version": "==62.4.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
|
|
|
@ -12,6 +12,7 @@ from common import DIContainer
|
|||
from monkey_island.cc.database import database, mongo
|
||||
from monkey_island.cc.resources import AgentBinaries, RemoteRun
|
||||
from monkey_island.cc.resources.AbstractResource import AbstractResource
|
||||
from monkey_island.cc.resources.agent_configuration import AgentConfiguration
|
||||
from monkey_island.cc.resources.agent_controls import StopAgentCheck, StopAllAgents
|
||||
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
||||
from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
|
||||
|
@ -155,6 +156,7 @@ def init_api_resources(api: FlaskDIWrapper):
|
|||
api.add_resource(IslandConfiguration)
|
||||
api.add_resource(ConfigurationExport)
|
||||
api.add_resource(ConfigurationImport)
|
||||
api.add_resource(AgentConfiguration)
|
||||
api.add_resource(AgentBinaries)
|
||||
api.add_resource(NetMap)
|
||||
api.add_resource(Edge)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from .file_storage import FileRetrievalError, IFileRepository, LocalStorageFileRepository
|
||||
from .i_agent_binary_repository import IAgentBinaryRepository, AgentRetrievalError
|
||||
from .errors import RemovalError, RetrievalError, StorageError
|
||||
from .file_storage import FileNotFoundError, IFileRepository, LocalStorageFileRepository
|
||||
from .i_agent_binary_repository import IAgentBinaryRepository
|
||||
from .agent_binary_repository import AgentBinaryRepository
|
||||
from .i_agent_configuration_repository import IAgentConfigurationRepository
|
||||
from .file_agent_configuration_repository import FileAgentConfigurationRepository
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from typing import BinaryIO
|
||||
|
||||
from . import AgentRetrievalError, FileRetrievalError, IAgentBinaryRepository, IFileRepository
|
||||
from . import IAgentBinaryRepository, IFileRepository, RetrievalError
|
||||
|
||||
LINUX_AGENT_FILE_NAME = "monkey-linux-64"
|
||||
WINDOWS_AGENT_FILE_NAME = "monkey-windows-64.exe"
|
||||
|
@ -20,8 +20,8 @@ class AgentBinaryRepository(IAgentBinaryRepository):
|
|||
try:
|
||||
agent_binary = self._file_repository.open_file(filename)
|
||||
return agent_binary
|
||||
except FileRetrievalError as err:
|
||||
raise AgentRetrievalError(
|
||||
except Exception as err:
|
||||
raise RetrievalError(
|
||||
f"An error occurred while retrieving the {filename}"
|
||||
f" agent binary from {self._file_repository}: {err}"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
class RemovalError(RuntimeError):
|
||||
"""
|
||||
Raised when a repository encounters an error while attempting to remove data.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RetrievalError(RuntimeError):
|
||||
"""
|
||||
Raised when a repository encounters an error while attempting to retrieve data.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StorageError(RuntimeError):
|
||||
"""
|
||||
Raised when a repository encounters an error while attempting to store data.
|
||||
"""
|
||||
|
||||
pass
|
|
@ -0,0 +1,37 @@
|
|||
import io
|
||||
|
||||
from common.configuration import AgentConfiguration
|
||||
from monkey_island.cc import repository
|
||||
from monkey_island.cc.repository import (
|
||||
IAgentConfigurationRepository,
|
||||
IFileRepository,
|
||||
RetrievalError,
|
||||
)
|
||||
|
||||
AGENT_CONFIGURATION_FILE_NAME = "agent_configuration.json"
|
||||
|
||||
|
||||
class FileAgentConfigurationRepository(IAgentConfigurationRepository):
|
||||
def __init__(
|
||||
self, default_agent_configuration: AgentConfiguration, file_repository: IFileRepository
|
||||
):
|
||||
self._default_agent_configuration = default_agent_configuration
|
||||
self._file_repository = file_repository
|
||||
|
||||
def get_configuration(self) -> AgentConfiguration:
|
||||
try:
|
||||
with self._file_repository.open_file(AGENT_CONFIGURATION_FILE_NAME) as f:
|
||||
configuration_json = f.read().decode()
|
||||
|
||||
return AgentConfiguration.from_json(configuration_json)
|
||||
except repository.FileNotFoundError:
|
||||
return self._default_agent_configuration
|
||||
except Exception as err:
|
||||
raise RetrievalError(f"Error retrieving the agent configuration: {err}")
|
||||
|
||||
def store_configuration(self, agent_configuration: AgentConfiguration):
|
||||
configuration_json = AgentConfiguration.to_json(agent_configuration)
|
||||
|
||||
self._file_repository.save_file(
|
||||
AGENT_CONFIGURATION_FILE_NAME, io.BytesIO(configuration_json.encode())
|
||||
)
|
|
@ -1,2 +1,2 @@
|
|||
from .i_file_repository import IFileRepository, FileRetrievalError
|
||||
from .i_file_repository import IFileRepository, FileNotFoundError
|
||||
from .local_storage_file_repository import LocalStorageFileRepository
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import abc
|
||||
from typing import BinaryIO
|
||||
|
||||
from monkey_island.cc.repository import RetrievalError
|
||||
|
||||
class FileRetrievalError(RuntimeError):
|
||||
|
||||
class FileNotFoundError(RetrievalError):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -18,6 +20,7 @@ class IFileRepository(metaclass=abc.ABCMeta):
|
|||
|
||||
:param unsafe_file_name: An unsanitized file name that will identify the file
|
||||
:param file_contents: The data to be stored in the file
|
||||
:raises StorageError: If an error was encountered while attempting to store the file
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -28,7 +31,8 @@ class IFileRepository(metaclass=abc.ABCMeta):
|
|||
|
||||
:param unsafe_file_name: An unsanitized file name that identifies the file to be opened
|
||||
:return: A file-like object providing access to the file's contents
|
||||
:raises FileRetrievalError: if the file cannot be opened
|
||||
:raises FileNotFoundError: if the file does not exist
|
||||
:raises RetrievalError: if the file exists but cannot be retrieved
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -41,6 +45,7 @@ class IFileRepository(metaclass=abc.ABCMeta):
|
|||
idempotent and will succeed if the file to be deleted does not exist.
|
||||
|
||||
:param unsafe_file_name: An unsanitized file name that identifies the file to be deleted
|
||||
:raises RemovalError: If an error was encountered while attempting to remove a file
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -48,5 +53,7 @@ class IFileRepository(metaclass=abc.ABCMeta):
|
|||
def delete_all_files(self):
|
||||
"""
|
||||
Delete all files that have been stored using this service.
|
||||
|
||||
:raises RemovalError: If an error was encountered while attempting to remove a file
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -4,9 +4,10 @@ from pathlib import Path
|
|||
from typing import BinaryIO
|
||||
|
||||
from common.utils.file_utils import get_all_regular_files_in_directory
|
||||
from monkey_island.cc.repository import RemovalError, RetrievalError, StorageError
|
||||
from monkey_island.cc.server_utils.file_utils import create_secure_directory
|
||||
|
||||
from . import FileRetrievalError, IFileRepository
|
||||
from . import IFileRepository, i_file_repository
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -31,34 +32,42 @@ class LocalStorageFileRepository(IFileRepository):
|
|||
self._storage_directory = storage_directory
|
||||
|
||||
def save_file(self, unsafe_file_name: str, file_contents: BinaryIO):
|
||||
safe_file_path = self._get_safe_file_path(unsafe_file_name)
|
||||
try:
|
||||
safe_file_path = self._get_safe_file_path(unsafe_file_name)
|
||||
|
||||
logger.debug(f"Saving file contents to {safe_file_path}")
|
||||
with open(safe_file_path, "wb") as dest:
|
||||
shutil.copyfileobj(file_contents, dest)
|
||||
logger.debug(f"Saving file contents to {safe_file_path}")
|
||||
with open(safe_file_path, "wb") as dest:
|
||||
shutil.copyfileobj(file_contents, dest)
|
||||
except Exception as err:
|
||||
raise StorageError(f"Error while attempting to store {unsafe_file_name}: {err}")
|
||||
|
||||
def open_file(self, unsafe_file_name: str) -> BinaryIO:
|
||||
safe_file_path = self._get_safe_file_path(unsafe_file_name)
|
||||
|
||||
try:
|
||||
safe_file_path = self._get_safe_file_path(unsafe_file_name)
|
||||
|
||||
logger.debug(f"Opening {safe_file_path}")
|
||||
return open(safe_file_path, "rb")
|
||||
except OSError as err:
|
||||
# TODO: The interface should make a destinction between file not found and an error when
|
||||
# retrieving a file that should exist. The built-in `FileNotFoundError` is not
|
||||
# sufficient because it inherits from `OSError` and the interface does not
|
||||
# guarantee that the file is stored on the local file system.
|
||||
raise FileRetrievalError(f"Failed to retrieve file {safe_file_path}: {err}") from err
|
||||
except FileNotFoundError as err:
|
||||
# Wrap Python's FileNotFound error, which is-an OSError, in repository.FileNotFoundError
|
||||
raise i_file_repository.FileNotFoundError(
|
||||
f'The requested file "{unsafe_file_name}" does not exist: {err}'
|
||||
)
|
||||
except Exception as err:
|
||||
raise RetrievalError(
|
||||
f'Error retrieving file "{unsafe_file_name}" from the repository: {err}'
|
||||
)
|
||||
|
||||
def delete_file(self, unsafe_file_name: str):
|
||||
safe_file_path = self._get_safe_file_path(unsafe_file_name)
|
||||
|
||||
try:
|
||||
safe_file_path = self._get_safe_file_path(unsafe_file_name)
|
||||
|
||||
logger.debug(f"Deleting {safe_file_path}")
|
||||
safe_file_path.unlink()
|
||||
except FileNotFoundError:
|
||||
# This method is idempotent.
|
||||
pass
|
||||
except Exception as err:
|
||||
raise RemovalError(f"Error while attempting to remove {unsafe_file_name}: {err}")
|
||||
|
||||
def _get_safe_file_path(self, unsafe_file_name: str):
|
||||
# Remove any path information from the file name.
|
||||
|
@ -67,12 +76,17 @@ class LocalStorageFileRepository(IFileRepository):
|
|||
|
||||
# This is a paranoid check to avoid directory traversal attacks.
|
||||
if self._storage_directory.resolve() not in safe_file_path.parents:
|
||||
raise ValueError(f"The file named {unsafe_file_name} can not be safely retrieved")
|
||||
raise ValueError(
|
||||
f'The file named "{unsafe_file_name}" cannot be safely retrieved or written'
|
||||
)
|
||||
|
||||
logger.debug(f"Untrusted file name {unsafe_file_name} sanitized: {safe_file_path}")
|
||||
return safe_file_path
|
||||
|
||||
def delete_all_files(self):
|
||||
for file in get_all_regular_files_in_directory(self._storage_directory):
|
||||
logger.debug(f"Deleting {file}")
|
||||
file.unlink()
|
||||
try:
|
||||
for file in get_all_regular_files_in_directory(self._storage_directory):
|
||||
logger.debug(f"Deleting {file}")
|
||||
file.unlink()
|
||||
except Exception as err:
|
||||
raise RemovalError(f"Error while attempting to clear the repository: {err}")
|
||||
|
|
|
@ -2,10 +2,6 @@ import abc
|
|||
from typing import BinaryIO
|
||||
|
||||
|
||||
class AgentRetrievalError(IOError):
|
||||
pass
|
||||
|
||||
|
||||
class IAgentBinaryRepository(metaclass=abc.ABCMeta):
|
||||
"""
|
||||
A repository that retrieves the agent binaries
|
||||
|
@ -17,6 +13,7 @@ class IAgentBinaryRepository(metaclass=abc.ABCMeta):
|
|||
Retrieve linux agent binary
|
||||
|
||||
:return: A file-like object that represents the linux agent binary
|
||||
:raises RetrievalError: If the agent binary could not be retrieved
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
|
@ -25,4 +22,5 @@ class IAgentBinaryRepository(metaclass=abc.ABCMeta):
|
|||
Retrieve windows agent binary
|
||||
|
||||
:return: A file-like object that represents the windows agent binary
|
||||
:raises RetrievalError: If the agent binary could not be retrieved
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
from common.configuration import AgentConfiguration
|
||||
|
||||
|
||||
class IAgentConfigurationRepository(ABC):
|
||||
"""
|
||||
A repository used to store and retrieve the agent configuration.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_configuration(self) -> AgentConfiguration:
|
||||
"""
|
||||
Retrieve the agent configuration from the repository
|
||||
|
||||
:return: The agent configuration as retrieved from the repository, or the default
|
||||
configuration if the repository is empty
|
||||
:raises RetrievalError: If the configuration could not be retrieved
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def store_configuration(self, agent_configuration: AgentConfiguration):
|
||||
"""
|
||||
Store the agent configuration in the repository
|
||||
|
||||
:param agent_configuration: The agent configuration to store in the repository
|
||||
:raises StorageError: If the configuration could not be stored
|
||||
"""
|
||||
pass
|
|
@ -1,30 +0,0 @@
|
|||
from abc import ABC
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
|
||||
class IConfigRepository(ABC):
|
||||
|
||||
# Config
|
||||
###############################################
|
||||
|
||||
# This returns the current config
|
||||
# TODO investigate if encryption should be here or where
|
||||
# TODO potentially should be a DTO as well, but it's structure is defined in schema already
|
||||
def get_config(self) -> Mapping:
|
||||
pass
|
||||
|
||||
def set_config(self, config: dict):
|
||||
pass
|
||||
|
||||
# Used when only a subset of config is submitted, for example only PBAFiles
|
||||
# Used by passing keys, like ['monkey', 'post_breach_actions', 'linux_filename']
|
||||
# Using a list is less ambiguous IMO, than using . notation
|
||||
def set_config_field(self, key_list: Sequence[str], value: Any):
|
||||
pass
|
||||
|
||||
# Used when only a subset of config is needed, for example only PBAFiles
|
||||
# Used by passing keys, like ['monkey', 'post_breach_actions', 'linux_filename']
|
||||
# Using a list is less ambiguous IMO, than using . notation
|
||||
# TODO Still in doubt about encryption, this should probably be determined automatically
|
||||
def get_config_field(self, key_list: Sequence[str]) -> Any:
|
||||
pass
|
|
@ -2,7 +2,7 @@ import logging
|
|||
|
||||
from flask import make_response, send_file
|
||||
|
||||
from monkey_island.cc.repository import AgentRetrievalError, IAgentBinaryRepository
|
||||
from monkey_island.cc.repository import IAgentBinaryRepository, RetrievalError
|
||||
from monkey_island.cc.resources.AbstractResource import AbstractResource
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -31,10 +31,10 @@ class AgentBinaries(AbstractResource):
|
|||
file = agent_binaries[os]()
|
||||
|
||||
return send_file(file, mimetype="application/octet-stream")
|
||||
except AgentRetrievalError as err:
|
||||
logger.error(err)
|
||||
return make_response({"error": str(err)}, 500)
|
||||
except KeyError as err:
|
||||
error_msg = f'No Agents are available for unsupported operating system "{os}": {err}'
|
||||
logger.error(error_msg)
|
||||
return make_response({"error": error_msg}, 404)
|
||||
except RetrievalError as err:
|
||||
logger.error(err)
|
||||
return make_response({"error": str(err)}, 500)
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import json
|
||||
|
||||
from flask import make_response, request
|
||||
|
||||
from common.configuration.agent_configuration import AgentConfiguration as AgentConfigurationObject
|
||||
from common.configuration.agent_configuration import InvalidConfigurationError
|
||||
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
|
||||
|
||||
|
||||
class AgentConfiguration(AbstractResource):
|
||||
urls = ["/api/agent-configuration"]
|
||||
|
||||
def __init__(self, agent_configuration_repository: IAgentConfigurationRepository):
|
||||
self._agent_configuration_repository = agent_configuration_repository
|
||||
|
||||
@jwt_required
|
||||
def get(self):
|
||||
configuration = self._agent_configuration_repository.get_configuration()
|
||||
configuration_json = AgentConfigurationObject.to_json(configuration)
|
||||
return make_response(configuration_json, 200)
|
||||
|
||||
@jwt_required
|
||||
def post(self):
|
||||
|
||||
try:
|
||||
configuration_object = AgentConfigurationObject.from_json(request.data)
|
||||
self._agent_configuration_repository.store_configuration(configuration_object)
|
||||
return make_response({}, 200)
|
||||
except (InvalidConfigurationError, json.JSONDecodeError) as err:
|
||||
return make_response(
|
||||
{"error": f"Invalid configuration supplied: {err}"},
|
||||
400,
|
||||
)
|
|
@ -2,7 +2,8 @@ import logging
|
|||
|
||||
from flask import make_response, send_file
|
||||
|
||||
from monkey_island.cc.repository import FileRetrievalError, IFileRepository
|
||||
from monkey_island.cc import repository
|
||||
from monkey_island.cc.repository import IFileRepository
|
||||
from monkey_island.cc.resources.AbstractResource import AbstractResource
|
||||
|
||||
logger = logging.getLogger(__file__)
|
||||
|
@ -24,7 +25,6 @@ class PBAFileDownload(AbstractResource):
|
|||
|
||||
# `send_file()` handles the closing of the open file.
|
||||
return send_file(file, mimetype="application/octet-stream")
|
||||
except FileRetrievalError as err:
|
||||
error_msg = f"Failed to open file {filename}: {err}"
|
||||
logger.error(error_msg)
|
||||
return make_response({"error": error_msg}, 404)
|
||||
except repository.FileNotFoundError as err:
|
||||
logger.error(str(err))
|
||||
return make_response({"error": str(err)}, 404)
|
||||
|
|
|
@ -5,7 +5,8 @@ from flask import Response, make_response, request, send_file
|
|||
from werkzeug.utils import secure_filename as sanitize_filename
|
||||
|
||||
from common.config_value_paths import PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH
|
||||
from monkey_island.cc.repository import FileRetrievalError, IFileRepository
|
||||
from monkey_island.cc import repository
|
||||
from monkey_island.cc.repository import IFileRepository
|
||||
from monkey_island.cc.resources.AbstractResource import AbstractResource
|
||||
from monkey_island.cc.resources.request_authentication import jwt_required
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
|
@ -57,10 +58,9 @@ class FileUpload(AbstractResource):
|
|||
|
||||
# `send_file()` handles the closing of the open file.
|
||||
return send_file(file, mimetype="application/octet-stream")
|
||||
except FileRetrievalError as err:
|
||||
error_msg = f"Failed to open file {filename}: {err}"
|
||||
logger.error(error_msg)
|
||||
return make_response({"error": error_msg}, 404)
|
||||
except repository.FileNotFoundError as err:
|
||||
logger.error(str(err))
|
||||
return make_response({"error": str(err)}, 404)
|
||||
|
||||
@jwt_required
|
||||
def post(self, target_os):
|
||||
|
|
|
@ -3,7 +3,6 @@ import copy
|
|||
import functools
|
||||
import logging
|
||||
import re
|
||||
from itertools import chain
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from jsonschema import Draft4Validator, validators
|
||||
|
@ -180,6 +179,7 @@ class ConfigService:
|
|||
should_encrypt=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _filter_none_values(data):
|
||||
if isinstance(data, dict):
|
||||
return {
|
||||
|
@ -357,6 +357,7 @@ class ConfigService:
|
|||
ConfigService._format_payloads_from_flat_config(config)
|
||||
ConfigService._format_pbas_from_flat_config(config)
|
||||
ConfigService._format_propagation_from_flat_config(config)
|
||||
ConfigService._format_credential_collectors(config)
|
||||
|
||||
# Ok, I'll admit this is just sort of jammed in here. But this code is going away very soon.
|
||||
del config["HTTP_PORTS"]
|
||||
|
@ -376,9 +377,18 @@ class ConfigService:
|
|||
for field in fields_to_remove:
|
||||
config.pop(field, None)
|
||||
|
||||
@staticmethod
|
||||
def _format_credential_collectors(config: Dict):
|
||||
collectors = [
|
||||
{"name": collector, "options": {}} for collector in config["credential_collectors"]
|
||||
]
|
||||
config["credential_collectors"] = collectors
|
||||
|
||||
@staticmethod
|
||||
def _format_payloads_from_flat_config(config: Dict):
|
||||
config.setdefault("payloads", {})["ransomware"] = config["ransomware"]
|
||||
config.setdefault("payloads", []).append(
|
||||
{"name": "ransomware", "options": config["ransomware"]}
|
||||
)
|
||||
config.pop("ransomware", None)
|
||||
|
||||
@staticmethod
|
||||
|
@ -388,9 +398,9 @@ class ConfigService:
|
|||
flat_windows_command_field = "custom_PBA_windows_cmd"
|
||||
flat_windows_filename_field = "PBA_windows_filename"
|
||||
|
||||
formatted_pbas_config = {}
|
||||
for pba in config.get("post_breach_actions", []):
|
||||
formatted_pbas_config[pba] = {}
|
||||
formatted_pbas_config = [
|
||||
{"name": pba, "options": {}} for pba in config.get("post_breach_actions", [])
|
||||
]
|
||||
|
||||
config["custom_pbas"] = {
|
||||
"linux_command": config.get(flat_linux_command_field, ""),
|
||||
|
@ -408,24 +418,24 @@ class ConfigService:
|
|||
|
||||
@staticmethod
|
||||
def _format_propagation_from_flat_config(config: Dict):
|
||||
formatted_propagation_config = {"network_scan": {}, "targets": {}}
|
||||
formatted_propagation_config = {"network_scan": {}, "maximum_depth": {}, "exploitation": {}}
|
||||
|
||||
formatted_propagation_config[
|
||||
"network_scan"
|
||||
] = ConfigService._format_network_scan_from_flat_config(config)
|
||||
|
||||
formatted_propagation_config["targets"] = ConfigService._format_targets_from_flat_config(
|
||||
config
|
||||
)
|
||||
formatted_propagation_config[
|
||||
"exploiters"
|
||||
"exploitation"
|
||||
] = ConfigService._format_exploiters_from_flat_config(config)
|
||||
|
||||
formatted_propagation_config["maximum_depth"] = config["depth"]
|
||||
del config["depth"]
|
||||
|
||||
config["propagation"] = formatted_propagation_config
|
||||
|
||||
@staticmethod
|
||||
def _format_network_scan_from_flat_config(config: Dict) -> Dict[str, Any]:
|
||||
formatted_network_scan_config = {"tcp": {}, "icmp": {}, "fingerprinters": []}
|
||||
formatted_network_scan_config = {"tcp": {}, "icmp": {}, "fingerprinters": [], "targets": {}}
|
||||
|
||||
formatted_network_scan_config["tcp"] = ConfigService._format_tcp_scan_from_flat_config(
|
||||
config
|
||||
|
@ -437,6 +447,10 @@ class ConfigService:
|
|||
"fingerprinters"
|
||||
] = ConfigService._format_fingerprinters_from_flat_config(config)
|
||||
|
||||
formatted_network_scan_config["targets"] = ConfigService._format_targets_from_flat_config(
|
||||
config
|
||||
)
|
||||
|
||||
return formatted_network_scan_config
|
||||
|
||||
@staticmethod
|
||||
|
@ -447,7 +461,7 @@ class ConfigService:
|
|||
|
||||
formatted_tcp_scan_config = {}
|
||||
|
||||
formatted_tcp_scan_config["timeout_ms"] = config[flat_tcp_timeout_field]
|
||||
formatted_tcp_scan_config["timeout"] = config[flat_tcp_timeout_field] / 1000
|
||||
|
||||
ports = ConfigService._union_tcp_and_http_ports(
|
||||
config[flat_tcp_ports_field], config[flat_http_ports_field]
|
||||
|
@ -471,7 +485,7 @@ class ConfigService:
|
|||
flat_ping_timeout_field = "ping_scan_timeout"
|
||||
|
||||
formatted_icmp_scan_config = {}
|
||||
formatted_icmp_scan_config["timeout_ms"] = config[flat_ping_timeout_field]
|
||||
formatted_icmp_scan_config["timeout"] = config[flat_ping_timeout_field] / 1000
|
||||
|
||||
config.pop(flat_ping_timeout_field, None)
|
||||
|
||||
|
@ -519,9 +533,7 @@ class ConfigService:
|
|||
formatted_scan_targets_config[flat_local_network_scan_field] = config[
|
||||
flat_local_network_scan_field
|
||||
]
|
||||
formatted_scan_targets_config[flat_subnet_scan_list_field] = config[
|
||||
flat_subnet_scan_list_field
|
||||
]
|
||||
formatted_scan_targets_config["subnets"] = config[flat_subnet_scan_list_field]
|
||||
|
||||
config.pop(flat_blocked_ips_field, None)
|
||||
config.pop(flat_inaccessible_subnets_field, None)
|
||||
|
@ -567,7 +579,7 @@ class ConfigService:
|
|||
formatted_exploiters_config = ConfigService._add_smb_download_timeout_to_exploiters(
|
||||
formatted_exploiters_config
|
||||
)
|
||||
return ConfigService._add_supported_os_to_exploiters(formatted_exploiters_config)
|
||||
return formatted_exploiters_config
|
||||
|
||||
@staticmethod
|
||||
def _add_smb_download_timeout_to_exploiters(
|
||||
|
@ -580,23 +592,3 @@ class ConfigService:
|
|||
exploiter["options"]["smb_download_timeout"] = SMB_DOWNLOAD_TIMEOUT
|
||||
|
||||
return new_config
|
||||
|
||||
@staticmethod
|
||||
def _add_supported_os_to_exploiters(
|
||||
formatted_config: Dict,
|
||||
) -> Dict[str, List[Dict[str, Any]]]:
|
||||
supported_os = {
|
||||
"HadoopExploiter": ["linux", "windows"],
|
||||
"Log4ShellExploiter": ["linux", "windows"],
|
||||
"MSSQLExploiter": ["windows"],
|
||||
"PowerShellExploiter": ["windows"],
|
||||
"SSHExploiter": ["linux"],
|
||||
"SmbExploiter": ["windows"],
|
||||
"WmiExploiter": ["windows"],
|
||||
"ZerologonExploiter": ["windows"],
|
||||
}
|
||||
new_config = copy.deepcopy(formatted_config)
|
||||
for exploiter in chain(new_config["brute_force"], new_config["vulnerability"]):
|
||||
exploiter["supported_os"] = supported_os.get(exploiter["name"], [])
|
||||
|
||||
return new_config
|
||||
|
|
|
@ -3,13 +3,16 @@ from pathlib import Path
|
|||
|
||||
from common import DIContainer
|
||||
from common.aws import AWSInstance
|
||||
from common.configuration import DEFAULT_AGENT_CONFIGURATION, AgentConfiguration
|
||||
from common.utils.file_utils import get_binary_io_sha256_hash
|
||||
from monkey_island.cc.repository import (
|
||||
AgentBinaryRepository,
|
||||
AgentRetrievalError,
|
||||
FileAgentConfigurationRepository,
|
||||
IAgentBinaryRepository,
|
||||
IAgentConfigurationRepository,
|
||||
IFileRepository,
|
||||
LocalStorageFileRepository,
|
||||
RetrievalError,
|
||||
)
|
||||
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
|
||||
from monkey_island.cc.services import AWSService
|
||||
|
@ -28,14 +31,20 @@ def initialize_services(data_dir: Path) -> DIContainer:
|
|||
container = DIContainer()
|
||||
|
||||
container.register_convention(Path, "data_dir", data_dir)
|
||||
container.register_convention(
|
||||
AgentConfiguration, "default_agent_configuration", DEFAULT_AGENT_CONFIGURATION
|
||||
)
|
||||
container.register_instance(AWSInstance, AWSInstance())
|
||||
|
||||
container.register_instance(
|
||||
IFileRepository, LocalStorageFileRepository(data_dir / "custom_pbas")
|
||||
IFileRepository, LocalStorageFileRepository(data_dir / "runtime_data")
|
||||
)
|
||||
container.register_instance(AWSService, container.resolve(AWSService))
|
||||
container.register_instance(IAgentBinaryRepository, _build_agent_binary_repository())
|
||||
container.register_instance(LocalMonkeyRunService, container.resolve(LocalMonkeyRunService))
|
||||
container.register_instance(
|
||||
IAgentConfigurationRepository, container.resolve(FileAgentConfigurationRepository)
|
||||
)
|
||||
|
||||
# This is temporary until we get DI all worked out.
|
||||
PostBreachFilesService.initialize(container.resolve(IFileRepository))
|
||||
|
@ -71,7 +80,7 @@ def _log_agent_binary_hashes(agent_binary_repository: IAgentBinaryRepository):
|
|||
agent_binary = get_agent_binary()
|
||||
binary_sha256_hash = get_binary_io_sha256_hash(agent_binary)
|
||||
agent_hashes[os] = binary_sha256_hash
|
||||
except AgentRetrievalError as err:
|
||||
except RetrievalError as err:
|
||||
logger.error(f"No agent available for {os}: {err}")
|
||||
|
||||
for os, binary_sha256_hash in agent_hashes.items():
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
import bson
|
||||
from bson.json_util import dumps
|
||||
|
@ -11,19 +12,27 @@ def normalize_obj(obj):
|
|||
del obj["_id"]
|
||||
|
||||
for key, value in list(obj.items()):
|
||||
if isinstance(value, bson.objectid.ObjectId):
|
||||
obj[key] = str(value)
|
||||
if isinstance(value, datetime):
|
||||
obj[key] = str(value)
|
||||
if isinstance(value, dict):
|
||||
obj[key] = normalize_obj(value)
|
||||
if isinstance(value, list):
|
||||
for i in range(0, len(value)):
|
||||
if isinstance(value[i], dict):
|
||||
value[i] = normalize_obj(value[i])
|
||||
obj[key][i] = _normalize_value(value[i])
|
||||
else:
|
||||
obj[key] = _normalize_value(value)
|
||||
return obj
|
||||
|
||||
|
||||
def _normalize_value(value):
|
||||
if type(value) == dict:
|
||||
return normalize_obj(value)
|
||||
if isinstance(value, bson.objectid.ObjectId):
|
||||
return str(value)
|
||||
if isinstance(value, datetime):
|
||||
return str(value)
|
||||
if issubclass(type(value), Enum):
|
||||
return value.name
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def output_json(obj, code, headers=None):
|
||||
obj = normalize_obj(obj)
|
||||
resp = make_response(dumps(obj), code)
|
||||
|
|
|
@ -5,7 +5,7 @@ import subprocess
|
|||
from pathlib import Path
|
||||
from shutil import copyfileobj
|
||||
|
||||
from monkey_island.cc.repository import AgentRetrievalError, IAgentBinaryRepository
|
||||
from monkey_island.cc.repository import IAgentBinaryRepository, RetrievalError
|
||||
from monkey_island.cc.server_utils.consts import ISLAND_PORT
|
||||
from monkey_island.cc.services.utils.network_utils import local_ip_addresses
|
||||
|
||||
|
@ -29,7 +29,7 @@ class LocalMonkeyRunService:
|
|||
}
|
||||
|
||||
agent_binary = agents[platform.system().lower()]()
|
||||
except AgentRetrievalError as err:
|
||||
except RetrievalError as err:
|
||||
logger.error(
|
||||
f"No Agent can be retrieved for the specified operating system"
|
||||
f'"{operating_system}"'
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
PLUGIN_NAME = "bond"
|
||||
PLUGIN_OPTIONS = {"gun": "Walther PPK", "car": "Aston Martin DB5"}
|
||||
PLUGIN_CONFIGURATION = {"name": PLUGIN_NAME, "options": PLUGIN_OPTIONS}
|
||||
|
||||
LINUX_COMMAND = "a"
|
||||
LINUX_FILENAME = "b"
|
||||
WINDOWS_COMMAND = "c"
|
||||
WINDOWS_FILENAME = "d"
|
||||
CUSTOM_PBA_CONFIGURATION = {
|
||||
"linux_command": LINUX_COMMAND,
|
||||
"linux_filename": LINUX_FILENAME,
|
||||
"windows_command": WINDOWS_COMMAND,
|
||||
"windows_filename": WINDOWS_FILENAME,
|
||||
}
|
||||
|
||||
BLOCKED_IPS = ["10.0.0.1", "192.168.1.1"]
|
||||
INACCESSIBLE_SUBNETS = ["172.0.0.0/24", "172.2.2.0/24", "192.168.56.0/24"]
|
||||
LOCAL_NETWORK_SCAN = True
|
||||
SUBNETS = ["10.0.0.2", "10.0.0.2/16"]
|
||||
SCAN_TARGET_CONFIGURATION = {
|
||||
"blocked_ips": BLOCKED_IPS,
|
||||
"inaccessible_subnets": INACCESSIBLE_SUBNETS,
|
||||
"local_network_scan": LOCAL_NETWORK_SCAN,
|
||||
"subnets": SUBNETS,
|
||||
}
|
||||
|
||||
TIMEOUT = 2.525
|
||||
ICMP_CONFIGURATION = {"timeout": TIMEOUT}
|
||||
|
||||
PORTS = [8080, 443]
|
||||
TCP_SCAN_CONFIGURATION = {"timeout": TIMEOUT, "ports": PORTS}
|
||||
|
||||
FINGERPRINTERS = [{"name": "mssql", "options": {}}]
|
||||
NETWORK_SCAN_CONFIGURATION = {
|
||||
"tcp": TCP_SCAN_CONFIGURATION,
|
||||
"icmp": ICMP_CONFIGURATION,
|
||||
"fingerprinters": FINGERPRINTERS,
|
||||
"targets": SCAN_TARGET_CONFIGURATION,
|
||||
}
|
||||
|
||||
BRUTE_FORCE = [
|
||||
{"name": "ex1", "options": {}},
|
||||
{
|
||||
"name": "ex2",
|
||||
"options": {"smb_download_timeout": 10},
|
||||
},
|
||||
]
|
||||
VULNERABILITY = [
|
||||
{
|
||||
"name": "ex3",
|
||||
"options": {"smb_download_timeout": 10},
|
||||
},
|
||||
]
|
||||
EXPLOITATION_CONFIGURATION = {
|
||||
"options": {"http_ports": PORTS},
|
||||
"brute_force": BRUTE_FORCE,
|
||||
"vulnerability": VULNERABILITY,
|
||||
}
|
||||
|
||||
PROPAGATION_CONFIGURATION = {
|
||||
"maximum_depth": 5,
|
||||
"network_scan": NETWORK_SCAN_CONFIGURATION,
|
||||
"exploitation": EXPLOITATION_CONFIGURATION,
|
||||
}
|
||||
|
||||
AGENT_CONFIGURATION = {
|
||||
"keep_tunnel_open_time": 30,
|
||||
"custom_pbas": CUSTOM_PBA_CONFIGURATION,
|
||||
"post_breach_actions": [PLUGIN_CONFIGURATION],
|
||||
"credential_collectors": [PLUGIN_CONFIGURATION],
|
||||
"payloads": [PLUGIN_CONFIGURATION],
|
||||
"propagation": PROPAGATION_CONFIGURATION,
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
{
|
||||
"config": {
|
||||
"propagation": {
|
||||
"network_scan": {
|
||||
"tcp": {
|
||||
"timeout_ms": 3000,
|
||||
"ports": [
|
||||
22,
|
||||
2222,
|
||||
445,
|
||||
135,
|
||||
3389,
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008,
|
||||
3306,
|
||||
7001,
|
||||
8088,
|
||||
9200
|
||||
]
|
||||
},
|
||||
"icmp": {
|
||||
"timeout_ms": 1000
|
||||
},
|
||||
"fingerprinters": [
|
||||
"SMBFinger",
|
||||
"SSHFinger",
|
||||
"HTTPFinger",
|
||||
"MySQLFinger",
|
||||
"MSSQLFinger",
|
||||
"ElasticFinger"
|
||||
]
|
||||
},
|
||||
"targets": {
|
||||
"blocked_ips": ["192.168.1.1", "192.168.1.100"],
|
||||
"inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"],
|
||||
"local_network_scan": true,
|
||||
"subnet_scan_list": [
|
||||
"192.168.1.50",
|
||||
"192.168.56.0/24",
|
||||
"10.0.33.0/30",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2"
|
||||
]
|
||||
},
|
||||
"exploiters": {
|
||||
"options": {},
|
||||
"brute_force": [
|
||||
{"name": "MSSQLExploiter", "supported_os": ["windows"], "options": {}},
|
||||
{"name": "PowerShellExploiter", "supported_os": ["windows"], "options": {}},
|
||||
{"name": "SmbExploiter", "supported_os": ["windows"], "options": {}},
|
||||
{"name": "SSHExploiter", "supported_os": ["linux"], "options": {}},
|
||||
{"name": "WmiExploiter", "supported_os": ["windows"], "options": {}}
|
||||
],
|
||||
"vulnerability": [
|
||||
{"name": "HadoopExploiter", "supported_os": ["linux", "windows"], "options": {}},
|
||||
{"name": "ShellShockExploiter", "supported_os": ["linux"], "options": {}},
|
||||
{"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"PBA_linux_filename": "",
|
||||
"PBA_windows_filename": "",
|
||||
"custom_pbas": {
|
||||
"linux_command": "",
|
||||
"windows_command": ""
|
||||
},
|
||||
"depth": 2,
|
||||
"exploit_lm_hash_list": ["DEADBEEF", "FACADE"],
|
||||
"exploit_ntlm_hash_list": ["BEADED", "ACCEDE", "DECADE"],
|
||||
"exploit_password_list": ["p1", "p2", "p3"],
|
||||
"exploit_ssh_keys": "hidden",
|
||||
"exploit_user_list": ["u1", "u2", "u3"],
|
||||
"exploiter_classes": [],
|
||||
"max_depth": 2,
|
||||
"post_breach_actions": {
|
||||
"CommunicateAsBackdoorUser": {},
|
||||
"ModifyShellStartupFiles": {},
|
||||
"HiddenFiles": {},
|
||||
"TrapCommand": {},
|
||||
"ChangeSetuidSetgid": {},
|
||||
"ScheduleJobs": {},
|
||||
"Timestomping": {},
|
||||
"AccountDiscovery": {},
|
||||
"Custom": {
|
||||
"linux_command": "chmod u+x my_exec && ./my_exec",
|
||||
"windows_cmd": "powershell test_driver.ps1",
|
||||
"linux_filename": "my_exec",
|
||||
"windows_filename": "test_driver.ps1"
|
||||
}
|
||||
},
|
||||
"payloads": {
|
||||
"ransomware": {
|
||||
"encryption": {
|
||||
"directories": {"linux_target_dir": "", "windows_target_dir": ""},
|
||||
"enabled": true
|
||||
},
|
||||
"other_behaviors": {"readme": true}
|
||||
}
|
||||
},
|
||||
"credential_collectors": [
|
||||
"MimikatzCollector",
|
||||
"SSHCollector"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@
|
|||
],
|
||||
"PBA_linux_filename": "test.sh",
|
||||
"PBA_windows_filename": "test.ps1",
|
||||
"alive": true,
|
||||
"blocked_ips": ["192.168.1.1", "192.168.1.100"],
|
||||
"custom_PBA_linux_cmd": "bash test.sh",
|
||||
"custom_PBA_windows_cmd": "powershell test.ps1",
|
||||
|
@ -27,6 +26,7 @@
|
|||
"private_key": "my_private_key"
|
||||
}
|
||||
],
|
||||
"credential_collectors": ["MimikatzCollector", "SSHCollector"],
|
||||
"exploit_user_list": [
|
||||
"Administrator",
|
||||
"root",
|
||||
|
@ -53,7 +53,6 @@
|
|||
"inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"],
|
||||
"keep_tunnel_open_time": 60,
|
||||
"local_network_scan": true,
|
||||
"max_depth": null,
|
||||
"ping_scan_timeout": 1000,
|
||||
"post_breach_actions": [
|
||||
"CommunicateAsBackdoorUser",
|
||||
|
@ -75,9 +74,6 @@
|
|||
}
|
||||
},
|
||||
"subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"],
|
||||
"system_info_collector_classes": [
|
||||
"MimikatzCollector"
|
||||
],
|
||||
"tcp_scan_timeout": 3000,
|
||||
"tcp_target_ports": [
|
||||
22,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
from .single_file_repository import SingleFileRepository
|
||||
from .mock_file_repository import MockFileRepository, FILE_CONTENTS, FILE_NAME
|
||||
from .open_error_file_repository import OpenErrorFileRepository
|
||||
from .in_memory_agent_configuration_repository import InMemoryAgentConfigurationRepository
|
|
@ -0,0 +1,15 @@
|
|||
from tests.common.example_agent_configuration import AGENT_CONFIGURATION
|
||||
|
||||
from common.configuration.agent_configuration import AgentConfiguration
|
||||
from monkey_island.cc.repository import IAgentConfigurationRepository
|
||||
|
||||
|
||||
class InMemoryAgentConfigurationRepository(IAgentConfigurationRepository):
|
||||
def __init__(self):
|
||||
self._configuration = AgentConfiguration.from_mapping(AGENT_CONFIGURATION)
|
||||
|
||||
def get_configuration(self):
|
||||
return self._configuration
|
||||
|
||||
def store_configuration(self, agent_configuration):
|
||||
self._configuration = agent_configuration
|
|
@ -0,0 +1,28 @@
|
|||
import io
|
||||
from typing import BinaryIO
|
||||
|
||||
from monkey_island.cc import repository
|
||||
from monkey_island.cc.repository import IFileRepository
|
||||
|
||||
FILE_NAME = "test_file"
|
||||
FILE_CONTENTS = b"HelloWorld!"
|
||||
|
||||
|
||||
class MockFileRepository(IFileRepository):
|
||||
def __init__(self):
|
||||
self._file = io.BytesIO(FILE_CONTENTS)
|
||||
|
||||
def save_file(self, unsafe_file_name: str, file_contents: BinaryIO):
|
||||
pass
|
||||
|
||||
def open_file(self, unsafe_file_name: str) -> BinaryIO:
|
||||
if unsafe_file_name != FILE_NAME:
|
||||
raise repository.FileNotFoundError()
|
||||
|
||||
return self._file
|
||||
|
||||
def delete_file(self, unsafe_file_name: str):
|
||||
pass
|
||||
|
||||
def delete_all_files(self):
|
||||
pass
|
|
@ -0,0 +1,10 @@
|
|||
from typing import BinaryIO
|
||||
|
||||
from monkey_island.cc.repository import RetrievalError
|
||||
|
||||
from . import MockFileRepository
|
||||
|
||||
|
||||
class OpenErrorFileRepository(MockFileRepository):
|
||||
def open_file(self, unsafe_file_name: str) -> BinaryIO:
|
||||
raise RetrievalError("Error retrieving file")
|
|
@ -0,0 +1,24 @@
|
|||
import io
|
||||
from typing import BinaryIO
|
||||
|
||||
from monkey_island.cc import repository
|
||||
from monkey_island.cc.repository import IFileRepository
|
||||
|
||||
|
||||
class SingleFileRepository(IFileRepository):
|
||||
def __init__(self):
|
||||
self._file = None
|
||||
|
||||
def save_file(self, unsafe_file_name: str, file_contents: BinaryIO):
|
||||
self._file = io.BytesIO(file_contents.read())
|
||||
|
||||
def open_file(self, unsafe_file_name: str) -> BinaryIO:
|
||||
if self._file is None:
|
||||
raise repository.FileNotFoundError()
|
||||
return self._file
|
||||
|
||||
def delete_file(self, unsafe_file_name: str):
|
||||
self._file = None
|
||||
|
||||
def delete_all_files(self):
|
||||
self.delete_file("")
|
|
@ -0,0 +1,219 @@
|
|||
import json
|
||||
from copy import deepcopy
|
||||
|
||||
import pytest
|
||||
from tests.common.example_agent_configuration import (
|
||||
AGENT_CONFIGURATION,
|
||||
BLOCKED_IPS,
|
||||
CUSTOM_PBA_CONFIGURATION,
|
||||
EXPLOITATION_CONFIGURATION,
|
||||
FINGERPRINTERS,
|
||||
ICMP_CONFIGURATION,
|
||||
INACCESSIBLE_SUBNETS,
|
||||
LINUX_COMMAND,
|
||||
LINUX_FILENAME,
|
||||
LOCAL_NETWORK_SCAN,
|
||||
NETWORK_SCAN_CONFIGURATION,
|
||||
PLUGIN_CONFIGURATION,
|
||||
PLUGIN_NAME,
|
||||
PLUGIN_OPTIONS,
|
||||
PORTS,
|
||||
PROPAGATION_CONFIGURATION,
|
||||
SCAN_TARGET_CONFIGURATION,
|
||||
SUBNETS,
|
||||
TCP_SCAN_CONFIGURATION,
|
||||
TIMEOUT,
|
||||
WINDOWS_COMMAND,
|
||||
WINDOWS_FILENAME,
|
||||
)
|
||||
|
||||
from common.configuration import AgentConfiguration, InvalidConfigurationError
|
||||
from common.configuration.agent_configuration import AgentConfigurationSchema
|
||||
from common.configuration.agent_sub_configuration_schemas import (
|
||||
CustomPBAConfigurationSchema,
|
||||
ExploitationConfigurationSchema,
|
||||
ExploitationOptionsConfigurationSchema,
|
||||
ICMPScanConfigurationSchema,
|
||||
NetworkScanConfigurationSchema,
|
||||
PluginConfigurationSchema,
|
||||
PropagationConfigurationSchema,
|
||||
ScanTargetConfigurationSchema,
|
||||
TCPScanConfigurationSchema,
|
||||
)
|
||||
from common.configuration.agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
ExploitationConfiguration,
|
||||
NetworkScanConfiguration,
|
||||
PluginConfiguration,
|
||||
PropagationConfiguration,
|
||||
)
|
||||
|
||||
|
||||
def test_build_plugin_configuration():
|
||||
schema = PluginConfigurationSchema()
|
||||
|
||||
config = schema.load(PLUGIN_CONFIGURATION)
|
||||
|
||||
assert config.name == PLUGIN_NAME
|
||||
assert config.options == PLUGIN_OPTIONS
|
||||
|
||||
|
||||
def test_custom_pba_configuration_schema():
|
||||
schema = CustomPBAConfigurationSchema()
|
||||
|
||||
config = schema.load(CUSTOM_PBA_CONFIGURATION)
|
||||
|
||||
assert config.linux_command == LINUX_COMMAND
|
||||
assert config.linux_filename == LINUX_FILENAME
|
||||
assert config.windows_command == WINDOWS_COMMAND
|
||||
assert config.windows_filename == WINDOWS_FILENAME
|
||||
|
||||
|
||||
def test_scan_target_configuration():
|
||||
schema = ScanTargetConfigurationSchema()
|
||||
|
||||
config = schema.load(SCAN_TARGET_CONFIGURATION)
|
||||
|
||||
assert config.blocked_ips == BLOCKED_IPS
|
||||
assert config.inaccessible_subnets == INACCESSIBLE_SUBNETS
|
||||
assert config.local_network_scan == LOCAL_NETWORK_SCAN
|
||||
assert config.subnets == SUBNETS
|
||||
|
||||
|
||||
def test_icmp_scan_configuration_schema():
|
||||
schema = ICMPScanConfigurationSchema()
|
||||
|
||||
config = schema.load(ICMP_CONFIGURATION)
|
||||
|
||||
assert config.timeout == TIMEOUT
|
||||
|
||||
|
||||
def test_tcp_scan_configuration_schema():
|
||||
schema = TCPScanConfigurationSchema()
|
||||
|
||||
config = schema.load(TCP_SCAN_CONFIGURATION)
|
||||
|
||||
assert config.timeout == TIMEOUT
|
||||
assert config.ports == PORTS
|
||||
|
||||
|
||||
def test_network_scan_configuration():
|
||||
schema = NetworkScanConfigurationSchema()
|
||||
|
||||
config = schema.load(NETWORK_SCAN_CONFIGURATION)
|
||||
|
||||
assert config.tcp.ports == TCP_SCAN_CONFIGURATION["ports"]
|
||||
assert config.tcp.timeout == TCP_SCAN_CONFIGURATION["timeout"]
|
||||
assert config.icmp.timeout == ICMP_CONFIGURATION["timeout"]
|
||||
assert config.fingerprinters[0].name == FINGERPRINTERS[0]["name"]
|
||||
assert config.fingerprinters[0].options == FINGERPRINTERS[0]["options"]
|
||||
assert config.targets.blocked_ips == BLOCKED_IPS
|
||||
assert config.targets.inaccessible_subnets == INACCESSIBLE_SUBNETS
|
||||
assert config.targets.local_network_scan == LOCAL_NETWORK_SCAN
|
||||
assert config.targets.subnets == SUBNETS
|
||||
|
||||
|
||||
def test_exploitation_options_configuration_schema():
|
||||
ports = [1, 2, 3]
|
||||
schema = ExploitationOptionsConfigurationSchema()
|
||||
|
||||
config = schema.load({"http_ports": ports})
|
||||
|
||||
assert config.http_ports == ports
|
||||
|
||||
|
||||
def test_exploiter_configuration_schema():
|
||||
name = "bond"
|
||||
options = {"gun": "Walther PPK", "car": "Aston Martin DB5"}
|
||||
schema = PluginConfigurationSchema()
|
||||
|
||||
config = schema.load({"name": name, "options": options})
|
||||
|
||||
assert config.name == name
|
||||
assert config.options == options
|
||||
|
||||
|
||||
def test_exploitation_configuration():
|
||||
schema = ExploitationConfigurationSchema()
|
||||
|
||||
config = schema.load(EXPLOITATION_CONFIGURATION)
|
||||
config_dict = schema.dump(config)
|
||||
|
||||
assert isinstance(config, ExploitationConfiguration)
|
||||
assert config_dict == EXPLOITATION_CONFIGURATION
|
||||
|
||||
|
||||
def test_propagation_configuration():
|
||||
schema = PropagationConfigurationSchema()
|
||||
|
||||
config = schema.load(PROPAGATION_CONFIGURATION)
|
||||
config_dict = schema.dump(config)
|
||||
|
||||
assert isinstance(config, PropagationConfiguration)
|
||||
assert isinstance(config.network_scan, NetworkScanConfiguration)
|
||||
assert isinstance(config.exploitation, ExploitationConfiguration)
|
||||
assert config.maximum_depth == 5
|
||||
assert config_dict == PROPAGATION_CONFIGURATION
|
||||
|
||||
|
||||
def test_agent_configuration():
|
||||
config = AgentConfiguration.from_mapping(AGENT_CONFIGURATION)
|
||||
config_json = AgentConfiguration.to_json(config)
|
||||
|
||||
assert isinstance(config, AgentConfiguration)
|
||||
assert config.keep_tunnel_open_time == 30
|
||||
assert isinstance(config.custom_pbas, CustomPBAConfiguration)
|
||||
assert isinstance(config.post_breach_actions[0], PluginConfiguration)
|
||||
assert isinstance(config.credential_collectors[0], PluginConfiguration)
|
||||
assert isinstance(config.payloads[0], PluginConfiguration)
|
||||
assert isinstance(config.propagation, PropagationConfiguration)
|
||||
assert json.loads(config_json) == AGENT_CONFIGURATION
|
||||
|
||||
|
||||
def test_incorrect_type():
|
||||
valid_config = AgentConfiguration.from_mapping(AGENT_CONFIGURATION)
|
||||
with pytest.raises(InvalidConfigurationError):
|
||||
valid_config_dict = valid_config.__dict__
|
||||
valid_config_dict["keep_tunnel_open_time"] = "not_a_float"
|
||||
AgentConfiguration(**valid_config_dict)
|
||||
|
||||
|
||||
def test_from_dict():
|
||||
schema = AgentConfigurationSchema()
|
||||
dict_ = deepcopy(AGENT_CONFIGURATION)
|
||||
|
||||
config = AgentConfiguration.from_mapping(dict_)
|
||||
|
||||
assert schema.dump(config) == dict_
|
||||
|
||||
|
||||
def test_from_dict__invalid_data():
|
||||
dict_ = deepcopy(AGENT_CONFIGURATION)
|
||||
dict_["payloads"] = "payloads"
|
||||
|
||||
with pytest.raises(InvalidConfigurationError):
|
||||
AgentConfiguration.from_mapping(dict_)
|
||||
|
||||
|
||||
def test_from_json():
|
||||
schema = AgentConfigurationSchema()
|
||||
dict_ = deepcopy(AGENT_CONFIGURATION)
|
||||
|
||||
config = AgentConfiguration.from_json(json.dumps(dict_))
|
||||
|
||||
assert isinstance(config, AgentConfiguration)
|
||||
assert schema.dump(config) == dict_
|
||||
|
||||
|
||||
def test_from_json__invalid_data():
|
||||
invalid_dict = deepcopy(AGENT_CONFIGURATION)
|
||||
invalid_dict["payloads"] = "payloads"
|
||||
|
||||
with pytest.raises(InvalidConfigurationError):
|
||||
AgentConfiguration.from_json(json.dumps(invalid_dict))
|
||||
|
||||
|
||||
def test_to_json():
|
||||
config = deepcopy(AGENT_CONFIGURATION)
|
||||
|
||||
assert json.loads(AgentConfiguration.to_json(config)) == AGENT_CONFIGURATION
|
|
@ -9,6 +9,8 @@ from _pytest.monkeypatch import MonkeyPatch
|
|||
MONKEY_BASE_PATH = str(Path(__file__).parent.parent.parent)
|
||||
sys.path.insert(0, MONKEY_BASE_PATH)
|
||||
|
||||
from common.configuration import DEFAULT_AGENT_CONFIGURATION, AgentConfiguration # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def data_for_tests_dir(pytestconfig):
|
||||
|
@ -54,3 +56,8 @@ def load_monkey_config(data_for_tests_dir) -> Callable[[str], Dict]:
|
|||
return json.loads(open(config_path, "r").read())
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def default_agent_configuration() -> AgentConfiguration:
|
||||
return DEFAULT_AGENT_CONFIGURATION
|
||||
|
|
|
@ -15,8 +15,3 @@ class TelemetryMessengerSpy(ITelemetryMessenger):
|
|||
@pytest.fixture
|
||||
def telemetry_messenger_spy():
|
||||
return TelemetryMessengerSpy()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def automated_master_config(load_monkey_config):
|
||||
return load_monkey_config("automated_master_config.json")
|
||||
|
|
|
@ -2,6 +2,7 @@ from unittest.mock import Mock
|
|||
|
||||
import pytest
|
||||
|
||||
from common import OperatingSystems
|
||||
from infection_monkey.exploit.tools.helpers import (
|
||||
AGENT_BINARY_PATH_LINUX,
|
||||
AGENT_BINARY_PATH_WIN64,
|
||||
|
@ -13,22 +14,28 @@ from infection_monkey.exploit.tools.helpers import (
|
|||
def _get_host(os):
|
||||
host = Mock()
|
||||
host.os = {"type": os}
|
||||
host.is_windows = lambda: os == OperatingSystems.WINDOWS
|
||||
return host
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"os, path", [("linux", AGENT_BINARY_PATH_LINUX), ("windows", AGENT_BINARY_PATH_WIN64)]
|
||||
"os, path",
|
||||
[
|
||||
(OperatingSystems.LINUX, AGENT_BINARY_PATH_LINUX),
|
||||
(OperatingSystems.WINDOWS, AGENT_BINARY_PATH_WIN64),
|
||||
],
|
||||
)
|
||||
def test_get_agent_dst_path(os, path):
|
||||
host = _get_host(os)
|
||||
rand_path = get_agent_dst_path(host)
|
||||
print(f"{os}: {rand_path}")
|
||||
|
||||
# Assert that filename got longer by RAND_SUFFIX_LEN and one dash
|
||||
assert len(str(rand_path)) == (len(str(path)) + RAND_SUFFIX_LEN + 1)
|
||||
|
||||
|
||||
def test_get_agent_dst_path_randomness():
|
||||
host = _get_host("windows")
|
||||
host = _get_host(OperatingSystems.WINDOWS)
|
||||
|
||||
path1 = get_agent_dst_path(host)
|
||||
path2 = get_agent_dst_path(host)
|
||||
|
@ -37,7 +44,7 @@ def test_get_agent_dst_path_randomness():
|
|||
|
||||
|
||||
def test_get_agent_dst_path_str_place():
|
||||
host = _get_host("windows")
|
||||
host = _get_host(OperatingSystems.WINDOWS)
|
||||
|
||||
rand_path = get_agent_dst_path(host)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
|||
import threading
|
||||
from typing import Dict, Iterable, List, Sequence
|
||||
|
||||
from common import OperatingSystems
|
||||
from infection_monkey.credential_collectors import LMHash, Password, SSHKeypair, Username
|
||||
from infection_monkey.i_puppet import (
|
||||
Credentials,
|
||||
|
@ -182,19 +183,23 @@ class MockPuppet(IPuppet):
|
|||
"vulnerable_ports": [22],
|
||||
"executed_cmds": [],
|
||||
}
|
||||
os_windows = "windows"
|
||||
os_linux = "linux"
|
||||
|
||||
successful_exploiters = {
|
||||
DOT_1: {
|
||||
"ZerologonExploiter": ExploiterResultData(
|
||||
False, False, False, os_windows, {}, [], "Zerologon failed"
|
||||
False, False, False, OperatingSystems.WINDOWS, {}, [], "Zerologon failed"
|
||||
),
|
||||
"SSHExploiter": ExploiterResultData(
|
||||
False, False, False, os_linux, info_ssh, attempts, "Failed exploiting"
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
OperatingSystems.LINUX,
|
||||
info_ssh,
|
||||
attempts,
|
||||
"Failed exploiting",
|
||||
),
|
||||
"WmiExploiter": ExploiterResultData(
|
||||
True, True, False, os_windows, info_wmi, attempts, None
|
||||
True, True, False, OperatingSystems.WINDOWS, info_wmi, attempts, None
|
||||
),
|
||||
},
|
||||
DOT_3: {
|
||||
|
@ -202,16 +207,22 @@ class MockPuppet(IPuppet):
|
|||
False,
|
||||
False,
|
||||
False,
|
||||
os_windows,
|
||||
OperatingSystems.WINDOWS,
|
||||
info_wmi,
|
||||
attempts,
|
||||
"PowerShell Exploiter Failed",
|
||||
),
|
||||
"SSHExploiter": ExploiterResultData(
|
||||
False, False, False, os_linux, info_ssh, attempts, "Failed exploiting"
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
OperatingSystems.LINUX,
|
||||
info_ssh,
|
||||
attempts,
|
||||
"Failed exploiting",
|
||||
),
|
||||
"ZerologonExploiter": ExploiterResultData(
|
||||
True, False, False, os_windows, {}, [], None
|
||||
True, False, False, OperatingSystems.WINDOWS, {}, [], None
|
||||
),
|
||||
},
|
||||
}
|
||||
|
@ -220,7 +231,13 @@ class MockPuppet(IPuppet):
|
|||
return successful_exploiters[host.ip_addr][name]
|
||||
except KeyError:
|
||||
return ExploiterResultData(
|
||||
False, False, False, os_linux, {}, [], f"{name} failed for host {host}"
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
OperatingSystems.LINUX,
|
||||
{},
|
||||
[],
|
||||
f"{name} failed for host {host}",
|
||||
)
|
||||
|
||||
def run_payload(self, name: str, options: Dict, interrupt: threading.Event):
|
||||
|
|
|
@ -41,14 +41,14 @@ def test_stop_if_cant_get_config_from_island(monkeypatch):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def sleep_and_return_config(automated_master_config):
|
||||
def sleep_and_return_config(default_agent_configuration):
|
||||
# Ensure that should_agent_stop times out before get_config() returns to prevent the
|
||||
# Propagator's sub-threads from hanging
|
||||
get_config_sleep_time = INTERVAL * (CHECK_FOR_STOP_AGENT_COUNT + 1)
|
||||
|
||||
def _inner():
|
||||
time.sleep(get_config_sleep_time)
|
||||
return automated_master_config
|
||||
return default_agent_configuration
|
||||
|
||||
return _inner
|
||||
|
||||
|
|
|
@ -7,6 +7,11 @@ from unittest.mock import MagicMock
|
|||
import pytest
|
||||
from tests.unit_tests.infection_monkey.master.mock_puppet import MockPuppet
|
||||
|
||||
from common import OperatingSystems
|
||||
from common.configuration.agent_sub_configurations import (
|
||||
ExploitationConfiguration,
|
||||
PluginConfiguration,
|
||||
)
|
||||
from infection_monkey.master import Exploiter
|
||||
from infection_monkey.model import VictimHost
|
||||
|
||||
|
@ -34,18 +39,18 @@ def callback():
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def exploiter_config():
|
||||
return {
|
||||
"options": {"dropper_path_linux": "/tmp/monkey"},
|
||||
"brute_force": [
|
||||
{"name": "HadoopExploiter", "supported_os": ["windows"], "options": {"timeout": 10}},
|
||||
{"name": "SSHExploiter", "supported_os": ["linux"], "options": {}},
|
||||
{"name": "WmiExploiter", "supported_os": ["windows"], "options": {"timeout": 10}},
|
||||
],
|
||||
"vulnerability": [
|
||||
{"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}},
|
||||
],
|
||||
}
|
||||
def exploiter_config(default_agent_configuration):
|
||||
brute_force = [
|
||||
PluginConfiguration(name="MSSQLExploiter", options={"timeout": 10}),
|
||||
PluginConfiguration(name="SSHExploiter", options={}),
|
||||
PluginConfiguration(name="WmiExploiter", options={"timeout": 10}),
|
||||
]
|
||||
vulnerability = [PluginConfiguration(name="ZerologonExploiter", options={})]
|
||||
return ExploitationConfiguration(
|
||||
options=default_agent_configuration.propagation.exploitation.options,
|
||||
brute_force=brute_force,
|
||||
vulnerability=vulnerability,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -105,11 +110,11 @@ def test_exploiter(callback, hosts, hosts_to_exploit, run_exploiters):
|
|||
host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list)
|
||||
|
||||
assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos
|
||||
assert ("HadoopExploiter", hosts[0]) in host_exploit_combos
|
||||
assert ("MSSQLExploiter", hosts[0]) in host_exploit_combos
|
||||
assert ("SSHExploiter", hosts[0]) in host_exploit_combos
|
||||
assert ("WmiExploiter", hosts[0]) in host_exploit_combos
|
||||
assert ("ZerologonExploiter", hosts[1]) in host_exploit_combos
|
||||
assert ("HadoopExploiter", hosts[1]) in host_exploit_combos
|
||||
assert ("MSSQLExploiter", hosts[1]) in host_exploit_combos
|
||||
assert ("WmiExploiter", hosts[1]) in host_exploit_combos
|
||||
assert ("SSHExploiter", hosts[1]) in host_exploit_combos
|
||||
|
||||
|
@ -160,7 +165,7 @@ def test_exploiter_raises_exception(callback, hosts, hosts_to_exploit, run_explo
|
|||
|
||||
def test_windows_exploiters_run_on_windows_host(callback, hosts, hosts_to_exploit, run_exploiters):
|
||||
host = VictimHost("10.0.0.1")
|
||||
host.os["type"] = "windows"
|
||||
host.os["type"] = OperatingSystems.WINDOWS
|
||||
q = enqueue_hosts([host])
|
||||
run_exploiters(MockPuppet(), 1, q)
|
||||
|
||||
|
@ -172,7 +177,7 @@ def test_windows_exploiters_run_on_windows_host(callback, hosts, hosts_to_exploi
|
|||
|
||||
def test_linux_exploiters_run_on_linux_host(callback, hosts, hosts_to_exploit, run_exploiters):
|
||||
host = VictimHost("10.0.0.1")
|
||||
host.os["type"] = "linux"
|
||||
host.os["type"] = OperatingSystems.LINUX
|
||||
q = enqueue_hosts([host])
|
||||
run_exploiters(MockPuppet(), 1, q)
|
||||
|
||||
|
@ -196,6 +201,6 @@ def test_all_exploiters_run_on_unknown_host(callback, hosts, hosts_to_exploit, r
|
|||
host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list)
|
||||
|
||||
assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos
|
||||
assert ("HadoopExploiter", hosts[0]) in host_exploit_combos
|
||||
assert ("MSSQLExploiter", hosts[0]) in host_exploit_combos
|
||||
assert ("SSHExploiter", host) in host_exploit_combos
|
||||
assert ("WmiExploiter", hosts[0]) in host_exploit_combos
|
||||
|
|
|
@ -5,6 +5,12 @@ from unittest.mock import MagicMock
|
|||
import pytest
|
||||
from tests.unit_tests.infection_monkey.master.mock_puppet import MockPuppet
|
||||
|
||||
from common.configuration.agent_sub_configurations import (
|
||||
ICMPScanConfiguration,
|
||||
NetworkScanConfiguration,
|
||||
PluginConfiguration,
|
||||
TCPScanConfiguration,
|
||||
)
|
||||
from infection_monkey.i_puppet import FingerprintData, PortScanData, PortStatus
|
||||
from infection_monkey.master import IPScanner
|
||||
from infection_monkey.network import NetworkAddress
|
||||
|
@ -14,28 +20,31 @@ LINUX_OS = "linux"
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def scan_config():
|
||||
return {
|
||||
"tcp": {
|
||||
"timeout_ms": 3000,
|
||||
"ports": [
|
||||
22,
|
||||
445,
|
||||
3389,
|
||||
443,
|
||||
8008,
|
||||
3306,
|
||||
],
|
||||
},
|
||||
"icmp": {
|
||||
"timeout_ms": 1000,
|
||||
},
|
||||
"fingerprinters": [
|
||||
{"name": "HTTPFinger", "options": {}},
|
||||
{"name": "SMBFinger", "options": {}},
|
||||
{"name": "SSHFinger", "options": {}},
|
||||
def scan_config(default_agent_configuration):
|
||||
tcp_config = TCPScanConfiguration(
|
||||
timeout=3,
|
||||
ports=[
|
||||
22,
|
||||
445,
|
||||
3389,
|
||||
443,
|
||||
8008,
|
||||
3306,
|
||||
],
|
||||
}
|
||||
)
|
||||
icmp_config = ICMPScanConfiguration(timeout=1)
|
||||
fingerprinter_config = [
|
||||
PluginConfiguration(name="HTTPFinger", options={}),
|
||||
PluginConfiguration(name="SMBFinger", options={}),
|
||||
PluginConfiguration(name="SSHFinger", options={}),
|
||||
]
|
||||
scan_config = NetworkScanConfiguration(
|
||||
tcp_config,
|
||||
icmp_config,
|
||||
fingerprinter_config,
|
||||
default_agent_configuration.propagation.network_scan.targets,
|
||||
)
|
||||
return scan_config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -3,6 +3,11 @@ from unittest.mock import MagicMock
|
|||
|
||||
import pytest
|
||||
|
||||
from common.configuration.agent_sub_configurations import (
|
||||
NetworkScanConfiguration,
|
||||
PropagationConfiguration,
|
||||
ScanTargetConfiguration,
|
||||
)
|
||||
from infection_monkey.i_puppet import (
|
||||
ExploiterResultData,
|
||||
FingerprintData,
|
||||
|
@ -135,24 +140,37 @@ class StubExploiter:
|
|||
pass
|
||||
|
||||
|
||||
def test_scan_result_processing(telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory):
|
||||
def get_propagation_config(
|
||||
default_agent_configuration, scan_target_config: ScanTargetConfiguration
|
||||
):
|
||||
network_scan = NetworkScanConfiguration(
|
||||
default_agent_configuration.propagation.network_scan.tcp,
|
||||
default_agent_configuration.propagation.network_scan.icmp,
|
||||
default_agent_configuration.propagation.network_scan.fingerprinters,
|
||||
scan_target_config,
|
||||
)
|
||||
propagation_config = PropagationConfiguration(
|
||||
default_agent_configuration.propagation.maximum_depth,
|
||||
network_scan,
|
||||
default_agent_configuration.propagation.exploitation,
|
||||
)
|
||||
return propagation_config
|
||||
|
||||
|
||||
def test_scan_result_processing(
|
||||
telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_configuration
|
||||
):
|
||||
p = Propagator(
|
||||
telemetry_messenger_spy, mock_ip_scanner, StubExploiter(), mock_victim_host_factory, []
|
||||
)
|
||||
p.propagate(
|
||||
{
|
||||
"targets": {
|
||||
"subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"],
|
||||
"local_network_scan": False,
|
||||
"inaccessible_subnets": [],
|
||||
"blocked_ips": [],
|
||||
},
|
||||
"network_scan": {}, # This is empty since MockIPscanner ignores it
|
||||
"exploiters": {}, # This is empty since StubExploiter ignores it
|
||||
},
|
||||
1,
|
||||
Event(),
|
||||
targets = ScanTargetConfiguration(
|
||||
blocked_ips=[],
|
||||
inaccessible_subnets=[],
|
||||
local_network_scan=False,
|
||||
subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"],
|
||||
)
|
||||
propagation_config = get_propagation_config(default_agent_configuration, targets)
|
||||
p.propagate(propagation_config, 1, Event())
|
||||
|
||||
assert len(telemetry_messenger_spy.telemetries) == 3
|
||||
|
||||
|
@ -237,25 +255,20 @@ class MockExploiter:
|
|||
|
||||
|
||||
def test_exploiter_result_processing(
|
||||
telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory
|
||||
telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_configuration
|
||||
):
|
||||
p = Propagator(
|
||||
telemetry_messenger_spy, mock_ip_scanner, MockExploiter(), mock_victim_host_factory, []
|
||||
)
|
||||
p.propagate(
|
||||
{
|
||||
"targets": {
|
||||
"subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"],
|
||||
"local_network_scan": False,
|
||||
"inaccessible_subnets": [],
|
||||
"blocked_ips": [],
|
||||
},
|
||||
"network_scan": {}, # This is empty since MockIPscanner ignores it
|
||||
"exploiters": {}, # This is empty since MockExploiter ignores it
|
||||
},
|
||||
1,
|
||||
Event(),
|
||||
|
||||
targets = ScanTargetConfiguration(
|
||||
blocked_ips=[],
|
||||
inaccessible_subnets=[],
|
||||
local_network_scan=False,
|
||||
subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"],
|
||||
)
|
||||
propagation_config = get_propagation_config(default_agent_configuration, targets)
|
||||
p.propagate(propagation_config, 1, Event())
|
||||
|
||||
exploit_telems = [t for t in telemetry_messenger_spy.telemetries if isinstance(t, ExploitTelem)]
|
||||
assert len(exploit_telems) == 4
|
||||
|
@ -278,7 +291,9 @@ def test_exploiter_result_processing(
|
|||
assert data["propagation_result"]
|
||||
|
||||
|
||||
def test_scan_target_generation(telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory):
|
||||
def test_scan_target_generation(
|
||||
telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_configuration
|
||||
):
|
||||
local_network_interfaces = [NetworkInterface("10.0.0.9", "/29")]
|
||||
p = Propagator(
|
||||
telemetry_messenger_spy,
|
||||
|
@ -287,20 +302,15 @@ def test_scan_target_generation(telemetry_messenger_spy, mock_ip_scanner, mock_v
|
|||
mock_victim_host_factory,
|
||||
local_network_interfaces,
|
||||
)
|
||||
p.propagate(
|
||||
{
|
||||
"targets": {
|
||||
"subnet_scan_list": ["10.0.0.0/29", "172.10.20.30"],
|
||||
"local_network_scan": True,
|
||||
"blocked_ips": ["10.0.0.3"],
|
||||
"inaccessible_subnets": ["10.0.0.128/30", "10.0.0.8/29"],
|
||||
},
|
||||
"network_scan": {}, # This is empty since MockIPscanner ignores it
|
||||
"exploiters": {}, # This is empty since MockExploiter ignores it
|
||||
},
|
||||
1,
|
||||
Event(),
|
||||
targets = ScanTargetConfiguration(
|
||||
blocked_ips=["10.0.0.3"],
|
||||
inaccessible_subnets=["10.0.0.128/30", "10.0.0.8/29"],
|
||||
local_network_scan=True,
|
||||
subnets=["10.0.0.0/29", "172.10.20.30"],
|
||||
)
|
||||
propagation_config = get_propagation_config(default_agent_configuration, targets)
|
||||
p.propagate(propagation_config, 1, Event())
|
||||
|
||||
expected_ip_scan_list = [
|
||||
"10.0.0.0",
|
||||
"10.0.0.1",
|
||||
|
|
|
@ -4,6 +4,7 @@ from unittest.mock import MagicMock
|
|||
|
||||
import pytest
|
||||
|
||||
from common import OperatingSystems
|
||||
from infection_monkey.network_scanning import ping
|
||||
from infection_monkey.network_scanning.ping_scanner import EMPTY_PING_SCAN
|
||||
|
||||
|
@ -91,7 +92,7 @@ def test_linux_ping_success(patch_subprocess_running_ping_with_ping_output):
|
|||
result = ping("192.168.1.1", 1.0)
|
||||
|
||||
assert result.response_received
|
||||
assert result.os == "linux"
|
||||
assert result.os == OperatingSystems.LINUX
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("set_os_linux")
|
||||
|
@ -109,7 +110,7 @@ def test_windows_ping_success(patch_subprocess_running_ping_with_ping_output):
|
|||
result = ping("192.168.1.1", 1.0)
|
||||
|
||||
assert result.response_received
|
||||
assert result.os == "windows"
|
||||
assert result.os == OperatingSystems.WINDOWS
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("set_os_windows")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from common import OperatingSystems
|
||||
from infection_monkey.i_puppet import FingerprintData, PortScanData, PortStatus
|
||||
from infection_monkey.network_scanning.ssh_fingerprinter import SSHFingerprinter
|
||||
|
||||
|
@ -56,7 +57,7 @@ def test_ssh_os(ssh_fingerprinter):
|
|||
results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None)
|
||||
|
||||
assert results == FingerprintData(
|
||||
"linux",
|
||||
OperatingSystems.LINUX,
|
||||
"Ubuntu-4ubuntu0.2",
|
||||
{
|
||||
"tcp-22": {
|
||||
|
@ -78,7 +79,7 @@ def test_multiple_os(ssh_fingerprinter):
|
|||
results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None)
|
||||
|
||||
assert results == FingerprintData(
|
||||
"linux",
|
||||
OperatingSystems.LINUX,
|
||||
"Debian",
|
||||
{
|
||||
"tcp-22": {
|
||||
|
|
|
@ -1,32 +1,22 @@
|
|||
from infection_monkey.utils.propagation import should_propagate
|
||||
from infection_monkey.utils.propagation import maximum_depth_reached
|
||||
|
||||
|
||||
def get_config(max_depth):
|
||||
return {"config": {"depth": max_depth}}
|
||||
|
||||
|
||||
def test_should_propagate_current_less_than_max():
|
||||
max_depth = 2
|
||||
def test_maximum_depth_reached__current_less_than_max():
|
||||
maximum_depth = 2
|
||||
current_depth = 1
|
||||
|
||||
config = get_config(max_depth)
|
||||
|
||||
assert should_propagate(config, current_depth) is True
|
||||
assert maximum_depth_reached(maximum_depth, current_depth) is True
|
||||
|
||||
|
||||
def test_should_propagate_current_greater_than_max():
|
||||
max_depth = 2
|
||||
def test_maximum_depth_reached__current_greater_than_max():
|
||||
maximum_depth = 2
|
||||
current_depth = 3
|
||||
|
||||
config = get_config(max_depth)
|
||||
|
||||
assert should_propagate(config, current_depth) is False
|
||||
assert maximum_depth_reached(maximum_depth, current_depth) is False
|
||||
|
||||
|
||||
def test_should_propagate_current_equal_to_max():
|
||||
max_depth = 2
|
||||
current_depth = max_depth
|
||||
def test_maximum_depth_reached__current_equal_to_max():
|
||||
maximum_depth = 2
|
||||
current_depth = maximum_depth
|
||||
|
||||
config = get_config(max_depth)
|
||||
|
||||
assert should_propagate(config, current_depth) is False
|
||||
assert maximum_depth_reached(maximum_depth, current_depth) is False
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from monkey_island.cc.repository import AgentBinaryRepository, IFileRepository, RetrievalError
|
||||
|
||||
LINUX_AGENT_BINARY = b"linux_binary"
|
||||
WINDOWS_AGENT_BINARY = b"windows_binary"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def error_mock_file_repository():
|
||||
mock_file_repository = MagicMock(wraps=IFileRepository)
|
||||
mock_file_repository.open_file = MagicMock(side_effect=OSError)
|
||||
|
||||
return mock_file_repository
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent_binary_repository(error_mock_file_repository):
|
||||
return AgentBinaryRepository(error_mock_file_repository)
|
||||
|
||||
|
||||
def test_get_linux_binary_retrieval_error(agent_binary_repository):
|
||||
with pytest.raises(RetrievalError):
|
||||
agent_binary_repository.get_linux_binary()
|
||||
|
||||
|
||||
def test_get_windows_binary_retrieval_error(agent_binary_repository):
|
||||
with pytest.raises(RetrievalError):
|
||||
agent_binary_repository.get_windows_binary()
|
|
@ -0,0 +1,35 @@
|
|||
import pytest
|
||||
from tests.common.example_agent_configuration import AGENT_CONFIGURATION
|
||||
from tests.monkey_island import OpenErrorFileRepository, SingleFileRepository
|
||||
|
||||
from common.configuration import AgentConfiguration
|
||||
from monkey_island.cc.repository import FileAgentConfigurationRepository, RetrievalError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repository(default_agent_configuration):
|
||||
return FileAgentConfigurationRepository(default_agent_configuration, SingleFileRepository())
|
||||
|
||||
|
||||
def test_store_agent_config(repository):
|
||||
agent_configuration = AgentConfiguration.from_mapping(AGENT_CONFIGURATION)
|
||||
|
||||
repository.store_configuration(agent_configuration)
|
||||
retrieved_agent_configuration = repository.get_configuration()
|
||||
|
||||
assert retrieved_agent_configuration == agent_configuration
|
||||
|
||||
|
||||
def test_get_default_agent_config(repository, default_agent_configuration):
|
||||
retrieved_agent_configuration = repository.get_configuration()
|
||||
|
||||
assert retrieved_agent_configuration == default_agent_configuration
|
||||
|
||||
|
||||
def test_get_agent_config_retrieval_error(default_agent_configuration):
|
||||
repository = FileAgentConfigurationRepository(
|
||||
default_agent_configuration, OpenErrorFileRepository()
|
||||
)
|
||||
|
||||
with pytest.raises(RetrievalError):
|
||||
repository.get_configuration()
|
|
@ -1,10 +1,12 @@
|
|||
import io
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from tests.monkey_island.utils import assert_linux_permissions, assert_windows_permissions
|
||||
|
||||
from monkey_island.cc.repository import FileRetrievalError, LocalStorageFileRepository
|
||||
from monkey_island.cc import repository
|
||||
from monkey_island.cc.repository import LocalStorageFileRepository
|
||||
from monkey_island.cc.server_utils.file_utils import is_windows_os
|
||||
|
||||
|
||||
|
@ -131,5 +133,13 @@ def test_remove_nonexistant_file(tmp_path):
|
|||
def test_open_nonexistant_file(tmp_path):
|
||||
fss = LocalStorageFileRepository(tmp_path)
|
||||
|
||||
with pytest.raises(FileRetrievalError):
|
||||
with pytest.raises(repository.FileNotFoundError):
|
||||
fss.open_file("nonexistant_file.txt")
|
||||
|
||||
|
||||
def test_open_locked_file(tmp_path, monkeypatch):
|
||||
fss = LocalStorageFileRepository(tmp_path)
|
||||
|
||||
with patch("builtins.open", Mock(side_effect=Exception())):
|
||||
with pytest.raises(repository.RetrievalError):
|
||||
fss.open_file("locked_file.txt")
|
||||
|
|
|
@ -2,11 +2,14 @@ from unittest.mock import MagicMock
|
|||
|
||||
import flask_jwt_extended
|
||||
import pytest
|
||||
from tests.common import StubDIContainer
|
||||
from tests.monkey_island import OpenErrorFileRepository
|
||||
from tests.unit_tests.monkey_island.conftest import init_mock_app
|
||||
|
||||
import monkey_island.cc.app
|
||||
import monkey_island.cc.resources.auth.auth
|
||||
import monkey_island.cc.resources.island_mode
|
||||
from monkey_island.cc.repository import IFileRepository
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -38,3 +41,12 @@ def get_mock_app(container):
|
|||
flask_jwt_extended.JWTManager(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def open_error_flask_client(build_flask_client):
|
||||
container = StubDIContainer()
|
||||
container.register(IFileRepository, OpenErrorFileRepository)
|
||||
|
||||
with build_flask_client(container) as flask_client:
|
||||
yield flask_client
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
from tests.common import StubDIContainer
|
||||
from tests.common.example_agent_configuration import AGENT_CONFIGURATION
|
||||
from tests.monkey_island import InMemoryAgentConfigurationRepository
|
||||
from tests.unit_tests.monkey_island.conftest import get_url_for_resource
|
||||
|
||||
from monkey_island.cc.repository import IAgentConfigurationRepository
|
||||
from monkey_island.cc.resources.agent_configuration import AgentConfiguration
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def flask_client(build_flask_client):
|
||||
container = StubDIContainer()
|
||||
|
||||
container.register(IAgentConfigurationRepository, InMemoryAgentConfigurationRepository)
|
||||
|
||||
with build_flask_client(container) as flask_client:
|
||||
yield flask_client
|
||||
|
||||
|
||||
def test_agent_configuration_endpoint(flask_client):
|
||||
agent_configuration_url = get_url_for_resource(AgentConfiguration)
|
||||
|
||||
flask_client.post(
|
||||
agent_configuration_url, data=json.dumps(AGENT_CONFIGURATION), follow_redirects=True
|
||||
)
|
||||
resp = flask_client.get(agent_configuration_url)
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert json.loads(resp.data) == AGENT_CONFIGURATION
|
||||
|
||||
|
||||
def test_agent_configuration_invalid_config(flask_client):
|
||||
agent_configuration_url = get_url_for_resource(AgentConfiguration)
|
||||
|
||||
resp = flask_client.post(
|
||||
agent_configuration_url, data=json.dumps({"invalid_config": "invalid_stuff"})
|
||||
)
|
||||
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
def test_agent_configuration_invalid_json(flask_client):
|
||||
agent_configuration_url = get_url_for_resource(AgentConfiguration)
|
||||
|
||||
resp = flask_client.post(agent_configuration_url, data="InvalidJson!")
|
||||
|
||||
assert resp.status_code == 400
|
|
@ -1,36 +1,11 @@
|
|||
import io
|
||||
from typing import BinaryIO
|
||||
|
||||
import pytest
|
||||
from tests.common import StubDIContainer
|
||||
from tests.monkey_island import FILE_CONTENTS, FILE_NAME, MockFileRepository
|
||||
from tests.unit_tests.monkey_island.conftest import get_url_for_resource
|
||||
|
||||
from monkey_island.cc.repository import FileRetrievalError, IFileRepository
|
||||
from monkey_island.cc.repository import IFileRepository
|
||||
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
|
||||
|
||||
FILE_NAME = "test_file"
|
||||
FILE_CONTENTS = b"HelloWorld!"
|
||||
|
||||
|
||||
class MockFileRepository(IFileRepository):
|
||||
def __init__(self):
|
||||
self._file = io.BytesIO(FILE_CONTENTS)
|
||||
|
||||
def save_file(self, unsafe_file_name: str, file_contents: BinaryIO):
|
||||
pass
|
||||
|
||||
def open_file(self, unsafe_file_name: str) -> BinaryIO:
|
||||
if unsafe_file_name != FILE_NAME:
|
||||
raise FileRetrievalError()
|
||||
|
||||
return self._file
|
||||
|
||||
def delete_file(self, unsafe_file_name: str):
|
||||
pass
|
||||
|
||||
def delete_all_files(self):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def flask_client(build_flask_client):
|
||||
|
@ -56,3 +31,11 @@ def test_file_download_endpoint_404(tmp_path, flask_client):
|
|||
resp = flask_client.get(download_url)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def test_file_download_endpoint_500(tmp_path, open_error_flask_client):
|
||||
download_url = get_url_for_resource(PBAFileDownload, filename="test")
|
||||
|
||||
resp = open_error_flask_client.get(download_url)
|
||||
|
||||
assert resp.status_code == 500
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import io
|
||||
from typing import BinaryIO
|
||||
|
||||
import pytest
|
||||
from tests.common import StubDIContainer
|
||||
from tests.monkey_island import SingleFileRepository
|
||||
from tests.unit_tests.monkey_island.conftest import get_url_for_resource
|
||||
from tests.utils import raise_
|
||||
|
||||
from monkey_island.cc.repository import FileRetrievalError, IFileRepository
|
||||
from monkey_island.cc.repository import IFileRepository
|
||||
from monkey_island.cc.resources.pba_file_upload import LINUX_PBA_TYPE, WINDOWS_PBA_TYPE, FileUpload
|
||||
|
||||
TEST_FILE_CONTENTS = b"m0nk3y"
|
||||
|
@ -40,28 +38,9 @@ def mock_get_config_value(monkeypatch):
|
|||
)
|
||||
|
||||
|
||||
class MockFileRepository(IFileRepository):
|
||||
def __init__(self):
|
||||
self._file = None
|
||||
|
||||
def save_file(self, unsafe_file_name: str, file_contents: BinaryIO):
|
||||
self._file = io.BytesIO(file_contents.read())
|
||||
|
||||
def open_file(self, unsafe_file_name: str) -> BinaryIO:
|
||||
if self._file is None:
|
||||
raise FileRetrievalError()
|
||||
return self._file
|
||||
|
||||
def delete_file(self, unsafe_file_name: str):
|
||||
self._file = None
|
||||
|
||||
def delete_all_files(self):
|
||||
self.delete_file("")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def file_repository():
|
||||
return MockFileRepository()
|
||||
return SingleFileRepository()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -119,6 +98,15 @@ def test_pba_file_upload_get__file_not_found(flask_client, pba_os, mock_get_conf
|
|||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pba_os", [LINUX_PBA_TYPE, WINDOWS_PBA_TYPE])
|
||||
def test_file_download_endpoint_500(open_error_flask_client, pba_os):
|
||||
url = get_url_for_resource(FileUpload, target_os=pba_os, filename="bobug_mogus.py")
|
||||
|
||||
resp = open_error_flask_client.get(url)
|
||||
|
||||
assert resp.status_code == 500
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pba_os", [LINUX_PBA_TYPE, WINDOWS_PBA_TYPE])
|
||||
def test_pba_file_upload_endpoint(
|
||||
flask_client, pba_os, mock_get_config_value, mock_set_config_value
|
||||
|
|
|
@ -25,34 +25,33 @@ def test_format_config_for_agent__credentials_removed():
|
|||
|
||||
def test_format_config_for_agent__ransomware_payload():
|
||||
expected_ransomware_options = {
|
||||
"ransomware": {
|
||||
"encryption": {
|
||||
"enabled": True,
|
||||
"directories": {
|
||||
"linux_target_dir": "/tmp/ransomware-target",
|
||||
"windows_target_dir": "C:\\windows\\temp\\ransomware-target",
|
||||
},
|
||||
"encryption": {
|
||||
"enabled": True,
|
||||
"directories": {
|
||||
"linux_target_dir": "/tmp/ransomware-target",
|
||||
"windows_target_dir": "C:\\windows\\temp\\ransomware-target",
|
||||
},
|
||||
"other_behaviors": {"readme": True},
|
||||
}
|
||||
},
|
||||
"other_behaviors": {"readme": True},
|
||||
}
|
||||
|
||||
flat_monkey_config = ConfigService.format_flat_config_for_agent()
|
||||
|
||||
assert "payloads" in flat_monkey_config
|
||||
assert flat_monkey_config["payloads"] == expected_ransomware_options
|
||||
assert flat_monkey_config["payloads"][0]["name"] == "ransomware"
|
||||
assert flat_monkey_config["payloads"][0]["options"] == expected_ransomware_options
|
||||
|
||||
assert "ransomware" not in flat_monkey_config
|
||||
|
||||
|
||||
def test_format_config_for_agent__pbas():
|
||||
expected_pbas_config = {
|
||||
"CommunicateAsBackdoorUser": {},
|
||||
"ModifyShellStartupFiles": {},
|
||||
"ScheduleJobs": {},
|
||||
"Timestomping": {},
|
||||
"AccountDiscovery": {},
|
||||
}
|
||||
expected_pbas_config = [
|
||||
{"name": "CommunicateAsBackdoorUser", "options": {}},
|
||||
{"name": "ModifyShellStartupFiles", "options": {}},
|
||||
{"name": "ScheduleJobs", "options": {}},
|
||||
{"name": "Timestomping", "options": {}},
|
||||
{"name": "AccountDiscovery", "options": {}},
|
||||
]
|
||||
flat_monkey_config = ConfigService.format_flat_config_for_agent()
|
||||
|
||||
assert "post_breach_actions" in flat_monkey_config
|
||||
|
@ -93,32 +92,14 @@ def test_format_config_for_agent__propagation():
|
|||
flat_monkey_config = ConfigService.format_flat_config_for_agent()
|
||||
|
||||
assert "propagation" in flat_monkey_config
|
||||
assert "targets" in flat_monkey_config["propagation"]
|
||||
assert "network_scan" in flat_monkey_config["propagation"]
|
||||
assert "exploiters" in flat_monkey_config["propagation"]
|
||||
|
||||
|
||||
def test_format_config_for_agent__propagation_targets():
|
||||
expected_targets = {
|
||||
"blocked_ips": ["192.168.1.1", "192.168.1.100"],
|
||||
"inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"],
|
||||
"local_network_scan": True,
|
||||
"subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"],
|
||||
}
|
||||
|
||||
flat_monkey_config = ConfigService.format_flat_config_for_agent()
|
||||
|
||||
assert flat_monkey_config["propagation"]["targets"] == expected_targets
|
||||
assert "blocked_ips" not in flat_monkey_config
|
||||
assert "inaccessible_subnets" not in flat_monkey_config
|
||||
assert "local_network_scan" not in flat_monkey_config
|
||||
assert "subnet_scan_list" not in flat_monkey_config
|
||||
assert "exploitation" in flat_monkey_config["propagation"]
|
||||
|
||||
|
||||
def test_format_config_for_agent__network_scan():
|
||||
expected_network_scan_config = {
|
||||
"tcp": {
|
||||
"timeout_ms": 3000,
|
||||
"timeout": 3.0,
|
||||
"ports": [
|
||||
22,
|
||||
80,
|
||||
|
@ -136,7 +117,13 @@ def test_format_config_for_agent__network_scan():
|
|||
],
|
||||
},
|
||||
"icmp": {
|
||||
"timeout_ms": 1000,
|
||||
"timeout": 1.0,
|
||||
},
|
||||
"targets": {
|
||||
"blocked_ips": ["192.168.1.1", "192.168.1.100"],
|
||||
"inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"],
|
||||
"local_network_scan": True,
|
||||
"subnets": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"],
|
||||
},
|
||||
"fingerprinters": [
|
||||
{"name": "elastic", "options": {}},
|
||||
|
@ -161,36 +148,63 @@ def test_format_config_for_agent__network_scan():
|
|||
assert "finger_classes" not in flat_monkey_config
|
||||
|
||||
|
||||
def test_format_config_for_agent__propagation_network_scan_targets():
|
||||
expected_targets = {
|
||||
"blocked_ips": ["192.168.1.1", "192.168.1.100"],
|
||||
"inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"],
|
||||
"local_network_scan": True,
|
||||
"subnets": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"],
|
||||
}
|
||||
|
||||
flat_monkey_config = ConfigService.format_flat_config_for_agent()
|
||||
|
||||
assert flat_monkey_config["propagation"]["network_scan"]["targets"] == expected_targets
|
||||
assert "blocked_ips" not in flat_monkey_config
|
||||
assert "inaccessible_subnets" not in flat_monkey_config
|
||||
assert "local_network_scan" not in flat_monkey_config
|
||||
assert "subnet_scan_list" not in flat_monkey_config
|
||||
|
||||
|
||||
def test_format_config_for_agent__exploiters():
|
||||
expected_exploiters_config = {
|
||||
"options": {
|
||||
"http_ports": [80, 443, 7001, 8008, 8080, 9200],
|
||||
},
|
||||
"brute_force": [
|
||||
{"name": "MSSQLExploiter", "supported_os": ["windows"], "options": {}},
|
||||
{"name": "PowerShellExploiter", "supported_os": ["windows"], "options": {}},
|
||||
{"name": "SSHExploiter", "supported_os": ["linux"], "options": {}},
|
||||
{"name": "MSSQLExploiter", "options": {}},
|
||||
{
|
||||
"name": "PowerShellExploiter",
|
||||
"options": {},
|
||||
},
|
||||
{"name": "SSHExploiter", "options": {}},
|
||||
{
|
||||
"name": "SmbExploiter",
|
||||
"supported_os": ["windows"],
|
||||
"options": {"smb_download_timeout": 30},
|
||||
},
|
||||
{
|
||||
"name": "WmiExploiter",
|
||||
"supported_os": ["windows"],
|
||||
"options": {"smb_download_timeout": 30},
|
||||
},
|
||||
],
|
||||
"vulnerability": [
|
||||
{"name": "HadoopExploiter", "supported_os": ["linux", "windows"], "options": {}},
|
||||
{"name": "Log4ShellExploiter", "supported_os": ["linux", "windows"], "options": {}},
|
||||
{"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}},
|
||||
{
|
||||
"name": "HadoopExploiter",
|
||||
"options": {},
|
||||
},
|
||||
{
|
||||
"name": "Log4ShellExploiter",
|
||||
"options": {},
|
||||
},
|
||||
{
|
||||
"name": "ZerologonExploiter",
|
||||
"options": {},
|
||||
},
|
||||
],
|
||||
}
|
||||
flat_monkey_config = ConfigService.format_flat_config_for_agent()
|
||||
|
||||
assert "propagation" in flat_monkey_config
|
||||
assert "exploiters" in flat_monkey_config["propagation"]
|
||||
assert "exploitation" in flat_monkey_config["propagation"]
|
||||
|
||||
assert flat_monkey_config["propagation"]["exploiters"] == expected_exploiters_config
|
||||
assert flat_monkey_config["propagation"]["exploitation"] == expected_exploiters_config
|
||||
assert "exploiter_classes" not in flat_monkey_config
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from unittest import TestCase
|
||||
|
||||
import bson
|
||||
|
@ -44,3 +45,11 @@ class TestRepresentations(TestCase):
|
|||
}
|
||||
),
|
||||
)
|
||||
|
||||
def test_normalize__enum(self):
|
||||
class BogusEnum(Enum):
|
||||
bogus_val = "Bogus"
|
||||
|
||||
my_obj = {"something": "something", "my_enum": BogusEnum.bogus_val}
|
||||
|
||||
assert {"something": "something", "my_enum": "bogus_val"} == normalize_obj(my_obj)
|
||||
|
|
|
@ -23,6 +23,7 @@ log_cli_date_format = "%H:%M:%S"
|
|||
addopts = "-v --capture=sys tests/unit_tests"
|
||||
norecursedirs = "node_modules dist"
|
||||
markers = ["slow: mark test as slow"]
|
||||
pythonpath = "./monkey"
|
||||
|
||||
[tool.vulture]
|
||||
exclude = ["monkey/monkey_island/cc/ui/", "monkey/tests/", "monkey/monkey_island/docs/"]
|
||||
|
|
|
@ -184,6 +184,19 @@ architecture # unused variable (monkey/infection_monkey/exploit/caching_agent_r
|
|||
response_code # unused variable (monkey/monkey_island/cc/services/aws/aws_command_runner.py:26)
|
||||
release_convention # unused method (monkey/common/di_container.py:174)
|
||||
|
||||
# Agent Configuration
|
||||
_make_plugin_configuration # unused method (monkey/common/configuration/agent_configuration.py:19)
|
||||
_make_custom_pba_configuration # unused method (monkey/common/configuration/agent_configuration.py:24)
|
||||
_make_exploiter_configuration # unused method (monkey/common/configuration/agent_configuration.py:69)
|
||||
_make_exploitation_options_configuration # unused method (monkey/common/configuration/agent_configuration.py:86)
|
||||
_make_scan_target_configuration # unused method (monkey/common/configuration/agent_configuration.py:105)
|
||||
_make_icmp_scan_configuration # unused method (monkey/common/configuration/agent_configuration.py:107)
|
||||
_make_tcp_scan_configuration # unused method (monkey/common/configuration/agent_configuration.py:122)
|
||||
_make_network_scan_configuration # unused method (monkey/common/configuration/agent_configuration.py:110)
|
||||
_make_propagation_configuration # unused method (monkey/common/configuration/agent_configuration.py:167)
|
||||
_make_agent_configuration # unused method (monkey/common/configuration/agent_configuration.py:192)
|
||||
|
||||
|
||||
# TODO DELETE AFTER RESOURCE REFACTORING
|
||||
NetworkMap
|
||||
Arc.dst_machine
|
||||
|
|
Loading…
Reference in New Issue