diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1b35c914325..caa7695b4c8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -28,6 +28,7 @@ repos:
- id: check-json
- id: check-merge-conflict
- id: detect-private-key
+ exclude: "envs/monkey_zoo/blackbox/expected_credentials.py"
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/eslint/eslint
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8dac4690b0a..e1c7fa97ca0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,12 +5,54 @@ file.
The format is based on [Keep a
Changelog](https://keepachangelog.com/en/1.0.0/).
-## [2.1.1 - 2023-06-21]
+## [2.3.0 - 2023-09-19]
+### Added
+- Ability to filter Agent events by timestamp. #3397
+- Ability to filter Agent events by tag. #3396
+- Provide a common server object to the plugins that can be used to serve agent
+ binaries to the exploited machine over HTTP. #3410
+- CPUConsumptionEvent. #3411
+- RAMConsumptionEvent. #3411
+- HTTPRequestEvent. #3411
+- DefacementEvent. #1247
+- RDP exploiter plugin. #3425
+- A cryptojacker payload to simulate cryptojacker attacks. #3411
+- `PUT /api/install-agent-plugin`. #3417
+- `GET /api/agent-plugins/installed/manifests`. #3424
+- `GET /api/agent-plugins/available/index`. #3420
+- `POST /api/uninstall-agent-plugin` # 3422
+- Chrome credentials collector plugin. #3426
+- A plugin interface for payloads. #3390
+- The ability to install plugins from an online repository. #3413, #3418, #3616
+- Support for SMBv2+ in SMB exploiter. #3577
+- A UI for uploading agent plugin archives. #3417, #3611
+
+### Changed
+- Plugin source is now gzipped. #3392
+- Allowed characters in Agent event tags. #3399, #3676
+- Hard-coded Log4Shell exploiter to a plugin. #3388
+- Hard-coded SSH exploiter to a plugin. #3170
+- Identities and secrets can be associated when configuring credentials in the
+ UI. #3393
+- Hard-coded ransomware payload to a plugin. #3391
+- Text on the registration screen to improve clarity. #1984
### Fixed
-- A configuration issue that prevents Mimikatz from being used. #3433
+- Agent hanging if plugins do not shut down. #3557
+- WMI exploiter hanging. #3543
+- Discovered network services are displayed in reports. #3000
+
+### Removed
+- Island mode configuration. #3400
+- Agent plugins from Island packages. #3616
+
+### Security
+- Fixed a ReDoS issue when validating ransomware file extensions. #3391
+## [2.2.1 - 2023-06-21]
+### Fixed
+- A configuration issue that prevents Mimikatz from being used. #3433
## [2.2.0 - 2023-05-31]
### Added
@@ -37,6 +79,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- Hard-coded PowerShell exploiter to a plugin. #3165
### Fixed
+- Agents were being caught by Windows Defender (and other antiviruses). #1289
- Plugins are now being checked for local OS compatibility. #3275
- A bug that could prevent multi-hop propagation via SMB. #3173
- Exceptions being raised when WMI and Zerologon are used together. #1774
@@ -45,8 +88,8 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- A bug in URL sanitization. #3318
### Security
-- Fixes a bug where OTPs can be leaked by the hadoop exploiter. #3296
-- Fixes pypykatz leaking sensitive information into the logs. #3168, #3293
+- Fixed a bug where OTPs can be leaked by the hadoop exploiter. #3296
+- Fixed pypykatz leaking sensitive information into the logs. #3168, #3293
## [2.1.0] - 2023-04-19
### Added
diff --git a/build_scripts/appimage/appimage.sh b/build_scripts/appimage/appimage.sh
index 3f06f0cf020..7ee85985649 100755
--- a/build_scripts/appimage/appimage.sh
+++ b/build_scripts/appimage/appimage.sh
@@ -2,7 +2,7 @@
# Changes: python version
LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
-PYTHON_VERSION="3.11.4"
+PYTHON_VERSION="3.11.5"
PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.11/python${PYTHON_VERSION}-cp311-cp311-manylinux2014_x86_64.AppImage"
APPIMAGE_DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
APPDIR="$APPIMAGE_DIR/squashfs-root"
diff --git a/build_scripts/appimage/server_config.json.standard b/build_scripts/appimage/server_config.json.standard
index 889654ea2b2..0fee130ccb5 100644
--- a/build_scripts/appimage/server_config.json.standard
+++ b/build_scripts/appimage/server_config.json.standard
@@ -1,9 +1,6 @@
{
"data_dir": "~/.monkey_island",
"log_level": "DEBUG",
- "environment": {
- "server_config": "password"
- },
"mongodb": {
"start_mongodb": true
}
diff --git a/build_scripts/build_agent_linux.sh b/build_scripts/build_agent_linux.sh
index 0a55907abb8..830fd7e33ed 100755
--- a/build_scripts/build_agent_linux.sh
+++ b/build_scripts/build_agent_linux.sh
@@ -69,7 +69,7 @@ cd /src/monkey/infection_monkey &&
"
build_commands="
-pipenv sync &&
+SKIP_CYTHON=1 PIP_NO_BINARY=pydantic pipenv sync &&
pipenv run bash build_linux.sh &&
echo 'Copying agent binary to \"${DIST_DIR}\"' &&
cp dist/monkey-linux-64 /dist
diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md
index 112deeb402e..5b7d53b2781 100644
--- a/docs/content/FAQ/_index.md
+++ b/docs/content/FAQ/_index.md
@@ -163,6 +163,8 @@ is sent to the update server to fetch the latest version number and a
download link for it. This information is used by the Monkey Island to
suggest an update if one is available.
+1. When you install a plugin it is downloaded from our official repository.
+
## Logging and how to find logs
### Downloading logs
diff --git a/docs/content/development/contribute-documentation.md b/docs/content/development/contribute-documentation.md
index bd9e7fd6800..8be24cb4b83 100644
--- a/docs/content/development/contribute-documentation.md
+++ b/docs/content/development/contribute-documentation.md
@@ -9,7 +9,7 @@ tags: ["contribute"]
The `/docs` folder contains the Infection Monkey Documentation site.
The site is based on [Hugo](https://gohugo.io/) and the [learn](https://themes.gohugo.io/theme/hugo-theme-learn/en) theme.
-The Hugo version being used is 0.92.0.
+The Hugo version being used is [v0.92.0](https://github.com/gohugoio/hugo/releases/tag/v0.92.0).
- [Directory structure](#directory-structure)
- [content](#content)
diff --git a/docs/content/development/setup-development-environment.md b/docs/content/development/setup-development-environment.md
index 0b1c3bcfd93..aa5d4b9c167 100644
--- a/docs/content/development/setup-development-environment.md
+++ b/docs/content/development/setup-development-environment.md
@@ -8,21 +8,32 @@ tags: ["contribute"]
## Deployment scripts
-To set up a development environment using scripts, look at the readme under [`/deployment_scripts`](https://github.com/guardicore/monkey/blob/develop/deployment_scripts). If you want to set it up manually or run into problems, keep reading.
+To set up a development environment using scripts, look at the readme under [`/deployment_scripts`](https://github.com/guardicore/monkey/blob/master/deployment_scripts). If you want to set it up manually or run into problems, keep reading.
## The Infection Monkey Agent
-The Agent (which we sometimes refer to as the Monkey) is a single Python project under the [`infection_monkey`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey) folder. The Infection Monkey Agent was built for Python 3.11. You can get it up and running by setting up a [virtual environment](https://docs.python-guide.org/dev/virtualenvs/) and installing the requirements listed in the [`requirements.txt`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/requirements.txt) inside it.
+The Agent (which we sometimes refer to as the Monkey) is a single Python project under the [`infection_monkey`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey) folder. The Infection Monkey Agent was built for Python 3.11. You can get it up and running by using[`pipenv`](https://pypi.org/project/pipenv/).
-In order to compile the Infection Monkey for distribution by the Monkey Island, you'll need to run the instructions listed in the [`readme.txt`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/readme.txt) on each supported environment.
+Follow these steps to install the requirements-
+
+- Create and activate your virtual environment
+- Run
+```bash
+ pip install -U pip
+ pip install pipenv
+```
+- Do a `find` to find all files named 'Pipfile'
+- For each `Pipfile`, cd to that directory and run `pipenv sync`
+
+In order to compile the Infection Monkey for distribution by the Monkey Island, you'll need to run the instructions listed in the [`readme.txt`](https://github.com/guardicore/monkey/tree/master/monkey/infection_monkey) on each supported environment.
This means setting up an environment with Linux 64-bit with Python installed and a Windows 64-bit machine with developer tools, along with 64-bit Python versions.
## The Monkey Island
-The Monkey Island is a Python backend React frontend project. Similar to the Agent, the backend's requirements are listed in the matching [`requirements.txt`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/requirements.txt).
+The Monkey Island is a Python backend React frontend project. Similar to the Agent, the backend can be installed similar to `infection_monkey`.
-To setup a working front environment, run the instructions listed in the [`readme.txt`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/readme.txt)
+To setup a working front environment, run the instructions listed in the [`readme.txt`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/readme.md)
## Pre-commit
diff --git a/docs/content/reference/credentials_collectors/_index.md b/docs/content/reference/credentials_collectors/_index.md
new file mode 100644
index 00000000000..b669792b3b4
--- /dev/null
+++ b/docs/content/reference/credentials_collectors/_index.md
@@ -0,0 +1,15 @@
+---
+title: "Credentials Collectors"
+date: 2023-09-13T16:35:19+05:30
+weight: 100
+chapter: true
+pre: ' '
+tags: ["reference", "credentials collectors"]
+---
+
+
+# Credentials Collectors
+
+Infection Monkey has multiple ways to steal credentials from compromised machines:
+
+{{% children %}}
diff --git a/docs/content/reference/credentials_collectors/chrome.md b/docs/content/reference/credentials_collectors/chrome.md
new file mode 100644
index 00000000000..25c443cadfd
--- /dev/null
+++ b/docs/content/reference/credentials_collectors/chrome.md
@@ -0,0 +1,12 @@
+---
+title: "Chrome"
+date: 2023-09-13T16:35:11+05:30
+tags: ["credentials collector", "chrome", "linux", "windows"]
+weight: 1
+---
+
+## Description
+
+The Chrome Credentials Collector steals saved credentials from Chrome-based browsers.
+On Linux, it targets Google Chrome and Chromium. On Windows, it targets Google Chrome
+and Microsoft Edge.
diff --git a/docs/content/reference/credentials_collectors/mimikatz.md b/docs/content/reference/credentials_collectors/mimikatz.md
new file mode 100644
index 00000000000..72da7b00869
--- /dev/null
+++ b/docs/content/reference/credentials_collectors/mimikatz.md
@@ -0,0 +1,12 @@
+---
+title: "Mimikatz"
+date: 2023-09-13T16:51:44+05:30
+tags: ["credentials collector", "mimikatz", "windows"]
+weight: 2
+---
+
+## Description
+
+The Mimikatz Credentials Collector uses [pypykatz](https://github.com/skelsec/pypykatz)
+(a pure-Python implementation of [mimikatz](https://github.com/gentilkiwi/mimikatz))
+to steal credentials from Windows Credential Manager.
diff --git a/docs/content/reference/credentials_collectors/ssh.md b/docs/content/reference/credentials_collectors/ssh.md
new file mode 100644
index 00000000000..4486c7a4efa
--- /dev/null
+++ b/docs/content/reference/credentials_collectors/ssh.md
@@ -0,0 +1,14 @@
+---
+title: "SSH"
+date: 2023-09-13T16:51:38+05:30
+tags: ["credentials collector", "ssh", "linux"]
+weight: 3
+---
+
+## Description
+
+The SSH Credentials Collector steals SSH keys from Linux users.
+
+For all users on the system, it locates the `/home//.ssh`
+directory and steals keypairs from it. The supported private key
+encryption formats are RSA, DSA, EC, and ECDSA.
diff --git a/docs/content/reference/exploiters/RDP.md b/docs/content/reference/exploiters/RDP.md
new file mode 100644
index 00000000000..13f16cf749d
--- /dev/null
+++ b/docs/content/reference/exploiters/RDP.md
@@ -0,0 +1,39 @@
+---
+title: "RDP"
+date: 2023-08-08T13:29:21+03:00
+draft: false
+tags: ["exploit", "windows"]
+---
+
+### Description
+
+This exploiter uses brute force to propagate through the network via Remote
+Desktop Protocol (RDP). For more information about RDP, see [Microsoft's
+documentation](https://learn.microsoft.com/en-us/windows/win32/termserv/remote-desktop-protocol).
+
+
+#### Credentials used
+
+The RDP exploiter can be run from both Linux and Windows attackers and will
+use configured or stolen credentials to propagate. Different combinations of
+credentials are attempted in the following order:
+
+1. **Brute force usernames and passwords** - The exploiter will attempt to use
+ all combinations of usernames and passwords that were set in the
+ [configuration]({{< ref "/usage/configuration/credentials" >}}) or stolen by
+ a credentials collector.
+
+
+1. **Brute force usernames and NT hashes** - The exploiter will attempt to use
+ all combinations of usernames and NT Hashes that were set in the [configuration]({{< ref
+ "/usage/configuration/credentials" >}}) or stolen by a credentials collector.
+
+ This only works on Windows 8.1 and Windows Server 2012 R2. You can read more
+ [here](https://www.kali.org/blog/passing-hash-remote-desktop/).
+
+
+#### Securing Remote Desktop Protocol
+
+For information about remediating RDP-related security risks, see
+[Microsoft's
+guidance](https://www.microsoft.com/en-us/security/blog/2020/04/16/security-guidance-remote-desktop-adoption/).
diff --git a/docs/content/reports/ransomware.md b/docs/content/reports/ransomware.md
index 29c9b7663e6..40bd879fa29 100644
--- a/docs/content/reports/ransomware.md
+++ b/docs/content/reports/ransomware.md
@@ -7,13 +7,12 @@ description: "Provides information about ransomware simulation on your network"
---
{{% notice info %}}
-Check out [the Infection Monkey's ransomware simulation documentation]({{< ref
-"/usage/scenarios/ransomware-simulation" >}}) and [the documentation for other
+Check out [the documentation for other
available reports]({{< ref "/reports" >}}).
{{% /notice %}}
The Infection Monkey can be configured to [simulate a ransomware
-attack](/usage/scenarios/ransomware-simulation) on your network. After running,
+attack](/usage/ransomware-simulation) on your network. After running,
it generates a **Ransomware Report** that provides you with insight into how
ransomware might behave within your environment.
diff --git a/docs/content/reports/security.files/infection_monkey_security_report_example.pdf b/docs/content/reports/security.files/infection_monkey_security_report_example.pdf
deleted file mode 100644
index 37cbb0e9113..00000000000
Binary files a/docs/content/reports/security.files/infection_monkey_security_report_example.pdf and /dev/null differ
diff --git a/docs/content/reports/security.md b/docs/content/reports/security.md
index 353bf3f0571..9b9673081d6 100644
--- a/docs/content/reports/security.md
+++ b/docs/content/reports/security.md
@@ -10,9 +10,7 @@ description: "Provides actionable recommendations and insight into an attacker's
Check out [the documentation for other reports available in the Infection Monkey]({{< ref "/reports" >}}).
{{% /notice %}}
-The Infection Monkey's **Security Report** provides you with actionable recommendations and insight into an attacker's view of your network. You can download a PDF of an example report here:
-
-{{%attachments title="Download the PDF" pattern=".*(pdf)"/%}}
+The Infection Monkey's **Security Report** provides you with actionable recommendations and insight into an attacker's view of your network.
The report is split into the following categories:
diff --git a/docs/content/setup/aws.md b/docs/content/setup/aws.md
index 8fa319e0e31..91dd613d4b5 100644
--- a/docs/content/setup/aws.md
+++ b/docs/content/setup/aws.md
@@ -26,6 +26,8 @@ When ready, you can browse to the Infection Monkey running on the fresh deployme
To login to the machine, use *ubuntu* username.
+Once you have access to the Monkey Island server, check out the [getting started page]({{< ref "/usage/getting-started" >}}).
+
## Integration with AWS services
The Infection Monkey has built-in integrations with AWS that allows running Agents on EC2 instances.
diff --git a/docs/content/setup/azure.md b/docs/content/setup/azure.md
index c1d60cb58ea..68887c112da 100644
--- a/docs/content/setup/azure.md
+++ b/docs/content/setup/azure.md
@@ -28,6 +28,8 @@ you can browse to the Infection Monkey running on your fresh deployment at:
`https://{public-ip-address}:5000`
+Once you have access to the Monkey Island server, check out the [getting started page]({{< ref "/usage/getting-started" >}}).
+
## Upgrading
Currently, there's no "upgrade-in-place" option when a new version is released.
diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md
index dcd866c5d48..8120f5b3fa4 100644
--- a/docs/content/setup/docker.md
+++ b/docs/content/setup/docker.md
@@ -65,6 +65,8 @@ been signed by a private certificate authority.
After the Monkey Island docker container starts, you can access Monkey Island by pointing your browser at `https://localhost:5000`.
+Once you have access to the Monkey Island server, check out the [getting started page]({{< ref "/usage/getting-started" >}}).
+
## Configuring the server
You can configure the server by mounting a volume and specifying a
diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md
index 44438643613..d475c9bc53c 100644
--- a/docs/content/setup/linux.md
+++ b/docs/content/setup/linux.md
@@ -29,11 +29,11 @@ On Windows, AppImage can be run in WSL 2.
1. Make the AppImage package executable:
```bash
- chmod u+x InfectionMonkey-v2.2.0.AppImage
+ chmod u+x InfectionMonkey-v2.3.0.AppImage
```
1. Start Monkey Island by running the Infection Monkey AppImage package:
```bash
- ./InfectionMonkey-v2.2.0.AppImage
+ ./InfectionMonkey-v2.3.0.AppImage
```
If you get errors related to FUSE, you may need to install FUSE 2.X first:
@@ -43,7 +43,8 @@ On Windows, AppImage can be run in WSL 2.
```
More information about fixing FUSE-related errors can be found [here](https://docs.appimage.org/user-guide/troubleshooting/fuse.html).
1. Access the Monkey Island web UI by pointing your browser at
- `https://localhost:5000`.
+ `https://localhost:5000`. Once you have access to the Monkey Island server, check out the
+[getting started page]({{< ref "/usage/getting-started" >}}).
{{% notice info %}}
If you're prompted to delete your data directory and you're not sure what to
@@ -58,12 +59,12 @@ The Infection Monkey can be installed as a service and run on boot by running th
with the following parameters. This requires root permissions, so run `sudo -v` and enter your
password before running the script, if required.
```bash
-./InfectionMonkey-v2.2.0.AppImage service --install --user
+./InfectionMonkey-v2.3.0.AppImage service --install --user
```
To uninstall it, run:
```bash
-./InfectionMonkey-v2.2.0.AppImage service --uninstall
+./InfectionMonkey-v2.3.0.AppImage service --uninstall
```
{{% notice info %}}
@@ -77,7 +78,7 @@ You can configure the server by creating
a [server configuration file](../../reference/server_configuration) and
providing a path to it via command line parameters:
-`./InfectionMonkey-v2.2.0.AppImage --server-config="/path/to/server_config.json"`
+`./InfectionMonkey-v2.3.0.AppImage --server-config="/path/to/server_config.json"`
### Start Monkey Island with user-provided certificate
@@ -113,7 +114,7 @@ The server configuration file should look something like:
1. Start Monkey Island by running the Infection Monkey AppImage package:
```bash
- ./InfectionMonkey-v2.2.0.AppImage --server-config="/path/to/server_config.json"
+ ./InfectionMonkey-v2.3.0.AppImage --server-config="/path/to/server_config.json"
```
1. Access the Monkey Island web UI by pointing your browser at
@@ -134,7 +135,7 @@ The server configuration file should look something like:
1. Start Monkey Island by running the Infection Monkey AppImage package:
```bash
- ./InfectionMonkey-v2.2.0.AppImage --server-config="/path/to/server_config.json"
+ ./InfectionMonkey-v2.3.0.AppImage --server-config="/path/to/server_config.json"
```
1. Access the Monkey Island web UI by pointing your browser at
diff --git a/docs/content/setup/windows.md b/docs/content/setup/windows.md
index 860d931aac6..2855f68d0ca 100644
--- a/docs/content/setup/windows.md
+++ b/docs/content/setup/windows.md
@@ -27,7 +27,9 @@ do, see the [FAQ]({{< ref
"/faq/#i-updated-to-a-new-version-of-the-infection-monkey-and-im-being-asked-to-delete-my-existing-data-directory-why"
>}}) for more information.
{{% /notice %}}
->
+
+Once you have access to the Monkey Island server, check out the [getting started page]({{< ref "/usage/getting-started" >}}).
+
## Configuring the server
You can configure the server by editing [the configuration
diff --git a/docs/content/usage/cryptojacker-simulation.md b/docs/content/usage/cryptojacker-simulation.md
new file mode 100644
index 00000000000..177503e9edd
--- /dev/null
+++ b/docs/content/usage/cryptojacker-simulation.md
@@ -0,0 +1,45 @@
+---
+title: " Cryptojacker Simulation"
+date: 2022-08-09T13:51:13-04:00
+draft: false
+description: "Simulate a cryptojacking attack on your network and assess the potential damage."
+weight: 6
+pre: " "
+---
+
+The Infection Monkey is capable of simulating the behavior of a cryptojacker by
+using the CPU and memory of infected systems to perform cryptographic
+operations. It can also send network requests that imitate bitcoin mining
+network traffic.
+
+## Production safety
+
+This module is not considered to be safe for production environments because
+it can consume large amounts of CPU and RAM. If the module is configured to
+consume excessive amounts of CPU and RAM, it may cause some systems or services
+to become unstable. Users are advised to use caution when setting the CPU and
+memory utilization options so as not to negatively impact production
+environments.
+
+## Configuration options
+![Cryptojacker configuration](/images/island/configuration_page/cryptojacker_configuration.png "Cryptojacker configuration")
+### Duration
+This option controls how long the cryptojacking simulation will run. The
+simulation will automatically shut itself down after the configured time has
+elapsed.
+
+### CPU utilization
+The cryptojacking simulation will attempt to consume the specified percentage
+of a single CPU core.
+
+### Memory utilization
+The cryptojacking simulation will attempt to consume the specified percentage
+of system RAM. Note that an internal safeguard prevents this component from
+consuming more than 90% of the available RAM. Therefore, while specifying 100%
+is possible, this component has a theoretical upper limit that prevents it from
+consuming more than 90% of total system RAM.
+
+### Simulate bitcoin mining network traffic
+If enabled, the cryptojacking simulation will send bitcoin `getblocktemplate`
+requests via HTTP over the network to the Island. This can help verify that
+NIDSs are working properly.
diff --git a/docs/content/usage/file-checksums.md b/docs/content/usage/file-checksums.md
index a0a730f7a37..9b358a676bb 100644
--- a/docs/content/usage/file-checksums.md
+++ b/docs/content/usage/file-checksums.md
@@ -30,17 +30,22 @@ $ sha256sum
| Filename | Type | Version | SHA256 |
|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------|
-| monkey-linux-64 | Linux Agent | 2.2.1 | `333a9f5c32780aa32e822f95801a13263015afd3fa11d5e5b1c4fb0af30279fc` |
-| monkey-windows-64.exe | Windows Agent | 2.2.1 | `d49b233a31f808af4786b8cedd3772f8b79f28fe726915387542d5c8bed2fafc` |
-| InfectionMonkey-docker-v2.2.1.tgz | Docker | 2.2.1 | `21f84970ef69d21d779c43046a475cd8acefaaf16836f801d5c4c4ca987f86e8` |
-| InfectionMonkey-v2.2.1.AppImage | Linux Package | 2.2.1 | `cc9af5fe29cd978bbecafe454c9d722921870361fd1351c2447681b4cbe1b4a4` |
-| InfectionMonkey-v2.2.1.exe | Windows Installer | 2.2.1 | `74cd99dd087334ab7a87aafc5cc21faca48f32baa5bb38b51223434c548776b3` |
+| monkey-linux-64 | Linux Agent | 2.3.0 | `0a4d11edbd3b96053d84d9e5190f45c75bffd601a97dadfddefe2a8e1c189c37` |
+| monkey-windows-64.exe | Windows Agent | 2.3.0 | `30ccba0b6066c7a6179b6c00f81af1f8171dab34de27b7f08096c2fe016cd72d` |
+| InfectionMonkey-docker-v2.3.0.tgz | Docker | 2.3.0 | `35d92032582ea1a8bee8cfcc5a686389a6c39e8ab883c2ecd7c89548b5523653` |
+| InfectionMonkey-v2.3.0.AppImage | Linux Package | 2.3.0 | `8fc613a02c18c45d136753224b86a24740e18c0e3226786c92b93ca6be7d345d` |
+| InfectionMonkey-v2.3.0.exe | Windows Installer | 2.3.0 | `66f5b06f1c85c26393dd990e0539a22833a4455b0eed3a08f43dcafaaa92fa0c` |
## Older checksums
| Filename | Type | Version | SHA256 |
|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------|
+| monkey-linux-64 | Linux Agent | 2.2.1 | `333a9f5c32780aa32e822f95801a13263015afd3fa11d5e5b1c4fb0af30279fc` |
+| monkey-windows-64.exe | Windows Agent | 2.2.1 | `d49b233a31f808af4786b8cedd3772f8b79f28fe726915387542d5c8bed2fafc` |
+| InfectionMonkey-docker-v2.2.1.tgz | Docker | 2.2.1 | `21f84970ef69d21d779c43046a475cd8acefaaf16836f801d5c4c4ca987f86e8` |
+| InfectionMonkey-v2.2.1.AppImage | Linux Package | 2.2.1 | `cc9af5fe29cd978bbecafe454c9d722921870361fd1351c2447681b4cbe1b4a4` |
+| InfectionMonkey-v2.2.1.exe | Windows Installer | 2.2.1 | `74cd99dd087334ab7a87aafc5cc21faca48f32baa5bb38b51223434c548776b3` |
| monkey-windows-64.exe | Windows Agent | 2.1.0 | `883b16c9f8d9a532da6787a1a088bef6fabba782058bcedbd1e02afc613b88c2` |
| monkey-linux-64 | Linux Agent | 2.1.0 | `d7217665f714fbde6657f3b4ac60182779dfd2668fc9a308a77fc595be711252` |
| InfectionMonkey-v2.1.0.AppImage | Linux Package | 2.1.0 | `a788e693d4e785e039aad513ebd626be1d265e961a2f44dc06df3c9eae33cd8e` |
diff --git a/docs/content/usage/getting-started.md b/docs/content/usage/getting-started.md
index f374656700b..f41626b237e 100644
--- a/docs/content/usage/getting-started.md
+++ b/docs/content/usage/getting-started.md
@@ -20,6 +20,14 @@ After deploying the Monkey Island in your environment, navigate to `https://}}).
+
+![Plugin Installation Screen](/images/island/plugins_page/plugin_installation.PNG "Plugin Installation")
+
### Running the Infection Monkey
To get the Infection Monkey running as fast as possible, click **Run Monkey**. Optionally, you can configure the Infection Monkey before you continue by clicking on **Configuration** (see [how to configure the Infection Monkey](../configuration)).
diff --git a/docs/content/usage/plugins/_index.md b/docs/content/usage/plugins/_index.md
new file mode 100644
index 00000000000..432afcc478d
--- /dev/null
+++ b/docs/content/usage/plugins/_index.md
@@ -0,0 +1,26 @@
++++
+title = "Plugins"
+date = 2023-08-31T14:39:18+00:00
+weight = 10
+chapter = false
+pre = ' '
++++
+
+## Overview
+
+The Infection Monkey is extensible! You can install plugins to enhance the functionality of the Infection Monkey.
+
+Plugins page consists of three tabs:
+
+- **Available Plugins** - A table of plugins that are available for download and installation.
+- **Installed Plugins** - A table of plugins that are currently installed on the Monkey Island. You can
+ uninstall or update plugins from this table.
+- **Upload New Plugin** - A form to upload a new plugin to the Monkey Island. This is useful if you
+ want to install a plugin that is not officially released yet.
+
+![Plugin Installation Screen](/images/island/plugins_page/plugin_installation.PNG "Plugin Installation")
+
+## Troubleshooting
+
+First, make sure that the machine running Monkey Island has access to the internet. If it does,
+check the [Island logs]({{< ref "/FAQ/#monkey-island-server-logs" >}}).
diff --git a/docs/content/usage/scenarios/ransomware-simulation.md b/docs/content/usage/ransomware-simulation.md
similarity index 61%
rename from docs/content/usage/scenarios/ransomware-simulation.md
rename to docs/content/usage/ransomware-simulation.md
index a64950679fd..3960a875a67 100644
--- a/docs/content/usage/scenarios/ransomware-simulation.md
+++ b/docs/content/usage/ransomware-simulation.md
@@ -3,35 +3,33 @@ title: " Ransomware Simulation"
date: 2021-06-23T18:13:59+05:30
draft: false
description: "Simulate a ransomware attack on your network and assess the potential damage."
-weight: 1
-pre: ""
+weight: 6
+pre: " "
---
The Infection Monkey is capable of simulating a ransomware attack on your
-network using a set of configurable behaviors.
-
-
-## Encryption
-
-In order to simulate the behavior of ransomware as accurately as possible,
-the Infection Monkey can [encrypt user-specified files](#configuring-encryption)
-using a [fully reversible algorithm](#how-are-the-files-encrypted). A number of
-mechanisms are in place to ensure that all actions performed by the encryption
-routine are safe for production environments.
-
-### Preparing your environment for a ransomware simulation
-
-The Infection Monkey will only encrypt files that you allow it to. In
-order to take full advantage of the Infection Monkey's ransomware simulation, you'll
+network using a set of configurable behaviors. In order to simulate the
+behavior of ransomware as accurately as possible, the Infection Monkey can
+[encrypt user-specified files](#configuring-encryption) using a [fully
+reversible algorithm](#how-are-the-files-encrypted). A number of mechanisms are
+in place to ensure that all actions performed by the encryption routine are
+safe for production environments.
+
+## Workflow
+### 1. Prepare your environment for a ransomware simulation
+
+The Infection Monkey will only encrypt files that you allow it to. In order to
+take full advantage of the Infection Monkey's ransomware simulation, you'll
need to provide the Infection Monkey with a directory that contains files that
-are safe for it to encrypt. The recommended approach is to use a remote
-administration tool, such as
+are safe for it to encrypt. The recommended approach is to use a configuration
+management tool, such as
[Ansible](https://docs.ansible.com/ansible/latest/user_guide/) or
-[PsExec](https://theitbros.com/using-psexec-to-run-commands-remotely/) to add a
-"ransomware target" directory to each machine in your environment. The Infection
-Monkey can then be configured to encrypt files in this directory.
+[PsExec](https://theitbros.com/using-psexec-to-run-commands-remotely/), or even
+a Windows GPO, to add a "ransomware target" directory to each machine in your
+environment. The Infection Monkey can then be configured to encrypt files in
+this directory.
-### Configuring encryption
+### 2. Configure encryption
To ensure minimum interference and easy recoverability, the ransomware
simulation will only encrypt files contained in a user-specified directory. If
@@ -43,8 +41,33 @@ Monkey to use instead. You can even provide no file extension, but take
caution: you'll no longer be able to tell if the file has been encrypted based
on the filename alone!
-![Ransomware configuration](/images/island/configuration_page/ransomware_configuration.png "Ransomware configuration")
+![Ransomware
+configuration](/images/island/configuration_page/ransomware_configuration.png
+"Ransomware configuration")
+
+### 3. Configure propagation
+
+If you would like the Infection Monkey to propagate through the network,
+[Configure](/usage/configuration/) the network settings and one or more
+exploiters.
+
+### 4. Run the Agent
+
+Once everything is configured to your liking, simply [run the
+agent](/usage/getting-started#running-the-infection-monkey) to begin the
+ransomware simulation.
+
+### 5. Clean up
+
+After the simulation is complete, you can use the same mechanism you used in
+[step
+1](/usage/ransomware-simulation#1-prepare-your-environment-for-a-ransomware-simulation)
+to either remove the target directory or replace the encrypted files with
+unencrypted files. In most cases, there's no need to attempt to decrypt the
+files, as you should still have the originals.
+
+## Technical details
### How are the files encrypted?
Files are "encrypted" in place with a simple bit flip. Encrypted files are
@@ -57,17 +80,16 @@ Flipping a file's bits is sufficient to simulate the encryption behavior of
ransomware, as the data in your files has been manipulated (leaving them
temporarily unusable). Files are then renamed with a new extension appended,
which is similar to the way that many ransomwares behave. As this is a
-simulation, your
-security solutions should be triggered to notify you or prevent these changes
-from taking place.
+simulation, your security solutions should be triggered to notify you or
+prevent these changes from taking place.
### Which files are encrypted?
During the ransomware simulation, attempts will be made to encrypt all regular
files with [targeted file extensions](#files-targeted-for-encryption) in the
configured directory. The simulation is not recursive, i.e. it will not touch
-any files in sub-directories of the configured directory. The Infection Monkey will
-not follow any symlinks or shortcuts.
+any files in sub-directories of the configured directory. The Infection Monkey
+will not follow any symlinks or shortcuts.
These precautions are taken to prevent the Infection Monkey from accidentally
encrypting files that you didn't intend to encrypt.
@@ -154,14 +176,14 @@ BitDefender](https://labs.bitdefender.com/2017/07/a-technical-look-into-the-gold
- .zip
-## Leaving a README.txt file
+### Leaving a README.txt file
Many ransomware packages leave a README.txt file on the victim machine with an
-explanation of what has occurred and instructions for paying the attacker.
-The Infection Monkey will also leave a README.txt file in the target directory on
+explanation of what has occurred and instructions for paying the attacker. The
+Infection Monkey will also leave a README.txt file in the target directory on
the victim machine in order to replicate this behavior.
The README.txt file informs the user that a ransomware simulation has taken
place and that they should contact their administrator. The contents of the
file can be found
-[here](https://github.com/guardicore/monkey/tree/develop/monkey/infection_monkey/ransomware/ransomware_readme.txt).
+[here](https://github.com/guardicore/monkey/blob/master/monkey/agent_plugins/payloads/ransomware/src/ransomware_readme.txt).
diff --git a/docs/content/usage/scenarios/_index.md b/docs/content/usage/scenarios/_index.md
index 3126414367e..1f2ac6d88d6 100644
--- a/docs/content/usage/scenarios/_index.md
+++ b/docs/content/usage/scenarios/_index.md
@@ -8,21 +8,14 @@ pre = " "
# Scenarios
-This section describes the different attack scenarios that the Infection Monkey can simulate.
-
{{% notice note %}}
Don't worry! The Infection Monkey uses safe exploiters and does not cause any permanent system modifications that could impact security or operations.
{{% /notice %}}
-The Infection Monkey has pre-built scenarios to simulate common types of attacks that take place. These scenarios, when selected, manipulate the configuration to only show you what you need to see for that scenario. This makes it possible for you to quickly run the Monkey on your network in order to accomplish a specific objective.
-
-Choosing the "Custom" scenario will allow you to fine-tune your simulation and access all available features. [Read more about configuring a custom simulation.](/custom-scenario/_index.md)
-
-![Choose scenario](/images/island/landing_page/choose_scenario.png "Choose a scenario")
-
-To exit a scenario and select another one, click on "Reset".
-![Reset](/images/island/others/reset_modal.png "Reset")
+The Infection Monkey is a versatile breach and attack simulation tool. It allows
+you to configure the simulation according to your needs.
+You can enhance, optimize, and fine-tune the Monkey's behavior.
-## Section contents
+Here are some examples with instructions on how to configure them.
{{% children description=True style="p"%}}
diff --git a/docs/content/usage/scenarios/custom-scenario/credential-leak.md b/docs/content/usage/scenarios/credential-leak.md
similarity index 100%
rename from docs/content/usage/scenarios/custom-scenario/credential-leak.md
rename to docs/content/usage/scenarios/credential-leak.md
diff --git a/docs/content/usage/scenarios/custom-scenario/_index.md b/docs/content/usage/scenarios/custom-scenario/_index.md
deleted file mode 100644
index 172a692c8c2..00000000000
--- a/docs/content/usage/scenarios/custom-scenario/_index.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: " Custom"
-date: 2021-07-28T14:36:02+05:30
-description: "Configure a custom scenario to test your network's defenses."
-weight: 100
-pre: ""
-chapter: true
----
-
-# Custom
-
-The Infection Monkey is a versatile breach and attack simulation tool. Choosing the "Custom" scenario will allow you to access all of its capabilities and configure the simulation exactly according to your needs. You can enhance, optimize, and fine-tune the Monkey's behavior.
-
-![Custom scenario](/images/island/landing_page/choose_scenario_with_arrow_to_custom.png "Custom scenario")
-
-Below are some examples with instructions on how to configure them.
-
-{{% children description=True style="p"%}}
diff --git a/docs/content/usage/scenarios/custom-scenario/network-breach.md b/docs/content/usage/scenarios/network-breach.md
similarity index 100%
rename from docs/content/usage/scenarios/custom-scenario/network-breach.md
rename to docs/content/usage/scenarios/network-breach.md
diff --git a/docs/content/usage/scenarios/custom-scenario/network-segmentation.md b/docs/content/usage/scenarios/network-segmentation.md
similarity index 100%
rename from docs/content/usage/scenarios/custom-scenario/network-segmentation.md
rename to docs/content/usage/scenarios/network-segmentation.md
diff --git a/docs/content/usage/scenarios/custom-scenario/other.md b/docs/content/usage/scenarios/other.md
similarity index 100%
rename from docs/content/usage/scenarios/custom-scenario/other.md
rename to docs/content/usage/scenarios/other.md
diff --git a/docs/static/images/island/configuration_page/cryptojacker_configuration.png b/docs/static/images/island/configuration_page/cryptojacker_configuration.png
new file mode 100644
index 00000000000..44727e456b7
Binary files /dev/null and b/docs/static/images/island/configuration_page/cryptojacker_configuration.png differ
diff --git a/docs/static/images/island/configuration_page/ransomware_configuration.png b/docs/static/images/island/configuration_page/ransomware_configuration.png
index 2662e7b091a..b4776c7a62e 100644
Binary files a/docs/static/images/island/configuration_page/ransomware_configuration.png and b/docs/static/images/island/configuration_page/ransomware_configuration.png differ
diff --git a/docs/static/images/island/plugins_page/plugin_installation.PNG b/docs/static/images/island/plugins_page/plugin_installation.PNG
new file mode 100644
index 00000000000..bfb38cacb70
Binary files /dev/null and b/docs/static/images/island/plugins_page/plugin_installation.PNG differ
diff --git a/envs/monkey_zoo/README.md b/envs/monkey_zoo/README.md
index d0d239b0051..fd8fcc7aa21 100644
--- a/envs/monkey_zoo/README.md
+++ b/envs/monkey_zoo/README.md
@@ -24,10 +24,18 @@ scanning times in a real-world scenario and many more.
- This account should have `Service Account User` and `Compute Instance Admin` permissions
- A GCP key file for the service account
-Run envs/monkey_zoo/build_images.sh to build the images for the MonkeyZoo. These are the images from which the zoo will be deployed.
+To install the requirements run packer init, for example:
+```bash
+packer init ./browser-credentials.pkr.hcl
+```
+
+Then run the envs/monkey_zoo/build_images.sh to build the images for the MonkeyZoo. These are the images from which the zoo will be deployed.
Example:
- ./build_images.sh --project my-gcp-project --account-file /path/to/gcp_key.json packer/tunneling.pkr.hcl
+ `../build_images.sh --project-id my-gcp-project --account-file /path/to/gcp_key.json packer/tunneling.pkr.hcl`
+
+If you want to keep the machine running to debug the image, add `--debug` flag to the command.
+If you want to override an already existing image add `--force` flag to the command.
## MonkeyZoo network
diff --git a/envs/monkey_zoo/blackbox/analyzers/stolen_credentials_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/stolen_credentials_analyzer.py
index e1bb3ea3ae8..56b5b692156 100644
--- a/envs/monkey_zoo/blackbox/analyzers/stolen_credentials_analyzer.py
+++ b/envs/monkey_zoo/blackbox/analyzers/stolen_credentials_analyzer.py
@@ -20,18 +20,11 @@ def analyze_test_results(self) -> bool:
stolen_credentials = set(self.island_client.get_stolen_credentials())
- if self.expected_stolen_credentials == stolen_credentials:
+ if set(self.expected_stolen_credentials).issubset(stolen_credentials):
self.log.add_entry("All expected credentials were stolen")
return True
- if len(stolen_credentials) != len(self.expected_stolen_credentials):
- self.log.add_entry(
- f"Expected {len(self.expected_stolen_credentials)} credentials to be stolen but "
- f"{len(stolen_credentials)} were stolen"
- )
- elif self.expected_stolen_credentials != stolen_credentials:
- self.log.add_entry(
- "The contents of the stolen credentials did not match the expected credentials"
- )
+ missing = set(self.expected_stolen_credentials) - set(stolen_credentials)
+ self.log.add_entry(f"Some credentials were not stolen: {list(missing)}")
return False
diff --git a/envs/monkey_zoo/blackbox/expected_credentials.py b/envs/monkey_zoo/blackbox/expected_credentials.py
new file mode 100644
index 00000000000..c6bd28d219b
--- /dev/null
+++ b/envs/monkey_zoo/blackbox/expected_credentials.py
@@ -0,0 +1,71 @@
+from common.credentials import Credentials, EmailAddress, NTHash, Password, SSHKeypair, Username
+
+# Depth 2a
+ssh_private_key = """-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAyH0k1LOILDTVli5NlqcvRdoRc2aMn5I5ZhJsnBNuzB28D6Fd
+AEbjDn/v+dPK58L4WGoGMpHqk47mNDgdTIkfP5BBgbuQpBUmrsCZn8QVRpqZ3ESC
+XsnMrOjrYRqTelquGWR9xJvIJwNz3UbME2c8SYOPc3tHsINyn8Tt2ssA8L9KjcTe
+6CzNpbCNbZ6Q3o7/isYP79ogiFY+VHK3rtBY17aG9bDx5vce8RoIr463u/+a+jYX
+PuzZgndTtO3EPwq4Ti1pydpuJo9PYh1iY6RP3XPMYNwpoKToYzyESeqqwolmz+nh
+Eh/rQtuwwW7043IM+62w9UPkHWv28pqDZxBxhwIDAQABAoIBAGA1/ei8xwo/yIer
+bLxxOnRQ87LncXBaIYVkLg6wHKmDU25Ex3aMjgW1S5oeEu8pVzhGmPbHo0RwfPRu
+QVErNH2yYl05f23eYJPYBWDwHi2ln1Re5BlMyhXoKJyOvlsnDQlOejRRdbmTJJT5
+lpFxJzM4GS0X6g1A507YmDQ42xisOkmL/Wsv/t9/GiE9P6h0I1bNmXzSy8sDZwea
+NDe09U+rfuIkh2tO+nEzWs13AG3CxV9YlK4vMK7A0KiWF8LPvrbBegEm5VG+qrJ2
+sxoDkCBc5DV6QRyLU1SIyDIRIR2J0gTgfLDSbqNp0qm+Zby2o3V/q26bvrWWwP9a
+U/W2vJECgYEA5jK52pUMmriCiWNgQOiyOGx5OfHo6gRomiiiALivaMhNN3mm8CFI
+uIXMjU1V0BoHXCW8ciMOAeXl72rX/XVC+/E3GJQFCtBHJQ6tPNOOnWcxR/Ldwvxd
+sBz8Wx50MlxvbrxqtzTn+VmVnExKskwsZGI/GDPotPo7QKcBJUsGfhsCgYEA3vXx
+cyG805RsJH/J54cg+cHW5xDn6YNuHwbVdB4FWfi184oDDxtPT84XF1JA3dN3gwJF
+SfO1kNwpNK0C58evJA+6rZfUps/HOcQqFPvzCUhkLeZD5QgaOTQZBKndXYgeXkJD
+tpN+kjhCdxWN40N6FAMtLUYTbaQdTUHSBuXzoQUCgYEAgwXgTw+DCxV2ByjvAkLw
+HblwDpEoVvqHZyc1fl+gR22qtaaiZA8tywks8khQTZBjHAnGhth5Ao+OHoWbxoHV
+zHzxNSYa8Jq3w9nktLhddi3kGOWdX3ww/yqgYGSnEnsWWdsYioqsdnqM81dhNLay
+lbht3SK+kzPSQexMdKONYH0CgYAS1lKk+Ie8lICigM1tK0SE9XSTpyEA4KLQKkKk
+gdjP5ixxPArQHu2Pf4kB5mgmlbQ2NF3oRpfjekZc9fUV4hARCucpvXcw9MMPRVyM
+01CQSzZzjk3ULuAQTy+B7lwOh+6Q5iZUaZe7ANfUudR4C/5nbHFHrvD7RW9YVKRL
+AuiXhQKBgDMFeZRfu/dhTdVQ9XZigOWvkeXYxxoloiIHIg3ByZwEAlH/RnlA0M1Z
+OaLt/Q1KNh2UDKkstfOAJ1FdqLm3JU0Hqx/D8dpvTUQBkqoMf8U1WQC2WVmlpmUv
+drIj1d5/r2N1Cxorx0IbVWsW7WPVM/lVyBU7+2QsKoI5YIervsJY
+-----END RSA PRIVATE KEY-----\n"""
+
+ssh_public_key = (
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDIfSTUs4gsNNWWLk2Wpy"
+ "9F2hFzZoyfkjlmEmycE27MHbwPoV0ARuMOf+/508rnwvhYagYykeqTjuY0"
+ "OB1MiR8/kEGBu5CkFSauwJmfxBVGmpncRIJeycys6OthGpN6Wq4ZZH3Em8"
+ "gnA3PdRswTZzxJg49ze0ewg3KfxO3aywDwv0qNxN7oLM2lsI1tnpDejv+K"
+ "xg/v2iCIVj5Ucreu0FjXtob1sPHm9x7xGgivjre7/5r6Nhc+7NmCd1O07c"
+ "Q/CrhOLWnJ2m4mj09iHWJjpE/dc8xg3CmgpOhjPIRJ6qrCiWbP6eESH+tC"
+ "27DBbvTjcgz7rbD1Q+Qda/bymoNnEHGH m0nk3y@sshkeys-11\n"
+)
+
+expected_credentials_depth_2_a = {
+ # NTLM hash stolen from mimikatz-14
+ Credentials(
+ identity=Username(username="m0nk3y"),
+ secret=NTHash(nt_hash="5da0889ea2081aa79f6852294cba4a5e"),
+ ),
+ Credentials(identity=Username(username="m0nk3y"), secret=Password(password="pAJfG56JX><")),
+ Credentials(identity=Username(username="m0nk3y"), secret=Password(password="Ivrrw5zEzs")),
+ # SSH keypair from tunneling-11
+ Credentials(
+ identity=Username(username="m0nk3y"),
+ secret=SSHKeypair(private_key=ssh_private_key, public_key=ssh_public_key),
+ ),
+}
+
+expected_credentials_depth_1_a = {
+ # Stolen from Chrome browser on 10.2.2.65
+ Credentials(identity=Username(username="forBBtests"), secret=Password(password="supersecret")),
+ Credentials(
+ identity=Username(username="usernameFromForm"),
+ secret=Password(password="passwordFromForm"),
+ ),
+ # Stolen from Chromium browser on 10.2.3.70
+ Credentials(
+ identity=EmailAddress(email_address="my@email.com"),
+ secret=Password(password="mysecretpass"),
+ ),
+ Credentials(identity=Username(username="m0nk3y"), secret=Password(password="blahblahblah")),
+ Credentials(identity=Username(username="test"), secret=Password(password="password123")),
+}
diff --git a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py
index 10186343a0b..a2f3fbcb1bc 100644
--- a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py
+++ b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py
@@ -30,23 +30,25 @@
"log4j-tomcat-51",
"log4j-tomcat-52",
"snmp-20",
+ "rdp-64",
+ "rdp-65",
+ "browser-credentials-66",
+ "browser-credentials-67",
],
}
DEPTH_2_A = {
- "europe-west3-a": [
- "sshkeys-11",
- "sshkeys-12",
- ],
+ "europe-west3-a": ["sshkeys-11", "sshkeys-12", "mimikatz-14", "mimikatz-15"],
"europe-west1-b": [
"powershell-3-46",
"powershell-3-44",
+ "rdp-64",
+ "rdp-65",
],
}
-
DEPTH_1_A = {
- "europe-west3-a": ["hadoop-2", "hadoop-3", "mssql-16", "mimikatz-14", "mimikatz-15"],
+ "europe-west3-a": ["hadoop-2", "hadoop-3", "mssql-16", "mimikatz-15"],
"europe-west1-b": [
"log4j-logstash-55",
"log4j-logstash-56",
@@ -55,6 +57,8 @@
"log4j-tomcat-51",
"log4j-tomcat-52",
"snmp-20",
+ "browser-credentials-66",
+ "browser-credentials-67",
],
}
@@ -63,7 +67,6 @@
"tunneling-9",
"tunneling-10",
"tunneling-11",
- "mimikatz-15",
],
"europe-west1-b": [
"powershell-3-45",
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 7ac0d84c7be..7d5124c51d4 100644
--- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
+++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
@@ -2,9 +2,11 @@
import logging
import time
from http import HTTPStatus
-from typing import List, Mapping, Optional, Sequence
+from threading import Thread
+from typing import Any, Dict, List, Mapping, Optional, Sequence
from common import OperatingSystem
+from common.agent_plugins import AgentPluginRepositoryIndex, AgentPluginType
from common.credentials import Credentials
from common.types import AgentID, MachineID
from envs.monkey_zoo.blackbox.island_client.i_monkey_island_requests import IMonkeyIslandRequests
@@ -19,8 +21,9 @@
GET_AGENT_EVENTS_ENDPOINT = "api/agent-events"
LOGOUT_ENDPOINT = "api/logout"
GET_AGENT_OTP_ENDPOINT = "/api/agent-otp"
+INSTALL_PLUGIN_URL = "api/install-agent-plugin"
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
def avoid_race_condition(func):
@@ -35,15 +38,95 @@ def __init__(self, requests: IMonkeyIslandRequests):
def get_api_status(self):
return self.requests.get("api")
+ def install_agent_plugins(self):
+ available_plugins_index_url = "api/agent-plugins/available/index"
+ installed_plugins_manifests_url = "api/agent-plugins/installed/manifests"
+
+ response = self.requests.get(available_plugins_index_url)
+ plugin_repository_index = AgentPluginRepositoryIndex(**response.json())
+
+ response = self.requests.get(installed_plugins_manifests_url)
+ installed_plugins = response.json()
+
+ install_threads: List[Thread] = []
+
+ # all of the responses from the API endpoints are serialized
+ # so we don't need to worry about type conversion
+ for plugin_type in plugin_repository_index.plugins:
+ install_threads.extend(
+ self._install_all_agent_plugins_of_type(
+ plugin_type, plugin_repository_index, installed_plugins
+ )
+ )
+
+ for t in install_threads:
+ t.join()
+
+ def _install_all_agent_plugins_of_type(
+ self,
+ plugin_type: AgentPluginType,
+ plugin_repository_index: AgentPluginRepositoryIndex,
+ installed_plugins: Dict[str, Any],
+ ) -> Sequence[Thread]:
+ logger.info(f"Installing {plugin_type} plugins")
+ install_threads: List[Thread] = []
+ for plugin_name in plugin_repository_index.plugins[plugin_type]:
+ plugin_versions = plugin_repository_index.plugins[plugin_type][plugin_name]
+ latest_version = str(plugin_versions[-1].version)
+
+ if self._latest_version_already_installed(
+ installed_plugins, plugin_type, plugin_name, latest_version
+ ):
+ logger.info(f"{plugin_type}-{plugin_name}-v{latest_version} is already installed")
+ continue
+
+ t = Thread(
+ target=self._install_single_agent_plugin,
+ args=(plugin_name, plugin_type, latest_version),
+ daemon=True,
+ )
+ t.start()
+ install_threads.append(t)
+
+ return install_threads
+
+ def _latest_version_already_installed(
+ self,
+ installed_plugins: Dict[str, Any],
+ plugin_type: AgentPluginType,
+ plugin_name: str,
+ latest_version: str,
+ ) -> bool:
+ installed_plugin = installed_plugins.get(plugin_type, {}).get(plugin_name, {})
+ return installed_plugin and installed_plugin.get("version", "") == latest_version
+
+ def _install_single_agent_plugin(
+ self,
+ plugin_name: str,
+ plugin_type: AgentPluginType,
+ latest_version: str,
+ ):
+ install_plugin_request = {
+ "plugin_type": plugin_type,
+ "name": plugin_name,
+ "version": latest_version,
+ }
+ if self.requests.put_json(url=INSTALL_PLUGIN_URL, json=install_plugin_request).ok:
+ logger.info(f"Installed {plugin_name} {plugin_type} v{latest_version} to Island")
+ else:
+ logger.error(
+ f"Could not install {plugin_name} {plugin_type} " f"v{latest_version} to Island"
+ )
+
@avoid_race_condition
def set_masque(self, masque):
masque = b"" if masque is None else masque
for operating_system in [operating_system.name for operating_system in OperatingSystem]:
if self.requests.put(f"api/agent-binaries/{operating_system}/masque", data=masque).ok:
formatted_masque = masque if len(masque) <= 64 else (masque[:64] + b"...")
- LOGGER.info(f'Setting {operating_system} masque to "{formatted_masque}"')
+ logger.info(f'Setting {operating_system} masque to "{formatted_masque}"')
else:
- LOGGER.error(f"Failed to set {operating_system} masque")
+ logger.error(f"Failed to set {operating_system} masque")
assert False
def get_agent_binary(self, operating_system: OperatingSystem) -> bytes:
@@ -60,18 +143,9 @@ def get_stolen_credentials(self) -> Sequence[Credentials]:
@avoid_race_condition
def import_config(self, test_configuration: TestConfiguration):
- self._set_island_mode()
self._import_config(test_configuration)
self._import_credentials(test_configuration.propagation_credentials)
- @avoid_race_condition
- def _set_island_mode(self):
- if self.requests.put_json("api/island/mode", json="advanced").ok:
- LOGGER.info("Setting island mode to Custom.")
- else:
- LOGGER.error("Failed to set island mode")
- assert False
-
@avoid_race_condition
def _import_config(self, test_configuration: TestConfiguration):
response = self.requests.put_json(
@@ -79,9 +153,9 @@ def _import_config(self, test_configuration: TestConfiguration):
json=test_configuration.agent_configuration.dict(simplify=True),
)
if response.ok:
- LOGGER.info("Configuration is imported.")
+ logger.info("Configuration is imported.")
else:
- LOGGER.error(f"Failed to import config: {response}")
+ logger.error(f"Failed to import config: {response}")
assert False
@avoid_race_condition
@@ -94,18 +168,18 @@ def _import_credentials(self, propagation_credentials: List[Credentials]):
json=serialized_propagation_credentials,
)
if response.ok:
- LOGGER.info("Credentials are imported.")
+ logger.info("Credentials are imported.")
else:
- LOGGER.error(f"Failed to import credentials: {response}")
+ logger.error(f"Failed to import credentials: {response}")
assert False
@avoid_race_condition
def run_monkey_local(self):
response = self.requests.post_json("api/local-monkey", json={"action": "run"})
if MonkeyIslandClient.monkey_ran_successfully(response):
- LOGGER.info("Running the monkey.")
+ logger.info("Running the monkey.")
else:
- LOGGER.error("Failed to run the monkey.")
+ logger.error("Failed to run the monkey.")
assert False
@staticmethod
@@ -120,11 +194,11 @@ def kill_all_monkeys(self):
json=TerminateAllAgents(timestamp=time.time()).dict(simplify=True),
)
if response.ok:
- LOGGER.info("Killing all monkeys after the test.")
+ logger.info("Killing all monkeys after the test.")
else:
- LOGGER.error("Failed to kill all monkeys.")
- LOGGER.error(response.status_code)
- LOGGER.error(response.content)
+ logger.error("Failed to kill all monkeys.")
+ logger.error(response.status_code)
+ logger.error(response.content)
assert False
@avoid_race_condition
@@ -132,35 +206,27 @@ def reset_island(self):
self._reset_agent_configuration()
self._reset_simulation_data()
self._reset_credentials()
- self._reset_island_mode()
self.set_masque(b"")
def _reset_agent_configuration(self):
if self.requests.post("api/reset-agent-configuration", data=None).ok:
- LOGGER.info("Resetting agent-configuration after the test.")
+ logger.info("Resetting agent-configuration after the test.")
else:
- LOGGER.error("Failed to reset agent configuration.")
+ logger.error("Failed to reset agent configuration.")
assert False
def _reset_simulation_data(self):
if self.requests.post("api/clear-simulation-data", data=None).ok:
- LOGGER.info("Clearing simulation data.")
+ logger.info("Clearing simulation data.")
else:
- LOGGER.error("Failed to clear simulation data")
+ logger.error("Failed to clear simulation data")
assert False
def _reset_credentials(self):
if self.requests.put_json("api/propagation-credentials/configured-credentials", json=[]).ok:
- LOGGER.info("Resseting configured credentials after the test.")
- else:
- LOGGER.error("Failed to reset configured credentials")
- assert False
-
- def _reset_island_mode(self):
- if self.requests.put_json("api/island/mode", json="unset").ok:
- LOGGER.info("Resetting island mode after the test.")
+ logger.info("Resseting configured credentials after the test.")
else:
- LOGGER.error("Failed to reset island mode")
+ logger.error("Failed to reset configured credentials")
assert False
def get_agents(self) -> Sequence[Agent]:
@@ -178,7 +244,7 @@ def get_agent_log(self, agent_id: AgentID) -> Optional[str]:
response = self.requests.get(f"{GET_LOG_ENDPOINT}/{agent_id}")
if response.status_code == HTTPStatus.NOT_FOUND:
- LOGGER.error(f"No log found for agent: {agent_id}")
+ logger.error(f"No log found for agent: {agent_id}")
return None
else:
response.raise_for_status()
@@ -204,23 +270,23 @@ def is_all_monkeys_dead(self):
def register(self):
try:
self.requests.register()
- LOGGER.info("Successfully registered a user with the Island.")
+ logger.info("Successfully registered a user with the Island.")
except Exception:
- LOGGER.error("Failed to register a user with the Island.")
+ logger.error("Failed to register a user with the Island.")
def login(self):
try:
self.requests.login()
- LOGGER.info("Logged into the Island.")
+ logger.info("Logged into the Island.")
except Exception:
- LOGGER.error("Failed to log into the Island.")
+ logger.error("Failed to log into the Island.")
assert False
def logout(self):
if self.requests.post(LOGOUT_ENDPOINT, data=None).ok:
- LOGGER.info("Logged out of the Island.")
+ logger.info("Logged out of the Island.")
else:
- LOGGER.error("Failed to log out of the Island.")
+ logger.error("Failed to log out of the Island.")
assert False
def get_agent_otp(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 7596911d89e..5921f59d093 100644
--- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py
+++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py
@@ -9,7 +9,7 @@
ISLAND_USERNAME = "test"
ISLAND_PASSWORD = "testtest"
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
class InvalidRequestError(Exception):
diff --git a/envs/monkey_zoo/blackbox/log_handlers/island_log_parser.py b/envs/monkey_zoo/blackbox/log_handlers/island_log_parser.py
index 91aacb6f450..aa63cdddc88 100644
--- a/envs/monkey_zoo/blackbox/log_handlers/island_log_parser.py
+++ b/envs/monkey_zoo/blackbox/log_handlers/island_log_parser.py
@@ -1,7 +1,7 @@
import logging
from datetime import datetime
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
class IslandLogParser:
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 6a046a47498..05046221296 100644
--- a/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py
+++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py
@@ -1,7 +1,7 @@
import logging
import re
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
class MonkeyLogParser(object):
@@ -16,11 +16,11 @@ def read_log(self):
def print_errors(self):
errors = MonkeyLogParser.get_errors(self.log_contents)
if len(errors) > 0:
- LOGGER.info("Found {} errors:".format(len(errors)))
+ logger.info("Found {} errors:".format(len(errors)))
for index, error_line in enumerate(errors):
- LOGGER.info("Err #{}: {}".format(index, error_line))
+ logger.info("Err #{}: {}".format(index, error_line))
else:
- LOGGER.info("No errors!")
+ logger.info("No errors!")
@staticmethod
def get_errors(log_contents):
@@ -30,11 +30,11 @@ def get_errors(log_contents):
def print_warnings(self):
warnings = MonkeyLogParser.get_warnings(self.log_contents)
if len(warnings) > 0:
- LOGGER.info("Found {} warnings:".format(len(warnings)))
+ logger.info("Found {} warnings:".format(len(warnings)))
for index, warning_line in enumerate(warnings):
- LOGGER.info("Warn #{}: {}".format(index, warning_line))
+ logger.info("Warn #{}: {}".format(index, warning_line))
else:
- LOGGER.info("No warnings!")
+ logger.info("No warnings!")
@staticmethod
def get_warnings(log_contents):
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 989bbf9629a..35853c62d93 100644
--- a/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py
+++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py
@@ -10,7 +10,7 @@
from envs.monkey_zoo.blackbox.utils import bb_singleton
from monkey_island.cc.models import Agent, Machine
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
class MonkeyLogsDownloader(object):
@@ -21,7 +21,7 @@ def __init__(self, island_client: MonkeyIslandClient, log_dir_path: str):
def download_monkey_logs(self):
try:
- LOGGER.info("Downloading each monkey log.")
+ logger.info("Downloading each monkey log.")
agents = self.island_client.get_agents()
machines = self.island_client.get_machines()
@@ -41,7 +41,7 @@ def download_monkey_logs(self):
self._download_island_log()
except Exception as err:
- LOGGER.exception(err)
+ logger.exception(err)
def _download_agent_log(self, agent: Agent, machines: Mapping[MachineID, Machine]):
log_file_path = self._get_log_file_path(agent, machines)
@@ -66,7 +66,7 @@ def _get_log_file_path(self, agent: Agent, machines: Mapping[MachineID, Machine]
try:
machine_ip = str(machines[agent.machine_id].network_interfaces[0].ip)
except IndexError:
- LOGGER.error(f"Machine with ID {agent.machine_id} has no network interfaces")
+ logger.error(f"Machine with ID {agent.machine_id} has no network interfaces")
machine_ip = "UNKNOWN"
start_time = agent.start_time.strftime("%Y-%m-%d_%H-%M-%S")
@@ -75,7 +75,7 @@ def _get_log_file_path(self, agent: Agent, machines: Mapping[MachineID, Machine]
@staticmethod
def _write_log_to_file(log_file_path: Path, log_contents: str):
- LOGGER.debug(f"Writing {len(log_contents)} bytes to {log_file_path}")
+ logger.debug(f"Writing {len(log_contents)} bytes to {log_file_path}")
with open(log_file_path, "w") as f:
f.write(log_contents)
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 026bf917f53..d950824abd2 100644
--- a/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py
+++ b/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py
@@ -6,7 +6,7 @@
from envs.monkey_zoo.blackbox.log_handlers.monkey_logs_downloader import MonkeyLogsDownloader
LOG_DIR_NAME = "logs"
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
class TestLogsHandler(object):
@@ -20,7 +20,7 @@ def __init__(self, test_name, island_client, log_dir_path):
def parse_test_logs(self):
log_paths = self.download_logs()
if not log_paths:
- LOGGER.error(
+ logger.error(
"No logs were downloaded. Maybe no monkeys were ran "
"or early exception prevented log download?"
)
@@ -37,7 +37,7 @@ def try_create_log_dir_for_test(self):
try:
os.mkdir(self.log_dir_path)
except Exception as e:
- LOGGER.error("Can't create a dir for test logs: {}".format(e))
+ logger.error("Can't create a dir for test logs: {}".format(e))
@staticmethod
def delete_log_folder_contents(log_dir_path):
@@ -47,7 +47,7 @@ def delete_log_folder_contents(log_dir_path):
@staticmethod
def parse_logs(log_paths):
for log_path in log_paths:
- LOGGER.info("Info from log at {}".format(log_path))
+ logger.info("Info from log at {}".format(log_path))
log_parser = MonkeyLogParser(log_path)
log_parser.print_errors()
log_parser.print_warnings()
diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py
index db6ef851ead..c500c93fbe9 100644
--- a/envs/monkey_zoo/blackbox/test_blackbox.py
+++ b/envs/monkey_zoo/blackbox/test_blackbox.py
@@ -10,13 +10,17 @@
import pytest
import requests
+from treelib import Tree
from common import OperatingSystem
-from common.credentials import Credentials, NTHash, Password, Username
from common.types import OTP, SocketAddress
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
from envs.monkey_zoo.blackbox.analyzers.stolen_credentials_analyzer import StolenCredentialsAnalyzer
from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer
+from envs.monkey_zoo.blackbox.expected_credentials import (
+ expected_credentials_depth_1_a,
+ expected_credentials_depth_2_a,
+)
from envs.monkey_zoo.blackbox.island_client.agent_requests import AgentRequests
from envs.monkey_zoo.blackbox.island_client.i_monkey_island_requests import IMonkeyIslandRequests
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import (
@@ -59,7 +63,7 @@
MACHINE_BOOTUP_WAIT_SECONDS = 30
LOG_DIR_PATH = "./logs"
logging.basicConfig(level=logging.INFO)
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
@pytest.fixture(autouse=True, scope="session")
@@ -67,15 +71,15 @@ def GCPHandler(request, no_gcp, gcp_machines_to_start):
if no_gcp:
return
if len(gcp_machines_to_start) == 0:
- LOGGER.info("No GCP machines to start.")
+ logger.info("No GCP machines to start.")
else:
- LOGGER.info(f"MACHINES TO START: {gcp_machines_to_start}")
+ logger.info(f"MACHINES TO START: {gcp_machines_to_start}")
try:
initialize_gcp_client()
start_machines(gcp_machines_to_start)
except Exception as e:
- LOGGER.error("GCP Handler failed to initialize: %s." % 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()
@@ -87,7 +91,7 @@ def fin():
@pytest.fixture(autouse=True, scope="session")
def delete_logs():
- LOGGER.info("Deleting monkey logs before new tests.")
+ logger.info("Deleting monkey logs before new tests.")
TestLogsHandler.delete_log_folder_contents(TestMonkeyBlackbox.get_log_dir_path())
@@ -117,10 +121,13 @@ def island_client(monkey_island_requests):
@pytest.fixture(autouse=True, scope="session")
-def register(island_client):
+def setup_island(island_client):
logging.info("Registering a new user")
island_client.register()
+ logging.info("Installing all available plugins")
+ island_client.install_agent_plugins()
+
@pytest.mark.parametrize(
"authenticated_endpoint",
@@ -326,7 +333,6 @@ def test_island__cannot_access_nonisland_endpoints(island):
CLEAR_SIMULATION_DATA_ENDPOINT = "/api/clear-simulation-data"
MONKEY_EXPLOITATION_ENDPOINT = "/api/exploitations/monkey"
GET_ISLAND_LOG_ENDPOINT = "/api/island/log"
-ISLAND_MODE_ENDPOINT = "/api/island/mode"
ISLAND_RUN_ENDPOINT = "/api/local-monkey"
GET_NODES_ENDPOINT = "/api/nodes"
PROPAGATION_CREDENTIALS_ENDPOINT = "/api/propagation-credentials"
@@ -337,6 +343,9 @@ def test_island__cannot_access_nonisland_endpoints(island):
GET_SECURITY_REPORT_ENDPOINT = "/api/report/security"
GET_ISLAND_VERSION_ENDPOINT = "/api/island/version"
PUT_AGENT_CONFIG_ENDPOINT = "/api/agent-configuration"
+INSTALL_AGENT_PLUGIN_ENDPOINT = "/api/install-agent-plugin"
+AVAILABLE_AGENT_PLUGIN_INDEX_ENDPOINT = "/api/agent-plugins/available/index?force_refresh=true"
+UNINSTALL_AGENT_PLUGIN_ENDPOINT = "/api/uninstall-agent-plugin"
def test_agent__cannot_access_nonagent_endpoints(island):
@@ -361,8 +370,6 @@ def test_agent__cannot_access_nonagent_endpoints(island):
)
assert agent_requests.get(MONKEY_EXPLOITATION_ENDPOINT).status_code == HTTPStatus.FORBIDDEN
assert agent_requests.get(GET_ISLAND_LOG_ENDPOINT).status_code == HTTPStatus.FORBIDDEN
- assert agent_requests.get(ISLAND_MODE_ENDPOINT).status_code == HTTPStatus.FORBIDDEN
- assert agent_requests.put(ISLAND_MODE_ENDPOINT, data=None).status_code == HTTPStatus.FORBIDDEN
assert agent_requests.post(ISLAND_RUN_ENDPOINT, data=None).status_code == HTTPStatus.FORBIDDEN
assert agent_requests.get(GET_MACHINES_ENDPOINT).status_code == HTTPStatus.FORBIDDEN
assert agent_requests.get(GET_NODES_ENDPOINT).status_code == HTTPStatus.FORBIDDEN
@@ -383,6 +390,18 @@ def test_agent__cannot_access_nonagent_endpoints(island):
assert (
agent_requests.put(PUT_AGENT_CONFIG_ENDPOINT, data=None).status_code == HTTPStatus.FORBIDDEN
)
+ assert (
+ agent_requests.put(INSTALL_AGENT_PLUGIN_ENDPOINT, data=None).status_code
+ == HTTPStatus.FORBIDDEN
+ )
+ assert (
+ agent_requests.get(AVAILABLE_AGENT_PLUGIN_INDEX_ENDPOINT).status_code
+ == HTTPStatus.FORBIDDEN
+ )
+ assert (
+ agent_requests.post(UNINSTALL_AGENT_PLUGIN_ENDPOINT, data=None).status_code
+ == HTTPStatus.FORBIDDEN
+ )
def test_unauthenticated_user_cannot_access_API(island):
@@ -419,10 +438,6 @@ def test_unauthenticated_user_cannot_access_API(island):
)
assert island_requests.get(MONKEY_EXPLOITATION_ENDPOINT).status_code == HTTPStatus.UNAUTHORIZED
assert island_requests.get(GET_ISLAND_LOG_ENDPOINT).status_code == HTTPStatus.UNAUTHORIZED
- assert island_requests.get(ISLAND_MODE_ENDPOINT).status_code == HTTPStatus.UNAUTHORIZED
- assert (
- island_requests.put(ISLAND_MODE_ENDPOINT, data=None).status_code == HTTPStatus.UNAUTHORIZED
- )
assert (
island_requests.post(ISLAND_RUN_ENDPOINT, data=None).status_code == HTTPStatus.UNAUTHORIZED
)
@@ -453,6 +468,18 @@ def test_unauthenticated_user_cannot_access_API(island):
island_requests.put(PUT_AGENT_CONFIG_ENDPOINT, data=None).status_code
== HTTPStatus.UNAUTHORIZED
)
+ assert (
+ island_requests.put(INSTALL_AGENT_PLUGIN_ENDPOINT, data=None).status_code
+ == HTTPStatus.UNAUTHORIZED
+ )
+ assert (
+ island_requests.get(AVAILABLE_AGENT_PLUGIN_INDEX_ENDPOINT).status_code
+ == HTTPStatus.UNAUTHORIZED
+ )
+ assert (
+ island_requests.post(UNINSTALL_AGENT_PLUGIN_ENDPOINT, data=None).status_code
+ == HTTPStatus.UNAUTHORIZED
+ )
LOGOUT_AGENT_ID = uuid4()
@@ -524,6 +551,18 @@ def assert_unique_agent_hashes(agents: Sequence[Agent]):
assert len(agent_hashes) == len(set(agent_hashes))
+ @staticmethod
+ def assert_depth_restriction(agents: Sequence[Agent], configured_depth: int):
+ # Trying to add a node to the tree whose parent doesn't exist in the tree yet
+ # raises `NodeIDAbsentError`. Sorting the agents by registration time prevents that.
+ sorted_agents = sorted(agents, key=lambda agent: agent.registration_time)
+
+ propagation_tree = Tree()
+ for agent in sorted_agents:
+ propagation_tree.create_node(tag=agent.id, identifier=agent.id, parent=agent.parent_id)
+
+ assert propagation_tree.depth() <= configured_depth
+
@staticmethod
def run_exploitation_test(
island_client: MonkeyIslandClient,
@@ -539,7 +578,7 @@ def run_exploitation_test(
log_handler = TestLogsHandler(
test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
)
- ExploitationTest(
+ exploitation_test = ExploitationTest(
name=test_name,
island_client=island_client,
test_configuration=test_configuration,
@@ -547,7 +586,13 @@ def run_exploitation_test(
analyzers=[analyzer],
timeout=timeout_in_seconds,
log_handler=log_handler,
- ).run()
+ )
+ exploitation_test.run()
+
+ TestMonkeyBlackbox.assert_depth_restriction(
+ agents=exploitation_test.agents,
+ configured_depth=test_configuration.agent_configuration.propagation.maximum_depth,
+ )
@staticmethod
def get_log_dir_path():
@@ -560,6 +605,7 @@ def test_credentials_reuse_ssh_key(self, island_client):
def test_depth_2_a(self, island_client):
test_name = "Depth2A test suite"
+
communication_analyzer = CommunicationAnalyzer(
island_client,
get_target_ips(depth_2_a_test_configuration),
@@ -567,12 +613,16 @@ def test_depth_2_a(self, island_client):
log_handler = TestLogsHandler(
test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
)
+
+ stolen_credentials_analyzer = StolenCredentialsAnalyzer(
+ island_client, expected_credentials_depth_2_a
+ )
exploitation_test = ExploitationTest(
name=test_name,
island_client=island_client,
test_configuration=depth_2_a_test_configuration,
masque=None,
- analyzers=[communication_analyzer],
+ analyzers=[communication_analyzer, stolen_credentials_analyzer],
timeout=DEFAULT_TIMEOUT_SECONDS + 30,
log_handler=log_handler,
)
@@ -581,36 +631,15 @@ def test_depth_2_a(self, island_client):
# asserting that Agent hashes are not unique
assert len({a.sha256 for a in exploitation_test.agents}) == 2
+ TestMonkeyBlackbox.assert_depth_restriction(
+ agents=exploitation_test.agents,
+ configured_depth=depth_2_a_test_configuration.agent_configuration.propagation.maximum_depth, # noqa: E501
+ )
+
def test_depth_1_a(self, island_client):
test_name = "Depth1A test suite"
masque = b"m0nk3y"
- expected_credentials = {
- Credentials(
- identity=Username(username="m0nk3y"),
- secret=NTHash(nt_hash="5da0889ea2081aa79f6852294cba4a5e"),
- ),
- Credentials(
- identity=Username(username="m0nk3y"), secret=Password(password="pAJfG56JX><")
- ),
- Credentials(
- identity=Username(username="m0nk3y"), secret=Password(password="Ivrrw5zEzs")
- ),
- Credentials(
- identity=Username(username="vakaris_zilius"),
- secret=NTHash(nt_hash="e1c0dc690821c13b10a41dccfc72e43a"),
- ),
- Credentials(
- identity=Username(username="m0nk3y"),
- secret=NTHash(nt_hash="fc525c9683e8fe067095ba2ddc971889"),
- ),
- Credentials(
- identity=Username(username="m0nk3y"),
- secret=NTHash(nt_hash="201fe0a0db9733e419875201c6bd36f2"),
- ),
- }
-
- stolen_credentials_analyzer = StolenCredentialsAnalyzer(island_client, expected_credentials)
communication_analyzer = CommunicationAnalyzer(
island_client,
get_target_ips(depth_1_a_test_configuration),
@@ -618,20 +647,29 @@ def test_depth_1_a(self, island_client):
log_handler = TestLogsHandler(
test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
)
+ stolen_credentials_analyzer = StolenCredentialsAnalyzer(
+ island_client, expected_credentials_depth_1_a
+ )
exploitation_test = ExploitationTest(
name=test_name,
island_client=island_client,
test_configuration=depth_1_a_test_configuration,
masque=masque,
- analyzers=[stolen_credentials_analyzer, communication_analyzer],
+ analyzers=[communication_analyzer, stolen_credentials_analyzer],
timeout=DEFAULT_TIMEOUT_SECONDS + 30,
log_handler=log_handler,
)
exploitation_test.run()
+
TestMonkeyBlackbox.assert_unique_agent_hashes(exploitation_test.agents)
+ TestMonkeyBlackbox.assert_depth_restriction(
+ agents=exploitation_test.agents,
+ configured_depth=depth_1_a_test_configuration.agent_configuration.propagation.maximum_depth, # noqa: E501
+ )
def test_depth_3_a(self, island_client):
test_name = "Depth3A test suite"
+
communication_analyzer = CommunicationAnalyzer(
island_client,
get_target_ips(depth_3_a_test_configuration),
@@ -649,7 +687,12 @@ def test_depth_3_a(self, island_client):
log_handler=log_handler,
)
exploitation_test.run()
+
TestMonkeyBlackbox.assert_unique_agent_hashes(exploitation_test.agents)
+ TestMonkeyBlackbox.assert_depth_restriction(
+ agents=exploitation_test.agents,
+ configured_depth=depth_3_a_test_configuration.agent_configuration.propagation.maximum_depth, # noqa: E501
+ )
def test_depth_4_a(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(
@@ -674,7 +717,7 @@ def test_zerologon_exploiter(self, island_client):
log_handler = TestLogsHandler(
test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
)
- ExploitationTest(
+ exploitation_test = ExploitationTest(
name=test_name,
island_client=island_client,
test_configuration=zerologon_test_configuration,
@@ -682,7 +725,13 @@ def test_zerologon_exploiter(self, island_client):
analyzers=[zero_logon_analyzer, communication_analyzer],
timeout=DEFAULT_TIMEOUT_SECONDS + 30,
log_handler=log_handler,
- ).run()
+ )
+ exploitation_test.run()
+
+ TestMonkeyBlackbox.assert_depth_restriction(
+ agents=exploitation_test.agents,
+ configured_depth=zerologon_test_configuration.agent_configuration.propagation.maximum_depth, # noqa: E501
+ )
# Not grouped because it's depth 1 but conflicts with SMB exploiter in group depth_1_a
def test_smb_pth(self, island_client):
diff --git a/envs/monkey_zoo/blackbox/test_configurations/credentials_reuse_ssh_key.py b/envs/monkey_zoo/blackbox/test_configurations/credentials_reuse_ssh_key.py
index 33cf8e3030f..b7d0afeb83a 100644
--- a/envs/monkey_zoo/blackbox/test_configurations/credentials_reuse_ssh_key.py
+++ b/envs/monkey_zoo/blackbox/test_configurations/credentials_reuse_ssh_key.py
@@ -22,7 +22,7 @@
# then B(10.2.4.15) exploits C(10.2.5.16) with that key
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
exploiters: Dict[str, Mapping] = {
- "SSHExploiter": {},
+ "SSH": {},
}
return add_exploiters(agent_configuration, exploiters=exploiters)
diff --git a/envs/monkey_zoo/blackbox/test_configurations/depth_1_a.py b/envs/monkey_zoo/blackbox/test_configurations/depth_1_a.py
index e21aa477cc4..052bb8ed41c 100644
--- a/envs/monkey_zoo/blackbox/test_configurations/depth_1_a.py
+++ b/envs/monkey_zoo/blackbox/test_configurations/depth_1_a.py
@@ -2,14 +2,13 @@
from typing import Dict, Mapping
from common.agent_configuration import AgentConfiguration, PluginConfiguration
-from common.credentials import Credentials, Password, Username
+from common.credentials import Credentials, NTHash, Password, Username
from .noop import noop_test_configuration
from .utils import (
add_credentials_collectors,
add_exploiters,
add_fingerprinters,
- add_http_ports,
add_subnets,
add_tcp_ports,
replace_agent_configuration,
@@ -22,19 +21,23 @@
# Hadoop (10.2.2.2, 10.2.2.3)
# Log4shell (10.2.3.55, 10.2.3.56, 10.2.3.49, 10.2.3.50, 10.2.3.51, 10.2.3.52)
# MSSQL (10.2.2.16)
-# SMB mimikatz password stealing and brute force (10.2.2.14 and 10.2.2.15)
# SNMP (10.2.3.20)
+# WMI pass the hash (10.2.2.15)
+# Chrome credentials stealing (10.2.3.66 - Windows exploited by RDP, Chrome browser
+# 10.2.3.67 - Linux exploited by SSH, Chromium browser files)
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
exploiters: Dict[str, Mapping] = {
+ "WMI": {"agent_binary_upload_timeout": 30, "smb_connect_timeout": 5},
+ "RDP": {},
"Hadoop": {
"target_ports": [8088],
"request_timeout": 15,
"agent_binary_download_timeout": 60,
"yarn_application_suffix": "M0NK3Y3XPL01T",
},
- "Log4ShellExploiter": {},
+ "Log4Shell": {},
"MSSQL": {
"target_ports": [1433],
"try_discovered_mssql_ports": False,
@@ -42,22 +45,32 @@ def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfigurati
"server_timeout": 15,
"agent_binary_download_timeout": 60,
},
- "SMB": {"agent_binary_upload_timeout": 30, "smb_connect_timeout": 15},
"SNMP": {
"snmp_request_timeout": 0.5,
"snmp_retries": 1,
},
+ "SSH": {},
}
return add_exploiters(agent_configuration, exploiters=exploiters)
def _add_fingerprinters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
- fingerprinters = [PluginConfiguration(name="http", options={})]
+ fingerprinters = [
+ PluginConfiguration(name="http", options={}),
+ PluginConfiguration(name="mssql", options={}),
+ ]
return add_fingerprinters(agent_configuration, fingerprinters)
+def _add_credentials_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
+ credentials_collectors: Dict[str, Mapping] = {"Chrome": {}}
+ return add_credentials_collectors(
+ agent_configuration, credentials_collectors=credentials_collectors
+ )
+
+
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
subnets = [
"10.2.2.2",
@@ -68,47 +81,36 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
"10.2.3.50",
"10.2.3.51",
"10.2.3.52",
- "10.2.2.16",
- "10.2.2.14",
"10.2.2.15",
+ "10.2.2.16",
"10.2.3.20",
+ "10.2.3.66",
+ "10.2.3.67",
]
return add_subnets(agent_configuration, subnets)
-def _add_credentials_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
- credentials_collectors: Dict[str, Mapping] = {"Mimikatz": {}}
- return add_credentials_collectors(
- agent_configuration, credentials_collectors=credentials_collectors
- )
-
-
-HTTP_PORTS = [8080, 8983, 9600]
-
-
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
- ports = [22, 445] + HTTP_PORTS
+ ports = [22, 135, 445, 3389]
return add_tcp_ports(agent_configuration, ports)
-def _add_http_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
- return add_http_ports(agent_configuration, HTTP_PORTS)
-
-
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
test_agent_configuration = _add_exploiters(test_agent_configuration)
test_agent_configuration = _add_fingerprinters(test_agent_configuration)
+test_agent_configuration = _add_credentials_collectors(test_agent_configuration)
test_agent_configuration = _add_subnets(test_agent_configuration)
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
-test_agent_configuration = _add_credentials_collectors(test_agent_configuration)
-test_agent_configuration = _add_http_ports(test_agent_configuration)
test_agent_configuration = set_randomize_agent_hash(test_agent_configuration, True)
CREDENTIALS = (
Credentials(identity=Username(username="m0nk3y"), secret=None),
Credentials(identity=Username(username="c0mmun1ty"), secret=None),
- Credentials(identity=None, secret=Password(password="Ivrrw5zEzs")),
Credentials(identity=None, secret=Password(password="Xk8VDTsC")),
+ # Hash for Mimikatz-15
+ Credentials(identity=None, secret=NTHash(nt_hash="F7E457346F7743DAECE17258667C936D")),
+ Credentials(identity=Username(username="m0nk3y"), secret=Password(password="P@ssw0rd!")),
+ Credentials(identity=Username(username="m0nk3y"), secret=Password(password="password")),
)
depth_1_a_test_configuration = dataclasses.replace(noop_test_configuration)
diff --git a/envs/monkey_zoo/blackbox/test_configurations/depth_2_a.py b/envs/monkey_zoo/blackbox/test_configurations/depth_2_a.py
index 59f6cda04ec..2ad2269bd62 100644
--- a/envs/monkey_zoo/blackbox/test_configurations/depth_2_a.py
+++ b/envs/monkey_zoo/blackbox/test_configurations/depth_2_a.py
@@ -2,10 +2,11 @@
from typing import Dict, Mapping
from common.agent_configuration import AgentConfiguration, PluginConfiguration
-from common.credentials import Credentials, Password, Username
+from common.credentials import Credentials, NTHash, Password, Username
from .noop import noop_test_configuration
from .utils import (
+ add_credentials_collectors,
add_exploiters,
add_fingerprinters,
add_http_ports,
@@ -16,41 +17,66 @@
set_maximum_depth,
)
-
# Tests:
# SSH password and key brute-force, key stealing (10.2.2.11, 10.2.2.12)
# Powershell credential reuse (logging in without credentials
# to an identical user on another machine)(10.2.3.44, 10.2.3.46)
+# SMB mimikatz password stealing and brute force (10.2.2.14 and 10.2.2.15)
+
+
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
exploiters: Dict[str, Mapping] = {
# Log4Shell is required to hop into 46, which then uses credential reuse on 44.
# Look at envs/monkey_zoo/docs/network_diagrams/powershell_credential_reuse.drawio.png
- "Log4ShellExploiter": {},
- "SSHExploiter": {},
+ "Log4Shell": {
+ # no ports are configured but because `try_all_discovered_http_ports` is
+ # set to true, the exploiter should exploit 10.2.3.46 on port 8080 (configured
+ # at `agent_configuration.propagation.exploitation.options.http_ports`)
+ "try_all_discovered_http_ports": True,
+ "target_ports": [],
+ },
+ "SSH": {},
"PowerShell": {},
+ "RDP": {},
+ "SMB": {"agent_binary_upload_timeout": 30, "smb_connect_timeout": 15},
}
return add_exploiters(agent_configuration, exploiters=exploiters)
+def _add_credentials_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
+ credentials_collectors: Dict[str, Mapping] = {"Mimikatz": {}, "SSH": {}}
+ return add_credentials_collectors(
+ agent_configuration, credentials_collectors=credentials_collectors
+ )
+
+
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
subnets = [
"10.2.2.11",
"10.2.2.12",
+ "10.2.2.14",
+ "10.2.2.15",
"10.2.3.44",
"10.2.3.46",
+ "10.2.3.64",
+ "10.2.3.65",
]
return add_subnets(agent_configuration, subnets)
def _add_fingerprinters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
- fingerprinters = [PluginConfiguration(name="http", options={})]
+ fingerprinters = [
+ PluginConfiguration(name="http", options={}),
+ PluginConfiguration(name="smb", options={}),
+ PluginConfiguration(name="ssh", options={}),
+ ]
return add_fingerprinters(agent_configuration, fingerprinters)
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
- ports = [22, 5985, 5986, 8080]
+ ports = [22, 3389, 5985, 5986, 8080]
return add_tcp_ports(agent_configuration, ports)
@@ -64,10 +90,14 @@ def _add_http_ports(agent_configuration: AgentConfiguration) -> AgentConfigurati
test_agent_configuration = _add_fingerprinters(test_agent_configuration)
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
test_agent_configuration = _add_http_ports(test_agent_configuration)
+test_agent_configuration = _add_credentials_collectors(test_agent_configuration)
CREDENTIALS = (
Credentials(identity=Username(username="m0nk3y"), secret=None),
Credentials(identity=None, secret=Password(password="^NgDvY59~8")),
+ Credentials(identity=None, secret=Password(password="P@ssw0rd!")),
+ Credentials(identity=None, secret=Password(password="Ivrrw5zEzs")),
+ Credentials(identity=None, secret=NTHash(nt_hash="68965ABB32F8CE46F7E40075FA5B623E")),
)
depth_2_a_test_configuration = dataclasses.replace(noop_test_configuration)
diff --git a/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py b/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py
index e2e52c14036..d24f74402e5 100644
--- a/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py
+++ b/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py
@@ -19,14 +19,12 @@
# Tests:
# Powershell (10.2.3.45, 10.2.3.46, 10.2.3.47, 10.2.3.48)
# Tunneling through grandparent agent (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.11)
-# WMI pass the hash (10.2.2.15)
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
exploiters: Dict[str, Mapping] = {
"PowerShell": {},
- "SSHExploiter": {},
- "WMI": {"agent_binary_upload_timeout": 30},
+ "SSH": {},
}
return add_exploiters(agent_configuration, exploiters=exploiters)
@@ -40,7 +38,6 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
"10.2.3.48",
"10.2.1.10",
"10.2.0.11",
- "10.2.2.15",
]
return add_subnets(agent_configuration, subnets)
@@ -58,9 +55,8 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
test_agent_configuration = set_randomize_agent_hash(test_agent_configuration, True)
CREDENTIALS = (
- Credentials(identity=Username(username="m0nk3y"), secret=None),
+ Credentials(identity=Username(username="m0nk3y"), secret=Password(password="Passw0rd!")),
Credentials(identity=Username(username="m0nk3y-user"), secret=None),
- Credentials(identity=None, secret=Password(password="Passw0rd!")),
Credentials(identity=None, secret=Password(password="3Q=(Ge(+&w]*")),
Credentials(identity=None, secret=Password(password="`))jU7L(w}")),
Credentials(identity=None, secret=NTHash(nt_hash="d0f0132b308a0c4e5d1029cc06f48692")),
diff --git a/envs/monkey_zoo/blackbox/test_configurations/depth_4_a.py b/envs/monkey_zoo/blackbox/test_configurations/depth_4_a.py
index ed69edb122d..a560b48eea0 100644
--- a/envs/monkey_zoo/blackbox/test_configurations/depth_4_a.py
+++ b/envs/monkey_zoo/blackbox/test_configurations/depth_4_a.py
@@ -21,7 +21,7 @@
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
exploiters: Dict[str, Mapping] = {
- "SSHExploiter": {},
+ "SSH": {},
"WMI": {"agent_binary_upload_timeout": 30},
}
diff --git a/envs/monkey_zoo/blackbox/test_configurations/smb_pth.py b/envs/monkey_zoo/blackbox/test_configurations/smb_pth.py
index 70667942cd4..2fea038271b 100644
--- a/envs/monkey_zoo/blackbox/test_configurations/smb_pth.py
+++ b/envs/monkey_zoo/blackbox/test_configurations/smb_pth.py
@@ -43,14 +43,10 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
CREDENTIALS = (
- Credentials(identity=Username(username="Administrator"), secret=None),
Credentials(identity=Username(username="m0nk3y"), secret=None),
- Credentials(identity=Username(username="user"), secret=None),
- Credentials(identity=None, secret=Password(password="Ivrrw5zEzs")),
- Credentials(identity=None, secret=Password(password="Password1!")),
- Credentials(identity=None, secret=NTHash(nt_hash="d0f0132b308a0c4e5d1029cc06f48692")),
- Credentials(identity=None, secret=NTHash(nt_hash="5da0889ea2081aa79f6852294cba4a5e")),
- Credentials(identity=None, secret=NTHash(nt_hash="50c9987a6bf1ac59398df9f911122c9b")),
+ Credentials(identity=Username(username="unused"), secret=None),
+ Credentials(identity=None, secret=Password(password="Unused")),
+ Credentials(identity=None, secret=NTHash(nt_hash="F7E457346F7743DAECE17258667C936D")),
)
smb_pth_test_configuration = dataclasses.replace(noop_test_configuration)
diff --git a/envs/monkey_zoo/blackbox/tests/exploitation.py b/envs/monkey_zoo/blackbox/tests/exploitation.py
index 0c0c4868f94..d54e91142a2 100644
--- a/envs/monkey_zoo/blackbox/tests/exploitation.py
+++ b/envs/monkey_zoo/blackbox/tests/exploitation.py
@@ -13,7 +13,7 @@
WAIT_TIME_BETWEEN_REQUESTS = 1
TIME_FOR_MONKEY_PROCESS_TO_FINISH = 5
DELAY_BETWEEN_ANALYSIS = 1
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
class ExploitationTest(BasicTest):
@@ -46,9 +46,9 @@ def run(self):
self.island_client.reset_island()
def print_test_starting_info(self):
- LOGGER.info("Started {} test".format(self.name))
+ logger.info("Started {} test".format(self.name))
machine_list = ", ".join(get_target_ips(self.test_configuration))
- LOGGER.info(f"Machines participating in test: {machine_list}")
+ logger.info(f"Machines participating in test: {machine_list}")
print("")
def test_until_timeout(self):
@@ -58,21 +58,21 @@ def test_until_timeout(self):
self.log_success(timer)
return
sleep(DELAY_BETWEEN_ANALYSIS)
- LOGGER.debug(
+ 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(
+ logger.info(self.get_analyzer_logs())
+ 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(
+ logger.info(self.get_analyzer_logs())
+ logger.error(
"{} test failed because of timeout. Time taken: {:.1f} seconds.".format(
self.name, timer.get_time_taken()
)
@@ -96,14 +96,14 @@ def wait_until_monkeys_die(self):
):
sleep(WAIT_TIME_BETWEEN_REQUESTS)
time_passed += WAIT_TIME_BETWEEN_REQUESTS
- LOGGER.debug("Waiting for all monkeys to die. Time passed: {}".format(time_passed))
+ logger.debug("Waiting for all monkeys to die. Time passed: {}".format(time_passed))
if time_passed > MAX_TIME_FOR_MONKEYS_TO_DIE:
- LOGGER.error("Some monkeys didn't die after the test, failing")
+ logger.error("Some monkeys didn't die after the test, failing")
assert False
- LOGGER.info(f"After {time_passed} seconds all monkeys have died")
+ logger.info(f"After {time_passed} seconds all monkeys have died")
def parse_logs(self):
- LOGGER.info("Parsing test logs:")
+ logger.info("Parsing test logs:")
self.log_handler.parse_test_logs()
@staticmethod
@@ -113,5 +113,5 @@ def wait_for_monkey_process_to_finish():
If we try to launch monkey during that time window monkey will fail to start, that's
why test needs to wait a bit even after all monkeys are dead.
"""
- LOGGER.debug("Waiting for Monkey process to close...")
+ logger.debug("Waiting for Monkey process to close...")
sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH)
diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py
index 5e017d03092..b73846ddc36 100644
--- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py
+++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py
@@ -3,7 +3,7 @@
import subprocess
from multiprocessing.dummy import Pool
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
AUTHENTICATION_COMMAND = "gcloud auth activate-service-account --key-file=%s"
SET_PROPERTY_PROJECT = "gcloud config set project %s"
@@ -19,11 +19,11 @@ def initialize_gcp_client():
abs_key_path = get_absolute_key_path()
subprocess.call(get_auth_command(abs_key_path), shell=True) # noqa: DUO116
- LOGGER.info("GCP Handler passed key")
+ logger.info("GCP Handler passed key")
subprocess.call(get_set_project_command(DEFAULT_PROJECT), shell=True) # noqa: DUO116
- LOGGER.info("GCP Handler set project")
- LOGGER.info("GCP Handler initialized successfully")
+ logger.info("GCP Handler set project")
+ logger.info("GCP Handler initialized successfully")
def get_absolute_key_path() -> str:
@@ -43,21 +43,21 @@ def start_machines(machine_list):
Start all the machines in the list.
:param machine_list: A dictionary with zone and machines per zone.
"""
- LOGGER.info("Setting up all GCP machines...")
+ logger.info("Setting up all GCP machines...")
try:
run_gcp_pool(MACHINE_STARTING_COMMAND, machine_list)
- LOGGER.info("GCP machines successfully started.")
+ logger.info("GCP machines successfully started.")
except Exception as e:
- LOGGER.error("GCP Handler failed to start GCP machines: %s" % e)
+ logger.error("GCP Handler failed to start GCP machines: %s" % e)
raise e
def stop_machines(machine_list):
try:
run_gcp_pool(MACHINE_STOPPING_COMMAND, machine_list)
- LOGGER.info("GCP machines stopped successfully.")
+ logger.info("GCP machines stopped successfully.")
except Exception as e:
- LOGGER.error("GCP Handler failed to stop network machines: %s" % e)
+ logger.error("GCP Handler failed to stop network machines: %s" % e)
def get_auth_command(key_path):
diff --git a/envs/monkey_zoo/build_images.sh b/envs/monkey_zoo/build_images.sh
index cabffc3cb3d..60b20846b91 100755
--- a/envs/monkey_zoo/build_images.sh
+++ b/envs/monkey_zoo/build_images.sh
@@ -12,6 +12,7 @@ show_usage() {
printerr " -p|--project-id GCP project ID (required)"
printerr " --account-file GCP service account file (required)"
printerr " -f|--force Force build even if image already exists"
+ printerr " -d|--debug Leave the instance running for debugging"
printerr " -h|--help Show this help message and exit"
}
@@ -19,6 +20,7 @@ show_usage() {
PROJECT_ID=
ACCOUNT_FILE=
FORCE=
+DEBUG=
while :; do
case $1 in
@@ -48,6 +50,10 @@ while :; do
FORCE=-force
shift
;;
+ -d|--debug)
+ DEBUG=-debug
+ shift
+ ;;
*)
break
esac
@@ -69,9 +75,9 @@ prevdir=$(pwd)
cd "$ROOT" || exit 1
for file in "$@"; do
if file_path=$(realpath -q "$file") && [ -f "$file_path" ]; then
- packer build $FORCE -var "project_id=$PROJECT_ID" -var "account_file=$ACCOUNT_FILE" "$file_path"
+ packer build $FORCE $DEBUG -on-error=ask -var "project_id=$PROJECT_ID" -var "account_file=$ACCOUNT_FILE" "$file_path"
elif file_path=$(realpath -q "$prevdir/$file") && [ -f "$file_path" ]; then
- packer build $FORCE -var "project_id=$PROJECT_ID" -var "account_file=$ACCOUNT_FILE" "$file_path"
+ packer build $FORCE $DEBUG -on-error=ask -var "project_id=$PROJECT_ID" -var "account_file=$ACCOUNT_FILE" "$file_path"
else
printerr "File does not exist: '$file'. Skipping."
fi
diff --git a/envs/monkey_zoo/docs/network_diagrams/credentials_ssh_reuse_diagram.drawio b/envs/monkey_zoo/docs/network_diagrams/credentials_ssh_reuse_diagram.drawio
new file mode 100644
index 00000000000..72d53a426d3
--- /dev/null
+++ b/envs/monkey_zoo/docs/network_diagrams/credentials_ssh_reuse_diagram.drawio
@@ -0,0 +1 @@
+7Vrfc9o4EP5reIzHlmxDHxsIzc30LpnhbpreS0exFVAjW4wsAvSvPwlLtoVM4ksIzqThIbFWqx/+9tvVamEAx9nmC0fLxZ8sxXQA/HQzgJMBAEEIYvlPSbalZBgPS8Gck1Qr1YIZ+YW10NfSFUlxYSkKxqggS1uYsDzHibBkiHO2ttXuGLVXXaI5dgSzBFFX+o2kYlFKR5Ffyy8xmS/MyoGvezJklLWgWKCUrRsieDGAY86YKJ+yzRhTBZ7BpRw3PdBbbYzjXHQZ8Gv219Xy9mr2L1kFxfjn/ebvu5szUM7ygOhKv3DCcSpnJIgWZxyvCnwWRAMQU7nG+S2XT3P15EoC3wNe6LUp77qiXdcOCLE16HK2ylOsNuhLvfWCCDxbokT1riWfpGwhMipbwW7N3VYxF3hzEIOgQlZSErMMC76VKnpAGGtjaDYCY611bdsAatnCtqvmlObTvJq7hlw+aNTbLRBNftDJ1Vf0bZL8uLyeZtuAT7tZIOxsAei1KRvjhMewgAN3i1EOWgC8PQsEQ8cEDkzFPRbJQqO0ZCQXmF88SBgKDU3l3UohRcWiwrSBXyE4u8djRhmXkpzlcvbzO0KpEQ0AnExH41ACeE7RLabXrCCCsFz2JVgtKjsU2ERGqK97CrdMCJY1FD5TMlcdgik7It2q5mErQUkut2Mip1++x1K9c7aZq4DuPWCMMuBJxdXmOAyIothmwDB2GABDlwBGdnz7f/qw/wntD3u0f+sZGDjmb4nAcecILA+6FmUTnOM3dwbC0SkjcKsFwqcdEOfpZ5XOKQJTVBQksYGxUcQbIm50j3r+ruQeiHRzsmnoTbamkct3uWk2msNUux63a5mBtltLH55OofxUPSZxhJUBcepknY5HFWzFE/wIajqdEojPsXgqw3Dp0DB31GJtI+OYIkEe7O22MUCvcK2CY822IAa2t4fA8+Gn+hPbM5ZvrSdpZrN78w6jPRYP98hZwuJMtONnhcLzKRs5lP2joChPe3dv0Gd+1QpVfHTvNo4a2I46fMpRTVjwrbAwfDwsON5d8vaI3v3YvaDp3YeJ2Jd3xyObbSHYI1Fnd4b9unOHK8DvfQK9iKO9nkDvhqOjdxZHffkZj187S2rh6NvLkt4NRztcpZ8XR30vapAt6BhGvchidu9RtCtDD99TPxj6QoaaNPcVKGpFw64nvU3R4P9S9DRB1Hzz8nsEUbh3hwph1ImikjNo21Db1QmLwxuGIGpdp2Z8OeNx+e+Wuxz+f1Q7m9XOF13H96vdYdR3tRM49p8JjNS42exS/h3XxU+HGHXgC05TzIhtD2krVYKWiBL7r4UePE2C47/O6aHpLvl9389NMHgfJ0TvSYxbMf+nwMWOh4zi/E06c7z/1a85/vrzZreKeyRvtss+z6v6PFn0OYY3v+xGEn5486PeLJv1D3xK9fpnUvDiPw==
diff --git a/envs/monkey_zoo/docs/network_diagrams/credentials_ssh_reuse_diagram.drawio.png b/envs/monkey_zoo/docs/network_diagrams/credentials_ssh_reuse_diagram.drawio.png
new file mode 100644
index 00000000000..d0361b3426e
Binary files /dev/null and b/envs/monkey_zoo/docs/network_diagrams/credentials_ssh_reuse_diagram.drawio.png differ
diff --git a/envs/monkey_zoo/docs/network_diagrams/rdp_diagram.drawio b/envs/monkey_zoo/docs/network_diagrams/rdp_diagram.drawio
new file mode 100644
index 00000000000..4561cc0db0b
--- /dev/null
+++ b/envs/monkey_zoo/docs/network_diagrams/rdp_diagram.drawio
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/envs/monkey_zoo/docs/network_diagrams/rdp_diagram.drawio.png b/envs/monkey_zoo/docs/network_diagrams/rdp_diagram.drawio.png
new file mode 100644
index 00000000000..687394d117e
Binary files /dev/null and b/envs/monkey_zoo/docs/network_diagrams/rdp_diagram.drawio.png differ
diff --git a/envs/monkey_zoo/docs/zoo_network.md b/envs/monkey_zoo/docs/zoo_network.md
index 5f11f0c3b2e..c48f15b1b53 100644
--- a/envs/monkey_zoo/docs/zoo_network.md
+++ b/envs/monkey_zoo/docs/zoo_network.md
@@ -33,8 +33,12 @@ This document describes Infection Monkey’s test network.
[Nr. 3-52 Log4j Tomcat](#_Toc536021486)
[Nr. 3-55 Log4j Logstash](#_Toc536021487)
[Nr. 3-56 Log4j Logstash](#_Toc536021488)
-[Nr. 250 MonkeyIsland](#_Toc536021489)
-[Nr. 251 MonkeyIsland](#_Toc536021490)
+[Nr. 3-64 RDP](#_Toc536021489)
+[Nr. 3-65 RDP](#_Toc536021490)
+[Nr. 3-66 Browser Credentials](#_Toc536021492)
+[Nr. 3-67 Browser Credentials](#_Toc536021491)
+[Nr. 250 MonkeyIsland](#_Toc536021492)
+[Nr. 251 MonkeyIsland](#_Toc536021493)
[Network topography](#network-topography)
@@ -280,7 +284,10 @@ This prevents ssh exploitation, but allows tunneling.
Server’s config:
-
Configured to disable traffic from/to 10.2.0.10 and 10.2.0.11(via ufw and iptables)
+
Configured to disable traffic from/to 10.2.0.10 and 10.2.0.11 (via ufw and iptables).
+This machine has iptables rules configured that don't allow any machine to ping it.
+We decided to keep it this way to ensure that plugins run even if the OS of a target is unknown.
+