forked from p15670423/monkey
Compare commits
2 Commits
develop
...
PROTECTED_
Author | SHA1 | Date |
---|---|---|
vakarisz | c7b212d3b6 | |
vakarisz | 4321bfafb4 |
|
@ -4,5 +4,5 @@ contact_links:
|
|||
url: https://join.slack.com/t/infectionmonkey/shared_invite/enQtNDU5MjAxMjg1MjU1LWM0NjVmNWE2ZTMzYzAxOWJiYmMxMzU0NWU3NmUxYjcyNjk0YWY2MDkwODk4NGMyNDU4NzA4MDljOWNmZWViNDU
|
||||
about: Our community Slack channel - you can ask questions or suggest things here.
|
||||
- name: FAQs
|
||||
url: https://www.guardicore.com/infectionmonkey/docs/faq/
|
||||
url: https://www.guardicore.com/infectionmonkey/faq/
|
||||
about: Frequently Asked Questions - if you have a question, see if we've already answered it!
|
||||
|
|
|
@ -104,6 +104,3 @@ venv/
|
|||
|
||||
# mypy
|
||||
.mypy_cache
|
||||
|
||||
# MacOS
|
||||
.DS_Store
|
||||
|
|
|
@ -35,17 +35,6 @@ repos:
|
|||
hooks:
|
||||
- id: eslint
|
||||
args: ["monkey/monkey_island/cc/ui/src/", "--fix", "--max-warnings=0"]
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.971
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies: [types-ipaddress, types-paramiko, types-python-dateutil, types-requests]
|
||||
exclude: "vulture_allowlist.py"
|
||||
args: [--ignore-missing-imports]
|
||||
- repo: https://github.com/koalaman/shellcheck-precommit
|
||||
rev: v0.7.2
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pytest
|
||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -8,7 +8,6 @@ branches:
|
|||
only:
|
||||
- develop
|
||||
- master
|
||||
- fix-travis
|
||||
|
||||
jobs:
|
||||
include:
|
||||
|
@ -34,8 +33,8 @@ jobs:
|
|||
install:
|
||||
# Python
|
||||
- nproc
|
||||
- pip install pip --upgrade
|
||||
- pipenv --version
|
||||
- pip install pipenv --upgrade
|
||||
|
||||
# Install island and monkey requirements as they are needed by UT's
|
||||
- pushd monkey/monkey_island
|
||||
- pipenv sync --dev # This installs dependencies from lock
|
||||
|
@ -76,7 +75,7 @@ jobs:
|
|||
## run unit tests and generate coverage data
|
||||
- cd monkey # this is our source dir
|
||||
- pip install pytest-xdist
|
||||
- python -m pytest -n auto --dist loadscope --cov=. # have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path.
|
||||
- python -m pytest -n auto --cov=. # have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path.
|
||||
|
||||
# check js code. the npm install must happen after the flake8 because the node_modules folder will cause a lot of errors.
|
||||
- cd monkey_island/cc/ui
|
||||
|
@ -120,7 +119,7 @@ jobs:
|
|||
install:
|
||||
# Python
|
||||
- nproc
|
||||
- pip install pipenv==2022.7.4
|
||||
- pip install pipenv --upgrade
|
||||
# Install island and monkey requirements as they are needed by UT's
|
||||
- pushd monkey/monkey_island
|
||||
- pipenv sync --dev # This installs dependencies from lock
|
||||
|
@ -133,7 +132,7 @@ jobs:
|
|||
## run unit tests and generate coverage data
|
||||
- cd monkey # this is our source dir
|
||||
- pip install pytest-xdist
|
||||
- python -m pytest -n auto --dist loadscope
|
||||
- python -m pytest -n auto
|
||||
|
||||
|
||||
notifications:
|
||||
|
|
28
CHANGELOG.md
28
CHANGELOG.md
|
@ -18,15 +18,6 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- The ability to download the Monkey Island logs from the Infection Map page. #1640
|
||||
- `/api/reset-agent-configuration` endpoint. #2036
|
||||
- `/api/clear-simulation-data` endpoint. #2036
|
||||
- `/api/registration-status` endpoint. #2149
|
||||
- authentication to `/api/island/version`. #2109
|
||||
- `/api/agent-events` endpoint. #2155, #2300
|
||||
- The ability to customize the file extension used by ransomware when
|
||||
encrypting files. #1242
|
||||
- `/api/agents` endpoint. #2362
|
||||
- `/api/agent-signals` endpoint. #2261
|
||||
- `/api/agent-logs/<uuid:agent_id>` endpoint. #2274
|
||||
- `/api/machines` endpoint. #2362
|
||||
|
||||
### Changed
|
||||
- Reset workflow. Now it's possible to delete data gathered by agents without
|
||||
|
@ -46,7 +37,6 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- The "/api/netmap/nodeStates" endpoint to "/api/netmap/node-states". #1888
|
||||
- All "/api/monkey_control" endpoints to "/api/monkey-control". #1888
|
||||
- All "/api/monkey" endpoints to "/api/agent". #1888
|
||||
- Analytics and version update queries are sent separately instead of just one query. #2165
|
||||
- Update MongoDB version to 4.4.x. #1924
|
||||
- Endpoint to get agent binaries from "/api/agent/download/<string:os>" to
|
||||
"/api/agent-binaries/<string:os>". #1978
|
||||
|
@ -55,20 +45,6 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Agent configuration structure. #1996, #1998, #1961, #1997, #1994, #1741,
|
||||
#1761, #1695, #1605, #2028, #2003
|
||||
- `/api/island-mode` to accept and return new "unset" mode. #2036
|
||||
- `/api/version-update` to `api/island/version`. #2109
|
||||
- `/api/island-mode` to `/api/island/mode`. #2106
|
||||
- `/api/log/island/download` endpoint to `/api/island/log`. #2107
|
||||
- `/api/auth` endpoint to `/api/authenticate`. #2105
|
||||
- `/api/registration` endpoint to `/api/register`. #2105
|
||||
- `/api/file-upload` endpoit to `/api/pba/upload`. #2154
|
||||
- Improved the speed of ransomware encryption by 2-3x. #2123
|
||||
- "-s/--server" to "-s/--servers". #2216
|
||||
- "-s/--servers" accepts list of servers separated by comma. #2216
|
||||
- Tunneling to relays to provide better firewall evasion, faster Island
|
||||
connection times, unlimited hops, and a more resilient way for agents to call
|
||||
home. #2216, #1583
|
||||
- "/api/monkey-control/stop-all-agents" to "/api/agent-signals/terminate-all-agents". #2261
|
||||
- "Local network scan" option to "Scan Agent's networks". #2299
|
||||
|
||||
### Removed
|
||||
- VSFTPD exploiter. #1533
|
||||
|
@ -113,10 +89,6 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- "/api/configuration/import" endpoint. #2002
|
||||
- "/api/configuration/export" endpoint. #2002
|
||||
- "/api/island-configuration" endpoint. #2003
|
||||
- "-t/--tunnel" from agent command line arguments. #2216
|
||||
- "/api/monkey-control/neets-to-stop". #2261
|
||||
- "GET /api/test/monkey" endpoint. #2269
|
||||
- "GET /api/test/log" endpoint. #2269
|
||||
|
||||
### Fixed
|
||||
- A bug in network map page that caused delay of telemetry log loading. #1545
|
||||
|
|
16
README.md
16
README.md
|
@ -18,18 +18,20 @@ The Infection Monkey is comprised of two parts:
|
|||
* **Monkey** - A tool which infects other machines and propagates to them.
|
||||
* **Monkey Island** - A dedicated server to control and visualize the Infection Monkey's progress inside the data center.
|
||||
|
||||
To read more about the Monkey, visit [akamai.com/infectionmonkey](https://www.akamai.com/infectionmonkey).
|
||||
To read more about the Monkey, visit [infectionmonkey.com](https://infectionmonkey.com).
|
||||
|
||||
## 💥 We're Hiring 💥
|
||||
We are looking for a software engineering manager with a passion for UX and
|
||||
cybersecurity to join the Infection Monkey development team. This is a remote
|
||||
position and is open anywhere in Israel. You can learn more about Infection
|
||||
Monkey on our [website](https://www.akamai.com/infectionmonkey).
|
||||
We are looking for a senior developer with a passion for cybersecurity to join
|
||||
the Infection Monkey development team. This is a remote position and is open
|
||||
anywhere in the US Eastern timezone or in Brazil. If you're excited about Infection Monkey,
|
||||
we want to see your resume. You can learn more about Infection Monkey on our
|
||||
[website](https://www.guardicore.com/infectionmonkey/).
|
||||
|
||||
For more information, or to apply, see the official job post:
|
||||
- [Israel](https://akamaicareers.inflightcloud.com/jobdetails/aka_ext/028224?section=aka_ext&job=028224)
|
||||
- [USA](https://akamaicareers.inflightcloud.com/jobdetails/aka_ext/028789?section=aka_ext&job=028789).
|
||||
- [Brazil](https://akamaicareers.inflightcloud.com/jobdetails/aka_ext/028197?section=aka_ext&job=028197).
|
||||
|
||||
|
||||
test1111
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
||||
PYTHON_VERSION="3.7.14"
|
||||
PYTHON_VERSION="3.7.13"
|
||||
PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage"
|
||||
APPIMAGE_DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
|
||||
APPDIR="$APPIMAGE_DIR/squashfs-root"
|
||||
|
@ -77,8 +77,7 @@ install_monkey_island_python_dependencies() {
|
|||
log_message "Installing island requirements"
|
||||
|
||||
log_message "Installing pipenv"
|
||||
"$APPDIR"/AppRun -m pip install pipenv==2022.7.4 || handle_error
|
||||
export CI=1
|
||||
"$APPDIR"/AppRun -m pip install pipenv || handle_error
|
||||
|
||||
log_message "Installing dependencies"
|
||||
pushd "$BUILD_DIR/monkey_island" || handle_error
|
||||
|
|
|
@ -4,10 +4,9 @@ FROM bitnami/python:3.7 as builder
|
|||
COPY ./monkey /monkey
|
||||
WORKDIR /monkey
|
||||
RUN virtualenv .
|
||||
RUN export CI=1
|
||||
RUN . bin/activate && \
|
||||
cd monkey_island && \
|
||||
pip install pipenv==2022.7.4 && \
|
||||
pip install pipenv && \
|
||||
pipenv sync
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import json
|
||||
data = {
|
||||
'name' : 'myname',
|
||||
'age' : 100,
|
||||
}
|
||||
# separators:是分隔符的意思,参数意思分别为不同dict项之间的分隔符和dict项内key和value之间的分隔符,把:和,后面的空格都除去了.
|
||||
# dumps 将python对象字典转换为json字符串
|
||||
json_str = json.dumps(data, separators=(',', ':'))
|
||||
print(type(json_str), json_str)
|
||||
|
||||
# loads 将json字符串转化为python对象字典
|
||||
pyton_obj = json.loads(json_str)
|
||||
print(type(pyton_obj), pyton_obj)
|
|
@ -1,2 +1,2 @@
|
|||
baseURL = "https://monkey-documentation.website-us-southeast-1.linodeobjects.com"
|
||||
canonifyURLs = false
|
||||
baseURL = "https://www.guardicore.com/infectionmonkey/docs/"
|
||||
canonifyURLs = true
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
baseURL = "http://monkey-documentation-staging.website-us-southeast-1.linodeobjects.com"
|
||||
canonifyURLs = false
|
||||
baseURL = "http://staging-infectionmonkey.temp312.kinsta.cloud/docs/"
|
||||
canonifyURLs = true
|
||||
|
|
|
@ -36,7 +36,7 @@ Below are some of the most common questions we receive about the Infection Monke
|
|||
|
||||
## Where can I get the latest version of the Infection Monkey?
|
||||
|
||||
For the latest **stable** release, visit [our downloads page](https://www.akamai.com/infectionmonkey#download). **This is the recommended and supported version**!
|
||||
For the latest **stable** release, visit [our downloads page](https://www.guardicore.com/infectionmonkey/#download). **This is the recommended and supported version**!
|
||||
|
||||
If you want to see what has changed between versions, refer to the [releases page on GitHub](https://github.com/guardicore/monkey/releases). For the latest development version, visit the [develop version on GitHub](https://github.com/guardicore/monkey/tree/develop).
|
||||
|
||||
|
@ -81,6 +81,7 @@ Monkey in the newly created folder.
|
|||
|
||||
## Reset the Monkey Island password
|
||||
|
||||
|
||||
{{% notice warning %}}
|
||||
If you reset the credentials, the database will be cleared. Any findings of the Infection Monkey from previous runs will be lost. <br/><br/>
|
||||
However, you can save the Monkey's existing configuration by logging in with your current credentials and clicking on the **Export config** button on the configuration page.
|
||||
|
@ -159,25 +160,8 @@ If internet access is available, the Infection Monkey will use the internet for
|
|||
|
||||
The Monkey performs queries out to the Internet on two separate occasions:
|
||||
|
||||
1. The Infection Monkey agent checks if it has internet access by performing
|
||||
requests to pre-configured domains. By default, these domains are
|
||||
`monkey.guardicore.com` and `www.google.com`, which can be changed. The
|
||||
request doesn't include any extra information - it's a GET request with no
|
||||
extra parameters. Since the Infection Monkey is 100% open-source, you can
|
||||
find the domains in the configuration
|
||||
[here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152)
|
||||
and the code that performs the internet check
|
||||
[here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123).
|
||||
This **IS NOT** used for statistics collection.
|
||||
1. After the Monkey Island starts it sends a GET request with current
|
||||
deployment type to the update server to fetch the latest version and a
|
||||
download link for it. This information is used by the Monkey Island to
|
||||
suggest an update if one is available. No information gets collected during
|
||||
this process.
|
||||
1. After the Monkey Island starts it sends a GET request to the analytics
|
||||
server with your deployment type and a version number. This information gets
|
||||
collected on the analytics server. It is used to understand which deployment
|
||||
types/versions are no longer used and can be deprecated.
|
||||
1. The Infection Monkey agent checks if it has internet access by performing requests to pre-configured domains. By default, these domains are `monkey.guardicore.com` and `www.google.com`, which can be changed. The request doesn't include any extra information - it's a GET request with no extra parameters. Since the Infection Monkey is 100% open-source, you can find the domains in the configuration [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152) and the code that performs the internet check [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123). This **IS NOT** used for statistics collection.
|
||||
1. After installing the Monkey Island, it sends a request to check for updates on `updates.infectionmonkey.com`. The request doesn't include any PII other than the IP address of the request. It also includes the server's deployment type (e.g., Windows Server, Debian Package, AWS Marketplace) and the server's version (e.g., "1.6.3"), so we can check if we have an update available for this type of deployment. Since the Infection Monkey is 100% open-source, you can inspect the code that performs this [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/monkey_island/cc/services/version_update.py#L37). This **IS** used for statistics collection. However, due to this data's anonymous nature, we use this to get an aggregate assumption of how many deployments we see over a specific time period - it's not used for "personal" tracking.
|
||||
|
||||
## Logging and how to find logs
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ draft: false
|
|||
The Infection Monkey is an open-source breach and attack simulation tool for testing a data center's resiliency to perimeter breaches and internal server infection.
|
||||
Infection Monkey will help you validate existing security solutions and will provide a view of the internal network from an attacker's perspective.
|
||||
|
||||
Infection Monkey is free and can be downloaded from [our homepage](https://www.akamai.com/infectionmonkey).
|
||||
Infection Monkey is free and can be downloaded from [our homepage](https://infectionmonkey.com/).
|
||||
|
||||
![Infection Monkey Documentation Hub Logo](/images/monkey-teacher.svg?height=400px "Infection Monkey Documentation Hub Logo")
|
||||
|
||||
|
@ -40,7 +40,7 @@ A more in-depth description of reports generated can be found in the [reports do
|
|||
|
||||
## Getting Started
|
||||
|
||||
If you haven't downloaded Infection Monkey yet you can do so [from our homepage](https://www.akamai.com/infectionmonkey#download). After downloading the Monkey, install it using one of our [setup guides]({{< ref "/setup" >}}), and read our [getting started guide]({{< ref "/usage/getting-started" >}}) for a quick-start on Monkey!
|
||||
If you haven't downloaded Infection Monkey yet you can do so [from our homepage](https://www.guardicore.com/infectionmonkey/#download). After downloading the Monkey, install it using one of our [setup guides]({{< ref "/setup" >}}), and read our [getting started guide]({{< ref "/usage/getting-started" >}}) for a quick-start on Monkey!
|
||||
|
||||
## Support and community
|
||||
|
||||
|
|
|
@ -38,6 +38,6 @@ We always want to improve the core Infection Monkey code to make it smaller, fas
|
|||
|
||||
### Documentation 📚
|
||||
|
||||
Every project requires excellent documentation. The Infection Monkey is no different. Please feel free to open pull requests with suggestions, improvements or issues and ask us to document various parts of the Monkey.
|
||||
Every project requires excellent documentation. The Infection Monkey is no different. Please feel free to open pull requests with suggestions, improvements or issues and asking us to document various parts of the Monkey.
|
||||
|
||||
The Infection Monkey's documentation is stored in the `/docs/content` directory.
|
||||
|
|
|
@ -14,11 +14,11 @@ The Infection Monkey has development tutorials that use [`swimm.io`](https://swi
|
|||
|
||||
First, [sign up for swimm's beta](https://swimm.io/sign-beta). `swimm` is free for open-source projects, but as they're still in beta you'll need to sign up in order to download it.
|
||||
|
||||
After you've downloaded and installed `swimm`, open a shell in the Infection Monkey repo folder and run:
|
||||
After you've downloaded and installed `swimm`, open a shell in the Infeciton Monkey repo folder and run:
|
||||
|
||||
```shell script
|
||||
swimm start
|
||||
```
|
||||
```
|
||||
|
||||
A local web server with the currently available tutorials should show up, and will look something like this:
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@ date = 2020-05-26T20:55:04+03:00
|
|||
weight = 30
|
||||
chapter = true
|
||||
pre = '<i class="fas fa-layer-group"></i> '
|
||||
tags = ["reference"]
|
||||
tags = ["reference"]
|
||||
+++
|
||||
|
||||
# Reference
|
||||
|
||||
Find detailed information about the Infection Monkey:
|
||||
Find detailed information about the Infection Monkey.
|
||||
|
||||
{{% children %}}
|
||||
|
|
|
@ -23,7 +23,7 @@ The location of the data directory is set in the `data_dir` field in the
|
|||
`server_config.json` file.
|
||||
|
||||
1. [Create a custom server_config.json file](../server_configuration) and set the `data_dir` field. Its
|
||||
contents will look like this:
|
||||
contents will look like:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
@ -7,4 +7,4 @@ tags: ["exploit", "windows"]
|
|||
|
||||
### Description
|
||||
|
||||
For this exploit, the Infection Monkey will try to brute force into an MsSQL server and use an insecure configuration to execute commands on the server.
|
||||
For this exploit, the Infection Monkey will try to brute force into a MsSQL server and use an insecure configuration to execute commands on the server.
|
||||
|
|
|
@ -22,7 +22,8 @@ The PowerShell exploiter can be run from both Linux and Windows attackers. On
|
|||
Windows attackers, the exploiter has the ability to use the cached username
|
||||
and/or password from the current user. On both Linux and Windows attackers, the
|
||||
exploiter uses all combinations of the [user-configured usernames and
|
||||
passwords]({{< ref "/usage/configuration/basic-credentials" >}}), as well as LM or NT hashes that have been collected. Different combinations of
|
||||
passwords]({{< ref "/usage/configuration/basic-credentials" >}}), as well as
|
||||
and LM or NT hashes that have been collected. Different combinations of
|
||||
credentials are attempted in the following order:
|
||||
|
||||
1. **Cached username and password (Windows attacker only)** - The exploiter will
|
||||
|
|
|
@ -21,10 +21,10 @@ is, therefore, **not** enabled by default.
|
|||
|
||||
During successful exploitation, the Zerologon exploiter:
|
||||
|
||||
* Will temporarily change the target domain controller's password.
|
||||
* May break the target domain controller's communication with other systems in the network, affecting functionality.
|
||||
* May change the administrator's password.
|
||||
* Will *attempt* to revert all changes.
|
||||
* will temporarily change the target domain controller's password.
|
||||
* may break the target domain controller's communication with other systems in the network, affecting functionality.
|
||||
* may change the administrator's password.
|
||||
* will *attempt* to revert all changes.
|
||||
|
||||
While the Zerologon exploiter is usually successful in reverting its changes
|
||||
and restoring the original passwords, it sometimes fails. Restoring passwords
|
||||
|
@ -58,17 +58,17 @@ to regain access to the system.
|
|||
|
||||
#### Use Reset-ComputerMachinePassword
|
||||
|
||||
If you are able to log in as the administrator, you can use the
|
||||
If you are able to login as the administrator, you can use the
|
||||
[Reset-ComputerMachinePassword](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/reset-computermachinepassword?view=powershell-5.1)
|
||||
powershell command to restore the domain controller's password.
|
||||
|
||||
|
||||
#### Try a Zerologon password restoration tool
|
||||
#### Try a zerologon password restoration tool
|
||||
If all other approaches fail, you can try the tools and steps found
|
||||
[here](https://github.com/risksense/zerologon).
|
||||
|
||||
|
||||
|
||||
### Note
|
||||
### Notes
|
||||
|
||||
* The Infection Monkey exploiter implementation is based on implementations by [@dirkjanm](https://github.com/dirkjanm/CVE-2020-1472/) and [@risksense](https://github.com/risksense/zerologon).
|
||||
|
|
|
@ -9,7 +9,7 @@ tags = ["reference", "exploit"]
|
|||
|
||||
# Exploiters
|
||||
|
||||
The Infection Monkey uses various remote code execution (RCE) exploiters. To our best knowledge, most of these pose no risk to performance or services on victim machines. This documentation serves as a quick introduction to the exploiters currently implemented and the vulnerabilities they use:
|
||||
The Infection Monkey uses various remote code execution (RCE) exploiters. To our best knowledge, most of these pose no risk to performance or services on victim machines. This documentation serves as a quick introduction to the exploiters currently implemented and the vulnerabilities they use.
|
||||
|
||||
{{% children %}}
|
||||
|
||||
|
|
|
@ -8,6 +8,6 @@ pre = "<i class='fas fa-scroll'></i> "
|
|||
|
||||
# Infection Monkey's Reports
|
||||
|
||||
The Infection Monkey offers four reports:
|
||||
The Infection Monkey offers three reports:
|
||||
|
||||
{{% children description=true style="p"%}}
|
||||
|
|
|
@ -18,7 +18,7 @@ Watch the overview video:
|
|||
|
||||
## How to use the report
|
||||
|
||||
The MITRE ATT&CK report is centered around the ATT&CK matrix:
|
||||
The MITRE ATT&CK report is centred around the ATT&CK matrix:
|
||||
|
||||
![MITRE Report](/images/usage/reports/mitre-report-0.png "MITRE Report")
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ This diagram provides you with a quick glance at how your organization scores on
|
|||
|
||||
![Zero Trust Report summary](/images/usage/reports/ztreport1.png "Zero Trust Report summary")
|
||||
|
||||
## Test results
|
||||
## Test Results
|
||||
|
||||
This section shows how your network fared against each of the tests the Infection Monkey ran. The tests are ordered by Zero Trust pillar, so you can quickly navigate to the category you want to prioritize.
|
||||
|
||||
|
|
|
@ -10,4 +10,4 @@ pre = '<i class="fas fa-users-cog"></i> '
|
|||
|
||||
If you're new to the Infection Monkey, check out our [Getting Started](getting-started) page.
|
||||
|
||||
If you haven't downloaded the Infection Monkey yet, {{% button href="https://www.akamai.com/infectionmonkey#download" icon="fas fa-download" %}}Get Infection Monkey here{{% /button %}}!
|
||||
If you haven't downloaded the Infection Monkey yet, {{% button href="https://www.guardicore.com/infectionmonkey/#download" icon="fas fa-download" %}}Get Infection Monkey here{{% /button %}}!
|
||||
|
|
|
@ -8,7 +8,5 @@ description: "Configure settings related to the Monkey's network activity."
|
|||
Here you can control multiple important settings, such as:
|
||||
|
||||
* Network propagation depth - How many hops from the base machine will the Infection Monkey spread?
|
||||
* Scan Agent's networks - Should the Infection Monkey attempt to attack any machine in its subnet?
|
||||
|
||||
_Be careful when using this option. If a machine is connected to a public network, then the agent will scan the public network!_
|
||||
* Local network scan - Should the Infection Monkey attempt to attack any machine in its subnet?
|
||||
* Scanner IP/subnet list - Which specific IP ranges should the Infection Monkey should try to attack?
|
||||
|
|
|
@ -6,7 +6,7 @@ weight: 100
|
|||
pre: "<i class='fas fa-certificate'></i> "
|
||||
---
|
||||
|
||||
The official distribution of Infection Monkey is compiled and supplied by Guardicore ([download from our official site here](https://www.akamai.com/infectionmonkey#download)). The team signs all software packages to certify that a particular Infection Monkey package is a valid and unaltered Infection Monkey release. Before installing Monkey, you should validate the package using the SHA-256 checksum.
|
||||
The official distribution of Infection Monkey is compiled and supplied by Guardicore ([download from our official site here](https://www.guardicore.com/infectionmonkey/#download)). The team signs all software packages to certify that a particular Infection Monkey package is a valid and unaltered Infection Monkey release. Before installing Monkey, you should validate the package using the SHA-256 checksum.
|
||||
|
||||
## How to get SHA-256 checksum
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ In order for the Infection Monkey to successfully view your instances, you'll ne
|
|||
|
||||
#### Creating a custom IAM role
|
||||
|
||||
Go to the [AWS IAM roles dashboard](https://console.aws.amazon.com/iam/home?#/roles) and create a new IAM role for EC2. The role will need to have some specific permissions (see Appendix A), but you can just create a role with the `AmazonEC2RoleforSSM`, `AWSSecurityHubFullAccess` and `AmazonSSMFullAccess` pre-made permissions. In the end it should look something like this:
|
||||
Go to the [AWS IAM roles dashboard](https://console.aws.amazon.com/iam/home?#/roles) and create a new IAM role for EC2. The role will need to have some specific permissions (see Appendix A), but you can just create a role with the `AmazonEC2RoleforSSM`, `AWSSecurityHubFullAccess` and `AmazonSSMFullAccess` pre-made permissions. In the end it should like something like this:
|
||||
|
||||
![Creating a custom IAM role](/images/usage/integrations/monkey-island-aws-screenshot-3.png "Creating a custom IAM role")
|
||||
|
||||
|
@ -72,7 +72,7 @@ After you click on **Run on AWS machine of your choice** you can choose one of t
|
|||
|
||||
## Notes
|
||||
|
||||
- The machines that can use IAM roles and be listed MUST be internet connected (or you can set up a proxy for IAM). This is standard AWS practice and you can read about it (and about how to set up the required proxy machines) in the AWS IAM documentation.
|
||||
- The machines which can use IAM roles and be listed MUST be internet connected (or you can set up a proxy for IAM). This is standard AWS practice and you can read about it (and about how to set up the required proxy machines) in the AWS IAM documentation.
|
||||
- You can view the Infection Monkey in [the AWS marketplace](https://aws.amazon.com/marketplace/pp/B07B3J7K6D).
|
||||
|
||||
### Appendix A: Specific policy permissions required
|
||||
|
|
|
@ -9,16 +9,16 @@ weight: 5
|
|||
## Overview
|
||||
|
||||
Numerous attack techniques (from phishing to dumpster diving) might result in a credential leak,
|
||||
which can be **extremely costly** as demonstrated in our report [IResponse to IEncrypt](https://web.archive.org/web/20210117224801/https://www.guardicore.com/2019/04/iresponse-to-iencrypt/).
|
||||
which can be **extremely costly** as demonstrated in our report [IResponse to IEncrypt](https://www.guardicore.com/2019/04/iresponse-to-iencrypt/).
|
||||
|
||||
The Infection Monkey can help you assess the impact of stolen credentials by automatically searching
|
||||
where bad actors can reuse these credentials in your network.
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Propagation -> Credentials** After setting up the Monkey Island, add your users' **real** credentials
|
||||
- **Exploits -> Credentials** After setting up the Monkey Island, add your users' **real** credentials
|
||||
(usernames and passwords) here. Don't worry; this sensitive data is not accessible, distributed or used in any way other than being sent to the Infection Monkey agents. You can easily eliminate it by resetting the configuration of your Monkey Island.
|
||||
- **Propagation -> Credentials -> SSH key pairs list** When enabled, the Infection Monkey automatically gathers SSH keys on the current system.
|
||||
- **Internal -> Exploits -> SSH keypair list** When enabled, the Infection Monkey automatically gathers SSH keys on the current system.
|
||||
For this to work, the Monkey Island or initial agent needs to access SSH key files.
|
||||
To make sure SSH keys were gathered successfully, refresh the page and check this configuration value after you run the Infection Monkey
|
||||
(content of keys will not be displayed, it will appear as `<Object>`).
|
||||
|
|
|
@ -8,21 +8,24 @@ weight: 3
|
|||
|
||||
## Overview
|
||||
|
||||
From the [Hex-Men campaign](https://web.archive.org/web/20210115171355/https://www.guardicore.com/2017/12/beware-the-hex-men/) that hit
|
||||
internet-facing DB servers to a [cryptomining operation that attacks WordPress sites](https://web.archive.org/web/20210115185135/https://www.guardicore.com/2018/06/operation-prowli-traffic-manipulation-cryptocurrency-mining-2/) or any other malicious campaign – attackers are now trying to go deeper into your network.
|
||||
From the [Hex-Men campaign](https://www.guardicore.com/2017/12/beware-the-hex-men/) that hit
|
||||
internet-facing DB servers to a [cryptomining operation that attacks WordPress sites](https://www.guardicore.com/2018/06/operation-prowli-traffic-manipulation-cryptocurrency-mining-2/) or any other malicious campaign – attackers are now trying to go deeper into your network.
|
||||
|
||||
Infection Monkey will help you assess the impact of a future breach by attempting to propagate within your internal network using service vulnerabilities, brute-forcing and other safe exploiters.
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Propagation -> Exploiters** Here you can review the exploits the Infection Monkey will be using. By default all
|
||||
- **Exploits -> Exploits** Here you can review the exploits the Infection Monkey will be using. By default all
|
||||
safe exploiters are selected.
|
||||
- **Propagation -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
|
||||
- **Propagation -> Network analysis -> Network** Make sure to properly configure the scope of the scan. You can select **Scan Agent's networks**
|
||||
- **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
|
||||
- **Network -> Scope** Make sure to properly configure the scope of the scan. You can select **Local network scan**
|
||||
and allow Monkey to propagate until maximum **Scan depth**(hop count) is reached, or you can fine tune it by providing
|
||||
specific network ranges in **Scan target list**. Scanning a local network is more realistic, but providing specific
|
||||
targets will make the scanning process substantially faster.
|
||||
- **(Optional) Propagation -> Network Analysis -> TCP scanner** Here you can add custom ports your organization is using.
|
||||
- **(Optional) Internal -> Network -> TCP scanner** Here you can add custom ports your organization is using.
|
||||
- **(Optional) Monkey -> Post-Breach Actions** If you only want to test propagation in the network, you can turn off
|
||||
all post-breach actions. These actions simulate an attacker's behavior after getting access to a new system but in no
|
||||
way helps the Infection Monkey exploit new machines.
|
||||
|
||||
![Exploiter selector](/images/usage/use-cases/network-breach.PNG "Exploiter selector")
|
||||
|
||||
|
|
|
@ -10,24 +10,25 @@ weight: 4
|
|||
|
||||
Segmentation is a method of creating secure zones in data centers and cloud deployments. It allows organizations to isolate workloads from one another and secure them individually, typically using policies. A useful way to test your company's segmentation effectiveness is to ensure that your network segments are properly separated (e.g., your development environment is isolated from your production environment and your applications are isolated from one another).
|
||||
|
||||
[Segmentation is key](https://www.akamai.com/products/akamai-segmentation/use-cases) to protecting your network. It can reduce the network's attack surface and minimize the damage caused during a breach.
|
||||
[Segmentation is key](https://www.guardicore.com/use-cases/micro-segmentation/) to protecting your network. It can reduce the network's attack surface and minimize the damage caused during a breach.
|
||||
|
||||
You can use the Infection Monkey's cross-segment traffic feature to verify that your network segmentation configuration is adequate. This way, you can ensure that, even if a bad actor breaches your defenses, they can't move laterally between segments.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Propagation -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
|
||||
- **Network -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
|
||||
subnets that should be segregated from each other. If any of the provided networks can reach each other, you'll see it
|
||||
in the security report.
|
||||
- **(Optional) Propagation -> Network analysis -> Network** You can disable **Scan Agent's networks** and leave all other options at the default setting if you only want to test for network segmentation without any lateral movement.
|
||||
- **(Optional) Network -> Scope** You can disable **Local network scan** and leave all other options at the default setting if you only want to test for network segmentation without any lateral movement.
|
||||
- **(Optional) Monkey -> Post-Breach Actions** If you only want to test segmentation in the network, you can turn off all post-breach actions. These actions simulate an attacker's behavior after getting access to a new system, so they might trigger your defense solutions and interrupt the segmentation test.
|
||||
|
||||
## Suggested run mode
|
||||
|
||||
Execute The Infection Monkey on machines in different subnetworks using the “Manual” run option.
|
||||
|
||||
Note that if the Infection Monkey can't communicate to the Monkey Island, it will
|
||||
not be able to send scan results, so make sure all machines can reach the Monkey Island.
|
||||
not be able to send scan results, so make sure all machines can reach the the Monkey Island.
|
||||
|
||||
![How to configure network segmentation testing](/images/usage/scenarios/segmentation-config.png "How to configure network segmentation testing")
|
||||
|
||||
|
|
|
@ -9,26 +9,37 @@ weight: 100
|
|||
## Overview
|
||||
This page provides additional information about configuring the Infection Monkey, tips and tricks and creative usage scenarios.
|
||||
|
||||
## Custom behaviour
|
||||
|
||||
If you want the Infection Monkey to run a specific script or tool after it breaches a machine, you can configure it in
|
||||
**Configuration -> Monkey -> Post-breach**. Input commands you want to execute in the corresponding fields.
|
||||
You can also upload files and call them through the commands you entered.
|
||||
|
||||
## Accelerate the test
|
||||
|
||||
To improve scanning speed you could **specify a subnet instead of scanning all of the local network**.
|
||||
|
||||
The following configuration values also have an impact on scanning speed:
|
||||
- **Propagation -> Credentials** - The more usernames and passwords you input, the longer it will take the Infection Monkey to scan machines that have
|
||||
- **Credentials** - The more usernames and passwords you input, the longer it will take the Infection Monkey to scan machines that have
|
||||
remote access services. The Infection Monkey agents try to stay elusive and leave a low impact, and thus brute-forcing takes longer than with loud conventional tools.
|
||||
- **Propagation -> Network analysis -> Network** - Scanning large networks with a lot of propagations can become unwieldy. Instead, try to scan your
|
||||
- **Network scope** - Scanning large networks with a lot of propagations can become unwieldy. Instead, try to scan your
|
||||
networks bit by bit with multiple runs.
|
||||
- **Propagation -> Network analysis -> TCP scanner** - Here you can trim down the list of ports the Infection Monkey tries to scan, improving performance.
|
||||
- **Post-breach actions** - If you only care about propagation, you can disable most of these.
|
||||
- **Internal -> TCP scanner** - Here you can trim down the list of ports the Infection Monkey tries to scan, improving performance.
|
||||
|
||||
## Combining different scenarios
|
||||
|
||||
The Infection Monkey is not limited to the scenarios mentioned in this section. Once you get the hang of configuring it, you might come up with your own use case or test all of the suggested scenarios at the same time! Whatever you do, the Infection Monkey's Security, ATT&CK and Zero Trust reports will be waiting for you with your results!
|
||||
|
||||
## Persistent scanning
|
||||
|
||||
Use **Monkey -> Persistent** scanning configuration section to either run periodic scans or increase the reliability of exploitations by running consecutive scans with the Infection Monkey.
|
||||
|
||||
## Credentials
|
||||
|
||||
Every network has its old "skeleton keys" that it should have long discarded. Configuring the Infection Monkey with old and stale passwords will enable you to ensure they were really discarded.
|
||||
|
||||
To add the old passwords, go to the Monkey Island's **Exploit password list** under **Propagation -> Credentials** and use the "+" button to add the old passwords to the configuration. For example, here we added a few extra passwords (and a username as well) to the configuration:
|
||||
To add the old passwords, go to the Monkey Island's **Exploit password list** under **Basic - Credentials** and use the "+" button to add the old passwords to the configuration. For example, here we added a few extra passwords (and a username as well) to the configuration:
|
||||
|
||||
![Exploit password and user lists](/images/usage/scenarios/user-password-lists.png "Exploit password and user lists")
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@ Want to assess your progress in achieving a Zero Trust network? The Infection Mo
|
|||
|
||||
## Configuration
|
||||
|
||||
- **Propagation -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
|
||||
- **Propagation -> Network analysis -> Network** Disable “Scan Agent's networks” and instead provide specific network ranges in the “Scan target list.”
|
||||
- **Propagation -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
|
||||
- **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
|
||||
- **Network -> Scope** Disable “Local network scan” and instead provide specific network ranges in the “Scan target list.”
|
||||
- **Network -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
|
||||
subnets that should be segregated from each other.
|
||||
|
||||
In general, other configuration value defaults should be good enough, but feel free to see the “Other” section for tips and tricks about more features and in-depth configuration parameters you can use.
|
||||
|
|
|
@ -37,25 +37,19 @@ To ensure minimum interference and easy recoverability, the ransomware
|
|||
simulation will only encrypt files contained in a user-specified directory. If
|
||||
no directory is specified, no files will be encrypted.
|
||||
|
||||
Infection Monkey appends the `.m0nk3y` file extension to files that it
|
||||
encrypts. You may optionally provide a custom file extension for Infection
|
||||
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/usage/scenarios/ransomware-config.png "Ransomware configuration")
|
||||
|
||||
### How are the files encrypted?
|
||||
|
||||
Files are "encrypted" in place with a simple bit flip. Encrypted files are
|
||||
renamed to have a file extension (`.m0nk3y` by default) appended to their
|
||||
names. This is a safe way to simulate encryption since it is easy to "decrypt"
|
||||
your files. You can simply perform a bit flip on the files again and rename
|
||||
them to remove the appended `.m0nk3y` extension.
|
||||
renamed to have `.m0nk3y` appended to their names. This is a safe way to
|
||||
simulate encryption since it is easy to "decrypt" your files. You can simply
|
||||
perform a bit flip on the files again and rename them to remove the appended
|
||||
`.m0nk3y` extension.
|
||||
|
||||
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,
|
||||
temporarily unusuable). 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
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 436 KiB After Width: | Height: | Size: 140 KiB |
|
@ -1,5 +1,4 @@
|
|||
from ipaddress import IPv4Address
|
||||
from typing import Collection, Iterable
|
||||
from typing import Iterable
|
||||
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
||||
|
@ -14,22 +13,15 @@ class CommunicationAnalyzer(Analyzer):
|
|||
|
||||
def analyze_test_results(self):
|
||||
self.log.clear()
|
||||
all_agents_communicated = True
|
||||
agent_ips = self._get_agent_ips()
|
||||
|
||||
all_monkeys_communicated = True
|
||||
for machine_ip in self.machine_ips:
|
||||
if self._agent_communicated_back(machine_ip, agent_ips):
|
||||
self.log.add_entry("Agent from {} communicated back".format(machine_ip))
|
||||
if not self.did_monkey_communicate_back(machine_ip):
|
||||
self.log.add_entry("Monkey from {} didn't communicate back".format(machine_ip))
|
||||
all_monkeys_communicated = False
|
||||
else:
|
||||
self.log.add_entry("Agent from {} didn't communicate back".format(machine_ip))
|
||||
all_agents_communicated = False
|
||||
self.log.add_entry("Monkey from {} communicated back".format(machine_ip))
|
||||
return all_monkeys_communicated
|
||||
|
||||
return all_agents_communicated
|
||||
|
||||
def _get_agent_ips(self) -> Collection[IPv4Address]:
|
||||
agents = self.island_client.get_agents()
|
||||
machines = self.island_client.get_machines()
|
||||
return {i.ip for a in agents for i in machines[a.machine_id].network_interfaces}
|
||||
|
||||
def _agent_communicated_back(self, machine_ip: str, agent_ips: Collection[IPv4Address]) -> bool:
|
||||
return IPv4Address(machine_ip) in agent_ips
|
||||
def did_monkey_communicate_back(self, machine_ip: str):
|
||||
query = {"ip_addresses": {"$elemMatch": {"$eq": machine_ip}}}
|
||||
return len(self.island_client.find_monkeys_in_db(query)) > 0
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from pprint import pformat
|
||||
from typing import List
|
||||
|
||||
from common.credentials import Credentials, LMHash, NTHash, Username
|
||||
from common.credentials import CredentialComponentType, Credentials
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
|
@ -34,13 +34,14 @@ class ZerologonAnalyzer(Analyzer):
|
|||
@staticmethod
|
||||
def _get_relevant_credentials(propagation_credentials: Credentials) -> List[str]:
|
||||
credentials_on_island = set()
|
||||
|
||||
for credentials in propagation_credentials:
|
||||
if isinstance(credentials.identity, Username):
|
||||
if credentials.identity.credential_type is CredentialComponentType.USERNAME:
|
||||
credentials_on_island.update([credentials.identity.username])
|
||||
if isinstance(credentials.secret, NTHash):
|
||||
credentials_on_island.update([credentials.secret.nt_hash.get_secret_value()])
|
||||
if isinstance(credentials.secret, LMHash):
|
||||
credentials_on_island.update([credentials.secret.lm_hash.get_secret_value()])
|
||||
if credentials.secret.credential_type is CredentialComponentType.NT_HASH:
|
||||
credentials_on_island.update([credentials.secret.nt_hash])
|
||||
if credentials.secret.credential_type is CredentialComponentType.LM_HASH:
|
||||
credentials_on_island.update([credentials.secret.lm_hash])
|
||||
|
||||
return list(credentials_on_island)
|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
from typing import Collection, Dict, Mapping, Set
|
||||
|
||||
import pytest
|
||||
|
||||
from envs.monkey_zoo.blackbox.gcp_test_machine_list import GCP_SINGLE_TEST_LIST
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
|
@ -18,6 +14,12 @@ def pytest_addoption(parser):
|
|||
default=False,
|
||||
help="Use for no interaction with the cloud.",
|
||||
)
|
||||
parser.addoption(
|
||||
"--skip-powershell-reuse",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Use to run PowerShell credentials reuse test.",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
@ -31,14 +33,15 @@ def no_gcp(request):
|
|||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def gcp_machines_to_start(request: pytest.FixtureRequest) -> Mapping[str, Collection[str]]:
|
||||
machines_to_start: Dict[str, Set[str]] = {}
|
||||
def machines_to_start(request):
|
||||
return request.config.getoption("-k")
|
||||
|
||||
enabled_tests = (test.name for test in request.node.items)
|
||||
machines_for_enabled_tests = (GCP_SINGLE_TEST_LIST[test] for test in enabled_tests)
|
||||
|
||||
for machine_dict in machines_for_enabled_tests:
|
||||
for zone, machines in machine_dict.items():
|
||||
machines_to_start.setdefault(zone, set()).update(machines)
|
||||
|
||||
return machines_to_start
|
||||
def pytest_runtest_setup(item):
|
||||
if "skip_powershell_reuse" in item.keywords and item.config.getoption(
|
||||
"--skip-powershell-reuse"
|
||||
):
|
||||
pytest.skip(
|
||||
"Skipping powershell credentials reuse test because "
|
||||
"--skip-powershell-cached flag isn't specified."
|
||||
)
|
||||
|
|
|
@ -11,18 +11,13 @@ GCP_TEST_MACHINE_LIST = {
|
|||
"tunneling-10",
|
||||
"tunneling-11",
|
||||
"tunneling-12",
|
||||
"tunneling-13",
|
||||
"zerologon-25",
|
||||
],
|
||||
"europe-west1-b": [
|
||||
"powershell-3-44",
|
||||
"powershell-3-45",
|
||||
"powershell-3-46",
|
||||
"powershell-3-47",
|
||||
"powershell-3-48",
|
||||
"credentials-reuse-14",
|
||||
"credentials-reuse-15",
|
||||
"credentials-reuse-16",
|
||||
"log4j-logstash-55",
|
||||
"log4j-logstash-56",
|
||||
"log4j-solr-49",
|
||||
|
@ -36,11 +31,7 @@ DEPTH_2_A = {
|
|||
"europe-west3-a": [
|
||||
"sshkeys-11",
|
||||
"sshkeys-12",
|
||||
],
|
||||
"europe-west1-b": [
|
||||
"powershell-3-46",
|
||||
"powershell-3-44",
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
@ -61,22 +52,21 @@ DEPTH_3_A = {
|
|||
"tunneling-9",
|
||||
"tunneling-10",
|
||||
"tunneling-11",
|
||||
"tunneling-12",
|
||||
"mimikatz-15",
|
||||
],
|
||||
"europe-west1-b": [
|
||||
"powershell-3-45",
|
||||
"powershell-3-46",
|
||||
"powershell-3-47",
|
||||
"powershell-3-48",
|
||||
],
|
||||
}
|
||||
|
||||
DEPTH_4_A = {
|
||||
"europe-west3-a": [
|
||||
"tunneling-9",
|
||||
"tunneling-10",
|
||||
"tunneling-12",
|
||||
"tunneling-13",
|
||||
],
|
||||
POWERSHELL_EXPLOITER_REUSE = {
|
||||
"europe-west1-b": [
|
||||
"powershell-3-46",
|
||||
]
|
||||
}
|
||||
|
||||
ZEROLOGON = {
|
||||
|
@ -85,14 +75,6 @@ ZEROLOGON = {
|
|||
],
|
||||
}
|
||||
|
||||
CREDENTIALS_REUSE_SSH_KEY = {
|
||||
"europe-west1-b": [
|
||||
"credentials-reuse-14",
|
||||
"credentials-reuse-15",
|
||||
"credentials-reuse-16",
|
||||
],
|
||||
}
|
||||
|
||||
WMI_AND_MIMIKATZ = {
|
||||
"europe-west3-a": [
|
||||
"mimikatz-14",
|
||||
|
@ -106,9 +88,8 @@ GCP_SINGLE_TEST_LIST = {
|
|||
"test_depth_2_a": DEPTH_2_A,
|
||||
"test_depth_1_a": DEPTH_1_A,
|
||||
"test_depth_3_a": DEPTH_3_A,
|
||||
"test_depth_4_a": DEPTH_4_A,
|
||||
"test_powershell_exploiter_credentials_reuse": POWERSHELL_EXPLOITER_REUSE,
|
||||
"test_zerologon_exploiter": ZEROLOGON,
|
||||
"test_credentials_reuse_ssh_key": CREDENTIALS_REUSE_SSH_KEY,
|
||||
"test_wmi_and_mimikatz_exploiters": WMI_AND_MIMIKATZ,
|
||||
"test_smb_pth": SMB_PTH,
|
||||
}
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import List, Mapping, Sequence, Union
|
||||
from typing import Sequence, Union
|
||||
|
||||
from bson import json_util
|
||||
|
||||
from common.agent_configuration import AgentConfiguration
|
||||
from common.credentials import Credentials
|
||||
from common.types import AgentID, MachineID
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
|
||||
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration
|
||||
from monkey_island.cc.models import Agent, Machine
|
||||
|
||||
SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
|
||||
GET_AGENTS_ENDPOINT = "api/agents"
|
||||
GET_LOG_ENDPOINT = "api/agent-logs"
|
||||
GET_MACHINES_ENDPOINT = "api/machines"
|
||||
MONKEY_TEST_ENDPOINT = "api/test/monkey"
|
||||
TELEMETRY_TEST_ENDPOINT = "api/test/telemetry"
|
||||
LOG_TEST_ENDPOINT = "api/test/log"
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -33,27 +31,18 @@ class MonkeyIslandClient(object):
|
|||
|
||||
def get_propagation_credentials(self) -> Sequence[Credentials]:
|
||||
response = self.requests.get("api/propagation-credentials")
|
||||
return [Credentials(**credentials) for credentials in response.json()]
|
||||
return [Credentials.from_mapping(credentials) for credentials in response.json()]
|
||||
|
||||
@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(
|
||||
response = self.requests.post_json(
|
||||
"api/agent-configuration",
|
||||
json=test_configuration.agent_configuration.dict(simplify=True),
|
||||
json=AgentConfiguration.to_mapping(test_configuration.agent_configuration),
|
||||
)
|
||||
if response.ok:
|
||||
LOGGER.info("Configuration is imported.")
|
||||
|
@ -62,11 +51,11 @@ class MonkeyIslandClient(object):
|
|||
assert False
|
||||
|
||||
@avoid_race_condition
|
||||
def _import_credentials(self, propagation_credentials: List[Credentials]):
|
||||
def _import_credentials(self, propagation_credentials: Credentials):
|
||||
serialized_propagation_credentials = [
|
||||
credentials.dict(simplify=True) for credentials in propagation_credentials
|
||||
Credentials.to_mapping(credentials) for credentials in propagation_credentials
|
||||
]
|
||||
response = self.requests.put_json(
|
||||
response = self.requests.post_json(
|
||||
"/api/propagation-credentials/configured-credentials",
|
||||
json=serialized_propagation_credentials,
|
||||
)
|
||||
|
@ -91,9 +80,8 @@ class MonkeyIslandClient(object):
|
|||
|
||||
@avoid_race_condition
|
||||
def kill_all_monkeys(self):
|
||||
# TODO change this request, because monkey-control resource got removed
|
||||
response = self.requests.post_json(
|
||||
"api/agent-signals/terminate-all-agents", json={"terminate_time": time.time()}
|
||||
"api/monkey-control/stop-all-agents", json={"kill_time": time.time()}
|
||||
)
|
||||
if response.ok:
|
||||
LOGGER.info("Killing all monkeys after the test.")
|
||||
|
@ -125,19 +113,27 @@ class MonkeyIslandClient(object):
|
|||
assert False
|
||||
|
||||
def _reset_credentials(self):
|
||||
if self.requests.put_json("api/propagation-credentials/configured-credentials", json=[]).ok:
|
||||
if self.requests.delete("api/propagation-credentials/configured-credentials").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.")
|
||||
if self.requests.post("api/island-mode", data='{"mode": "unset"}').ok:
|
||||
LOGGER.info("Resseting island mode after the test.")
|
||||
else:
|
||||
LOGGER.error("Failed to reset island mode")
|
||||
assert False
|
||||
|
||||
def find_monkeys_in_db(self, query):
|
||||
if query is None:
|
||||
raise TypeError
|
||||
response = self.requests.get(
|
||||
MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
|
||||
)
|
||||
return MonkeyIslandClient.get_test_query_results(response)
|
||||
|
||||
def find_telems_in_db(self, query: dict):
|
||||
if query is None:
|
||||
raise TypeError
|
||||
|
@ -146,21 +142,17 @@ class MonkeyIslandClient(object):
|
|||
)
|
||||
return MonkeyIslandClient.get_test_query_results(response)
|
||||
|
||||
def get_agents(self) -> Sequence[Agent]:
|
||||
response = self.requests.get(GET_AGENTS_ENDPOINT)
|
||||
def get_all_monkeys_from_db(self):
|
||||
response = self.requests.get(
|
||||
MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(None)
|
||||
)
|
||||
return MonkeyIslandClient.get_test_query_results(response)
|
||||
|
||||
return [Agent(**a) for a in response.json()]
|
||||
|
||||
def get_machines(self) -> Mapping[MachineID, Machine]:
|
||||
response = self.requests.get(GET_MACHINES_ENDPOINT)
|
||||
machines = (Machine(**m) for m in response.json())
|
||||
|
||||
return {m.id: m for m in machines}
|
||||
|
||||
def get_agent_log(self, agent_id: AgentID) -> str:
|
||||
response = self.requests.get(f"{GET_LOG_ENDPOINT}/{agent_id}")
|
||||
|
||||
return response.json()
|
||||
def find_log_in_db(self, query):
|
||||
response = self.requests.get(
|
||||
LOG_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
|
||||
)
|
||||
return MonkeyIslandClient.get_test_query_results(response)
|
||||
|
||||
@staticmethod
|
||||
def form_find_query_for_request(query: Union[dict, None]) -> dict:
|
||||
|
@ -171,5 +163,5 @@ class MonkeyIslandClient(object):
|
|||
return json.loads(response.content)["results"]
|
||||
|
||||
def is_all_monkeys_dead(self):
|
||||
agents = self.get_agents()
|
||||
return all((a.stop_time is not None for a in agents))
|
||||
query = {"dead": False}
|
||||
return len(self.find_monkeys_in_db(query)) == 0
|
||||
|
|
|
@ -39,7 +39,7 @@ class MonkeyIslandRequests(object):
|
|||
|
||||
def get_jwt_from_server(self):
|
||||
resp = requests.post( # noqa: DUO123
|
||||
self.addr + "api/authenticate",
|
||||
self.addr + "api/auth",
|
||||
json={"username": ISLAND_USERNAME, "password": ISLAND_PASSWORD},
|
||||
verify=False,
|
||||
)
|
||||
|
@ -49,7 +49,7 @@ class MonkeyIslandRequests(object):
|
|||
|
||||
def try_set_island_to_credentials(self):
|
||||
resp = requests.post( # noqa: DUO123
|
||||
self.addr + "api/register",
|
||||
self.addr + "api/registration",
|
||||
json={"username": ISLAND_USERNAME, "password": ISLAND_PASSWORD},
|
||||
verify=False,
|
||||
)
|
||||
|
@ -82,18 +82,6 @@ class MonkeyIslandRequests(object):
|
|||
self.addr + url, data=data, headers=self.get_jwt_header(), verify=False
|
||||
)
|
||||
|
||||
@_Decorators.refresh_jwt_token
|
||||
def put(self, url, data):
|
||||
return requests.put( # noqa: DUO123
|
||||
self.addr + url, data=data, headers=self.get_jwt_header(), verify=False
|
||||
)
|
||||
|
||||
@_Decorators.refresh_jwt_token
|
||||
def put_json(self, url, json: Dict):
|
||||
return requests.put( # noqa: DUO123
|
||||
self.addr + url, json=json, headers=self.get_jwt_header(), verify=False
|
||||
)
|
||||
|
||||
@_Decorators.refresh_jwt_token
|
||||
def post_json(self, url, json: Dict):
|
||||
return requests.post( # noqa: DUO123
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MonkeyLog(object):
|
||||
def __init__(self, monkey, log_dir_path):
|
||||
self.monkey = monkey
|
||||
self.log_dir_path = log_dir_path
|
||||
|
||||
def download_log(self, island_client):
|
||||
log = island_client.find_log_in_db({"monkey_id": ObjectId(self.monkey["_id"])})
|
||||
if not log:
|
||||
LOGGER.error("Log for monkey {} not found".format(self.monkey["ip_addresses"][0]))
|
||||
return False
|
||||
else:
|
||||
self.write_log_to_file(log)
|
||||
return True
|
||||
|
||||
def write_log_to_file(self, log):
|
||||
with open(self.get_log_path_for_monkey(self.monkey), "w") as log_file:
|
||||
log_file.write(MonkeyLog.parse_log(log))
|
||||
|
||||
@staticmethod
|
||||
def parse_log(log):
|
||||
log = log.strip('"')
|
||||
log = log.replace("\\n", "\n ")
|
||||
return log
|
||||
|
||||
@staticmethod
|
||||
def get_filename_for_monkey_log(monkey):
|
||||
return "{}.txt".format(monkey["ip_addresses"][0])
|
||||
|
||||
def get_log_path_for_monkey(self, monkey):
|
||||
return os.path.join(self.log_dir_path, MonkeyLog.get_filename_for_monkey_log(monkey))
|
|
@ -1,65 +1,25 @@
|
|||
import logging
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from typing import List, Mapping
|
||||
|
||||
from common.types import MachineID
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
from monkey_island.cc.models import Agent, Machine
|
||||
from envs.monkey_zoo.blackbox.log_handlers.monkey_log import MonkeyLog
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MonkeyLogsDownloader(object):
|
||||
def __init__(self, island_client: MonkeyIslandClient, log_dir_path: str):
|
||||
def __init__(self, island_client, log_dir_path):
|
||||
self.island_client = island_client
|
||||
self.log_dir_path = Path(log_dir_path)
|
||||
self.monkey_log_paths: List[Path] = []
|
||||
self.log_dir_path = log_dir_path
|
||||
self.monkey_log_paths = []
|
||||
|
||||
def download_monkey_logs(self):
|
||||
try:
|
||||
LOGGER.info("Downloading each monkey log.")
|
||||
LOGGER.info("Downloading each monkey log.")
|
||||
all_monkeys = self.island_client.get_all_monkeys_from_db()
|
||||
for monkey in all_monkeys:
|
||||
downloaded_log_path = self._download_monkey_log(monkey)
|
||||
if downloaded_log_path:
|
||||
self.monkey_log_paths.append(downloaded_log_path)
|
||||
|
||||
agents = self.island_client.get_agents()
|
||||
machines = self.island_client.get_machines()
|
||||
|
||||
download_threads: List[Thread] = []
|
||||
|
||||
# TODO: Does downloading logs concurrently still improve performance after resolving
|
||||
# https://github.com/guardicore/monkey/issues/2383?
|
||||
for agent in agents:
|
||||
t = Thread(target=self._download_log, args=(agent, machines), daemon=True)
|
||||
t.start()
|
||||
download_threads.append(t)
|
||||
|
||||
for thread in download_threads:
|
||||
thread.join()
|
||||
|
||||
except Exception as err:
|
||||
LOGGER.exception(err)
|
||||
|
||||
def _download_log(self, agent: Agent, machines: Mapping[MachineID, Machine]):
|
||||
log_file_path = self._get_log_file_path(agent, machines)
|
||||
log_contents = self.island_client.get_agent_log(agent.id)
|
||||
|
||||
MonkeyLogsDownloader._write_log_to_file(log_file_path, log_contents)
|
||||
|
||||
self.monkey_log_paths.append(log_file_path)
|
||||
|
||||
def _get_log_file_path(self, agent: Agent, machines: Mapping[MachineID, Machine]) -> Path:
|
||||
try:
|
||||
machine_ip = machines[agent.machine_id].network_interfaces[0].ip
|
||||
except IndexError:
|
||||
LOGGER.error(f"Machine with ID {agent.machine_id} has no network interfaces")
|
||||
machine_ip = "UNKNOWN"
|
||||
|
||||
start_time = agent.start_time.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
return self.log_dir_path / f"agent_{start_time}_{machine_ip}.log"
|
||||
|
||||
@staticmethod
|
||||
def _write_log_to_file(log_file_path: Path, log_contents: str):
|
||||
LOGGER.debug(f"Writing {len(log_contents)} bytes to {log_file_path}")
|
||||
|
||||
with open(log_file_path, "w") as f:
|
||||
f.write(log_contents)
|
||||
def _download_monkey_log(self, monkey):
|
||||
log_handler = MonkeyLog(monkey, self.log_dir_path)
|
||||
download_successful = log_handler.download_log(self.island_client)
|
||||
return log_handler.get_log_path_for_monkey(monkey) if download_successful else None
|
||||
|
|
|
@ -6,15 +6,18 @@ import pytest
|
|||
|
||||
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
|
||||
from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer
|
||||
from envs.monkey_zoo.blackbox.gcp_test_machine_list import (
|
||||
GCP_SINGLE_TEST_LIST,
|
||||
GCP_TEST_MACHINE_LIST,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
from envs.monkey_zoo.blackbox.island_client.test_configuration_parser import get_target_ips
|
||||
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
|
||||
from envs.monkey_zoo.blackbox.test_configurations import (
|
||||
credentials_reuse_ssh_key_test_configuration,
|
||||
depth_1_a_test_configuration,
|
||||
depth_2_a_test_configuration,
|
||||
depth_3_a_test_configuration,
|
||||
depth_4_a_test_configuration,
|
||||
powershell_credentials_reuse_test_configuration,
|
||||
smb_pth_test_configuration,
|
||||
wmi_mimikatz_test_configuration,
|
||||
zerologon_test_configuration,
|
||||
|
@ -35,20 +38,21 @@ LOGGER = logging.getLogger(__name__)
|
|||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def GCPHandler(request, no_gcp, gcp_machines_to_start):
|
||||
def GCPHandler(request, no_gcp, machines_to_start):
|
||||
if not no_gcp:
|
||||
LOGGER.info(f"MACHINES TO START: {gcp_machines_to_start}")
|
||||
|
||||
list_machines = GCP_TEST_MACHINE_LIST
|
||||
if machines_to_start:
|
||||
list_machines = GCP_SINGLE_TEST_LIST[machines_to_start]
|
||||
try:
|
||||
initialize_gcp_client()
|
||||
start_machines(gcp_machines_to_start)
|
||||
start_machines(list_machines)
|
||||
except Exception as e:
|
||||
LOGGER.error("GCP Handler failed to initialize: %s." % e)
|
||||
pytest.exit("Encountered an error while starting GCP machines. Stopping the tests.")
|
||||
wait_machine_bootup()
|
||||
|
||||
def fin():
|
||||
stop_machines(gcp_machines_to_start)
|
||||
stop_machines(list_machines)
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
|
@ -124,9 +128,13 @@ class TestMonkeyBlackbox:
|
|||
island_client, depth_3_a_test_configuration, "Depth3A test suite"
|
||||
)
|
||||
|
||||
def test_depth_4_a(self, island_client):
|
||||
# Not grouped because can only be ran on windows
|
||||
@pytest.mark.skip_powershell_reuse
|
||||
def test_powershell_exploiter_credentials_reuse(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, depth_4_a_test_configuration, "Depth4A test suite"
|
||||
island_client,
|
||||
powershell_credentials_reuse_test_configuration,
|
||||
"PowerShell_Remoting_exploiter_credentials_reuse",
|
||||
)
|
||||
|
||||
# Not grouped because it's slow
|
||||
|
@ -154,11 +162,6 @@ class TestMonkeyBlackbox:
|
|||
log_handler=log_handler,
|
||||
).run()
|
||||
|
||||
def test_credentials_reuse_ssh_key(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, credentials_reuse_ssh_key_test_configuration, "Credentials_Reuse_SSH_Key"
|
||||
)
|
||||
|
||||
# Not grouped because conflicts with SMB.
|
||||
# Consider grouping when more depth 1 exploiters collide with group depth_1_a
|
||||
def test_wmi_and_mimikatz_exploiters(self, island_client):
|
||||
|
|
|
@ -2,8 +2,7 @@ from .test_configuration import TestConfiguration
|
|||
from .depth_1_a import depth_1_a_test_configuration
|
||||
from .depth_2_a import depth_2_a_test_configuration
|
||||
from .depth_3_a import depth_3_a_test_configuration
|
||||
from .depth_4_a import depth_4_a_test_configuration
|
||||
from .powershell_credentials_reuse import powershell_credentials_reuse_test_configuration
|
||||
from .smb_pth import smb_pth_test_configuration
|
||||
from .wmi_mimikatz import wmi_mimikatz_test_configuration
|
||||
from .zerologon import zerologon_test_configuration
|
||||
from .credentials_reuse_ssh_key import credentials_reuse_ssh_key_test_configuration
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
import dataclasses
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
from common.credentials import Credentials, Password, Username
|
||||
|
||||
from .noop import noop_test_configuration
|
||||
from .utils import (
|
||||
add_credential_collectors,
|
||||
add_exploiters,
|
||||
add_subnets,
|
||||
add_tcp_ports,
|
||||
replace_agent_configuration,
|
||||
replace_propagation_credentials,
|
||||
set_keep_tunnel_open_time,
|
||||
set_maximum_depth,
|
||||
)
|
||||
|
||||
|
||||
# Tests:
|
||||
# SSHCollector steals key from machine A(10.2.3.14),
|
||||
# then B(10.2.4.15) exploits C(10.2.5.16) with that key
|
||||
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
brute_force = [
|
||||
PluginConfiguration(name="SSHExploiter", options={}),
|
||||
]
|
||||
|
||||
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
|
||||
|
||||
|
||||
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
subnets = ["10.2.3.14", "10.2.4.15", "10.2.5.16"]
|
||||
return add_subnets(agent_configuration, subnets)
|
||||
|
||||
|
||||
def _add_credential_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
credential_collectors = [
|
||||
PluginConfiguration(name="SSHCollector", options={}),
|
||||
]
|
||||
|
||||
return add_credential_collectors(
|
||||
agent_configuration, credential_collectors=credential_collectors
|
||||
)
|
||||
|
||||
|
||||
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
ports = [22]
|
||||
return add_tcp_ports(agent_configuration, ports)
|
||||
|
||||
|
||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
|
||||
test_agent_configuration = set_keep_tunnel_open_time(test_agent_configuration, 20)
|
||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||
test_agent_configuration = _add_credential_collectors(test_agent_configuration)
|
||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||
|
||||
CREDENTIALS = (
|
||||
Credentials(identity=Username(username="m0nk3y"), secret=None),
|
||||
Credentials(identity=None, secret=Password(password="u26gbVQe")),
|
||||
Credentials(identity=None, secret=Password(password="5BuYHeVl")),
|
||||
)
|
||||
|
||||
credentials_reuse_ssh_key_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||
replace_agent_configuration(
|
||||
test_configuration=credentials_reuse_ssh_key_test_configuration,
|
||||
agent_configuration=test_agent_configuration,
|
||||
)
|
||||
replace_propagation_credentials(
|
||||
test_configuration=credentials_reuse_ssh_key_test_configuration,
|
||||
propagation_credentials=CREDENTIALS,
|
||||
)
|
|
@ -1,5 +1,3 @@
|
|||
import dataclasses
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
from common.credentials import Credentials, Password, Username
|
||||
|
||||
|
@ -62,7 +60,7 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
|||
|
||||
def _add_credential_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
return add_credential_collectors(
|
||||
agent_configuration, [PluginConfiguration(name="MimikatzCollector", options={})]
|
||||
agent_configuration, [PluginConfiguration("MimikatzCollector", {})]
|
||||
)
|
||||
|
||||
|
||||
|
@ -78,24 +76,22 @@ def _add_http_ports(agent_configuration: AgentConfiguration) -> AgentConfigurati
|
|||
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_subnets(test_agent_configuration)
|
||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||
test_agent_configuration = _add_credential_collectors(test_agent_configuration)
|
||||
test_agent_configuration = _add_http_ports(test_agent_configuration)
|
||||
test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
|
||||
test_configuration = _add_exploiters(test_configuration)
|
||||
test_configuration = _add_fingerprinters(test_configuration)
|
||||
test_configuration = _add_subnets(test_configuration)
|
||||
test_configuration = _add_tcp_ports(test_configuration)
|
||||
test_configuration = _add_credential_collectors(test_configuration)
|
||||
test_configuration = _add_http_ports(test_configuration)
|
||||
|
||||
depth_1_a_test_configuration = replace_agent_configuration(
|
||||
noop_test_configuration, test_configuration
|
||||
)
|
||||
CREDENTIALS = (
|
||||
Credentials(identity=Username(username="m0nk3y"), secret=None),
|
||||
Credentials(identity=None, secret=Password(password="Ivrrw5zEzs")),
|
||||
Credentials(identity=None, secret=Password(password="Xk8VDTsC")),
|
||||
Credentials(Username("m0nk3y"), None),
|
||||
Credentials(None, Password("Ivrrw5zEzs")),
|
||||
Credentials(None, Password("Xk8VDTsC")),
|
||||
)
|
||||
|
||||
depth_1_a_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||
replace_agent_configuration(
|
||||
test_configuration=depth_1_a_test_configuration, agent_configuration=test_agent_configuration
|
||||
)
|
||||
replace_propagation_credentials(
|
||||
test_configuration=depth_1_a_test_configuration, propagation_credentials=CREDENTIALS
|
||||
depth_1_a_test_configuration = replace_propagation_credentials(
|
||||
depth_1_a_test_configuration, CREDENTIALS
|
||||
)
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import dataclasses
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
from common.credentials import Credentials, Password, Username
|
||||
|
||||
from .noop import noop_test_configuration
|
||||
from .utils import (
|
||||
add_exploiters,
|
||||
add_fingerprinters,
|
||||
add_http_ports,
|
||||
add_subnets,
|
||||
add_tcp_ports,
|
||||
replace_agent_configuration,
|
||||
|
@ -18,60 +14,40 @@ from .utils import (
|
|||
|
||||
# Tests:
|
||||
# SSH password and key brute-force, key stealing (10.2.2.11, 10.2.2.12)
|
||||
# Powershell credential reuse (logging in without credentials
|
||||
# to an identical user on another machine)(10.2.3.44, 10.2.3.46)
|
||||
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
brute_force = [
|
||||
PluginConfiguration(name="SSHExploiter", options={}),
|
||||
PluginConfiguration(name="PowerShellExploiter", options={}),
|
||||
]
|
||||
vulnerability = [
|
||||
PluginConfiguration(name="Log4ShellExploiter", options={}),
|
||||
]
|
||||
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=vulnerability)
|
||||
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
|
||||
|
||||
|
||||
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
subnets = [
|
||||
"10.2.2.11",
|
||||
"10.2.2.12",
|
||||
"10.2.3.44",
|
||||
"10.2.3.46",
|
||||
]
|
||||
return add_subnets(agent_configuration, subnets)
|
||||
|
||||
|
||||
def _add_fingerprinters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
fingerprinters = [PluginConfiguration(name="http", options={})]
|
||||
|
||||
return add_fingerprinters(agent_configuration, fingerprinters)
|
||||
|
||||
|
||||
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
ports = [22, 5985, 5986, 8080]
|
||||
ports = [22]
|
||||
return add_tcp_ports(agent_configuration, ports)
|
||||
|
||||
|
||||
def _add_http_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
return add_http_ports(agent_configuration, [8080])
|
||||
test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 2)
|
||||
test_configuration = _add_exploiters(test_configuration)
|
||||
test_configuration = _add_subnets(test_configuration)
|
||||
test_configuration = _add_tcp_ports(test_configuration)
|
||||
|
||||
depth_2_a_test_configuration = replace_agent_configuration(
|
||||
noop_test_configuration, test_configuration
|
||||
)
|
||||
|
||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 2)
|
||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||
test_agent_configuration = _add_fingerprinters(test_agent_configuration)
|
||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||
test_agent_configuration = _add_http_ports(test_agent_configuration)
|
||||
|
||||
CREDENTIALS = (
|
||||
Credentials(identity=Username(username="m0nk3y"), secret=None),
|
||||
Credentials(identity=None, secret=Password(password="^NgDvY59~8")),
|
||||
Credentials(Username("m0nk3y"), None),
|
||||
Credentials(None, Password("^NgDvY59~8")),
|
||||
)
|
||||
|
||||
depth_2_a_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||
replace_agent_configuration(
|
||||
test_configuration=depth_2_a_test_configuration, agent_configuration=test_agent_configuration
|
||||
)
|
||||
replace_propagation_credentials(
|
||||
test_configuration=depth_2_a_test_configuration, propagation_credentials=CREDENTIALS
|
||||
depth_2_a_test_configuration = replace_propagation_credentials(
|
||||
depth_2_a_test_configuration, CREDENTIALS
|
||||
)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import dataclasses
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
from common.credentials import Credentials, NTHash, Password, Username
|
||||
|
||||
|
@ -16,7 +14,7 @@ from .utils import (
|
|||
|
||||
# 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)
|
||||
# Tunneling (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.12, 10.2.0.11)
|
||||
# WMI pass the hash (10.2.2.15)
|
||||
|
||||
|
||||
|
@ -34,9 +32,11 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
|||
subnets = [
|
||||
"10.2.2.9",
|
||||
"10.2.3.45",
|
||||
"10.2.3.46",
|
||||
"10.2.3.47",
|
||||
"10.2.3.48",
|
||||
"10.2.1.10",
|
||||
"10.2.0.12",
|
||||
"10.2.0.11",
|
||||
"10.2.2.15",
|
||||
]
|
||||
|
@ -48,27 +48,28 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
|
|||
return add_tcp_ports(agent_configuration, ports)
|
||||
|
||||
|
||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
|
||||
test_agent_configuration = set_keep_tunnel_open_time(test_agent_configuration, 20)
|
||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||
test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
|
||||
test_configuration = set_keep_tunnel_open_time(test_configuration, 20)
|
||||
test_configuration = _add_exploiters(test_configuration)
|
||||
test_configuration = _add_subnets(test_configuration)
|
||||
test_configuration = _add_tcp_ports(test_configuration)
|
||||
|
||||
depth_3_a_test_configuration = replace_agent_configuration(
|
||||
noop_test_configuration, test_configuration
|
||||
)
|
||||
|
||||
|
||||
CREDENTIALS = (
|
||||
Credentials(identity=Username(username="m0nk3y"), secret=None),
|
||||
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")),
|
||||
Credentials(identity=None, secret=NTHash(nt_hash="5da0889ea2081aa79f6852294cba4a5e")),
|
||||
Credentials(identity=None, secret=NTHash(nt_hash="50c9987a6bf1ac59398df9f911122c9b")),
|
||||
Credentials(Username("m0nk3y"), None),
|
||||
Credentials(Username("m0nk3y-user"), None),
|
||||
Credentials(None, Password("Passw0rd!")),
|
||||
Credentials(None, Password("3Q=(Ge(+&w]*")),
|
||||
Credentials(None, Password("`))jU7L(w}")),
|
||||
Credentials(None, Password("t67TC5ZDmz")),
|
||||
Credentials(None, NTHash("d0f0132b308a0c4e5d1029cc06f48692")),
|
||||
Credentials(None, NTHash("5da0889ea2081aa79f6852294cba4a5e")),
|
||||
Credentials(None, NTHash("50c9987a6bf1ac59398df9f911122c9b")),
|
||||
)
|
||||
|
||||
depth_3_a_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||
replace_agent_configuration(
|
||||
test_configuration=depth_3_a_test_configuration, agent_configuration=test_agent_configuration
|
||||
)
|
||||
replace_propagation_credentials(
|
||||
test_configuration=depth_3_a_test_configuration, propagation_credentials=CREDENTIALS
|
||||
depth_3_a_test_configuration = replace_propagation_credentials(
|
||||
depth_3_a_test_configuration, CREDENTIALS
|
||||
)
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
import dataclasses
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
from common.credentials import Credentials, Password, Username
|
||||
|
||||
from .noop import noop_test_configuration
|
||||
from .utils import (
|
||||
add_exploiters,
|
||||
add_subnets,
|
||||
add_tcp_ports,
|
||||
replace_agent_configuration,
|
||||
replace_propagation_credentials,
|
||||
set_keep_tunnel_open_time,
|
||||
set_maximum_depth,
|
||||
)
|
||||
|
||||
# Tests:
|
||||
# Tunneling (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.12, 10.2.0.13)
|
||||
|
||||
|
||||
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
brute_force = [
|
||||
PluginConfiguration(name="SSHExploiter", options={}),
|
||||
PluginConfiguration(name="WmiExploiter", options={"smb_download_timeout": 30}),
|
||||
]
|
||||
|
||||
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
|
||||
|
||||
|
||||
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
subnets = [
|
||||
"10.2.2.9",
|
||||
"10.2.1.10",
|
||||
"10.2.0.12",
|
||||
"10.2.0.13",
|
||||
]
|
||||
return add_subnets(agent_configuration, subnets)
|
||||
|
||||
|
||||
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
ports = [22, 135, 5985, 5986]
|
||||
return add_tcp_ports(agent_configuration, ports)
|
||||
|
||||
|
||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 4)
|
||||
test_agent_configuration = set_keep_tunnel_open_time(test_agent_configuration, 20)
|
||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||
|
||||
CREDENTIALS = (
|
||||
Credentials(identity=Username(username="m0nk3y"), secret=None),
|
||||
Credentials(identity=None, secret=Password(password="3Q=(Ge(+&w]*")),
|
||||
Credentials(identity=None, secret=Password(password="`))jU7L(w}")),
|
||||
Credentials(identity=None, secret=Password(password="prM2qsroTI")),
|
||||
Credentials(identity=None, secret=Password(password="t67TC5ZDmz")),
|
||||
)
|
||||
|
||||
depth_4_a_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||
replace_agent_configuration(
|
||||
test_configuration=depth_4_a_test_configuration, agent_configuration=test_agent_configuration
|
||||
)
|
||||
replace_propagation_credentials(
|
||||
test_configuration=depth_4_a_test_configuration, propagation_credentials=CREDENTIALS
|
||||
)
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Tuple
|
||||
|
||||
from common.agent_configuration import (
|
||||
AgentConfiguration,
|
||||
CustomPBAConfiguration,
|
||||
|
@ -11,18 +9,15 @@ from common.agent_configuration import (
|
|||
ScanTargetConfiguration,
|
||||
TCPScanConfiguration,
|
||||
)
|
||||
from common.credentials import Credentials
|
||||
|
||||
from . import TestConfiguration
|
||||
|
||||
_custom_pba_configuration = CustomPBAConfiguration(
|
||||
linux_command="", linux_filename="", windows_command="", windows_filename=""
|
||||
)
|
||||
_custom_pba_configuration = CustomPBAConfiguration("", "", "", "")
|
||||
|
||||
_tcp_scan_configuration = TCPScanConfiguration(timeout=3.0, ports=[])
|
||||
_icmp_scan_configuration = ICMPScanConfiguration(timeout=1.0)
|
||||
_scan_target_configuration = ScanTargetConfiguration(
|
||||
blocked_ips=[], inaccessible_subnets=[], scan_my_networks=False, subnets=[]
|
||||
blocked_ips=[], inaccessible_subnets=[], local_network_scan=False, subnets=[]
|
||||
)
|
||||
_network_scan_configuration = NetworkScanConfiguration(
|
||||
tcp=_tcp_scan_configuration,
|
||||
|
@ -50,9 +45,9 @@ _agent_configuration = AgentConfiguration(
|
|||
payloads=[],
|
||||
propagation=_propagation_configuration,
|
||||
)
|
||||
_propagation_credentials: Tuple[Credentials, ...] = tuple()
|
||||
_propagation_credentials = tuple()
|
||||
|
||||
# This is an empty, NOOP configuration from which other configurations can be built
|
||||
noop_test_configuration: TestConfiguration = TestConfiguration(
|
||||
noop_test_configuration = TestConfiguration(
|
||||
agent_configuration=_agent_configuration, propagation_credentials=_propagation_credentials
|
||||
)
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
|
||||
from .noop import noop_test_configuration
|
||||
from .utils import (
|
||||
add_exploiters,
|
||||
add_subnets,
|
||||
add_tcp_ports,
|
||||
replace_agent_configuration,
|
||||
set_maximum_depth,
|
||||
)
|
||||
|
||||
|
||||
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
brute_force = [
|
||||
PluginConfiguration(name="PowerShellExploiter", options={}),
|
||||
]
|
||||
|
||||
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
|
||||
|
||||
|
||||
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
subnets = [
|
||||
"10.2.3.46",
|
||||
]
|
||||
return add_subnets(agent_configuration, subnets)
|
||||
|
||||
|
||||
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
ports = [5985, 5986]
|
||||
return add_tcp_ports(agent_configuration, ports)
|
||||
|
||||
|
||||
test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
|
||||
test_configuration = _add_exploiters(test_configuration)
|
||||
test_configuration = _add_subnets(test_configuration)
|
||||
test_configuration = _add_tcp_ports(test_configuration)
|
||||
|
||||
powershell_credentials_reuse_test_configuration = replace_agent_configuration(
|
||||
noop_test_configuration, test_configuration
|
||||
)
|
|
@ -1,5 +1,3 @@
|
|||
import dataclasses
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
from common.credentials import Credentials, NTHash, Password, Username
|
||||
|
||||
|
@ -35,27 +33,27 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
|
|||
return add_tcp_ports(agent_configuration, ports)
|
||||
|
||||
|
||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
|
||||
test_agent_configuration = set_keep_tunnel_open_time(test_agent_configuration, 20)
|
||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||
test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
|
||||
test_configuration = set_keep_tunnel_open_time(test_configuration, 20)
|
||||
test_configuration = _add_exploiters(test_configuration)
|
||||
test_configuration = _add_subnets(test_configuration)
|
||||
test_configuration = _add_tcp_ports(test_configuration)
|
||||
|
||||
smb_pth_test_configuration = replace_agent_configuration(
|
||||
noop_test_configuration, test_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(Username("Administrator"), None),
|
||||
Credentials(Username("m0nk3y"), None),
|
||||
Credentials(Username("user"), None),
|
||||
Credentials(None, Password("Ivrrw5zEzs")),
|
||||
Credentials(None, Password("Password1!")),
|
||||
Credentials(None, NTHash("d0f0132b308a0c4e5d1029cc06f48692")),
|
||||
Credentials(None, NTHash("5da0889ea2081aa79f6852294cba4a5e")),
|
||||
Credentials(None, NTHash("50c9987a6bf1ac59398df9f911122c9b")),
|
||||
)
|
||||
|
||||
smb_pth_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||
replace_agent_configuration(
|
||||
test_configuration=smb_pth_test_configuration, agent_configuration=test_agent_configuration
|
||||
)
|
||||
replace_propagation_credentials(
|
||||
test_configuration=smb_pth_test_configuration, propagation_credentials=CREDENTIALS
|
||||
smb_pth_test_configuration = replace_propagation_credentials(
|
||||
smb_pth_test_configuration, CREDENTIALS
|
||||
)
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
from dataclasses import replace
|
||||
from typing import Sequence, Tuple
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
from common.agent_configuration import (
|
||||
AgentConfiguration,
|
||||
ExploitationConfiguration,
|
||||
ExploitationOptionsConfiguration,
|
||||
NetworkScanConfiguration,
|
||||
PluginConfiguration,
|
||||
PropagationConfiguration,
|
||||
ScanTargetConfiguration,
|
||||
)
|
||||
from common.credentials import Credentials
|
||||
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration
|
||||
|
||||
from . import TestConfiguration
|
||||
|
||||
|
||||
def add_exploiters(
|
||||
|
@ -10,91 +20,133 @@ def add_exploiters(
|
|||
brute_force: Sequence[PluginConfiguration] = [],
|
||||
vulnerability: Sequence[PluginConfiguration] = [],
|
||||
) -> AgentConfiguration:
|
||||
|
||||
agent_configuration_copy = agent_configuration.copy(deep=True)
|
||||
agent_configuration_copy.propagation.exploitation.brute_force = brute_force
|
||||
agent_configuration_copy.propagation.exploitation.vulnerability = vulnerability
|
||||
|
||||
return agent_configuration_copy
|
||||
exploitation_configuration = replace(
|
||||
agent_configuration.propagation.exploitation,
|
||||
brute_force=brute_force,
|
||||
vulnerability=vulnerability,
|
||||
)
|
||||
return replace_exploitation_configuration(agent_configuration, exploitation_configuration)
|
||||
|
||||
|
||||
def add_fingerprinters(
|
||||
agent_configuration: AgentConfiguration, fingerprinters: Sequence[PluginConfiguration]
|
||||
) -> AgentConfiguration:
|
||||
network_scan_configuration = replace(
|
||||
agent_configuration.propagation.network_scan, fingerprinters=fingerprinters
|
||||
)
|
||||
|
||||
agent_configuration_copy = agent_configuration.copy(deep=True)
|
||||
agent_configuration_copy.propagation.network_scan.fingerprinters = fingerprinters
|
||||
|
||||
return agent_configuration_copy
|
||||
return replace_network_scan_configuration(agent_configuration, network_scan_configuration)
|
||||
|
||||
|
||||
def add_tcp_ports(
|
||||
agent_configuration: AgentConfiguration, tcp_ports: Sequence[int]
|
||||
) -> AgentConfiguration:
|
||||
tcp_scan_configuration = replace(
|
||||
agent_configuration.propagation.network_scan.tcp, ports=tuple(tcp_ports)
|
||||
)
|
||||
network_scan_configuration = replace(
|
||||
agent_configuration.propagation.network_scan, tcp=tcp_scan_configuration
|
||||
)
|
||||
|
||||
agent_configuration_copy = agent_configuration.copy(deep=True)
|
||||
agent_configuration_copy.propagation.network_scan.tcp.ports = tuple(tcp_ports)
|
||||
|
||||
return agent_configuration_copy
|
||||
return replace_network_scan_configuration(agent_configuration, network_scan_configuration)
|
||||
|
||||
|
||||
def add_subnets(
|
||||
agent_configuration: AgentConfiguration, subnets: Sequence[str]
|
||||
) -> AgentConfiguration:
|
||||
|
||||
agent_configuration_copy = agent_configuration.copy(deep=True)
|
||||
agent_configuration_copy.propagation.network_scan.targets.subnets = subnets
|
||||
|
||||
return agent_configuration_copy
|
||||
scan_target_configuration = replace(
|
||||
agent_configuration.propagation.network_scan.targets, subnets=subnets
|
||||
)
|
||||
return replace_scan_target_configuration(agent_configuration, scan_target_configuration)
|
||||
|
||||
|
||||
def add_credential_collectors(
|
||||
agent_configuration: AgentConfiguration, credential_collectors: Sequence[PluginConfiguration]
|
||||
) -> AgentConfiguration:
|
||||
|
||||
agent_configuration_copy = agent_configuration.copy(deep=True)
|
||||
agent_configuration_copy.credential_collectors = tuple(credential_collectors)
|
||||
|
||||
return agent_configuration_copy
|
||||
return replace(agent_configuration, credential_collectors=tuple(credential_collectors))
|
||||
|
||||
|
||||
def add_http_ports(
|
||||
agent_configuration: AgentConfiguration, http_ports: Sequence[int]
|
||||
) -> AgentConfiguration:
|
||||
exploitation_options_configuration = agent_configuration.propagation.exploitation.options
|
||||
exploitation_options_configuration = replace(
|
||||
exploitation_options_configuration, http_ports=http_ports
|
||||
)
|
||||
|
||||
agent_configuration_copy = agent_configuration.copy(deep=True)
|
||||
agent_configuration_copy.propagation.exploitation.options.http_ports = http_ports
|
||||
|
||||
return agent_configuration_copy
|
||||
return replace_exploitation_options_configuration(
|
||||
agent_configuration, exploitation_options_configuration
|
||||
)
|
||||
|
||||
|
||||
def set_keep_tunnel_open_time(
|
||||
agent_configuration: AgentConfiguration, keep_tunnel_open_time: int
|
||||
) -> AgentConfiguration:
|
||||
|
||||
agent_configuration_copy = agent_configuration.copy(deep=True)
|
||||
agent_configuration_copy.keep_tunnel_open_time = keep_tunnel_open_time
|
||||
|
||||
return agent_configuration_copy
|
||||
return replace(agent_configuration, keep_tunnel_open_time=keep_tunnel_open_time)
|
||||
|
||||
|
||||
def set_maximum_depth(
|
||||
agent_configuration: AgentConfiguration, maximum_depth: int
|
||||
) -> AgentConfiguration:
|
||||
propagation_configuration = replace(
|
||||
agent_configuration.propagation, maximum_depth=maximum_depth
|
||||
)
|
||||
return replace_propagation_configuration(agent_configuration, propagation_configuration)
|
||||
|
||||
agent_configuration_copy = agent_configuration.copy(deep=True)
|
||||
agent_configuration_copy.propagation.maximum_depth = maximum_depth
|
||||
|
||||
return agent_configuration_copy
|
||||
def replace_exploitation_configuration(
|
||||
agent_configuration: AgentConfiguration, exploitation_configuration: ExploitationConfiguration
|
||||
) -> AgentConfiguration:
|
||||
propagation_configuration = replace(
|
||||
agent_configuration.propagation, exploitation=exploitation_configuration
|
||||
)
|
||||
|
||||
return replace_propagation_configuration(agent_configuration, propagation_configuration)
|
||||
|
||||
|
||||
def replace_scan_target_configuration(
|
||||
agent_configuration: AgentConfiguration, scan_target_configuration: ScanTargetConfiguration
|
||||
) -> AgentConfiguration:
|
||||
network_scan_configuration = replace(
|
||||
agent_configuration.propagation.network_scan, targets=scan_target_configuration
|
||||
)
|
||||
|
||||
return replace_network_scan_configuration(agent_configuration, network_scan_configuration)
|
||||
|
||||
|
||||
def replace_network_scan_configuration(
|
||||
agent_configuration: AgentConfiguration, network_scan_configuration: NetworkScanConfiguration
|
||||
) -> AgentConfiguration:
|
||||
propagation_configuration = replace(
|
||||
agent_configuration.propagation, network_scan=network_scan_configuration
|
||||
)
|
||||
return replace_propagation_configuration(agent_configuration, propagation_configuration)
|
||||
|
||||
|
||||
def replace_propagation_configuration(
|
||||
agent_configuration: AgentConfiguration, propagation_configuration: PropagationConfiguration
|
||||
) -> AgentConfiguration:
|
||||
return replace(agent_configuration, propagation=propagation_configuration)
|
||||
|
||||
|
||||
def replace_exploitation_options_configuration(
|
||||
agent_configuration: AgentConfiguration,
|
||||
exploitation_options_configuration: ExploitationOptionsConfiguration,
|
||||
) -> AgentConfiguration:
|
||||
exploitation_configuration = agent_configuration.propagation.exploitation
|
||||
exploitation_configuration = replace(
|
||||
exploitation_configuration, options=exploitation_options_configuration
|
||||
)
|
||||
return replace_exploitation_configuration(agent_configuration, exploitation_configuration)
|
||||
|
||||
|
||||
def replace_agent_configuration(
|
||||
test_configuration: TestConfiguration, agent_configuration: AgentConfiguration
|
||||
):
|
||||
test_configuration.agent_configuration = agent_configuration
|
||||
) -> TestConfiguration:
|
||||
return replace(test_configuration, agent_configuration=agent_configuration)
|
||||
|
||||
|
||||
def replace_propagation_credentials(
|
||||
test_configuration: TestConfiguration, propagation_credentials: Tuple[Credentials, ...]
|
||||
):
|
||||
test_configuration.propagation_credentials = propagation_credentials
|
||||
return replace(test_configuration, propagation_credentials=propagation_credentials)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import dataclasses
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
from common.credentials import Credentials, Password, Username
|
||||
|
||||
|
@ -33,7 +31,7 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
|||
|
||||
def _add_credential_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||
return add_credential_collectors(
|
||||
agent_configuration, [PluginConfiguration(name="MimikatzCollector", options={})]
|
||||
agent_configuration, [PluginConfiguration("MimikatzCollector", {})]
|
||||
)
|
||||
|
||||
|
||||
|
@ -42,25 +40,25 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
|
|||
return add_tcp_ports(agent_configuration, ports)
|
||||
|
||||
|
||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
|
||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||
test_agent_configuration = _add_credential_collectors(test_agent_configuration)
|
||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||
test_agent_configuration = _add_credential_collectors(test_agent_configuration)
|
||||
test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
|
||||
test_configuration = _add_exploiters(test_configuration)
|
||||
test_configuration = _add_subnets(test_configuration)
|
||||
test_configuration = _add_credential_collectors(test_configuration)
|
||||
test_configuration = _add_tcp_ports(test_configuration)
|
||||
test_configuration = _add_credential_collectors(test_configuration)
|
||||
|
||||
wmi_mimikatz_test_configuration = replace_agent_configuration(
|
||||
noop_test_configuration, test_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(Username("Administrator"), None),
|
||||
Credentials(Username("m0nk3y"), None),
|
||||
Credentials(Username("user"), None),
|
||||
Credentials(None, Password("Ivrrw5zEzs")),
|
||||
Credentials(None, Password("Password1!")),
|
||||
)
|
||||
|
||||
wmi_mimikatz_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||
replace_agent_configuration(
|
||||
test_configuration=wmi_mimikatz_test_configuration, agent_configuration=test_agent_configuration
|
||||
)
|
||||
replace_propagation_credentials(
|
||||
test_configuration=wmi_mimikatz_test_configuration, propagation_credentials=CREDENTIALS
|
||||
wmi_mimikatz_test_configuration = replace_propagation_credentials(
|
||||
wmi_mimikatz_test_configuration, CREDENTIALS
|
||||
)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import dataclasses
|
||||
|
||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||
|
||||
from .noop import noop_test_configuration
|
||||
|
@ -29,12 +27,11 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
|||
return add_subnets(agent_configuration, subnets)
|
||||
|
||||
|
||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
|
||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||
test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
|
||||
test_configuration = _add_exploiters(test_configuration)
|
||||
test_configuration = _add_tcp_ports(test_configuration)
|
||||
test_configuration = _add_subnets(test_configuration)
|
||||
|
||||
zerologon_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||
replace_agent_configuration(
|
||||
test_configuration=zerologon_test_configuration, agent_configuration=test_agent_configuration
|
||||
zerologon_test_configuration = replace_agent_configuration(
|
||||
noop_test_configuration, test_configuration
|
||||
)
|
||||
|
|
|
@ -11,9 +11,6 @@ This document describes Infection Monkey’s test network, how to deploy and use
|
|||
[Nr. 3 Hadoop](#_Toc526517183)<br>
|
||||
[Nr. 9 Tunneling M1](#_Toc536021462)<br>
|
||||
[Nr. 10 Tunneling M2](#_Toc536021463)<br>
|
||||
[Nr. 11 Tunneling M1](#_Toc536021464)<br>
|
||||
[Nr. 12 Tunneling M2](#_Toc536021465)<br>
|
||||
[Nr. 13 Tunneling M2](#_Toc536021466)<br>
|
||||
[Nr. 11 SSH key steal](#_Toc526517190)<br>
|
||||
[Nr. 12 SSH key steal](#_Toc526517191)<br>
|
||||
[Nr. 13 RDP grinder](#_Toc526517192)<br>
|
||||
|
@ -28,9 +25,6 @@ This document describes Infection Monkey’s test network, how to deploy and use
|
|||
[Nr. 3-46 Powershell](#_Toc536021480)<br>
|
||||
[Nr. 3-47 Powershell](#_Toc536021481)<br>
|
||||
[Nr. 3-48 Powershell](#_Toc536021482)<br>
|
||||
[Nr. 14 Credentials Reuse](#_Toc536121480)<br>
|
||||
[Nr. 15 Credentials Reuse](#_Toc536121481)<br>
|
||||
[Nr. 16 Credentials Reuse](#_Toc536121482)<br>
|
||||
[Nr. 3-49 Log4j Solr](#_Toc536021483)<br>
|
||||
[Nr. 3-50 Log4j Solr](#_Toc536021484)<br>
|
||||
[Nr. 3-51 Log4j Tomcat](#_Toc536021485)<br>
|
||||
|
@ -309,7 +303,7 @@ Update all requirements using deployment script:<br>
|
|||
</tr>
|
||||
<tr class="even">
|
||||
<td>Root password:</td>
|
||||
<td>3Q=(Ge(+&w]*</td>
|
||||
<td>3Q=(Ge(+&w]*</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Server’s config:</td>
|
||||
|
@ -325,7 +319,7 @@ Update all requirements using deployment script:<br>
|
|||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021464" class="anchor"></span>Nr. <strong>11</strong> Tunneling M3</p>
|
||||
<th><p><span id="_Toc536021463" class="anchor"></span>Nr. <strong>11</strong> Tunneling M3</p>
|
||||
<p>(10.2.0.11)</p></th>
|
||||
<th>(Exploitable)</th>
|
||||
</tr>
|
||||
|
@ -349,10 +343,7 @@ Update all requirements using deployment script:<br>
|
|||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Server’s config:</td>
|
||||
<td>Contains firewall rules to block everything from 10.2.1.10 except ssh.
|
||||
This prevents tunneling communication, but allows ssh exploitation.
|
||||
Contains firewall rules to allow everything from 10.2.1.9 except ssh.
|
||||
This prevents ssh exploitation, but allows tunneling.</td>
|
||||
<td>Default</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
|
@ -364,7 +355,7 @@ This prevents ssh exploitation, but allows tunneling.</td>
|
|||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021465" class="anchor"></span>Nr. <strong>12</strong> Tunneling M4</p>
|
||||
<th><p><span id="_Toc536021463" class="anchor"></span>Nr. <strong>12</strong> Tunneling M4</p>
|
||||
<p>(10.2.0.12)</p></th>
|
||||
<th>(Exploitable)</th>
|
||||
</tr>
|
||||
|
@ -393,38 +384,6 @@ This prevents ssh exploitation, but allows tunneling.</td>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021466" class="anchor"></span>Nr. <strong>13</strong> Tunneling M5</p>
|
||||
<p>(10.2.0.13)</p></th>
|
||||
<th>(Exploitable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Ubuntu 18 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default service’s port:</td>
|
||||
<td>22</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Root password:</td>
|
||||
<td>prM2qsroTI</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Server’s config:</td>
|
||||
<td>Configured to disable traffic from/to 10.2.0.10 and 10.2.0.11(via ufw and iptables)</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>Accessible only through Nr.12</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
|
@ -759,38 +718,6 @@ This prevents ssh exploitation, but allows tunneling.</td>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021479" class="anchor"></span>Nr. <strong>3-44 Powershell</strong></p>
|
||||
<p>(10.2.3.44)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Windows Server 2016 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>WinRM service</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port: 5985, 5986</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: m0nk3y, Password: nPj8rbc3<br>
|
||||
Accessible using the same m0nk3y user from powershell-3-46,
|
||||
in other words powershell exploiter can exploit
|
||||
this machine without credentials as long as the user running the agent has
|
||||
the same credentials on both machines</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
|
@ -836,17 +763,17 @@ Accessibale through Island using m0nk3y-user.</td>
|
|||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>WinRM service</td>
|
||||
<td>Tomcat 8.0.36</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:8080</td>
|
||||
<td>Default server’s port:</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: m0nk3y, Password: nPj8rbc3<br>
|
||||
Exploited from island via log4shell(tomcat). Then uses cached powershell credentials to
|
||||
propagate to powershell-3-44</td>
|
||||
Accessible using the same m0nk3y user from island, in other words powershell exploiter can exploit
|
||||
this machine without credentials as long as the user running the agent is the same on both
|
||||
machines</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -909,120 +836,6 @@ Accessiable only through <strong>3-45 Powershell</strong> using credentials reus
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536121480" class="anchor"></span>Nr. <strong>14</strong> Credentials Reuse</p>
|
||||
<p>(10.2.3.14, 10.2.4.14)</p></th>
|
||||
<th>(Exploitable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Ubuntu 16.04.05 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>OpenSSL</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default service’s port:</td>
|
||||
<td>22</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Credentials:</td>
|
||||
<td>m0nk3y:u26gbVQe</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Server’s config:</td>
|
||||
<td>VPC network that can only access Credentials Reuse 15 and Island.</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>Accessible from the Island with password authentication</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536121481" class="anchor"></span>Nr. <strong>15</strong> Credentials Reuse</p>
|
||||
<p>(10.2.4.15, 10.2.5.15)</p></th>
|
||||
<th>(Exploitable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Ubuntu 16.04.05 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>OpenSSL</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default service’s port:</td>
|
||||
<td>22</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Credentials:</td>
|
||||
<td>m0nk3y:5BuYHeVl</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Server’s config:</td>
|
||||
<td>VPC network that can be only accessed by Credentials Reuse 14 and communicate to<br>
|
||||
Credentials Reuse 16.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>Accessible from the Credentials Reuse 14 with password authentication</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536121482" class="anchor"></span>Nr. <strong>16</strong> Credentials Reuse</p>
|
||||
<p>(10.2.3.16, 10.2.5.16)</p></th>
|
||||
<th>(Exploitable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Ubuntu 16.04.05 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>OpenSSL</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default service’s port:</td>
|
||||
<td>22</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Credentials:</td>
|
||||
<td>m0nk3y:lIZl6vTR</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Server’s config:</td>
|
||||
<td>VPC network that can be only accessed by Credentials Reuse 15 and communicate to<br>
|
||||
the Island.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>Accessible from the Credentials Reuse 15 with passwordless ssh key authentication.<br>
|
||||
We use the ssh key that was stolen from Credentials Reuse 16</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
|
@ -1 +0,0 @@
|
|||
<mxfile host="app.diagrams.net" modified="2022-09-23T15:01:54.105Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" etag="spZrDzUM2aBFwquXRZY8" version="20.3.0" type="device"><diagram id="YCekmHjAy1LVhBsJn630" name="Page-1">7VjbjpswEP2aSO1DEGBIyOPmst1WqZRqpV6eVg444K3B1Di3/fraYC4OyXajJKKqKuWBGXuM58zxGZMemMS7Dwym0WcaINKzzWDXA9OebVuObffkzwz2hWc4cgpHyHCgJtWOR/yClNNU3jUOUKZN5JQSjlPd6dMkQT7XfJAxutWnrSjR35rCELUcjz4kbe83HPCo8HquWfsfEA6j8s2WqUZiWE5WjiyCAd02XGDWAxNGKS+e4t0EEQleiUsRd39itNoYQwl/S4CN5unz0yd7/JBEwfPw69MX8KuvVsn4vkwYBSJ/ZVLGIxrSBJJZ7R0zuk4CJFc1hVXPmVOaCqclnM+I870qJlxzKlwRj4kaFRtm++8qPjd+SMOw3dKe7pqj072yVpiQCSWU5VsFK89Hvi/8GWf0J2qMLD3XcWVEkZ7M6SRqJQR0zXz0ClSKyByyEPHXIHWq4opTgWiMRAoikCECOd7oG4GKnmE1T4XeMQb3jQkpxQnPGisvpENMUCfNBopm6pzZgwM2nDdfPBQ7OBE9PIh2R8ZIz6jAScVpCzdQqV05Wc8gbqkrG0jWCtI5DZ3HSMS+4zT2IX9/lNtzuBQSpfEREhwm4tkXnECCQeMNYhwLDbhTAzEOgoL6KMMvcJmvJ+mlqiIWd8c9d1oRTi6AdlqllUCp4FoWmlQ8fUTbdFKr98WxsSxPq8agsM4j3BGGaIv2HX0Bulpl6DalbVX2Y0ZgErTKqQvRNsIcPaYwP8Rb0Y30Ip8sTEsLTmI9OiC9U5F+W7eGqgFEjbbgmafR1+A7FyvQiX7vMM/l2zBLsxBwz1Vmrd/S2DeMBWJYpC6P2fTKAm05b1RouwuBBgNLYw+wLhFcYLYEt8DndoLrdntVMNzmZcH6m28KbyciuJCJF2lH2ScaQrugW8SyqAj2GQpE7hiSfEvrDP0bDbVg8kmVNw1rqHc+cFk/vX3HLAnXqGRaVbIP+o6o9IAITMZLJp5CnuN66LHEVcIAhjPovNGCwWGnNTvus2UODYRbKFUfeBKkAArwS8Qa6OgClNAEtdVqdu/d23IykUdrQTPMMT16nuYHE5aUi7vvkQPHpd62zyVdc4ITsZ3y41nuVuSRypTiXSi/6Y0NQjA24uxpi5Mg/6C+Ro0d57CjtWvsHCkxGBnerYo87KLFXbHxlFf2m9+ALoMZ/EGtnHPUyulcrSrV2eus7U6s2u3gv1hdtSFZtxQrYdb/ChZXhPq/VTD7DQ==</diagram></mxfile>
|
|
@ -1 +0,0 @@
|
|||
<mxfile host="app.diagrams.net" modified="2022-09-09T14:43:18.604Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" etag="hR7zJg_PJGBkx010pojR" version="20.3.0" type="device"><diagram id="UsVsRtHn_Xg_yxI8nRbj" name="Page-1">7VrbbuM2EP0aP9aQRN38uLHsTYEtGiAo0jwFjMSV2KVEg6J86deXtEhbF1eWW9tSsEHyYM6MJHLOOcPRZQLm6fYrg6vkNxohMrGMaDsBwcSyTNuyJvLfiHalxZvZpSFmOFJBR8Mz/hspo6GsBY5QXgvklBKOV3VjSLMMhbxmg4zRTT3sOyX1q65gjFqG5xCStvUFRzwprb5jHO2PCMeJvrJpKE8KdbAy5AmM6KZiAosJmDNKefkr3c4RkcnTeSmPW/6L9zAxhjLe5wAneCPB79/gSxC+PT4t053Jlr94am58pxeMIrF+NaSMJzSmGSSLo/WB0SKLkDyrIUbHmG+UroTRFMa/EOc7BSYsOBWmhKdEedszV4vJacFC1DFdzQDIYsQ74hTp5FoqF1B5+YpoijjbiQCGCOR4XccaKsrEhzh16BfG4K4SsKI443nlzE/SIAIU+w/QK+6bbgOhy+LFj3IGelRZytG0R/0CBqhFryEpVBp+zQnMohYx6rBvEszR8wru4doI7feEeI0YR9tOUJTXbyRDS25zlOFBbElFgvq4UzBWUnl5pvyPpRWrp1bMUWpFbwB9tdKIv41WZkMwQADPdn+q4/eDVzmYWo4eB9uqN9ip0e2pA0ZBHdCsFF43dc7E34Y6VqvM8kJ0LARnsaSVS0SeH96Z+BXzPXZNiykQF38nYvcec9om570rtuWOrWTrLvKj1GzQU3j2KIRnzxp4n6nZZ+Jv1N9YP0nR7ssdZ5Tc0TXgWlwIXsHzDIePThy8sMfiD7i0uG51hqgGjXTfFmJrEESNBqLGmWrQHX+bagA6tmFZjXruw+b0VPDeZUxPbDr33olte3Q78YDa+y9Ssy+R2jWr6f/Kst1Fb/MSep8I1vRuIzk4vb2h2Q2GbTIqfcVr1Xn1JsPpKYtx3BnarlPjCbhLw2n/JA1nXy64o+CC12w3/O725Ez8bbjjdNVvq3f9FkW6fddz7yLtNB6zHJ4ODFelnY/Vg7g9BTaOOzrPrOMNzjwNOBN/G4G5XQIDlwisvePfW2CePTqBea30trKU/0A8TFSS9pxCbLFGklplZg7vSWVABPPkkNJK+nLO6A80p4QyYcloJqX6HROiTRMLBEt/bov8PRD4jsgTzTHHNBO+EMmLCofMNQ6FlhsB75RzmlYCvhAcSweXmn+AanQ4Dy24YJCYjn4HbZTrWMk1p9tYvhqfrhGCqTUVgcX2ShW2+b5w1iYAsNv4a9v18fc/8b8j/qBZAMyh8Z994j+g/r2B4dfT+YT/LvD7DfiB4Q6Mf/tbkjb+14J3sfSXljkueKdp/rbBWbT/6uwaELuNN6rAakvcPtHhgdnUvxhkMTx+jVY23Mdv+sDiHw==</diagram></mxfile>
|
|
@ -23,10 +23,6 @@ data "google_compute_image" "tunneling-12" {
|
|||
name = "tunneling-12"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "tunneling-13" {
|
||||
name = "tunneling-13"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "sshkeys-11" {
|
||||
name = "sshkeys-11"
|
||||
project = local.monkeyzoo_project
|
||||
|
@ -59,26 +55,10 @@ data "google_compute_image" "powershell-3-46" {
|
|||
name = "powershell-3-46"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "powershell-3-44" {
|
||||
name = "powershell-3-44"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "powershell-3-45" {
|
||||
name = "powershell-3-45"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "credentials-reuse-14" {
|
||||
name = "credentials-reuse-14"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "credentials-reuse-15" {
|
||||
name = "credentials-reuse-15"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "credentials-reuse-16" {
|
||||
name = "credentials-reuse-16"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "log4j-solr-49" {
|
||||
name = "log4j-solr-49"
|
||||
project = local.monkeyzoo_project
|
||||
|
|
|
@ -44,18 +44,6 @@ resource "google_compute_subnetwork" "tunneling2-main" {
|
|||
network = google_compute_network.tunneling2.self_link
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork" "credential-reuse" {
|
||||
name = "${local.resource_prefix}credential-reuse"
|
||||
ip_cidr_range = "10.2.4.0/24"
|
||||
network = google_compute_network.credential-reuse.self_link
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork" "credential-reuse2" {
|
||||
name = "${local.resource_prefix}credential-reuse2"
|
||||
ip_cidr_range = "10.2.5.0/24"
|
||||
network = google_compute_network.credential-reuse2.self_link
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "hadoop-2" {
|
||||
name = "${local.resource_prefix}hadoop-2"
|
||||
source_instance_template = local.default_ubuntu
|
||||
|
@ -139,10 +127,6 @@ resource "google_compute_instance_from_template" "tunneling-11" {
|
|||
subnetwork="${local.resource_prefix}tunneling2-main"
|
||||
network_ip="10.2.0.11"
|
||||
}
|
||||
network_interface{
|
||||
subnetwork="${local.resource_prefix}tunneling-main"
|
||||
network_ip="10.2.1.11"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "tunneling-12" {
|
||||
|
@ -160,21 +144,6 @@ resource "google_compute_instance_from_template" "tunneling-12" {
|
|||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "tunneling-13" {
|
||||
name = "${local.resource_prefix}tunneling-13"
|
||||
source_instance_template = local.default_ubuntu
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.tunneling-13.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface{
|
||||
subnetwork="${local.resource_prefix}tunneling2-main"
|
||||
network_ip="10.2.0.13"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "sshkeys-11" {
|
||||
name = "${local.resource_prefix}sshkeys-11"
|
||||
source_instance_template = local.default_ubuntu
|
||||
|
@ -311,18 +280,18 @@ resource "google_compute_instance_from_template" "powershell-3-46" {
|
|||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "powershell-3-44" {
|
||||
name = "${local.resource_prefix}powershell-3-44"
|
||||
resource "google_compute_instance_from_template" "powershell-3-45" {
|
||||
name = "${local.resource_prefix}powershell-3-45"
|
||||
source_instance_template = local.default_windows
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.powershell-3-44.self_link
|
||||
image = data.google_compute_image.powershell-3-45.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||
network_ip="10.2.3.44"
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main"
|
||||
network_ip="10.2.3.45"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,68 +305,11 @@ resource "google_compute_instance_from_template" "powershell-3-45" {
|
|||
auto_delete = true
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main"
|
||||
network_ip="10.2.3.45"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "credentials-reuse-14" {
|
||||
name = "${local.resource_prefix}credentials-reuse-14"
|
||||
source_instance_template = local.default_linux
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.credentials-reuse-14.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||
network_ip="10.2.3.14"
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}credential-reuse"
|
||||
network_ip="10.2.4.14"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "credentials-reuse-15" {
|
||||
name = "${local.resource_prefix}credentials-reuse-15"
|
||||
source_instance_template = local.default_linux
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.credentials-reuse-15.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}credential-reuse"
|
||||
network_ip="10.2.4.15"
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}credential-reuse2"
|
||||
network_ip="10.2.5.15"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "credentials-reuse-16" {
|
||||
name = "${local.resource_prefix}credentials-reuse-16"
|
||||
source_instance_template = local.default_linux
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.credentials-reuse-16.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}credential-reuse2"
|
||||
network_ip="10.2.5.16"
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||
network_ip="10.2.3.16"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "log4j-solr-49" {
|
||||
name = "${local.resource_prefix}log4j-solr-49"
|
||||
source_instance_template = local.default_linux
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
from ipaddress import IPv4Address
|
||||
from typing import Collection
|
||||
|
||||
import pytest
|
||||
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
|
@ -43,17 +40,18 @@ def island_client(island):
|
|||
@pytest.mark.usefixtures("island_client")
|
||||
# noinspection PyUnresolvedReferences
|
||||
class TestOSCompatibility(object):
|
||||
def test_os_compat(self, island_client: MonkeyIslandClient):
|
||||
def test_os_compat(self, island_client):
|
||||
print()
|
||||
ips_that_communicated = self._get_agent_ips(island_client)
|
||||
all_monkeys = island_client.get_all_monkeys_from_db()
|
||||
ips_that_communicated = []
|
||||
for monkey in all_monkeys:
|
||||
for ip in monkey["ip_addresses"]:
|
||||
if ip in machine_list:
|
||||
ips_that_communicated.append(ip)
|
||||
break
|
||||
for ip, os in machine_list.items():
|
||||
if IPv4Address(ip) not in ips_that_communicated:
|
||||
if ip not in ips_that_communicated:
|
||||
print("{} didn't communicate to island".format(os))
|
||||
|
||||
if len(ips_that_communicated) < len(machine_list):
|
||||
assert False
|
||||
|
||||
def _get_agent_ips(self, island_client: MonkeyIslandClient) -> Collection[IPv4Address]:
|
||||
agents = island_client.get_agents()
|
||||
machines = island_client.get_machines()
|
||||
return {i.ip for a in agents for i in machines[a.machine_id].network_interfaces}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
"""
|
||||
Used for a common things between agent and island
|
||||
"""
|
||||
from . import transforms
|
||||
from .di_container import DIContainer, UnresolvableDependencyError
|
||||
from .operating_system import OperatingSystem
|
||||
from . import types
|
||||
from . import base_models
|
||||
from .agent_registration_data import AgentRegistrationData
|
||||
from .agent_signals import AgentSignals
|
||||
from .di_container import DIContainer, UnregisteredTypeError
|
||||
from .operating_systems import OperatingSystems
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .agent_configuration import AgentConfiguration
|
||||
from .agent_configuration import AgentConfiguration, InvalidConfigurationError
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
PluginConfiguration,
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
from typing import Tuple
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import confloat
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Mapping, Tuple
|
||||
|
||||
from common.base_models import MutableInfectionMonkeyBaseModel
|
||||
from marshmallow import Schema, fields, validate
|
||||
from marshmallow.exceptions import MarshmallowError
|
||||
|
||||
from ..utils.code_utils import freeze_lists_in_mapping
|
||||
from .agent_sub_configuration_schemas import (
|
||||
CustomPBAConfigurationSchema,
|
||||
PluginConfigurationSchema,
|
||||
PropagationConfigurationSchema,
|
||||
)
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
PluginConfiguration,
|
||||
|
@ -11,10 +19,107 @@ from .agent_sub_configurations import (
|
|||
)
|
||||
|
||||
|
||||
class AgentConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
keep_tunnel_open_time: confloat(ge=0) # type: ignore[valid-type]
|
||||
class InvalidConfigurationError(Exception):
|
||||
def __init__(self, message: str):
|
||||
self._message = message
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"Cannot construct an AgentConfiguration object with the supplied, invalid data: "
|
||||
f"{self._message}"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AgentConfiguration:
|
||||
"""
|
||||
A configuration for Infection Monkey agents
|
||||
|
||||
Attributes:
|
||||
:param keep_tunnel_open_time: Maximum time in seconds to keep a tunnel open after
|
||||
the last exploit
|
||||
:param custom_pbas: Configuration for custom post-breach actions
|
||||
:param post_breach_actions: Configuration for post-breach actions
|
||||
:param credential_collectors: Configuration for credential collectors
|
||||
:param payloads: Configuration for payloads
|
||||
:param propagation: Configuration for propagation
|
||||
"""
|
||||
|
||||
keep_tunnel_open_time: float
|
||||
custom_pbas: CustomPBAConfiguration
|
||||
post_breach_actions: Tuple[PluginConfiguration, ...]
|
||||
credential_collectors: Tuple[PluginConfiguration, ...]
|
||||
payloads: Tuple[PluginConfiguration, ...]
|
||||
propagation: PropagationConfiguration
|
||||
|
||||
def __post_init__(self):
|
||||
# This will raise an exception if the object is invalid. Calling this in __post__init()
|
||||
# makes it impossible to construct an invalid object
|
||||
try:
|
||||
AgentConfigurationSchema().dump(self)
|
||||
except Exception as err:
|
||||
raise InvalidConfigurationError(str(err))
|
||||
|
||||
@staticmethod
|
||||
def from_mapping(config_mapping: Mapping[str, Any]) -> AgentConfiguration:
|
||||
"""
|
||||
Construct an AgentConfiguration from a Mapping
|
||||
|
||||
:param config_mapping: A Mapping that represents an AgentConfiguration
|
||||
:return: An AgentConfiguration
|
||||
:raises: InvalidConfigurationError if the provided Mapping does not represent a valid
|
||||
AgentConfiguration
|
||||
"""
|
||||
|
||||
try:
|
||||
config_dict = AgentConfigurationSchema().load(config_mapping)
|
||||
config_dict = freeze_lists_in_mapping(config_dict)
|
||||
return AgentConfiguration(**config_dict)
|
||||
except MarshmallowError as err:
|
||||
raise InvalidConfigurationError(str(err))
|
||||
|
||||
@staticmethod
|
||||
def from_json(config_json: str) -> AgentConfiguration:
|
||||
"""
|
||||
Construct an AgentConfiguration from a JSON string
|
||||
|
||||
:param config_json: A JSON string that represents an AgentConfiguration
|
||||
:return: An AgentConfiguration
|
||||
:raises: InvalidConfigurationError if the provided JSON does not represent a valid
|
||||
AgentConfiguration
|
||||
"""
|
||||
try:
|
||||
config_dict = AgentConfigurationSchema().loads(config_json)
|
||||
config_dict = freeze_lists_in_mapping(config_dict)
|
||||
return AgentConfiguration(**config_dict)
|
||||
except MarshmallowError as err:
|
||||
raise InvalidConfigurationError(str(err))
|
||||
|
||||
@staticmethod
|
||||
def to_mapping(config: AgentConfiguration) -> Mapping[str, Any]:
|
||||
"""
|
||||
Serialize an AgentConfiguration to a Mapping
|
||||
|
||||
:param config: An AgentConfiguration
|
||||
:return: A Mapping that represents the AgentConfiguration
|
||||
"""
|
||||
return AgentConfigurationSchema().dump(config)
|
||||
|
||||
@staticmethod
|
||||
def to_json(config: AgentConfiguration) -> str:
|
||||
"""
|
||||
Serialize an AgentConfiguration to JSON
|
||||
|
||||
:param config: An AgentConfiguration
|
||||
:return: A JSON string that represents the AgentConfiguration
|
||||
"""
|
||||
return AgentConfigurationSchema().dumps(config)
|
||||
|
||||
|
||||
class AgentConfigurationSchema(Schema):
|
||||
keep_tunnel_open_time = fields.Float(validate=validate.Range(min=0))
|
||||
custom_pbas = fields.Nested(CustomPBAConfigurationSchema)
|
||||
post_breach_actions = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
credential_collectors = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
payloads = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
propagation = fields.Nested(PropagationConfigurationSchema)
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
import re
|
||||
|
||||
from marshmallow import Schema, ValidationError, fields, post_load, validate, validates
|
||||
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
ExploitationConfiguration,
|
||||
ExploitationOptionsConfiguration,
|
||||
ICMPScanConfiguration,
|
||||
NetworkScanConfiguration,
|
||||
PluginConfiguration,
|
||||
PropagationConfiguration,
|
||||
ScanTargetConfiguration,
|
||||
TCPScanConfiguration,
|
||||
)
|
||||
from .utils import freeze_lists
|
||||
|
||||
valid_windows_custom_pba_filename_regex = re.compile(r"^[^<>:\"\\\/|?*]*[^<>:\"\\\/|?* \.]+$|^$")
|
||||
valid_linux_custom_pba_filename_regex = re.compile(r"^[^\0/]*$")
|
||||
|
||||
|
||||
class CustomPBAConfigurationSchema(Schema):
|
||||
linux_command = fields.Str()
|
||||
linux_filename = fields.Str(
|
||||
validate=validate.Regexp(regex=valid_linux_custom_pba_filename_regex)
|
||||
)
|
||||
windows_command = fields.Str()
|
||||
windows_filename = fields.Str(
|
||||
validate=validate.Regexp(regex=valid_windows_custom_pba_filename_regex)
|
||||
)
|
||||
|
||||
@validates("windows_filename")
|
||||
def validate_windows_filename_not_reserved(self, windows_filename):
|
||||
# filename shouldn't start with any of these and be followed by a period
|
||||
if windows_filename.split(".")[0].upper() in [
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"NUL",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"COM5",
|
||||
"COM6",
|
||||
"COM7",
|
||||
"COM8",
|
||||
"COM9",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"LPT4",
|
||||
"LPT5",
|
||||
"LPT6",
|
||||
"LPT7",
|
||||
"LPT8",
|
||||
"LPT9",
|
||||
]:
|
||||
raise ValidationError("Invalid Windows filename: reserved name used")
|
||||
|
||||
@post_load
|
||||
def _make_custom_pba_configuration(self, data, **kwargs):
|
||||
return CustomPBAConfiguration(**data)
|
||||
|
||||
|
||||
class PluginConfigurationSchema(Schema):
|
||||
name = fields.Str()
|
||||
options = fields.Mapping()
|
||||
|
||||
@post_load
|
||||
def _make_plugin_configuration(self, data, **kwargs):
|
||||
return PluginConfiguration(**data)
|
||||
|
||||
|
||||
class ScanTargetConfigurationSchema(Schema):
|
||||
blocked_ips = fields.List(fields.Str())
|
||||
inaccessible_subnets = fields.List(fields.Str())
|
||||
local_network_scan = fields.Bool()
|
||||
subnets = fields.List(fields.Str())
|
||||
|
||||
@post_load
|
||||
@freeze_lists
|
||||
def _make_scan_target_configuration(self, data, **kwargs):
|
||||
return ScanTargetConfiguration(**data)
|
||||
|
||||
|
||||
class ICMPScanConfigurationSchema(Schema):
|
||||
timeout = fields.Float(validate=validate.Range(min=0))
|
||||
|
||||
@post_load
|
||||
def _make_icmp_scan_configuration(self, data, **kwargs):
|
||||
return ICMPScanConfiguration(**data)
|
||||
|
||||
|
||||
class TCPScanConfigurationSchema(Schema):
|
||||
timeout = fields.Float(validate=validate.Range(min=0))
|
||||
ports = fields.List(fields.Int(validate=validate.Range(min=0, max=65535)))
|
||||
|
||||
@post_load
|
||||
@freeze_lists
|
||||
def _make_tcp_scan_configuration(self, data, **kwargs):
|
||||
return TCPScanConfiguration(**data)
|
||||
|
||||
|
||||
class NetworkScanConfigurationSchema(Schema):
|
||||
tcp = fields.Nested(TCPScanConfigurationSchema)
|
||||
icmp = fields.Nested(ICMPScanConfigurationSchema)
|
||||
fingerprinters = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
targets = fields.Nested(ScanTargetConfigurationSchema)
|
||||
|
||||
@post_load
|
||||
@freeze_lists
|
||||
def _make_network_scan_configuration(self, data, **kwargs):
|
||||
return NetworkScanConfiguration(**data)
|
||||
|
||||
|
||||
class ExploitationOptionsConfigurationSchema(Schema):
|
||||
http_ports = fields.List(fields.Int(validate=validate.Range(min=0, max=65535)))
|
||||
|
||||
@post_load
|
||||
@freeze_lists
|
||||
def _make_exploitation_options_configuration(self, data, **kwargs):
|
||||
return ExploitationOptionsConfiguration(**data)
|
||||
|
||||
|
||||
class ExploitationConfigurationSchema(Schema):
|
||||
options = fields.Nested(ExploitationOptionsConfigurationSchema)
|
||||
brute_force = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
vulnerability = fields.List(fields.Nested(PluginConfigurationSchema))
|
||||
|
||||
@post_load
|
||||
@freeze_lists
|
||||
def _make_exploitation_options_configuration(self, data, **kwargs):
|
||||
return ExploitationConfiguration(**data)
|
||||
|
||||
|
||||
class PropagationConfigurationSchema(Schema):
|
||||
maximum_depth = fields.Int(validate=validate.Range(min=0))
|
||||
network_scan = fields.Nested(NetworkScanConfigurationSchema)
|
||||
exploitation = fields.Nested(ExploitationConfigurationSchema)
|
||||
|
||||
@post_load
|
||||
def _make_propagation_configuration(self, data, **kwargs):
|
||||
return PropagationConfiguration(**data)
|
|
@ -1,19 +1,9 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from pydantic import PositiveFloat, conint, validator
|
||||
|
||||
from common.base_models import MutableInfectionMonkeyBaseModel
|
||||
from common.types import NetworkPort
|
||||
|
||||
from .validators import (
|
||||
validate_ip,
|
||||
validate_linux_filename,
|
||||
validate_subnet_range,
|
||||
validate_windows_filename,
|
||||
)
|
||||
|
||||
|
||||
class CustomPBAConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
@dataclass(frozen=True)
|
||||
class CustomPBAConfiguration:
|
||||
"""
|
||||
A configuration for custom post-breach actions
|
||||
|
||||
|
@ -34,18 +24,9 @@ class CustomPBAConfiguration(MutableInfectionMonkeyBaseModel):
|
|||
windows_command: str
|
||||
windows_filename: str
|
||||
|
||||
@validator("linux_filename")
|
||||
def linux_filename_valid(cls, filename):
|
||||
validate_linux_filename(filename)
|
||||
return filename
|
||||
|
||||
@validator("windows_filename")
|
||||
def windows_filename_valid(cls, filename):
|
||||
validate_windows_filename(filename)
|
||||
return filename
|
||||
|
||||
|
||||
class PluginConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
@dataclass(frozen=True)
|
||||
class PluginConfiguration:
|
||||
"""
|
||||
A configuration for plugins
|
||||
|
||||
|
@ -71,44 +52,16 @@ class PluginConfiguration(MutableInfectionMonkeyBaseModel):
|
|||
options: Dict
|
||||
|
||||
|
||||
class ScanTargetConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
"""
|
||||
Configuration of network targets to scan and exploit
|
||||
|
||||
Attributes:
|
||||
:param blocked_ips: IP's that won't be scanned
|
||||
Example: ("1.1.1.1", "2.2.2.2")
|
||||
:param inaccessible_subnets: Subnet ranges that shouldn't be accessible for the agent
|
||||
Example: ("1.1.1.1", "2.2.2.2/24", "myserver")
|
||||
:param scan_my_networks: If true the Agent will scan networks it belongs to
|
||||
in addition to the provided subnet ranges
|
||||
:param subnets: Subnet ranges to scan
|
||||
Example: ("192.168.1.1-192.168.2.255", "3.3.3.3", "2.2.2.2/24",
|
||||
"myHostname")
|
||||
"""
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ScanTargetConfiguration:
|
||||
blocked_ips: Tuple[str, ...]
|
||||
inaccessible_subnets: Tuple[str, ...]
|
||||
scan_my_networks: bool
|
||||
local_network_scan: bool
|
||||
subnets: Tuple[str, ...]
|
||||
|
||||
@validator("blocked_ips", each_item=True)
|
||||
def blocked_ips_valid(cls, ip):
|
||||
validate_ip(ip)
|
||||
return ip
|
||||
|
||||
@validator("inaccessible_subnets", each_item=True)
|
||||
def inaccessible_subnets_valid(cls, subnet_range):
|
||||
validate_subnet_range(subnet_range)
|
||||
return subnet_range
|
||||
|
||||
@validator("subnets", each_item=True)
|
||||
def subnets_valid(cls, subnet_range):
|
||||
validate_subnet_range(subnet_range)
|
||||
return subnet_range
|
||||
|
||||
|
||||
class ICMPScanConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
@dataclass(frozen=True)
|
||||
class ICMPScanConfiguration:
|
||||
"""
|
||||
A configuration for ICMP scanning
|
||||
|
||||
|
@ -116,10 +69,11 @@ class ICMPScanConfiguration(MutableInfectionMonkeyBaseModel):
|
|||
:param timeout: Maximum time in seconds to wait for a response from the target
|
||||
"""
|
||||
|
||||
timeout: PositiveFloat
|
||||
timeout: float
|
||||
|
||||
|
||||
class TCPScanConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
@dataclass(frozen=True)
|
||||
class TCPScanConfiguration:
|
||||
"""
|
||||
A configuration for TCP scanning
|
||||
|
||||
|
@ -128,11 +82,12 @@ class TCPScanConfiguration(MutableInfectionMonkeyBaseModel):
|
|||
:param ports: Ports to scan
|
||||
"""
|
||||
|
||||
timeout: PositiveFloat
|
||||
ports: Tuple[NetworkPort, ...]
|
||||
timeout: float
|
||||
ports: Tuple[int, ...]
|
||||
|
||||
|
||||
class NetworkScanConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
@dataclass(frozen=True)
|
||||
class NetworkScanConfiguration:
|
||||
"""
|
||||
A configuration for network scanning
|
||||
|
||||
|
@ -149,7 +104,8 @@ class NetworkScanConfiguration(MutableInfectionMonkeyBaseModel):
|
|||
targets: ScanTargetConfiguration
|
||||
|
||||
|
||||
class ExploitationOptionsConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
@dataclass(frozen=True)
|
||||
class ExploitationOptionsConfiguration:
|
||||
"""
|
||||
A configuration for exploitation options
|
||||
|
||||
|
@ -157,10 +113,11 @@ class ExploitationOptionsConfiguration(MutableInfectionMonkeyBaseModel):
|
|||
:param http_ports: HTTP ports to exploit
|
||||
"""
|
||||
|
||||
http_ports: Tuple[NetworkPort, ...]
|
||||
http_ports: Tuple[int, ...]
|
||||
|
||||
|
||||
class ExploitationConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
@dataclass(frozen=True)
|
||||
class ExploitationConfiguration:
|
||||
"""
|
||||
A configuration for exploitation
|
||||
|
||||
|
@ -175,7 +132,8 @@ class ExploitationConfiguration(MutableInfectionMonkeyBaseModel):
|
|||
vulnerability: Tuple[PluginConfiguration, ...]
|
||||
|
||||
|
||||
class PropagationConfiguration(MutableInfectionMonkeyBaseModel):
|
||||
@dataclass(frozen=True)
|
||||
class PropagationConfiguration:
|
||||
"""
|
||||
A configuration for propagation
|
||||
|
||||
|
@ -187,6 +145,6 @@ class PropagationConfiguration(MutableInfectionMonkeyBaseModel):
|
|||
:param exploitation: Configuration for exploitation
|
||||
"""
|
||||
|
||||
maximum_depth: conint(ge=0) # type: ignore[valid-type]
|
||||
maximum_depth: int
|
||||
network_scan: NetworkScanConfiguration
|
||||
exploitation: ExploitationConfiguration
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import dataclasses
|
||||
|
||||
from . import AgentConfiguration
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
|
@ -25,21 +27,20 @@ PBAS = (
|
|||
|
||||
CREDENTIAL_COLLECTORS = ("MimikatzCollector", "SSHCollector")
|
||||
|
||||
PBA_CONFIGURATION = tuple(PluginConfiguration(name=pba, options={}) for pba in PBAS)
|
||||
PBA_CONFIGURATION = tuple(PluginConfiguration(pba, {}) for pba in PBAS)
|
||||
CREDENTIAL_COLLECTOR_CONFIGURATION = tuple(
|
||||
PluginConfiguration(name=collector, options={}) for collector in CREDENTIAL_COLLECTORS
|
||||
PluginConfiguration(collector, {}) for collector in CREDENTIAL_COLLECTORS
|
||||
)
|
||||
|
||||
RANSOMWARE_OPTIONS = {
|
||||
"encryption": {
|
||||
"enabled": True,
|
||||
"file_extension": ".m0nk3y",
|
||||
"directories": {"linux_target_dir": "", "windows_target_dir": ""},
|
||||
},
|
||||
"other_behaviors": {"readme": True},
|
||||
}
|
||||
|
||||
PAYLOAD_CONFIGURATION = tuple([PluginConfiguration(name="ransomware", options=RANSOMWARE_OPTIONS)])
|
||||
PAYLOAD_CONFIGURATION = tuple([PluginConfiguration("ransomware", RANSOMWARE_OPTIONS)])
|
||||
|
||||
CUSTOM_PBA_CONFIGURATION = CustomPBAConfiguration(
|
||||
linux_command="", linux_filename="", windows_command="", windows_filename=""
|
||||
|
@ -69,42 +70,35 @@ TCP_SCAN_CONFIGURATION = TCPScanConfiguration(timeout=3.0, ports=TCP_PORTS)
|
|||
ICMP_CONFIGURATION = ICMPScanConfiguration(timeout=1.0)
|
||||
HTTP_PORTS = (80, 443, 7001, 8008, 8080, 8983, 9200, 9600)
|
||||
FINGERPRINTERS = (
|
||||
PluginConfiguration(name="elastic", options={}),
|
||||
PluginConfiguration("elastic", {}),
|
||||
# Plugin configuration option contents are not converted to tuples
|
||||
PluginConfiguration(name="http", options={"http_ports": list(HTTP_PORTS)}),
|
||||
PluginConfiguration(name="mssql", options={}),
|
||||
PluginConfiguration(name="smb", options={}),
|
||||
PluginConfiguration(name="ssh", options={}),
|
||||
PluginConfiguration("http", {"http_ports": list(HTTP_PORTS)}),
|
||||
PluginConfiguration("mssql", {}),
|
||||
PluginConfiguration("smb", {}),
|
||||
PluginConfiguration("ssh", {}),
|
||||
)
|
||||
|
||||
SCAN_TARGET_CONFIGURATION = ScanTargetConfiguration(
|
||||
blocked_ips=tuple(), inaccessible_subnets=tuple(), scan_my_networks=False, subnets=tuple()
|
||||
)
|
||||
SCAN_TARGET_CONFIGURATION = ScanTargetConfiguration(tuple(), tuple(), True, tuple())
|
||||
NETWORK_SCAN_CONFIGURATION = NetworkScanConfiguration(
|
||||
tcp=TCP_SCAN_CONFIGURATION,
|
||||
icmp=ICMP_CONFIGURATION,
|
||||
fingerprinters=FINGERPRINTERS,
|
||||
targets=SCAN_TARGET_CONFIGURATION,
|
||||
TCP_SCAN_CONFIGURATION, ICMP_CONFIGURATION, FINGERPRINTERS, SCAN_TARGET_CONFIGURATION
|
||||
)
|
||||
|
||||
EXPLOITATION_OPTIONS_CONFIGURATION = ExploitationOptionsConfiguration(http_ports=HTTP_PORTS)
|
||||
EXPLOITATION_OPTIONS_CONFIGURATION = ExploitationOptionsConfiguration(HTTP_PORTS)
|
||||
BRUTE_FORCE_EXPLOITERS = (
|
||||
PluginConfiguration(name="MSSQLExploiter", options={}),
|
||||
PluginConfiguration(name="PowerShellExploiter", options={}),
|
||||
PluginConfiguration(name="SSHExploiter", options={}),
|
||||
PluginConfiguration(name="SmbExploiter", options={"smb_download_timeout": 30}),
|
||||
PluginConfiguration(name="WmiExploiter", options={"smb_download_timeout": 30}),
|
||||
PluginConfiguration("MSSQLExploiter", {}),
|
||||
PluginConfiguration("PowerShellExploiter", {}),
|
||||
PluginConfiguration("SSHExploiter", {}),
|
||||
PluginConfiguration("SmbExploiter", {"smb_download_timeout": 30}),
|
||||
PluginConfiguration("WmiExploiter", {"smb_download_timeout": 30}),
|
||||
)
|
||||
|
||||
VULNERABILITY_EXPLOITERS = (
|
||||
PluginConfiguration(name="Log4ShellExploiter", options={}),
|
||||
PluginConfiguration(name="HadoopExploiter", options={}),
|
||||
PluginConfiguration("Log4ShellExploiter", {}),
|
||||
PluginConfiguration("HadoopExploiter", {}),
|
||||
)
|
||||
|
||||
EXPLOITATION_CONFIGURATION = ExploitationConfiguration(
|
||||
options=EXPLOITATION_OPTIONS_CONFIGURATION,
|
||||
brute_force=BRUTE_FORCE_EXPLOITERS,
|
||||
vulnerability=VULNERABILITY_EXPLOITERS,
|
||||
EXPLOITATION_OPTIONS_CONFIGURATION, BRUTE_FORCE_EXPLOITERS, VULNERABILITY_EXPLOITERS
|
||||
)
|
||||
|
||||
PROPAGATION_CONFIGURATION = PropagationConfiguration(
|
||||
|
@ -122,6 +116,6 @@ DEFAULT_AGENT_CONFIGURATION = AgentConfiguration(
|
|||
propagation=PROPAGATION_CONFIGURATION,
|
||||
)
|
||||
|
||||
DEFAULT_RANSOMWARE_AGENT_CONFIGURATION = DEFAULT_AGENT_CONFIGURATION.copy(
|
||||
update={"post_breach_actions": tuple()}
|
||||
DEFAULT_RANSOMWARE_AGENT_CONFIGURATION = dataclasses.replace(
|
||||
DEFAULT_AGENT_CONFIGURATION, post_breach_actions=tuple()
|
||||
)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
from functools import wraps
|
||||
from typing import Callable
|
||||
|
||||
from common.utils.code_utils import freeze_lists_in_mapping
|
||||
|
||||
|
||||
def freeze_lists(function: Callable):
|
||||
@wraps(function)
|
||||
def wrapper(self, data, **kwargs):
|
||||
data = freeze_lists_in_mapping(data)
|
||||
return function(self, data, **kwargs)
|
||||
|
||||
return wrapper
|
|
@ -1,8 +0,0 @@
|
|||
from .filenames import validate_linux_filename, validate_windows_filename
|
||||
from .ip_ranges import (
|
||||
validate_ip,
|
||||
validate_hostname,
|
||||
validate_ip_range,
|
||||
validate_subnet_range,
|
||||
validate_ip_network,
|
||||
)
|
|
@ -1,22 +0,0 @@
|
|||
import re
|
||||
from pathlib import PureWindowsPath
|
||||
|
||||
_valid_windows_filename_regex = re.compile(r"^[^<>:\"\\\/|?*]*[^<>:\"\\\/|?* \.]+$|^$")
|
||||
_valid_linux_filename_regex = re.compile(r"^[^\0/]*$")
|
||||
|
||||
|
||||
def validate_linux_filename(linux_filename: str):
|
||||
if not re.match(_valid_linux_filename_regex, linux_filename):
|
||||
raise ValueError(f"Invalid Unix filename {linux_filename}: illegal characters")
|
||||
|
||||
|
||||
def validate_windows_filename(windows_filename: str):
|
||||
_validate_windows_filename_not_reserved(windows_filename)
|
||||
if not re.match(_valid_windows_filename_regex, windows_filename):
|
||||
raise ValueError(f"Invalid Windows filename {windows_filename}: illegal characters")
|
||||
|
||||
|
||||
def _validate_windows_filename_not_reserved(windows_filename: str):
|
||||
# filename shouldn't start with any of these and be followed by a period
|
||||
if PureWindowsPath(windows_filename).is_reserved():
|
||||
raise ValueError(f"Invalid Windows filename {windows_filename}: reserved name used")
|
|
@ -1,65 +0,0 @@
|
|||
import re
|
||||
from ipaddress import AddressValueError, IPv4Address, IPv4Network, NetmaskValueError
|
||||
|
||||
|
||||
def validate_subnet_range(subnet_range: str):
|
||||
try:
|
||||
return validate_ip(subnet_range)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return validate_ip_range(subnet_range)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return validate_ip_network(subnet_range)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return validate_hostname(subnet_range)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid subnet range {subnet_range}")
|
||||
|
||||
|
||||
def validate_hostname(hostname: str):
|
||||
# Based on hostname syntax: https://www.rfc-editor.org/rfc/rfc1123#page-13
|
||||
hostname_segments = hostname.split(".")
|
||||
if any((part.endswith("-") or part.startswith("-") for part in hostname_segments)):
|
||||
raise ValueError(f"Hostname segment can't start or end with a hyphen: {hostname}")
|
||||
if not any((char.isalpha() for char in hostname_segments[-1])):
|
||||
raise ValueError(f"Last segment of a hostname must contain a letter: {hostname}")
|
||||
|
||||
valid_characters_pattern = r"^[A-Za-z0-9\-]+$"
|
||||
valid_characters_regex = re.compile(valid_characters_pattern)
|
||||
matches = (
|
||||
re.match(valid_characters_regex, hostname_segment) for hostname_segment in hostname_segments
|
||||
)
|
||||
|
||||
if not all(matches):
|
||||
raise ValueError(f"Hostname contains invalid characters: {hostname}")
|
||||
|
||||
|
||||
def validate_ip_network(ip_network: str):
|
||||
try:
|
||||
IPv4Network(ip_network, strict=False)
|
||||
except (NetmaskValueError, AddressValueError):
|
||||
raise ValueError(f"Invalid IPv4 network {ip_network}")
|
||||
|
||||
|
||||
def validate_ip_range(ip_range: str):
|
||||
ip_range = ip_range.replace(" ", "")
|
||||
ips = ip_range.split("-")
|
||||
if len(ips) != 2:
|
||||
raise ValueError(f"Invalid IP range {ip_range}")
|
||||
validate_ip(ips[0])
|
||||
validate_ip(ips[1])
|
||||
|
||||
|
||||
def validate_ip(ip: str):
|
||||
try:
|
||||
IPv4Address(ip)
|
||||
except AddressValueError:
|
||||
raise ValueError(f"Invalid IP address {ip}")
|
|
@ -1,5 +0,0 @@
|
|||
from .consts import EVENT_TYPE_FIELD
|
||||
from .i_agent_event_serializer import IAgentEventSerializer
|
||||
from .agent_event_serializer_registry import AgentEventSerializerRegistry
|
||||
from .pydantic_agent_event_serializer import PydanticAgentEventSerializer
|
||||
from .register import register_common_agent_event_serializers
|
|
@ -1,45 +0,0 @@
|
|||
from typing import Type, Union
|
||||
|
||||
from common.agent_event_serializers import IAgentEventSerializer
|
||||
from common.agent_events import AbstractAgentEvent
|
||||
|
||||
|
||||
class AgentEventSerializerRegistry:
|
||||
"""
|
||||
Registry for event serializers using event class.
|
||||
|
||||
Example:
|
||||
event_serializer_registry = AgentEventSerializerRegistry()
|
||||
event_serializer_registry[MyEvent] = MyEventSerializer()
|
||||
|
||||
my_event_dict = {"type": "MyEvent", "data": "123"}
|
||||
|
||||
serializer = event_serializer_registry[my_event_dict["type"]]
|
||||
my_event_object = serializer.deserialize(my_event_dict)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._registry = {}
|
||||
|
||||
def __setitem__(
|
||||
self, event_class: Type[AbstractAgentEvent], event_serializer: IAgentEventSerializer
|
||||
):
|
||||
if not issubclass(event_class, AbstractAgentEvent):
|
||||
raise TypeError(f"Event class must be of type: {AbstractAgentEvent.__name__}")
|
||||
|
||||
if not isinstance(event_serializer, IAgentEventSerializer):
|
||||
raise TypeError(f"Event serializer must be of type: {IAgentEventSerializer.__name__}")
|
||||
|
||||
self._registry[event_class] = event_serializer
|
||||
self._registry[event_class.__name__] = event_serializer
|
||||
|
||||
def __getitem__(
|
||||
self, event_class: Union[str, Type[AbstractAgentEvent]]
|
||||
) -> IAgentEventSerializer:
|
||||
if not (isinstance(event_class, str) or issubclass(event_class, AbstractAgentEvent)):
|
||||
raise TypeError(
|
||||
f"Registry get key {event_class} must be of type: {AbstractAgentEvent.__name__} or "
|
||||
f"{str.__name__}"
|
||||
)
|
||||
|
||||
return self._registry[event_class]
|
|
@ -1 +0,0 @@
|
|||
EVENT_TYPE_FIELD = "type"
|
|
@ -1,33 +0,0 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
from common.agent_events import AbstractAgentEvent
|
||||
from common.types import JSONSerializable
|
||||
|
||||
|
||||
class IAgentEventSerializer(ABC):
|
||||
"""
|
||||
Manages serialization and deserialization of events
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def serialize(self, event: AbstractAgentEvent) -> JSONSerializable:
|
||||
"""
|
||||
Serializes an event
|
||||
|
||||
:param event: Event to serialize
|
||||
:return: Serialized event
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def deserialize(self, serialized_event: JSONSerializable) -> AbstractAgentEvent:
|
||||
"""
|
||||
Deserializes an event
|
||||
|
||||
:param serialized_event: Serialized event to deserialize
|
||||
:return: Deserialized event
|
||||
:raises TypeError: If one or more of the serialized fields contains data of an incompatible
|
||||
type
|
||||
:raises ValueError: If one or more of the serialized fields contains an incompatible value
|
||||
"""
|
||||
pass
|
|
@ -1,38 +0,0 @@
|
|||
import logging
|
||||
from typing import Generic, Type, TypeVar
|
||||
|
||||
from common.agent_events import AbstractAgentEvent
|
||||
from common.types import JSONSerializable
|
||||
from common.utils.code_utils import del_key
|
||||
|
||||
from . import EVENT_TYPE_FIELD, IAgentEventSerializer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
T = TypeVar("T", bound=AbstractAgentEvent)
|
||||
|
||||
|
||||
class PydanticAgentEventSerializer(IAgentEventSerializer, Generic[T]):
|
||||
def __init__(self, event_class: Type[T]):
|
||||
self._event_class = event_class
|
||||
|
||||
def serialize(self, event: T) -> JSONSerializable:
|
||||
if not isinstance(event, self._event_class):
|
||||
raise TypeError(f"Event object must be of type: {self._event_class.__name__}")
|
||||
|
||||
event_dict = event.dict(simplify=True)
|
||||
event_dict[EVENT_TYPE_FIELD] = type(event).__name__
|
||||
|
||||
return event_dict
|
||||
|
||||
def deserialize(self, serialized_event: JSONSerializable) -> T:
|
||||
if not isinstance(serialized_event, dict):
|
||||
raise TypeError(
|
||||
"Serialized pydantic events must be a dictionary, but got {type(serialized_event)}"
|
||||
)
|
||||
|
||||
# pydantic serialized events will always be dicts with a copy() method
|
||||
event_dict = serialized_event.copy() # type: ignore[union-attr]
|
||||
del_key(event_dict, EVENT_TYPE_FIELD)
|
||||
|
||||
return self._event_class(**event_dict)
|
|
@ -1,21 +0,0 @@
|
|||
from common.agent_events import (
|
||||
CredentialsStolenEvent,
|
||||
ExploitationEvent,
|
||||
PingScanEvent,
|
||||
PropagationEvent,
|
||||
TCPScanEvent,
|
||||
)
|
||||
|
||||
from . import AgentEventSerializerRegistry, PydanticAgentEventSerializer
|
||||
|
||||
|
||||
def register_common_agent_event_serializers(
|
||||
event_serializer_registry: AgentEventSerializerRegistry,
|
||||
):
|
||||
event_serializer_registry[CredentialsStolenEvent] = PydanticAgentEventSerializer(
|
||||
CredentialsStolenEvent
|
||||
)
|
||||
event_serializer_registry[PingScanEvent] = PydanticAgentEventSerializer(PingScanEvent)
|
||||
event_serializer_registry[TCPScanEvent] = PydanticAgentEventSerializer(TCPScanEvent)
|
||||
event_serializer_registry[PropagationEvent] = PydanticAgentEventSerializer(PropagationEvent)
|
||||
event_serializer_registry[ExploitationEvent] = PydanticAgentEventSerializer(ExploitationEvent)
|
|
@ -1,6 +0,0 @@
|
|||
from .abstract_agent_event import AbstractAgentEvent
|
||||
from .credentials_stolen_events import CredentialsStolenEvent
|
||||
from .ping_scan_event import PingScanEvent
|
||||
from .tcp_scan_event import TCPScanEvent
|
||||
from .exploitation_event import ExploitationEvent
|
||||
from .propagation_event import PropagationEvent
|
|
@ -1,30 +0,0 @@
|
|||
import time
|
||||
from abc import ABC
|
||||
from ipaddress import IPv4Address
|
||||
from typing import FrozenSet, Union
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from common.base_models import InfectionMonkeyBaseModel
|
||||
from common.types import AgentID, MachineID
|
||||
|
||||
|
||||
class AbstractAgentEvent(InfectionMonkeyBaseModel, ABC):
|
||||
"""
|
||||
An event that was initiated or observed by an agent
|
||||
|
||||
Agents perform actions and collect data. These actions and data are represented as "events".
|
||||
Subtypes of `AbstractAgentEvent` will have additional properties that provide context and
|
||||
information about the event.
|
||||
|
||||
Attributes:
|
||||
:param source: The UUID of the agent that observed the event
|
||||
:param target: The target of the event (if not the local system)
|
||||
:param timestamp: The time that the event occurred (seconds since the Unix epoch)
|
||||
:param tags: The set of tags associated with the event
|
||||
"""
|
||||
|
||||
source: AgentID
|
||||
target: Union[IPv4Address, MachineID, None] = Field(default=None)
|
||||
timestamp: float = Field(default_factory=time.time)
|
||||
tags: FrozenSet[str] = Field(default_factory=frozenset)
|
|
@ -1,23 +0,0 @@
|
|||
from typing import Sequence
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from common.base_models import InfectionMonkeyModelConfig
|
||||
from common.credentials import Credentials
|
||||
|
||||
from ..credentials.encoding import SecretEncodingConfig
|
||||
from . import AbstractAgentEvent
|
||||
|
||||
|
||||
class CredentialsStolenEvent(AbstractAgentEvent):
|
||||
"""
|
||||
An event that occurs when an agent collects credentials from the victim
|
||||
|
||||
Attributes:
|
||||
:param stolen_credentials: The credentials that were stolen by an agent
|
||||
"""
|
||||
|
||||
stolen_credentials: Sequence[Credentials] = Field(default_factory=list)
|
||||
|
||||
class Config(SecretEncodingConfig, InfectionMonkeyModelConfig):
|
||||
pass
|
|
@ -1,22 +0,0 @@
|
|||
from ipaddress import IPv4Address
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from . import AbstractAgentEvent
|
||||
|
||||
|
||||
class ExploitationEvent(AbstractAgentEvent):
|
||||
"""
|
||||
An event that occurs when the Agent exploits a host
|
||||
|
||||
Attributes:
|
||||
:param target: IP address of the exploited system
|
||||
:param success: Status of the exploitation
|
||||
:param exploiter_name: Name of the exploiter that triggered the event
|
||||
:param error_message: Message if an error occurs during exploitation
|
||||
"""
|
||||
|
||||
target: IPv4Address
|
||||
success: bool
|
||||
exploiter_name: str
|
||||
error_message: str = Field(default="")
|
|
@ -1,21 +0,0 @@
|
|||
from ipaddress import IPv4Address
|
||||
from typing import Optional
|
||||
|
||||
from common import OperatingSystem
|
||||
|
||||
from . import AbstractAgentEvent
|
||||
|
||||
|
||||
class PingScanEvent(AbstractAgentEvent):
|
||||
"""
|
||||
An event that occurs when the agent performs a ping scan on its network
|
||||
|
||||
Attributes:
|
||||
:param target: IP address of the pinged system
|
||||
:param response_received: Indicates if target responded to the ping
|
||||
:param os: Operating system type determined by ICMP fingerprinting
|
||||
"""
|
||||
|
||||
target: IPv4Address
|
||||
response_received: bool
|
||||
os: Optional[OperatingSystem]
|
|
@ -1,22 +0,0 @@
|
|||
from ipaddress import IPv4Address
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from . import AbstractAgentEvent
|
||||
|
||||
|
||||
class PropagationEvent(AbstractAgentEvent):
|
||||
"""
|
||||
An event that occurs when the Agent propagates on a host
|
||||
|
||||
Attributes:
|
||||
:param target: IP address of the propagated system
|
||||
:param success: Status of the propagation
|
||||
:param exploiter_name: Name of the exploiter that propagated
|
||||
:param error_message: Message if an error occurs during propagation
|
||||
"""
|
||||
|
||||
target: IPv4Address
|
||||
success: bool
|
||||
exploiter_name: str
|
||||
error_message: str = Field(default="")
|
|
@ -1,19 +0,0 @@
|
|||
from ipaddress import IPv4Address
|
||||
from typing import Dict
|
||||
|
||||
from common.types import NetworkPort, PortStatus
|
||||
|
||||
from . import AbstractAgentEvent
|
||||
|
||||
|
||||
class TCPScanEvent(AbstractAgentEvent):
|
||||
"""
|
||||
An event that occurs when the Agent performs a TCP scan on a host
|
||||
|
||||
Attributes:
|
||||
:param target: IP address of the scanned system
|
||||
:param ports: The scanned ports and their status (open/closed)
|
||||
"""
|
||||
|
||||
target: IPv4Address
|
||||
ports: Dict[NetworkPort, PortStatus]
|
|
@ -1,23 +0,0 @@
|
|||
from datetime import datetime
|
||||
from ipaddress import IPv4Interface
|
||||
from typing import Optional, Sequence
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import validator
|
||||
|
||||
from .base_models import InfectionMonkeyBaseModel
|
||||
from .transforms import make_immutable_sequence
|
||||
from .types import HardwareID, SocketAddress
|
||||
|
||||
|
||||
class AgentRegistrationData(InfectionMonkeyBaseModel):
|
||||
id: UUID
|
||||
machine_hardware_id: HardwareID
|
||||
start_time: datetime
|
||||
parent_id: Optional[UUID]
|
||||
cc_server: SocketAddress
|
||||
network_interfaces: Sequence[IPv4Interface]
|
||||
|
||||
_make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)(
|
||||
make_immutable_sequence
|
||||
)
|
|
@ -1,8 +0,0 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .base_models import InfectionMonkeyBaseModel
|
||||
|
||||
|
||||
class AgentSignals(InfectionMonkeyBaseModel):
|
||||
terminate: Optional[datetime]
|
|
@ -1,56 +0,0 @@
|
|||
import json
|
||||
from typing import Sequence
|
||||
|
||||
from pydantic import BaseModel, Extra, ValidationError
|
||||
|
||||
|
||||
class InfectionMonkeyModelConfig:
|
||||
allow_mutation = False
|
||||
underscore_attrs_are_private = True
|
||||
extra = Extra.forbid
|
||||
|
||||
|
||||
class MutableInfectionMonkeyModelConfig(InfectionMonkeyModelConfig):
|
||||
allow_mutation = True
|
||||
validate_assignment = True
|
||||
|
||||
|
||||
class InfectionMonkeyBaseModel(BaseModel):
|
||||
class Config(InfectionMonkeyModelConfig):
|
||||
pass
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
try:
|
||||
super().__init__(**kwargs)
|
||||
except ValidationError as err:
|
||||
# TLDR: This exception handler allows users of this class to be decoupled from pydantic.
|
||||
#
|
||||
# When validation of a pydantic object fails, pydantic raises a `ValidationError`, which
|
||||
# is a `ValueError`, even if the real cause was a `TypeError`. Furthermore, allowing
|
||||
# `pydantic.ValueError` to be raised would couple other modules to pydantic, which is
|
||||
# undesirable. This exception handler re-raises the first validation error that pydantic
|
||||
# encountered. This allows users of these models to `except` `TypeError` or `ValueError`
|
||||
# and handle them. Pydantic-specific errors are still raised, but they inherit from
|
||||
# `TypeError` or `ValueError`.
|
||||
e = err.raw_errors[0]
|
||||
while isinstance(e, Sequence):
|
||||
e = e[0]
|
||||
|
||||
raise e.exc
|
||||
|
||||
# We need to be able to convert our models to fully simplified dictionaries. The
|
||||
# `BaseModel.dict()` does not support this. There is a proposal to add a `simplify` keyword
|
||||
# argument to `dict()` to support this. See
|
||||
# https://github.com/pydantic/pydantic/issues/951#issuecomment-552463606. The hope is that we
|
||||
# can override `dict()` with an implementation of `simplify` and remove it when the feature gets
|
||||
# merged. If the feature doesn't get merged, or the interface is changed, this function can
|
||||
# continue to serve as a wrapper until we can update all references to it.
|
||||
def dict(self, simplify=False, **kwargs):
|
||||
if simplify:
|
||||
return json.loads(self.json())
|
||||
return BaseModel.dict(self, **kwargs)
|
||||
|
||||
|
||||
class MutableInfectionMonkeyBaseModel(InfectionMonkeyBaseModel):
|
||||
class Config(MutableInfectionMonkeyModelConfig):
|
||||
pass
|
|
@ -1,9 +1,11 @@
|
|||
class TelemCategoryEnum:
|
||||
ATTACK = "attack"
|
||||
AWS_INFO = "aws_info"
|
||||
CREDENTIALS = "credentials"
|
||||
EXPLOIT = "exploit"
|
||||
FILE_ENCRYPTION = "file_encryption"
|
||||
POST_BREACH = "post_breach"
|
||||
SCAN = "scan"
|
||||
STATE = "state"
|
||||
TRACE = "trace"
|
||||
TUNNEL = "tunnel"
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from .credential_component_type import CredentialComponentType
|
||||
from .i_credential_component import ICredentialComponent
|
||||
|
||||
from .validators import InvalidCredentialComponentError, InvalidCredentialsError
|
||||
|
||||
from .lm_hash import LMHash
|
||||
from .nt_hash import NTHash
|
||||
from .password import Password
|
||||
from .ssh_keypair import SSHKeypair
|
||||
from .username import Username
|
||||
from .encoding import get_plaintext, SecretEncodingConfig
|
||||
|
||||
from .credentials import Credentials
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
from marshmallow import Schema, post_load, validate
|
||||
from marshmallow_enum import EnumField
|
||||
|
||||
from common.utils.code_utils import del_key
|
||||
|
||||
from . import CredentialComponentType
|
||||
|
||||
|
||||
class CredentialTypeField(EnumField):
|
||||
def __init__(self, credential_component_type: CredentialComponentType):
|
||||
super().__init__(
|
||||
CredentialComponentType, validate=validate.Equal(credential_component_type)
|
||||
)
|
||||
|
||||
|
||||
class CredentialComponentSchema(Schema):
|
||||
@post_load
|
||||
def _strip_credential_type(self, data, **kwargs):
|
||||
del_key(data, "credential_type")
|
||||
return data
|
|
@ -0,0 +1,9 @@
|
|||
from enum import Enum, auto
|
||||
|
||||
|
||||
class CredentialComponentType(Enum):
|
||||
USERNAME = auto()
|
||||
PASSWORD = auto()
|
||||
NT_HASH = auto()
|
||||
LM_HASH = auto()
|
||||
SSH_KEYPAIR = auto()
|
|
@ -1,23 +1,190 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Union
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Mapping, Optional, Type
|
||||
|
||||
from ..base_models import InfectionMonkeyBaseModel, InfectionMonkeyModelConfig
|
||||
from . import LMHash, NTHash, Password, SSHKeypair, Username
|
||||
from .encoding import SecretEncodingConfig
|
||||
from marshmallow import Schema, fields, post_load, pre_dump
|
||||
from marshmallow.exceptions import MarshmallowError
|
||||
|
||||
Secret = Union[Password, LMHash, NTHash, SSHKeypair]
|
||||
Identity = Username
|
||||
from ..utils import IJSONSerializable
|
||||
from . import (
|
||||
CredentialComponentType,
|
||||
InvalidCredentialComponentError,
|
||||
InvalidCredentialsError,
|
||||
LMHash,
|
||||
NTHash,
|
||||
Password,
|
||||
SSHKeypair,
|
||||
Username,
|
||||
)
|
||||
from .i_credential_component import ICredentialComponent
|
||||
from .lm_hash import LMHashSchema
|
||||
from .nt_hash import NTHashSchema
|
||||
from .password import PasswordSchema
|
||||
from .ssh_keypair import SSHKeypairSchema
|
||||
from .username import UsernameSchema
|
||||
|
||||
CREDENTIAL_COMPONENT_TYPE_TO_CLASS: Mapping[CredentialComponentType, Type[ICredentialComponent]] = {
|
||||
CredentialComponentType.LM_HASH: LMHash,
|
||||
CredentialComponentType.NT_HASH: NTHash,
|
||||
CredentialComponentType.PASSWORD: Password,
|
||||
CredentialComponentType.SSH_KEYPAIR: SSHKeypair,
|
||||
CredentialComponentType.USERNAME: Username,
|
||||
}
|
||||
|
||||
CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA: Mapping[CredentialComponentType, Schema] = {
|
||||
CredentialComponentType.LM_HASH: LMHashSchema(),
|
||||
CredentialComponentType.NT_HASH: NTHashSchema(),
|
||||
CredentialComponentType.PASSWORD: PasswordSchema(),
|
||||
CredentialComponentType.SSH_KEYPAIR: SSHKeypairSchema(),
|
||||
CredentialComponentType.USERNAME: UsernameSchema(),
|
||||
}
|
||||
|
||||
CredentialComponentMapping = Optional[Mapping[str, Any]]
|
||||
CredentialsMapping = Mapping[str, CredentialComponentMapping]
|
||||
|
||||
|
||||
class Credentials(InfectionMonkeyBaseModel):
|
||||
"""Represents a credential pair (an identity and a secret)"""
|
||||
class CredentialsSchema(Schema):
|
||||
identity = fields.Mapping(allow_none=True)
|
||||
secret = fields.Mapping(allow_none=True)
|
||||
|
||||
identity: Optional[Identity]
|
||||
"""Identity part of credentials, like a username or an email"""
|
||||
@post_load
|
||||
def _make_credentials(
|
||||
self,
|
||||
credentials: CredentialsMapping,
|
||||
**kwargs: Mapping[str, Any],
|
||||
) -> Mapping[str, Optional[ICredentialComponent]]:
|
||||
if not any(credentials.values()):
|
||||
raise InvalidCredentialsError("At least one credentials component must be defined")
|
||||
|
||||
secret: Optional[Secret]
|
||||
"""Secret part of credentials, like a password or a hash"""
|
||||
return {
|
||||
key: CredentialsSchema._build_credential_component(credential_component_mapping)
|
||||
for key, credential_component_mapping in credentials.items()
|
||||
}
|
||||
|
||||
class Config(SecretEncodingConfig, InfectionMonkeyModelConfig):
|
||||
pass
|
||||
@staticmethod
|
||||
def _build_credential_component(
|
||||
credential_component: CredentialComponentMapping,
|
||||
) -> Optional[ICredentialComponent]:
|
||||
if credential_component is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
credential_component_type = CredentialComponentType[
|
||||
credential_component["credential_type"]
|
||||
]
|
||||
except KeyError as err:
|
||||
raise InvalidCredentialsError(f"Unknown credential component type {err}")
|
||||
|
||||
credential_component_class = CREDENTIAL_COMPONENT_TYPE_TO_CLASS[credential_component_type]
|
||||
credential_component_schema = CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA[
|
||||
credential_component_type
|
||||
]
|
||||
|
||||
try:
|
||||
return credential_component_class(
|
||||
**credential_component_schema.load(credential_component)
|
||||
)
|
||||
except MarshmallowError as err:
|
||||
raise InvalidCredentialComponentError(credential_component_class, str(err))
|
||||
|
||||
@pre_dump
|
||||
def _serialize_credentials(self, credentials: Credentials, **kwargs) -> CredentialsMapping:
|
||||
return {
|
||||
"identity": CredentialsSchema._serialize_credential_component(credentials.identity),
|
||||
"secret": CredentialsSchema._serialize_credential_component(credentials.secret),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _serialize_credential_component(
|
||||
credential_component: Optional[ICredentialComponent],
|
||||
) -> CredentialComponentMapping:
|
||||
if credential_component is None:
|
||||
return None
|
||||
|
||||
credential_component_schema = CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA[
|
||||
credential_component.credential_type
|
||||
]
|
||||
|
||||
return credential_component_schema.dump(credential_component)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Credentials(IJSONSerializable):
|
||||
identity: Optional[ICredentialComponent]
|
||||
secret: Optional[ICredentialComponent]
|
||||
|
||||
def __post_init__(self):
|
||||
schema = CredentialsSchema()
|
||||
try:
|
||||
serialized_data = schema.dump(self)
|
||||
|
||||
# This will raise an exception if the object is invalid. Calling this in __post__init()
|
||||
# makes it impossible to construct an invalid object
|
||||
schema.load(serialized_data)
|
||||
except Exception as err:
|
||||
raise InvalidCredentialsError(err)
|
||||
|
||||
@staticmethod
|
||||
def from_mapping(credentials: CredentialsMapping) -> Credentials:
|
||||
"""
|
||||
Construct a Credentials object from a Mapping
|
||||
|
||||
:param credentials: A mapping that represents a Credentials object
|
||||
:return: A Credentials object
|
||||
:raises InvalidCredentialsError: If the provided Mapping does not represent a valid
|
||||
Credentials object
|
||||
:raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets`
|
||||
are not a valid ICredentialComponent
|
||||
"""
|
||||
|
||||
try:
|
||||
deserialized_data = CredentialsSchema().load(credentials)
|
||||
return Credentials(**deserialized_data)
|
||||
except (InvalidCredentialsError, InvalidCredentialComponentError) as err:
|
||||
raise err
|
||||
except MarshmallowError as err:
|
||||
raise InvalidCredentialsError(str(err))
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, credentials: str) -> Credentials:
|
||||
"""
|
||||
Construct a Credentials object from a JSON string
|
||||
|
||||
:param credentials: A JSON string that represents a Credentials object
|
||||
:return: A Credentials object
|
||||
:raises InvalidCredentialsError: If the provided JSON does not represent a valid
|
||||
Credentials object
|
||||
:raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets`
|
||||
are not a valid ICredentialComponent
|
||||
"""
|
||||
|
||||
try:
|
||||
deserialized_data = CredentialsSchema().loads(credentials)
|
||||
return Credentials(**deserialized_data)
|
||||
except (InvalidCredentialsError, InvalidCredentialComponentError) as err:
|
||||
raise err
|
||||
except MarshmallowError as err:
|
||||
raise InvalidCredentialsError(str(err))
|
||||
|
||||
@staticmethod
|
||||
def to_mapping(credentials: Credentials) -> CredentialsMapping:
|
||||
"""
|
||||
Serialize a Credentials object to a Mapping
|
||||
|
||||
:param credentials: A Credentials object
|
||||
:return: A mapping representing a Credentials object
|
||||
"""
|
||||
|
||||
return CredentialsSchema().dump(credentials)
|
||||
|
||||
@classmethod
|
||||
def to_json(cls, credentials: Credentials) -> str:
|
||||
"""
|
||||
Serialize a Credentials object to JSON
|
||||
|
||||
:param credentials: A Credentials object
|
||||
:return: A JSON string representing a Credentials object
|
||||
"""
|
||||
|
||||
return CredentialsSchema().dumps(credentials)
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
from pydantic import SecretBytes, SecretStr
|
||||
|
||||
|
||||
def get_plaintext(secret: Union[SecretStr, SecretBytes, None, str]) -> Optional[Union[str, bytes]]:
|
||||
if isinstance(secret, (SecretStr, SecretBytes)):
|
||||
return secret.get_secret_value()
|
||||
else:
|
||||
return secret
|
||||
|
||||
|
||||
class SecretEncodingConfig:
|
||||
json_encoders = {
|
||||
# This makes secrets dumpable to json, but not loggable
|
||||
SecretStr: get_plaintext,
|
||||
SecretBytes: get_plaintext,
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
from . import CredentialComponentType
|
||||
|
||||
|
||||
class ICredentialComponent(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def credential_type(self) -> CredentialComponentType:
|
||||
pass
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue