Module: Msf::Exploit::Remote::HTTP::NagiosXi::Login

Includes:
URIs
Included in:
Msf::Exploit::Remote::HTTP::NagiosXi, Install
Defined in:
lib/msf/core/exploit/remote/http/nagios_xi/login.rb

Constant Summary collapse

AUTH_RESULTS =
{
  :connection_failed => 1,
  :unexpected_error => 2,
  :not_nagios_application => 3,
  :not_fully_installed => 4,
  :failed_to_handle_license_agreement => 5,
  :failed_to_extract_tokens => 6,
  :unable_to_obtain_version => 7
}

Instance Method Summary collapse

Methods included from URIs

#nagios_xi_backend_url, #nagios_xi_install_url, #nagios_xi_login_url

Instance Method Details

#authenticate(username, password, finish_install, handle_full_install = true, handle_license = true, handle_nsp = false) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 60

def authenticate(username, password, finish_install, handle_full_install = true, handle_license = true, handle_nsp = false)
  , res_array = (username, password, finish_install)
  case 
  when 1..3
    return , res_array[0]
  when 4 # Nagios XI is not fully installed
    return , 'Nagios is not fully installed.' unless handle_full_install
    , res_array = install_full_nagios(username, password, finish_install)
    return , res_array unless ( == 0)
  when 5
    return , 'The Nagios license has not been signed.' unless handle_license
    , res_array = handle_unsigned_license(res_array, username, password, finish_install)
    return , res_array unless ( == 0)
  end

  print_good('Successfully authenticated to Nagios XI.')

  return 6, "Failed to extract auth cookies and nsp string" unless res_array.length == 2

  auth_cookies = extract_auth_cookies(res_array) # if we are here, this cannot be nil since the mixin checks for that already
  return 6, 'Failed to extract authentication cookies' unless auth_cookies.present?

  nsp = get_nsp(res_array[0]) if handle_nsp
  return 6, 'Failed to extract nsp string' if handle_nsp && !nsp.present?

  # Obtain the Nagios XI version
  nagios_version = nagios_xi_version(res_array[0])
  if nagios_version.nil?
    return 7, 'Unable to obtain the Nagios XI version from the dashboard'
  end

  print_status("Target is Nagios XI with version #{nagios_version}.")

  # Versions of NagiosXI pre-5.2 have different formats (5r1.0, 2014r2.7, 2012r2.8b, etc.) that Rex cannot handle,
  # so we set pre-5.2 versions to 1.0.0 for easier Rex comparison because the module only works on post-5.2 versions.

  if /#{Msf::Exploit::Remote::HTTP::NagiosXi::PRE_5_2_VERSION_REGEX}/.match(nagios_version) || nagios_version == '5r1.0'
    nagios_version = '1.0.0'
  end
  version = Rex::Version.new(nagios_version)

  return 0, 'Successfully authenticated and retrieved NagiosXI Version.', auth_cookies, version, nsp
end

#clean_cookies(pre_auth_cookies, auth_cookies) ⇒ String?

Compares cookies obtained before and after authentication and modifies the latter to remove cookies that may cause session timeouts

Parameters:

  • pre_auth_cookies (String)

    Cookies obtained before authenticating to Nagios XI

  • auth_cookies (String)

    Cookies obtained while authenticating to Nagios XI

Returns:

  • (String, nil)

    String containing the cookies required for authentication, stripped off unnecessary/unwanted cookies, nil if one or both of the parameters passed to this method are nil



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 189

def clean_cookies(pre_auth_cookies, auth_cookies)
  if pre_auth_cookies.nil? || auth_cookies.nil?
    return nil
  end
  # Nagios XI may sometimes send the cookie `nagiosxi=deleted;` as part of the cookies after authentication.
  # This was observed when trying to access Nagios XI 5.3.0 when the license agreement had not been accepted yet.
  # The `nagiosxi=deleted;` cookie should be filtered out, since it may break authentication.
  if auth_cookies.include?('nagiosxi=deleted;')
    auth_cookies = auth_cookies.gsub('nagiosxi=deleted;', '').strip
  end

  # Remove duplicate cookies, necessary to make the next check work in case multiple
  # identical cookies were set (as observed on older Nagios versions)
  auth_cookies_array = auth_cookies.split(' ')
  auth_cookies_array.uniq!
  auth_cookies = auth_cookies_array.join(' ')

  # For newer Nagios XI versions, we need to remove the pre_auth cookies from the auth_cookies
  # string, otherwise the session will timeout. However, older Nagios XI versions use a single cookie
  # which has the same name both before and after authentication.
  unless pre_auth_cookies == auth_cookies
    if auth_cookies.include?(pre_auth_cookies)
      auth_cookies = auth_cookies.gsub(pre_auth_cookies, '').strip
    end
  end

  auth_cookies
end

#extract_auth_cookies(res_array) ⇒ String?

Grabs the auth_cookies value from an HTTP response and validate using regex

Parameters:

Returns:

  • (String, nil)

    auth_cookies value, nil if not found



265
266
267
268
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 265

def extract_auth_cookies(res_array)
  auth_cookies = res_array[1]
  return auth_cookies if auth_cookies && /nagiosxi=[a-z0-9]+;/.match(auth_cookies)
end

#get_nsp(res) ⇒ String?

Grabs the nsp_str value from an HTTP response using regex

Parameters:

Returns:

  • (String, nil)

    nsp_str value, nil if not found



274
275
276
277
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 274

def get_nsp(res)
  res = res.body if res.kind_of?(Rex::Proto::Http::Response)
  nsp = res.scan(/nsp_str = "([a-z0-9]+)/)&.flatten&.first
end

#handle_unsigned_license(res_array, username, password, finish_install) ⇒ Object

Returns a status code an a error message on failure. On success returns the status code and an array so we can update the login_result and res_array variables appropriately.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 19

def handle_unsigned_license(res_array, username, password, finish_install)
  auth_cookies, nsp = res_array
  sign_license_result = sign_license_agreement(auth_cookies, nsp)
  if sign_license_result
    return 5, 'Failed to sign license agreement'
  end

  print_status('License agreement signed. The module will wait for 5 seconds and retry the login.')
  sleep 5
  , res_array = (username, password, finish_install)
  case 
  when 1..4 # An error occurred, propagate the error message
    return , res_array[0]
  when 5 # The Nagios XI license agreement still has not been signed
    return 5, 'Failed to sign the license agreement.'
  end

  return , res_array
end

#install_full_nagios(username, password, finish_install) ⇒ Object

Returns a status code an a error message on failure. On success returns the status code and an array so we can update the login_result and res_array variables appropriately.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 42

def install_full_nagios(username, password, finish_install)
  install_result = install_nagios_xi(password)
  if install_result
    return install_result[0], install_result[1]
  end

  , res_array = (username, password, finish_install)
  case 
  when 1..4 # An error occurred, propagate the error message
    return , res_array[0]
  when 5 # The license agreement still needs to be signed
    , res_array = handle_unsigned_license(res_array, username, password, finish_install)
    return , res_array
  end

  return , res_array
end

#login_after_install_or_license(username, password, finish_install) ⇒ Array

Performs an authentication attempt. If the server does not return a response, a second attempt is made after a delay of 5 seconds

Parameters:

  • username (String)

    Username required for authentication

  • password (String)

    Password required for authentication

  • finish_install (Boolean)

    Boolean indicating if the module should finish installing Nagios XI on target hosts if the installation hasn't been completed or the license agreement is not signed

Returns:

  • (Array)

    Array containing the HTTP response body and session cookies if the Nagios XI backend was accessed successfully, otherwise Array containing an error code and an error message



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 286

def (username, password, finish_install)
  # After installing Nagios XI or signing the license agreement, we sometimes don't receive a server response.
  # This loop ensures that at least 2 login attempts are performed if this happens, as the second one usually works.
  second_attempt = false
  while true
    , error_message = (username, password, finish_install)

    break unless error_message == ['Connection failed']

    if second_attempt
      print_warning('The server is still not responding. If you wait a few seconds and rerun the module, it might still work.')
      break
    else
      print_warning('No response received from the server. This can happen after installing Nagios XI or signing the license agreement')
      print_status('The module will wait for 5 seconds and retry.')
      second_attempt = true
      sleep 5
    end
  end

  return [, error_message]

end

#nagios_xi_login(user, pass, finish_install) ⇒ Array

performs a Nagios XI login

Parameters:

  • user (String)

    Username

  • pass (String)

    Password

  • finish_install (Boolean)

    Boolean indicating if the module should finish installing Nagios XI on target hosts if the installation hasn't been completed or the license agreement is not signed

Returns:

  • (Array)

    Array containing a response code and an Array containing one of four possibilities: the HTTP response body and session cookies if the Nagios XI backend was accessed successfully; nil if Nagios XI hasn't been fully installed; cookies and the nsp token if the license agreement is not signed; otherwise an error message



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 110

def (user, pass, finish_install)
  print_status('Attempting to authenticate to Nagios XI...')

  # Visit the login page in order to obtain the cookies and the `nsp_str` token required for authentication
   = send_request_cgi({
    'method' => 'GET',
    'uri' => ,
  })

  unless 
    return [1, ['Connection failed']]
  end

  unless .code == 200 && .body.include?('>Nagios XI<')
    # Check if we are perhaps dealing with a Nagios XI app that hasn't been fully installed yet
    unless .code == 302 && .body.include?('>Nagios XI<') && .headers['Location'].end_with?('/install.php')
      return [3, ['Target is not a Nagios XI application']]
    end

    print_warning('The target seems to be a Nagios XI application that has not been fully installed yet.')
    unless finish_install
      return [2, ['You can let the module complete the installation by setting `FINISH_INSTALL` to true.']]
    end

    return [4, [nil]]
  end

  # Grab the necessary tokens required for authentication
  nsp = get_nsp()
  if nsp.nil?
    return [2, ['Unable to obtain the value of the `nsp_str` token required for authentication']]
  end

  pre_auth_cookies = .get_cookies
  if pre_auth_cookies.blank?
    return [2, ['Unable to obtain the cookies required for authentication']]
  end

  # authenticate
   = send_request_cgi({
    'method' => 'POST',
    'uri' => ,
    'cookie' => pre_auth_cookies,
    'vars_post' => {
      'nsp' => nsp,
      'pageopt' => 'login',
      'username' => user,
      'password' => pass
    }
  })

  unless 
    return [1, ['Connection failed']]
  end

  unless .code == 302 && .headers['Location'] == 'index.php'
    return [2, ['Received unexpected reply while trying to authenticate. Please check your credentials.']]
  end

  # Grab the cookies
  auth_cookies = .get_cookies

  if auth_cookies.blank?
    return [2, ['Unable to obtain the cookies required for authentication']]
  end

  # Make sure we only use the cookies we need, otherwise we may encounter a session timeout
  auth_cookies = clean_cookies(pre_auth_cookies, auth_cookies)

  # Try to visit the dashboard
  visit_nagios_dashboard(auth_cookies, finish_install)
end

#visit_nagios_dashboard(auth_cookies, finish_install) ⇒ Array

Performs an HTTP GET request to the Nagios XI backend to verify if authentication succeeded

Parameters:

  • auth_cookies (String)

    Cookies required for authentication

  • finish_install (Boolean)

    Boolean indicating if the module should finish installing Nagios XI on target hosts if the installation hasn't been completed or the license agreement is not signed

Returns:

  • (Array)

    Array containing a result code and an Array containing one of three possibilities: an HTTP response body and cookies if the Nagios XI backend was accessed successfully; cookies and the nsp token if the license agreement is not signed; otherwise an error message



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 223

def visit_nagios_dashboard(auth_cookies, finish_install)
  # Visit the index page to verify we successfully authenticated
  res_index = send_request_cgi({
    'method' => 'GET',
    'uri' => nagios_xi_backend_url,
    'cookie' => auth_cookies
  })

  unless res_index
    return [1, ['Connection failed']]
  end

  unless res_index.code == 200 && res_index.body.include?('>Home Dashboard<')
    # Check if we need to sign the license agreement
    unless res_index.code == 302 && res_index.headers['Location'].end_with?('login.php?showlicense')
      return [2, ['Received unexpected reply while trying to access the NagiosXI home dashboard after authenticating.']]
    end

    print_warning('The Nagios XI license agreement has not yet been signed on the target.')
    unless finish_install
      return [2, ['You can let the module sign the Nagios XI license agreement by setting `FINISH_INSTALL` to true.']]
    end

    nsp = get_nsp(res_index)
    if nsp.nil?
      return [2, ['Failed to obtain the nsp token required for signing the license agreement.']]
    end

    return [5, [auth_cookies, nsp]]
  end

  # Return the HTTP response body and the authentication cookies.
  # The response body can be used to obtain the version number.
  # The cookies can be used by exploit modules to send authenticated requests.
  [0, [res_index.body, auth_cookies]]

end