From 5dc6fffc2a2547d7001b4781c494ccaa624f7cd4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 20 Dec 2019 12:12:26 +0200 Subject: [PATCH 01/53] Added config won't change warning --- .../ui/src/components/pages/ConfigurePage.js | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 6d9325487..a49e198a6 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -30,7 +30,7 @@ class ConfigurePageComponent extends AuthComponent { lastAction: 'none', sections: [], selectedSection: 'attack', - allMonkeysAreDead: true, + monkeysRan: false, PBAwinFile: [], PBAlinuxFile: [], showAttackAlert: false @@ -363,13 +363,7 @@ class ConfigurePageComponent extends AuthComponent { this.authFetch('/api') .then(res => res.json()) .then(res => { - // This check is used to prevent unnecessary re-rendering - let allMonkeysAreDead = (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']); - if (allMonkeysAreDead !== this.state.allMonkeysAreDead) { - this.setState({ - allMonkeysAreDead: allMonkeysAreDead - }); - } + this.setState({monkeysRan: res['completed_steps']['run_monkey']}); }); }; @@ -470,15 +464,15 @@ class ConfigurePageComponent extends AuthComponent { ) }; - renderRunningMonkeysWarning = () => { + renderConfigWontChangeWarning = () => { return (
- {this.state.allMonkeysAreDead ? - '' : + {this.state.monkeysRan ?
- Some monkeys are currently running. Note that changing the configuration will only apply to new - infections. + Changed configuration will only apply to new infections. + "Start over" to run again with different configuration.
+ : '' }
) }; @@ -520,7 +514,7 @@ class ConfigurePageComponent extends AuthComponent { {this.renderAttackAlertModal()}

Monkey Configuration

{this.renderNav()} - {this.renderRunningMonkeysWarning()} + {this.renderConfigWontChangeWarning()} {content}
+ +
+ + + ) + + }; +} From 18731a430bf2e431623d66a499a6cd3f1692aabc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 7 Jan 2020 16:06:50 +0200 Subject: [PATCH 11/53] Fixed start over modal and start over page, added stylesheet --- .../ui/src/components/pages/StartOverPage.js | 63 ++++++----------- .../ui-components/StartOverModal.js | 67 +++++++++++++------ .../cc/ui/src/styles/StartOverPage.scss | 9 +++ 3 files changed, 77 insertions(+), 62 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/styles/StartOverPage.scss diff --git a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js index eca159133..2aaeddec7 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js @@ -1,7 +1,9 @@ import React from 'react'; -import {Col, Modal} from 'react-bootstrap'; +import {Col} from 'react-bootstrap'; import {Link} from 'react-router-dom'; import AuthComponent from '../AuthComponent'; +import StartOverModal from '../ui-components/StartOverModal'; +import '../../styles/StartOverPage.scss'; class StartOverPageComponent extends AuthComponent { constructor(props) { @@ -12,6 +14,9 @@ class StartOverPageComponent extends AuthComponent { showCleanDialog: false, allMonkeysAreDead: false }; + + this.cleanup = this.cleanup.bind(this); + this.closeModal = this.closeModal.bind(this); } updateMonkeysRunning = () => { @@ -25,48 +30,14 @@ class StartOverPageComponent extends AuthComponent { }); }; - renderCleanDialogModal = () => { - return ( - this.setState({showCleanDialog: false})}> - -

-
Reset environment
-

-

- Are you sure you want to reset the environment? -

- { - !this.state.allMonkeysAreDead ? -
- - Some monkeys are still running. It's advised to kill all monkeys before resetting. -
- : -
- } -
- - -
- - - ) - - }; - render() { return ( - {this.renderCleanDialogModal()} +

Start Over

@@ -104,7 +75,7 @@ class StartOverPageComponent extends AuthComponent { this.setState({ cleaned: false }); - this.authFetch('/api?action=reset') + return this.authFetch('/api?action=reset') .then(res => res.json()) .then(res => { if (res['status'] === 'OK') { @@ -112,8 +83,14 @@ class StartOverPageComponent extends AuthComponent { cleaned: true }); } - }); - } + }).then(this.updateMonkeysRunning()); + }; + + closeModal = () => { + this.setState({ + showCleanDialog: false, + }) + }; } export default StartOverPageComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/StartOverModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/StartOverModal.js index 057d45779..5c0ae1cb2 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/StartOverModal.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/StartOverModal.js @@ -1,13 +1,30 @@ import {Modal} from "react-bootstrap"; -import Modal from "react-bootstrap/es/Modal"; -import ReactComponent from "react"; -import AuthComponent from "../AuthComponent"; +import React from "react"; +import {GridLoader} from 'react-spinners'; -class StartOverModal extends ReactComponent { +class StartOverModal extends React.PureComponent { + + constructor(props) { + super(props); + + this.state = { + showCleanDialog: this.props.showCleanDialog, + allMonkeysAreDead: this.props.allMonkeysAreDead, + loading: false + }; + } + + componentDidUpdate(prevProps) { + if (this.props !== prevProps) { + this.setState({ showCleanDialog: this.props.showCleanDialog, + allMonkeysAreDead: this.props.allMonkeysAreDead}) + } + } + render = () => { return ( - this.setState({showCleanDialog: false})}> + this.props.onClose()}>

Reset environment
@@ -24,22 +41,34 @@ class StartOverModal extends ReactComponent { :
} -
- - -
+ { + this.state.loading ?
: this.showModalButtons() + } ) - }; + + showModalButtons() { + return (
+ + +
) + } + + modalVerificationOnClick = async () => { + this.setState({loading: true}); + this.props.onVerify() + .then(() => {this.setState({loading: false}); + this.props.onClose();}) + + } } + +export default StartOverModal; diff --git a/monkey/monkey_island/cc/ui/src/styles/StartOverPage.scss b/monkey/monkey_island/cc/ui/src/styles/StartOverPage.scss new file mode 100644 index 000000000..ee4ab65ea --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/StartOverPage.scss @@ -0,0 +1,9 @@ +$yellow: #ffcc00; + +.modalLoader div{ + margin-left: auto; + margin-right: auto; +} +.modalLoader div>div{ + background-color: $yellow; +} From b130dd35ec8456f6c4870ee12af0c4312cefc547 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 8 Jan 2020 08:57:25 +0200 Subject: [PATCH 12/53] double quotes to single quotes in StartOverModal --- .../components/ui-components/StartOverModal.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/StartOverModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/StartOverModal.js index 5c0ae1cb2..5694d463a 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/StartOverModal.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/StartOverModal.js @@ -1,5 +1,5 @@ -import {Modal} from "react-bootstrap"; -import React from "react"; +import {Modal} from 'react-bootstrap'; +import React from 'react'; import {GridLoader} from 'react-spinners'; @@ -27,22 +27,22 @@ class StartOverModal extends React.PureComponent { this.props.onClose()}>

-
Reset environment
+
Reset environment

Are you sure you want to reset the environment?

{ !this.state.allMonkeysAreDead ? -
- +
+ Some monkeys are still running. It's advised to kill all monkeys before resetting.
:
} { - this.state.loading ?
: this.showModalButtons() + this.state.loading ?
: this.showModalButtons() } @@ -50,12 +50,12 @@ class StartOverModal extends React.PureComponent { }; showModalButtons() { - return (
- - From 91c05ae0f4651ef133b3912d8de34f125fa38461 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 8 Jan 2020 09:19:29 +0200 Subject: [PATCH 13/53] dangling comma removed from StartOverPage --- .../monkey_island/cc/ui/src/components/pages/StartOverPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js index 2aaeddec7..a57c81ca5 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js @@ -88,7 +88,7 @@ class StartOverPageComponent extends AuthComponent { closeModal = () => { this.setState({ - showCleanDialog: false, + showCleanDialog: false }) }; } From b9d2614271689d3752cbf0f57b3e2f4f07f37a9c Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 8 Jan 2020 11:09:52 +0200 Subject: [PATCH 14/53] CR: Moved AWS classes to own package, create generic CloudInstance class --- monkey/common/cloud/aws/__init__.py | 0 monkey/common/cloud/{ => aws}/aws_instance.py | 6 ++++-- monkey/common/cloud/{ => aws}/aws_service.py | 2 +- monkey/common/cloud/{ => aws}/aws_service_test.py | 0 monkey/common/cloud/azure/azure_instance.py | 8 +++++--- monkey/common/cloud/environment_names.py | 7 ++++++- monkey/common/cloud/instance.py | 3 +++ monkey/common/cmd/aws/aws_cmd_runner.py | 2 +- monkey/infection_monkey/system_info/aws_collector.py | 4 ++-- .../system_info/collectors/environment_collector.py | 6 +++--- monkey/monkey_island/cc/environment/aws.py | 2 +- monkey/monkey_island/cc/resources/remote_run.py | 2 +- monkey/monkey_island/cc/services/remote_run_aws.py | 6 +++--- .../monkey_island/cc/services/reporting/aws_exporter.py | 2 +- 14 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 monkey/common/cloud/aws/__init__.py rename monkey/common/cloud/{ => aws}/aws_instance.py (96%) rename monkey/common/cloud/{ => aws}/aws_service.py (98%) rename monkey/common/cloud/{ => aws}/aws_service_test.py (100%) create mode 100644 monkey/common/cloud/instance.py diff --git a/monkey/common/cloud/aws/__init__.py b/monkey/common/cloud/aws/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/common/cloud/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py similarity index 96% rename from monkey/common/cloud/aws_instance.py rename to monkey/common/cloud/aws/aws_instance.py index 4339fbcf4..301881894 100644 --- a/monkey/common/cloud/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -6,6 +6,8 @@ import logging __author__ = 'itay.mizeretz' +from common.cloud.instance import CloudInstance + 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" @@ -13,7 +15,7 @@ ACCOUNT_ID_KEY = "accountId" logger = logging.getLogger(__name__) -class AwsInstance(object): +class AwsInstance(CloudInstance): """ Class which gives useful information about the current instance you're on. """ @@ -57,7 +59,7 @@ class AwsInstance(object): def get_region(self): return self.region - def is_aws_instance(self): + def is_instance(self): return self.instance_id is not None @staticmethod diff --git a/monkey/common/cloud/aws_service.py b/monkey/common/cloud/aws/aws_service.py similarity index 98% rename from monkey/common/cloud/aws_service.py rename to monkey/common/cloud/aws/aws_service.py index 6ef385542..a42c2e1dd 100644 --- a/monkey/common/cloud/aws_service.py +++ b/monkey/common/cloud/aws/aws_service.py @@ -4,7 +4,7 @@ import boto3 import botocore from botocore.exceptions import ClientError -from common.cloud.aws_instance import AwsInstance +from common.cloud.aws.aws_instance import AwsInstance __author__ = ['itay.mizeretz', 'shay.nehmad'] diff --git a/monkey/common/cloud/aws_service_test.py b/monkey/common/cloud/aws/aws_service_test.py similarity index 100% rename from monkey/common/cloud/aws_service_test.py rename to monkey/common/cloud/aws/aws_service_test.py diff --git a/monkey/common/cloud/azure/azure_instance.py b/monkey/common/cloud/azure/azure_instance.py index a58e0e126..5222c7620 100644 --- a/monkey/common/cloud/azure/azure_instance.py +++ b/monkey/common/cloud/azure/azure_instance.py @@ -1,13 +1,15 @@ import logging import requests -LATEST_AZURE_METADATA_API_VERSION = "2019-06-04" +from common.cloud.instance import CloudInstance + +LATEST_AZURE_METADATA_API_VERSION = "2019-04-30" AZURE_METADATA_SERVICE_URL = "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION logger = logging.getLogger(__name__) -class AzureInstance(object): +class AzureInstance(CloudInstance): """ Access to useful information about the current machine if it's an Azure VM. Based on Azure metadata service: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service @@ -46,5 +48,5 @@ class AzureInstance(object): except KeyError: logger.exception("Error while parsing response from Azure metadata service.") - def is_azure_instance(self): + def is_instance(self): return self.on_azure diff --git a/monkey/common/cloud/environment_names.py b/monkey/common/cloud/environment_names.py index 1745eed62..0c8655753 100644 --- a/monkey/common/cloud/environment_names.py +++ b/monkey/common/cloud/environment_names.py @@ -1,7 +1,12 @@ +# When adding a new environment to this file, make sure to add it to ALL_ENV_NAMES as well! + UNKNOWN = "Unknown" ON_PREMISE = "On Premise" AZURE = "Azure" AWS = "AWS" GCP = "GCP" +ALIBABA = "Alibaba Cloud" +IBM = "IBM Cloud" +DigitalOcean = "Digital Ocean" -ALL_ENV_NAMES = [UNKNOWN, ON_PREMISE, AZURE, AWS, GCP] +ALL_ENV_NAMES = [UNKNOWN, ON_PREMISE, AZURE, AWS, GCP, ALIBABA, IBM, DigitalOcean] diff --git a/monkey/common/cloud/instance.py b/monkey/common/cloud/instance.py new file mode 100644 index 000000000..52dd56b02 --- /dev/null +++ b/monkey/common/cloud/instance.py @@ -0,0 +1,3 @@ +class CloudInstance(object): + def is_instance(self) -> bool: + raise NotImplementedError() diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py index 459a42129..1ab680c4d 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -1,6 +1,6 @@ import logging -from common.cloud.aws_service import AwsService +from common.cloud.aws.aws_service import AwsService from common.cmd.aws.aws_cmd_result import AwsCmdResult from common.cmd.cmd_runner import CmdRunner from common.cmd.cmd_status import CmdStatus diff --git a/monkey/infection_monkey/system_info/aws_collector.py b/monkey/infection_monkey/system_info/aws_collector.py index df90e5913..f39662d13 100644 --- a/monkey/infection_monkey/system_info/aws_collector.py +++ b/monkey/infection_monkey/system_info/aws_collector.py @@ -1,6 +1,6 @@ import logging -from common.cloud.aws_instance import AwsInstance +from common.cloud.aws.aws_instance import AwsInstance __author__ = 'itay.mizeretz' @@ -17,7 +17,7 @@ class AwsCollector(object): LOG.info("Collecting AWS info") aws = AwsInstance() info = {} - if aws.is_aws_instance(): + if aws.is_instance(): LOG.info("Machine is an AWS instance") info = \ { diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 523989f61..208bbfa42 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -1,4 +1,4 @@ -from common.cloud.aws_instance import AwsInstance +from common.cloud.aws.aws_instance import AwsInstance from common.cloud.azure.azure_instance import AzureInstance from common.cloud.environment_names import ON_PREMISE, AZURE, AWS from infection_monkey.system_info.system_info_collector import SystemInfoCollector @@ -6,9 +6,9 @@ from infection_monkey.system_info.system_info_collector import SystemInfoCollect def get_monkey_environment(): # Check if on any cloud env. Default is on prem. - if AwsInstance().is_aws_instance(): + if AwsInstance().is_instance(): env = AWS - elif AzureInstance().is_azure_instance(): + elif AzureInstance().is_instance(): env = AZURE # TODO: elif GcpInstance().is_gcp_instance(): else: diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index 18db5c376..5608bddcd 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -1,6 +1,6 @@ import monkey_island.cc.auth from monkey_island.cc.environment import Environment -from common.cloud.aws_instance import AwsInstance +from common.cloud.aws.aws_instance import AwsInstance __author__ = 'itay.mizeretz' diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index c41699add..98d3694bf 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -6,7 +6,7 @@ import flask_restful from monkey_island.cc.auth import jwt_required from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService -from common.cloud.aws_service import AwsService +from common.cloud.aws.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. " diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 9627bf74c..0ba6fa4ef 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -1,7 +1,7 @@ import logging -from common.cloud.aws_instance import AwsInstance -from common.cloud.aws_service import AwsService +from common.cloud.aws.aws_instance import AwsInstance +from common.cloud.aws.aws_service import AwsService from common.cmd.aws.aws_cmd_runner import AwsCmdRunner from common.cmd.cmd import Cmd from common.cmd.cmd_runner import CmdRunner @@ -54,7 +54,7 @@ class RemoteRunAwsService: @staticmethod def is_running_on_aws(): - return RemoteRunAwsService.aws_instance.is_aws_instance() + return RemoteRunAwsService.aws_instance.is_instance() @staticmethod def update_aws_region_authless(): diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 1df12e2eb..86486b9ba 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -5,7 +5,7 @@ from datetime import datetime import boto3 from botocore.exceptions import UnknownServiceError -from common.cloud.aws_instance import AwsInstance +from common.cloud.aws.aws_instance import AwsInstance from monkey_island.cc.environment.environment import load_server_configuration_from_file from monkey_island.cc.services.reporting.exporter import Exporter From 676d46307bf4c761bf59f56226e53dc314022155 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 8 Jan 2020 11:20:49 +0200 Subject: [PATCH 15/53] Using the generic CloudInstance class to determine environment --- monkey/common/cloud/aws/aws_instance.py | 9 ++++++--- monkey/common/cloud/azure/azure_instance.py | 9 ++++++--- monkey/common/cloud/instance.py | 15 +++++++++++++++ .../collectors/environment_collector.py | 15 ++++++--------- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index 301881894..c77220d17 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -6,6 +6,7 @@ import logging __author__ = 'itay.mizeretz' +from common.cloud.environment_names import AWS from common.cloud.instance import CloudInstance AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254" @@ -19,6 +20,11 @@ class AwsInstance(CloudInstance): """ Class which gives useful information about the current instance you're on. """ + def is_instance(self): + return self.instance_id is not None + + def get_cloud_provider_name(self) -> str: + return AWS def __init__(self): self.instance_id = None @@ -59,9 +65,6 @@ class AwsInstance(CloudInstance): def get_region(self): return self.region - def is_instance(self): - return self.instance_id is not None - @staticmethod def _extract_account_id(instance_identity_document_response): """ diff --git a/monkey/common/cloud/azure/azure_instance.py b/monkey/common/cloud/azure/azure_instance.py index 5222c7620..f0d5a8044 100644 --- a/monkey/common/cloud/azure/azure_instance.py +++ b/monkey/common/cloud/azure/azure_instance.py @@ -1,6 +1,7 @@ import logging import requests +from common.cloud.environment_names import AZURE from common.cloud.instance import CloudInstance LATEST_AZURE_METADATA_API_VERSION = "2019-04-30" @@ -14,6 +15,11 @@ class AzureInstance(CloudInstance): Access to useful information about the current machine if it's an Azure VM. Based on Azure metadata service: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service """ + def is_instance(self): + return self.on_azure + + def get_cloud_provider_name(self) -> str: + return AZURE def __init__(self): """ @@ -47,6 +53,3 @@ class AzureInstance(CloudInstance): self.location = response_data["compute"]["location"] except KeyError: logger.exception("Error while parsing response from Azure metadata service.") - - def is_instance(self): - return self.on_azure diff --git a/monkey/common/cloud/instance.py b/monkey/common/cloud/instance.py index 52dd56b02..2e702b867 100644 --- a/monkey/common/cloud/instance.py +++ b/monkey/common/cloud/instance.py @@ -1,3 +1,18 @@ +from typing import List + +from common.cloud.aws.aws_instance import AwsInstance +from common.cloud.azure.azure_instance import AzureInstance + + class CloudInstance(object): def is_instance(self) -> bool: raise NotImplementedError() + + def get_cloud_provider_name(self) -> str: + raise NotImplementedError() + + all_cloud_instances = [AwsInstance(), AzureInstance()] + + @staticmethod + def get_all_cloud_instances() -> List['CloudInstance']: + return CloudInstance.all_cloud_instances diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 208bbfa42..56df5906b 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -1,18 +1,15 @@ from common.cloud.aws.aws_instance import AwsInstance from common.cloud.azure.azure_instance import AzureInstance from common.cloud.environment_names import ON_PREMISE, AZURE, AWS +from common.cloud.instance import CloudInstance from infection_monkey.system_info.system_info_collector import SystemInfoCollector -def get_monkey_environment(): - # Check if on any cloud env. Default is on prem. - if AwsInstance().is_instance(): - env = AWS - elif AzureInstance().is_instance(): - env = AZURE - # TODO: elif GcpInstance().is_gcp_instance(): - else: - env = ON_PREMISE +def get_monkey_environment() -> str: + env = ON_PREMISE + for instance in CloudInstance.get_all_cloud_instances(): + if instance.is_instance(): + env = instance.get_cloud_provider_name() return env From 875cf3318d4a6373922c99ba6f58a88d0af1b234 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 8 Jan 2020 12:21:38 +0200 Subject: [PATCH 16/53] Fixed circular import --- monkey/common/cloud/all_instances.py | 11 +++++++++++ monkey/common/cloud/instance.py | 17 +++++------------ .../collectors/environment_collector.py | 3 ++- 3 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 monkey/common/cloud/all_instances.py diff --git a/monkey/common/cloud/all_instances.py b/monkey/common/cloud/all_instances.py new file mode 100644 index 000000000..986bf1a80 --- /dev/null +++ b/monkey/common/cloud/all_instances.py @@ -0,0 +1,11 @@ +from typing import List + +from common.cloud.aws.aws_instance import AwsInstance +from common.cloud.azure.azure_instance import AzureInstance +from common.cloud.instance import CloudInstance + +all_cloud_instances = [AwsInstance(), AzureInstance()] + + +def get_all_cloud_instances() -> List[CloudInstance]: + return all_cloud_instances diff --git a/monkey/common/cloud/instance.py b/monkey/common/cloud/instance.py index 2e702b867..61ab4c734 100644 --- a/monkey/common/cloud/instance.py +++ b/monkey/common/cloud/instance.py @@ -1,18 +1,11 @@ -from typing import List - -from common.cloud.aws.aws_instance import AwsInstance -from common.cloud.azure.azure_instance import AzureInstance - - class CloudInstance(object): + """ + This is an abstract class which represents a cloud instance. + + The current machine can be a cloud instance (for example EC2 instance or Azure VM). + """ def is_instance(self) -> bool: raise NotImplementedError() def get_cloud_provider_name(self) -> str: raise NotImplementedError() - - all_cloud_instances = [AwsInstance(), AzureInstance()] - - @staticmethod - def get_all_cloud_instances() -> List['CloudInstance']: - return CloudInstance.all_cloud_instances diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 56df5906b..c679e04f7 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -1,3 +1,4 @@ +from common.cloud.all_instances import get_all_cloud_instances from common.cloud.aws.aws_instance import AwsInstance from common.cloud.azure.azure_instance import AzureInstance from common.cloud.environment_names import ON_PREMISE, AZURE, AWS @@ -7,7 +8,7 @@ from infection_monkey.system_info.system_info_collector import SystemInfoCollect def get_monkey_environment() -> str: env = ON_PREMISE - for instance in CloudInstance.get_all_cloud_instances(): + for instance in get_all_cloud_instances(): if instance.is_instance(): env = instance.get_cloud_provider_name() return env From a3d81a00864c4f8b33bd158b4eb11a1d68921a33 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 8 Jan 2020 14:00:12 +0200 Subject: [PATCH 17/53] Renamed PlannedShutdown to PlannedShutdownException --- monkey/infection_monkey/monkey.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 8543505a7..06a08f131 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -39,7 +39,7 @@ __author__ = 'itamar' LOG = logging.getLogger(__name__) -class PlannedShutdown(Exception): +class PlannedShutdownException(Exception): pass @@ -216,14 +216,14 @@ class InfectionMonkey(object): if monkey_tunnel: monkey_tunnel.stop() monkey_tunnel.join() - except PlannedShutdown: + except PlannedShutdownException: LOG.info("A planned shutdown of the Monkey occurred. Logging the reason and finishing execution.") LOG.exception("Planned shutdown, reason:") def shutdown_by_max_depth_reached(self): if 0 == WormConfiguration.depth: TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send() - raise PlannedShutdown(MAX_DEPTH_REACHED_MESSAGE) + raise PlannedShutdownException(MAX_DEPTH_REACHED_MESSAGE) else: LOG.debug("Running with depth: %d" % WormConfiguration.depth) @@ -236,7 +236,7 @@ class InfectionMonkey(object): def shutdown_by_not_alive_config(self): if not WormConfiguration.alive: - raise PlannedShutdown("Marked 'not alive' from configuration.") + raise PlannedShutdownException("Marked 'not alive' from configuration.") def upgrade_to_64_if_needed(self): if WindowsUpgrader.should_upgrade(): @@ -244,7 +244,7 @@ class InfectionMonkey(object): self._singleton.unlock() LOG.info("32bit monkey running on 64bit Windows. Upgrading.") WindowsUpgrader.upgrade(self._opts) - raise PlannedShutdown("Finished upgrading from 32bit to 64bit.") + raise PlannedShutdownException("Finished upgrading from 32bit to 64bit.") def cleanup(self): LOG.info("Monkey cleanup started") @@ -369,9 +369,9 @@ class InfectionMonkey(object): def set_default_server(self): """ Sets the default server for the Monkey to communicate back to. - :raises PlannedShutdown if couldn't find the server. + :raises PlannedShutdownException if couldn't find the server. """ if not ControlClient.find_server(default_tunnel=self._default_tunnel): - raise PlannedShutdown("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)) + raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)) self._default_server = WormConfiguration.current_server LOG.debug("default server set to: %s" % self._default_server) From 41fa1d3e3fe92e96f8c8de99640e06ec910b0303 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 8 Jan 2020 14:08:53 +0200 Subject: [PATCH 18/53] Made collect an abstract method --- .../infection_monkey/system_info/system_info_collector.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/system_info/system_info_collector.py b/monkey/infection_monkey/system_info/system_info_collector.py index 3a977fcde..f65b4c080 100644 --- a/monkey/infection_monkey/system_info/system_info_collector.py +++ b/monkey/infection_monkey/system_info/system_info_collector.py @@ -1,11 +1,11 @@ from config import WormConfiguration from infection_monkey.utils.plugins.plugin import Plugin - +from abc import ABCMeta, abstractmethod import infection_monkey.system_info.collectors -class SystemInfoCollector(Plugin): +class SystemInfoCollector(Plugin, metaclass=ABCMeta): def __init__(self, name="unknown"): self.name = name @@ -21,10 +21,10 @@ class SystemInfoCollector(Plugin): def base_package_name(): return infection_monkey.system_info.collectors.__package__ + @abstractmethod def collect(self) -> dict: """ Collect the relevant information and return it in a dictionary. To be implemented by each collector. - TODO should this be an abstractmethod, or will that ruin the plugin system somehow? if can be abstract should add UT """ raise NotImplementedError() From 26355540bd70081f32563c6041696a46c8bf3a80 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 8 Jan 2020 21:06:02 +0200 Subject: [PATCH 19/53] Update system_info_collectors_handler.py --- .../system_info/system_info_collectors_handler.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py index 1fdc74cfa..792024b12 100644 --- a/monkey/infection_monkey/system_info/system_info_collectors_handler.py +++ b/monkey/infection_monkey/system_info/system_info_collectors_handler.py @@ -2,14 +2,11 @@ import logging from typing import Sequence from infection_monkey.system_info.system_info_collector import SystemInfoCollector -from telemetry.system_info_telem import SystemInfoTelem +from infection_monkey.telemetry.system_info_telem import SystemInfoTelem LOG = logging.getLogger(__name__) -PATH_TO_COLLECTORS = "infection_monkey.system_info.collectors." - -# TODO Add new collectors to config and config schema class SystemInfoCollectorsHandler(object): def __init__(self): self.collectors_list = self.config_to_collectors_list() From 422fe6ff06e45d7d91164af4090140f70247bafb Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 19 Jan 2020 16:22:28 +0200 Subject: [PATCH 20/53] Added GCP instance as well --- monkey/common/cloud/all_instances.py | 3 +- monkey/common/cloud/gcp/__init__.py | 0 monkey/common/cloud/gcp/gcp_instance.py | 41 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 monkey/common/cloud/gcp/__init__.py create mode 100644 monkey/common/cloud/gcp/gcp_instance.py diff --git a/monkey/common/cloud/all_instances.py b/monkey/common/cloud/all_instances.py index 986bf1a80..6387730f6 100644 --- a/monkey/common/cloud/all_instances.py +++ b/monkey/common/cloud/all_instances.py @@ -2,9 +2,10 @@ from typing import List from common.cloud.aws.aws_instance import AwsInstance from common.cloud.azure.azure_instance import AzureInstance +from common.cloud.gcp.gcp_instance import GcpInstance from common.cloud.instance import CloudInstance -all_cloud_instances = [AwsInstance(), AzureInstance()] +all_cloud_instances = [AwsInstance(), AzureInstance(), GcpInstance()] def get_all_cloud_instances() -> List[CloudInstance]: diff --git a/monkey/common/cloud/gcp/__init__.py b/monkey/common/cloud/gcp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/common/cloud/gcp/gcp_instance.py b/monkey/common/cloud/gcp/gcp_instance.py new file mode 100644 index 000000000..5f04aabd0 --- /dev/null +++ b/monkey/common/cloud/gcp/gcp_instance.py @@ -0,0 +1,41 @@ +import logging +import requests + +from common.cloud.environment_names import GCP +from common.cloud.instance import CloudInstance + +logger = logging.getLogger(__name__) +GCP_METADATA_SERVICE_URL = "http://metadata.google.internal/" + + +class GcpInstance(CloudInstance): + def is_instance(self): + return self.on_gcp + + def get_cloud_provider_name(self) -> str: + return GCP + + def __init__(self): + """ + Determines if on GCP. + """ + self.on_gcp = False + + try: + # If not on GCP, this domain shouldn't resolve. + response = requests.get(GCP_METADATA_SERVICE_URL) + + if response: + logger.debug("Got response, so probably on GCP. Trying to parse.") + self.on_gcp = True + + if "Metadata-Flavor" not in response.headers: + logger.warning("Got unexpected GCP Metadata format") + else: + if not response.headers["Metadata-Flavor"] == "Google": + logger.warning("Got unexpected Metadata flavor: {}".format(response.headers["Metadata-Flavor"])) + else: + logger.warning("On GCP, but metadata response not ok: {}".format(response.status_code)) + except requests.RequestException: + logger.debug("Failed to get response from GCP metadata service: This instance is not on GCP.") + self.on_gcp = False From d52672f4d765d77eb5b602fc99c9287eb4a84e6b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 19 Jan 2020 16:28:04 +0200 Subject: [PATCH 21/53] Added some documentation --- monkey/common/cloud/gcp/gcp_instance.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/monkey/common/cloud/gcp/gcp_instance.py b/monkey/common/cloud/gcp/gcp_instance.py index 5f04aabd0..26738db43 100644 --- a/monkey/common/cloud/gcp/gcp_instance.py +++ b/monkey/common/cloud/gcp/gcp_instance.py @@ -5,10 +5,15 @@ from common.cloud.environment_names import GCP from common.cloud.instance import CloudInstance logger = logging.getLogger(__name__) + + GCP_METADATA_SERVICE_URL = "http://metadata.google.internal/" class GcpInstance(CloudInstance): + """ + Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving-metadata#runninggce + """ def is_instance(self): return self.on_gcp @@ -16,9 +21,6 @@ class GcpInstance(CloudInstance): return GCP def __init__(self): - """ - Determines if on GCP. - """ self.on_gcp = False try: @@ -26,7 +28,7 @@ class GcpInstance(CloudInstance): response = requests.get(GCP_METADATA_SERVICE_URL) if response: - logger.debug("Got response, so probably on GCP. Trying to parse.") + logger.debug("Got ok metadata response: on GCP") self.on_gcp = True if "Metadata-Flavor" not in response.headers: @@ -37,5 +39,5 @@ class GcpInstance(CloudInstance): else: logger.warning("On GCP, but metadata response not ok: {}".format(response.status_code)) except requests.RequestException: - logger.debug("Failed to get response from GCP metadata service: This instance is not on GCP.") + logger.debug("Failed to get response from GCP metadata service: This instance is not on GCP") self.on_gcp = False From 9583956683676c2a559b4c88714bbdbdc669f188 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 19 Jan 2020 18:14:59 +0200 Subject: [PATCH 22/53] Optimised imports and added some documentation --- .../system_info/collectors/environment_collector.py | 5 +---- .../infection_monkey/system_info/system_info_collector.py | 8 ++++++++ .../system_info/system_info_collectors_handler.py | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index c679e04f7..ac5a5433d 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -1,8 +1,5 @@ from common.cloud.all_instances import get_all_cloud_instances -from common.cloud.aws.aws_instance import AwsInstance -from common.cloud.azure.azure_instance import AzureInstance -from common.cloud.environment_names import ON_PREMISE, AZURE, AWS -from common.cloud.instance import CloudInstance +from common.cloud.environment_names import ON_PREMISE from infection_monkey.system_info.system_info_collector import SystemInfoCollector diff --git a/monkey/infection_monkey/system_info/system_info_collector.py b/monkey/infection_monkey/system_info/system_info_collector.py index f65b4c080..c511c1e86 100644 --- a/monkey/infection_monkey/system_info/system_info_collector.py +++ b/monkey/infection_monkey/system_info/system_info_collector.py @@ -6,6 +6,14 @@ import infection_monkey.system_info.collectors class SystemInfoCollector(Plugin, metaclass=ABCMeta): + """ + ABC for system info collection. See system_info_collector_handler for more info. Basically, to implement a new system info + collector, inherit from this class in an implementation in the infection_monkey.system_info.collectors class, and override + the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the collector to the configuration + as well - see monkey_island.cc.services.processing.system_info_collectors for examples. + + See the Wiki page "How to add a new System Info Collector to the Monkey?" for a detailed guide. + """ def __init__(self, name="unknown"): self.name = name diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py index 792024b12..cc007ff86 100644 --- a/monkey/infection_monkey/system_info/system_info_collectors_handler.py +++ b/monkey/infection_monkey/system_info/system_info_collectors_handler.py @@ -21,6 +21,7 @@ class SystemInfoCollectorsHandler(object): system_info_telemetry[collector.name] = collected_info successful_collections += 1 except Exception as e: + # If we failed one collector, no need to stop execution. Log and continue. LOG.error("Collector {} failed. Error info: {}".format(collector.name, e)) LOG.info("All system info collectors executed. Total {} executed, out of which {} collected successfully.". format(len(self.collectors_list), successful_collections)) From 3496a78f6c0fdf2b0ee4128da2c14c9a77b72d6e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 19 Jan 2020 21:36:01 +0200 Subject: [PATCH 23/53] Added generic collector processing functions, a dispatcher (name to function) with unit tests, and moved AWS to collector from regular sysinfo --- monkey/infection_monkey/config.py | 2 +- .../infection_monkey/system_info/__init__.py | 10 --- .../system_info/collectors/aws_collector.py | 30 +++++++++ monkey/monkey_island/cc/server_config.json | 2 +- .../cc/services/config_schema.py | 11 +++- .../telemetry/processing/system_info.py | 11 +--- .../processing/system_info_collectors/aws.py | 15 +++++ .../system_info_collectors/environment.py | 12 ++-- .../system_info_telemetry_dispatcher.py | 36 ++++++++++ .../test_system_info_telemetry_dispatcher.py | 65 +++++++++++++++++++ 10 files changed, 165 insertions(+), 29 deletions(-) create mode 100644 monkey/infection_monkey/system_info/collectors/aws_collector.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index e76ed8101..e1b1ece83 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -125,7 +125,7 @@ class Configuration(object): finger_classes = [] exploiter_classes = [] - system_info_collectors_classes = ["EnvironmentCollector"] + system_info_collectors_classes = ["EnvironmentCollector", "AwsCollector"] # how many victims to look for in a single scan iteration victims_max_find = 100 diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index ccec45cde..fc4aa1caf 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -6,7 +6,6 @@ import psutil from enum import IntEnum from infection_monkey.network.info import get_host_subnets -from infection_monkey.system_info.aws_collector import AwsCollector from infection_monkey.system_info.azure_cred_collector import AzureCollector from infection_monkey.system_info.netstat_collector import NetstatCollector from system_info.system_info_collectors_handler import SystemInfoCollectorsHandler @@ -67,7 +66,6 @@ class InfoCollector(object): self.get_process_list() self.get_network_info() self.get_azure_info() - self.get_aws_info() # Collect all plugins SystemInfoCollectorsHandler().execute_all_configured() @@ -155,11 +153,3 @@ class InfoCollector(object): except Exception: # If we failed to collect azure info, no reason to fail all the collection. Log and continue. LOG.error("Failed collecting Azure info.", exc_info=True) - - def get_aws_info(self): - # noinspection PyBroadException - try: - self.info['aws'] = AwsCollector().get_aws_info() - except Exception: - # If we failed to collect aws info, no reason to fail all the collection. Log and continue. - LOG.error("Failed collecting AWS info.", exc_info=True) diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py new file mode 100644 index 000000000..71f9e58c1 --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py @@ -0,0 +1,30 @@ +import logging + +from common.cloud.aws.aws_instance import AwsInstance +from infection_monkey.system_info.system_info_collector import SystemInfoCollector + + +logger = logging.getLogger(__name__) + + +class AwsCollector(SystemInfoCollector): + """ + Extract info from AWS machines. + """ + def __init__(self): + super(AwsCollector, self).__init__(name="AwsCollector") + + def collect(self) -> dict: + logger.info("Collecting AWS info") + aws = AwsInstance() + info = {} + if aws.is_instance(): + logger.info("Machine is an AWS instance") + info = \ + { + 'instance_id': aws.get_instance_id() + } + else: + logger.info("Machine is NOT an AWS instance") + + return info diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 420f1b303..7bf106194 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,4 +1,4 @@ { - "server_config": "standard", + "server_config": "testing", "deployment": "develop" } diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 5de57e26b..d5e015866 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -111,6 +111,14 @@ SCHEMA = { "title": "Which Environment this machine is on (on prem/cloud)", "attack_techniques": [] }, + { + "type": "string", + "enum": [ + "AwsCollector" + ], + "title": "If on AWS, collect more information about the instance", + "attack_techniques": [] + }, ], }, "post_breach_acts": { @@ -455,7 +463,8 @@ SCHEMA = { "$ref": "#/definitions/system_info_collectors_classes" }, "default": [ - "EnvironmentCollector" + "EnvironmentCollector", + "AwsCollector" ], "description": "Determines which system information collectors will collect information." }, diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 04ab27d95..915fa7a25 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -5,6 +5,7 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.services import mimikatz_utils from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence from monkey_island.cc.services.wmi_handler import WMIHandler @@ -18,7 +19,7 @@ def process_system_info_telemetry(telemetry_json): process_ssh_info, process_credential_info, process_mimikatz_and_wmi_info, - process_aws_data, + process_aws_telemetry, update_db_with_new_hostname, test_antivirus_existence, process_environment_telemetry @@ -116,13 +117,5 @@ def process_mimikatz_and_wmi_info(telemetry_json): wmi_handler.process_and_handle_wmi_info() -def process_aws_data(telemetry_json): - if 'aws' in telemetry_json['data']: - if 'instance_id' in telemetry_json['data']['aws']: - monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') - mongo.db.monkey.update_one({'_id': monkey_id}, - {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}}) - - def update_db_with_new_hostname(telemetry_json): Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).set_hostname(telemetry_json['data']['hostname']) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py new file mode 100644 index 000000000..2b4d8085e --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py @@ -0,0 +1,15 @@ +import logging + +from monkey_island.cc.models.monkey import Monkey + +logger = logging.getLogger(__name__) + + +def process_aws_telemetry(collector_results, monkey_guid): + relevant_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) + + if "instance_id" in collector_results: + instance_id = collector_results["instance_id"] + relevant_monkey.aws_instance_id = instance_id + relevant_monkey.save() + logger.debug("Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py index d66019b39..9ddee70ce 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py @@ -5,10 +5,8 @@ from monkey_island.cc.models.monkey import Monkey logger = logging.getLogger(__name__) -def process_environment_telemetry(telemetry_json): - if "EnvironmentCollector" in telemetry_json["data"]["collectors"]: - env = telemetry_json["data"]["collectors"]["EnvironmentCollector"]["environment"] - relevant_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - relevant_monkey.environment = env - relevant_monkey.save() - logger.debug("Updated Monkey {} with env {}".format(str(relevant_monkey), env)) +def process_environment_telemetry(collector_results, monkey_guid): + relevant_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) + relevant_monkey.environment = collector_results + relevant_monkey.save() + logger.debug("Updated Monkey {} with env {}".format(str(relevant_monkey), collector_results)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py new file mode 100644 index 000000000..661034efb --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -0,0 +1,36 @@ +import logging + +from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry +from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry + +logger = logging.getLogger(__name__) + +SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSOR = { + "AwsCollector": process_aws_telemetry, + "EnvironmentCollector": process_environment_telemetry, +} + + +class SystemInfoTelemetryDispatcher(object): + def __init__(self, collector_to_parsing_function=None): + if collector_to_parsing_function is None: + collector_to_parsing_function = SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSOR + self.collector_to_parsing_function = collector_to_parsing_function + + def dispatch_to_relevant_collector(self, telemetry_json): + if "collectors" in telemetry_json["data"]: + self.send_each_result_to_relevant_processor(telemetry_json) + + def send_each_result_to_relevant_processor(self, telemetry_json): + relevant_monkey_guid = telemetry_json['monkey_guid'] + for collector_name, collector_results in telemetry_json["data"]["collectors"].items(): + if collector_name in self.collector_to_parsing_function: + # noinspection PyBroadException + try: + self.collector_to_parsing_function[collector_name](collector_results, relevant_monkey_guid) + except Exception as e: + logger.error( + "Error {} while processing {} system info telemetry".format(str(e), collector_name), + exc_info=True) + else: + logger.warning("Unknown system info collector name: {}".format(collector_name)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py new file mode 100644 index 000000000..db36cd5bb --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -0,0 +1,65 @@ +from importlib import reload +from unittest import mock +from unittest.mock import MagicMock + +import uuid + +from monkey_island.cc.models import Monkey +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ + SystemInfoTelemetryDispatcher +from monkey_island.cc.testing.IslandTestCase import IslandTestCase +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ + process_aws_telemetry + +TEST_SYS_INFO_TO_PROCESSING = { + "AwsCollector": process_aws_telemetry, +} + + +def do_nothing(x, y): + pass + + +class SystemInfoTelemetryDispatcherTest(IslandTestCase): + def test_dispatch_to_relevant_collector_bad_inputs(self): + self.fail_if_not_testing_env() + + dispatcher = SystemInfoTelemetryDispatcher(TEST_SYS_INFO_TO_PROCESSING) + + # Bad format telem JSONs - throws + bad_empty_telem_json = {} + self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collector, bad_empty_telem_json) + bad_no_data_telem_json = {"monkey_guid": "bla"} + self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collector, bad_no_data_telem_json) + bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}} + self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collector, bad_no_monkey_telem_json) + + # Telem JSON with no collectors - nothing gets dispatched + good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}} + good_telem_empty_collectors = {"monkey_guid": "bla", "data": {"bla": "bla", "collectors": {}}} + + dispatcher.dispatch_to_relevant_collector(good_telem_no_collectors) + dispatcher.dispatch_to_relevant_collector(good_telem_empty_collectors) + + def test_dispatch_to_relevant_collector(self): + self.fail_if_not_testing_env() + self.clean_monkey_db() + + a_monkey = Monkey(guid=str(uuid.uuid4())) + a_monkey.save() + + dispatcher = SystemInfoTelemetryDispatcher() + + # JSON with results - make sure functions are called + instance_id = "i-0bd2c14bd4c7d703f" + telem_json = { + "data": { + "collectors": { + "AwsCollector": {"instance_id": instance_id}, + } + }, + "monkey_guid": a_monkey.guid + } + dispatcher.dispatch_to_relevant_collector(telem_json) + + self.assertEquals(Monkey.get_single_monkey_by_guid(a_monkey.guid).aws_instance_id, instance_id) From 6815433a85d493778f085c908cbd79f1d4c58f0b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 19 Jan 2020 21:39:36 +0200 Subject: [PATCH 24/53] Using the dispatcher instead of naming the functions one by one + optimize imports --- .../telemetry/processing/system_info.py | 13 +++++------ .../system_info_telemetry_dispatcher.py | 2 +- .../test_system_info_telemetry_dispatcher.py | 22 ++++++------------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 915fa7a25..6734c6725 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -1,28 +1,27 @@ import logging -from monkey_island.cc.database import mongo +from monkey_island.cc.encryptor import encryptor from monkey_island.cc.models import Monkey from monkey_island.cc.services import mimikatz_utils -from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry -from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry +from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ + SystemInfoTelemetryDispatcher from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence from monkey_island.cc.services.wmi_handler import WMIHandler -from monkey_island.cc.encryptor import encryptor logger = logging.getLogger(__name__) def process_system_info_telemetry(telemetry_json): + dispatcher = SystemInfoTelemetryDispatcher() telemetry_processing_stages = [ process_ssh_info, process_credential_info, process_mimikatz_and_wmi_info, - process_aws_telemetry, update_db_with_new_hostname, test_antivirus_existence, - process_environment_telemetry + dispatcher.dispatch_to_relevant_collectors ] # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of failing the rest of diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index 661034efb..64fb146ab 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -17,7 +17,7 @@ class SystemInfoTelemetryDispatcher(object): collector_to_parsing_function = SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSOR self.collector_to_parsing_function = collector_to_parsing_function - def dispatch_to_relevant_collector(self, telemetry_json): + def dispatch_to_relevant_collectors(self, telemetry_json): if "collectors" in telemetry_json["data"]: self.send_each_result_to_relevant_processor(telemetry_json) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index db36cd5bb..4db5352c8 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -1,25 +1,17 @@ -from importlib import reload -from unittest import mock -from unittest.mock import MagicMock - import uuid from monkey_island.cc.models import Monkey from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ SystemInfoTelemetryDispatcher -from monkey_island.cc.testing.IslandTestCase import IslandTestCase from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ process_aws_telemetry +from monkey_island.cc.testing.IslandTestCase import IslandTestCase TEST_SYS_INFO_TO_PROCESSING = { "AwsCollector": process_aws_telemetry, } -def do_nothing(x, y): - pass - - class SystemInfoTelemetryDispatcherTest(IslandTestCase): def test_dispatch_to_relevant_collector_bad_inputs(self): self.fail_if_not_testing_env() @@ -28,18 +20,18 @@ class SystemInfoTelemetryDispatcherTest(IslandTestCase): # Bad format telem JSONs - throws bad_empty_telem_json = {} - self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collector, bad_empty_telem_json) + self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collectors, bad_empty_telem_json) bad_no_data_telem_json = {"monkey_guid": "bla"} - self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collector, bad_no_data_telem_json) + self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collectors, bad_no_data_telem_json) bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}} - self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collector, bad_no_monkey_telem_json) + self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collectors, bad_no_monkey_telem_json) # Telem JSON with no collectors - nothing gets dispatched good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}} good_telem_empty_collectors = {"monkey_guid": "bla", "data": {"bla": "bla", "collectors": {}}} - dispatcher.dispatch_to_relevant_collector(good_telem_no_collectors) - dispatcher.dispatch_to_relevant_collector(good_telem_empty_collectors) + dispatcher.dispatch_to_relevant_collectors(good_telem_no_collectors) + dispatcher.dispatch_to_relevant_collectors(good_telem_empty_collectors) def test_dispatch_to_relevant_collector(self): self.fail_if_not_testing_env() @@ -60,6 +52,6 @@ class SystemInfoTelemetryDispatcherTest(IslandTestCase): }, "monkey_guid": a_monkey.guid } - dispatcher.dispatch_to_relevant_collector(telem_json) + dispatcher.dispatch_to_relevant_collectors(telem_json) self.assertEquals(Monkey.get_single_monkey_by_guid(a_monkey.guid).aws_instance_id, instance_id) From 2a09d54ed12587eaf6ed6c69397a500d638b49ae Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 19 Jan 2020 21:45:31 +0200 Subject: [PATCH 25/53] Fixed dict bugs + server config --- monkey/monkey_island/cc/server_config.json | 2 +- .../cc/services/telemetry/processing/system_info.py | 3 ++- .../telemetry/processing/system_info_collectors/environment.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 7bf106194..420f1b303 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,4 +1,4 @@ { - "server_config": "testing", + "server_config": "standard", "deployment": "develop" } diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 6734c6725..d4368469e 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -117,4 +117,5 @@ def process_mimikatz_and_wmi_info(telemetry_json): def update_db_with_new_hostname(telemetry_json): - Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).set_hostname(telemetry_json['data']['hostname']) + if 'hostname' in telemetry_json['data']: + Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).set_hostname(telemetry_json['data']['hostname']) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py index 9ddee70ce..4c685a01b 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py @@ -7,6 +7,6 @@ logger = logging.getLogger(__name__) def process_environment_telemetry(collector_results, monkey_guid): relevant_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) - relevant_monkey.environment = collector_results + relevant_monkey.environment = collector_results["environment"] relevant_monkey.save() logger.debug("Updated Monkey {} with env {}".format(str(relevant_monkey), collector_results)) From f7c9cfe7a6f296108901462224aa8164cc23038f Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 20 Jan 2020 08:22:57 +0200 Subject: [PATCH 26/53] Update debian installation to use python3 --- monkey/monkey_island/deb-package/DEBIAN/postinst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/deb-package/DEBIAN/postinst b/monkey/monkey_island/deb-package/DEBIAN/postinst index 8efc19bbb..93053a76e 100644 --- a/monkey/monkey_island/deb-package/DEBIAN/postinst +++ b/monkey/monkey_island/deb-package/DEBIAN/postinst @@ -5,8 +5,8 @@ INSTALLATION_FOLDER=/var/monkey/monkey_island/installation PYTHON_FOLDER=/var/monkey/monkey_island/bin/python # Prepare python virtualenv -pip2 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER -virtualenv -p python2.7 ${PYTHON_FOLDER} +pip3 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER +virtualenv -p python3 ${PYTHON_FOLDER} # install pip requirements ${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER From 203d85a56e2b2d648c7923fbe8c409d72549961c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 20 Jan 2020 08:27:03 +0200 Subject: [PATCH 27/53] Update debian installation to require python3 dependencies --- monkey/monkey_island/deb-package/DEBIAN/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/deb-package/DEBIAN/control b/monkey/monkey_island/deb-package/DEBIAN/control index 2693afbd9..88723149c 100644 --- a/monkey/monkey_island/deb-package/DEBIAN/control +++ b/monkey/monkey_island/deb-package/DEBIAN/control @@ -5,4 +5,4 @@ Homepage: http://www.guardicore.com Priority: optional Version: 1.0 Description: Guardicore Infection Monkey Island installation package -Depends: openssl, python-pip, python-dev +Depends: openssl, python3-pip, python3-dev From d581b484cd4fa6110999d1ced99c680ae0f32c9e Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 20 Jan 2020 08:28:49 +0200 Subject: [PATCH 28/53] Update mongo installation to use python3 --- monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst b/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst index 76e57caa3..42dc2d5a2 100644 --- a/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst +++ b/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst @@ -5,8 +5,8 @@ INSTALLATION_FOLDER=/var/monkey/monkey_island/installation PYTHON_FOLDER=/var/monkey/monkey_island/bin/python # Prepare python virtualenv -pip2 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER -virtualenv -p python2.7 ${PYTHON_FOLDER} +pip3 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER +virtualenv -p python3 ${PYTHON_FOLDER} # install pip requirements ${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER From 9cdb87c59812a8270365556bb0be38c0f7004780 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 20 Jan 2020 08:30:43 +0200 Subject: [PATCH 29/53] Warning fixes in install.sh cause why not --- monkey/monkey_island/linux/install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/linux/install.sh b/monkey/monkey_island/linux/install.sh index d4ebfedbe..ab03e4798 100644 --- a/monkey/monkey_island/linux/install.sh +++ b/monkey/monkey_island/linux/install.sh @@ -1,7 +1,7 @@ #!/bin/bash -MACHINE_TYPE=`uname -m` -if [ ${MACHINE_TYPE} == 'x86_64' ]; then +MACHINE_TYPE=$(uname -m) +if [ "${MACHINE_TYPE}" == 'x86_64' ]; then # 64-bit stuff here ARCH=64 else @@ -11,4 +11,4 @@ fi MONKEY_FILE=monkey-linux-$ARCH cp -f /var/monkey/monkey_island/cc/binaries/$MONKEY_FILE /tmp -/tmp/$MONKEY_FILE m0nk3y $@ +/tmp/$MONKEY_FILE m0nk3y "$@" From 27405f7b62b2a58b8cc82e9d4479a17f3d0a002a Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 20 Jan 2020 08:31:10 +0200 Subject: [PATCH 30/53] Warning fixes in run.sh cause why not --- monkey/monkey_island/linux/run.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh index 54e1cdd65..2a5c45bbe 100644 --- a/monkey/monkey_island/linux/run.sh +++ b/monkey/monkey_island/linux/run.sh @@ -2,13 +2,13 @@ # Detecting command that calls python 3.7 python_cmd="" -if [[ `python --version 2>&1` == *"Python 3.7"* ]]; then +if [[ $(python --version 2>&1) == *"Python 3.7"* ]]; then python_cmd="python" fi -if [[ `python37 --version 2>&1` == *"Python 3.7"* ]]; then +if [[ $(python37 --version 2>&1) == *"Python 3.7"* ]]; then python_cmd="python37" fi -if [[ `python3.7 --version 2>&1` == *"Python 3.7"* ]]; then +if [[ $(python3.7 --version 2>&1) == *"Python 3.7"* ]]; then python_cmd="python3.7" fi From 40f7fd09ec845430c68811a4c1e25f64354f2bb9 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 20 Jan 2020 08:31:44 +0200 Subject: [PATCH 31/53] Warning fixes in monkey.sh cause why not --- monkey/monkey_island/linux/monkey.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/linux/monkey.sh b/monkey/monkey_island/linux/monkey.sh index d4ebfedbe..ab03e4798 100644 --- a/monkey/monkey_island/linux/monkey.sh +++ b/monkey/monkey_island/linux/monkey.sh @@ -1,7 +1,7 @@ #!/bin/bash -MACHINE_TYPE=`uname -m` -if [ ${MACHINE_TYPE} == 'x86_64' ]; then +MACHINE_TYPE=$(uname -m) +if [ "${MACHINE_TYPE}" == 'x86_64' ]; then # 64-bit stuff here ARCH=64 else @@ -11,4 +11,4 @@ fi MONKEY_FILE=monkey-linux-$ARCH cp -f /var/monkey/monkey_island/cc/binaries/$MONKEY_FILE /tmp -/tmp/$MONKEY_FILE m0nk3y $@ +/tmp/$MONKEY_FILE m0nk3y "$@" From ed138de8c45673e1016b4be8bfa88386fff6aa7d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 20 Jan 2020 11:57:19 +0200 Subject: [PATCH 32/53] Deleted the old (unused) aws collector --- .../system_info/aws_collector.py | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 monkey/infection_monkey/system_info/aws_collector.py diff --git a/monkey/infection_monkey/system_info/aws_collector.py b/monkey/infection_monkey/system_info/aws_collector.py deleted file mode 100644 index f39662d13..000000000 --- a/monkey/infection_monkey/system_info/aws_collector.py +++ /dev/null @@ -1,29 +0,0 @@ -import logging - -from common.cloud.aws.aws_instance import AwsInstance - -__author__ = 'itay.mizeretz' - -LOG = logging.getLogger(__name__) - - -class AwsCollector(object): - """ - Extract info from AWS machines - """ - - @staticmethod - def get_aws_info(): - LOG.info("Collecting AWS info") - aws = AwsInstance() - info = {} - if aws.is_instance(): - LOG.info("Machine is an AWS instance") - info = \ - { - 'instance_id': aws.get_instance_id() - } - else: - LOG.info("Machine is NOT an AWS instance") - - return info From 99785236720bfe4c4912832298573b24e8dccd6b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 20 Jan 2020 15:58:06 +0200 Subject: [PATCH 33/53] Fixed configuration bug - didn't use the same instance of WormConfiguration --- monkey/infection_monkey/config.py | 4 ++-- monkey/infection_monkey/system_info/system_info_collector.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index e1b1ece83..49aa38426 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -1,6 +1,6 @@ import hashlib -import os import json +import os import sys import uuid from abc import ABCMeta @@ -125,7 +125,7 @@ class Configuration(object): finger_classes = [] exploiter_classes = [] - system_info_collectors_classes = ["EnvironmentCollector", "AwsCollector"] + system_info_collectors_classes = [] # how many victims to look for in a single scan iteration victims_max_find = 100 diff --git a/monkey/infection_monkey/system_info/system_info_collector.py b/monkey/infection_monkey/system_info/system_info_collector.py index c511c1e86..8c0b6aa65 100644 --- a/monkey/infection_monkey/system_info/system_info_collector.py +++ b/monkey/infection_monkey/system_info/system_info_collector.py @@ -1,4 +1,4 @@ -from config import WormConfiguration +from infection_monkey.config import WormConfiguration from infection_monkey.utils.plugins.plugin import Plugin from abc import ABCMeta, abstractmethod From d584890dca5c9e8c0f6b99ba6013718e9ba262ce Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 20 Jan 2020 15:58:28 +0200 Subject: [PATCH 34/53] Added hostname collector + moved collector names to common file --- .../common/data/system_info_collectors_names.py | 3 +++ .../system_info/collectors/hostname_collector.py | 16 ++++++++++++++++ .../monkey_island/cc/services/config_schema.py | 13 +++++++++++-- .../services/telemetry/processing/system_info.py | 6 ------ .../system_info_collectors/hostname.py | 9 +++++++++ .../system_info_telemetry_dispatcher.py | 7 +++++-- 6 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 monkey/common/data/system_info_collectors_names.py create mode 100644 monkey/infection_monkey/system_info/collectors/hostname_collector.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py diff --git a/monkey/common/data/system_info_collectors_names.py b/monkey/common/data/system_info_collectors_names.py new file mode 100644 index 000000000..8bdf757c7 --- /dev/null +++ b/monkey/common/data/system_info_collectors_names.py @@ -0,0 +1,3 @@ +AWS_COLLECTOR = "AwsCollector" +HOSTNAME_COLLECTOR = "HostnameCollector" +ENVIRONMENT_COLLECTOR = "EnvironmentCollector" diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py new file mode 100644 index 000000000..92a522bf9 --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py @@ -0,0 +1,16 @@ +import logging +import socket + +from common.data.system_info_collectors_names import HOSTNAME_COLLECTOR +from infection_monkey.system_info.system_info_collector import SystemInfoCollector + + +logger = logging.getLogger(__name__) + + +class HostnameCollector(SystemInfoCollector): + def __init__(self): + super(HostnameCollector, self).__init__(name=HOSTNAME_COLLECTOR) + + def collect(self) -> dict: + return {"hostname": socket.getfqdn()} diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d5e015866..86e6225e0 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -108,7 +108,7 @@ SCHEMA = { "enum": [ "EnvironmentCollector" ], - "title": "Which Environment this machine is on (on prem/cloud)", + "title": "Collect which environment this machine is on (on prem/cloud)", "attack_techniques": [] }, { @@ -119,6 +119,14 @@ SCHEMA = { "title": "If on AWS, collect more information about the instance", "attack_techniques": [] }, + { + "type": "string", + "enum": [ + "HostnameCollector" + ], + "title": "Collect the machine's hostname", + "attack_techniques": [] + }, ], }, "post_breach_acts": { @@ -464,7 +472,8 @@ SCHEMA = { }, "default": [ "EnvironmentCollector", - "AwsCollector" + "AwsCollector", + "HostnameCollector" ], "description": "Determines which system information collectors will collect information." }, diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index d4368469e..c490b1d69 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -1,7 +1,6 @@ import logging from monkey_island.cc.encryptor import encryptor -from monkey_island.cc.models import Monkey from monkey_island.cc.services import mimikatz_utils from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService @@ -19,7 +18,6 @@ def process_system_info_telemetry(telemetry_json): process_ssh_info, process_credential_info, process_mimikatz_and_wmi_info, - update_db_with_new_hostname, test_antivirus_existence, dispatcher.dispatch_to_relevant_collectors ] @@ -115,7 +113,3 @@ def process_mimikatz_and_wmi_info(telemetry_json): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() - -def update_db_with_new_hostname(telemetry_json): - if 'hostname' in telemetry_json['data']: - Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).set_hostname(telemetry_json['data']['hostname']) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py new file mode 100644 index 000000000..e2de4519c --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py @@ -0,0 +1,9 @@ +import logging + +from monkey_island.cc.models.monkey import Monkey + +logger = logging.getLogger(__name__) + + +def process_hostname_telemetry(collector_results, monkey_guid): + Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results["hostname"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index 64fb146ab..6a3890491 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -1,13 +1,16 @@ import logging +from common.data.system_info_collectors_names import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry +from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry logger = logging.getLogger(__name__) SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSOR = { - "AwsCollector": process_aws_telemetry, - "EnvironmentCollector": process_environment_telemetry, + AWS_COLLECTOR: process_aws_telemetry, + ENVIRONMENT_COLLECTOR: process_environment_telemetry, + HOSTNAME_COLLECTOR: process_hostname_telemetry, } From 476c6e7a4b69552c4a612aed7f3be6319b680866 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 20 Jan 2020 16:43:25 +0200 Subject: [PATCH 35/53] Deleted hostname old collection, moved to collector --- monkey/infection_monkey/system_info/__init__.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index fc4aa1caf..66056828e 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -62,7 +62,6 @@ class InfoCollector(object): def get_info(self): # Collect all hardcoded - self.get_hostname() self.get_process_list() self.get_network_info() self.get_azure_info() @@ -70,14 +69,6 @@ class InfoCollector(object): # Collect all plugins SystemInfoCollectorsHandler().execute_all_configured() - def get_hostname(self): - """ - Adds the fully qualified computer hostname to the system information. - :return: None. Updates class information - """ - LOG.debug("Reading hostname") - self.info['hostname'] = socket.getfqdn() - def get_process_list(self): """ Adds process information from the host to the system information. From f8aff44e8b80331fc46b129231dbd086fc756308 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 20 Jan 2020 16:44:30 +0200 Subject: [PATCH 36/53] Changed dispatcher to use a list of processing functions to support multiple processing functions --- .../system_info_telemetry_dispatcher.py | 54 +++++++++++++------ .../test_system_info_telemetry_dispatcher.py | 14 ++--- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index 6a3890491..e20231d8b 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -1,39 +1,61 @@ import logging +import typing -from common.data.system_info_collectors_names import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR +from common.data.system_info_collectors_names import * from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry logger = logging.getLogger(__name__) -SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSOR = { - AWS_COLLECTOR: process_aws_telemetry, - ENVIRONMENT_COLLECTOR: process_environment_telemetry, - HOSTNAME_COLLECTOR: process_hostname_telemetry, +SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { + AWS_COLLECTOR: [process_aws_telemetry], + ENVIRONMENT_COLLECTOR: [process_environment_telemetry], + HOSTNAME_COLLECTOR: [process_hostname_telemetry], } class SystemInfoTelemetryDispatcher(object): - def __init__(self, collector_to_parsing_function=None): - if collector_to_parsing_function is None: - collector_to_parsing_function = SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSOR - self.collector_to_parsing_function = collector_to_parsing_function + def __init__(self, collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None): + """ + :param collector_to_parsing_functions: Map between collector names and a list of functions that process the output of + that collector. If `None` is supplied, uses the default one; This should be the normal flow, overriding the + collector->functions mapping is useful mostly for testing. + """ + if collector_to_parsing_functions is None: + collector_to_parsing_functions = SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS + self.collector_to_processing_functions = collector_to_parsing_functions - def dispatch_to_relevant_collectors(self, telemetry_json): + def dispatch_collector_results_to_relevant_processors(self, telemetry_json): + """ + If the telemetry has collectors' results, dispatches the results to the relevant processing functions. + :param telemetry_json: Telemetry sent from the Monkey + """ if "collectors" in telemetry_json["data"]: - self.send_each_result_to_relevant_processor(telemetry_json) + self.dispatch_each_result_to_relevant_processors(telemetry_json) - def send_each_result_to_relevant_processor(self, telemetry_json): + def dispatch_each_result_to_relevant_processors(self, telemetry_json): relevant_monkey_guid = telemetry_json['monkey_guid'] + for collector_name, collector_results in telemetry_json["data"]["collectors"].items(): - if collector_name in self.collector_to_parsing_function: + self.dispatch_result_of_single_collector_to_processing_functions( + collector_name, + collector_results, + relevant_monkey_guid) + + def dispatch_result_of_single_collector_to_processing_functions( + self, + collector_name, + collector_results, + relevant_monkey_guid): + if collector_name in self.collector_to_processing_functions: + for processing_function in self.collector_to_processing_functions[collector_name]: # noinspection PyBroadException try: - self.collector_to_parsing_function[collector_name](collector_results, relevant_monkey_guid) + processing_function(collector_results, relevant_monkey_guid) except Exception as e: logger.error( "Error {} while processing {} system info telemetry".format(str(e), collector_name), exc_info=True) - else: - logger.warning("Unknown system info collector name: {}".format(collector_name)) + else: + logger.warning("Unknown system info collector name: {}".format(collector_name)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index 4db5352c8..c5cc7aca2 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -8,7 +8,7 @@ from monkey_island.cc.services.telemetry.processing.system_info_collectors.syste from monkey_island.cc.testing.IslandTestCase import IslandTestCase TEST_SYS_INFO_TO_PROCESSING = { - "AwsCollector": process_aws_telemetry, + "AwsCollector": [process_aws_telemetry], } @@ -20,18 +20,18 @@ class SystemInfoTelemetryDispatcherTest(IslandTestCase): # Bad format telem JSONs - throws bad_empty_telem_json = {} - self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collectors, bad_empty_telem_json) + self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_empty_telem_json) bad_no_data_telem_json = {"monkey_guid": "bla"} - self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collectors, bad_no_data_telem_json) + self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_no_data_telem_json) bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}} - self.assertRaises(KeyError, dispatcher.dispatch_to_relevant_collectors, bad_no_monkey_telem_json) + self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_no_monkey_telem_json) # Telem JSON with no collectors - nothing gets dispatched good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}} good_telem_empty_collectors = {"monkey_guid": "bla", "data": {"bla": "bla", "collectors": {}}} - dispatcher.dispatch_to_relevant_collectors(good_telem_no_collectors) - dispatcher.dispatch_to_relevant_collectors(good_telem_empty_collectors) + dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_no_collectors) + dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_empty_collectors) def test_dispatch_to_relevant_collector(self): self.fail_if_not_testing_env() @@ -52,6 +52,6 @@ class SystemInfoTelemetryDispatcherTest(IslandTestCase): }, "monkey_guid": a_monkey.guid } - dispatcher.dispatch_to_relevant_collectors(telem_json) + dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) self.assertEquals(Monkey.get_single_monkey_by_guid(a_monkey.guid).aws_instance_id, instance_id) From 04b737057540435ba0d429588d77b2d610170fe4 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 20 Jan 2020 17:11:30 +0200 Subject: [PATCH 37/53] Fixed bug in report generation, added lock release for exceptions in report generation --- .../cc/services/reporting/report.py | 5 ++-- .../report_generation_synchronisation.py | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 6a44679a4..97e8fa4f1 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -386,10 +386,11 @@ class ReportService: @staticmethod def get_monkey_subnets(monkey_guid): network_info = mongo.db.telemetry.find_one( - {'telem_category': 'system_info', 'monkey_guid': monkey_guid}, + {'telem_category': 'system_info', + 'monkey_guid': monkey_guid}, {'data.network_info.networks': 1} ) - if network_info is None: + if network_info is None or not network_info["data"]: return [] return \ diff --git a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py index 9025ff68f..1a041bb3b 100644 --- a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py +++ b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py @@ -15,28 +15,34 @@ __regular_report_generating_lock = threading.Semaphore() def safe_generate_reports(): # Entering the critical section; Wait until report generation is available. __report_generating_lock.acquire() - report = safe_generate_regular_report() - attack_report = safe_generate_attack_report() - # Leaving the critical section. - __report_generating_lock.release() + try: + report = safe_generate_regular_report() + attack_report = safe_generate_attack_report() + finally: + # Leaving the critical section. + __report_generating_lock.release() return report, attack_report def safe_generate_regular_report(): # Local import to avoid circular imports from monkey_island.cc.services.reporting.report import ReportService - __regular_report_generating_lock.acquire() - report = ReportService.generate_report() - __regular_report_generating_lock.release() + try: + __regular_report_generating_lock.acquire() + report = ReportService.generate_report() + finally: + __regular_report_generating_lock.release() return report def safe_generate_attack_report(): # Local import to avoid circular imports from monkey_island.cc.services.attack.attack_report import AttackReportService - __attack_report_generating_lock.acquire() - attack_report = AttackReportService.generate_new_report() - __attack_report_generating_lock.release() + try: + __attack_report_generating_lock.acquire() + attack_report = AttackReportService.generate_new_report() + finally: + __attack_report_generating_lock.release() return attack_report From 2286571a72030d4497470c3c9611c3ee1e5e9a44 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 20 Jan 2020 17:12:12 +0200 Subject: [PATCH 38/53] Refactored process list collector --- .../data/system_info_collectors_names.py | 1 + .../infection_monkey/system_info/__init__.py | 32 ------------ .../collectors/process_list_collector.py | 50 +++++++++++++++++++ .../cc/services/config_schema.py | 23 ++++++--- .../telemetry/processing/system_info.py | 4 +- .../system_info_telemetry_dispatcher.py | 6 ++- .../zero_trust_tests/antivirus_existence.py | 50 +++++++++---------- 7 files changed, 98 insertions(+), 68 deletions(-) create mode 100644 monkey/infection_monkey/system_info/collectors/process_list_collector.py diff --git a/monkey/common/data/system_info_collectors_names.py b/monkey/common/data/system_info_collectors_names.py index 8bdf757c7..831bbe142 100644 --- a/monkey/common/data/system_info_collectors_names.py +++ b/monkey/common/data/system_info_collectors_names.py @@ -1,3 +1,4 @@ AWS_COLLECTOR = "AwsCollector" HOSTNAME_COLLECTOR = "HostnameCollector" ENVIRONMENT_COLLECTOR = "EnvironmentCollector" +PROCESS_LIST_COLLECTOR = "ProcessListCollector" diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index 66056828e..889b558a1 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -62,44 +62,12 @@ class InfoCollector(object): def get_info(self): # Collect all hardcoded - self.get_process_list() self.get_network_info() self.get_azure_info() # Collect all plugins SystemInfoCollectorsHandler().execute_all_configured() - def get_process_list(self): - """ - Adds process information from the host to the system information. - Currently lists process name, ID, parent ID, command line - and the full image path of each process. - :return: None. Updates class information - """ - LOG.debug("Reading process list") - processes = {} - for process in psutil.process_iter(): - try: - processes[process.pid] = {"name": process.name(), - "pid": process.pid, - "ppid": process.ppid(), - "cmdline": " ".join(process.cmdline()), - "full_image_path": process.exe(), - } - except (psutil.AccessDenied, WindowsError): - # we may be running as non root - # and some processes are impossible to acquire in Windows/Linux - # in this case we'll just add what we can - processes[process.pid] = {"name": "null", - "pid": process.pid, - "ppid": process.ppid(), - "cmdline": "ACCESS DENIED", - "full_image_path": "null", - } - continue - - self.info['process_list'] = processes - def get_network_info(self): """ Adds network information from the host to the system information. diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py new file mode 100644 index 000000000..93f836376 --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py @@ -0,0 +1,50 @@ +import logging +import psutil + +from common.data.system_info_collectors_names import PROCESS_LIST_COLLECTOR +from infection_monkey.system_info.system_info_collector import SystemInfoCollector + +logger = logging.getLogger(__name__) + +# Linux doesn't have WindowsError +try: + WindowsError +except NameError: + # noinspection PyShadowingBuiltins + WindowsError = psutil.AccessDenied + + +class ProcessListCollector(SystemInfoCollector): + def __init__(self): + super(ProcessListCollector, self).__init__(name=PROCESS_LIST_COLLECTOR) + + def collect(self) -> dict: + """ + Adds process information from the host to the system information. + Currently lists process name, ID, parent ID, command line + and the full image path of each process. + """ + logger.debug("Reading process list") + processes = {} + for process in psutil.process_iter(): + try: + processes[process.pid] = { + "name": process.name(), + "pid": process.pid, + "ppid": process.ppid(), + "cmdline": " ".join(process.cmdline()), + "full_image_path": process.exe(), + } + except (psutil.AccessDenied, WindowsError): + # we may be running as non root and some processes are impossible to acquire in Windows/Linux. + # In this case we'll just add what we know. + processes[process.pid] = { + "name": "null", + "pid": process.pid, + "ppid": process.ppid(), + "cmdline": "ACCESS DENIED", + "full_image_path": "null", + } + continue + + return {'process_list': processes} diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 86e6225e0..e7d599cc5 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -1,3 +1,5 @@ +from common.data.system_info_collectors_names import * + WARNING_SIGN = " \u26A0" SCHEMA = { @@ -106,7 +108,7 @@ SCHEMA = { { "type": "string", "enum": [ - "EnvironmentCollector" + ENVIRONMENT_COLLECTOR ], "title": "Collect which environment this machine is on (on prem/cloud)", "attack_techniques": [] @@ -114,7 +116,7 @@ SCHEMA = { { "type": "string", "enum": [ - "AwsCollector" + AWS_COLLECTOR ], "title": "If on AWS, collect more information about the instance", "attack_techniques": [] @@ -122,11 +124,19 @@ SCHEMA = { { "type": "string", "enum": [ - "HostnameCollector" + HOSTNAME_COLLECTOR ], "title": "Collect the machine's hostname", "attack_techniques": [] }, +{ + "type": "string", + "enum": [ + PROCESS_LIST_COLLECTOR + ], + "title": "Collect running processes on the machine", + "attack_techniques": [] + }, ], }, "post_breach_acts": { @@ -471,9 +481,10 @@ SCHEMA = { "$ref": "#/definitions/system_info_collectors_classes" }, "default": [ - "EnvironmentCollector", - "AwsCollector", - "HostnameCollector" + ENVIRONMENT_COLLECTOR, + AWS_COLLECTOR, + HOSTNAME_COLLECTOR, + PROCESS_LIST_COLLECTOR ], "description": "Determines which system information collectors will collect information." }, diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index c490b1d69..b923b6395 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -6,7 +6,6 @@ from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ SystemInfoTelemetryDispatcher -from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence from monkey_island.cc.services.wmi_handler import WMIHandler logger = logging.getLogger(__name__) @@ -18,8 +17,7 @@ def process_system_info_telemetry(telemetry_json): process_ssh_info, process_credential_info, process_mimikatz_and_wmi_info, - test_antivirus_existence, - dispatcher.dispatch_to_relevant_collectors + dispatcher.dispatch_collector_results_to_relevant_processors ] # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of failing the rest of diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index e20231d8b..d67979e8d 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -5,6 +5,7 @@ from common.data.system_info_collectors_names import * from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry +from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence logger = logging.getLogger(__name__) @@ -12,6 +13,7 @@ SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { AWS_COLLECTOR: [process_aws_telemetry], ENVIRONMENT_COLLECTOR: [process_environment_telemetry], HOSTNAME_COLLECTOR: [process_hostname_telemetry], + PROCESS_LIST_COLLECTOR: [test_antivirus_existence] } @@ -32,9 +34,9 @@ class SystemInfoTelemetryDispatcher(object): :param telemetry_json: Telemetry sent from the Monkey """ if "collectors" in telemetry_json["data"]: - self.dispatch_each_result_to_relevant_processors(telemetry_json) + self.dispatch_single_result_to_relevant_processor(telemetry_json) - def dispatch_each_result_to_relevant_processors(self, telemetry_json): + def dispatch_single_result_to_relevant_processor(self, telemetry_json): relevant_monkey_guid = telemetry_json['monkey_guid'] for collector_name, collector_results in telemetry_json["data"]["collectors"].items(): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index ddc1af65b..1916291e2 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -7,36 +7,36 @@ from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES -def test_antivirus_existence(telemetry_json): - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - if 'process_list' in telemetry_json['data']: - process_list_event = Event.create_event( - title="Process list", - message="Monkey on {} scanned the process list".format(current_monkey.hostname), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL) - events = [process_list_event] +def test_antivirus_existence(process_list_json, monkey_guid): + current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) - av_processes = filter_av_processes(telemetry_json) + process_list_event = Event.create_event( + title="Process list", + message="Monkey on {} scanned the process list".format(current_monkey.hostname), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL) + events = [process_list_event] - for process in av_processes: - events.append(Event.create_event( - title="Found AV process", - message="The process '{}' was recognized as an Anti Virus process. Process " - "details: {}".format(process[1]['name'], json.dumps(process[1])), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL - )) + av_processes = filter_av_processes(process_list_json["process_list"]) - if len(av_processes) > 0: - test_status = zero_trust_consts.STATUS_PASSED - else: - test_status = zero_trust_consts.STATUS_FAILED - AggregateFinding.create_or_add_to_existing( - test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events - ) + for process in av_processes: + events.append(Event.create_event( + title="Found AV process", + message="The process '{}' was recognized as an Anti Virus process. Process " + "details: {}".format(process[1]['name'], json.dumps(process[1])), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL + )) + + if len(av_processes) > 0: + test_status = zero_trust_consts.STATUS_PASSED + else: + test_status = zero_trust_consts.STATUS_FAILED + AggregateFinding.create_or_add_to_existing( + test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events + ) -def filter_av_processes(telemetry_json): - all_processes = list(telemetry_json['data']['process_list'].items()) +def filter_av_processes(process_list): + all_processes = list(process_list.items()) av_processes = [] for process in all_processes: process_name = process[1]['name'] From 18232424fad06ad41ec91e2d4459b762660e9f74 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 20 Jan 2020 20:54:41 +0200 Subject: [PATCH 39/53] Bugfix, spec should not check kernel version using platform.architecture but rather the python interpreter version. This handles cases where we run 32b python on 64b machines --- monkey/infection_monkey/monkey.spec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 9c5fa9a18..3a376a73b 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -1,5 +1,6 @@ # -*- mode: python -*- import os +import sys import platform @@ -48,7 +49,7 @@ def is_windows(): def is_32_bit(): - return platform.architecture()[0] == "32bit" + return sys.maxsize <= 2**32 def get_bin_folder(): From ab330219d5ec322773abf9021b42a6fa4e4b0aa7 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 21 Jan 2020 15:27:41 +0200 Subject: [PATCH 40/53] Using new style `super` calles --- .../infection_monkey/system_info/collectors/aws_collector.py | 3 ++- .../system_info/collectors/environment_collector.py | 3 ++- .../system_info/collectors/hostname_collector.py | 2 +- .../system_info/collectors/process_list_collector.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py index 71f9e58c1..68d125279 100644 --- a/monkey/infection_monkey/system_info/collectors/aws_collector.py +++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py @@ -1,6 +1,7 @@ import logging from common.cloud.aws.aws_instance import AwsInstance +from common.data.system_info_collectors_names import AWS_COLLECTOR from infection_monkey.system_info.system_info_collector import SystemInfoCollector @@ -12,7 +13,7 @@ class AwsCollector(SystemInfoCollector): Extract info from AWS machines. """ def __init__(self): - super(AwsCollector, self).__init__(name="AwsCollector") + super().__init__(name=AWS_COLLECTOR) def collect(self) -> dict: logger.info("Collecting AWS info") diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index ac5a5433d..5567477d3 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -1,5 +1,6 @@ from common.cloud.all_instances import get_all_cloud_instances from common.cloud.environment_names import ON_PREMISE +from common.data.system_info_collectors_names import ENVIRONMENT_COLLECTOR from infection_monkey.system_info.system_info_collector import SystemInfoCollector @@ -13,7 +14,7 @@ def get_monkey_environment() -> str: class EnvironmentCollector(SystemInfoCollector): def __init__(self): - super(EnvironmentCollector, self).__init__(name="EnvironmentCollector") + super().__init__(name=ENVIRONMENT_COLLECTOR) def collect(self) -> dict: return {"environment": get_monkey_environment()} diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py index 92a522bf9..21d03aac7 100644 --- a/monkey/infection_monkey/system_info/collectors/hostname_collector.py +++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) class HostnameCollector(SystemInfoCollector): def __init__(self): - super(HostnameCollector, self).__init__(name=HOSTNAME_COLLECTOR) + super().__init__(name=HOSTNAME_COLLECTOR) def collect(self) -> dict: return {"hostname": socket.getfqdn()} diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py index 93f836376..c0610cc74 100644 --- a/monkey/infection_monkey/system_info/collectors/process_list_collector.py +++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py @@ -16,7 +16,7 @@ except NameError: class ProcessListCollector(SystemInfoCollector): def __init__(self): - super(ProcessListCollector, self).__init__(name=PROCESS_LIST_COLLECTOR) + super().__init__(name=PROCESS_LIST_COLLECTOR) def collect(self) -> dict: """ From db5c0f478626f3dc9c3ce1cd5db52d4f44f52f34 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 21 Jan 2020 15:29:46 +0200 Subject: [PATCH 41/53] Changed get_monkey_env logic to return as soon as a results is found and added docs --- .../system_info/collectors/environment_collector.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 5567477d3..12a58b6d5 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -5,11 +5,15 @@ from infection_monkey.system_info.system_info_collector import SystemInfoCollect def get_monkey_environment() -> str: - env = ON_PREMISE + """ + Get the Monkey's running environment. + :return: One of the cloud providers if on cloud; otherwise, assumes "on premise". + """ for instance in get_all_cloud_instances(): if instance.is_instance(): - env = instance.get_cloud_provider_name() - return env + return instance.get_cloud_provider_name() + + return ON_PREMISE class EnvironmentCollector(SystemInfoCollector): From 6f289915fc404bfc68d1276eaedb8a8f3d443b90 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 21 Jan 2020 16:19:10 +0200 Subject: [PATCH 42/53] Made envs an enum --- monkey/common/cloud/aws/aws_instance.py | 6 ++--- monkey/common/cloud/azure/azure_instance.py | 6 ++--- monkey/common/cloud/environment_names.py | 23 +++++++++++-------- monkey/common/cloud/gcp/gcp_instance.py | 6 ++--- monkey/common/cloud/instance.py | 5 +++- .../collectors/environment_collector.py | 6 ++--- monkey/monkey_island/cc/models/monkey.py | 2 +- 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index c77220d17..03c5482ba 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -6,7 +6,7 @@ import logging __author__ = 'itay.mizeretz' -from common.cloud.environment_names import AWS +from common.cloud.environment_names import Environment from common.cloud.instance import CloudInstance AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254" @@ -23,8 +23,8 @@ class AwsInstance(CloudInstance): def is_instance(self): return self.instance_id is not None - def get_cloud_provider_name(self) -> str: - return AWS + def get_cloud_provider_name(self) -> Environment: + return Environment.AWS def __init__(self): self.instance_id = None diff --git a/monkey/common/cloud/azure/azure_instance.py b/monkey/common/cloud/azure/azure_instance.py index f0d5a8044..ec910fb98 100644 --- a/monkey/common/cloud/azure/azure_instance.py +++ b/monkey/common/cloud/azure/azure_instance.py @@ -1,7 +1,7 @@ import logging import requests -from common.cloud.environment_names import AZURE +from common.cloud.environment_names import Environment from common.cloud.instance import CloudInstance LATEST_AZURE_METADATA_API_VERSION = "2019-04-30" @@ -18,8 +18,8 @@ class AzureInstance(CloudInstance): def is_instance(self): return self.on_azure - def get_cloud_provider_name(self) -> str: - return AZURE + def get_cloud_provider_name(self) -> Environment: + return Environment.AZURE def __init__(self): """ diff --git a/monkey/common/cloud/environment_names.py b/monkey/common/cloud/environment_names.py index 0c8655753..945d438ce 100644 --- a/monkey/common/cloud/environment_names.py +++ b/monkey/common/cloud/environment_names.py @@ -1,12 +1,15 @@ -# When adding a new environment to this file, make sure to add it to ALL_ENV_NAMES as well! +from enum import Enum -UNKNOWN = "Unknown" -ON_PREMISE = "On Premise" -AZURE = "Azure" -AWS = "AWS" -GCP = "GCP" -ALIBABA = "Alibaba Cloud" -IBM = "IBM Cloud" -DigitalOcean = "Digital Ocean" -ALL_ENV_NAMES = [UNKNOWN, ON_PREMISE, AZURE, AWS, GCP, ALIBABA, IBM, DigitalOcean] +class Environment(Enum): + UNKNOWN = "Unknown" + ON_PREMISE = "On Premise" + AZURE = "Azure" + AWS = "AWS" + GCP = "GCP" + ALIBABA = "Alibaba Cloud" + IBM = "IBM Cloud" + DigitalOcean = "Digital Ocean" + + +ALL_ENVIRONMENTS_NAMES = [x.value for x in Environment] diff --git a/monkey/common/cloud/gcp/gcp_instance.py b/monkey/common/cloud/gcp/gcp_instance.py index 26738db43..184465bf5 100644 --- a/monkey/common/cloud/gcp/gcp_instance.py +++ b/monkey/common/cloud/gcp/gcp_instance.py @@ -1,7 +1,7 @@ import logging import requests -from common.cloud.environment_names import GCP +from common.cloud.environment_names import Environment from common.cloud.instance import CloudInstance logger = logging.getLogger(__name__) @@ -17,8 +17,8 @@ class GcpInstance(CloudInstance): def is_instance(self): return self.on_gcp - def get_cloud_provider_name(self) -> str: - return GCP + def get_cloud_provider_name(self) -> Environment: + return Environment.GCP def __init__(self): self.on_gcp = False diff --git a/monkey/common/cloud/instance.py b/monkey/common/cloud/instance.py index 61ab4c734..abe0c7910 100644 --- a/monkey/common/cloud/instance.py +++ b/monkey/common/cloud/instance.py @@ -1,3 +1,6 @@ +from common.cloud.environment_names import Environment + + class CloudInstance(object): """ This is an abstract class which represents a cloud instance. @@ -7,5 +10,5 @@ class CloudInstance(object): def is_instance(self) -> bool: raise NotImplementedError() - def get_cloud_provider_name(self) -> str: + def get_cloud_provider_name(self) -> Environment: raise NotImplementedError() diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 12a58b6d5..7a953fce9 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -1,10 +1,10 @@ from common.cloud.all_instances import get_all_cloud_instances -from common.cloud.environment_names import ON_PREMISE +from common.cloud.environment_names import Environment from common.data.system_info_collectors_names import ENVIRONMENT_COLLECTOR from infection_monkey.system_info.system_info_collector import SystemInfoCollector -def get_monkey_environment() -> str: +def get_monkey_environment() -> Environment: """ Get the Monkey's running environment. :return: One of the cloud providers if on cloud; otherwise, assumes "on premise". @@ -13,7 +13,7 @@ def get_monkey_environment() -> str: if instance.is_instance(): return instance.get_cloud_provider_name() - return ON_PREMISE + return Environment.ON_PREMISE class EnvironmentCollector(SystemInfoCollector): diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 65dda0850..da6d880b4 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -45,7 +45,7 @@ class Monkey(Document): command_control_channel = EmbeddedDocumentField(CommandControlChannel) # Environment related fields - environment = StringField(default=environment_names.UNKNOWN, choices=environment_names.ALL_ENV_NAMES) + environment = StringField(default=environment_names.Environment.UNKNOWN, choices=environment_names.ALL_ENVIRONMENTS_NAMES) aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS # instance. See https://github.com/guardicore/monkey/issues/426. From a51c1c0629ec18efd442a707d4e0e61142ae8fbc Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 21 Jan 2020 16:31:06 +0200 Subject: [PATCH 43/53] Used star imports by accident :-1: --- monkey/monkey_island/cc/services/config_schema.py | 3 ++- .../system_info_collectors/system_info_telemetry_dispatcher.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index e7d599cc5..3d0220ee2 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -1,4 +1,5 @@ -from common.data.system_info_collectors_names import * +from common.data.system_info_collectors_names \ + import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR WARNING_SIGN = " \u26A0" diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index d67979e8d..b5f2d24ea 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -1,7 +1,8 @@ import logging import typing -from common.data.system_info_collectors_names import * +from common.data.system_info_collectors_names \ + import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry From 0f73567ad4b2b86d8265848374866cd5a3a58bc5 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 21 Jan 2020 16:32:41 +0200 Subject: [PATCH 44/53] Line too long fix --- monkey/monkey_island/cc/services/remote_run_aws.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 0ba6fa4ef..dfaa0e327 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -130,7 +130,8 @@ class RemoteRunAwsService: return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \ r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \ r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \ - r";Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " + r";Start-Process -FilePath '.\\monkey.exe' " \ + r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " @staticmethod def _get_run_monkey_cmd_line(is_linux, is_64bit, island_ip): From 5548cf2acfee76dcd120b9494d9c0d049b4a200a Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 21 Jan 2020 16:53:00 +0200 Subject: [PATCH 45/53] Fixed UTs - enum change wasn't 100% correct --- .../system_info/collectors/environment_collector.py | 6 +++--- monkey/monkey_island/cc/models/monkey.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 7a953fce9..100d23175 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -4,16 +4,16 @@ from common.data.system_info_collectors_names import ENVIRONMENT_COLLECTOR from infection_monkey.system_info.system_info_collector import SystemInfoCollector -def get_monkey_environment() -> Environment: +def get_monkey_environment() -> str: """ Get the Monkey's running environment. :return: One of the cloud providers if on cloud; otherwise, assumes "on premise". """ for instance in get_all_cloud_instances(): if instance.is_instance(): - return instance.get_cloud_provider_name() + return instance.get_cloud_provider_name().value - return Environment.ON_PREMISE + return Environment.ON_PREMISE.value class EnvironmentCollector(SystemInfoCollector): diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index da6d880b4..f658a3d06 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -45,7 +45,8 @@ class Monkey(Document): command_control_channel = EmbeddedDocumentField(CommandControlChannel) # Environment related fields - environment = StringField(default=environment_names.Environment.UNKNOWN, choices=environment_names.ALL_ENVIRONMENTS_NAMES) + environment = StringField(default=environment_names.Environment.UNKNOWN.value, + choices=environment_names.ALL_ENVIRONMENTS_NAMES) aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS # instance. See https://github.com/guardicore/monkey/issues/426. From ef69058f91c2ce1b4d7054df764b623baa5f986d Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 21 Jan 2020 17:31:57 +0200 Subject: [PATCH 46/53] Add pyinstaller hook for systeminfo plugins --- .../hook-infection_monkey.system_info.collectors.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py new file mode 100644 index 000000000..97cf81bfb --- /dev/null +++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py @@ -0,0 +1,6 @@ +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +# Import all actions as modules +hiddenimports = collect_submodules('infection_monkey.system_info.collectors') +# Add action files that we enumerate +datas = (collect_data_files('infection_monkey.system_info.collectors', include_py_files=True)) From f999e7221d04414b37743bdcd49bac16c86af5fc Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 21 Jan 2020 17:38:24 +0200 Subject: [PATCH 47/53] Fully explicit imports all the way --- monkey/infection_monkey/system_info/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index 889b558a1..d9107e7bf 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -8,7 +8,7 @@ from enum import IntEnum from infection_monkey.network.info import get_host_subnets from infection_monkey.system_info.azure_cred_collector import AzureCollector from infection_monkey.system_info.netstat_collector import NetstatCollector -from system_info.system_info_collectors_handler import SystemInfoCollectorsHandler +from infection_monkey.system_info.system_info_collectors_handler import SystemInfoCollectorsHandler LOG = logging.getLogger(__name__) From 61a41b407b8a4f330ba93f81aec4bc8e383cdf3f Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 23 Jan 2020 15:50:56 +0200 Subject: [PATCH 48/53] Change filename to account for bitness. --- monkey/infection_monkey/monkey.spec | 9 ++++++++- monkey/infection_monkey/readme.md | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 3a376a73b..2157b697c 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -94,7 +94,14 @@ def get_traceroute_binaries(): def get_monkey_filename(): - return 'monkey.exe' if is_windows() else 'monkey' + name = 'monkey' + if is_32_bit(): + name = name+"32" + else: + name = name+"64" + if is_windows(): + name = name+".exe" + return name def get_exe_strip(): diff --git a/monkey/infection_monkey/readme.md b/monkey/infection_monkey/readme.md index d6e17acdb..8522767dd 100644 --- a/monkey/infection_monkey/readme.md +++ b/monkey/infection_monkey/readme.md @@ -34,7 +34,7 @@ The monkey is composed of three separate parts. 6. To build the final exe: - `cd monkey\infection_monkey` - `build_windows.bat` - - `output is placed under dist\monkey.exe` + - output is placed under `dist\monkey32.exe` or `dist\monkey64.exe` depending on your version of Python ## Linux @@ -66,7 +66,7 @@ Tested on Ubuntu 16.04. - `chmod +x build_linux.sh` - `./build_linux.sh` - output is placed under dist/monkey + output is placed under `dist/monkey32` or `dist/monkey64` depending on your version of python ### Sambacry From 2e8582cd410f68909d5bd6cb8d4c1b82a5a56a20 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 28 Jan 2020 19:11:15 +0200 Subject: [PATCH 49/53] fixes notification bug that routes to wrong route --- monkey/monkey_island/cc/ui/src/components/Main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 2c54822a5..b4325e55c 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -30,7 +30,7 @@ let infectionMonkeyImage = require('../images/infection-monkey.svg'); let guardicoreLogoImage = require('../images/guardicore-logo.png'); let notificationIcon = require('../images/notification-logo-512x512.png'); -const reportZeroTrustRoute = '/report/zero_trust'; +const reportZeroTrustRoute = '/report/zeroTrust'; class AppComponent extends AuthComponent { updateStatus = () => { From e51f2e927f3819e8f4b36514fe56685f4e4bb64d Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 9 Feb 2020 10:52:31 +0200 Subject: [PATCH 50/53] Change homepage to be InfectionMonkey related --- monkey/monkey_island/deb-package/DEBIAN/control | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/deb-package/DEBIAN/control b/monkey/monkey_island/deb-package/DEBIAN/control index 88723149c..16aed4614 100644 --- a/monkey/monkey_island/deb-package/DEBIAN/control +++ b/monkey/monkey_island/deb-package/DEBIAN/control @@ -1,7 +1,7 @@ Package: gc-monkey-island Architecture: amd64 -Maintainer: Guardicore -Homepage: http://www.guardicore.com +Maintainer: Guardicore +Homepage: http://www.infectionmonkey.com Priority: optional Version: 1.0 Description: Guardicore Infection Monkey Island installation package From 290eddf2497d0e1c61431eb18910a16c73d2444b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 9 Feb 2020 12:47:30 +0200 Subject: [PATCH 51/53] Fix to be HTTPS --- monkey/monkey_island/deb-package/DEBIAN/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/deb-package/DEBIAN/control b/monkey/monkey_island/deb-package/DEBIAN/control index 16aed4614..a47371005 100644 --- a/monkey/monkey_island/deb-package/DEBIAN/control +++ b/monkey/monkey_island/deb-package/DEBIAN/control @@ -1,7 +1,7 @@ Package: gc-monkey-island Architecture: amd64 Maintainer: Guardicore -Homepage: http://www.infectionmonkey.com +Homepage: https://www.infectionmonkey.com Priority: optional Version: 1.0 Description: Guardicore Infection Monkey Island installation package From ad9450a77e1a49ebb53327cab4722ccb4cab6a76 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 9 Feb 2020 18:40:44 +0200 Subject: [PATCH 52/53] Fix names to match island naming conventions --- monkey/infection_monkey/monkey.spec | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 2157b697c..004cffa4c 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -94,7 +94,11 @@ def get_traceroute_binaries(): def get_monkey_filename(): - name = 'monkey' + name = 'monkey-' + if is_windows(): + name = name+"windows-" + else: + name = name+"linux-" if is_32_bit(): name = name+"32" else: From 6f745d0e50f5527e19d0962af2756f4e02c68635 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 13 Feb 2020 01:07:19 +0530 Subject: [PATCH 53/53] Fix typo in deployment script for linux --- deployment_scripts/deploy_linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 834d811a7..67eeaa957 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -46,7 +46,7 @@ if ! exists git; then fi if ! exists wget; then - echo 'Your system does have wget, please install and re-run this script' + echo 'Your system does not have wget, please install and re-run this script' exit 1 fi