Merge branch 'develop' into feature/check-for-updates

# Conflicts:
#	monkey/monkey_island/cc/app.py
#	monkey/monkey_island/requirements.txt
This commit is contained in:
itay 2019-05-12 11:23:01 +03:00
commit 7059f6a1a4
24 changed files with 848 additions and 522 deletions

3
.gitignore vendored
View File

@ -82,4 +82,5 @@ MonkeyZoo/*
!MonkeyZoo/config.tf !MonkeyZoo/config.tf
!MonkeyZoo/MonkeyZooDocs.pdf !MonkeyZoo/MonkeyZooDocs.pdf
# vim swap files
*.swp

View File

@ -89,25 +89,8 @@ kernel=`uname -m`
linux_dist=`lsb_release -a 2> /dev/null` linux_dist=`lsb_release -a 2> /dev/null`
# If a user haven't installed mongo manually check if we can install it with our script # If a user haven't installed mongo manually check if we can install it with our script
if [[ ! -f "$MONGO_BIN_PATH/mongod" ]] && { [[ ${kernel} != "x86_64" ]] || \ log_message "Installing MongoDB"
{ [[ ${linux_dist} != *"Debian"* ]] && [[ ${linux_dist} != *"Ubuntu"* ]]; }; }; then ${ISLAND_PATH}/linux/install_mongo.sh ${MONGO_BIN_PATH} || handle_error
echo "Script does not support your operating system for mongodb installation.
Reference monkey island readme and install it manually"
exit 1
fi
# Download mongo
if [[ ! -f "$MONGO_BIN_PATH/mongod" ]]; then
log_message "Downloading mongodb"
if [[ ${linux_dist} == *"Debian"* ]]; then
wget -c -N -O "/tmp/mongo.tgz" ${MONGO_DEBIAN_URL}
elif [[ ${linux_dist} == *"Ubuntu"* ]]; then
wget -c -N -O "/tmp/mongo.tgz" ${MONGO_UBUNTU_URL}
fi
tar --strip 2 --wildcards -C ${MONGO_BIN_PATH} -zxvf /tmp/mongo.tgz mongo*/bin/* || handle_error
else
log_message "Mongo db already installed"
fi
log_message "Installing openssl" log_message "Installing openssl"
sudo apt-get install openssl sudo apt-get install openssl

View File

@ -1,22 +1,48 @@
import json
import re import re
import urllib2 import urllib2
import logging
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254"
AWS_LATEST_METADATA_URI_PREFIX = 'http://{0}/latest/'.format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS)
ACCOUNT_ID_KEY = "accountId"
logger = logging.getLogger(__name__)
class AwsInstance(object): class AwsInstance(object):
"""
Class which gives useful information about the current instance you're on.
"""
def __init__(self): def __init__(self):
self.instance_id = None
self.region = None
self.account_id = None
try: try:
self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read() self.instance_id = urllib2.urlopen(
AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/instance-id', timeout=2).read()
self.region = self._parse_region( self.region = self._parse_region(
urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()) urllib2.urlopen(AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/placement/availability-zone').read())
except urllib2.URLError: except urllib2.URLError as e:
self.instance_id = None logger.warning("Failed init of AwsInstance while getting metadata: {}".format(e.message))
self.region = None
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))
@staticmethod @staticmethod
def _parse_region(region_url_response): def _parse_region(region_url_response):
# For a list of regions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html # For a list of regions, see:
# https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
# This regex will find any AWS region format string in the response. # This regex will find any AWS region format string in the response.
re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])' re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])'
finding = re.findall(re_phrase, region_url_response, re.IGNORECASE) finding = re.findall(re_phrase, region_url_response, re.IGNORECASE)
@ -33,3 +59,21 @@ class AwsInstance(object):
def is_aws_instance(self): def is_aws_instance(self):
return self.instance_id is not None return self.instance_id is not None
@staticmethod
def _extract_account_id(instance_identity_document_response):
"""
Extracts the account id from the dynamic/instance-identity/document metadata path.
Based on https://forums.aws.amazon.com/message.jspa?messageID=409028 which has a few more solutions,
in case Amazon break this mechanism.
:param instance_identity_document_response: json returned via the web page ../dynamic/instance-identity/document
:return: The account id
"""
return json.loads(instance_identity_document_response)[ACCOUNT_ID_KEY]
def get_account_id(self):
"""
:return: the AWS account ID which "owns" this instance.
See https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html
"""
return self.account_id

View File

@ -1,23 +1,44 @@
import logging
import boto3 import boto3
import botocore
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
__author__ = 'itay.mizeretz' from common.cloud.aws_instance import AwsInstance
__author__ = ['itay.mizeretz', 'shay.nehmad']
INSTANCE_INFORMATION_LIST_KEY = 'InstanceInformationList'
INSTANCE_ID_KEY = 'InstanceId'
COMPUTER_NAME_KEY = 'ComputerName'
PLATFORM_TYPE_KEY = 'PlatformType'
IP_ADDRESS_KEY = 'IPAddress'
logger = logging.getLogger(__name__)
def filter_instance_data_from_aws_response(response):
return [{
'instance_id': x[INSTANCE_ID_KEY],
'name': x[COMPUTER_NAME_KEY],
'os': x[PLATFORM_TYPE_KEY].lower(),
'ip_address': x[IP_ADDRESS_KEY]
} for x in response[INSTANCE_INFORMATION_LIST_KEY]]
class AwsService(object): class AwsService(object):
""" """
Supplies various AWS services A wrapper class around the boto3 client and session modules, which supplies various AWS services.
This class will assume:
1. That it's running on an EC2 instance
2. That the instance is associated with the correct IAM role. See
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details.
""" """
access_key_id = None
secret_access_key = None
region = None region = None
@staticmethod
def set_auth_params(access_key_id, secret_access_key):
AwsService.access_key_id = access_key_id
AwsService.secret_access_key = secret_access_key
@staticmethod @staticmethod
def set_region(region): def set_region(region):
AwsService.region = region AwsService.region = region
@ -26,15 +47,11 @@ class AwsService(object):
def get_client(client_type, region=None): def get_client(client_type, region=None):
return boto3.client( return boto3.client(
client_type, client_type,
aws_access_key_id=AwsService.access_key_id,
aws_secret_access_key=AwsService.secret_access_key,
region_name=region if region is not None else AwsService.region) region_name=region if region is not None else AwsService.region)
@staticmethod @staticmethod
def get_session(): def get_session():
return boto3.session.Session( return boto3.session.Session()
aws_access_key_id=AwsService.access_key_id,
aws_secret_access_key=AwsService.secret_access_key)
@staticmethod @staticmethod
def get_regions(): def get_regions():
@ -50,14 +67,22 @@ class AwsService(object):
@staticmethod @staticmethod
def get_instances(): def get_instances():
return \ """
[ Get the information for all instances with the relevant roles.
{
'instance_id': x['InstanceId'],
'name': x['ComputerName'],
'os': x['PlatformType'].lower(),
'ip_address': x['IPAddress']
}
for x in AwsService.get_client('ssm').describe_instance_information()['InstanceInformationList']
]
This function will assume that it's running on an EC2 instance with the correct IAM role.
See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details.
:raises: botocore.exceptions.ClientError if can't describe local instance information.
:return: All visible instances from this instance
"""
current_instance = AwsInstance()
local_ssm_client = boto3.client("ssm", current_instance.get_region())
try:
response = local_ssm_client.describe_instance_information()
filtered_instances_data = filter_instance_data_from_aws_response(response)
return filtered_instances_data
except botocore.exceptions.ClientError as e:
logger.warning("AWS client error while trying to get instances: " + e.message)
raise e

View File

@ -0,0 +1,59 @@
from unittest import TestCase
from aws_service import filter_instance_data_from_aws_response
import json
__author__ = 'shay.nehmad'
class TestFilter_instance_data_from_aws_response(TestCase):
def test_filter_instance_data_from_aws_response(self):
json_response_full = """
{
"InstanceInformationList": [
{
"ActivationId": "string",
"AgentVersion": "string",
"AssociationOverview": {
"DetailedStatus": "string",
"InstanceAssociationStatusAggregatedCount": {
"string" : 6
}
},
"AssociationStatus": "string",
"ComputerName": "string",
"IamRole": "string",
"InstanceId": "string",
"IPAddress": "string",
"IsLatestVersion": "True",
"LastAssociationExecutionDate": 6,
"LastPingDateTime": 6,
"LastSuccessfulAssociationExecutionDate": 6,
"Name": "string",
"PingStatus": "string",
"PlatformName": "string",
"PlatformType": "string",
"PlatformVersion": "string",
"RegistrationDate": 6,
"ResourceType": "string"
}
],
"NextToken": "string"
}
"""
json_response_empty = """
{
"InstanceInformationList": [],
"NextToken": "string"
}
"""
self.assertEqual(filter_instance_data_from_aws_response(json.loads(json_response_empty)), [])
self.assertEqual(
filter_instance_data_from_aws_response(json.loads(json_response_full)),
[{'instance_id': u'string',
'ip_address': u'string',
'name': u'string',
'os': u'string'}])

View File

@ -7,6 +7,7 @@ import urllib2
import httplib import httplib
import unicodedata import unicodedata
import re import re
import ssl
import logging import logging
from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.web_rce import WebRCE
@ -47,7 +48,7 @@ class Struts2Exploiter(WebRCE):
headers = {'User-Agent': 'Mozilla/5.0'} headers = {'User-Agent': 'Mozilla/5.0'}
request = urllib2.Request(url, headers=headers) request = urllib2.Request(url, headers=headers)
try: try:
return urllib2.urlopen(request).geturl() return urllib2.urlopen(request, context=ssl._create_unverified_context()).geturl()
except urllib2.URLError: except urllib2.URLError:
LOG.error("Can't reach struts2 server") LOG.error("Can't reach struts2 server")
return False return False

View File

@ -13,7 +13,7 @@ __author__ = 'VakarisZ'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# Command used to check if monkeys already exists # Command used to check if monkeys already exists
LOOK_FOR_FILE = "ls %s" LOOK_FOR_FILE = "ls %s"
POWERSHELL_NOT_FOUND = "owershell is not recognized" POWERSHELL_NOT_FOUND = "powershell is not recognized"
# Constants used to refer to windows architectures( used in host.os['machine']) # Constants used to refer to windows architectures( used in host.os['machine'])
WIN_ARCH_32 = "32" WIN_ARCH_32 = "32"
WIN_ARCH_64 = "64" WIN_ARCH_64 = "64"
@ -253,7 +253,7 @@ class WebRCE(HostExploiter):
if 'No such file' in resp: if 'No such file' in resp:
return False return False
else: else:
LOG.info("Host %s was already infected under the current configuration, done" % host) LOG.info("Host %s was already infected under the current configuration, done" % str(host))
return True return True
def check_remote_files(self, url): def check_remote_files(self, url):
@ -281,7 +281,7 @@ class WebRCE(HostExploiter):
""" """
ports = self.get_open_service_ports(ports, names) ports = self.get_open_service_ports(ports, names)
if not ports: if not ports:
LOG.info("All default web ports are closed on %r, skipping", host) LOG.info("All default web ports are closed on %r, skipping", str(host))
return False return False
else: else:
return ports return ports

View File

@ -13,7 +13,8 @@ from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
from infection_monkey.dropper import MonkeyDrops from infection_monkey.dropper import MonkeyDrops
from infection_monkey.model import MONKEY_ARG, DROPPER_ARG from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
from infection_monkey.monkey import InfectionMonkey from infection_monkey.monkey import InfectionMonkey
import infection_monkey.post_breach # dummy import for pyinstaller # noinspection PyUnresolvedReferences
import infection_monkey.post_breach # dummy import for pyinstaller
__author__ = 'itamar' __author__ = 'itamar'
@ -23,7 +24,7 @@ LOG_CONFIG = {'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,
'formatters': {'standard': { 'formatters': {'standard': {
'format': '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'}, 'format': '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'},
}, },
'handlers': {'console': {'class': 'logging.StreamHandler', 'handlers': {'console': {'class': 'logging.StreamHandler',
'level': 'DEBUG', 'level': 'DEBUG',
'formatter': 'standard'}, 'formatter': 'standard'},
@ -70,7 +71,8 @@ def main():
print("Loaded Configuration: %r" % WormConfiguration.as_dict()) print("Loaded Configuration: %r" % WormConfiguration.as_dict())
# Make sure we're not in a machine that has the kill file # Make sure we're not in a machine that has the kill file
kill_path = os.path.expandvars(WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux kill_path = os.path.expandvars(
WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux
if os.path.exists(kill_path): if os.path.exists(kill_path):
print("Kill path found, finished run") print("Kill path found, finished run")
return True return True

View File

@ -31,6 +31,7 @@ from monkey_island.cc.resources.pba_file_download import PBAFileDownload
from monkey_island.cc.resources.version_update import VersionUpdate from monkey_island.cc.resources.version_update import VersionUpdate
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from monkey_island.cc.resources.pba_file_upload import FileUpload from monkey_island.cc.resources.pba_file_upload import FileUpload
from monkey_island.cc.resources.attack_telem import AttackTelem from monkey_island.cc.resources.attack_telem import AttackTelem
@ -98,6 +99,9 @@ def init_app_services(app):
database.init() database.init()
ConfigService.init_config() ConfigService.init_config()
# If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island.
RemoteRunAwsService.init()
def init_app_url_rules(app): def init_app_url_rules(app):
app.add_url_rule('/', 'serve_home', serve_home) app.add_url_rule('/', 'serve_home', serve_home)

View File

@ -1,19 +1,19 @@
from monkey_island.cc.environment.environment import load_env_from_file, AWS import logging
from monkey_island.cc.report_exporter_manager import ReportExporterManager from monkey_island.cc.report_exporter_manager import ReportExporterManager
from monkey_island.cc.resources.aws_exporter import AWSExporter from monkey_island.cc.resources.aws_exporter import AWSExporter
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
__author__ = 'maor.rayzin' logger = logging.getLogger(__name__)
def populate_exporter_list(): def populate_exporter_list():
manager = ReportExporterManager() manager = ReportExporterManager()
if is_aws_exporter_required(): RemoteRunAwsService.init()
if RemoteRunAwsService.is_running_on_aws():
manager.add_exporter_to_list(AWSExporter) manager.add_exporter_to_list(AWSExporter)
if len(manager.get_exporters_list()) != 0:
logger.debug(
"Populated exporters list with the following exporters: {0}".format(str(manager.get_exporters_list())))
def is_aws_exporter_required():
if str(load_env_from_file()) == AWS:
return True
else:
return False

View File

@ -29,6 +29,7 @@ class ReportExporterManager(object):
def export(self, report): def export(self, report):
try: try:
for exporter in self._exporters_set: for exporter in self._exporters_set:
logger.debug("Trying to export using " + repr(exporter))
exporter().handle_report(report) exporter().handle_report(report)
except Exception as e: except Exception as e:
logger.exception('Failed to export report') logger.exception('Failed to export report, error: ' + e.message)

View File

@ -1,53 +1,42 @@
import logging import logging
import uuid import uuid
from datetime import datetime from datetime import datetime
import boto3 import boto3
from botocore.exceptions import UnknownServiceError from botocore.exceptions import UnknownServiceError
from monkey_island.cc.resources.exporter import Exporter
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.environment.environment import load_server_configuration_from_file
from common.cloud.aws_instance import AwsInstance from common.cloud.aws_instance import AwsInstance
from monkey_island.cc.environment.environment import load_server_configuration_from_file
from monkey_island.cc.resources.exporter import Exporter
__author__ = 'maor.rayzin' __authors__ = ['maor.rayzin', 'shay.nehmad']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'],
['cnc', 'aws_config', 'aws_secret_access_key'],
['cnc', 'aws_config', 'aws_account_id']]
class AWSExporter(Exporter): class AWSExporter(Exporter):
@staticmethod @staticmethod
def handle_report(report_json): def handle_report(report_json):
aws = AwsInstance()
findings_list = [] findings_list = []
issues_list = report_json['recommendations']['issues'] issues_list = report_json['recommendations']['issues']
if not issues_list: if not issues_list:
logger.info('No issues were found by the monkey, no need to send anything') logger.info('No issues were found by the monkey, no need to send anything')
return True return True
current_aws_region = AwsInstance().get_region()
for machine in issues_list: for machine in issues_list:
for issue in issues_list[machine]: for issue in issues_list[machine]:
if issue.get('aws_instance_id', None): if issue.get('aws_instance_id', None):
findings_list.append(AWSExporter._prepare_finding(issue, aws.get_region())) findings_list.append(AWSExporter._prepare_finding(issue, current_aws_region))
if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys(), aws.get_region()): if not AWSExporter._send_findings(findings_list, current_aws_region):
logger.error('Exporting findings to aws failed') logger.error('Exporting findings to aws failed')
return False return False
return True return True
@staticmethod
def _get_aws_keys():
creds_dict = {}
for key in AWS_CRED_CONFIG_KEYS:
creds_dict[key[2]] = str(ConfigService.get_config_value(key))
return creds_dict
@staticmethod @staticmethod
def merge_two_dicts(x, y): def merge_two_dicts(x, y):
z = x.copy() # start with x's keys and values z = x.copy() # start with x's keys and values
@ -82,7 +71,8 @@ class AWSExporter(Exporter):
configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '')
product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn)
instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}' instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}'
account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') account_id = AwsInstance().get_account_id()
logger.debug("aws account id acquired: {}".format(account_id))
finding = { finding = {
"SchemaVersion": "2018-10-08", "SchemaVersion": "2018-10-08",
@ -100,27 +90,26 @@ class AWSExporter(Exporter):
return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue, instance_arn)) return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue, instance_arn))
@staticmethod @staticmethod
def _send_findings(findings_list, creds_dict, region): def _send_findings(findings_list, region):
try: try:
if not creds_dict: logger.debug("Trying to acquire securityhub boto3 client in " + region)
logger.info('No AWS access credentials received in configuration') security_hub_client = boto3.client('securityhub', region_name=region)
return False logger.debug("Client acquired: {0}".format(repr(security_hub_client)))
securityhub = boto3.client('securityhub', # Assumes the machine has the correct IAM role to do this, @see
aws_access_key_id=creds_dict.get('aws_access_key_id', ''), # https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances
aws_secret_access_key=creds_dict.get('aws_secret_access_key', ''), import_response = security_hub_client.batch_import_findings(Findings=findings_list)
region_name=region) logger.debug("Import findings response: {0}".format(repr(import_response)))
import_response = securityhub.batch_import_findings(Findings=findings_list)
if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: if import_response['ResponseMetadata']['HTTPStatusCode'] == 200:
return True return True
else: else:
return False return False
except UnknownServiceError as e: except UnknownServiceError as e:
logger.warning('AWS exporter called but AWS-CLI securityhub service is not installed') logger.warning('AWS exporter called but AWS-CLI securityhub service is not installed. Error: ' + e.message)
return False return False
except Exception as e: except Exception as e:
logger.exception('AWS security hub findings failed to send.') logger.exception('AWS security hub findings failed to send. Error: ' + e.message)
return False return False
@staticmethod @staticmethod
@ -159,7 +148,7 @@ class AWSExporter(Exporter):
title="Weak segmentation - Machines were able to communicate over unused ports.", title="Weak segmentation - Machines were able to communicate over unused ports.",
description="Use micro-segmentation policies to disable communication other than the required.", description="Use micro-segmentation policies to disable communication other than the required.",
recommendation="Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" recommendation="Machines are not locked down at port level. Network tunnel was set up from {0} to {1}"
.format(issue['machine'], issue['dest']), .format(issue['machine'], issue['dest']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -171,9 +160,9 @@ class AWSExporter(Exporter):
severity=10, severity=10,
title="Samba servers are vulnerable to 'SambaCry'", title="Samba servers are vulnerable to 'SambaCry'",
description="Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ description="Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \
.format(issue['username']), .format(issue['username']),
recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(
issue['machine'], issue['ip_address'], issue['username']), issue['machine'], issue['ip_address'], issue['username']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -185,9 +174,9 @@ class AWSExporter(Exporter):
severity=5, severity=5,
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), issue['username']),
recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format( recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(
issue['machine'], issue['ip_address'], issue['username']), issue['machine'], issue['ip_address'], issue['username']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -199,9 +188,9 @@ class AWSExporter(Exporter):
severity=1, severity=1,
title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), issue['username']),
recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format( recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(
issue['machine'], issue['ip_address'], issue['username']), issue['machine'], issue['ip_address'], issue['username']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -214,7 +203,7 @@ class AWSExporter(Exporter):
title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']),
recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format( recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format(
machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']), machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -227,7 +216,7 @@ class AWSExporter(Exporter):
title="Elastic Search servers are vulnerable to CVE-2015-1427", title="Elastic Search servers are vulnerable to CVE-2015-1427",
description="Update your Elastic Search server to version 1.4.3 and up.", description="Update your Elastic Search server to version 1.4.3 and up.",
recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(
issue['machine'], issue['ip_address']), issue['machine'], issue['ip_address']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -243,7 +232,8 @@ class AWSExporter(Exporter):
{0} in the networks {1} \ {0} in the networks {1} \
could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], could directly access the Monkey Island server in the networks {2}.".format(issue['machine'],
issue['networks'], issue['networks'],
issue['server_networks']), issue[
'server_networks']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -269,7 +259,7 @@ class AWSExporter(Exporter):
description="Update your Bash to a ShellShock-patched version.", description="Update your Bash to a ShellShock-patched version.",
recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. "
"The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format( "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(
issue['machine'], issue['ip_address'], issue['port'], issue['paths']), issue['machine'], issue['ip_address'], issue['port'], issue['paths']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -281,9 +271,9 @@ class AWSExporter(Exporter):
severity=1, severity=1,
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), issue['username']),
recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format( recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(
issue['machine'], issue['ip_address'], issue['username']), issue['machine'], issue['ip_address'], issue['username']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -296,7 +286,7 @@ class AWSExporter(Exporter):
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.",
recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format( recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format(
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -308,9 +298,9 @@ class AWSExporter(Exporter):
severity=1, severity=1,
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), issue['username']),
recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format( recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format(
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -322,9 +312,9 @@ class AWSExporter(Exporter):
severity=1, severity=1,
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), issue['username']),
recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format(
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -337,7 +327,7 @@ class AWSExporter(Exporter):
title="Multiple users have the same password.", title="Multiple users have the same password.",
description="Some domain users are sharing passwords, this should be fixed by changing passwords.", description="Some domain users are sharing passwords, this should be fixed by changing passwords.",
recommendation="These users are sharing access password: {shared_with}.".format( recommendation="These users are sharing access password: {shared_with}.".format(
shared_with=issue['shared_with']), shared_with=issue['shared_with']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -350,7 +340,7 @@ class AWSExporter(Exporter):
title="Shared local administrator account - Different machines have the same account as a local administrator.", title="Shared local administrator account - Different machines have the same account as a local administrator.",
description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.",
recommendation="Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format( recommendation="Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format(
username=issue['username'], shared_machines=issue['shared_machines']), username=issue['username'], shared_machines=issue['shared_machines']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -363,7 +353,7 @@ class AWSExporter(Exporter):
title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.", title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.",
description="This critical machine is open to attacks via strong users with access to it.", description="This critical machine is open to attacks via strong users with access to it.",
recommendation="The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format( recommendation="The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format(
services=issue['services'], threatening_users=issue['threatening_users']), services=issue['services'], threatening_users=issue['threatening_users']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -377,7 +367,7 @@ class AWSExporter(Exporter):
description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.",
recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
" The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format( " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format(
machine=issue['machine'], ip_address=issue['ip_address']), machine=issue['machine'], ip_address=issue['ip_address']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )
@ -392,7 +382,7 @@ class AWSExporter(Exporter):
"Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.",
recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
" The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format( " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format(
machine=issue['machine'], ip_address=issue['ip_address']), machine=issue['machine'], ip_address=issue['ip_address']),
instance_arn=instance_arn, instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
) )

View File

@ -1,4 +1,6 @@
import json import json
from botocore.exceptions import NoCredentialsError, ClientError
from flask import request, jsonify, make_response from flask import request, jsonify, make_response
import flask_restful import flask_restful
@ -6,6 +8,11 @@ from monkey_island.cc.auth import jwt_required
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from common.cloud.aws_service import AwsService from common.cloud.aws_service import AwsService
CLIENT_ERROR_FORMAT = "ClientError, error message: '{}'. Probably, the IAM role that has been associated with the " \
"instance doesn't permit SSM calls. "
NO_CREDS_ERROR_FORMAT = "NoCredentialsError, error message: '{}'. Probably, no IAM role has been associated with the " \
"instance. "
class RemoteRun(flask_restful.Resource): class RemoteRun(flask_restful.Resource):
def __init__(self): def __init__(self):
@ -24,10 +31,14 @@ class RemoteRun(flask_restful.Resource):
is_aws = RemoteRunAwsService.is_running_on_aws() is_aws = RemoteRunAwsService.is_running_on_aws()
resp = {'is_aws': is_aws} resp = {'is_aws': is_aws}
if is_aws: if is_aws:
is_auth = RemoteRunAwsService.update_aws_auth_params() try:
resp['auth'] = is_auth
if is_auth:
resp['instances'] = AwsService.get_instances() resp['instances'] = AwsService.get_instances()
except NoCredentialsError as e:
resp['error'] = NO_CREDS_ERROR_FORMAT.format(e.message)
return jsonify(resp)
except ClientError as e:
resp['error'] = CLIENT_ERROR_FORMAT.format(e.message)
return jsonify(resp)
return jsonify(resp) return jsonify(resp)
return {} return {}
@ -37,11 +48,9 @@ class RemoteRun(flask_restful.Resource):
body = json.loads(request.data) body = json.loads(request.data)
resp = {} resp = {}
if body.get('type') == 'aws': if body.get('type') == 'aws':
is_auth = RemoteRunAwsService.update_aws_auth_params() RemoteRunAwsService.update_aws_region_authless()
resp['auth'] = is_auth result = self.run_aws_monkeys(body)
if is_auth: resp['result'] = result
result = self.run_aws_monkeys(body)
resp['result'] = result
return jsonify(resp) return jsonify(resp)
# default action # default action

View File

@ -54,7 +54,7 @@ class TelemetryFeed(flask_restful.Resource):
@staticmethod @staticmethod
def get_state_telem_brief(telem): def get_state_telem_brief(telem):
if telem['data']['done']: if telem['data']['done']:
return '''Monkey finishing it's execution.''' return '''Monkey finishing its execution.'''
else: else:
return 'Monkey started.' return 'Monkey started.'

View File

@ -26,14 +26,6 @@ ENCRYPTED_CONFIG_ARRAYS = \
['internal', 'exploits', 'exploit_ssh_keys'] ['internal', 'exploits', 'exploit_ssh_keys']
] ]
# This should be used for config values of string type
ENCRYPTED_CONFIG_STRINGS = \
[
['cnc', 'aws_config', 'aws_access_key_id'],
['cnc', 'aws_config', 'aws_account_id'],
['cnc', 'aws_config', 'aws_secret_access_key']
]
class ConfigService: class ConfigService:
default_config = None default_config = None
@ -76,8 +68,6 @@ class ConfigService:
if should_decrypt: if should_decrypt:
if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS: if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS:
config = [encryptor.dec(x) for x in config] config = [encryptor.dec(x) for x in config]
elif config_key_as_arr in ENCRYPTED_CONFIG_STRINGS:
config = encryptor.dec(config)
return config return config
@staticmethod @staticmethod
@ -243,11 +233,8 @@ class ConfigService:
""" """
Same as decrypt_config but for a flat configuration Same as decrypt_config but for a flat configuration
""" """
if is_island: keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS]
keys = [config_arr_as_array[2] for config_arr_as_array in
(ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)]
else:
keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS]
for key in keys: for key in keys:
if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types): if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types):
# Check if we are decrypting ssh key pair # Check if we are decrypting ssh key pair
@ -261,7 +248,7 @@ class ConfigService:
@staticmethod @staticmethod
def _encrypt_or_decrypt_config(config, is_decrypt=False): def _encrypt_or_decrypt_config(config, is_decrypt=False):
for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS): for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS:
config_arr = config config_arr = config
parent_config_arr = None parent_config_arr = None

View File

@ -250,8 +250,9 @@ SCHEMA = {
"default": [ "default": [
], ],
"description": "description":
"List of IPs/subnets the monkey should scan." "List of IPs/subnets/hosts the monkey should scan."
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\","
" \"printer.example\""
} }
} }
}, },
@ -695,31 +696,6 @@ SCHEMA = {
} }
} }
}, },
'aws_config': {
'title': 'AWS Configuration',
'type': 'object',
'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.',
'properties': {
'aws_account_id': {
'title': 'AWS account ID',
'type': 'string',
'description': 'Your AWS account ID that is subscribed to security hub feeds',
'default': ''
},
'aws_access_key_id': {
'title': 'AWS access key ID',
'type': 'string',
'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.',
'default': ''
},
'aws_secret_access_key': {
'title': 'AWS secret access key',
'type': 'string',
'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.',
'default': ''
}
}
}
} }
}, },
"exploits": { "exploits": {

View File

@ -46,22 +46,12 @@ class RemoteRunAwsService:
return RemoteRunAwsService.aws_instance.is_aws_instance() return RemoteRunAwsService.aws_instance.is_aws_instance()
@staticmethod @staticmethod
def update_aws_auth_params(): def update_aws_region_authless():
""" """
Updates the AWS authentication parameters according to config Updates the AWS region without auth params (via IAM role)
:return: True if new params allow successful authentication. False otherwise
""" """
access_key_id = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_access_key_id'], False, True)
secret_access_key = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_secret_access_key'], False, True)
if (access_key_id != AwsService.access_key_id) or (secret_access_key != AwsService.secret_access_key):
AwsService.set_auth_params(access_key_id, secret_access_key)
RemoteRunAwsService.is_auth = AwsService.test_client()
AwsService.set_region(RemoteRunAwsService.aws_instance.region) AwsService.set_region(RemoteRunAwsService.aws_instance.region)
return RemoteRunAwsService.is_auth
@staticmethod @staticmethod
def get_bitness(instances): def get_bitness(instances):
""" """

View File

@ -1,4 +1,4 @@
{ {
"presets": ["es2015", "stage-0", "react"] "presets": ["es2015", "stage-0", "react"],
"plugins": ["emotion"]
} }

File diff suppressed because it is too large Load Diff

View File

@ -93,6 +93,8 @@
"react-table": "^6.8.6", "react-table": "^6.8.6",
"react-toggle": "^4.0.1", "react-toggle": "^4.0.1",
"redux": "^4.0.0", "redux": "^4.0.0",
"sha3": "^2.0.0" "sha3": "^2.0.0",
"react-spinners": "^0.5.4",
"@emotion/core": "^10.0.10"
} }
} }

View File

@ -1,11 +1,20 @@
import React from 'react'; import React from 'react';
import {Button, Col, Well, Nav, NavItem, Collapse, Form, FormControl, FormGroup} from 'react-bootstrap'; import { css } from '@emotion/core';
import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap';
import CopyToClipboard from 'react-copy-to-clipboard'; import CopyToClipboard from 'react-copy-to-clipboard';
import GridLoader from 'react-spinners/GridLoader';
import {Icon} from 'react-fa'; import {Icon} from 'react-fa';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import AwsRunTable from "../run-monkey/AwsRunTable"; import AwsRunTable from "../run-monkey/AwsRunTable";
const loading_css_override = css`
display: block;
margin-right: auto;
margin-left: auto;
`;
class RunMonkeyPageComponent extends AuthComponent { class RunMonkeyPageComponent extends AuthComponent {
constructor(props) { constructor(props) {
@ -20,12 +29,12 @@ class RunMonkeyPageComponent extends AuthComponent {
showManual: false, showManual: false,
showAws: false, showAws: false,
isOnAws: false, isOnAws: false,
isAwsAuth: false,
awsUpdateClicked: false, awsUpdateClicked: false,
awsUpdateFailed: false, awsUpdateFailed: false,
awsKeyId: '', awsMachines: [],
awsSecretKey: '', isLoadingAws: true,
awsMachines: [] isErrorWhileCollectingAwsMachines: false,
awsMachineCollectionErrorMsg: ''
}; };
} }
@ -48,13 +57,7 @@ class RunMonkeyPageComponent extends AuthComponent {
}); });
this.fetchAwsInfo(); this.fetchAwsInfo();
this.fetchConfig() this.fetchConfig();
.then(config => {
this.setState({
awsKeyId: config['cnc']['aws_config']['aws_access_key_id'],
awsSecretKey: config['cnc']['aws_config']['aws_secret_access_key']
});
});
this.authFetch('/api/client-monkey') this.authFetch('/api/client-monkey')
.then(res => res.json()) .then(res => res.json())
@ -75,17 +78,29 @@ class RunMonkeyPageComponent extends AuthComponent {
.then(res =>{ .then(res =>{
let is_aws = res['is_aws']; let is_aws = res['is_aws'];
if (is_aws) { if (is_aws) {
this.setState({isOnAws: true, awsMachines: res['instances'], isAwsAuth: res['auth']}); // On AWS!
// Checks if there was an error while collecting the aws machines.
let is_error_while_collecting_aws_machines = (res['error'] != null);
if (is_error_while_collecting_aws_machines) {
// There was an error. Finish loading, and display error message.
this.setState({isOnAws: true, isErrorWhileCollectingAwsMachines: true, awsMachineCollectionErrorMsg: res['error'], isLoadingAws: false});
} else {
// No error! Finish loading and display machines for user
this.setState({isOnAws: true, awsMachines: res['instances'], isLoadingAws: false});
}
} else {
// Not on AWS. Finish loading and don't display the AWS div.
this.setState({isOnAws: false, isLoadingAws: false});
} }
}); });
} }
generateLinuxCmd(ip, is32Bit) { static generateLinuxCmd(ip, is32Bit) {
let bitText = is32Bit ? '32' : '64'; let bitText = is32Bit ? '32' : '64';
return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000`
} }
generateWindowsCmd(ip, is32Bit) { static generateWindowsCmd(ip, is32Bit) {
let bitText = is32Bit ? '32' : '64'; let bitText = is32Bit ? '32' : '64';
return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe','.\\monkey.exe'); ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe','.\\monkey.exe'); ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`;
} }
@ -118,9 +133,9 @@ class RunMonkeyPageComponent extends AuthComponent {
let is32Bit = (this.state.selectedOs.split('-')[1] === '32'); let is32Bit = (this.state.selectedOs.split('-')[1] === '32');
let cmdText = ''; let cmdText = '';
if (isLinux) { if (isLinux) {
cmdText = this.generateLinuxCmd(this.state.selectedIp, is32Bit); cmdText = RunMonkeyPageComponent.generateLinuxCmd(this.state.selectedIp, is32Bit);
} else { } else {
cmdText = this.generateWindowsCmd(this.state.selectedIp, is32Bit); cmdText = RunMonkeyPageComponent.generateWindowsCmd(this.state.selectedIp, is32Bit);
} }
return ( return (
<Well key={'cmdDiv'+this.state.selectedIp} className="well-sm" style={{'margin': '0.5em'}}> <Well key={'cmdDiv'+this.state.selectedIp} className="well-sm" style={{'margin': '0.5em'}}>
@ -148,7 +163,7 @@ class RunMonkeyPageComponent extends AuthComponent {
}); });
}; };
renderIconByState(state) { static renderIconByState(state) {
if (state === 'running') { if (state === 'running') {
return <Icon name="check" className="text-success" style={{'marginLeft': '5px'}}/> return <Icon name="check" className="text-success" style={{'marginLeft': '5px'}}/>
} else if (state === 'installing') { } else if (state === 'installing') {
@ -204,19 +219,6 @@ class RunMonkeyPageComponent extends AuthComponent {
}); });
}); });
}; };
updateAwsKeyId = (evt) => {
this.setState({
awsKeyId: evt.target.value
});
};
updateAwsSecretKey = (evt) => {
this.setState({
awsSecretKey: evt.target.value
});
};
fetchConfig() { fetchConfig() {
return this.authFetch('/api/configuration/island') return this.authFetch('/api/configuration/island')
.then(res => res.json()) .then(res => res.json())
@ -224,41 +226,6 @@ class RunMonkeyPageComponent extends AuthComponent {
return res.configuration; return res.configuration;
}) })
} }
updateAwsKeys = () => {
this.setState({
awsUpdateClicked: true,
awsUpdateFailed: false
});
this.fetchConfig()
.then(config => {
let new_config = config;
new_config['cnc']['aws_config']['aws_access_key_id'] = this.state.awsKeyId;
new_config['cnc']['aws_config']['aws_secret_access_key'] = this.state.awsSecretKey;
return new_config;
})
.then(new_config => {
this.authFetch('/api/configuration/island',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(new_config)
})
.then(res => res.json())
.then(res => {
this.fetchAwsInfo()
.then(res => {
if (!this.state.isAwsAuth) {
this.setState({
awsUpdateClicked: false,
awsUpdateFailed: true
})
}
});
});
});
};
instanceIdToInstance = (instance_id) => { instanceIdToInstance = (instance_id) => {
let instance = this.state.awsMachines.find( let instance = this.state.awsMachines.find(
function (inst) { function (inst) {
@ -268,9 +235,15 @@ class RunMonkeyPageComponent extends AuthComponent {
}; };
renderAuthAwsDiv() { renderAwsMachinesDiv() {
return ( return (
<div style={{'marginBottom': '2em'}}> <div style={{'marginBottom': '2em'}}>
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<p className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
Not sure what this is? Not seeing your AWS EC2 instances? <a href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances">Read the documentation</a>!
</p>
</div>
{ {
this.state.ips.length > 1 ? this.state.ips.length > 1 ?
<Nav bsStyle="pills" justified activeKey={this.state.selectedIp} onSelect={this.setSelectedIp} <Nav bsStyle="pills" justified activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
@ -296,60 +269,6 @@ class RunMonkeyPageComponent extends AuthComponent {
</div> </div>
) )
} }
renderNotAuthAwsDiv() {
return (
<div style={{'marginBottom': '2em'}}>
<p style={{'fontSize': '1.2em'}}>
You haven't set your AWS account details or they're incorrect. Please enter them below to proceed.
</p>
<div style={{'marginTop': '1em'}}>
<div className="col-sm-12">
<div className="col-sm-6 col-sm-offset-3" style={{'fontSize': '1.2em'}}>
<div className="panel panel-default">
<div className="panel-body">
<div className="input-group center-block text-center">
<input type="text" className="form-control" placeholder="AWS Access Key ID"
value={this.state.awsKeyId}
onChange={evt => this.updateAwsKeyId(evt)}/>
<input type="text" className="form-control" placeholder="AWS Secret Access Key"
value={this.state.awsSecretKey}
onChange={evt => this.updateAwsSecretKey(evt)}/>
<Button
onClick={this.updateAwsKeys}
className={'btn btn-default btn-md center-block'}
disabled={this.state.awsUpdateClicked}
variant="primary">
Update AWS details
{ this.state.awsUpdateClicked ? <Icon name="refresh" className="text-success" style={{'marginLeft': '5px'}}/> : null }
</Button>
</div>
</div>
</div>
</div>
<div className="col-sm-8 col-sm-offset-2" style={{'fontSize': '1.2em'}}>
<p className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
In order to remotely run commands on AWS EC2 instances, please make sure you have
the <a href="https://docs.aws.amazon.com/console/ec2/run-command/prereqs" target="_blank">prerequisites</a> and if the
instances don't show up, check the
AWS <a href="https://docs.aws.amazon.com/console/ec2/run-command/troubleshooting" target="_blank">troubleshooting guide</a>.
</p>
</div>
{
this.state.awsUpdateFailed ?
<div className="col-sm-8 col-sm-offset-2" style={{'fontSize': '1.2em'}}>
<p className="alert alert-danger" role="alert">Authentication failed.</p>
</div>
:
null
}
</div>
</div>
</div>
)
}
render() { render() {
return ( return (
<Col xs={12} lg={8}> <Col xs={12} lg={8}>
@ -364,7 +283,7 @@ class RunMonkeyPageComponent extends AuthComponent {
disabled={this.state.runningOnIslandState !== 'not_running'} disabled={this.state.runningOnIslandState !== 'not_running'}
> >
Run on Monkey Island Server Run on Monkey Island Server
{ this.renderIconByState(this.state.runningOnIslandState) } { RunMonkeyPageComponent.renderIconByState(this.state.runningOnIslandState) }
</button> </button>
{ {
// TODO: implement button functionality // TODO: implement button functionality
@ -412,6 +331,21 @@ class RunMonkeyPageComponent extends AuthComponent {
{this.generateCmdDiv()} {this.generateCmdDiv()}
</div> </div>
</Collapse> </Collapse>
{
this.state.isLoadingAws ?
<p style={{'marginBottom': '2em', 'align': 'center'}}>
<div className='sweet-loading'>
<GridLoader
css={loading_css_override}
sizeUnit={"px"}
size={30}
color={'#ffcc00'}
loading={this.state.loading}
/>
</div>
</p>
: null
}
{ {
this.state.isOnAws ? this.state.isOnAws ?
<p className="text-center"> <p className="text-center">
@ -432,7 +366,17 @@ class RunMonkeyPageComponent extends AuthComponent {
} }
<Collapse in={this.state.showAws}> <Collapse in={this.state.showAws}>
{ {
this.state.isAwsAuth ? this.renderAuthAwsDiv() : this.renderNotAuthAwsDiv() this.state.isErrorWhileCollectingAwsMachines ?
<div style={{'marginTop': '1em'}}>
<p class="alert alert-danger">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
Error while collecting AWS machine data. Error message: <code>{this.state.awsMachineCollectionErrorMsg}</code><br/>
Are you sure you've set the correct role on your Island AWS machine?<br/>
Not sure what this is? <a href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances">Read the documentation</a>!
</p>
</div>
:
this.renderAwsMachinesDiv()
} }
</Collapse> </Collapse>

22
monkey/monkey_island/linux/install_mongo.sh Normal file → Executable file
View File

@ -3,19 +3,19 @@
export os_version_monkey=$(cat /etc/issue) export os_version_monkey=$(cat /etc/issue)
MONGODB_DIR=$1 # If using deb, this should be: /var/monkey/monkey_island/bin/mongodb MONGODB_DIR=$1 # If using deb, this should be: /var/monkey/monkey_island/bin/mongodb
if [[ $os_version_monkey == "Ubuntu 16.04"* ]] ; if [[ ${os_version_monkey} == "Ubuntu 16.04"* ]] ;
then then
echo Detected Ubuntu 16.04 echo Detected Ubuntu 16.04
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.6.12.tgz" export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.6.12.tgz"
elif [[ $os_version_monkey == "Ubuntu 18.04"* ]] ; elif [[ ${os_version_monkey} == "Ubuntu 18.04"* ]] ;
then then
echo Detected Ubuntu 18.04 echo Detected Ubuntu 18.04
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.0.8.tgz" export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.0.8.tgz"
elif [[ $os_version_monkey == "Debian GNU/Linux 8"* ]] ; elif [[ ${os_version_monkey} == "Debian GNU/Linux 8"* ]] ;
then then
echo Detected Debian 8 echo Detected Debian 8
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian81-3.6.12.tgz" export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian81-3.6.12.tgz"
elif [[ $os_version_monkey == "Debian GNU/Linux 9"* ]] ; elif [[ ${os_version_monkey} == "Debian GNU/Linux 9"* ]] ;
then then
echo Detected Debian 9 echo Detected Debian 9
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian92-3.6.12.tgz" export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian92-3.6.12.tgz"
@ -25,15 +25,15 @@ else
fi fi
TEMP_MONGO=$(mktemp -d) TEMP_MONGO=$(mktemp -d)
pushd $TEMP_MONGO pushd ${TEMP_MONGO}
wget $tgz_url -O mongodb.tgz wget ${tgz_url} -O mongodb.tgz
tar -xf mongodb.tgz tar -xf mongodb.tgz
popd popd
mkdir -p $MONGODB_DIR/bin mkdir -p ${MONGODB_DIR}/bin
cp $TEMP_MONGO/mongodb-*/bin/mongod $MONGODB_DIR/bin/mongod cp ${TEMP_MONGO}/mongodb-*/bin/mongod ${MONGODB_DIR}/bin/mongod
cp $TEMP_MONGO/mongodb-*/LICENSE-Community.txt $MONGODB_DIR/ cp ${TEMP_MONGO}/mongodb-*/LICENSE-Community.txt ${MONGODB_DIR}/
chmod a+x $MONGODB_DIR/bin/mongod chmod a+x ${MONGODB_DIR}/bin/mongod
rm -r $TEMP_MONGO rm -r ${TEMP_MONGO}
exit 0 exit 0

View File

@ -16,9 +16,10 @@ ipaddress
enum34 enum34
pycryptodome pycryptodome
boto3 boto3
botocore
PyInstaller
awscli awscli
cffi cffi
PyInstaller
virtualenv virtualenv
wheel wheel
requests requests

View File

@ -1,3 +1,3 @@
REM - Runs MongoDB Server - REM - Runs MongoDB Server -
@title MongoDB @title MongoDB
@bin\mongodb\mongod.exe --dbpath db @bin\mongodb\mongod.exe --dbpath db --bind_ip 127.0.0.1