Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/metasploit/framework/login_scanner/opnsense.rb
27907 views
1
require 'metasploit/framework/login_scanner/http'
2
3
module Metasploit
4
module Framework
5
module LoginScanner
6
7
# This is the LoginScanner class for dealing with Deciso B.V. OPNSense instances.
8
# It is responsible for taking a single target, and a list of credentials
9
# and attempting them. It then saves the results.
10
class OPNSense < HTTP
11
12
# Retrieve the wanted cookie value by name from the HTTP response.
13
#
14
# @param [Rex::Proto::Http::Response] response The response from which to extract cookie values
15
# @param [String] wanted_cookie_name The cookie name for which to get the value
16
def get_cookie_value(response, wanted_cookie_name)
17
response.get_cookies.split('; ').find { |cookie| cookie.start_with?(wanted_cookie_name) }.split('=').last
18
end
19
20
# Checks if the target is OPNSense. The login module should call this.
21
#
22
# @return [Boolean, String] FalseClass if target is OPNSense, otherwise String
23
def check_setup
24
request_params = {
25
'method' => 'GET',
26
'uri' => normalize_uri(@uri.to_s)
27
}
28
res = send_request(request_params)
29
30
if res && res.code == 200 && res.body&.include?('Login | OPNsense')
31
return false
32
end
33
34
"Unable to locate \"Login | OPNsense\" in body. (Is this really OPNSense?)"
35
end
36
37
# Query the magic value and cookies from the OPNSense login page.
38
#
39
# @return [Hash<Symbol, Object>] A hash of the status and error or result.
40
def query_magic_value_and_cookies
41
request_params = {
42
'method' => 'GET',
43
'uri' => normalize_uri(@uri.to_s)
44
}
45
46
res = send_request(request_params)
47
48
if res.nil?
49
return { status: :failure, error: 'Did not receive response to a GET request' }
50
end
51
52
if res.code != 200
53
return { status: :failure, error: "Unexpected return code from GET request - #{res.code}" }
54
end
55
56
if res.body.nil?
57
return { status: :failure, error: 'Received an empty body from GET request' }
58
end
59
60
# The magic name and value are hidden on the login form, so we extract them using get_html_document
61
form_input = res.get_html_document&.at('input')
62
63
if form_input.nil? || form_input['type'] != 'hidden'
64
return { status: :failure, error: 'Could not find hidden magic field in the login form.' }
65
end
66
67
magic_value = { name: form_input['name'], value: form_input['value'] }
68
cookies = "PHPSESSID=#{get_cookie_value(res, 'PHPSESSID')}; cookie_test=#{get_cookie_value(res, 'cookie_test')}"
69
{ status: :success, result: { magic_value: magic_value, cookies: cookies } }
70
end
71
72
# Each individual login needs their own magic name and value.
73
# This magic value comes from the login form received in response to a GET request to the login page.
74
# Each login attempt also requires specific cookies to be set, otherwise an error is returned.
75
#
76
# @param username Username
77
# @param password Password
78
# @param magic_value A hash containing the magic_value name and value
79
# @param cookies A cookie string
80
def try_login(username, password, magic_value, cookies)
81
request_params =
82
{
83
'method' => 'POST',
84
'uri' => normalize_uri(@uri.to_s),
85
'cookie' => cookies,
86
'vars_post' => {
87
magic_value[:name] => magic_value[:value],
88
'usernamefld' => username,
89
'passwordfld' => password,
90
'login' => '1'
91
}
92
}
93
94
{ status: :success, result: send_request(request_params) }
95
end
96
97
def attempt_login(credential)
98
result_options = {
99
credential: credential,
100
host: @host,
101
port: @port,
102
protocol: 'tcp',
103
service_name: 'opnsense'
104
}
105
106
# Each login needs its own magic name and value
107
magic_value_and_cookies = query_magic_value_and_cookies
108
109
if magic_value_and_cookies[:status] != :success
110
result_options.merge!(status: ::Metasploit::Model::Login::Status::UNTRIED, proof: magic_value_and_cookies[:error])
111
return Result.new(result_options)
112
end
113
114
login_result = try_login(credential.public, credential.private, magic_value_and_cookies[:result][:magic_value], magic_value_and_cookies[:result][:cookies])
115
116
if login_result[:result].nil?
117
result_options.merge!(status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to OPNSense')
118
return Result.new(result_options)
119
end
120
121
# 200 is incorrect result
122
if login_result[:result].code == 200 || login_result[:result].body.include?('Username or Password incorrect')
123
result_options.merge!(status: ::Metasploit::Model::Login::Status::INCORRECT, proof: 'Username or Password incorrect')
124
return Result.new(result_options)
125
end
126
127
login_status = login_result[:result].code == 302 ? ::Metasploit::Model::Login::Status::SUCCESSFUL : ::Metasploit::Model::Login::Status::INCORRECT
128
result_options.merge!(status: login_status, proof: login_result[:result])
129
Result.new(result_options)
130
131
rescue ::Rex::ConnectionError => _e
132
result_options.merge!(status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to OPNSense')
133
return Result.new(result_options)
134
end
135
end
136
end
137
end
138
end
139
140