Skip to content

Commit b6e750d

Browse files
committed
Nessus auxiliary scanner for updated REST API
1 parent 38a77c9 commit b6e750d

File tree

2 files changed

+197
-87
lines changed

2 files changed

+197
-87
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
2+
require 'metasploit/framework/login_scanner/http'
3+
4+
module Metasploit
5+
module Framework
6+
module LoginScanner
7+
8+
class Nessus < HTTP
9+
10+
DEFAULT_PORT = 8834
11+
PRIVATE_TYPES = [ :password ]
12+
LIKELY_SERVICE_NAMES = [ 'nessus' ]
13+
LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name
14+
15+
16+
# Checks if the target is a Tenable Nessus server.
17+
#
18+
# @return [Boolean] TrueClass if target is Nessus server, otherwise FalseClass
19+
def check_setup
20+
login_uri = "/server/properties"
21+
res = send_request({'uri'=> login_uri})
22+
if res && res.body.include?('Nessus')
23+
return true
24+
end
25+
26+
false
27+
end
28+
29+
# Actually doing the login. Called by #attempt_login
30+
#
31+
# @param username [String] The username to try
32+
# @param password [String] The password to try
33+
# @return [String]
34+
# * :status [Metasploit::Model::Login::Status]
35+
# * :proof [String] the HTTP response body
36+
def get_login_state(username, password)
37+
# Prep the data needed for login
38+
#protocol = ssl ? 'https' : 'http'
39+
#peer = "#{host}:#{port}"
40+
login_uri = "#{uri}"
41+
42+
res = send_request({
43+
'uri' => login_uri,
44+
'method' => 'POST',
45+
'vars_post' => {
46+
'username' => username,
47+
'password' => password
48+
}
49+
})
50+
51+
unless res
52+
return {:status => LOGIN_STATUS::UNABLE_TO_CONNECT, :proof => res.to_s}
53+
end
54+
if res.code == 200 && res.body =~ /token/
55+
return {:status => LOGIN_STATUS::SUCCESSFUL, :proof => res.body.to_s}
56+
end
57+
58+
{:status => LOGIN_STATUS::INCORRECT, :proof => res.to_s}
59+
end
60+
61+
62+
# Attempts to login to Nessus.
63+
#
64+
# @param credential [Metasploit::Framework::Credential] The credential object
65+
# @return [Result] A Result object indicating success or failure
66+
def attempt_login(credential)
67+
result_opts = {
68+
credential: credential,
69+
status: Metasploit::Model::Login::Status::INCORRECT,
70+
proof: nil,
71+
host: host,
72+
port: port,
73+
protocol: 'tcp'
74+
}
75+
76+
begin
77+
result_opts.merge!(get_login_state(credential.public, credential.private))
78+
rescue ::Rex::ConnectionError => e
79+
# Something went wrong during login. 'e' knows what's up.
80+
result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message)
81+
end
82+
83+
Result.new(result_opts)
84+
end
85+
86+
end
87+
end
88+
end
89+
end
90+
Lines changed: 107 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,140 @@
1-
##
2-
# nessus_xmlrpc_login.rb
3-
##
4-
51
##
62
# This module requires Metasploit: http://metasploit.com/download
73
# Current source: https://github.com/rapid7/metasploit-framework
84
##
95

106
require 'msf/core'
7+
require 'metasploit/framework/login_scanner/nessus'
8+
require 'metasploit/framework/credential_collection'
119

1210
class Metasploit3 < Msf::Auxiliary
1311

1412
include Msf::Exploit::Remote::HttpClient
15-
include Msf::Auxiliary::Report
1613
include Msf::Auxiliary::AuthBrute
14+
include Msf::Auxiliary::Report
1715
include Msf::Auxiliary::Scanner
1816

19-
def initialize
20-
super(
21-
'Name' => 'Nessus XMLRPC Interface Login Utility',
17+
def initialize(info={})
18+
super(update_info(info,
19+
'Name' => 'Nessus RPC Interface Login Utility',
2220
'Description' => %q{
23-
This module simply attempts to login to a Nessus XMLRPC interface using a
24-
specific user/pass.
21+
This module will attempt to authenticate to a Nessus server RPC interface.
2522
},
26-
'Author' => [ 'Vlatko Kosturjak <kost[at]linux.hr>' ],
23+
'Author' => [ 'void_in' ],
2724
'License' => MSF_LICENSE
28-
)
29-
25+
))
3026
register_options(
3127
[
3228
Opt::RPORT(8834),
33-
OptString.new('URI', [true, "URI for Nessus XMLRPC login. Default is /login", "/login"]),
34-
OptBool.new('BLANK_PASSWORDS', [false, "Try blank passwords for all users", false])
29+
OptString.new('TARGETURI', [ true, 'The path to the Nessus server login API', '/session']),
30+
OptBool.new('SSL', [true, 'Negotiate SSL for outgoing connections', true]),
31+
OptEnum.new('SSLVersion', [false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']])
3532
], self.class)
36-
37-
register_advanced_options(
38-
[
39-
OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true])
40-
], self.class)
4133
end
4234

43-
def run_host(ip)
44-
begin
45-
res = send_request_cgi({
46-
'uri' => datastore['URI'],
47-
'method' => 'GET'
48-
}, 25)
49-
http_fingerprint({ :response => res })
50-
rescue ::Rex::ConnectionError => e
51-
vprint_error("#{datastore['URI']} - #{e}")
52-
return
53-
end
5435

55-
if not res
56-
vprint_error("#{datastore['URI']} - No response")
57-
return
58-
end
59-
if res.code != 403
60-
vprint_error("Authorization not requested")
61-
return
62-
end
36+
# Initializes CredentialCollection and Nessus Scanner
37+
def init(ip)
38+
@cred_collection = Metasploit::Framework::CredentialCollection.new(
39+
blank_passwords: datastore['BLANK_PASSWORDS'],
40+
pass_file: datastore['PASS_FILE'],
41+
password: datastore['PASSWORD'],
42+
user_file: datastore['USER_FILE'],
43+
userpass_file: datastore['USERPASS_FILE'],
44+
username: datastore['USERNAME'],
45+
user_as_pass: datastore['USER_AS_PASS']
46+
)
6347

64-
each_user_pass do |user, pass|
65-
do_login(user, pass)
66-
end
48+
@scanner = Metasploit::Framework::LoginScanner::Nessus.new(
49+
host: ip,
50+
port: datastore['RPORT'],
51+
uri: datastore['TARGETURI'],
52+
cred_details: @cred_collection,
53+
stop_on_success: datastore['STOP_ON_SUCCESS'],
54+
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
55+
connection_timeout: 5
56+
)
57+
@scanner.ssl = datastore['SSL']
58+
@scanner.ssl_version = datastore['SSLVERSION']
6759
end
6860

69-
def do_login(user='nessus', pass='nessus')
70-
vprint_status("Trying username:'#{user}' with password:'#{pass}'")
71-
headers = {}
72-
73-
begin
74-
res = send_request_cgi({
75-
'encode' => true,
76-
'uri' => datastore['URI'],
77-
'method' => 'POST',
78-
'headers' => headers,
79-
'vars_post' => {
80-
'login' => user,
81-
'password' => pass
82-
}
83-
}, 25)
84-
85-
rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
86-
print_error("HTTP Connection Failed, Aborting")
87-
return :abort
88-
end
8961

90-
if not res
91-
print_error("Connection timed out, Aborting")
92-
return :abort
93-
end
62+
# Reports a good login credential
63+
def do_report(ip, port, result)
64+
service_data = {
65+
address: ip,
66+
port: port,
67+
service_name: 'http',
68+
protocol: 'tcp',
69+
workspace_id: myworkspace_id
70+
}
71+
72+
credential_data = {
73+
module_fullname: self.fullname,
74+
origin_type: :service,
75+
private_data: result.credential.private,
76+
private_type: :password,
77+
username: result.credential.public,
78+
}.merge(service_data)
79+
80+
login_data = {
81+
core: create_credential(credential_data),
82+
last_attempted_at: DateTime.now,
83+
status: result.status,
84+
proof: result.proof
85+
}.merge(service_data)
86+
87+
create_credential_login(login_data)
88+
end
9489

95-
if res.code != 200
96-
vprint_error("FAILED LOGIN. '#{user}' : '#{pass}'")
97-
return :skip_pass
98-
end
9990

100-
if res.code == 200
101-
if res.body =~ /<status>OK<\/status>/
102-
print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'")
103-
104-
report_hash = {
105-
:host => datastore['RHOST'],
106-
:port => datastore['RPORT'],
107-
:sname => 'nessus-xmlrpc',
108-
:user => user,
109-
:pass => pass,
110-
:active => true,
111-
:type => 'password'}
112-
113-
report_auth_info(report_hash)
114-
return :next_user
91+
# Attempts to login
92+
def bruteforce(ip)
93+
@scanner.scan! do |result|
94+
case result.status
95+
when Metasploit::Model::Login::Status::SUCCESSFUL
96+
print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'"
97+
do_report(ip, rport, result)
98+
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
99+
vprint_brute :level => :verror, :ip => ip, :msg => result.proof
100+
invalidate_login(
101+
address: ip,
102+
port: rport,
103+
protocol: 'tcp',
104+
public: result.credential.public,
105+
private: result.credential.private,
106+
realm_key: result.credential.realm_key,
107+
realm_value: result.credential.realm,
108+
status: result.status,
109+
proof: result.proof
110+
)
111+
when Metasploit::Model::Login::Status::INCORRECT
112+
vprint_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'"
113+
invalidate_login(
114+
address: ip,
115+
port: rport,
116+
protocol: 'tcp',
117+
public: result.credential.public,
118+
private: result.credential.private,
119+
realm_key: result.credential.realm_key,
120+
realm_value: result.credential.realm,
121+
status: result.status,
122+
proof: result.proof
123+
)
115124
end
116125
end
117-
vprint_error("FAILED LOGIN. '#{user}' : '#{pass}'")
118-
return :skip_pass
119126
end
127+
128+
129+
# Start here
130+
def run_host(ip)
131+
init(ip)
132+
unless @scanner.check_setup
133+
print_brute :level => :error, :ip => ip, :msg => 'Target is not a Tenable Nessus server'
134+
return
135+
end
136+
137+
bruteforce(ip)
138+
end
139+
120140
end

0 commit comments

Comments
 (0)