diff --git a/.gitmodules b/.gitmodules index b898f160a..2fb33dd37 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "docs/themes/learn"] path = docs/themes/learn url = https://github.com/guardicode/hugo-theme-learn.git -[submodule "monkey/infection_monkey/system_info/collectors/scoutsuite"] - path = monkey/common/cloud/scoutsuite - url = https://github.com/guardicode/ScoutSuite.git diff --git a/.travis.yml b/.travis.yml index 668d9cdc3..8ac8db204 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,6 @@ install: - pip install flake8 pytest pytest-cov dlint isort # for next stages - pip install coverage # for code coverage - pip install -r monkey/infection_monkey/requirements.txt # for unit tests -- pip install -r monkey/common/cloud/scoutsuite/requirements.txt - pip install pipdeptree # Fail builds on possible conflicting dependencies. - pipdeptree --warn fail @@ -56,7 +55,7 @@ install: script: # Check Python code ## Check syntax errors and fail the build if any are found. -- flake8 ./monkey --exclude=monkey/common/cloud/scoutsuite --config=./ci_scripts/flake8_syntax_check.ini +- flake8 ./monkey --config=./ci_scripts/flake8_syntax_check.ini ## Warn about linter issues. ### --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build. diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 718773fad..408aa3148 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -66,7 +66,6 @@ MONGO_PATH="$ISLAND_PATH/bin/mongodb" ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" INFECTION_MONKEY_DIR="$monkey_home/monkey/infection_monkey" MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin" -SCOUTSUITE_DIR="$monkey_home/monkey/common/cloud/scoutsuite" if ! has_sudo; then log_message "You need root permissions for some of this script operations. \ @@ -142,10 +141,6 @@ sudo apt-get install -y libffi-dev upx libssl-dev libc++1 requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt" ${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error -log_message "Installing ScoutSuite requirements" -requirements_scoutsuite="$SCOUTSUITE_DIR/requirements.txt" -${python_cmd} -m pip install -r "${requirements_scoutsuite}" --user --upgrade || handle_error - agents=${3:-true} # Download binaries if [ "$agents" = true ] ; then diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 89bbf8aba..2d46310cd 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -7,7 +7,7 @@ pre: " " Here are some of the most common questions we receive about the Infection Monkey. If the answer you're looking for isn't here, talk with us [on our Slack channel](https://infectionmonkey.slack.com/join/shared_invite/enQtNDU5MjAxMjg1MjU1LWM0NjVmNWE2ZTMzYzAxOWJiYmMxMzU0NWU3NmUxYjcyNjk0YWY2MDkwODk4NGMyNDU4NzA4MDljOWNmZWViNDU), email us at [support@infectionmonkey.com](mailto:support@infectionmonkey.com) or [open an issue on GitHub](https://github.com/guardicore/monkey). -- [Where can I get the latest Monkey version? πŸ“°](#where-can-i-get-the-latest-monkey-version) +- [Where can I get the latest Monkey version?](#where-can-i-get-the-latest-monkey-version) - [How long does a single Monkey run for? Is there a time limit?](#how-long-does-a-single-monkey-run-for-is-there-a-time-limit) - [How to reset the password?](#how-to-reset-the-password) - [Should I run the Monkey continuously?](#should-i-run-the-monkey-continuously) @@ -24,9 +24,9 @@ Here are some of the most common questions we receive about the Infection Monkey - [After I've set up Monkey Island, how can I execute the Monkey?](#after-ive-set-up-monkey-island-how-can-i-execute-the-monkey) - [How can I make the monkey propagate β€œdeeper” into the network?](#how-can-i-make-the-monkey-propagate-deeper-into-the-network) - [The report returns a blank screen](#the-report-returns-a-blank-screen) -- [How can I get involved with the project? πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»](#how-can-i-get-involved-with-the-project) +- [How can I get involved with the project?](#how-can-i-get-involved-with-the-project) -## Where can I get the latest Monkey version? πŸ“° +## Where can I get the latest Monkey version? For the latest **stable** release for users, visit [our downloads page](https://www.guardicore.com/infectionmonkey/#download). **This is the recommended and supported version**! @@ -167,7 +167,7 @@ This is sometimes caused when Monkey Island is installed with an old version of - **Linux**: First, uninstall the current version with `sudo apt uninstall mongodb` and then install the latest version using the [official mongodb manual](https://docs.mongodb.com/manual/administration/install-community/). - **Windows**: First, remove the MongoDB binaries from the `monkey\monkey_island\bin\mongodb` folder. Download and install the latest version of mongodb using the [official mongodb manual](https://docs.mongodb.com/manual/administration/install-community/). After installation is complete, copy the files from the `C:\Program Files\MongoDB\Server\4.2\bin` folder to the `monkey\monkey_island\bin\mongodb folder`. Try to run the Island again and everything should work. -## How can I get involved with the project? πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» +## How can I get involved with the project? The Monkey is an open-source project, and we weclome contributions and contributors. Check out the [contribution documentation](../development) for more information. diff --git a/docs/content/setup/debian.md b/docs/content/setup/debian.md index c2e875b68..b76d27ec0 100644 --- a/docs/content/setup/debian.md +++ b/docs/content/setup/debian.md @@ -8,33 +8,48 @@ disableToc: false tags: ["setup", "debian", "linux"] --- + +## Supported Distros + +This Debian package has been tested on Ubuntu Bionic 18.04 LTS and Ubuntu Focal 20.04 LTS. + ## Deployment -To extract the `tar.gz` file, run `tar -xvzf monkey-island-debian.tar.gz`. +1. Update your package list by running: + ```sh + sudo apt update + ``` +1. If you are using Ubuntu Focal 20.04, run the following commands to install + Python 3.7: + ```sh + sudo apt install software-properties-common + sudo add-apt-repository ppa:deadsnakes/ppa + sudo apt install python3.7 python3.7-dev + ``` +1. Extract the tarball by running: + ```sh + tar -xvzf monkey-island-debian.tgz + ``` +1. Install the Monkey Island Debian package: + ```sh + sudo dpkg -i monkey_island.deb # this might print errors + ``` +1. If, at this point, you receive dpkg errors that look like this: -Once you've extracted the package, deploy it using run the following commands: + ```sh + dpkg: error processing package gc-monkey-island (--install): + dependency problems - leaving unconfigured + Errors were encountered while processing: + gc-monkey-island + ``` -```sh -sudo apt update -sudo dpkg -i monkey_island.deb # this might print errors -``` + It just means that not all dependencies were pre-installed on your system. + That's no problem! Just run the following command, which will install all + dependencies, and then install the Monkey Island: -If, at this point, you receive dpkg printed errors that look like this: - -```sh -dpkg: error processing package gc-monkey-island (--install): - dependency problems - leaving unconfigured -Errors were encountered while processing: - gc-monkey-island -``` - -It just means that not all dependencies were pre-installed on your system. -That's no problem! Just run the following command, which will install all -dependencies, and then install the Monkey Island: - -```sh -sudo apt install -f -``` + ```sh + sudo apt install -f + ``` ## Troubleshooting diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index fb70347f2..14454bdc6 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -14,11 +14,11 @@ To extract the `tar.gz` file, run `tar -xvzf monkey-island-docker.tar.gz`. Once you've extracted the container from the tar.gz file, run the following commands: ```sh -sudo docker load -i dk.monkeyisland.1.9.0.tar -sudo docker pull mongo +sudo docker load -i dk.monkeyisland.1.10.0.tar +sudo docker pull mongo:4.2 sudo mkdir -p /var/monkey-mongo/data/db -sudo docker run --name monkey-mongo --network=host -v /var/monkey-mongo/data/db:/data/db -d mongo -sudo docker run --name monkey-island --network=host -d guardicore/monkey-island:1.9.0 +sudo docker run --name monkey-mongo --network=host -v /var/monkey-mongo/data/db:/data/db -d mongo:4.2 +sudo docker run --name monkey-island --network=host -d guardicore/monkey-island:1.10.0 ``` ## Upgrading diff --git a/docs/content/setup/vmware.md b/docs/content/setup/vmware.md index 21522f820..c6519672b 100644 --- a/docs/content/setup/vmware.md +++ b/docs/content/setup/vmware.md @@ -16,8 +16,9 @@ tags: ["setup", "vmware"] 1. Log in to the machine with the following credentials: 1. Username: **monkeyuser** 1. Password: **Noon.Earth.Always** -1. It's recommended you change the machine passwords by running the following - commands: `sudo passwd monkeyuser`, `sudo passwd root`. +1. For security purposes, it's recommended that you change the machine + passwords by running the following commands: `sudo passwd monkeyuser`, `sudo + passwd root`. ## OVA network modes @@ -26,37 +27,43 @@ You can use the OVA in one of two modes: 1. In a network with the DHCP configured β€” In this case, the Monkey Island will automatically query and receive an IP address from the network. 1. With a static IP address β€” In this case, you should log in to the VM console - with the username `root` and the password `G3aJ9szrvkxTmfAG`. After logging - in, edit the interfaces file by entering the following command in the + with the username `monkeyuser` and the password `Noon.Earth.Always`. After logging + in, edit the Netplan configuration by entering the following command in the prompt: ```sh - sudo nano /etc/network/interfaces + sudo nano /etc/netplan/00-installer-config.yaml ``` - Change the lines: + Make the following changes: - ```sh - auto ens160 - iface ens160 inet dhcp + ```diff + # This is the network config written by 'subiquity' + network: + ethernets: + ens160: + - dhcp4: true + + dhcp4: false + + addresses: [XXX.XXX.XXX.XXX/24] + + gateway4: YYY.YYY.YYY.YYY + + nameservers: + + addresses: [1.1.1.1] + version: 2 ``` - to the following: - - ```sh - auto ens160 - iface ens160 inet static - address AAA.BBB.CCC.DDD - netmask XXX.XXX.XXX.XXX - gateway YYY.YYY.YYY.YYY - ``` + Replace `XXX.XXX.XXX.XXX` with the desired IP addess of the VM. Replace + `YYY.YYY.YYY.YYY` with the default gateway. Save the changes then run the command: ```sh - sudo ifdown ens160 && ifup ens160 + sudo netplan apply ``` + If this configuration does not suit your needs, see + https://netplan.io/examples/ for more information about how to configure + Netplan. + ## Upgrading Currently, there's no "upgrade-in-place" option when a new version is released. diff --git a/docs/content/usage/file-checksums.md b/docs/content/usage/file-checksums.md index 9c09f570f..b063550ed 100644 --- a/docs/content/usage/file-checksums.md +++ b/docs/content/usage/file-checksums.md @@ -35,6 +35,24 @@ $ sha256sum monkey-linux-64 ## Latest version checksums +| Filename | Type | Version | SHA256 | +|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------| +| monkey-windows-64.exe | Windows Agent | 1.10.0 | `3b499a4cf1a67a33a91c73b05884e4d6749e990e444fa1d2a3281af4db833fa1` | +| monkey-windows-32.exe | Windows Agent | 1.10.0 | `8e891e90b11b97fbbef27f1408c1fcad486b19c612773f2d6a9edac5d4cdb47f` | +| monkey-linux-64 | Linux Agent | 1.10.0 | `932f703510b6484c3824fc797f90f99722e38a7f8956cf6fa58fdecb3790ab93` | +| monkey-linux-32 | Linux Agent | 1.10.0 | `a6de7d571051292b9db966afe025413dc20b214c4aab53e48d90d8e04264f4f5` | +| infection_monkey_deb.tgz | Debian Package | 1.10.0 | `534d85c4abc78e2c86a74d8b88759b091b62077dd9e32f02eeb43d716d359ff6` | +| infection_monkey_debzt.tgz | Debian Package | 1.10.0 | `bd01d8482f80990e6cc0ed654c07dbd80da71eebe3dd244365e9bc00f86b1c03` | +| Monkey Island v1.10.0_3593_windows.exe | Windows Installer | 1.10.0 | `ebd2c5627d21dd8670def02c3a5a995f9e799ba567cf4caacd702654264ddf06` | +| Monkey Island v1.10.0_3593_windowszt.exe | Windows Installer | 1.10.0 | `60aaf3b32e5d06c91fe0d4f1b950529517ac33796f67e9ccfef0e8ce1c5372d8` | +| infection_monkey_docker_docker_20210326_171631.tgz | Docker | 1.10.0 | `e4f9c7c5aafe7e38b33d2927a9c0cf6a3ac27858d3d0e3f2252c2e91809a78db` | +| infection_monkey_docker_dockerzt_20210326_172035.tgz | Docker | 1.10.0 | `248640e9eaa18e4c27f67237f0594d9533732f372ba4674d5d1bea43ab498cf5` | +| monkey-island-vmware.ova | OVA | 1.10.0 | `3472ad4ae557ddad7d7db8fbbfcfd33c4f2d95d870b18fa4cab49af6b562009c` | +| monkey-island-vmwarezt.ova | OVA | 1.10.0 | `3472ad4ae557ddad7d7db8fbbfcfd33c4f2d95d870b18fa4cab49af6b562009c` | + + +## Older checksums + | Filename | Type | Version | SHA256 | |------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------| | monkey-windows-64.exe | Windows Agent | 1.9.0 | `24622cb8dbabb0cf4b25ecd3c13800c72ec5b59b76895b737ece509640d4c068` | @@ -49,12 +67,6 @@ $ sha256sum monkey-linux-64 | infection_monkey_docker_dockerzt_20200806_154742.tgz | Docker | 1.9.0 | `a84dbaad32ae42cc2d359ffbe062aec493a7253cf706a2d45f0d0b1c230f9348` | | monkey-island-vmware.ova | OVA | 1.9.0 | `3861d46518e8a92e49992b26dbff9fe8e8a4ac5fd24d68e68b13e7fd3fa22247` | | monkey-island-vmwarezt.ova | OVA | 1.9.0 | `03d356eb35e6515146f5bd798bb62cb15c56fcdf83a5281cf6cdc9b901586026` | - - -## Older checksums - -| Filename | Type | Version | SHA256 | -|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------| | monkey-windows-64.exe | Windows Agent | 1.8.2 | `2e6a1cb5523d87ddfd48f75b10114617343fbac8125fa950ba7f00289b38b550` | | monkey-windows-32.exe | Windows Agent | 1.8.2 | `86a7d7065e73b795e38f2033be0c53f3ac808cc67478aed794a7a6c89123979f` | | monkey-linux-64 | Linux Agent | 1.8.2 | `4dce4a115d41b43adffc11672fae2164265f8902267f1355d02bebb802bd45c5` | diff --git a/envs/monkey_zoo/blackbox/README.md b/envs/monkey_zoo/blackbox/README.md index 30855b855..808a0a5cb 100644 --- a/envs/monkey_zoo/blackbox/README.md +++ b/envs/monkey_zoo/blackbox/README.md @@ -19,10 +19,10 @@ instead will just test performance of endpoints in already present island state. Example run command: -`monkey\envs\monkey_zoo\blackbox>python -m pytest -s --island=35.207.152.72:5000 test_blackbox.py` +`monkey\monkey>python -m pytest -s --island=35.207.152.72:5000 ..\envs\monkey_zoo\blackbox\test_blackbox.py` #### Running in PyCharm -Configure a PyTest configuration with the additional arguments `-s --island=35.207.152.72`, and to run from +Configure a PyTest configuration with the additional arguments `-s --island=35.207.152.72:5000`, and to run from directory `monkey\envs\monkey_zoo\blackbox`. ### Running telemetry performance test diff --git a/envs/monkey_zoo/blackbox/analyzers/analyzer.py b/envs/monkey_zoo/blackbox/analyzers/analyzer.py index d6043feeb..13db46cb3 100644 --- a/envs/monkey_zoo/blackbox/analyzers/analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/analyzer.py @@ -4,5 +4,5 @@ from abc import ABCMeta, abstractmethod class Analyzer(object, metaclass=ABCMeta): @abstractmethod - def analyze_test_results(self): + def analyze_test_results(self) -> bool: raise NotImplementedError() diff --git a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py new file mode 100644 index 000000000..f5da3a2e1 --- /dev/null +++ b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py @@ -0,0 +1,70 @@ +from typing import List +from pprint import pformat + +import dpath.util + +from common.config_value_paths import USER_LIST_PATH, PASSWORD_LIST_PATH, NTLM_HASH_LIST_PATH, LM_HASH_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} + + +class ZerologonAnalyzer(Analyzer): + + def __init__(self, island_client: MonkeyIslandClient, expected_credentials: List[str]): + self.island_client = island_client + self.expected_credentials = expected_credentials + self.log = AnalyzerLog(self.__class__.__name__) + + def analyze_test_results(self): + self.log.clear() + is_creds_gathered = self._analyze_credential_gathering() + is_creds_restored = self._analyze_credential_restore() + return is_creds_gathered and is_creds_restored + + def _analyze_credential_gathering(self) -> bool: + config = self.island_client.get_config() + credentials_on_island = ZerologonAnalyzer._get_relevant_credentials(config) + return self._is_all_credentials_in_list(credentials_on_island) + + @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)) + return credentials_on_island + + 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 + + def _log_creds_not_gathered(self, missing_creds: List[str]): + if not missing_creds: + self.log.add_entry("Zerologon exploiter gathered all credentials expected.") + else: + for cred in missing_creds: + self.log.add_entry(f"Credential Zerologon exploiter failed to gathered:{cred}.") + + def _analyze_credential_restore(self) -> bool: + cred_restore_telems = self.island_client.find_telems_in_db(TELEM_QUERY) + self._log_credential_restore(cred_restore_telems) + return bool(cred_restore_telems) + + 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.") + else: + 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/island_configs/__init__.py b/envs/monkey_zoo/blackbox/config_templates/__init__.py similarity index 100% rename from envs/monkey_zoo/blackbox/island_configs/__init__.py rename to envs/monkey_zoo/blackbox/config_templates/__init__.py diff --git a/envs/monkey_zoo/blackbox/island_configs/base_template.py b/envs/monkey_zoo/blackbox/config_templates/base_template.py similarity index 85% rename from envs/monkey_zoo/blackbox/island_configs/base_template.py rename to envs/monkey_zoo/blackbox/config_templates/base_template.py index 13a480286..9ebea6f1f 100644 --- a/envs/monkey_zoo/blackbox/island_configs/base_template.py +++ b/envs/monkey_zoo/blackbox/config_templates/base_template.py @@ -1,4 +1,4 @@ -from envs.monkey_zoo.blackbox.island_configs.config_template import ConfigTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate # Disables a lot of config values not required for a specific feature test diff --git a/envs/monkey_zoo/blackbox/island_configs/config_template.py b/envs/monkey_zoo/blackbox/config_templates/config_template.py similarity index 100% rename from envs/monkey_zoo/blackbox/island_configs/config_template.py rename to envs/monkey_zoo/blackbox/config_templates/config_template.py diff --git a/envs/monkey_zoo/blackbox/config_templates/drupal.py b/envs/monkey_zoo/blackbox/config_templates/drupal.py new file mode 100644 index 000000000..e202219dc --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/drupal.py @@ -0,0 +1,14 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +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"] + }) diff --git a/envs/monkey_zoo/blackbox/island_configs/elastic.py b/envs/monkey_zoo/blackbox/config_templates/elastic.py similarity index 53% rename from envs/monkey_zoo/blackbox/island_configs/elastic.py rename to envs/monkey_zoo/blackbox/config_templates/elastic.py index 97598f718..56021e959 100644 --- a/envs/monkey_zoo/blackbox/island_configs/elastic.py +++ b/envs/monkey_zoo/blackbox/config_templates/elastic.py @@ -1,7 +1,7 @@ from copy import copy -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate -from envs.monkey_zoo.blackbox.island_configs.config_template import ConfigTemplate +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate class Elastic(ConfigTemplate): @@ -10,5 +10,6 @@ class Elastic(ConfigTemplate): 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"] }) diff --git a/envs/monkey_zoo/blackbox/island_configs/hadoop.py b/envs/monkey_zoo/blackbox/config_templates/hadoop.py similarity index 56% rename from envs/monkey_zoo/blackbox/island_configs/hadoop.py rename to envs/monkey_zoo/blackbox/config_templates/hadoop.py index 8c42b8ee3..d136068e5 100644 --- a/envs/monkey_zoo/blackbox/island_configs/hadoop.py +++ b/envs/monkey_zoo/blackbox/config_templates/hadoop.py @@ -1,9 +1,10 @@ from copy import copy -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -class Hadoop(BaseTemplate): +class Hadoop(ConfigTemplate): config_values = copy(BaseTemplate.config_values) diff --git a/envs/monkey_zoo/blackbox/island_configs/mssql.py b/envs/monkey_zoo/blackbox/config_templates/mssql.py similarity index 77% rename from envs/monkey_zoo/blackbox/island_configs/mssql.py rename to envs/monkey_zoo/blackbox/config_templates/mssql.py index 5406494ee..003f9f8d3 100644 --- a/envs/monkey_zoo/blackbox/island_configs/mssql.py +++ b/envs/monkey_zoo/blackbox/config_templates/mssql.py @@ -1,9 +1,10 @@ from copy import copy -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -class Mssql(BaseTemplate): +class Mssql(ConfigTemplate): config_values = copy(BaseTemplate.config_values) config_values.update({ diff --git a/envs/monkey_zoo/blackbox/island_configs/performance.py b/envs/monkey_zoo/blackbox/config_templates/performance.py similarity index 97% rename from envs/monkey_zoo/blackbox/island_configs/performance.py rename to envs/monkey_zoo/blackbox/config_templates/performance.py index 3a9a48e9f..e9e34727d 100644 --- a/envs/monkey_zoo/blackbox/island_configs/performance.py +++ b/envs/monkey_zoo/blackbox/config_templates/performance.py @@ -1,4 +1,4 @@ -from envs.monkey_zoo.blackbox.island_configs.config_template import ConfigTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate class Performance(ConfigTemplate): diff --git a/envs/monkey_zoo/blackbox/island_configs/shellshock.py b/envs/monkey_zoo/blackbox/config_templates/shellshock.py similarity index 55% rename from envs/monkey_zoo/blackbox/island_configs/shellshock.py rename to envs/monkey_zoo/blackbox/config_templates/shellshock.py index 27e0dc34d..71d968e0b 100644 --- a/envs/monkey_zoo/blackbox/island_configs/shellshock.py +++ b/envs/monkey_zoo/blackbox/config_templates/shellshock.py @@ -1,9 +1,10 @@ from copy import copy -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -class ShellShock(BaseTemplate): +class ShellShock(ConfigTemplate): config_values = copy(BaseTemplate.config_values) config_values.update({ diff --git a/envs/monkey_zoo/blackbox/island_configs/smb_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py similarity index 80% rename from envs/monkey_zoo/blackbox/island_configs/smb_mimikatz.py rename to envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py index aed4ee9c7..f563bc8d1 100644 --- a/envs/monkey_zoo/blackbox/island_configs/smb_mimikatz.py +++ b/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py @@ -1,9 +1,10 @@ from copy import copy -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -class SmbMimikatz(BaseTemplate): +class SmbMimikatz(ConfigTemplate): config_values = copy(BaseTemplate.config_values) config_values.update({ diff --git a/envs/monkey_zoo/blackbox/island_configs/smb_pth.py b/envs/monkey_zoo/blackbox/config_templates/smb_pth.py similarity index 81% rename from envs/monkey_zoo/blackbox/island_configs/smb_pth.py rename to envs/monkey_zoo/blackbox/config_templates/smb_pth.py index 3bb92347e..edee4cdbd 100644 --- a/envs/monkey_zoo/blackbox/island_configs/smb_pth.py +++ b/envs/monkey_zoo/blackbox/config_templates/smb_pth.py @@ -1,9 +1,10 @@ from copy import copy -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -class SmbPth(BaseTemplate): +class SmbPth(ConfigTemplate): config_values = copy(BaseTemplate.config_values) config_value_list = { diff --git a/envs/monkey_zoo/blackbox/island_configs/ssh.py b/envs/monkey_zoo/blackbox/config_templates/ssh.py similarity index 74% rename from envs/monkey_zoo/blackbox/island_configs/ssh.py rename to envs/monkey_zoo/blackbox/config_templates/ssh.py index f6a5b1762..90871e52b 100644 --- a/envs/monkey_zoo/blackbox/island_configs/ssh.py +++ b/envs/monkey_zoo/blackbox/config_templates/ssh.py @@ -1,8 +1,11 @@ -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -class Ssh(BaseTemplate): - config_values = BaseTemplate.config_values +class Ssh(ConfigTemplate): + config_values = copy(BaseTemplate.config_values) config_values.update({ "basic.exploiters.exploiter_classes": ["SSHExploiter"], diff --git a/envs/monkey_zoo/blackbox/config_templates/struts2.py b/envs/monkey_zoo/blackbox/config_templates/struts2.py new file mode 100644 index 000000000..6eb399568 --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/struts2.py @@ -0,0 +1,14 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +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"] + }) diff --git a/envs/monkey_zoo/blackbox/island_configs/tunneling.py b/envs/monkey_zoo/blackbox/config_templates/tunneling.py similarity index 77% rename from envs/monkey_zoo/blackbox/island_configs/tunneling.py rename to envs/monkey_zoo/blackbox/config_templates/tunneling.py index 458b89794..ac46eb110 100644 --- a/envs/monkey_zoo/blackbox/island_configs/tunneling.py +++ b/envs/monkey_zoo/blackbox/config_templates/tunneling.py @@ -1,8 +1,11 @@ -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -class Tunneling(BaseTemplate): - config_values = BaseTemplate.config_values +class Tunneling(ConfigTemplate): + config_values = copy(BaseTemplate.config_values) config_values.update({ "basic.exploiters.exploiter_classes": ["SmbExploiter", @@ -13,6 +16,8 @@ class Tunneling(BaseTemplate): "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}", diff --git a/envs/monkey_zoo/blackbox/config_templates/weblogic.py b/envs/monkey_zoo/blackbox/config_templates/weblogic.py new file mode 100644 index 000000000..482f7abf9 --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/weblogic.py @@ -0,0 +1,14 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +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"] + }) diff --git a/envs/monkey_zoo/blackbox/island_configs/wmi_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py similarity index 75% rename from envs/monkey_zoo/blackbox/island_configs/wmi_mimikatz.py rename to envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py index 73bd913cd..b6dbc0c88 100644 --- a/envs/monkey_zoo/blackbox/island_configs/wmi_mimikatz.py +++ b/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py @@ -1,8 +1,11 @@ -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -class WmiMimikatz(BaseTemplate): - config_values = BaseTemplate.config_values +class WmiMimikatz(ConfigTemplate): + config_values = copy(BaseTemplate.config_values) config_values.update({ "basic.exploiters.exploiter_classes": ["WmiExploiter"], diff --git a/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py b/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py new file mode 100644 index 000000000..92746c3df --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py @@ -0,0 +1,22 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +class WmiPth(ConfigTemplate): + config_values = copy(BaseTemplate.config_values) + + config_values.update( + { + "basic.exploiters.exploiter_classes": ["WmiExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.15"], + "basic.credentials.exploit_password_list": ["Password1!"], + "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], + "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], + "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 new file mode 100644 index 000000000..28afa281f --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/zerologon.py @@ -0,0 +1,16 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +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": [] + }) 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 d9e81957e..5b7211f87 100644 --- a/envs/monkey_zoo/blackbox/island_client/island_config_parser.py +++ b/envs/monkey_zoo/blackbox/island_client/island_config_parser.py @@ -4,7 +4,7 @@ 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.island_configs.config_template import ConfigTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate class IslandConfigParser: 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 050cfe04c..304996ebd 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -1,6 +1,7 @@ import json import logging from time import sleep +from typing import Union from bson import json_util @@ -8,6 +9,7 @@ from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import Monkey SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5 MONKEY_TEST_ENDPOINT = 'api/test/monkey' +TELEMETRY_TEST_ENDPOINT = 'api/test/telemetry' LOG_TEST_ENDPOINT = 'api/test/log' LOGGER = logging.getLogger(__name__) @@ -67,6 +69,13 @@ class MonkeyIslandClient(object): 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)) + 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)) @@ -78,7 +87,7 @@ class MonkeyIslandClient(object): return MonkeyIslandClient.get_test_query_results(response) @staticmethod - def form_find_query_for_request(query): + def form_find_query_for_request(query: Union[dict, None]) -> dict: return {'find_query': json_util.dumps(query)} @staticmethod diff --git a/envs/monkey_zoo/blackbox/island_configs/struts2.py b/envs/monkey_zoo/blackbox/island_configs/struts2.py deleted file mode 100644 index e88c0899f..000000000 --- a/envs/monkey_zoo/blackbox/island_configs/struts2.py +++ /dev/null @@ -1,11 +0,0 @@ -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate - - -class Struts2(BaseTemplate): - - config_values = 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"] - }) diff --git a/envs/monkey_zoo/blackbox/island_configs/weblogic.py b/envs/monkey_zoo/blackbox/island_configs/weblogic.py deleted file mode 100644 index 433067cb9..000000000 --- a/envs/monkey_zoo/blackbox/island_configs/weblogic.py +++ /dev/null @@ -1,11 +0,0 @@ -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate - - -class Weblogic(BaseTemplate): - - config_values = 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"] - }) diff --git a/envs/monkey_zoo/blackbox/island_configs/wmi_pth.py b/envs/monkey_zoo/blackbox/island_configs/wmi_pth.py deleted file mode 100644 index dcb735c78..000000000 --- a/envs/monkey_zoo/blackbox/island_configs/wmi_pth.py +++ /dev/null @@ -1,18 +0,0 @@ -from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate - - -class WmiPth(BaseTemplate): - config_values = BaseTemplate.config_values - - config_values.update({ - "basic.exploiters.exploiter_classes": ["WmiExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.15"], - "basic.credentials.exploit_password_list": ["Password1!"], - "basic.credentials.exploit_user_list": ["Administrator", - "m0nk3y", - "user"], - "internal.classes.finger_classes": ["PingScanner", - "HTTPFinger"], - "internal.classes.exploits.exploit_ntlm_hash_list": ["5da0889ea2081aa79f6852294cba4a5e", - "50c9987a6bf1ac59398df9f911122c9b"] - }) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index e5a77ef45..bfcf32fba 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -7,24 +7,27 @@ from typing_extensions import Type 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.island_configs.config_template import ConfigTemplate -from envs.monkey_zoo.blackbox.island_configs.elastic import Elastic -from envs.monkey_zoo.blackbox.island_configs.hadoop import Hadoop -from envs.monkey_zoo.blackbox.island_configs.mssql import Mssql -from envs.monkey_zoo.blackbox.island_configs.performance import Performance -from envs.monkey_zoo.blackbox.island_configs.shellshock import ShellShock -from envs.monkey_zoo.blackbox.island_configs.smb_mimikatz import SmbMimikatz -from envs.monkey_zoo.blackbox.island_configs.smb_pth import SmbPth -from envs.monkey_zoo.blackbox.island_configs.ssh import Ssh -from envs.monkey_zoo.blackbox.island_configs.struts2 import Struts2 -from envs.monkey_zoo.blackbox.island_configs.tunneling import Tunneling -from envs.monkey_zoo.blackbox.island_configs.weblogic import Weblogic -from envs.monkey_zoo.blackbox.island_configs.wmi_mimikatz import WmiMimikatz -from envs.monkey_zoo.blackbox.island_configs.wmi_pth import WmiPth +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 +from envs.monkey_zoo.blackbox.config_templates.hadoop import Hadoop +from envs.monkey_zoo.blackbox.config_templates.mssql import Mssql +from envs.monkey_zoo.blackbox.config_templates.performance import Performance +from envs.monkey_zoo.blackbox.config_templates.shellshock import ShellShock +from envs.monkey_zoo.blackbox.config_templates.smb_mimikatz import SmbMimikatz +from envs.monkey_zoo.blackbox.config_templates.smb_pth import SmbPth +from envs.monkey_zoo.blackbox.config_templates.ssh import Ssh +from envs.monkey_zoo.blackbox.config_templates.struts2 import Struts2 +from envs.monkey_zoo.blackbox.config_templates.tunneling import Tunneling +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.tests.exploitation import ExploitationTest @@ -44,8 +47,10 @@ 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'] + '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__) @@ -138,6 +143,9 @@ class TestMonkeyBlackbox: def test_smb_pth(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, SmbPth, "SMB_PTH") + def test_drupal_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Drupal, "Drupal_exploiter") + def test_elastic_exploiter(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, Elastic, "Elastic_exploiter") @@ -159,6 +167,22 @@ class TestMonkeyBlackbox: 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"] + 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()) + ExploitationTest( + name=test_name, + island_client=island_client, + raw_config=raw_config, + analyzers=[analyzer], + timeout=DEFAULT_TIMEOUT_SECONDS, + log_handler=log_handler).run() + @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): """ diff --git a/envs/monkey_zoo/blackbox/utils/README.md b/envs/monkey_zoo/blackbox/utils/README.md new file mode 100644 index 000000000..69a6b8930 --- /dev/null +++ b/envs/monkey_zoo/blackbox/utils/README.md @@ -0,0 +1,13 @@ +# BlackBox utility scripts + +## Config generation script + +This script is used to generate config files for manual tests. +Config file will be generated according to the templates in `envs/monkey_zoo/blackbox/config_templates`. + +1. Reset the Island config to contain default configuration. +2. Run `envs/monkey_zoo/blackbox/utils/config_generation_script.py island_ip:5000` to populate +`envs/monkey_zoo/blackbox/utils/generated_configs` directory with configuration files. + +!! It's important to target the Island you'll be testing, because configs contain Island's IPs +in the configuration !! diff --git a/envs/monkey_zoo/blackbox/utils/config_generation_script.py b/envs/monkey_zoo/blackbox/utils/config_generation_script.py new file mode 100644 index 000000000..603e9fe4d --- /dev/null +++ b/envs/monkey_zoo/blackbox/utils/config_generation_script.py @@ -0,0 +1,78 @@ +import argparse +import pathlib +from typing import Type + +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 +from envs.monkey_zoo.blackbox.config_templates.hadoop import Hadoop +from envs.monkey_zoo.blackbox.config_templates.mssql import Mssql +from envs.monkey_zoo.blackbox.config_templates.performance import Performance +from envs.monkey_zoo.blackbox.config_templates.shellshock import ShellShock +from envs.monkey_zoo.blackbox.config_templates.smb_mimikatz import SmbMimikatz +from envs.monkey_zoo.blackbox.config_templates.smb_pth import SmbPth +from envs.monkey_zoo.blackbox.config_templates.ssh import Ssh +from envs.monkey_zoo.blackbox.config_templates.struts2 import Struts2 +from envs.monkey_zoo.blackbox.config_templates.tunneling import Tunneling +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, +) + +DST_DIR_NAME = "generated_configs" +DST_DIR_PATH = pathlib.Path(pathlib.Path(__file__).parent.absolute(), DST_DIR_NAME) + +parser = argparse.ArgumentParser(description="Generate config files.") +parser.add_argument( + "island_ip", + metavar="IP:PORT", + help="Island endpoint. Example: 123.123.123.123:5000", +) + +args = parser.parse_args() +island_client = MonkeyIslandClient(args.island_ip) + + +CONFIG_TEMPLATES = [ + Elastic, + Hadoop, + Mssql, + Performance, + ShellShock, + SmbMimikatz, + SmbPth, + Ssh, + Struts2, + Tunneling, + Weblogic, + WmiMimikatz, + WmiPth, + Zerologon, + Drupal, +] + + +def generate_templates(): + for template in CONFIG_TEMPLATES: + save_template_as_config(template) + + +def save_template_as_config(template: Type[ConfigTemplate]): + file_path = pathlib.Path(DST_DIR_PATH, f"{template.__name__}.conf") + file_contents = IslandConfigParser.get_raw_config(template, island_client) + save_to_file(file_path, file_contents) + + +def save_to_file(file_path, contents): + with open(file_path, "w") as file: + file.write(contents) + + +if __name__ == "__main__": + generate_templates() diff --git a/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore b/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore new file mode 100644 index 000000000..9c558e357 --- /dev/null +++ b/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore @@ -0,0 +1 @@ +. diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf index a402842b8..866a4f174 100644 --- a/envs/monkey_zoo/terraform/images.tf +++ b/envs/monkey_zoo/terraform/images.tf @@ -89,6 +89,10 @@ data "google_compute_image" "zerologon-25" { name = "zerologon-25" project = local.monkeyzoo_project } +data "google_compute_image" "drupal-28" { + name = "drupal-28" + project = local.monkeyzoo_project +} data "google_compute_image" "island-linux-250" { name = "island-linux-250" project = local.monkeyzoo_project diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index 6c3a49b2e..5eabc160b 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -447,6 +447,21 @@ resource "google_compute_instance_from_template" "zerologon-25" { } } +resource "google_compute_instance_from_template" "drupal-28" { + name = "${local.resource_prefix}drupal-28" + source_instance_template = local.default_windows + boot_disk{ + initialize_params { + image = data.google_compute_image.drupal-28.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.2.28" + } +} + resource "google_compute_instance_from_template" "island-linux-250" { name = "${local.resource_prefix}island-linux-250" machine_type = "n1-standard-2" diff --git a/monkey/common/cloud/scoutsuite b/monkey/common/cloud/scoutsuite deleted file mode 160000 index 9de1e78ba..000000000 --- a/monkey/common/cloud/scoutsuite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9de1e78ba475f925c66c5b645564ec9eb08e2309 diff --git a/monkey/monkey_island/cc/services/config_schema/config_value_paths.py b/monkey/common/config_value_paths.py similarity index 100% rename from monkey/monkey_island/cc/services/config_schema/config_value_paths.py rename to monkey/common/config_value_paths.py diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index 214e6d108..d9ad573b1 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,5 +1,8 @@ # abstract, static method decorator # noinspection PyPep8Naming +from typing import List + + class abstractstatic(staticmethod): __slots__ = () @@ -8,3 +11,10 @@ class abstractstatic(staticmethod): function.__isabstractmethod__ = True __isabstractmethod__ = True + + +def get_value_from_dict(dict_data: dict, path: List[str]): + current_data = dict_data + for key in path: + current_data = current_data[key] + return current_data diff --git a/monkey/common/version.py b/monkey/common/version.py index c4e38239e..5e8dd4bf4 100644 --- a/monkey/common/version.py +++ b/monkey/common/version.py @@ -3,7 +3,7 @@ import argparse from pathlib import Path MAJOR = "1" -MINOR = "9" +MINOR = "10" PATCH = "0" build_file_path = Path(__file__).parent.joinpath("BUILD") with open(build_file_path, "r") as build_file: diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index cb7be181d..9b374c9f1 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -134,7 +134,9 @@ class MonkeyDrops(object): 'monkey_commandline': inner_monkey_cmdline} monkey_process = subprocess.Popen(monkey_cmdline, shell=True, - stdin=None, stdout=None, stderr=None, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True, creationflags=DETACHED_PROCESS) LOG.info("Executed monkey process (PID=%d) with command line: %s", @@ -145,6 +147,8 @@ class MonkeyDrops(object): LOG.warning("Seems like monkey died too soon") def cleanup(self): + 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 \ @@ -166,5 +170,7 @@ class MonkeyDrops(object): 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") except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/exploit/drupal.py b/monkey/infection_monkey/exploit/drupal.py index 5872f4703..04b0ce431 100644 --- a/monkey/infection_monkey/exploit/drupal.py +++ b/monkey/infection_monkey/exploit/drupal.py @@ -36,6 +36,7 @@ class DrupalExploiter(WebRCE): 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 return exploit_config def add_vulnerable_urls(self, potential_urls, stop_checking=False): diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index f5af73d43..3a5c5619f 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -252,9 +252,12 @@ class InfectionMonkey(object): def collect_system_info_if_configured(self): LOG.debug("Calling for system info collection") - system_info_collector = SystemInfoCollector() - system_info = system_info_collector.get_info() - SystemInfoTelem(system_info).send() + try: + system_info_collector = SystemInfoCollector() + system_info = system_info_collector.get_info() + SystemInfoTelem(system_info).send() + except Exception as e: + LOG.exception(f"Exception encountered during system info collection: {str(e)}") def shutdown_by_not_alive_config(self): if not WormConfiguration.alive: diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 51bd4bb83..6248f4d2b 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -1,24 +1,24 @@ # -*- mode: python -*- import os -import sys import platform - +import sys __author__ = 'itay.mizeretz' +from PyInstaller.utils.hooks import collect_data_files + block_cipher = None def main(): + print(collect_data_files('policyuniverse')) a = Analysis(['main.py'], pathex=['..'], hiddenimports=get_hidden_imports(), hookspath=['./pyinstaller_hooks'], runtime_hooks=None, binaries=None, - datas=[ - ("../common/BUILD", "/common") - ], + datas=[("../common/BUILD", "/common")], excludes=None, win_no_prefer_redirects=None, win_private_assemblies=None, @@ -48,7 +48,7 @@ def is_windows(): def is_32_bit(): - return sys.maxsize <= 2**32 + return sys.maxsize <= 2 ** 32 def get_bin_folder(): @@ -79,7 +79,10 @@ def get_linux_only_binaries(): def get_hidden_imports(): - return ['_cffi_backend', 'queue', '_mssql'] if is_windows() else ['_cffi_backend','_mssql'] + imports = ['_cffi_backend', '_mssql'] + if is_windows(): + imports.append('queue') + return imports def get_sc_binaries(): @@ -94,15 +97,15 @@ def get_traceroute_binaries(): def get_monkey_filename(): name = 'monkey-' if is_windows(): - name = name+"windows-" + name = name + "windows-" else: - name = name+"linux-" + name = name + "linux-" if is_32_bit(): - name = name+"32" + name = name + "32" else: - name = name+"64" + name = name + "64" if is_windows(): - name = name+".exe" + name = name + ".exe" return name diff --git a/monkey/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py index 26e362a5f..86c48cbde 100644 --- a/monkey/infection_monkey/network/httpfinger.py +++ b/monkey/infection_monkey/network/httpfinger.py @@ -43,8 +43,8 @@ class HTTPFinger(HostFinger): LOG.info("Port %d is open on host %s " % (port[0], host)) break # https will be the same on the same port except Timeout: - pass + LOG.debug(f"Timout while requesting headers from {url}") except ConnectionError: # Someone doesn't like us - pass + LOG.debug(f"Connection error while requesting headers from {url}") return True 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 175d6b215..dd723c14d 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -15,10 +15,6 @@ LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' -# Default commands for executing PBA file and then removing it -DEFAULT_LINUX_COMMAND = "chmod +x {0} ; {0} ; rm {0}" -DEFAULT_WINDOWS_COMMAND = "{0} & del {0}" - DIR_CHANGE_WINDOWS = 'cd %s & ' DIR_CHANGE_LINUX = 'cd %s ; ' @@ -31,30 +27,23 @@ class UsersPBA(PBA): def __init__(self): super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION) self.filename = '' + if not is_windows_os(): # Add linux commands to PBA's if WormConfiguration.PBA_linux_filename: + 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.filename = WormConfiguration.PBA_linux_filename - else: - file_path = os.path.join(get_monkey_dir_path(), WormConfiguration.PBA_linux_filename) - self.command = DEFAULT_LINUX_COMMAND.format(file_path) - self.filename = WormConfiguration.PBA_linux_filename elif WormConfiguration.custom_PBA_linux_cmd: self.command = WormConfiguration.custom_PBA_linux_cmd else: # Add windows commands to PBA's if WormConfiguration.PBA_windows_filename: + 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.filename = WormConfiguration.PBA_windows_filename - else: - file_path = os.path.join(get_monkey_dir_path(), WormConfiguration.PBA_windows_filename) - self.command = DEFAULT_WINDOWS_COMMAND.format(file_path) - self.filename = WormConfiguration.PBA_windows_filename elif WormConfiguration.custom_PBA_windows_cmd: self.command = WormConfiguration.custom_PBA_windows_cmd diff --git a/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py b/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py new file mode 100644 index 000000000..83af6e00a --- /dev/null +++ b/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py @@ -0,0 +1,152 @@ +import pytest + +from infection_monkey.post_breach.actions.users_custom_pba import ( + DIR_CHANGE_LINUX, DIR_CHANGE_WINDOWS, UsersPBA) + +MONKEY_DIR_PATH = "/dir/to/monkey/" +CUSTOM_LINUX_CMD = "command-for-linux" +CUSTOM_LINUX_FILENAME = "filename-for-linux" +CUSTOM_WINDOWS_CMD = "command-for-windows" +CUSTOM_WINDOWS_FILENAME = "filename-for-windows" + + +@pytest.fixture +def fake_monkey_dir_path(monkeypatch): + monkeypatch.setattr( + "infection_monkey.post_breach.actions.users_custom_pba.get_monkey_dir_path", + lambda: MONKEY_DIR_PATH, + ) + + +@pytest.fixture +def set_os_linux(monkeypatch): + monkeypatch.setattr( + "infection_monkey.post_breach.actions.users_custom_pba.is_windows_os", + lambda: False, + ) + + +@pytest.fixture +def set_os_windows(monkeypatch): + monkeypatch.setattr( + "infection_monkey.post_breach.actions.users_custom_pba.is_windows_os", + lambda: True, + ) + + +@pytest.fixture +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, + ) + monkeypatch.setattr( + "infection_monkey.config.WormConfiguration.PBA_linux_filename", + CUSTOM_LINUX_FILENAME, + ) + return UsersPBA() + + +def test_command_linux_custom_file_and_cmd( + mock_UsersPBA_linux_custom_file_and_cmd, +): + expected_command = f"cd {MONKEY_DIR_PATH} ; {CUSTOM_LINUX_CMD}" + assert mock_UsersPBA_linux_custom_file_and_cmd.command == expected_command + + +@pytest.fixture +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, + ) + monkeypatch.setattr( + "infection_monkey.config.WormConfiguration.PBA_windows_filename", + CUSTOM_WINDOWS_FILENAME, + ) + return UsersPBA() + + +def test_command_windows_custom_file_and_cmd( + mock_UsersPBA_windows_custom_file_and_cmd, +): + expected_command = f"cd {MONKEY_DIR_PATH} & {CUSTOM_WINDOWS_CMD}" + assert mock_UsersPBA_windows_custom_file_and_cmd.command == expected_command + + +@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.PBA_linux_filename", + CUSTOM_LINUX_FILENAME, + ) + return UsersPBA() + + +def test_command_linux_custom_file(mock_UsersPBA_linux_custom_file): + expected_command = "" + assert mock_UsersPBA_linux_custom_file.command == expected_command + + +@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 + ) + monkeypatch.setattr( + "infection_monkey.config.WormConfiguration.PBA_windows_filename", + CUSTOM_WINDOWS_FILENAME, + ) + return UsersPBA() + + +def test_command_windows_custom_file(mock_UsersPBA_windows_custom_file): + expected_command = "" + assert mock_UsersPBA_windows_custom_file.command == expected_command + + +@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 + ) + return UsersPBA() + + +def test_command_linux_custom_cmd(mock_UsersPBA_linux_custom_cmd): + expected_command = CUSTOM_LINUX_CMD + assert mock_UsersPBA_linux_custom_cmd.command == expected_command + + +@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 + ) + return UsersPBA() + + +def test_command_windows_custom_cmd(mock_UsersPBA_windows_custom_cmd): + expected_command = CUSTOM_WINDOWS_CMD + assert mock_UsersPBA_windows_custom_cmd.command == expected_command diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index 0a1dbd282..069d1ce07 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -1,3 +1,4 @@ +cryptography==2.5 WinSys-3.x>=0.5.2 cffi>=1.14 ecdsa==0.15 @@ -15,3 +16,6 @@ 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/collectors/scoutsuite_collector/__init__.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py deleted file mode 100644 index 97e736b4b..000000000 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -import pkgutil -import sys -from pathlib import PurePath - -_scoutsuite_api_package = pkgutil.get_loader('common.cloud.scoutsuite.ScoutSuite.__main__') - - -def _add_scoutsuite_to_python_path(): - scoutsuite_path = PurePath(_scoutsuite_api_package.path).parent.parent.__str__() - sys.path.append(scoutsuite_path) - - -# Add ScoutSuite to python path because this way -# we don't need to change any imports in ScoutSuite code -_add_scoutsuite_to_python_path() diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py deleted file mode 100644 index 88ef32293..000000000 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py +++ /dev/null @@ -1,5 +0,0 @@ -import common.cloud.scoutsuite.ScoutSuite.api_run as scoutsuite_api - - -def run(*args, **kwargs): - return scoutsuite_api.run(*args, **kwargs) 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 c637e3593..79aabea56 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 @@ -1,8 +1,9 @@ import logging from typing import Union -import infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_api as scoutsuite_api -from common.cloud.scoutsuite.ScoutSuite.providers.base.provider import BaseProvider +import ScoutSuite.api_run +from ScoutSuite.providers.base.provider import BaseProvider + from common.cloud.scoutsuite_consts import CloudProviders from common.utils.exceptions import ScoutSuiteScanError from infection_monkey.config import WormConfiguration @@ -22,10 +23,10 @@ def scan_cloud_security(cloud_type: CloudProviders): def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]: - return scoutsuite_api.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/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 38feb6815..8a53898c7 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -1,5 +1,5 @@ import logging -import os +import subprocess import sys from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR @@ -46,16 +46,21 @@ class WindowsInfoCollector(InfoCollector): return self.info def get_installed_packages(self): - LOG.info('getting installed packages') - self.info["installed_packages"] = os.popen("dism /online /get-packages").read() - self.info["installed_features"] = os.popen("dism /online /get-features").read() + 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') + + features = subprocess.check_output("dism /online /get-features", shell=True) + self.info["installed_features"] = features.decode('utf-8', errors='ignore') + 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') + LOG.debug('Finished get_wmi_info') def get_mimikatz_info(self): LOG.info("Gathering mimikatz info") diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index 16cf47bdd..ba112f8b9 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -1,5 +1,5 @@ -from common.cloud.scoutsuite.ScoutSuite.output.result_encoder import ScoutJsonEncoder -from common.cloud.scoutsuite.ScoutSuite.providers.base.provider import BaseProvider +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 diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index c53c04caa..c7fd0006f 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -7,6 +7,7 @@ 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 @@ -145,9 +146,11 @@ def init_api_resources(api): 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') def init_app(mongo_url): diff --git a/monkey/monkey_island/cc/resources/test/telemetry_test.py b/monkey/monkey_island/cc/resources/test/telemetry_test.py new file mode 100644 index 000000000..29108070e --- /dev/null +++ b/monkey/monkey_island/cc/resources/test/telemetry_test.py @@ -0,0 +1,13 @@ +import flask_restful +from bson import json_util +from flask import request + +from monkey_island.cc.database import mongo +from monkey_island.cc.resources.auth.auth import jwt_required + + +class TelemetryTest(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))} 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 c3fcd03e8..3b18be488 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py @@ -4,7 +4,7 @@ from monkey_island.cc.services.config import ConfigService __author__ = "VakarisZ" -from monkey_island.cc.services.config_schema.config_value_paths import CURRENT_SERVER_PATH +from common.config_value_paths import CURRENT_SERVER_PATH class T1065(AttackTechnique): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index b4370a63b..390380131 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -14,10 +14,10 @@ from monkey_island.cc.services.config_schema.config_schema import SCHEMA __author__ = "itay.mizeretz" -from monkey_island.cc.services.config_schema.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 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) logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index 156dae7ad..f6b3523f0 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -159,7 +159,8 @@ INTERNAL = { 8080, 443, 8008, - 7001 + 7001, + 9200 ], "description": "List of ports the monkey will check if are being used for HTTP" }, @@ -181,7 +182,6 @@ INTERNAL = { 443, 8008, 3306, - 9200, 7001, 8088 ], diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index 01d463672..82a394b65 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -11,33 +11,39 @@ MONKEY = { "type": "object", "properties": { "custom_PBA_linux_cmd": { - "title": "Linux post breach command", + "title": "Linux post-breach command", "type": "string", "default": "", - "description": "Linux command to be executed after breaching." + "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\"" }, "PBA_linux_file": { - "title": "Linux post breach file", + "title": "Linux post-breach file", "type": "string", "format": "data-url", - "description": "File to be executed after breaching. " - "If you want custom execution behavior, " - "specify it in 'Linux post breach command' field. " + "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." }, "custom_PBA_windows_cmd": { - "title": "Windows post breach command", + "title": "Windows post-breach command", "type": "string", "default": "", - "description": "Windows command to be executed after breaching." + "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\"" }, "PBA_windows_file": { - "title": "Windows post breach file", + "title": "Windows post-breach file", "type": "string", "format": "data-url", - "description": "File to be executed after breaching. " - "If you want custom execution behavior, " - "specify it in 'Windows post breach command' field. " + "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." }, "PBA_windows_filename": { diff --git a/monkey/monkey_island/cc/services/configuration/utils.py b/monkey/monkey_island/cc/services/configuration/utils.py index 48857e2e3..493d5af03 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 monkey_island.cc.services.config_schema.config_value_paths import INACCESSIBLE_SUBNETS_PATH +from common.config_value_paths import INACCESSIBLE_SUBNETS_PATH def get_config_network_segments_as_subnet_groups(): diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 5970a33b7..a23aa6d85 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -12,9 +12,9 @@ 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 monkey_island.cc.services.config_schema.config_value_paths import (EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCAN_PATH, - PASSWORD_LIST_PATH, SUBNET_SCAN_LIST_PATH, - USER_LIST_PATH) +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.node import NodeService from monkey_island.cc.services.reporting.pth_report import PTHReportService diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index 8ee4737e8..9160861ea 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -3,6 +3,7 @@ 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.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 @@ -13,14 +14,14 @@ def process_scoutsuite_telemetry(telemetry_json): 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) + create_scoutsuite_findings(scoutsuite_data[SERVICES]) update_data(telemetry_json) -def create_scoutsuite_findings(scoutsuite_data): +def create_scoutsuite_findings(cloud_services: dict): for finding in SCOUTSUITE_FINDINGS: for rule in finding.rules: - rule_data = RuleParser.get_rule_data(scoutsuite_data, rule) + rule_data = RuleParser.get_rule_data(cloud_services, rule) rule = ScoutSuiteRuleService.get_rule_from_rule_data(rule_data) ScoutSuiteZTFindingService.process_rule(finding, rule) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py deleted file mode 100644 index e8a36338b..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import pkgutil -import sys -from pathlib import PurePath - -_scoutsuite_api_package = pkgutil.get_loader('common.cloud.scoutsuite.ScoutSuite.__main__') - - -def _add_scoutsuite_to_python_path(): - scoutsuite_path = PurePath(_scoutsuite_api_package.path).parent.parent.__str__() - sys.path.append(scoutsuite_path) - - -_add_scoutsuite_to_python_path() 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 e07431541..935f1c989 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 @@ -1,7 +1,6 @@ from enum import Enum -import dpath.util - +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 @@ -23,7 +22,7 @@ class RuleParser: @staticmethod def get_rule_data(scoutsuite_data: dict, rule_name: Enum) -> dict: rule_path = RuleParser._get_rule_path(rule_name) - return dpath.util.get(scoutsuite_data, rule_path) + return get_value_from_dict(scoutsuite_data, rule_path) @staticmethod def _get_rule_path(rule_name: Enum): 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 b4767124b..ee7f7c38b 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 @@ -3,7 +3,7 @@ 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, SERVICES, SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import FINDINGS, SERVICE_TYPES class AbstractRulePathCreator(ABC): @@ -21,4 +21,4 @@ class AbstractRulePathCreator(ABC): @classmethod def build_rule_path(cls, rule_name: Enum) -> List[str]: assert(rule_name in cls.supported_rules) - return [SERVICES, cls.service_type.value, FINDINGS, rule_name.value] + return [cls.service_type.value, FINDINGS, rule_name.value] 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 index 5a7572eb0..afe14c54c 100644 --- 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 @@ -4,6 +4,7 @@ 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 @@ -28,9 +29,9 @@ EXPECTED_RESULT = {'description': 'Security Group Opens All Ports to All', def test_get_rule_data(): # Test proper parsing of the raw data to rule - results = RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA, ALL_PORTS_OPEN) + 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, ExampleRules.NON_EXSISTENT_RULE) + 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 dc3f8d5ee..701598168 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 @@ -6,7 +6,7 @@ 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 monkey_island.cc.services.config_schema.config_value_paths import AWS_KEYS_PATH +from common.config_value_paths import AWS_KEYS_PATH def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: @@ -14,7 +14,7 @@ def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: if is_aws_keys_setup(): return True, "AWS keys already setup." - import common.cloud.scoutsuite.ScoutSuite.providers.aws.authentication_strategy as auth_strategy + 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. " diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index 24e700ce6..c35e55a8f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -6,7 +6,7 @@ import dpath.util from monkey_island.cc.database import mongo from monkey_island.cc.server_utils import encryptor from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.config_schema.config_value_paths import AWS_KEYS_PATH +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 diff --git a/monkey/monkey_island/cc/test_common/profiling/README.md b/monkey/monkey_island/cc/test_common/profiling/README.md index 1c1446b2f..d0cb92bfa 100644 --- a/monkey/monkey_island/cc/test_common/profiling/README.md +++ b/monkey/monkey_island/cc/test_common/profiling/README.md @@ -2,7 +2,7 @@ To profile specific methods on island a `@profile(sort_args=['cumulative'], print_args=[100])` decorator can be used. -Use it as any other decorator. After decorated method is used, a file will appear in a +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 diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 618f02d5e..1cc781c03 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.9.0", + "version": "1.10.0", "name": "infection-monkey", "description": "Infection Monkey C&C UI", "scripts": { diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/AdvancedMultiSelect.js b/monkey/monkey_island/cc/ui/src/components/ui-components/AdvancedMultiSelect.js index 8503a74fc..193cb40b0 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/AdvancedMultiSelect.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/AdvancedMultiSelect.js @@ -43,7 +43,7 @@ class AdvancedMultiSelect extends React.Component { infoPaneParams: getDefaultPaneParams( this.infoPaneRefString, this.registry, - this.isUnsafeOptionSelected(this.props.value) + this.isUnsafeOptionSelected(props.value) ) }; } @@ -94,9 +94,11 @@ class AdvancedMultiSelect extends React.Component { } setMasterCheckboxState(selectValues) { - this.setState(() => ({ - masterCheckboxState: this.getMasterCheckboxState(selectValues) - })); + let newState = this.getMasterCheckboxState(selectValues); + + if (newState != this.state.masterCheckboxState) { + this.setState({masterCheckboxState: newState}); + } } getMasterCheckboxState(selectValues) { @@ -162,11 +164,12 @@ class AdvancedMultiSelect extends React.Component { render() { const { - schema, + autofocus, id, - required, multiple, - autofocus + required, + schema, + value } = this.props; return ( @@ -179,7 +182,7 @@ class AdvancedMultiSelect extends React.Component { + selectedValues={value} enumOptions={this.enumOptions}/> ); } + + componentDidUpdate(_prevProps) { + this.setMasterCheckboxState(this.props.value); + } } export default AdvancedMultiSelect; diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 3cb3a4e42..b5be47a88 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -26,5 +26,6 @@ tqdm>=4.47 virtualenv>=20.0.26 werkzeug>=1.0.1 wheel>=0.34.2 +git+https://github.com/guardicode/ScoutSuite -pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability +pyjwt==1.7 # not directly required, pinned by Snyk to avoid a vulnerability